From dcc85807159964b98d588053a79f3adb3604e535 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 21 Jan 2025 09:02:59 -0800 Subject: [PATCH 001/378] Add MapTable to chainweb-storage --- libs/chainweb-storage/chainweb-storage.cabal | 1 + .../src/Chainweb/Storage/Table/Map.hs | 74 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 libs/chainweb-storage/src/Chainweb/Storage/Table/Map.hs diff --git a/libs/chainweb-storage/chainweb-storage.cabal b/libs/chainweb-storage/chainweb-storage.cabal index 1614be447e..725a0f8efa 100644 --- a/libs/chainweb-storage/chainweb-storage.cabal +++ b/libs/chainweb-storage/chainweb-storage.cabal @@ -31,6 +31,7 @@ library Chainweb.Storage.Table Chainweb.Storage.Table.Forgetful Chainweb.Storage.Table.HashMap + Chainweb.Storage.Table.Map Chainweb.Storage.Table.RocksDB Chainweb.Storage.DedupStore build-depends: diff --git a/libs/chainweb-storage/src/Chainweb/Storage/Table/Map.hs b/libs/chainweb-storage/src/Chainweb/Storage/Table/Map.hs new file mode 100644 index 0000000000..047a037962 --- /dev/null +++ b/libs/chainweb-storage/src/Chainweb/Storage/Table/Map.hs @@ -0,0 +1,74 @@ +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE FunctionalDependencies #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} + +-- | +-- Module: Chainweb.Storage.Table.HashMap +-- Copyright: Copyright © 2019 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- Description: +-- +-- A thread-safe in-memory 'Table' implementation based on 'M.HashMap'. +-- +module Chainweb.Storage.Table.Map +( MapTable +, emptyTable +, toList +, size +, deleteLt +, deleteLe +) where + +import Chainweb.Storage.Table +import Control.Concurrent.STM.TVar +import Control.Monad ((<$!>)) +import Control.Monad.STM +import Data.Map.Strict qualified as M + +-- | An 'IsTable' implementation that is base on 'M.HashMap'. +-- +newtype MapTable k v = MapTable (TVar (M.Map k v)) + +instance (Ord k, Eq k) => ReadableTable (MapTable k v) k v where + tableLookup (MapTable var) k = M.lookup k <$!> readTVarIO var + tableMember (MapTable var) k = + M.member k <$> readTVarIO var + +instance (Ord k, Eq k) => Table (MapTable k v) k v where + tableInsert (MapTable var) k v = + atomically $ modifyTVar' var (M.insert k v) + tableDelete (MapTable var) k = + atomically $ modifyTVar' var (M.delete k) + +-- | Create new empty CAS +-- +emptyTable :: (Ord k, Eq k) => IO (MapTable k v) +emptyTable = MapTable <$> newTVarIO mempty + +-- | Return all entries of CAS as List +-- +toList :: MapTable k v -> IO [v] +toList (MapTable var) = M.elems <$!> readTVarIO var + +-- | The number of items in the CAS +-- +size :: MapTable k v -> IO Int +size (MapTable var) = M.size <$!> readTVarIO var + +-- | Delete all keys that are strictly smaller than the given key +-- +deleteLt :: Ord k => MapTable k v -> k -> IO () +deleteLt (MapTable var) k = + atomically $ modifyTVar' var $ M.dropWhileAntitone (< k) + +-- | Delete all keys that are smaller or equal than the given key +-- +deleteLe :: Ord k => MapTable k v -> k -> IO () +deleteLe (MapTable var) k = + atomically $ modifyTVar' var $ M.dropWhileAntitone (<= k) From c487129232bdf706356e1aacb34a9c2e852a183b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 21 Jan 2025 09:07:15 -0800 Subject: [PATCH 002/378] Add .dockerignore file --- .dockerignore | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..2a57ffc73f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,27 @@ +# .git +**.hi +**.o +*.eventlog +*.eventlog +*.hp +*.prof +*.ps +*.tags +.DS_Store +.ghc.environment* +.stack-work +Dockerfile +Tmp* +chainweb-node +chainweb-node* +chainweb-node-prof* +chainweb-tests* +ci-logs +ci-logs* +dist +dist-newstyle +hie.log +out* +prof-data +tags +tmp* From 14a0ed63c46bbd5a652736f648b6e720a356ea93 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 15 Jan 2025 18:49:12 -0800 Subject: [PATCH 003/378] Fix docker file syntax --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8b75392a1b..bbeae40e32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -159,7 +159,7 @@ EOF # ############################################################################ # # Setup Context -FROM chainweb-build as chainweb-build-ctx +FROM chainweb-build AS chainweb-build-ctx ARG TARGETPLATFORM # RUN git clone --filter=tree:0 https://github.com/kadena-io/chainweb-node # WORKDIR /chainweb/chainweb-node @@ -178,7 +178,7 @@ RUN sh /tools/check-git-clean.sh || touch /tools/wip # ############################################################################ # # Build Dependencies -FROM chainweb-build-ctx as chainweb-build-dependencies +FROM chainweb-build-ctx AS chainweb-build-dependencies ARG TARGETPLATFORM ARG PROJECT_NAME ENV GIT_DISCOVERY_ACROSS_FILESYSTEM=1 From fdb4325bc16da91af41a9172b2e27e43d62f09d5 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 9 Jan 2025 12:41:03 -0800 Subject: [PATCH 004/378] Dockerfile: add mpfr to chainweb runtime image --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index bbeae40e32..cd98307210 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,6 +73,7 @@ RUN < Date: Fri, 10 Jan 2025 00:34:47 -0800 Subject: [PATCH 005/378] Dockerfile: run cabal update before freeze --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index cd98307210..1c5ae6e11e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -184,6 +184,9 @@ FROM chainweb-build-ctx AS chainweb-build-dependencies ARG TARGETPLATFORM ARG PROJECT_NAME ENV GIT_DISCOVERY_ACROSS_FILESYSTEM=1 +RUN --mount=type=cache,target=/root/.cabal,id=${TARGETPLATFORM} \ + --mount=type=cache,target=./dist-newstyle,id=${PROJECT_NAME}-${TARGETPLATFORM},sharing=locked \ + cabal update RUN --mount=type=cache,target=/root/.cabal,id=${TARGETPLATFORM} \ --mount=type=cache,target=./dist-newstyle,id=${PROJECT_NAME}-${TARGETPLATFORM},sharing=locked \ [ -f cabal.project.freeze ] || cabal --enable-tests --enable-benchmarks freeze From 12879668f1ad5f7f5b5cd81a71787ed8375ee77c Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 18 Jan 2025 23:22:18 -0800 Subject: [PATCH 006/378] Update pin of ethereum package --- cabal.project | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cabal.project b/cabal.project index 18e096ff4a..bd5d0b91b6 100644 --- a/cabal.project +++ b/cabal.project @@ -119,7 +119,7 @@ source-repository-package source-repository-package type: git location: https://github.com/kadena-io/kadena-ethereum-bridge.git - tag: 3837c4c81f1beaffc1d52375e61576366d49170a + tag: 7e0adf227b5a9c335c3c3840aba36252356c7225 --sha256: 1knhscph2g3saz0pjd1d5a32mr281msapccfrillgd2qk4pj7xjc source-repository-package From 1d2a3ebe69396c00b93873d2fd473e07b714f09c Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Jan 2025 00:25:08 -0800 Subject: [PATCH 007/378] JSON encoding for Ranked types --- src/Chainweb/Ranked.hs | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Ranked.hs b/src/Chainweb/Ranked.hs index c6713e78dd..aa408209d6 100644 --- a/src/Chainweb/Ranked.hs +++ b/src/Chainweb/Ranked.hs @@ -1,6 +1,13 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE DataKinds #-} -- | -- Module: Chainweb.Ranked @@ -19,17 +26,22 @@ module Chainweb.Ranked ( Ranked(..) , encodeRanked , decodeRanked +, JsonRanked(..) ) where import Chainweb.BlockHeight +import Chainweb.Utils import Chainweb.Utils.Serialization import Control.DeepSeq import Control.Monad +import Data.Aeson import Data.Hashable +import Data.Typeable (Proxy(..), Typeable, typeRep) -import GHC.Generics +import GHC.Generics (Generic) +import GHC.TypeLits -- -------------------------------------------------------------------------- -- -- BlockHeight Ranked Data @@ -61,3 +73,29 @@ decodeRanked decodeA = Ranked <*> decodeA {-# INLINE decodeRanked #-} +-- -------------------------------------------------------------------------- -- + +-- | JSON Encoding for Ranked Types. +-- +-- The first type parameter is the JSON key for the value. +-- +newtype JsonRanked (s :: Symbol) a = JsonRanked { _jsonRanked :: Ranked a } + +instance (ToJSON a, KnownSymbol s) => ToJSON (JsonRanked s a) where + toJSON (JsonRanked r) = object + [ "height" .= _rankedHeight r + , symbolText @s .= _ranked r + ] + toEncoding (JsonRanked r) = pairs $ mconcat + [ "height" .= _rankedHeight r + , symbolText @s .= _ranked r + ] + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance (KnownSymbol s, Typeable a, FromJSON a) => FromJSON (JsonRanked s a) where + parseJSON = withObject ("Ranked " <> show (typeRep (Proxy @a))) $ \o -> + fmap JsonRanked $ Ranked + <$> o .: "height" + <*> o .: symbolText @s + {-# INLINE parseJSON #-} From b95987f3402488298727765220b4ba8ea3040f57 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 18 Jan 2025 23:33:15 -0800 Subject: [PATCH 008/378] Chainweb.Cut: reorder imports --- src/Chainweb/Cut.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/Cut.hs b/src/Chainweb/Cut.hs index b75c22d4ed..3001aa06dc 100644 --- a/src/Chainweb/Cut.hs +++ b/src/Chainweb/Cut.hs @@ -102,6 +102,7 @@ import Control.Exception hiding (catch) import Control.Lens hiding ((:>), (.=)) import Control.Monad hiding (join) import Control.Monad.Catch +import Control.Monad.State.Strict import Data.Bifoldable import Data.Foldable @@ -140,7 +141,6 @@ import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB -import Control.Monad.State.Strict -- -------------------------------------------------------------------------- -- -- Cut From d65231c73eab9db6fc228e319f207580d0058cfa Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 7 Jan 2025 01:04:03 -0800 Subject: [PATCH 009/378] generalize someChainIdVal to accept HasChainId constraint --- src/Chainweb/ChainId.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index ba41648a5e..8e9effb3f0 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -228,9 +228,9 @@ instance KnownSymbol n => KnownChainIdSymbol ('ChainIdT n) where type ChainIdSymbol ('ChainIdT n) = n chainIdSymbolVal _ = T.pack $ symbolVal (Proxy @n) -someChainIdVal :: ChainId -> SomeChainIdT -someChainIdVal cid = case someSymbolVal (T.unpack (toText cid)) of - (SomeSymbol (Proxy :: Proxy v)) -> SomeChainIdT (Proxy @('ChainIdT v)) +someChainIdVal :: HasChainId c => c -> SomeChainIdT +someChainIdVal cid = case someSymbolVal (T.unpack (toText (_chainId cid))) of + (SomeSymbol (Proxy :: Proxy cid)) -> SomeChainIdT (Proxy @('ChainIdT cid)) -- -------------------------------------------------------------------------- -- -- Singletons From ba4eefa17adff1f7d3d82b7252b54d3a5d494ba1 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 7 Jan 2025 01:04:55 -0800 Subject: [PATCH 010/378] generalize someChainwebVersionVal to accept HashChainwebVersion constraint --- src/Chainweb/Version.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 3678b88d6c..289beb7ea9 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -595,8 +595,8 @@ instance (KnownSymbol n) => KnownChainwebVersionSymbol ('ChainwebVersionT n) whe type ChainwebVersionSymbol ('ChainwebVersionT n) = n chainwebVersionSymbolVal _ = T.pack $ symbolVal (Proxy @n) -someChainwebVersionVal :: ChainwebVersion -> SomeChainwebVersionT -someChainwebVersionVal v = someChainwebVersionVal' (_versionName v) +someChainwebVersionVal :: HasChainwebVersion v => v -> SomeChainwebVersionT +someChainwebVersionVal v = someChainwebVersionVal' (_versionName (_chainwebVersion v)) someChainwebVersionVal' :: ChainwebVersionName -> SomeChainwebVersionT someChainwebVersionVal' v = case someSymbolVal (show v) of From 5eabba39ce5e42bf12cc3e2b4b2e02cfb1b435db Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 15 Jan 2025 18:56:46 -0800 Subject: [PATCH 011/378] Add HasTextRepresentation instance for URI --- src/Chainweb/Utils.hs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index 1515b9ccd5..e4c6c6d55e 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -315,6 +315,7 @@ import System.Timeout qualified as Timeout import Text.Printf (printf) import Text.Read (readEither) +import Network.URI -- -------------------------------------------------------------------------- -- -- SI unit prefixes @@ -605,6 +606,16 @@ instance HasTextRepresentation UTCTime where fmt = iso8601DateTimeFormat {-# INLINE fromText #-} +-- | Textual representation for /absolute/ URIs. +-- +instance HasTextRepresentation URI where + toText uri = T.pack $ uriToString id uri "" + fromText t = case parseAbsoluteURI (T.unpack t) of + Nothing -> throwM $ TextFormatException $ "failed to parse URI " <> t + Just u -> return u + {-# INLINE toText #-} + {-# INLINE fromText #-} + -- | Decode a value from its textual representation. -- eitherFromText From 09c756f4cf9b91466e660a9ac732d95c241c8a7b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 15 Jan 2025 18:51:08 -0800 Subject: [PATCH 012/378] Add unidirectional pattern for ChainId --- src/Chainweb/ChainId.hs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index 8e9effb3f0..be3e8c613c 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -31,6 +31,7 @@ module Chainweb.ChainId ( ChainIdException(..) , ChainId +, pattern ChainId , HasChainId(..) , checkChainId , chainIdToText @@ -124,10 +125,14 @@ instance Exception ChainIdException -- * For some arbitrary but fixed chain id consider using 'someChainId'. -- newtype ChainId :: Type where - ChainId :: Word32 -> ChainId + ChainId' :: Word32 -> ChainId deriving stock (Show, Read, Eq, Ord, Generic) deriving anyclass (Hashable, ToJSON, FromJSON, NFData) +pattern ChainId :: Word32 -> ChainId +pattern ChainId n <- ChainId' n +{-# COMPLETE ChainId #-} + instance ToJSONKey ChainId where toJSONKey = toJSONKeyText toText {-# INLINE toJSONKey #-} @@ -178,11 +183,11 @@ checkChainId expected actual = _chainId {-# INLINE checkChainId #-} chainIdToText :: ChainId -> T.Text -chainIdToText (ChainId i) = sshow i +chainIdToText (ChainId' i) = sshow i {-# INLINE chainIdToText #-} chainIdFromText :: MonadThrow m => T.Text -> m ChainId -chainIdFromText = fmap ChainId . treadM +chainIdFromText = fmap ChainId' . treadM {-# INLINE chainIdFromText #-} instance HasTextRepresentation ChainId where @@ -195,11 +200,11 @@ instance HasTextRepresentation ChainId where -- Serialization encodeChainId :: ChainId -> Put -encodeChainId (ChainId w32) = putWord32le w32 +encodeChainId (ChainId' w32) = putWord32le w32 {-# INLINE encodeChainId #-} decodeChainId :: Get ChainId -decodeChainId = ChainId <$> getWord32le +decodeChainId = ChainId' <$> getWord32le {-# INLINE decodeChainId #-} decodeChainIdChecked @@ -264,11 +269,11 @@ pattern FromSingChainId sng <- ((\cid -> withSomeSing cid SomeSing) -> SomeSing -- of 'ChainId' for alternative ways to obtain 'ChainId' values. -- unsafeChainId :: Word32 -> ChainId -unsafeChainId = ChainId +unsafeChainId = ChainId' {-# INLINE unsafeChainId #-} chainIdInt :: Integral i => ChainId -> i -chainIdInt (ChainId cid) = int cid +chainIdInt (ChainId' cid) = int cid {-# INLINE chainIdInt #-} -- -------------------------------------------------------------------------- -- From 8d8cd996e659640192ea38ee3e13db84b863e9fa Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 9 Jan 2025 01:40:47 -0800 Subject: [PATCH 013/378] Remove -Werror from cabal files. cabal.project is a better place for it --- chainweb.cabal | 5 ----- cwtools/cwtools.cabal | 5 ----- node/chainweb-node.cabal | 5 ----- 3 files changed, 15 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index 6c1101f2cb..12b104cbf8 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -97,7 +97,6 @@ common debugging-flags common warning-flags ghc-options: -Wall - -Werror -Wcompat -Wpartial-fields -Wincomplete-record-updates @@ -107,10 +106,6 @@ common warning-flags -fmax-relevant-binds=0 -Wno-gadt-mono-local-binds - -- This needed because -Werror and missing-home-modules causes - -- problems with ghci. - -Wno-missing-home-modules - -- -------------------------------------------------------------------------- -- -- Chainweb Library -- -------------------------------------------------------------------------- -- diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index 6d530647a5..f5dd4c8f96 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -60,7 +60,6 @@ common debugging-flags common warning-flags ghc-options: -Wall - -Werror -Wcompat -Wpartial-fields -Wincomplete-record-updates @@ -69,10 +68,6 @@ common warning-flags -funclutter-valid-hole-fits -fmax-relevant-binds=0 - -- This needed because -Werror and missing-home-modules causes - -- problems with ghci. - -Wno-missing-home-modules - executable b64 import: warning-flags, debugging-flags default-language: Haskell2010 diff --git a/node/chainweb-node.cabal b/node/chainweb-node.cabal index 4c78bec088..f39baa68b3 100644 --- a/node/chainweb-node.cabal +++ b/node/chainweb-node.cabal @@ -56,7 +56,6 @@ common debugging-flags common warning-flags ghc-options: -Wall - -Werror -Wcompat -Wpartial-fields -Wincomplete-record-updates @@ -65,10 +64,6 @@ common warning-flags -funclutter-valid-hole-fits -fmax-relevant-binds=0 - -- This needed because -Werror and missing-home-modules causes - -- problems with ghci. - -Wno-missing-home-modules - custom-setup setup-depends: , Cabal >= 3.8 From 1eb67a2dec985789b1f966834f8b1f060bc78298 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 9 Jan 2025 13:00:01 -0800 Subject: [PATCH 014/378] [CI] remove GHC-9.6.6 from build matrix --- .github/workflows/applications.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index 1ab0734319..e1aba3f86e 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -135,7 +135,7 @@ jobs: MATRIX="$(jq -c '.' < Date: Thu, 9 Jan 2025 01:50:38 -0800 Subject: [PATCH 015/378] Add TODO comment to in-memory Mempool implementation --- src/Chainweb/Mempool/InMemTypes.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Chainweb/Mempool/InMemTypes.hs b/src/Chainweb/Mempool/InMemTypes.hs index e95fb936e8..d469077c0f 100644 --- a/src/Chainweb/Mempool/InMemTypes.hs +++ b/src/Chainweb/Mempool/InMemTypes.hs @@ -59,6 +59,11 @@ type PendingMap = HashMap TransactionHash PendingEntry ------------------------------------------------------------------------------ -- | Configuration for in-memory mempool. +-- +-- TODO: The type contains parameters that should be configurable as well as +-- parameters that are determined by the chainweb version or the chainweb +-- protocol. These should be separated in to two different types. +-- data InMemConfig t = InMemConfig { _inmemTxCfg :: {-# UNPACK #-} !(TransactionConfig t) , _inmemTxBlockSizeLimit :: !GasLimit From 640e89f268babe631305fc51bf2051dfce9a3742 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 14 Jan 2025 00:13:07 -0800 Subject: [PATCH 016/378] wip-7 (a526631d2): src/Chainweb/Ranked.hs --- src/Chainweb/Ranked.hs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/Chainweb/Ranked.hs b/src/Chainweb/Ranked.hs index aa408209d6..1a6fafded2 100644 --- a/src/Chainweb/Ranked.hs +++ b/src/Chainweb/Ranked.hs @@ -8,6 +8,7 @@ {-# LANGUAGE MagicHash #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} -- | -- Module: Chainweb.Ranked @@ -27,6 +28,9 @@ module Chainweb.Ranked , encodeRanked , decodeRanked , JsonRanked(..) + +-- * IsRanked Class +, IsRanked(..) ) where import Chainweb.BlockHeight @@ -42,6 +46,7 @@ import Data.Typeable (Proxy(..), Typeable, typeRep) import GHC.Generics (Generic) import GHC.TypeLits +import Data.Kind (Type) -- -------------------------------------------------------------------------- -- -- BlockHeight Ranked Data @@ -73,6 +78,32 @@ decodeRanked decodeA = Ranked <*> decodeA {-# INLINE decodeRanked #-} +-- -------------------------------------------------------------------------- -- +-- Has Rank Class + +-- | Class of Ranked Types. +-- +-- All instances of this class must have an 'Ord' instance that sorts by height +-- and a encoding functions that perserve this ordering on the lexicographic +-- order of the encoded values. +-- +-- This class is used because for some types the rank can be derived from the +-- value itself. In those cases the use of the 'Ranked' data type is wastefull +-- and a simple 'newtype' wrapper is better suited. This class is used to +-- abstract about both cases and to avoid cluttering the namespace. +-- +class Ord r => IsRanked r where + type Unranked r :: Type + rank :: r -> BlockHeight + unranked :: r -> Unranked r + ranked :: BlockHeight -> Unranked r -> r + +instance Ord a => IsRanked (Ranked a) where + type Unranked (Ranked a) = a + rank = _rankedHeight + unranked = _ranked + ranked = Ranked + -- -------------------------------------------------------------------------- -- -- | JSON Encoding for Ranked Types. From 1277cf75032ff1fd52f1f8a2eb9e77bca951f1e1 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:15:53 -0800 Subject: [PATCH 017/378] Add PayloadProvider API --- chainweb.cabal | 2 + src/Chainweb/PayloadProvider.hs | 919 ++++++++++++++++++ .../PayloadProvider/Initialization.hs | 46 + src/Chainweb/Version.hs | 121 ++- src/Chainweb/Version/Development.hs | 1 + src/Chainweb/Version/Mainnet.hs | 1 + src/Chainweb/Version/RecapDevelopment.hs | 1 + src/Chainweb/Version/Testnet04.hs | 1 + src/Chainweb/Version/Testnet05.hs | 1 + 9 files changed, 1086 insertions(+), 7 deletions(-) create mode 100644 src/Chainweb/PayloadProvider.hs create mode 100644 src/Chainweb/PayloadProvider/Initialization.hs diff --git a/chainweb.cabal b/chainweb.cabal index 12b104cbf8..2516fbe46a 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -222,6 +222,8 @@ library , Chainweb.Payload.RestAPI , Chainweb.Payload.RestAPI.Server , Chainweb.Payload.RestAPI.Client + , Chainweb.PayloadProvider + , Chainweb.PayloadProvider.Initialization , Chainweb.PowHash , Chainweb.Ranked , Chainweb.RestAPI diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs new file mode 100644 index 0000000000..cf388d7a39 --- /dev/null +++ b/src/Chainweb/PayloadProvider.hs @@ -0,0 +1,919 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeFamilies #-} + +-- | +-- Module: Chainweb.PayloadProvider +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider +( +-- * Hints + Hints(..) + +-- * SyncState +, SyncState(..) +, syncStateOfBlockHeader +, syncStateRankedBlockPayloadHash + +-- * ConsensusState +, ConsensusState(..) +, genesisSyncState +, genesisConsensusState +, latestRankedBlockPayloadHash +, safeRankedBlockPayloadHash +, finalRankedBlockPayloadHash +, genesisState + +-- * NewBlock Context +, NewBlockCtx(..) + +-- * Evaluation Context +, EvaluationCtx(..) +, _evaluationCtxRankedPayloadHash + +-- * Fork Info +, ForkInfo(..) +, PayloadProvider(..) +, EncodedPayloadData(..) +, EncodedPayloadOutputs(..) +, assertForkInfoInvariants +, _forkInfoBaseHeight +, _forkInfoBaseBlockHash +, _forkInfoBaseRankedPayloadHash + +-- * New Payload +, NewPayload(..) +, _newPayloadRankedParentHash +-- , SyncError(..) +-- , EvmPayloadCtx +-- , PactPayloadCtx +, blockHeaderToEvaluationCtx +, nextPayload +, nextPayloadStm +, payloadStream + +-- * Some PayloadProvider +, SomePayloadProvider(..) + +-- * Payload Providers for all Chains +, PayloadProviders(..) +, payloadProviders +, withPayloadProvider + +-- * Utils + +-- ** Consensus State Accessors +, _latestBlockHash +, _latestPayloadHash +, _latestHeight +, _safeBlockHash +, _safePayloadHash +, _safeHeight +, _finalBlockHash +, _finalPayloadHash +, _finalHeight +) where + +import Chainweb.BlockCreationTime +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.MinerReward +import Chainweb.Utils +import Chainweb.Version +import Control.Concurrent.STM +import Control.DeepSeq (NFData) +import Control.Lens hiding ((.=)) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.IO.Class +import Data.Aeson +import Data.ByteString qualified as B +import Data.HashMap.Strict qualified as HM +import Data.Text qualified as T +import GHC.Generics (Generic) +import GHC.Stack (HasCallStack) +import Numeric.Natural +import P2P.Peer +import Streaming.Prelude qualified as S +import Data.Function +import Data.Hashable +import Data.Maybe + +-- -------------------------------------------------------------------------- -- +-- Exceptions + +data PayloadProviderException + = InvalidForkInfo T.Text + deriving (Show, Eq, Generic) + +instance Exception PayloadProviderException + +-- -------------------------------------------------------------------------- -- +-- Sync State + +-- | This identifies the block that corresponds to the current state of the +-- payload provider. +-- +-- TODO: +-- To accomodate existing providers (Pact and EVM), we include both, block hash +-- and the block payload hash. When future versions of Pact are able to resolve +-- payloads through the payload block hash alone, we may drop the block hash +-- from this structure. +-- +-- TODO: +-- Safe and Finalized Block should be identified via the block payload hashes +-- (and possibly number) instead of the height. +-- +data SyncState = SyncState + { _syncStateHeight :: !BlockHeight + -- ^ The BlockHeight of the lastest Block + , _syncStateBlockHash :: !BlockHash + -- ^ The BlockHash of the latest Block + , _syncStateBlockPayloadHash :: !BlockPayloadHash + -- ^ The BlockPayloadHash of the latest Block + } + deriving (Show, Eq, Ord) + +syncStateOfBlockHeader :: BlockHeader -> SyncState +syncStateOfBlockHeader hdr = SyncState + { _syncStateHeight = view blockHeight hdr + , _syncStateBlockHash = view blockHash hdr + , _syncStateBlockPayloadHash = view blockPayloadHash hdr + } + +syncStateRankedBlockPayloadHash :: SyncState -> RankedBlockPayloadHash +syncStateRankedBlockPayloadHash s = RankedBlockPayloadHash + (_syncStateHeight s) (_syncStateBlockPayloadHash s) + +syncStateProperties :: forall e kv . KeyValue e kv => SyncState -> [kv] +syncStateProperties a = + [ "height" .= _syncStateHeight a + , "blockHash" .= _syncStateBlockHash a + , "payloadHash" .= _syncStateBlockPayloadHash a + ] + +instance ToJSON SyncState where + toEncoding = pairs . mconcat . syncStateProperties + toJSON = object . syncStateProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Consensus State + +data ConsensusState = ConsensusState + { _consensusStateLatest :: !SyncState + , _consensusStateSafe :: !SyncState + -- ^ The latest block that is generally considered safe. In a Chainweb a + -- block is considered safe, when it has at least Graph-diameter many + -- full layers on top of it. However, depending on on current conditions + -- of the network the required depth can be larger. For testing networks + -- with low-diameter chain graphs the minimal required depth is at least + -- 3. + , _consensusStateFinal :: !SyncState + -- ^ The latest block that is considered final. Note that this is a + -- heuristics. There is no definite finality in any PoW/PoH blockchain + -- system. (And the same should probably be true for PoS blockchains as + -- well.) + -- + -- Chainweb uses an very conservative measure of finality. A block that + -- is considered final can not be reorged automatically. Instead such a + -- reorg would require manual intervention of node maintainers. + } + deriving (Show, Eq, Ord) + +genesisSyncState + :: HasChainwebVersion v + => HasChainId c + => v + -> c + -> SyncState +genesisSyncState v c = + syncStateOfBlockHeader (genesisBlockHeader (_chainwebVersion v) c) + +genesisConsensusState + :: HasChainwebVersion v + => HasChainId c + => v + -> c + -> ConsensusState +genesisConsensusState v c = ConsensusState + { _consensusStateLatest = genesisSyncState v c + , _consensusStateSafe = genesisSyncState v c + , _consensusStateFinal = genesisSyncState v c + } + +latestRankedBlockPayloadHash :: ConsensusState -> RankedBlockPayloadHash +latestRankedBlockPayloadHash = + syncStateRankedBlockPayloadHash . _consensusStateLatest + +safeRankedBlockPayloadHash :: ConsensusState -> RankedBlockPayloadHash +safeRankedBlockPayloadHash = + syncStateRankedBlockPayloadHash . _consensusStateSafe + +finalRankedBlockPayloadHash :: ConsensusState -> RankedBlockPayloadHash +finalRankedBlockPayloadHash = + syncStateRankedBlockPayloadHash . _consensusStateFinal + +consensusStateProperties :: forall e kv . KeyValue e kv => ConsensusState -> [kv] +consensusStateProperties a = + [ "latest" .= _consensusStateLatest a + , "safe" .= _consensusStateSafe a + , "final" .= _consensusStateFinal a + ] + +instance ToJSON ConsensusState where + toEncoding = pairs . mconcat . consensusStateProperties + toJSON = object . consensusStateProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Payload Evaluation Context + +-- | The block payload evaluation context for a given payload provider. +-- +-- It contains all data that allows the provider to evaluate the payload of +-- a single block. +-- +-- IMPORTANT NOTE: +-- The context is deterministcally derived from the consensus state of the +-- blockchain. The payload provider must validate that the context is correct. +-- This is particularly important for payload providers that redundantly store +-- some contextual information internally in payload data structure. Some of the +-- contextual information, namely the timestamp and the mining reward, can not +-- be derived from the previous payload. While the mining reward is a constant +-- property of the blockchain, i.e. it is determined at genesis, the timestamp +-- is determined only at mining time. If such a property is stored and remotely +-- synchronized with remote peers, the payload provider must confirm the +-- validity of this information with the local consensus state before committing +-- the the payload evaluation. +-- +-- Binary format: The concatenation of the binary serialization of the +-- individual fields in the order as they appear in the data type definition. +-- +data EvaluationCtx = EvaluationCtx + { _evaluationCtxParentCreationTime :: !BlockCreationTime + -- ^ Creation time of the parent block. If transactions in the block + -- have a notion of "current" time, they should use this value. + , _evaluationCtxParentHash :: !BlockHash + -- ^ Block hash of the parent block. + , _evaluationCtxParentHeight :: !BlockHeight + -- ^ Block height of the parent block. + , _evaluationCtxMinerReward :: !MinerReward + -- ^ The miner reward that is assigned to the miner of the block. Miner + -- rewards are not constant and determined by the consensus protocol. It + -- depends on the block height and the respective chain graph. + -- + -- The payload provider must validate for each block that the reward is + -- correct. + -- + -- On payload provider API level the amount is provided in Stu and + -- encoded as unsigned 64 bit integer value in little endian encoding. + -- + -- Internally, encoding and unit is provider specific. For Pact it is a + -- Kda value for the EVM provider it is in Stu. Also the recipient and + -- the mechanism how it is credited is provider specific. + , _evaluationCtxPayloadHash :: !BlockPayloadHash + -- ^ Payload hash of the block that is validated. This is used as a + -- checksum for the payload validation. For the last block of a ForkInfo + -- structure this value must match the respective value in the target + -- sync state. + -- + -- The BlockPayloadHash is first computed when the respective payload is + -- created for mining and before it is included in a block. + , _evaluationCtxPayloadData :: !(Maybe EncodedPayloadData) + -- ^ Optional external payload data. This may be + -- the complete, self contained block payload or it may just contain + -- complementary data that aids with the validation. + -- + -- The main purpose of this field is to allow consensus to gossip around + -- payload data along with new cuts in the P2P network, which allows for + -- more efficient synchronization and a reduction of block propagation + -- latencies. + } + deriving (Show, Eq, Ord) + +_evaluationCtxRankedPayloadHash + :: EvaluationCtx + -> RankedBlockPayloadHash +_evaluationCtxRankedPayloadHash ctx = RankedBlockPayloadHash + (_evaluationCtxParentHeight ctx + 1) + (_evaluationCtxPayloadHash ctx) + +evaluationCtxProperties :: forall e kv . KeyValue e kv => EvaluationCtx -> [kv] +evaluationCtxProperties a = + [ "parentCreationTime" .= _evaluationCtxParentCreationTime a + , "parentBlockHash" .= _evaluationCtxParentHash a + , "parentHeight" .= _evaluationCtxParentHeight a + , "minerReward" .= _evaluationCtxMinerReward a + , "payloadHash" .= _evaluationCtxPayloadHash a + , "payloadData" .= _evaluationCtxPayloadData a + ] + +instance ToJSON EvaluationCtx where + toEncoding = pairs . mconcat . evaluationCtxProperties + toJSON = object . evaluationCtxProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +-- -------------------------------------------------------------------------- -- +-- New Block Context + +-- | Context for creating a new block on top of the latest Block. +-- +-- The miner reward depends on global consensus properties like block height and +-- chain graph. +-- +-- In the future this structure may be extended with additional fields. +-- +-- Note, that the mechanism how the reward is credited as well as the recipient +-- is defined in the scope of the specific payload provider. It is *not* part of +-- the global chainweb consensus protocol. Similarly, gas fees and the recipient +-- of those fees are provider specific. This implies a trust relationship +-- between payload provider and consensus (the miner, which is part of +-- consensus, must trust its payload provider). +-- +data NewBlockCtx = NewBlockCtx + { _newBlockCtxMinerReward :: !MinerReward + -- ^ the miner reward for the new block. + , _newBlockCtxParentCreationTime :: !BlockCreationTime + -- ^ the creation time of the block on which the new is created. + } + deriving (Show, Eq, Ord) + +-- | Get the evaluation context for given parent header and block payload hash +-- +blockHeaderToEvaluationCtx + :: ParentHeader + -> BlockPayloadHash + -> Maybe EncodedPayloadData + -> EvaluationCtx +blockHeaderToEvaluationCtx (ParentHeader ph) pld pldData = EvaluationCtx + { _evaluationCtxParentCreationTime = view blockCreationTime ph + , _evaluationCtxParentHash = view blockHash ph + , _evaluationCtxParentHeight = parentHeight + , _evaluationCtxMinerReward = blockMinerReward v height + , _evaluationCtxPayloadHash = pld + , _evaluationCtxPayloadData = pldData + } + where + parentHeight = view blockHeight ph + height = parentHeight + 1 + v = _chainwebVersion ph + +newBlockCtxProperties :: forall e kv . KeyValue e kv => NewBlockCtx -> [kv] +newBlockCtxProperties a = + [ "minerReward" .= _newBlockCtxMinerReward a + , "parentCreationTime" .= _newBlockCtxParentCreationTime a + ] + +instance ToJSON NewBlockCtx where + toEncoding = pairs . mconcat . newBlockCtxProperties + toJSON = object . newBlockCtxProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +-- -------------------------------------------------------------------------- -- +-- ForkInfo + +-- | Synchronize the state of a payload provider to a particular fork of a +-- Chainweb chain. +-- +-- This contains all data that is required for a payload provider to evaluate +-- the payloads of a consecutive sequence of blocks on top of some historic +-- provider state. +-- +-- In order to create this value the client makes an educated guess about the +-- current state of the payload provider. Under normal circumstances the client +-- is able to make an accurate guess. It can fail on the first initialization of +-- a provider or on re-initialization after an ungracefull shutdown of the +-- payload provider or the client. If that happens and no suitable historic +-- state is known to the payload provider it rejects the `ForkInfo` and returns +-- the current `PayloadProviderState` to the caller. This enables the caller to +-- prepare a new `ForkInfo` value. +-- +-- Binary format: the length of `_forkInfoTrace` as unsigned 32 bit integer +-- number in little endian byte order followed by the binary serialization of +-- the entries of `_forkInfoTrace`, followed by the binary serialization of +-- `forkInfoTraceHash`. +-- +-- NOTE: +-- +-- A special case are stateless payload providers for which the evaluation of a +-- payload depends only on the local evaluation context and not on the history +-- of the chain. Those payload providers do not need to resolve reorgs. However, +-- they cannot offer access to a complete and consistent payload history via the +-- service API. The minimal payload provider is an example for a stateless +-- payload provider. +-- +data ForkInfo = ForkInfo + { _forkInfoTrace :: ![EvaluationCtx] + -- ^ The payload evaluation contexts for a consecutive sequence of + -- blocks. The first entry determines the fork point which must be + -- known to the payload provider (although it is not necessary that the + -- payload provider is able to reconstruct the respective state. The + -- provider is not obligated to replay all blocks as long as the final + -- state is valid. + -- + -- If evaluation of the full list of payloads fails, the payload provider + -- may choose to remain in an intermediate state, as long as that state + -- is consistent with the evaluation of some prefix of this field. + -- + -- The payload provider is also obligated to validate the correctness of + -- the evaluation context with respect the payload provider state for + -- all provided contexts in this field up to the lastest validated + -- block. This allows the client to request the evaluation and + -- validation of a series of new blocks. The payload provider does not + -- need to guarantee the correctness of the validation context for + -- blocks that had been validated before. This includes all re-validated + -- blocks that are not included in this field, because it can be assumed + -- that those have been validated before with their respective contexts + -- provided. + -- + -- However, the operation for the respective evaluated prefix must + -- satisfy the ACID criteria. + -- + -- FIXME: + -- It may be more intuitive and convenient to store the trace in reverse + -- order. + -- + , _forkInfoBasePayloadHash :: !BlockPayloadHash + -- ^ The payload hash of the parent block of the first entry in the + -- fork info trace. If the fork info trace is empty, this is the payload + -- hash of the latest block in the consensus state. + -- + -- The payload hash of the parent block is not part of the evaluation + -- context, because it does need to be validated. However, some payload + -- providers may require it to identify the base state onto which the + -- fork info trace is applied. + , _forkInfoTargetState :: !ConsensusState + -- ^ The target sync state. This allows the payload provider + -- to update its `SyncState`. Intermediate block hashes are + -- available in form of `BlockParentHash`s from the `PayloadCtx` + -- entries. + , _forkInfoNewBlockCtx :: !(Maybe NewBlockCtx) + -- ^ If mining is enabled in the payload provider and this field is not + -- `Nothing`, the payload provider creates a new block payload with the + -- given miner info and miner reward. + } + deriving (Show, Eq, Ord) + +_forkInfoBaseHeight :: ForkInfo -> BlockHeight +_forkInfoBaseHeight fi = case _forkInfoTrace fi of + [] -> _latestHeight (_forkInfoTargetState fi) + (h:_) -> _evaluationCtxParentHeight h + +_forkInfoBaseBlockHash :: ForkInfo -> BlockHash +_forkInfoBaseBlockHash fi = case _forkInfoTrace fi of + [] -> _latestBlockHash (_forkInfoTargetState fi) + (h:_) -> _evaluationCtxParentHash h + +_forkInfoBaseRankedPayloadHash :: ForkInfo -> RankedBlockPayloadHash +_forkInfoBaseRankedPayloadHash fi = RankedBlockPayloadHash + (_forkInfoBaseHeight fi) + (_forkInfoBasePayloadHash fi) + +assertForkInfoInvariants :: MonadThrow m => ForkInfo -> m () +assertForkInfoInvariants forkInfo = do + when (null (_forkInfoTrace forkInfo)) $ + unless (trgPayloadHash forkInfo == _forkInfoBasePayloadHash forkInfo) $ + throwM $ InvalidForkInfo + "The base payload hash must match the target payload hash, when the fork info trace is empty" + + where + trgPayloadHash = _latestPayloadHash . _forkInfoTargetState + +forkInfoProperties :: forall e kv . KeyValue e kv => ForkInfo -> [kv] +forkInfoProperties a = + [ "trace" .= _forkInfoTrace a + , "basePayloadHash" .= _forkInfoBasePayloadHash a + , "target" .= _forkInfoTargetState a + , "newBlockContext" .= _forkInfoNewBlockCtx a + ] + +instance ToJSON ForkInfo where + toEncoding = pairs . mconcat . forkInfoProperties + toJSON = object . forkInfoProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Hints for querying Payloads + +data Hints = Hints + { _hintsOrigin :: !PeerInfo + } + +hintsProperties :: forall e kv . KeyValue e kv => Hints -> [kv] +hintsProperties a = + [ "origin" .= _hintsOrigin a + ] + +instance ToJSON Hints where + toEncoding = pairs . mconcat . hintsProperties + toJSON = object . hintsProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Encoded Payloads + +newtype EncodedPayloadData = EncodedPayloadData + { _encodedPayloadData :: B.ByteString } + deriving (Show, Eq, Ord, Generic) + deriving anyclass (NFData) + +instance ToJSON EncodedPayloadData where + toJSON = toJSON . encodeB64UrlNoPaddingText . _encodedPayloadData + toEncoding = b64UrlNoPaddingTextEncoding . _encodedPayloadData + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance FromJSON EncodedPayloadData where + parseJSON = withText "EncodedPayloadData" $ \t -> + case decodeB64UrlNoPaddingText t of + Left e -> fail (show e) + Right x -> return $ EncodedPayloadData x + {-# INLINE parseJSON #-} + +newtype EncodedPayloadOutputs = EncodedPayloadOutputs + { _encodedPayloadOutputs :: B.ByteString } + deriving (Show, Eq, Ord, Generic) + deriving anyclass (NFData) + +instance ToJSON EncodedPayloadOutputs where + toJSON = toJSON . encodeB64UrlNoPaddingText . _encodedPayloadOutputs + toEncoding = b64UrlNoPaddingTextEncoding . _encodedPayloadOutputs + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance FromJSON EncodedPayloadOutputs where + parseJSON = withText "EncodedPayloadOutputs" $ \t -> + case decodeB64UrlNoPaddingText t of + Left e -> fail (show e) + Right x -> return $ EncodedPayloadOutputs x + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- New Payload + +-- | +-- +-- Some metrics included that maybe used by consensus for performance +-- optimizations and optimal resource allocations and reporting of aggregated +-- metrics. +-- +-- TODO: describe encoding +-- +data NewPayload = NewPayload + { _newPayloadChainwebVersion :: !ChainwebVersion + , _newPayloadChainId :: !ChainId + , _newPayloadParentHeight :: !BlockHeight + , _newPayloadParentHash :: !BlockHash + , _newPayloadBlockPayloadHash :: !BlockPayloadHash + , _newPayloadEncodedPayloadData :: !(Maybe EncodedPayloadData) + , _newPayloadEncodedPayloadOutputs :: !(Maybe EncodedPayloadOutputs) + , _newPayloadNumber :: !Int + -- ^ a locally monotonically increasing identifier. Its purpose is to + -- induce an order on payloads that are created for the same parent + -- header. The miner will give precedence to larger id values. + + -- Informative: + , _newPayloadTxCount :: !Natural + -- ^ The number of user transactions in the block. The exact way how + -- transactions are counted is provider specific. + , _newPayloadSize :: !Natural + -- ^ On-chain storage size in bytes. This is not generally the same as + -- the size of the optional PayloadData. The exact meaning is provider + -- specific. + , _newPayloadOutputSize :: !Natural + -- ^ Size of evaluation outputs bytes. This is not generally the same as + -- the size of the optional PayloadOuputs. The exact meaning is provider + -- specific. + , _newPayloadFees :: !Stu + -- ^ The total amount of all fees payed for transaction processing in + -- Stu. The exact meaning is provider specific. + } + deriving (Show, Generic) + +instance Eq NewPayload where + (==) = on (==) $ \x -> + -- move entropy to the beginning of the comparision to fail fast + -- (assuming that tuple starts at the front) + ( _newPayloadBlockPayloadHash x + , _newPayloadChainwebVersion x + , _newPayloadChainId x + , _newPayloadNumber x + , isJust (_newPayloadEncodedPayloadData x) + , isJust (_newPayloadEncodedPayloadOutputs x) + ) + -- including the chainweb version and chain id should ideally be + -- redundant for almost all sane (production) payload provider + -- implementations. But, for instance, Ethereum Development blocks share + -- the same block payload hash accross chains. The same is true for some + -- empty pact blocks in certain testing scenarios. + +instance Ord NewPayload where + compare = on compare $ \x -> + ( _newPayloadChainwebVersion x + , _newPayloadChainId x + , _newPayloadBlockPayloadHash x + , _newPayloadNumber x + , isJust (_newPayloadEncodedPayloadData x) + , isJust (_newPayloadEncodedPayloadOutputs x) + ) + +-- | NOTE: this instance is efficient but opinionated. It hashes blocks from +-- different chains with the same block payload hash to the same value. +-- Hopefully, this is a degenerated case that is of no relevance for practical +-- production settings (cf. the code comment on the Eq instance for some more +-- details). For non-degenerated applications the hash values are of high +-- quality. +-- +instance Hashable NewPayload where + -- This is opinionated but should be fine for our purposes + hashWithSalt s = hashWithSalt s . _newPayloadBlockPayloadHash + {-# INLINE hashWithSalt #-} + +_newPayloadRankedParentHash :: NewPayload -> RankedBlockHash +_newPayloadRankedParentHash np = RankedBlockHash + (_newPayloadParentHeight np) + (_newPayloadParentHash np) + +instance HasChainwebVersion NewPayload where + _chainwebVersion = _newPayloadChainwebVersion + {-# INLINE _chainwebVersion #-} + +instance HasChainId NewPayload where + _chainId = _newPayloadChainId + {-# INLINE _chainId #-} + +newPayloadProperties :: forall e kv . KeyValue e kv => NewPayload -> [kv] +newPayloadProperties a = + [ "chainwebVersion" .= _versionName (_newPayloadChainwebVersion a) + , "chainId" .= _newPayloadChainId a + , "parentHeight" .= _newPayloadParentHeight a + , "parentHash" .= _newPayloadParentHash a + , "payloadHash" .= _newPayloadBlockPayloadHash a + , "data" .= _newPayloadEncodedPayloadData a + , "outputs" .= _newPayloadEncodedPayloadOutputs a + , "revision" .= _newPayloadNumber a + , "txCount" .= _newPayloadTxCount a + , "size" .= _newPayloadSize a + , "outputSize" .= _newPayloadOutputSize a + , "fees" .= _newPayloadFees a + ] + +instance ToJSON NewPayload where + toEncoding = pairs . mconcat . newPayloadProperties + toJSON = object . newPayloadProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Payload Provider + +-- | Payload Provider API. +-- +-- Typically, a payload this is a list of transactions, including some system +-- specific transactions for rewarding miners and paying gas fees. It may also +-- include additional metadata that affect the evaluation. +-- +-- The content is completely provider specific. Chainweb only requires that +-- it has a finite binary representation that can be efficiently stored, +-- copied, and moved around. In particular, if a provider has internal ways +-- to materialize payload contents (e.g. using an internal P2P gossip +-- network), this type may contain only enough information to identify the +-- payload (e.g. a hash value). +-- +-- There are some semantic requirements about the evaluation of a block +-- payload. Those include the payment of rewards to the miner of a block +-- and support for SPV proofs (both creation and verification). The details +-- of those are beyond the scope of this document which is concerned with +-- the syntactic aspects of the node-internal provider API. +-- +-- Note, that the use of the term `Payload` is overloaded in the context of +-- Chainweb. Here it refers only to the input to block validation that is +-- provided by the payload provider. It does not include the evaluation +-- context that is provided by the consensus component of Chainweb and also +-- does not include the evaluation results. It also does not include the +-- internal state of the payload provider and changes to the state that are +-- caused by the evaluation of the payload. +-- +-- In some case this data structure may represent only a commitment to the +-- payload and the actual payload is synchronized out-off-band (from the +-- viewpoint of consensus) or not needed at all (in case of ZK +-- computations). +-- +-- Some payload providers mix data from the evaluation context and the +-- payload itself in a single data structure. This API can accomodate this +-- behavior. +-- +class (HasChainwebVersion p, HasChainId p) => PayloadProvider p where + + -- | Returns the current sync state of the payload provider. + -- Note that this may be ahead of that returned by `prefetchBlock`. + -- + -- syncState :: p -> IO SyncState + + -- | Tell the PayloadProvider to fetch the block, and do whatever work is + -- necessary for us to synchronize with a block later that has this payload + -- hash. This is probably not necessary when compacted headers are added to + -- catchup. + -- + -- TODO: is this allowed to fail? Does it return or is it fire and forget? + -- + prefetchPayloads + :: p + -> Maybe Hints + -> ForkInfo + -- ^ TODO: do we really want to pass the full ForkInfo here? What is + -- the purpose of passing the full Consensus State? Maybe resolving + -- forks? Maybe a list of RankedBlockPayloadHashes (or + -- EvaluationCtx) would be sufficient here? + -> IO () + + -- | Request that the payload provider updates its internal state to + -- represent the validation of the last block in the provide `ForkInfo`. + -- + -- If the the first entry in `ForkInfo` is not known to the payload provider + -- this operation is a no-op and the provider returns its current sync + -- state. + -- + -- The payload provider may update the internal state only to a predecessor + -- of the requested block. This can happen if, for instance, the operation + -- times out or gets interrupted or an validation error occurs. In any case + -- the must be valid and the respective `SyncState` must be returned. + -- + -- The payload provider may update the internal state to a successor + -- of the requested block. This can happen if the provider is unable to + -- rewind blocks on a fork, for example the EVM. In that case 'syncToBlock' + -- will regardless return a SyncState for the requested block, not the successor. + -- + -- Independent of the actual final state, the operation must satisify ACID + -- criteria. In particular, any intermediate state while the operation is + -- ongoing must not be observable and the final state must be consistent + -- and persistent. + -- + syncToBlock + :: p + -- ^ Payload provider handle + -> Maybe Hints + -- ^ hints for fetching missing payloads + -> ForkInfo + -> IO ConsensusState + + -- | Asynchronoulsly yield new block payloads on top of the latest block. + -- + -- The new payload is identified by the block payload hash. + -- + -- New payloads only depend on there respective evaluation context and new + -- block contenxt and are only valid within this context. The context is + -- fully determined by the parent header, which is always the block of the + -- latest block of the current consensus state. It is represented by its + -- respective height and block hash. + -- + -- The result may include the actual payload and some representation of the + -- evaluation outputs. This allows for better error reporting and + -- optimizations in payload synchronizations (e.g. by piggybacking the new + -- payloads onto the respective new cuts in the P2P gossip network). Beside + -- of being a sequence of bytes the content of the those payloads are + -- completely parametetric outside the context of the payload provider. + -- + -- Payload provider should cache new payloads internally as long they have + -- not either been integrated into the longest chain or definitely + -- abandoned. Payload providers may also cache the validation result. + -- + latestPayloadSTM :: p -> STM NewPayload + + -- If backed by an TVar, this can usually be implemented more efficiently + -- using 'readTVarIO' + -- + latestPayloadIO :: p -> IO NewPayload + latestPayloadIO = atomically . latestPayloadSTM + +nextPayloadStm :: PayloadProvider p => p -> NewPayload -> STM NewPayload +nextPayloadStm p cur = do + new <- latestPayloadSTM p + when (new == cur) retry + return new + +nextPayload :: PayloadProvider p => p -> NewPayload -> IO NewPayload +nextPayload p = atomically . nextPayloadStm p + +payloadStream :: PayloadProvider p => p -> S.Stream (S.Of NewPayload) IO () +payloadStream p = do + cur <- liftIO $ latestPayloadIO p + S.yield cur + go cur + where + go c = do + n <- liftIO $ nextPayload p c + S.yield n + go n + +-- -------------------------------------------------------------------------- -- +-- Some Payload Provider + +data SomePayloadProvider where + SomePayloadProvider :: PayloadProvider p => p -> SomePayloadProvider + +instance HasChainwebVersion SomePayloadProvider where + _chainwebVersion (SomePayloadProvider p) = _chainwebVersion p + +instance HasChainId SomePayloadProvider where + _chainId (SomePayloadProvider p) = _chainId p + +-- -------------------------------------------------------------------------- -- +-- Payload Providers + +newtype PayloadProviders = PayloadProviders + { _payloadProviders :: HM.HashMap ChainId SomePayloadProvider + } + +payloadProviders :: Getter PayloadProviders (HM.HashMap ChainId SomePayloadProvider) +payloadProviders = to _payloadProviders + +type instance Index PayloadProviders = ChainId +type instance IxValue PayloadProviders = SomePayloadProvider + +instance IxedGet PayloadProviders where + ixg i = to _payloadProviders . ix i + +withPayloadProvider + :: HasCallStack + => HasChainId c + => PayloadProviders + -> c + -> (forall p . PayloadProvider p => p -> a) + -> a +withPayloadProvider (PayloadProviders ps) c f = case HM.lookup cid ps of + Just (SomePayloadProvider p) -> f p + Nothing -> error $ + "PayloadProviders: unknown ChainId " <> sshow cid <> ". This is a bug" + where + cid = _chainId c + +-- -------------------------------------------------------------------------- -- +-- Utils + +_latestBlockHash :: ConsensusState -> BlockHash +_latestBlockHash = _syncStateBlockHash . _consensusStateLatest + +_latestPayloadHash :: ConsensusState -> BlockPayloadHash +_latestPayloadHash = _syncStateBlockPayloadHash . _consensusStateLatest + +_latestHeight :: ConsensusState -> BlockHeight +_latestHeight = _syncStateHeight . _consensusStateLatest + +_safeBlockHash :: ConsensusState -> BlockHash +_safeBlockHash = _syncStateBlockHash . _consensusStateSafe + +_safePayloadHash :: ConsensusState -> BlockPayloadHash +_safePayloadHash = _syncStateBlockPayloadHash . _consensusStateSafe + +_safeHeight :: ConsensusState -> BlockHeight +_safeHeight = _syncStateHeight . _consensusStateSafe + +_finalBlockHash :: ConsensusState -> BlockHash +_finalBlockHash = _syncStateBlockHash . _consensusStateFinal + +_finalPayloadHash :: ConsensusState -> BlockPayloadHash +_finalPayloadHash = _syncStateBlockPayloadHash . _consensusStateFinal + +_finalHeight :: ConsensusState -> BlockHeight +_finalHeight = _syncStateHeight . _consensusStateFinal + +genesisState + :: HasChainwebVersion v + => HasChainId c + => v + -> c + -> ConsensusState +genesisState v c = ConsensusState + { _consensusStateLatest = s + , _consensusStateSafe = s + , _consensusStateFinal = s + } + where + s = SyncState + { _syncStateHeight = 0 + , _syncStateBlockHash = view blockHash hdr + , _syncStateBlockPayloadHash = view blockPayloadHash hdr + } + hdr = genesisBlockHeader (_chainwebVersion v) c + diff --git a/src/Chainweb/PayloadProvider/Initialization.hs b/src/Chainweb/PayloadProvider/Initialization.hs new file mode 100644 index 0000000000..d779d6941f --- /dev/null +++ b/src/Chainweb/PayloadProvider/Initialization.hs @@ -0,0 +1,46 @@ +{-# LANGUAGE ImportQualifiedPost #-} + +-- | +-- Module: Chainweb.PayloadProvider.Initialization +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.Initialization +( Resources(..) +) where + +import Chainweb.BlockHeaderDB +import Chainweb.ChainId +import Chainweb.Version +import Chainweb.Payload.PayloadStore +import Chainweb.PayloadProvider +import Chainweb.Pact.Types (MemPoolAccess) +import Data.Text qualified as T + +-- -------------------------------------------------------------------------- -- +-- Initialization + +-- | FIXME: this should be refined +-- +newtype MinerInfo = MinerInfo T.Text + +-- | Globally initialized resources that are needed by payload providers. +-- +-- Note that not all payload providers require all of these resources. +-- +data Resources tbl logger = Resources + { _resourceChainwebVersion :: !ChainwebVersion + , _resourceChainId :: !ChainId + , _resourceLogger :: !logger + , _resourceMempoolAccess :: !MemPoolAccess + -- ^ The mempool is owned completely by the payload provider. So, maybe + -- we should let it initialize the mempool by itself? At the moment all + -- network components are initialized centrally. + , _resourcePayloadDb :: !(PayloadDb tbl) + -- ^ For the EVM this is not needed, because it manages its own payload + -- database and synchronization. Pact relies on the consensus header for\ + -- synchronizing the payloads and populating the payload database. + , _resourceMinerInfo :: !MinerInfo + } diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 289beb7ea9..42a5bd81e7 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -20,6 +20,7 @@ {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TypeAbstractions #-} -- | -- Module: Chainweb.Version @@ -74,6 +75,7 @@ module Chainweb.Version , versionVerifierPluginNames , versionQuirks , versionServiceDate + , versionPayloadProviderTypes , genesisBlockPayload , genesisBlockPayloadHash , genesisBlockTarget @@ -100,11 +102,17 @@ module Chainweb.Version , KnownChainwebVersionSymbol , someChainwebVersionVal - -- * Singletons + -- ** Singletons , Sing(SChainwebVersion) , SChainwebVersion , pattern FromSingChainwebVersion + -- * Payload Provider Type + , PayloadProviderType(..) + , HasPayloadProviderType(..) + , payloadProviderTypeForChain + , Sing(SMinimalProvider, SPactProvider, SEvmProvider) + -- * HasChainwebVersion , HasChainwebVersion(..) , mkChainId @@ -164,7 +172,8 @@ import qualified Data.Text as T import Data.Word import GHC.Generics(Generic) -import GHC.TypeLits +import GHC.TypeLits hiding (Nat, fromSNat, withSomeSNat) +import GHC.TypeNats import GHC.Stack -- internal modules @@ -192,6 +201,9 @@ import Data.Singletons import P2P.Peer +-- -------------------------------------------------------------------------- -- +-- Forks + -- | Data type representing changes to block validation, whether in the payload -- or in the header. Always add new forks at the end, not in the middle of the -- constructors. @@ -323,6 +335,9 @@ data ForkHeight = ForkAtBlockHeight !BlockHeight | ForkAtGenesis | ForkNever makePrisms ''ForkHeight +-- -------------------------------------------------------------------------- -- +-- Chainweb Version Name + newtype ChainwebVersionName = ChainwebVersionName { getChainwebVersionName :: T.Text } deriving stock (Generic, Eq, Ord) @@ -331,6 +346,13 @@ newtype ChainwebVersionName = instance Show ChainwebVersionName where show = T.unpack . getChainwebVersionName +instance HasTextRepresentation ChainwebVersionName where + toText = getChainwebVersionName + fromText = pure . ChainwebVersionName + +-- -------------------------------------------------------------------------- -- +-- Chainweb Version Code + newtype ChainwebVersionCode = ChainwebVersionCode { getChainwebVersionCode :: Word32 } deriving stock (Generic, Eq, Ord) @@ -343,11 +365,17 @@ encodeChainwebVersionCode = putWord32le . getChainwebVersionCode decodeChainwebVersionCode :: Get ChainwebVersionCode decodeChainwebVersionCode = ChainwebVersionCode <$> getWord32le +-- -------------------------------------------------------------------------- -- +-- Merkle Tree Hash Algorithm + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ChainwebVersionCode where type Tag ChainwebVersionCode = 'ChainwebVersionTag toMerkleNode = encodeMerkleInputNode encodeChainwebVersionCode fromMerkleNode = decodeMerkleInputNode decodeChainwebVersionCode +-- -------------------------------------------------------------------------- -- +-- Pact Version + data PactVersion = Pact4 | Pact5 deriving stock (Eq, Show) data PactVersionT (v :: PactVersion) where @@ -391,6 +419,9 @@ deriving stock instance (Show (f Pact4), Show (f Pact5)) => Show (ForBothPactVer instance (NFData (f Pact4), NFData (f Pact5)) => NFData (ForBothPactVersions f) where rnf b = rnf (_forPact4 b) `seq` rnf (_forPact5 b) +-- -------------------------------------------------------------------------- -- +-- Pact Upgrade + -- The type of upgrades, which are sets of transactions to run at certain block -- heights during coinbase. @@ -425,6 +456,9 @@ instance NFData PactUpgrade where pact4Upgrade :: [Pact4.Transaction] -> PactUpgrade pact4Upgrade txs = Pact4Upgrade txs False +-- -------------------------------------------------------------------------- -- +-- Version Quirks + data TxIdxInBlock = TxBlockIdx Word deriving stock (Eq, Ord, Show, Generic) deriving anyclass (Hashable, NFData) @@ -445,6 +479,47 @@ noQuirks = VersionQuirks { _quirkGasFees = AllChains HM.empty } +-- -------------------------------------------------------------------------- -- +-- Payload Provider Type + +data PayloadProviderType + = MinimalProvider + | PactProvider + | EvmProvider Natural + -- ^ The natural number is the Ethereum network id that is used with by + -- this instance of the EVM provider + deriving (Show, Read, Eq, Ord, Generic) + deriving anyclass (NFData) + +data instance Sing (p :: PayloadProviderType) where + SMinimalProvider :: Sing 'MinimalProvider + SPactProvider :: Sing 'PactProvider + SEvmProvider :: SNat n -> Sing ('EvmProvider (n :: Natural)) + +instance SingI 'MinimalProvider where sing = SMinimalProvider +instance SingI 'PactProvider where sing = SPactProvider +instance KnownNat n => SingI ('EvmProvider n) where sing = SEvmProvider (natSing @n) + +instance SingKind PayloadProviderType where + type Demote PayloadProviderType = PayloadProviderType + fromSing SMinimalProvider = MinimalProvider + fromSing SPactProvider = PactProvider + fromSing (SEvmProvider n) = EvmProvider (fromSNat n) + toSing MinimalProvider = SomeSing SMinimalProvider + toSing PactProvider = SomeSing SPactProvider + toSing (EvmProvider n) = withSomeSNat n (SomeSing . SEvmProvider) + {-# INLINE fromSing #-} + {-# INLINE toSing #-} + +instance HasTextRepresentation PayloadProviderType where + toText = sshow + fromText = treadM + {-# INLINE toText #-} + {-# INLINE fromText #-} + +-- -------------------------------------------------------------------------- -- +-- Chainweb Version + -- | Chainweb versions are sets of properties that must remain consistent among -- all nodes on the same network. For examples see `Chainweb.Version.Mainnet`, -- `Chainweb.Version.Testnet`, `Chainweb.Version.RecapDevelopment`, and @@ -502,6 +577,8 @@ data ChainwebVersion -- ^ Modifications to behavior at particular blockheights , _versionServiceDate :: Maybe String -- ^ The node service date for this version. + , _versionPayloadProviderTypes :: ChainMap PayloadProviderType + -- ^ The payload provider type for each chain } deriving stock (Generic) deriving anyclass NFData @@ -558,6 +635,7 @@ data VersionCheats = VersionCheats data VersionGenesis = VersionGenesis { _genesisBlockTarget :: ChainMap HashTarget , _genesisBlockPayload :: ChainMap PayloadWithOutputs + -- ^ FIXME: This is currently only for the Pact Payload Provider , _genesisTime :: ChainMap BlockCreationTime } deriving stock (Generic, Eq) @@ -572,12 +650,14 @@ makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionCheats makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionDefaults makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionQuirks +-- | FIXME: This is currently only for the Pact Payload Provider +-- genesisBlockPayloadHash :: ChainwebVersion -> ChainId -> BlockPayloadHash -genesisBlockPayloadHash v cid = v ^?! versionGenesis . genesisBlockPayload . atChain cid . to _payloadWithOutputsPayloadHash - -instance HasTextRepresentation ChainwebVersionName where - toText = getChainwebVersionName - fromText = pure . ChainwebVersionName +genesisBlockPayloadHash v cid = v + ^?! versionGenesis + . genesisBlockPayload + . atChain cid + . to _payloadWithOutputsPayloadHash -------------------------------------------------------------------------- -- -- Type level ChainwebVersion @@ -654,6 +734,33 @@ mkChainId v h i = cid where cid = unsafeChainId i +-- -------------------------------------------------------------------------- -- +-- HasPayloadProviderType Class + +class HasPayloadProviderType p where + _payloadProviderType :: p -> PayloadProviderType + _payloadProviderType = view payloadProviderType + + payloadProviderType :: Getter p PayloadProviderType + payloadProviderType = to _payloadProviderType + + {-# INLINE _payloadProviderType #-} + {-# INLINE payloadProviderType #-} + {-# MINIMAL _payloadProviderType | payloadProviderType #-} + +payloadProviderTypeForChain + :: HasChainwebVersion v + => HasChainId c + => v + -> c + -> PayloadProviderType +payloadProviderTypeForChain v c = v + ^?! chainwebVersion . versionPayloadProviderTypes . atChain c + +instance (HasChainwebVersion p, HasChainId p) => HasPayloadProviderType p where + _payloadProviderType p = payloadProviderTypeForChain p p + {-# INLINE _payloadProviderType #-} + -------------------------------------------------------------------------- -- -- Properties of Chainweb Versions -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/Version/Development.hs b/src/Chainweb/Version/Development.hs index ad33768093..4ebcbf5201 100644 --- a/src/Chainweb/Version/Development.hs +++ b/src/Chainweb/Version/Development.hs @@ -64,4 +64,5 @@ devnet = ChainwebVersion (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) , _versionQuirks = noQuirks , _versionServiceDate = Nothing + , _versionPayloadProviderTypes = AllChains PactProvider } diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index 921ca26eb7..5b2533f336 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -226,4 +226,5 @@ mainnet = ChainwebVersion ] } , _versionServiceDate = Just "2025-04-30T00:00:00Z" + , _versionPayloadProviderTypes = AllChains PactProvider } diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index 69b5b02dc2..5f6b75451b 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -125,4 +125,5 @@ recapDevnet = ChainwebVersion Bottom (minBound, mempty) , _versionQuirks = noQuirks , _versionServiceDate = Nothing + , _versionPayloadProviderTypes = AllChains PactProvider } diff --git a/src/Chainweb/Version/Testnet04.hs b/src/Chainweb/Version/Testnet04.hs index 6a8daaa846..e4ddce9373 100644 --- a/src/Chainweb/Version/Testnet04.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -194,4 +194,5 @@ testnet04 = ChainwebVersion ] } , _versionServiceDate = Just "2025-04-30T00:00:00Z" + , _versionPayloadProviderTypes = AllChains PactProvider } diff --git a/src/Chainweb/Version/Testnet05.hs b/src/Chainweb/Version/Testnet05.hs index 6f133bbb61..c9e13512d2 100644 --- a/src/Chainweb/Version/Testnet05.hs +++ b/src/Chainweb/Version/Testnet05.hs @@ -102,4 +102,5 @@ testnet05 = ChainwebVersion Bottom (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) , _versionQuirks = noQuirks , _versionServiceDate = Nothing + , _versionPayloadProviderTypes = AllChains PactProvider } From df436a52b4603a4abb2bdd15f6dcd7e77a5ec3c2 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 16:13:23 -0800 Subject: [PATCH 018/378] Add NewPayload Cache --- chainweb.cabal | 1 + src/Chainweb/Miner/PayloadCache.hs | 270 +++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 src/Chainweb/Miner/PayloadCache.hs diff --git a/chainweb.cabal b/chainweb.cabal index 2516fbe46a..7832b469eb 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -209,6 +209,7 @@ library , Chainweb.Miner.Core , Chainweb.Miner.Miners , Chainweb.Miner.Pact + , Chainweb.Miner.PayloadCache , Chainweb.Miner.RestAPI , Chainweb.Miner.RestAPI.Client , Chainweb.Miner.RestAPI.Server diff --git a/src/Chainweb/Miner/PayloadCache.hs b/src/Chainweb/Miner/PayloadCache.hs new file mode 100644 index 0000000000..9ca0e4069f --- /dev/null +++ b/src/Chainweb/Miner/PayloadCache.hs @@ -0,0 +1,270 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} + +-- | +-- Module: Chainweb.Miner.PayloadCache +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- As payload providers propose new payloads for the latest blocks those are +-- cached by the miner. +-- +-- NOTE that consensus (and not the mining coordinator or payload provider) +-- decides when it makes sense to produce payloads ontop of a block. It does so +-- by notifying the payload provider in a syncToBlock call. This way block +-- production and mining can be supressed, for instance, during catchup or deep +-- catch merges. +-- +-- Because payloads do not depend on parents on adjacent chains, it always makes +-- sense to compute and cache new payloads because even as chains are still +-- blocked payloads can already be computed. +-- +-- The mining coordinator will request payloads for parent headers as chains +-- become unblocked. +-- +-- The cache will always provide the most recent payload for a given parent +-- header. However, because mining is asynchronous, it is possible that a miner +-- resolves a block for a non-recent parent header. Hence, all payloads for a +-- given parent header must be kept. +-- +-- Payloads can only be deleted when a parent header becomes outdated. Because +-- in Chainweb branches grow non-monotonically, there is no well-defined moment +-- when a payload becomes definitely outdated. However, usually, one or two +-- block heights should be sufficient. The diameter of the graph is a safe +-- choice. For any deeper reorgs payloads would have to be recomputed. Also, we +-- do not expect miners still working on older payloads anyways, so no miner +-- work would be lost. +-- +module Chainweb.Miner.PayloadCache +( PayloadCache(..) +, newIO +, clearSTM +, clearIO +, sizeSTM +, sizeIO +, insertSTM +, insertIO +, getLatestSTM +, getLatestIO +, awaitLatestSTM +, awaitLatestIO +, lookupSTM +, lookupIO + +-- * Misc Tools +, pruneSTM +, pruneIO +, payloadHashesSTM +, payloadHashesIO +) where + +import Chainweb.BlockHash +import Chainweb.BlockPayloadHash +import Chainweb.PayloadProvider +import Control.Concurrent.STM +import Data.List qualified as L +import Data.Map.Strict qualified as M +import Numeric.Natural +import Chainweb.BlockHeight + +-- | A new payload cache for a chain. +-- +-- Payloads are pruned from this cache when a new payload is received that is +-- more than graph diameter block heights ahead. Only items that have a block +-- height that is smaller than most recent item are guaranteed to be cached. +-- Items with larger block height may or may not be pruned. +-- +-- TODO: we should probably also limit the size, either globally or per block +-- height. +-- +-- TODO: This could be implemented more efficiently by using a ring buffer with +-- one map per block height. The buffer size would have to change when the graph +-- changes. However, graph changes are rare events and it is fine to always use +-- the largest graph defined by a fork point, even if the graph transition has +-- not yet occurred. +-- +data PayloadCache = PayloadCache + { _payloadCacheDepth :: !Natural + , _payloadCacheMap :: !(TVar (M.Map (RankedBlockHash, Int) NewPayload)) + } + +-- NOTE that we provide separate @STM@ and @IO@ versions for functions. The +-- reason for this is that @readTVarIO v@ is more efficient than @atomically +-- (readTVar v)@. In many cases the @IO@ version can benefit from that. + +newIO + :: Natural + -- ^ depth + -> IO PayloadCache +newIO n = PayloadCache n <$> newTVarIO mempty + +clearSTM :: PayloadCache -> STM () +clearSTM pc = writeTVar (_payloadCacheMap pc) mempty + +clearIO :: PayloadCache -> IO () +clearIO = atomically . clearSTM + +sizeSTM :: PayloadCache -> STM Natural +sizeSTM pc = fromIntegral . M.size <$> readTVar (_payloadCacheMap pc) + +sizeIO :: PayloadCache -> IO Natural +sizeIO pc = fromIntegral . M.size <$> readTVarIO (_payloadCacheMap pc) + +payloadHashesSTM :: PayloadCache -> STM [BlockPayloadHash] +payloadHashesSTM pc = fmap _newPayloadBlockPayloadHash . M.elems + <$> readTVar (_payloadCacheMap pc) + +payloadHashesIO :: PayloadCache -> IO [BlockPayloadHash] +payloadHashesIO pc = fmap _newPayloadBlockPayloadHash . M.elems + <$> readTVarIO (_payloadCacheMap pc) + +-- | Get the most recent payload for the given parent hash +-- +getLatestSTM + :: PayloadCache + -> RankedBlockHash + -> STM (Maybe NewPayload) +getLatestSTM pc rh = do + lookupValueGE (rh, minBound) <$> readTVar (_payloadCacheMap pc) >>= \case + Just x + | _newPayloadRankedParentHash x == rh -> return (Just x) + _ -> return Nothing + +-- | Get the most recent payload for the given parent hash +-- +getLatestIO + :: PayloadCache + -> RankedBlockHash + -> IO (Maybe NewPayload) +getLatestIO pc rh = + lookupValueGE (rh, minBound) <$> readTVarIO (_payloadCacheMap pc) >>= \case + Just x + | _newPayloadRankedParentHash x == rh -> return (Just x) + _ -> return Nothing + +-- | Await the most recent payload for the given parent hash +-- +awaitLatestSTM + :: PayloadCache + -> RankedBlockHash + -> STM NewPayload +awaitLatestSTM pc rh = getLatestSTM pc rh >>= maybe retry return + +-- | Await the most recent payload for the given parent hash +-- +awaitLatestIO + :: PayloadCache + -> RankedBlockHash + -> IO NewPayload +awaitLatestIO pc = atomically . awaitLatestSTM pc + +-- | Lookup a specific new payload by its block payload hash +-- +lookupSTM + :: PayloadCache + -> RankedBlockHash + -> BlockPayloadHash + -> STM (Maybe NewPayload) +lookupSTM pc rh pld = do + m <- readTVar (_payloadCacheMap pc) + + -- Focus on the ranked payload hash search for the payload hash starting + -- with the most recent value. + return + . L.find match + . fmap snd + . M.toDescList + . fst + . M.split (rh, maxBound) + . snd + . M.split (rh, minBound) + $ m + where + match = (== pld) . _newPayloadBlockPayloadHash + +-- | Lookup a specific new payload by its block payload hash +-- +lookupIO + :: PayloadCache + -> RankedBlockHash + -> BlockPayloadHash + -> IO (Maybe NewPayload) +lookupIO pc rh pld = do + m <- readTVarIO (_payloadCacheMap pc) + + -- Focus on the ranked payload hash search for the payload hash starting + -- with the most recent value. + return + . L.find match + . fmap snd + . M.toDescList + . fst + . M.split (rh, maxBound) + . snd + . M.split (rh, minBound) + $ m + where + match = (== pld) . _newPayloadBlockPayloadHash + +-- | Insert a new payload into the cache +-- +insertSTM + :: PayloadCache + -> NewPayload + -> STM () +insertSTM pc pld = + modifyTVar' (_payloadCacheMap pc) $ + M.insert key pld . prune (_payloadCacheDepth pc) h + where + h = _newPayloadParentHeight pld + p = _newPayloadParentHash pld + key = (RankedBlockHash h p, _newPayloadNumber pld) + +-- | Insert a new payload into the cache. The cache is pruned before the new +-- item is inserted. +-- +insertIO + :: PayloadCache + -> NewPayload + -> IO () +insertIO pc = atomically . insertSTM pc + +-- | Prune the cache for the given block height. Pruning is also performed each +-- time a new item is inserted into the cache +-- +pruneSTM + :: PayloadCache + -> BlockHeight + -> STM() +pruneSTM pc h = modifyTVar' (_payloadCacheMap pc) $! + prune (_payloadCacheDepth pc) h + +-- | Prune the cache for the given block height. Pruning is also performed each +-- time a new item is inserted into the cache +-- +pruneIO + :: PayloadCache + -> BlockHeight + -> IO () +pruneIO pc = atomically . pruneSTM pc + +-- -------------------------------------------------------------------------- -- +-- Utils + +lookupValueGE :: Ord k => k -> M.Map k v -> Maybe v +lookupValueGE k m = snd <$> M.lookupGE k m + +prune + :: Natural + -> BlockHeight + -> M.Map (RankedBlockHash, Int) v + -> M.Map (RankedBlockHash, Int) v +prune d h m + | h < fromIntegral d = m + | otherwise = snd $ M.split pivot m + where + pivot = (RankedBlockHash (h - fromIntegral d) nullBlockHash, minBound) + From 82595260c98effeb078c5463c89c4768f0a2feab Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 21:34:44 -0800 Subject: [PATCH 019/378] PayloadProvider P2P --- chainweb.cabal | 4 + src/Chainweb/PayloadProvider/P2P.hs | 295 ++++++++++++++++++ src/Chainweb/PayloadProvider/P2P/RestAPI.hs | 274 ++++++++++++++++ .../PayloadProvider/P2P/RestAPI/Client.hs | 90 ++++++ .../PayloadProvider/P2P/RestAPI/Server.hs | 172 ++++++++++ 5 files changed, 835 insertions(+) create mode 100644 src/Chainweb/PayloadProvider/P2P.hs create mode 100644 src/Chainweb/PayloadProvider/P2P/RestAPI.hs create mode 100644 src/Chainweb/PayloadProvider/P2P/RestAPI/Client.hs create mode 100644 src/Chainweb/PayloadProvider/P2P/RestAPI/Server.hs diff --git a/chainweb.cabal b/chainweb.cabal index 7832b469eb..e336fbb046 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -225,6 +225,10 @@ library , Chainweb.Payload.RestAPI.Client , Chainweb.PayloadProvider , Chainweb.PayloadProvider.Initialization + , Chainweb.PayloadProvider.P2P + , Chainweb.PayloadProvider.P2P.RestAPI + , Chainweb.PayloadProvider.P2P.RestAPI.Client + , Chainweb.PayloadProvider.P2P.RestAPI.Server , Chainweb.PowHash , Chainweb.Ranked , Chainweb.RestAPI diff --git a/src/Chainweb/PayloadProvider/P2P.hs b/src/Chainweb/PayloadProvider/P2P.hs new file mode 100644 index 0000000000..b148691670 --- /dev/null +++ b/src/Chainweb/PayloadProvider/P2P.hs @@ -0,0 +1,295 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} + +-- | +-- Module: Chainweb.PayloadProvider.P2P +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- FIXME: rename this module into Chainweb.PayloadProvider.PayloadStore +-- +module Chainweb.PayloadProvider.P2P +( PayloadStore(..) +, newPayloadStore +, getPayload +, getPayloadSimple +) where + +import Chainweb.BlockPayloadHash +import Chainweb.Storage.Table +import Chainweb.Utils +import Control.Monad.Catch +import Data.Aeson (object, (.=)) +import Data.Functor +import Data.LogMessage +import Data.PQueue +import Data.TaskMap +import Data.Text qualified as T +import Network.HTTP.Client qualified as HTTP +import P2P.Peer +import P2P.Session +import P2P.TaskQueue +import Servant.Client +import System.LogLevel +import Utils.Logging.Trace +import Chainweb.Storage.Table.HashMap (emptyTable) + +-- -------------------------------------------------------------------------- -- +-- Response Timeout Constants + +-- | For P2P queries we lower the response timeout to 500ms. This is tight +-- but guarantees that the task isn't blocked for too long and retries +-- another node quickly in case the first node isn't available. If no node +-- in the network is able to serve that quickly the local node is out of +-- luck. However, 500ms should be sufficient for a well-connected node +-- anywhere on the world. Otherwise, the node would have trouble anyways. +-- +taskResponseTimeout :: HTTP.ResponseTimeout +taskResponseTimeout = HTTP.responseTimeoutMicro 500_000 + +pullOriginResponseTimeout :: HTTP.ResponseTimeout +pullOriginResponseTimeout = HTTP.responseTimeoutMicro 1_000_000 + +-- | Set the response timeout on all requests made with the ClientEnv This +-- overwrites the default response timeout of the connection manager. +-- +setResponseTimeout :: HTTP.ResponseTimeout -> ClientEnv -> ClientEnv +setResponseTimeout t env = env + { makeClientRequest = \u r -> defaultMakeClientRequest u r <&> \req -> req + { HTTP.responseTimeout = t + } + } + +-- -------------------------------------------------------------------------- -- +-- Payload Store + +-- | P2P network service handle for payloads. +-- +-- It facilities storage and synchronization of payloads accross nodes in the +-- P2P network. +-- +-- Payload data is indexed by the ranked block payload hash. There are no +-- constraints on the payload data values. +-- +data PayloadStore tbl a = PayloadStore + { _payloadStoreTable :: !tbl + -- ^ Content addressed store for storing payload data + , _payloadStoreMemo :: !(TaskMap RankedBlockPayloadHash a) + -- ^ Internal memo table for active P2P tasks. Due to the way how cut + -- resolution works the same payload may be requested several times + -- while the first request is still processed. This map helps to avoid + -- running a P2P task redundantly. + , _payloadStoreQueue :: !(PQueue (Task ClientEnv a)) + -- ^ task queue for scheduling tasks with the P2P task server. This + -- Queue should be shared across chains (and possibly also across + -- payload providers, although for that we would have to hide the + -- payload type parameter, both here as well as in the HTTP client) + , _payloadStoreLogFunction :: !LogFunction + -- ^ LogFunction + , _payloadStoreMgr :: !HTTP.Manager + -- ^ Manager object for making HTTP requests + , _payloadStoreGetPayloadClient :: !(RankedBlockPayloadHash -> ClientM a) + -- ^ HTTP client for querying payloads (provided by the PayloadProvider) + -- + -- There is a default Payload REST API in + -- "Chainweb.PayloadProvider.P2P.RestAPI" that can be used in common + -- cases. + -- + -- NOTE: This used to have a ChainId parameter which allowed sharing of + -- the store between payload providers with the same payload type. This + -- could make it easier to limit concurrency across chains. If this is + -- needed the ChainId parameter would have to be re-added. + } + +-- FIXME: not sure whether the following instances are a good idea... + +-- | Expose the /local/ payload table. +-- +-- NOTE, this does /not/ look for payloads in P2P network. Data available in +-- this table is guaranteed to be fully validated, both by the execution client +-- as well as against the evaluation context. +-- +instance + (ReadableTable pdb RankedBlockPayloadHash a) + => ReadableTable (PayloadStore pdb a) RankedBlockPayloadHash a + where + tableLookup = tableLookup . _payloadStoreTable + tableLookupBatch' s = tableLookupBatch' (_payloadStoreTable s) + tableMember = tableMember . _payloadStoreTable + +-- | Expose the /local/ payload table. +-- +-- NOTE, this does /not/ look for payloads in P2P network. Data available in +-- this table is guaranteed to be fully validated, both by the execution client +-- as well as against the evaluation context. +-- +instance + (Table pdb RankedBlockPayloadHash a) + => Table (PayloadStore pdb a) RankedBlockPayloadHash a + where + tableInsert = tableInsert . _payloadStoreTable + tableInsertBatch s = tableInsertBatch (_payloadStoreTable s) + tableDelete s = tableDelete (_payloadStoreTable s) + tableDeleteBatch s = tableDeleteBatch (_payloadStoreTable s) + +-- -------------------------------------------------------------------------- -- +-- Initialization + +-- | FIXME: +-- +-- Why not move a generic payload DB here, so that it can be managed by the +-- payload store. (We can offer more complex payload providers to provide there +-- own version if desired.) +-- +-- FIXME: +-- +-- The queue needs to be hooked up to a p2p node. Where is that done? Given +-- that a single p2p node can serve many payload stores, the queue should +-- probably be passed as a parameter. +-- +newPayloadStore + :: Table tbl RankedBlockPayloadHash a + => HTTP.Manager + -- ^ Manager for P2P networking. This manager should be shared by all + -- P2P networks. + -> LogFunction + -> tbl + -- ^ initialized Payload DB. In particular it is expected that genesis + -- payloads are already available. + -> (RankedBlockPayloadHash -> ClientM a) + -> IO (PayloadStore tbl a) +newPayloadStore mgr logfun payloadDb cli = do + payloadTaskQueue <- newEmptyPQueue + payloadMemo <- new + return $! PayloadStore + { _payloadStoreTable = payloadDb + , _payloadStoreMemo = payloadMemo + , _payloadStoreQueue = payloadTaskQueue + , _payloadStoreLogFunction = logfun + , _payloadStoreMgr = mgr + , _payloadStoreGetPayloadClient = cli + } + +-- -------------------------------------------------------------------------- -- +-- Get Payload + +-- | Simple version of getPayload +-- +-- Mostly useful for testing and debugging +-- +getPayloadSimple + :: forall a tbl + . Table tbl RankedBlockPayloadHash a + => CasKeyType a ~ RankedBlockPayloadHash + => PayloadStore tbl a + -> RankedBlockPayloadHash + -> IO a +getPayloadSimple s r = do + candidates <- emptyTable + getPayload s candidates prio Nothing r + where + prio = Priority $ negate $ int $ _rankedBlockPayloadHashHeight r + +-- | Query a payload either from the local store, or the origin, or P2P network. +-- +-- The payload is only queried and not inserted into the local store. We want to +-- insert it only after it got validate by the payload provider. +-- +getPayload + :: forall a tbl candidateTbl + . Table tbl RankedBlockPayloadHash a + => Table candidateTbl RankedBlockPayloadHash a + => PayloadStore tbl a + -> candidateTbl + -> Priority + -- ^ larger values get precedence in the P2P task queue. A good + -- heuristics is to use the negated block height. + -> Maybe PeerInfo + -- ^ Peer from with the BlockPayloadHash originated, if available. + -> RankedBlockPayloadHash + -> IO a +getPayload s candidateStore priority maybeOrigin payloadHash = do + logfun Debug $ "getPayload: " <> sshow payloadHash + tableLookup candidateStore payloadHash >>= \case + Just !x -> return x + Nothing -> tableLookup tbl payloadHash >>= \case + Just !x -> return x + Nothing -> memo memoMap payloadHash $ \_ -> + pullOrigin payloadHash maybeOrigin >>= \case + Nothing -> do + t <- queryPayloadTask payloadHash + pQueueInsert queue t + awaitTask t + Just !x -> return x + where + mgr = _payloadStoreMgr s + tbl = _payloadStoreTable s + memoMap = _payloadStoreMemo s + queue = _payloadStoreQueue s + + logfun :: LogLevel -> T.Text -> IO () + logfun = _payloadStoreLogFunction s + + traceLogfun :: forall m . LogMessage m => LogLevel -> m -> IO () + traceLogfun = _payloadStoreLogFunction s + + taskMsg k msg = "payload task " <> sshow k <> " @ " <> sshow payloadHash <> ": " <> msg + + traceLabel subfun = + "Chainweb.PayloadProvider.P2P.getPayload." <> subfun + + -- Only used for logging trace telemetry + rankedHashJson rh = object + [ "height" .= _rankedBlockPayloadHashHeight rh + , "hash" .= _rankedBlockPayloadHashHash rh + ] + + -- | Try to pull a block payload from the given origin peer + -- + pullOrigin :: RankedBlockPayloadHash -> Maybe PeerInfo -> IO (Maybe a) + pullOrigin k Nothing = do + logfun Debug $ taskMsg k "no origin" + return Nothing + pullOrigin k (Just origin) = do + let originEnv = setResponseTimeout pullOriginResponseTimeout $ peerInfoClientEnv mgr origin + logfun Debug $ taskMsg k "lookup origin" + !r <- trace traceLogfun (traceLabel "pullOrigin") (rankedHashJson k) 0 + $ runClientM (_payloadStoreGetPayloadClient s k) originEnv + case r of + (Right !x) -> do + logfun Debug $ taskMsg k "received from origin" + return $ Just x + Left (e :: ClientError) -> do + logfun Debug $ taskMsg k $ "failed to receive from origin: " <> sshow e + return Nothing + + -- | Query a block payload via the task queue + -- + queryPayloadTask :: RankedBlockPayloadHash -> IO (Task ClientEnv a) + queryPayloadTask k = newTask (sshow k) priority $ \logg env -> do + logg @T.Text Debug $ taskMsg k "query remote block payload" + let taskEnv = setResponseTimeout taskResponseTimeout env + !r <- trace traceLogfun (traceLabel "queryPayloadTask") (rankedHashJson k) (let Priority i = priority in i) + $ runClientM (_payloadStoreGetPayloadClient s k) taskEnv + case r of + (Right !x) -> do + logg @T.Text Debug $ taskMsg k "received remote payload" + return x + Left (e :: ClientError) -> do + logg @T.Text Debug $ taskMsg k $ "failed: " <> sshow e + throwM e + diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs new file mode 100644 index 0000000000..0be6c9c8ae --- /dev/null +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs @@ -0,0 +1,274 @@ +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE DeriveGeneric #-} + +-- | +-- Module: Chainweb.PayloadProvider.P2P.RestAPI +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.P2P.RestAPI +( PayloadBatchLimit(..) +, BatchBody(..) +, IsPayloadProvider(..) +, RestPayload(..) + +-- * Payload API +, type PayloadApi +, payloadApi +, type PayloadGetApi +, payloadGetApi +, type PayloadPostApi +, payloadPostApi +-- ** Legacy API +, type ServicePayloadGetApi +, servicePayloadGetApi + +-- * SomePayloadApi +, somePayloadApi +, somePayloadApis +) where + +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB.RestAPI () +import Chainweb.BlockHeight +import Chainweb.ChainId +import Chainweb.RestAPI.Orphans () +import Chainweb.RestAPI.Utils +import Chainweb.Version +import Control.Monad.Identity +import Data.Aeson +import Data.Kind +import Data.Proxy +import Numeric.Natural +import Servant.API + +import Chainweb.PayloadProvider.Minimal.Payload qualified as Minimal +import Chainweb.Utils.Serialization (runPutL, putWord32le) +import Chainweb.Utils +import Chainweb.Payload qualified as Pact +import Chainweb.BlockPayloadHash +import GHC.Generics (Generic) +import Chainweb.Ranked + +-- -------------------------------------------------------------------------- -- +-- Constants + +-- | The maximum number of items that are returned in a batch +-- +newtype PayloadBatchLimit = PayloadBatchLimit Natural + deriving (Show, Eq) + deriving newtype (Ord, Enum, Num, Real, Integral, ToJSON, FromJSON) + +-- -------------------------------------------------------------------------- -- + +-- | Class of Payload Provider APIs +-- +-- We need to dispatch the APIs for different payload providers at type level +-- because servant composes APIs on type level. Once we run payload providers in +-- different processes with their own server instances we don't need that any +-- more. +-- +class + ( ToJSON (PayloadType p) + , FromJSON (PayloadType p) + , ToJSON (PayloadBatchType p) + , FromJSON (PayloadBatchType p) + , MimeRender OctetStream (PayloadType p) + , MimeRender OctetStream (PayloadBatchType p) + ) + => IsPayloadProvider (p :: PayloadProviderType) + where + type PayloadType p :: Type + type PayloadBatchType p :: Type + + batch :: [Maybe (PayloadType p)] -> PayloadBatchType p + + -- default Payload Batch limit + p2pPayloadBatchLimit :: PayloadBatchLimit + p2pPayloadBatchLimit = 0 + +-- -------------------------------------------------------------------------- -- + +-- | The body of a batch payload request. +-- +newtype BatchBody = BatchBody [RankedBlockPayloadHash] + deriving (Show, Eq, Generic) + deriving (ToJSON, FromJSON) via [JsonRanked "hash" BlockPayloadHash] + +-- -------------------------------------------------------------------------- -- +-- Wrapper for Rest API Payloads + +-- | Wrapper for Rest API Payloads +-- +-- The purpose of this newtype is to avoid IncohrentInstances when resolving +-- Servants type classes. +-- +newtype RestPayload a = RestPayload { _restPayload :: a } + deriving (Show, Eq) + deriving newtype (ToJSON, FromJSON) + +instance MimeRender OctetStream a => MimeRender OctetStream (RestPayload a) where + mimeRender p = mimeRender p . _restPayload + +-- -------------------------------------------------------------------------- -- +-- Legacy Payload GET API + +-- | The legacy API is not supported by all Payload Providers. +-- +-- @GET \/chainweb\/\\/\\/chain\/\\/payload\/\@ +-- +-- * For Pact the a parameter is PayloadData +-- * For EVM the a parameter is Header +-- +type ServicePayloadGetApi_ (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + = "payload" + :> Capture "BlockPayloadHash" BlockPayloadHash + :> QueryParam "height" BlockHeight + :> Get '[JSON, OctetStream] (RestPayload (PayloadType p)) + +type ServicePayloadGetApi (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + = 'ChainwebEndpoint v + :> ChainEndpoint c + :> ServicePayloadGetApi_ v c p + +servicePayloadGetApi + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + . IsPayloadProvider p + => Proxy (ServicePayloadGetApi v c p) +servicePayloadGetApi = Proxy + +-- -------------------------------------------------------------------------- -- +-- Payload GET API + +-- | @GET \/chainweb\/\\/\\/chain\/\\/height/\/payload\/\@ +-- +-- * For Pact the a parameter is PayloadData +-- * For EVM the a parameter is Header +-- +type PayloadGetApi_ (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + = "height" + :> Capture "BlockHeight" BlockHeight + :> "payload" + :> Capture "BlockPayloadHash" BlockPayloadHash + :> Get '[JSON, OctetStream] (RestPayload (PayloadType p)) + +type PayloadGetApi (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + = 'ChainwebEndpoint v :> ChainEndpoint c :> PayloadGetApi_ v c p + +payloadGetApi + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + . Proxy (PayloadGetApi v c p) +payloadGetApi = Proxy + +-- -------------------------------------------------------------------------- -- +-- Payload POST API + +-- | @POST \/chainweb\/\\/\\/chain\/\\/payload\/batch@ +-- +-- The query may return any number (including none) of the requested payload +-- data. Results are returned in any order. +-- +type PayloadPostApi_ (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + = "payload" + :> "batch" + :> ReqBody '[JSON] BatchBody + :> Post '[JSON, OctetStream] (RestPayload (PayloadBatchType p)) + +type PayloadPostApi (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + = 'ChainwebEndpoint v :> ChainEndpoint c :> PayloadPostApi_ v c p + +payloadPostApi + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + . Proxy (PayloadPostApi v c p) +payloadPostApi = Proxy + +-- -------------------------------------------------------------------------- -- +-- Payload API + +type PayloadApi v c p + = PayloadGetApi v c p + :<|> PayloadPostApi v c p + +payloadApi + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + . Proxy (PayloadApi v c p) +payloadApi = Proxy + +-- -------------------------------------------------------------------------- -- +-- Some Payload API + +-- | Dispatch provider specific APIs +-- +-- FIXME: Should we split this up and move it into the scope of the respective +-- payload provider. +-- +somePayloadApi + :: IsPayloadProvider 'MinimalProvider + => IsPayloadProvider 'PactProvider + -- => IsPayloadProvider 'EvmProvider + => ChainwebVersion + -> ChainId + -> SomeApi +somePayloadApi v c = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v') <- return $ someChainwebVersionVal v + SomeChainIdT (_ :: Proxy c') <- return $ someChainIdVal c + case provider of + MinimalProvider -> + return $! SomeApi (payloadApi @v' @c' @'MinimalProvider) + PactProvider -> + return $! SomeApi (payloadApi @v' @c' @'PactProvider) + EvmProvider -> + error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: IsPayloadProvider not implemented for EVM" + -- return $! SomeApi (payloadApi @v' @c' @'EvmProvider) + where + provider :: PayloadProviderType + provider = payloadProviderTypeForChain v c + +somePayloadApis :: ChainwebVersion -> [ChainId] -> SomeApi +somePayloadApis v = mconcat . fmap (somePayloadApi v) + +-- -------------------------------------------------------------------------- -- +-- Implementations of IsPayloadProvider + +instance IsPayloadProvider MinimalProvider where + type PayloadType MinimalProvider = Minimal.Payload + type PayloadBatchType MinimalProvider = [Minimal.Payload] + + p2pPayloadBatchLimit = 20 -- FIXME + +instance MimeRender OctetStream Minimal.Payload where + mimeRender _ = runPutL . Minimal.encodePayload + +instance MimeRender OctetStream [Minimal.Payload] where + mimeRender _ ps = runPutL $ do + putWord32le (int $ length ps) + mapM_ Minimal.encodePayload ps + +-- FIXME: fix the following instance to conform with the current API (or even +-- better fix the current bad binary encoding of payload data). + +instance IsPayloadProvider PactProvider where + type PayloadType PactProvider = Pact.PayloadData + type PayloadBatchType PactProvider = Pact.PayloadDataList + + p2pPayloadBatchLimit = 20 -- FIXME + diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI/Client.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI/Client.hs new file mode 100644 index 0000000000..884fde9b91 --- /dev/null +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI/Client.hs @@ -0,0 +1,90 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +-- | +-- Module: Chainweb.PayloadProvider.P2P.RestAPI.Client +-- Copyright: Copyright © 2018 - 2020 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- Client implementation of the block payload REST API +-- +module Chainweb.PayloadProvider.P2P.RestAPI.Client +( payloadClient +-- , payloadBatchClient +-- , outputsClient +-- , outputsBatchClient +) where + +import Servant.Client + +-- internal modules + +import Chainweb.ChainId +import Chainweb.PayloadProvider.P2P.RestAPI +import Chainweb.RestAPI.Orphans () +import Chainweb.Version +import Chainweb.BlockPayloadHash + +-- -------------------------------------------------------------------------- -- +-- GET Payload Client + +payloadClient + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + . KnownChainwebVersionSymbol v + => IsPayloadProvider p + => KnownChainIdSymbol c + => RankedBlockPayloadHash + -> ClientM (PayloadType p) +payloadClient rh = _restPayload <$> client (payloadGetApi @v @c @p) height hash + where + height = _rankedBlockPayloadHashHeight rh + hash = _rankedBlockPayloadHashHash rh + +-- payloadClient +-- :: ChainwebVersion +-- -> ChainId +-- -> RankedBlockPayloadHash +-- -> ClientM (RestPayload (PayloadType p)) +-- payloadClient v c h = runIdentity $ do +-- SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +-- SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c +-- case provider of +-- MinimalProvider -> +-- return $! payloadClient_ @v @c @'MinimalProvider h +-- PactProvider -> +-- return $! payloadClient_ @v @c @'PactProvider h +-- -- EvmProvider -> +-- -- return $! payloadClient_ @v @c @'PactProvider h +-- where +-- provider :: PayloadProviderType +-- provider = payloadProviderTypeForChain v c + +-- -- -------------------------------------------------------------------------- -- +-- -- Post Payload Batch Client +-- +-- payloadBatchClient_ +-- :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProvider) +-- . KnownChainwebVersionSymbol v +-- => KnownChainIdSymbol c +-- => BatchBody +-- -> ClientM PayloadDataList +-- payloadBatchClient_ = client (payloadPostApi @v @c) +-- +-- -- The query may return any number (including none) of the requested payload +-- -- data. Results are returned in any order. +-- -- +-- payloadBatchClient +-- :: ChainwebVersion +-- -> ChainId +-- -> BatchBody +-- -> ClientM PayloadDataList +-- payloadBatchClient v c k = runIdentity $ do +-- SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +-- SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c +-- return $ payloadBatchClient_ @v @c k +-- diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI/Server.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI/Server.hs new file mode 100644 index 0000000000..1125a87088 --- /dev/null +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI/Server.hs @@ -0,0 +1,172 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE AllowAmbiguousTypes #-} + +-- | +-- Module: Chainweb.Payload.RestAPI.Server +-- Copyright: Copyright © 2018 - 2020 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- Server implementation of the block payload REST API +-- +-- FIXME: consider to not expose the payload table directly, but only through an +-- API (possibly in the PayloadProvider class or may dedicated +-- PayloadProviderApi class). +-- Or, alternatively, create the apis along with the apis in the provider +-- initialization function internally and not in the resources module. +-- +module Chainweb.PayloadProvider.P2P.RestAPI.Server +( + somePayloadServer + +-- * Single Chain Server +, payloadApp +, payloadApiLayout +) where + +import Control.Applicative +import Control.Monad +import Control.Monad.IO.Class + +import Data.Aeson +import Data.Function +import Data.Maybe +import Data.Proxy +import qualified Data.Text.IO as T + +import Prelude hiding (lookup) + +import Servant + +-- internal modules + +import Chainweb.BlockHeader +import Chainweb.ChainId +import Chainweb.PayloadProvider.P2P.RestAPI +import Chainweb.RestAPI.Orphans () +import Chainweb.RestAPI.Utils +import Chainweb.Utils (int) +import Chainweb.Version + +import Chainweb.BlockHeight (BlockHeight) +import Chainweb.Storage.Table +import Chainweb.BlockPayloadHash + +-- -------------------------------------------------------------------------- -- +-- Utils + +err404Msg :: ToJSON msg => msg -> ServerError +err404Msg msg = setErrJSON msg err404 + +-- -------------------------------------------------------------------------- -- +-- GET Payload Handler + +-- | Query the 'BlockPayload' by its 'BlockPayloadHash' +-- +payloadHandler + :: forall tbl (p :: PayloadProviderType) + . ReadableTable tbl RankedBlockPayloadHash (PayloadType p) + => IsPayloadProvider p + => tbl + -> BlockHeight + -> BlockPayloadHash + -> Handler (RestPayload (PayloadType p)) +payloadHandler db h k = liftIO (tableLookup db rh) >>= \case + Nothing -> throwError $ err404Msg $ object + [ "reason" .= ("key not found" :: String) + , "key" .= k + ] + Just e -> return $ RestPayload e + where + rh = RankedBlockPayloadHash h k + +-- -------------------------------------------------------------------------- -- +-- POST Payload Batch Handler + +payloadBatchHandler + :: forall tbl (p :: PayloadProviderType) + . ReadableTable tbl RankedBlockPayloadHash (PayloadType p) + => IsPayloadProvider p + => PayloadBatchLimit + -> tbl + -> BatchBody + -> Handler (RestPayload (PayloadBatchType p)) +payloadBatchHandler 0 _ _ = throwError $ err404Msg @String "payload batch limit is 0" +payloadBatchHandler batchLimit db (BatchBody ks) = liftIO $ do + r <- batch @p <$> tableLookupBatch db (limit ks) + return $ RestPayload r + where + limit = take $ int (min (p2pPayloadBatchLimit @p) batchLimit) + +-- -------------------------------------------------------------------------- -- +-- Payload API Server + +payloadServer + :: forall tbl v c p + . ReadableTable tbl RankedBlockPayloadHash (PayloadType p) + => IsPayloadProvider p + => PayloadBatchLimit + -> tbl + -> Server (PayloadApi v c p) +payloadServer batchLimit db + = payloadHandler @tbl @p db + :<|> payloadBatchHandler @tbl @p batchLimit db + +-- -------------------------------------------------------------------------- -- +-- Application for a single PayloadDb + +payloadApp + :: forall tbl v c p + . ReadableTable tbl RankedBlockPayloadHash (PayloadType p) + => IsPayloadProvider p + => KnownChainwebVersionSymbol v + => KnownChainIdSymbol c + => PayloadBatchLimit + -> tbl + -> Application +payloadApp batchLimit db = serve + (Proxy @(PayloadApi v c p)) + (payloadServer @tbl @v @c @p batchLimit db) + +payloadApiLayout + :: forall tbl v c (p :: PayloadProviderType) + . KnownChainwebVersionSymbol v + => IsPayloadProvider p + => KnownChainIdSymbol c + => tbl + -> IO () +payloadApiLayout _ = T.putStrLn $ layout (Proxy @(PayloadApi v c p)) + +-- -------------------------------------------------------------------------- -- +-- Multichain Server + +somePayloadServer + :: forall tbl v c p + . ReadableTable tbl RankedBlockPayloadHash (PayloadType p) + => IsPayloadProvider p + => KnownChainwebVersionSymbol v + => KnownChainIdSymbol c + => PayloadBatchLimit + -> tbl + -> SomeServer +somePayloadServer batchLimit db = SomeServer + (Proxy @(PayloadApi v c p)) + (payloadServer @tbl @v @c @p batchLimit db) + +-- somePayloadServers +-- :: ReadableTable tbl RankedBlockPayloadHash a +-- => ChainwebVersion +-- -> PayloadBatchLimit +-- -> [(ChainId, PayloadDb tbl)] +-- -> SomeServer +-- somePayloadServers v batchLimit +-- = mconcat . fmap (somePayloadServer batchLimit . uncurry (somePayloadDbVal v)) From db213143a9f16e6d75e50400048778462956ef0d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 18 Jan 2025 23:30:06 -0800 Subject: [PATCH 020/378] Add Chainweb.Core.Brief module --- chainweb.cabal | 1 + src/Chainweb/Core/Brief.hs | 117 ++++++++++++++++++++++++++++++ src/Chainweb/Cut/Create.hs | 4 + src/Chainweb/Miner/Coordinator.hs | 1 + 4 files changed, 123 insertions(+) create mode 100644 src/Chainweb/Core/Brief.hs diff --git a/chainweb.cabal b/chainweb.cabal index e336fbb046..bddd96e0c9 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -177,6 +177,7 @@ library , Chainweb.Chainweb.MinerResources , Chainweb.Chainweb.PeerResources , Chainweb.Chainweb.PruneChainDatabase + , Chainweb.Core.Brief , Chainweb.Counter , Chainweb.Crypto.MerkleLog , Chainweb.Cut diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs new file mode 100644 index 0000000000..a19a9f2383 --- /dev/null +++ b/src/Chainweb/Core/Brief.hs @@ -0,0 +1,117 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeSynonymInstances #-} + +-- | +-- Module: Chainweb.Core.Brief +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.Core.Brief +( Brief(..) +, toTextShort + +-- * Utils +, briefJson +, BriefJson +, briefValue +) where + +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.ChainId +import Chainweb.Cut +import Chainweb.Cut.CutHashes +import Chainweb.PayloadProvider +import Chainweb.Ranked +import Chainweb.Utils +import Control.Lens +import Data.HashMap.Strict qualified as HM +import Data.List qualified as L +import Data.Text qualified as T +import Numeric.Natural +import Data.Aeson + +-- -------------------------------------------------------------------------- -- +-- Adhoc class for brief logging output + +class Brief a where + brief :: a -> T.Text + +toTextShort :: HasTextRepresentation a => a -> T.Text +toTextShort = T.take 6 . toText + +-- -------------------------------------------------------------------------- -- +-- Generic Instances + +instance (Brief a, Brief b) => Brief (a,b) where + brief (a,b) = "(" <> brief a <> "," <> brief b <> ")" + +instance (Brief a, Brief b) => Brief (Either a b) where + brief (Left a) = "left:" <> brief a + brief (Right b) = "right:" <> brief b + +instance Brief a => Brief (Maybe a) where + brief (Just a) = brief a + brief Nothing = "nothing" + +instance Brief a => Brief [a] where + brief l = "[" <> (T.intercalate "," $ brief <$> l) <> "]" + +-- -------------------------------------------------------------------------- -- +-- Core Chainweb Types + +instance Brief a => Brief (Ranked a) where + brief (Ranked r h) = sshow r <> ":" <> brief h + +instance Brief CutHeight where brief = toText . int @_ @Natural +instance Brief BlockHeight where brief = toText . int @_ @Natural +instance Brief CutId where brief = toTextShort +instance Brief ChainId where brief = toText +instance Brief BlockHash where brief = toTextShort +instance Brief BlockPayloadHash where brief = toTextShort +instance Brief BlockHeader where brief = brief . view blockHash +instance Brief ParentHeader where brief = brief . _parentHeader + +instance Brief BlockHashWithHeight where + brief a = brief (_bhwhHeight a) <> ":" <> brief (_bhwhHash a) + +instance Brief CutHashes where + brief c = T.intercalate ":" + [ brief (_cutHashesId c) + , brief (_cutHashesHeight c) + , brief (L.sort $ HM.toList $ _cutHashes c) + ] + +instance Brief Cut where + brief = brief . cutToCutHashes Nothing + +instance Brief NewPayload where + brief np = brief (_newPayloadChainId np) + <> ":" <> brief (_newPayloadRankedParentHash np) + +instance Brief SyncState where + brief = briefJson + +-- -------------------------------------------------------------------------- -- +-- Utils + +newtype BriefJson a = BriefJson a + +instance ToJSON a => ToJSON (BriefJson a) where + toJSON (BriefJson a) = briefValue $ toJSON a + +briefJson :: ToJSON a => a -> T.Text +briefJson = encodeToText . BriefJson + +briefValue :: Value -> Value +briefValue (Object o) = Object (briefValue <$> o) +briefValue (Array a) = Array (briefValue <$> a) +briefValue (String t) = String (toTextShort t) +briefValue n = n + diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index aecf68c481..7951c9f806 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -93,6 +93,7 @@ import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Utils +import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes @@ -390,6 +391,9 @@ data InvalidSolvedHeader = InvalidSolvedHeader BlockHeader T.Text instance Exception InvalidSolvedHeader +instance Brief SolvedWork where + brief (SolvedWork hdr) = "SolvedWork" <> ":" <> brief hdr + -- | Extend a Cut with a solved work value. -- -- The function verifies that the solved work actually is a valid extension of diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index d5745eb2a0..1b98071458 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -80,6 +80,7 @@ import Chainweb.Cut hiding (join) import Chainweb.Cut.Create import Chainweb.Cut.CutHashes import Chainweb.CutDB +import Chainweb.Core.Brief import Chainweb.Logger (Logger, logFunction) import Chainweb.Logging.Miner import Chainweb.Miner.Config From ec8042989b8aaba0f5c652bc04a7cd829661b4c1 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 21 Jan 2025 10:01:10 -0800 Subject: [PATCH 021/378] HasChainwebVersion and HasChainId instances for Cut.Create.SolvedWork --- src/Chainweb/Cut/Create.hs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 7951c9f806..e706c0dd4e 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -84,6 +84,7 @@ import Chainweb.BlockHeader import Chainweb.BlockHeader.Validation import Chainweb.BlockHeight import Chainweb.ChainValue +import Chainweb.Core.Brief import Chainweb.Cut import Chainweb.Cut.CutHashes import Chainweb.Difficulty @@ -93,13 +94,21 @@ import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Utils -import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes -- | Witnesses that a cut can be extended for the respective header. -- +-- Essentially, this guarantees the following: +-- +-- 1. The parent header is in the cut. +-- 2. Adjacent hashes match the adjacent chains in the chain graph. +-- 3. Adjacent hashes are in the cut or parents of headers in the cut. +-- +-- There are a few additional corner cases related to genesis blocks and graph +-- transitions. +-- data CutExtension = CutExtension { _cutExtensionCut' :: !Cut -- ^ the cut which is to be extended @@ -107,9 +116,11 @@ data CutExtension = CutExtension -- This is overly restrictive, since the same cut extension can be -- valid for more than one cut. It's fine for now. , _cutExtensionParent' :: !ParentHeader - -- ^ the header onto which the new block is created. It is expected + -- ^ The header onto which the new block is created. It is expected -- that this header is contained in the cut. , _cutExtensionAdjacentHashes' :: !BlockHashRecord + -- ^ The adjacent hashes for the new block. These are either part of the + -- cut or are parents of headers in the cut. } deriving (Show, Eq, Generic) @@ -385,6 +396,12 @@ encodeSolvedWork (SolvedWork hdr) = encodeBlockHeaderWithoutHash hdr decodeSolvedWork :: Get SolvedWork decodeSolvedWork = SolvedWork <$> decodeBlockHeaderWithoutHash +instance HasChainId SolvedWork where + _chainId (SolvedWork hdr) = _chainId hdr + +instance HasChainwebVersion SolvedWork where + _chainwebVersion (SolvedWork hdr) = _chainwebVersion hdr + data InvalidSolvedHeader = InvalidSolvedHeader BlockHeader T.Text deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) @@ -406,6 +423,9 @@ instance Brief SolvedWork where -- The result is 'Nothing' if the given cut can't be extended with the solved -- work. -- +-- FIXME: this should be the only function that can pattern match the Header out +-- of a 'SolvedWork' value. +-- extend :: MonadThrow m => Cut From f9e689e997ba7fdefeb8603a2e9284b8ad9beaed Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 9 Jan 2025 01:47:00 -0800 Subject: [PATCH 022/378] Add solvedWorkHeight Getter for SolvedWork --- src/Chainweb/Cut/Create.hs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index e706c0dd4e..c9ed6a13bb 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -56,6 +56,7 @@ module Chainweb.Cut.Create -- * Solved Work , SolvedWork(..) +, solvedWorkHeight , encodeSolvedWork , decodeSolvedWork , extend @@ -396,6 +397,9 @@ encodeSolvedWork (SolvedWork hdr) = encodeBlockHeaderWithoutHash hdr decodeSolvedWork :: Get SolvedWork decodeSolvedWork = SolvedWork <$> decodeBlockHeaderWithoutHash +solvedWorkHeight :: Getter SolvedWork BlockHeight +solvedWorkHeight = to (\(SolvedWork hdr) -> hdr) . blockHeight + instance HasChainId SolvedWork where _chainId (SolvedWork hdr) = _chainId hdr From 8dea929e7f4c732f8c4bfba85a79ac6d9c060058 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:50 -0800 Subject: [PATCH 023/378] BlockHeaderDB: remove Block APIs --- src/Chainweb/BlockHeaderDB/RestAPI.hs | 49 +------- src/Chainweb/BlockHeaderDB/RestAPI/Client.hs | 39 ------ src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 125 +++---------------- 3 files changed, 25 insertions(+), 188 deletions(-) diff --git a/src/Chainweb/BlockHeaderDB/RestAPI.hs b/src/Chainweb/BlockHeaderDB/RestAPI.hs index 3892b62eec..5dce533f55 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI.hs @@ -84,14 +84,11 @@ module Chainweb.BlockHeaderDB.RestAPI , headersApi , HashesApi , hashesApi -, BlocksApi -, BranchBlocksApi ) where import Data.Aeson import Data.Bifunctor import Data.ByteString.Lazy qualified as L -import Data.Maybe import Data.Proxy import Data.Text (Text) @@ -192,6 +189,7 @@ instance MimeRender JsonBlockHeaderObject BlockHeaderPage where {-# INLINE mimeRender #-} -- -------------------------------------------------------------------------- -- +-- TODO: this instance do *not* belong here instance MimeRender OctetStream BlockPayload where mimeRender _ = L.fromStrict . encodeBlockPayloads @@ -467,48 +465,15 @@ p2pHeaderApi . Proxy (P2pHeaderApi v c) p2pHeaderApi = Proxy --- -------------------------------------------------------------------------- -- -type BlocksApi_ - = "block" - :> PageParams (NextItem BlockHash) - :> FilterParams - :> Get '[JSON] BlockPage - --- | @GET \/chainweb\/\\/\\/chain\/\\/block@ --- --- Returns blocks in the block header tree database in ascending order --- with respect to the children relation. --- --- Note that for blocks on different branches, the order isn't determined. --- Therefore a block of higher block height can be returned before a block of --- lower block height. --- -type BlocksApi (v :: ChainwebVersionT) (c :: ChainIdT) - = 'ChainwebEndpoint v :> ChainEndpoint c :> Reassoc BlocksApi_ - --- -------------------------------------------------------------------------- -- -type BranchBlocksApi_ - = "block" :> "branch" - :> PageParams (NextItem BlockHash) - :> MinHeightParam - :> MaxHeightParam - :> ReqBody '[JSON] (BranchBounds BlockHeaderDb) - :> Post '[JSON] BlockPage - -type BranchBlocksApi (v :: ChainwebVersionT) (c :: ChainIdT) - = 'ChainwebEndpoint v :> ChainEndpoint c :> Reassoc BranchBlocksApi_ - -- -------------------------------------------------------------------------- -- -- | BlockHeaderDb Api -- type BlockHeaderDbApi v c = HashesApi v c :<|> HeadersApi v c - :<|> BlocksApi v c :<|> HeaderApi v c :<|> BranchHashesApi v c :<|> BranchHeadersApi v c - :<|> BranchBlocksApi v c -- | Restricted P2P BlockHeader DB API -- @@ -522,8 +487,6 @@ type P2pBlockHeaderDbApi v c data HeaderUpdate = HeaderUpdate { _huHeader :: !(ObjectEncoded BlockHeader) - , _huPayloadWithOutputs :: !(Maybe PayloadWithOutputs) - , _huTxCount :: !Int , _huPowHash :: !Text , _huTarget :: !Text } @@ -532,11 +495,8 @@ data HeaderUpdate = HeaderUpdate headerUpdateProperties :: KeyValue e kv => HeaderUpdate -> [kv] headerUpdateProperties o = [ "header" .= _huHeader o - , "txCount" .= _huTxCount o , "powHash" .= _huPowHash o , "target" .= _huTarget o - ] <> concatMap maybeToList - [ ("payloadWithOutputs" .=) <$> _huPayloadWithOutputs o ] {-# INLINE headerUpdateProperties #-} @@ -549,14 +509,15 @@ instance ToJSON HeaderUpdate where instance FromJSON HeaderUpdate where parseJSON = withObject "HeaderUpdate" $ \o -> HeaderUpdate <$> o .: "header" - <*> o .:? "payloadWithOutputs" - <*> o .: "txCount" <*> o .: "powHash" <*> o .: "target" {-# INLINE parseJSON #-} type BlockStreamApi_ = - "block" :> "updates" :> Raw :<|> + -- FIXME: the block API endpoint should be part of the payload provider. + -- Consensus has no access to the payload data + -- "block" :> "updates" :> Raw :<|> + "header" :> "updates" :> Raw -- | A stream of all new blocks that are accepted into the true `Cut`. diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Client.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Client.hs index 1cc32f4478..36217aa8b4 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Client.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Client.hs @@ -34,7 +34,6 @@ module Chainweb.BlockHeaderDB.RestAPI.Client , headersClientJson , headersClientJsonPretty -, blocksClient , branchHashesClient_ , branchHashesClient @@ -44,8 +43,6 @@ module Chainweb.BlockHeaderDB.RestAPI.Client , branchHeadersClientContentType_ , branchHeadersClientJson , branchHeadersClientJsonPretty - -, branchBlocksClient ) where import Control.Monad.Identity @@ -215,26 +212,6 @@ headersClientJsonPretty (FromSingChainwebVersion (SChainwebVersion :: Sing v)) c (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ headersClientContentType_ @v @c @JsonBlockHeaderObject limit start minr maxr -blocksClient - :: ChainwebVersion - -- ^ The remote chainweb that you wish to query from. - -> ChainId - -- ^ The remote chain within the web that you wish to query from. - -> Maybe Limit - -- ^ The number of responses per-`Page` to return. - -> Maybe (NextItem BlockHash) - -- ^ The first header you want to see within the `Page`. - -- `Page` contains a field `_pageNext`, which can be used - -- to produce the value needed for subsequent calls. - -> Maybe MinRank - -- ^ Filter: no header of `BlockHeight` lower than this will be returned. - -> Maybe MaxRank - -- ^ Filter: no header of `BlockHeight` higher than this will be returned. - -> ClientM BlockPage -blocksClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) c limit start minr maxr = runIdentity $ do - (SomeSing (SChainId :: Sing c)) <- return $ toSing c - return $ client (Proxy @(SetRespBodyContentType JSON (BlocksApi v c))) limit start minr maxr - -- -------------------------------------------------------------------------- -- -- Branch Hashes Client @@ -333,22 +310,6 @@ branchHeadersClientJsonPretty v c limit start minr maxr bounds = runIdentity $ d SomeSing (SChainId :: Sing c) <- return $ toSing c return $ branchHeadersClientContentType_ @v @c @JsonBlockHeaderObject limit start minr maxr bounds --- -------------------------------------------------------------------------- -- --- Branch Blocks Client - -branchBlocksClient - :: ChainwebVersion - -> ChainId - -> Maybe Limit - -> Maybe (NextItem BlockHash) - -> Maybe MinRank - -> Maybe MaxRank - -> BranchBounds BlockHeaderDb - -> ClientM BlockPage -branchBlocksClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) c limit start minr maxr bounds = runIdentity $ do - (SomeSing (SChainId :: Sing c)) <- return $ toSing c - return $ client (Proxy @(SetRespBodyContentType JSON (BranchBlocksApi v c))) limit start minr maxr bounds - -- -------------------------------------------------------------------------- -- -- Hashes Client diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index 7bda07fa04..e9579e4526 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -61,22 +61,18 @@ import qualified Streaming.Prelude as SP -- internal modules -import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.BlockHeaderDB.RestAPI import Chainweb.ChainId -import Chainweb.CutDB (CutDb, blockDiffStream, cutDbPayloadDb) +import Chainweb.CutDB (CutDb, blockDiffStream) import Chainweb.Difficulty (showTargetHex) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore import Chainweb.PowHash (powHashBytes) import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils import Chainweb.TreeDB import Chainweb.Utils.Paging import Chainweb.Version -import Chainweb.Block -- -------------------------------------------------------------------------- -- -- Handler Tools @@ -210,43 +206,6 @@ branchHeadersHandler db (BranchBoundsLimit boundsLimit) maxLimit limit next minr where effectiveLimit = min maxLimit <$> (limit <|> Just maxLimit) --- | Query Branch Blocks of the database. --- --- Cf. "Chainweb.BlockHeaderDB.RestAPI" for more details --- -branchBlocksHandler - :: CanReadablePayloadCas tbl - => BlockHeaderDb - -> PayloadDb tbl - -> BranchBoundsLimit - -> Limit - -- ^ max limit - -> Maybe Limit - -> Maybe (NextItem BlockHash) - -> Maybe MinRank - -> Maybe MaxRank - -> BranchBounds BlockHeaderDb - -> Handler (Page (NextItem BlockHash) Block) -branchBlocksHandler bhdb pdb (BranchBoundsLimit boundsLimit) maxLimit limit next minr maxr bounds - | fromIntegral (length (_branchBoundsUpper bounds)) > boundsLimit = throwError $ err400Msg $ - "upper branch bound limit exceeded. Only " <> show boundsLimit <> " values are supported." - | fromIntegral (length (_branchBoundsLower bounds)) > boundsLimit = throwError $ err400Msg $ - "lower branch bound limit exceeded. Only " <> show boundsLimit <> " values are supported." - | otherwise = do - nextChecked <- traverse (traverse $ checkKey bhdb) next - checkedBounds <- checkBounds bhdb bounds - liftIO - $ branchEntries bhdb nextChecked (succ <$> effectiveLimit) minr maxr - (_branchBoundsLower checkedBounds) - (_branchBoundsUpper checkedBounds) - $ finiteStreamToPage (key . _blockHeader) effectiveLimit . void . SP.mapM grabPayload - where - effectiveLimit = min maxLimit <$> (limit <|> Just maxLimit) - grabPayload :: BlockHeader -> IO Block - grabPayload h = do - Just x <- lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) - pure (Block h x) - -- | Every `TreeDb` key within a given range. -- -- Cf. "Chainweb.BlockHeaderDB.RestAPI" for more details @@ -292,33 +251,6 @@ headersHandler db maxLimit limit next minr maxr = do where effectiveLimit = min maxLimit <$> (limit <|> Just maxLimit) --- | Every block within a given range. --- --- Cf. "Chainweb.BlockHeaderDB.RestAPI" for more details --- -blocksHandler - :: CanReadablePayloadCas tbl - => BlockHeaderDb - -> PayloadDb tbl - -> Limit - -- ^ max limit - -> Maybe Limit - -> Maybe (NextItem BlockHash) - -> Maybe MinRank - -> Maybe MaxRank - -> Handler BlockPage -blocksHandler bhdb pdb maxLimit limit next minr maxr = do - nextChecked <- traverse (traverse $ checkKey bhdb) next - liftIO - $ entries bhdb nextChecked (succ <$> effectiveLimit) minr maxr - $ finitePrefixOfInfiniteStreamToPage (key . _blockHeader) effectiveLimit . void . SP.mapM grabPayload - where - effectiveLimit = min maxLimit <$> (limit <|> Just maxLimit) - grabPayload :: BlockHeader -> IO Block - grabPayload h = do - Just x <- lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) - pure (Block h x) - -- | Query a single 'BlockHeader' by its 'BlockHash' -- -- Cf. "Chainweb.BlockHeaderDB.RestAPI" for more details @@ -342,18 +274,14 @@ headerHandler db k = liftIO (lookup db k) >>= \case -- Full BlockHeader DB API (used for Service API) -- blockHeaderDbServer - :: CanReadablePayloadCas tbl - => BlockHeaderDb_ v c - -> PayloadDb tbl + :: BlockHeaderDb_ v c -> Server (BlockHeaderDbApi v c) -blockHeaderDbServer (BlockHeaderDb_ db) pdb +blockHeaderDbServer (BlockHeaderDb_ db) = hashesHandler db :<|> headersHandler db defaultEntryLimit - :<|> blocksHandler db pdb defaultEntryLimit :<|> headerHandler db :<|> branchHashesHandler db :<|> branchHeadersHandler db defaultBoundsLimit defaultEntryLimit - :<|> branchBlocksHandler db pdb defaultBoundsLimit defaultEntryLimit -- Restricted P2P BlockHeader DB API -- @@ -367,23 +295,18 @@ p2pBlockHeaderDbServer (BlockHeaderDb_ db) -- Multichain Server someBlockHeaderDbServer - :: CanReadablePayloadCas tbl - => SomeBlockHeaderDb - -> PayloadDb tbl + :: SomeBlockHeaderDb -> SomeServer -someBlockHeaderDbServer (SomeBlockHeaderDb (db :: BlockHeaderDb_ v c)) pdb - = SomeServer (Proxy @(BlockHeaderDbApi v c)) (blockHeaderDbServer db pdb) +someBlockHeaderDbServer (SomeBlockHeaderDb (db :: BlockHeaderDb_ v c)) + = SomeServer (Proxy @(BlockHeaderDbApi v c)) (blockHeaderDbServer db) someBlockHeaderDbServers - :: CanReadablePayloadCas tbl - => ChainwebVersion + :: ChainwebVersion -> [(ChainId, BlockHeaderDb)] - -> [(ChainId, PayloadDb tbl)] -> SomeServer -someBlockHeaderDbServers v cdbs pdbs = mconcat - [ someBlockHeaderDbServer (someBlockHeaderDbVal v cid cdb) pdb - | (cid, (cdb, pdb)) <- - Map.toList $ Map.intersectionWith (,) (Map.fromList cdbs) (Map.fromList pdbs) +someBlockHeaderDbServers v cdbs = mconcat + [ someBlockHeaderDbServer (someBlockHeaderDbVal v cid cdb) + | (cid, cdb) <- Map.toList $ (Map.fromList cdbs) ] someP2pBlockHeaderDbServer :: SomeBlockHeaderDb -> SomeServer @@ -397,13 +320,12 @@ someP2pBlockHeaderDbServers v = mconcat -- -------------------------------------------------------------------------- -- -- BlockHeader Event Stream -someBlockStreamServer :: CanReadablePayloadCas tbl => ChainwebVersion -> CutDb tbl -> SomeServer +someBlockStreamServer :: ChainwebVersion -> CutDb -> SomeServer someBlockStreamServer (FromSingChainwebVersion (SChainwebVersion :: Sing v)) cdb = - SomeServer (Proxy @(BlockStreamApi v)) $ - blockStreamHandler cdb True :<|> blockStreamHandler cdb False + SomeServer (Proxy @(BlockStreamApi v)) $ blockStreamHandler cdb -blockStreamHandler :: forall tbl. CanReadablePayloadCas tbl => CutDb tbl -> Bool -> Tagged Handler Application -blockStreamHandler db withPayloads = Tagged $ \req resp -> do +blockStreamHandler :: CutDb -> Tagged Handler Application +blockStreamHandler db = Tagged $ \req resp -> do streamRef <- newIORef $ SP.map f $ SP.mapM g $ SP.concat $ blockDiffStream db eventSourceAppIO (run streamRef) req resp where @@ -412,21 +334,14 @@ blockStreamHandler db withPayloads = Tagged $ \req resp -> do Nothing -> return CloseEvent Just (cur, !s') -> cur <$ writeIORef var s' - cas :: PayloadDb tbl - cas = view cutDbPayloadDb db - g :: BlockHeader -> IO HeaderUpdate - g bh = do - Just x <- lookupPayloadWithHeight cas (Just $ view blockHeight bh) (view blockPayloadHash bh) - pure $ HeaderUpdate - { _huHeader = ObjectEncoded bh - , _huPayloadWithOutputs = - x <$ guard withPayloads - , _huTxCount = length $ _payloadWithOutputsTransactions x - , _huPowHash = decodeUtf8 . B16.encode . BS.reverse . fromShort . powHashBytes $ view blockPow bh - , _huTarget = showTargetHex $ view blockTarget bh - } + g bh = pure $ HeaderUpdate + { _huHeader = ObjectEncoded bh + , _huPowHash = decodeUtf8 . B16.encode . BS.reverse . fromShort . powHashBytes $ view blockPow bh + , _huTarget = showTargetHex $ view blockTarget bh + } f :: HeaderUpdate -> ServerEvent f hu = ServerEvent (Just $ fromByteString "BlockHeader") Nothing [ fromLazyByteString . encode $ toJSON hu ] + From c390786d671ad594105f58bc9168ab06df1351c8 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 30 Dec 2024 12:59:21 -0800 Subject: [PATCH 024/378] wip-4 (df170909d): src/Chainweb/Chainweb.hs --- src/Chainweb/Chainweb.hs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index a1ae6439f4..daf133f173 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -59,7 +59,7 @@ module Chainweb.Chainweb , chainwebLogger , chainwebSocket , chainwebPeer -, chainwebPayloadDb +, chainwebPayloadProviders , chainwebPactData , chainwebThrottler , chainwebPutPeerThrottler @@ -184,6 +184,7 @@ import P2P.Peer import qualified Pact.Types.ChainMeta as P import qualified Pact.Types.Command as P +import Chainweb.PayloadProvider -- -------------------------------------------------------------------------- -- -- Chainweb Resources @@ -191,12 +192,12 @@ import qualified Pact.Types.Command as P data Chainweb logger tbl = Chainweb { _chainwebHostAddress :: !HostAddress , _chainwebChains :: !(HM.HashMap ChainId (ChainResources logger)) - , _chainwebCutResources :: !(CutResources logger tbl) - , _chainwebMiner :: !(Maybe (MinerResources logger tbl)) - , _chainwebCoordinator :: !(Maybe (MiningCoordination logger tbl)) + , _chainwebCutResources :: !(CutResources logger) + , _chainwebMiner :: !(Maybe (MinerResources logger)) + , _chainwebCoordinator :: !(Maybe (MiningCoordination logger)) , _chainwebLogger :: !logger , _chainwebPeer :: !(PeerResources logger) - , _chainwebPayloadDb :: !(PayloadDb tbl) + , _chainwebPayloadProviders :: !PayloadProviders , _chainwebManager :: !HTTP.Manager , _chainwebPactData :: ![(ChainId, PactServerData logger tbl)] , _chainwebThrottler :: !(Throttle Address) @@ -448,6 +449,8 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re then PersistIntraBlockWrites else DoNotPersistIntraBlockWrites , _pactTxTimeLimit = Nothing + -- FIXME + , _pactMiner = Nothing } pruningLogger :: T.Text -> logger @@ -475,14 +478,16 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re -> IO () global cs = do let !webchain = mkWebBlockHeaderDb v (HM.map _chainResBlockHeaderDb cs) - !pact = mkWebPactExecutionService (HM.map _chainResPact cs) + -- FIXME FIXME FIXME + -- !pact = mkWebPactExecutionService (HM.map _chainResPact cs) + providers = error "Chainweb.Chainweb.withChainwebInternal.global: provider initialization not yet implemented" !cutLogger = setComponent "cut" logger !mgr = _peerResManager peer logg Debug "start initializing cut resources" logFunctionJson logger Info InitializingCutResources - withCutResources cutConfig peer cutLogger rocksDb webchain payloadDb mgr pact $ \cuts -> do + withCutResources cutConfig peer cutLogger rocksDb webchain providers mgr $ \cuts -> do logg Debug "finished initializing cut resources" let !mLogger = setComponent "miner" logger @@ -585,7 +590,7 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re , _chainwebCoordinator = mc , _chainwebLogger = logger , _chainwebPeer = peer - , _chainwebPayloadDb = view cutDbPayloadDb $ _cutResCutDb cuts + , _chainwebPayloadProviders = providers , _chainwebManager = mgr , _chainwebPactData = pactData , _chainwebThrottler = throttler @@ -604,7 +609,7 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re withPactData :: HM.HashMap ChainId (ChainResources logger) - -> CutResources logger tbl + -> CutResources logger -> ([(ChainId, PactServerData logger tbl)] -> IO b) -> IO b withPactData cs cuts m = do @@ -614,6 +619,7 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re , _pactServerDataMempool = _chainResMempool cr , _pactServerDataLogger = _chainResLogger cr , _pactServerDataPact = _chainResPact cr + , _pactServerDataPayloadDb = _chainResPayloadDb cr }) v = _configChainwebVersion conf @@ -928,7 +934,7 @@ runChainweb cw nowServing = do -- Cut DB and Miner - cutDb :: CutDb tbl + cutDb :: CutDb cutDb = _cutResCutDb $ _chainwebCutResources cw cutPeerDb :: PeerDb From dfa6c711196931c675804d503cff47366862a0c5 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 30 Dec 2024 12:59:21 -0800 Subject: [PATCH 025/378] wip-4 (df170909d): src/Chainweb/Chainweb/ChainResources.hs --- src/Chainweb/Chainweb/ChainResources.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 02573d1b1c..913fdd3083 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -36,7 +36,6 @@ import Data.Maybe import Prelude hiding (log) - -- internal modules import Chainweb.BlockHeaderDB @@ -64,6 +63,7 @@ data ChainResources logger = ChainResources , _chainResLogger :: !logger , _chainResMempool :: !(MempoolBackend Pact4.UnparsedTransaction) , _chainResPact :: PactExecutionService + -- , _chainResPayloadDb :: _ } makeLenses ''ChainResources From cd9548265f0a02b3ffb9ef2a74af3c07c65e4caa Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 30 Dec 2024 12:59:21 -0800 Subject: [PATCH 026/378] wip-4 (df170909d): src/Chainweb/Chainweb/CutResources.hs --- src/Chainweb/Chainweb/CutResources.hs | 58 +++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index e9ee1a4969..994d3982df 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -46,12 +46,10 @@ import Chainweb.Chainweb.PeerResources import Chainweb.CutDB import qualified Chainweb.CutDB.Sync as C import Chainweb.Logger -import Chainweb.Payload.PayloadStore import Chainweb.RestAPI.NetworkID import Chainweb.Sync.WebBlockHeaderStore import Chainweb.Version import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService import Chainweb.Storage.Table.RocksDB @@ -59,6 +57,7 @@ import P2P.Node import P2P.Peer import P2P.Session import P2P.TaskQueue +import Chainweb.PayloadProvider -- -------------------------------------------------------------------------- -- -- Cuts Resources @@ -68,49 +67,47 @@ data CutSyncResources logger = CutSyncResources , _cutResSyncLogger :: !logger } -data CutResources logger tbl = CutResources +data CutResources logger = CutResources { _cutResCutConfig :: !CutDbParams , _cutResPeer :: !(PeerResources logger) - , _cutResCutDb :: !(CutDb tbl) + , _cutResCutDb :: !CutDb , _cutResLogger :: !logger , _cutResCutSync :: !(CutSyncResources logger) , _cutResHeaderSync :: !(CutSyncResources logger) - , _cutResPayloadSync :: !(CutSyncResources logger) } makeLensesFor [ ("_cutResCutDb", "cutsCutDb") ] ''CutResources -instance HasChainwebVersion (CutResources logger tbl) where +instance HasChainwebVersion (CutResources logger) where _chainwebVersion = _chainwebVersion . _cutResCutDb {-# INLINE _chainwebVersion #-} withCutResources :: Logger logger - => CanPayloadCas tbl => CutDbParams -> PeerResources logger -> logger -> RocksDb -> WebBlockHeaderDb - -> PayloadDb tbl + -> PayloadProviders -> HTTP.Manager - -> WebPactExecutionService - -> (forall tbl' . CanReadablePayloadCas tbl' => CutResources logger tbl' -> IO a) + -> (CutResources logger -> IO a) -> IO a -withCutResources cutDbParams peer logger rdb webchain payloadDb mgr pact f = do +withCutResources cutDbParams peer logger rdb webchain providers mgr f = do -- initialize blockheader store headerStore <- newWebBlockHeaderStore mgr webchain (logFunction logger) + -- FIXME -- initialize payload store - payloadStore <- newWebPayloadStore mgr pact payloadDb (logFunction logger) + -- payloadStore <- newWebPayloadStore mgr pact payloadDb (logFunction logger) -- initialize cutHashes store let cutHashesStore = cutHashesTable rdb - withCutDb cutDbParams (logFunction logger) headerStore payloadStore cutHashesStore $ \cutDb -> + withCutDb cutDbParams (logFunction logger) headerStore providers cutHashesStore $ \cutDb -> f $ CutResources { _cutResCutConfig = cutDbParams , _cutResPeer = peer @@ -124,10 +121,13 @@ withCutResources cutDbParams peer logger rdb webchain payloadDb mgr pact f = do { _cutResSyncSession = session 10 (_webBlockHeaderStoreQueue headerStore) , _cutResSyncLogger = addLabel ("sync", "header") syncLogger } - , _cutResPayloadSync = CutSyncResources - { _cutResSyncSession = session 10 (_webBlockPayloadStoreQueue payloadStore) - , _cutResSyncLogger = addLabel ("sync", "payload") syncLogger - } + + -- FIXME + -- , _cutResPayloadSync = CutSyncResources + -- { _cutResSyncSession = session 10 (_webBlockPayloadStoreQueue payloadStore) + -- , _cutResSyncLogger = addLabel ("sync", "payload") syncLogger + -- } + } where v = _chainwebVersion webchain @@ -138,12 +138,12 @@ withCutResources cutDbParams peer logger rdb webchain payloadDb mgr pact f = do cutNetworks :: Logger logger => HTTP.Manager - -> CutResources logger tbl + -> CutResources logger -> [IO ()] cutNetworks mgr cuts = [ runCutNetworkCutSync mgr cuts , runCutNetworkHeaderSync mgr cuts - , runCutNetworkPayloadSync mgr cuts +-- , runCutNetworkPayloadSync mgr cuts ] -- | P2P Network for pushing Cuts @@ -151,7 +151,7 @@ cutNetworks mgr cuts = runCutNetworkCutSync :: Logger logger => HTTP.Manager - -> CutResources logger tbl + -> CutResources logger -> IO () runCutNetworkCutSync mgr c = mkCutNetworkSync mgr True c "cut sync" $ _cutResCutSync c @@ -161,20 +161,20 @@ runCutNetworkCutSync mgr c runCutNetworkHeaderSync :: Logger logger => HTTP.Manager - -> CutResources logger tbl + -> CutResources logger -> IO () runCutNetworkHeaderSync mgr c = mkCutNetworkSync mgr False c "block header sync" $ _cutResHeaderSync c -- | P2P Network for Block Payloads -- -runCutNetworkPayloadSync - :: Logger logger - => HTTP.Manager - -> CutResources logger tbl - -> IO () -runCutNetworkPayloadSync mgr c - = mkCutNetworkSync mgr False c "block payload sync" $ _cutResPayloadSync c +-- runCutNetworkPayloadSync +-- :: Logger logger +-- => HTTP.Manager +-- -> CutResources logger +-- -> IO () +-- runCutNetworkPayloadSync mgr c +-- = mkCutNetworkSync mgr False c "block payload sync" $ _cutResPayloadSync c -- | P2P Network for Block Payloads -- @@ -186,7 +186,7 @@ mkCutNetworkSync => HTTP.Manager -> Bool -- ^ Do peer synchronization - -> CutResources logger tbl + -> CutResources logger -> T.Text -> CutSyncResources logger -> IO () From d508419bd06c1e2106205c1cba30647f33237c25 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Jan 2025 11:57:39 -0800 Subject: [PATCH 027/378] wip-5 (5c71825e1): src/Chainweb/Chainweb/MempoolSyncClient.hs --- src/Chainweb/Chainweb/MempoolSyncClient.hs | 99 +++++++++++++--------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/src/Chainweb/Chainweb/MempoolSyncClient.hs b/src/Chainweb/Chainweb/MempoolSyncClient.hs index 953c2cdcdc..e63a4508b7 100644 --- a/src/Chainweb/Chainweb/MempoolSyncClient.hs +++ b/src/Chainweb/Chainweb/MempoolSyncClient.hs @@ -53,6 +53,15 @@ import qualified Servant.Client as Sv -- -------------------------------------------------------------------------- -- -- Mempool sync. +-- FIXME this should be moved into Pact. +-- +-- While Pact is still part of the chainweb-node process, the pact networks will +-- be run via the global p2p infrastructure. +-- +-- However, These network should show up in the node initialization under +-- payloadProviderNetworks and should probably be bundled with the payload +-- networks from pact. + -- | Synchronize the local mempool over the P2P network. -- runMempoolSyncClient @@ -64,50 +73,58 @@ runMempoolSyncClient -> ChainResources logger -- ^ chain resources -> IO () -runMempoolSyncClient mgr memP2pConfig peerRes chain = bracket create destroy go - where - create = do - logg Debug "starting mempool p2p sync" - p2pCreateNode v netId peer (logFunction syncLogger) peerDb mgr True $ - mempoolSyncP2pSession chain (_mempoolP2pConfigPollInterval memP2pConfig) - go n = do - -- Run P2P client node - logg Debug "mempool sync p2p node initialized, starting session" - p2pStartNode p2pConfig n - - destroy n = p2pStopNode n `finally` logg Debug "mempool sync p2p node stopped" - - v = _chainwebVersion chain - peer = _peerResPeer peerRes - p2pConfig = _peerResConfig peerRes - & set p2pConfigMaxSessionCount (_mempoolP2pConfigMaxSessionCount memP2pConfig) - & set p2pConfigSessionTimeout (_mempoolP2pConfigSessionTimeout memP2pConfig) - peerDb = _peerResDb peerRes - netId = MempoolNetwork $ _chainId chain - - logg = logFunctionText syncLogger - syncLogger = setComponent "mempool-sync" $ _chainResLogger chain +runMempoolSyncClient mgr memP2pConfig peerRes chain = + error "Chainweb.Chainweb.MempoolSyncClient.mempoolSyncClient: only supported for pact service which is currently disabled" +-- bracket create destroy go +-- where +-- create = do +-- logg Debug "starting mempool p2p sync" +-- p2pCreateNode v netId peer (logFunction syncLogger) peerDb mgr True $ +-- mempoolSyncP2pSession chain (_mempoolP2pConfigPollInterval memP2pConfig) +-- go n = do +-- -- Run P2P client node +-- logg Debug "mempool sync p2p node initialized, starting session" +-- p2pStartNode p2pConfig n +-- +-- destroy n = p2pStopNode n `finally` logg Debug "mempool sync p2p node stopped" +-- +-- v = _chainwebVersion chain +-- peer = _peerResPeer peerRes +-- p2pConfig = _peerResConfig peerRes +-- & set p2pConfigMaxSessionCount (_mempoolP2pConfigMaxSessionCount memP2pConfig) +-- & set p2pConfigSessionTimeout (_mempoolP2pConfigSessionTimeout memP2pConfig) +-- peerDb = _peerResDb peerRes +-- netId = MempoolNetwork $ _chainId chain +-- +-- logg = logFunctionText syncLogger +-- syncLogger = setComponent "mempool-sync" $ _chainResLogger chain +-- | FIXME: +-- +-- the mempool should be part of pact. The API can be published to the node API +-- in the same way the Payload APIs are published. +-- mempoolSyncP2pSession :: ChainResources logger -> Seconds -> P2pSession mempoolSyncP2pSession chain (Seconds pollInterval) logg0 env _ = do - logg Debug "mempool sync session starting" - Mempool.syncMempools' logg syncIntervalUs pool peerMempool - logg Debug "mempool sync session finished" - return True - where - peerMempool = MPC.toMempool v cid txcfg env - - -- FIXME Potentially dangerous down-cast. - syncIntervalUs :: Int - syncIntervalUs = int pollInterval * 500000 - - remote = T.pack $ Sv.showBaseUrl $ Sv.baseUrl env - logg d m = logg0 d $ T.concat ["[mempool sync@", remote, "]:", m] - - pool = _chainResMempool chain - txcfg = Mempool.mempoolTxConfig pool - cid = _chainId chain - v = _chainwebVersion chain + error "Chainweb.Chainweb.MempoolSyncClient.mempoolSyncP2pSession: only supported for pact service which is currently disabled" +-- logg Debug "mempool sync session starting" +-- Mempool.syncMempools' logg syncIntervalUs pool peerMempool +-- logg Debug "mempool sync session finished" +-- return True +-- where +-- peerMempool = MPC.toMempool v cid txcfg env +-- +-- -- FIXME Potentially dangerous down-cast. +-- syncIntervalUs :: Int +-- syncIntervalUs = int pollInterval * 500000 +-- +-- remote = T.pack $ Sv.showBaseUrl $ Sv.baseUrl env +-- logg d m = logg0 d $ T.concat ["[mempool sync@", remote, "]:", m] +-- +-- pool = _chainResMempool chain +-- txcfg = Mempool.mempoolTxConfig pool +-- cid = _chainId chain +-- v = _chainwebVersion chain From 1872cc21f01502319dbf96837d89893278ce1b85 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:51 -0800 Subject: [PATCH 028/378] wip-4 (df170909d): src/Chainweb/Chainweb/MinerResources.hs --- src/Chainweb/Chainweb/MinerResources.hs | 280 ++++-------------------- 1 file changed, 44 insertions(+), 236 deletions(-) diff --git a/src/Chainweb/Chainweb/MinerResources.hs b/src/Chainweb/Chainweb/MinerResources.hs index 3602220286..bf8a72a41a 100644 --- a/src/Chainweb/Chainweb/MinerResources.hs +++ b/src/Chainweb/Chainweb/MinerResources.hs @@ -8,6 +8,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Chainweb.MinerResources @@ -31,262 +32,64 @@ module Chainweb.Chainweb.MinerResources , withMiningCoordination ) where -import Control.Concurrent (threadDelay) -import Control.Concurrent.Async -import Control.Concurrent.STM (atomically) -import Control.Concurrent.STM.TVar -import Control.Lens -import Control.Monad - -import Data.HashMap.Strict (HashMap) -import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS -import Data.IORef (IORef, atomicWriteIORef, newIORef, readIORef) -import qualified Data.Map.Strict as M -import Data.Maybe -import qualified Data.Set as S -import qualified Data.Vector as V - -import System.LogLevel (LogLevel(..)) -import qualified System.Random.MWC as MWC - --- internal modules - -import Chainweb.BlockHeader import Chainweb.ChainId import Chainweb.Chainweb.ChainResources -import Chainweb.Cut (_cutMap) -import Chainweb.CutDB (CutDb, awaitNewBlock, cutDbPactService, _cut) +import Chainweb.CutDB import Chainweb.Logger import Chainweb.Miner.Config import Chainweb.Miner.Coordinator import Chainweb.Miner.Miners -import Chainweb.Miner.Pact (Miner(..), minerId) -import Chainweb.Pact.Types -import Chainweb.Pact.Utils -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Sync.WebBlockHeaderStore -import Chainweb.Time -import Chainweb.Utils import Chainweb.Version -import Chainweb.WebPactExecutionService -import Utils.Logging.Trace - -import Data.LogMessage (JsonLog(..), LogFunction) - -import Numeric.AffineSpace +import Control.Concurrent.Async +import Control.Lens +import Data.HashMap.Strict (HashMap) +import Data.LogMessage (LogFunction) +import System.Random.MWC qualified as MWC -- -------------------------------------------------------------------------- -- -- Miner +-- | Setup and Start Mining Coordination. +-- +-- Provides the payload caches that can be filled by payload providers and +-- listens on new payloads and cuts and creates and delivers work to the mining +-- clients. +-- +-- FIXME: We may hide the constructors of MiningCoordination and only expose +-- what is useful to other components +-- withMiningCoordination :: Logger logger => logger -> MiningConfig - -> CutDb tbl - -> (Maybe (MiningCoordination logger tbl) -> IO a) + -> CutDb + -> (Maybe (MiningCoordination logger) -> IO a) -> IO a withMiningCoordination logger conf cdb inner | not (_coordinationEnabled coordConf) = inner Nothing | otherwise = do - cut <- _cut cdb - t <- newTVarIO mempty - initialPw <- fmap (PrimedWork . HM.fromList) $ - forM miners $ \miner -> - let mid = view minerId miner - in fmap ((mid,) . HM.fromList) $ - forM cids $ \cid -> do - let bh = fromMaybe (genesisBlockHeader v cid) (HM.lookup cid (_cutMap cut)) - newBlock <- throwIfNoHistory =<< getPayload cid miner (ParentHeader bh) - return (cid, WorkReady newBlock) - - m <- newTVarIO initialPw - c503 <- newIORef 0 - c403 <- newIORef 0 - l <- newIORef (_coordinationUpdateStreamLimit coordConf) - fmap thd . runConcurrently $ (,,) - <$> Concurrently (prune t m c503 c403) - <*> Concurrently (mapConcurrently_ (primeWork m) cids) - <*> Concurrently (inner . Just $ MiningCoordination - { _coordLogger = logger - , _coordCutDb = cdb - , _coordState = t - , _coordLimit = _coordinationReqLimit coordConf - , _coord503s = c503 - , _coord403s = c403 - , _coordConf = coordConf - , _coordUpdateStreamCount = l - , _coordPrimedWork = m - }) + coord <- newMiningCoordination logger coordConf cdb + fmap snd $ concurrently + -- maintain mining state + (runCoordination coord) + -- run inner computation + (inner (Just coord)) where + -- providers = view cutDbPayloadProviders cdb coordConf = _miningCoordination conf - inNodeConf = _miningInNode conf - v = _chainwebVersion cdb - - cids :: [ChainId] - cids = HS.toList (chainIds v) - - !miners = S.toList (_coordinationMiners coordConf) - <> [ _nodeMiner inNodeConf | _nodeMiningEnabled inNodeConf ] - chainLogger cid = addLabel ("chain", toText cid) - - -- we assume that this path always exists in PrimedWork and never delete it. - workForMiner :: Miner -> ChainId -> Traversal' PrimedWork WorkState - workForMiner miner cid = _Wrapped' . ix (view minerId miner) . ix cid - - periodicallyRefreshPayload :: TVar PrimedWork -> ChainId -> Miner -> IO a - periodicallyRefreshPayload tpw cid ourMiner = forever $ do - let delay = - timeSpanToMicros $ _coordinationPayloadRefreshDelay coordConf - threadDelay (fromIntegral @Micros @Int delay) - when (not $ v ^. versionCheats . disablePact) $ do - -- "stale" in the sense of not having all of the transactions - -- that it could. it still has the latest possible parent - mContinuableBlockInProgress <- atomically $ do - primed <- readTVar tpw <&> (^?! workForMiner ourMiner cid) - case primed of - WorkReady (NewBlockInProgress bip) -> return (Just bip) - WorkReady (NewBlockPayload {}) -> - error "periodicallyRefreshPayload: encountered NewBlockPayload in PrimedWork, which cannot be refreshed" - WorkAlreadyMined {} -> return Nothing - WorkStale -> return Nothing - - forM_ mContinuableBlockInProgress $ \continuableBlockInProgress -> do - maybeNewBlock <- case continuableBlockInProgress of - ForPact4 block -> fmap ForPact4 <$> _pactContinueBlock pact cid block - ForPact5 block -> fmap ForPact5 <$> _pactContinueBlock pact cid block - -- if continuing returns NoHistory then the parent header - -- isn't available in the checkpointer right now. - -- in that case we just mark the payload as not stale. - let newBlock = case maybeNewBlock of - NoHistory -> continuableBlockInProgress - Historical b -> b - - logFunctionText (chainLogger cid logger) Debug - $ "refreshed block, old and new tx count: " - <> sshow - ( forAnyPactVersion (V.length . _transactionPairs . _blockInProgressTransactions) continuableBlockInProgress - , forAnyPactVersion (V.length . _transactionPairs . _blockInProgressTransactions) newBlock - ) - - atomically $ modifyTVar' tpw $ - workForMiner ourMiner cid .~ WorkReady (NewBlockInProgress newBlock) - - -- | THREAD: Keep a live-updated cache of Payloads for specific miners, such - -- that when they request new work, the block can be instantly constructed - -- without interacting with the Pact Queue. - -- - primeWork :: TVar PrimedWork -> ChainId -> IO () - primeWork tpw cid = - forConcurrently_ miners $ \miner -> - runForever (logFunction (chainLogger cid logger)) "primeWork" (go miner) - where - go :: Miner -> IO () - go miner = do - pw <- readTVarIO tpw - let - -- we assume that this path always exists in PrimedWork and never delete it. - ourMiner :: Traversal' PrimedWork WorkState - ourMiner = workForMiner miner cid - let !outdatedPayload = fromJuste $ pw ^? ourMiner - let outdatedParentHash = case outdatedPayload of - WorkReady outdatedBlock -> view _1 (newBlockParent outdatedBlock) - WorkAlreadyMined outdatedBlockHash -> outdatedBlockHash - WorkStale -> error "primeWork loop: Invariant Violation: Stale work should be an impossibility" - - newParent <- either ParentHeader id <$> race - -- wait for a block different from what we've got primed work for - (awaitNewBlock cdb cid outdatedParentHash) - -- in the meantime, periodically refresh the payload to make sure - -- it has all of the transactions it can have - (periodicallyRefreshPayload tpw cid miner) - - -- Temporarily block this chain from being considered for queries - atomically $ modifyTVar' tpw (ourMiner .~ WorkStale) - - -- Get a payload for the new block - getPayload cid miner newParent >>= \case - NoHistory -> do - logFunctionText (addLabel ("chain", toText cid) logger) Warn - "current block is not in the checkpointer; halting primed work loop temporarily" - approximateThreadDelay 1_000_000 - atomically $ modifyTVar' tpw (ourMiner .~ outdatedPayload) - Historical newBlock -> - atomically $ modifyTVar' tpw (ourMiner .~ WorkReady newBlock) - - getPayload :: ChainId -> Miner -> ParentHeader -> IO (Historical NewBlock) - getPayload cid m ph = - if v ^. versionCheats . disablePact - -- if pact is disabled, we must keep track of the latest header - -- ourselves. otherwise we use the header we get from newBlock as the - -- real parent. newBlock may return a header in the past due to a race - -- with rocksdb though that shouldn't cause a problem, just wasted work, - -- see docs for - -- Chainweb.Pact.PactService.Checkpointer.findLatestValidBlockHeader' - then return $ Historical $ - NewBlockPayload ph emptyPayload - else trace (logFunction (chainLogger cid logger)) - "Chainweb.Chainweb.MinerResources.withMiningCoordination.newBlock" - () 1 (_pactNewBlock pact cid m NewBlockFill ph) - - pact :: PactExecutionService - pact = _webPactExecutionService $ view cutDbPactService cdb - - -- | THREAD: Periodically clear out the cached payloads kept for Mining - -- Coordination. - -- - prune :: TVar MiningState -> TVar PrimedWork -> IORef Int -> IORef Int -> IO () - prune t tpw c503 c403 = runForever (logFunction logger) "MinerResources.prune" $ do - let !d = 30_000_000 -- 30 seconds - let !maxAge = (5 :: Int) `scaleTimeSpan` minute -- 5 minutes - threadDelay d - ago <- (.-^ maxAge) <$> getCurrentTimeIntegral - m@(MiningState ms) <- atomically $ do - ms <- readTVar t - modifyTVar' t . over miningState $ M.filter (f ago) - pure ms - count503 <- readIORef c503 - count403 <- readIORef c403 - PrimedWork pw <- readTVarIO tpw - atomicWriteIORef c503 0 - atomicWriteIORef c403 0 - logFunction logger Info . JsonLog $ MiningStats - { _statsCacheSize = M.size ms - , _stats503s = count503 - , _stats403s = count403 - , _statsAvgTxs = avgTxs m - , _statsPrimedSize = HM.foldl' (\acc xs -> acc + HM.size xs) 0 pw } - - -- Filter for work items that are not older than maxAge - -- - -- NOTE: Should difficulty ever become that hard that five minutes aren't - -- sufficient to mine a block this constant must be changed in order to - -- recover. - -- - f :: Time Micros -> T3 a b (Time Micros) -> Bool - f ago (T3 _ _ added) = added > ago - - avgTxs :: MiningState -> Int - avgTxs (MiningState ms) = summed `div` max 1 (M.size ms) - where - summed :: Int - summed = M.foldl' (\acc (T3 _ ps _) -> acc + g ps) 0 ms - - g :: PayloadWithOutputs -> Int - g = V.length . _payloadWithOutputsTransactions +-- -------------------------------------------------------------------------- -- -- | Miner resources are used by the test-miner when in-node mining is -- configured or by the mempool noop-miner (which keeps the mempool updated) in -- production setups. -- -data MinerResources logger tbl = MinerResources +data MinerResources logger = MinerResources { _minerResLogger :: !logger - , _minerResCutDb :: !(CutDb tbl) + , _minerResCutDb :: !CutDb , _minerChainResources :: !(HashMap ChainId (ChainResources logger)) , _minerResConfig :: !NodeMiningConfig - , _minerResCoordination :: !(Maybe (MiningCoordination logger tbl)) + , _minerResCoordination :: !(Maybe (MiningCoordination logger)) -- ^ The primed work cache. This is Nothing when coordination is -- disabled. It is needed by the in-node test miner. The mempoolNoopMiner -- does not use it. @@ -296,9 +99,9 @@ withMinerResources :: logger -> NodeMiningConfig -> HashMap ChainId (ChainResources logger) - -> CutDb tbl - -> Maybe (MiningCoordination logger tbl) - -> (Maybe (MinerResources logger tbl) -> IO a) + -> CutDb + -> Maybe (MiningCoordination logger) + -> (Maybe (MinerResources logger) -> IO a) -> IO a withMinerResources logger conf chainRes cutDb tpw inner = inner . Just $ MinerResources @@ -313,12 +116,14 @@ withMinerResources logger conf chainRes cutDb tpw inner = -- -- When mining coordination is disabled, this function exits with an error. -- +-- FIXME: is the above true? I think, the mempoolNoopMiner is important for +-- keeping the mempools up to date when mining coordination is disabled. +-- runMiner - :: forall logger tbl + :: forall logger . Logger logger - => CanReadablePayloadCas tbl => ChainwebVersion - -> MinerResources logger tbl + -> MinerResources logger -> IO () runMiner v mr | enabled = case _minerResCoordination mr of @@ -327,12 +132,15 @@ runMiner v mr Just coord -> case v ^. versionCheats . disablePow of True -> testMiner coord False -> powMiner coord - | otherwise = mempoolNoopMiner lf (_chainResMempool <$> _minerChainResources mr) + | otherwise = return () + -- FIXME: this is needed / possible only for pact chain + -- (and should be handled internally by pact service) + -- mempoolNoopMiner lf (_chainResMempool <$> _minerChainResources mr) where enabled = _nodeMiningEnabled $ _minerResConfig mr - cdb :: CutDb tbl + cdb :: CutDb cdb = _minerResCutDb mr conf :: NodeMiningConfig @@ -343,6 +151,6 @@ runMiner v mr testMiner coord = do gen <- MWC.createSystemRandom - localTest lf v coord (_nodeMiner conf) cdb gen (_nodeTestMiners conf) + localTest lf v coord cdb gen (_nodeTestMiners conf) - powMiner coord = localPOW lf coord (_nodeMiner conf) cdb + powMiner coord = localPOW lf coord cdb From 4bc14ada46970b40e0651da8e0a702fc8a973c40 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 21 Jan 2025 10:05:47 -0800 Subject: [PATCH 029/378] Add WorkParents to Cut.Create --- src/Chainweb/Cut/Create.hs | 74 +++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index c9ed6a13bb..2883d6608b 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -47,6 +47,15 @@ module Chainweb.Cut.Create , cutExtensionAdjacentHashes , getCutExtension +-- * WorkParents +, WorkParents +, workParents +, _workParent +, workParent +, _workParentsAdjacentHashes +, workParentsAdjacentHashes +, newWork + -- * Work , WorkHeader(..) , encodeWorkHeader @@ -74,7 +83,7 @@ import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS import Data.Text qualified as T -import GHC.Generics +import GHC.Generics (Generic) import GHC.Stack -- internal modules @@ -376,6 +385,69 @@ getAdjacentParentHeaders hdb extension <> ". ChainId: " <> encodeToText cid <> ". CutHashes: " <> encodeToText (cutToCutHashes Nothing c) +-- -------------------------------------------------------------------------- -- +-- + +data WorkParents = WorkParents + { _workParent' :: !ParentHeader + -- ^ The header onto which the new block is created. + , _workAdjacentParents' :: !(HM.HashMap ChainId ParentHeader) + -- ^ The adjacent hashes for the new block. These must be at the same + -- height as the parent and must be have a pairwise valid braiding among + -- each other and with the parent. + -- + -- Actually, only the hash and the target of the adjacent parents is + -- used to construct the new work. + } + deriving (Show, Eq, Generic) + +_workParent :: WorkParents -> ParentHeader +_workParent = _workParent' + +workParent :: Getter WorkParents ParentHeader +workParent = to _workParent + +_workParentsAdjacentHashes :: WorkParents -> BlockHashRecord +_workParentsAdjacentHashes = BlockHashRecord + . fmap (view parentHeaderHash) + . _workAdjacentParents' + +workParentsAdjacentHashes :: Getter WorkParents BlockHashRecord +workParentsAdjacentHashes = to _workParentsAdjacentHashes + +-- | Returns the work parents for a given cut and a given chain. Returns +-- 'Nothing' if the chain in blocked for the cut. +-- +workParents + :: HasCallStack + => Applicative m + => HasChainId cid + => (ChainValue BlockHash -> m BlockHeader) + -> Cut + -- ^ the cut which is to be extended + -> cid + -- ^ the chain which is to be extended + -> m (Maybe WorkParents) +workParents hdb c cid = case getCutExtension c cid of + Nothing -> pure Nothing + Just e -> Just . WorkParents (_cutExtensionParent e) + <$> getAdjacentParentHeaders hdb e + +newWork + :: BlockCreationTime + -> WorkParents + -> BlockPayloadHash + -> WorkHeader +newWork creationTime parents pldHash = WorkHeader + { _workHeaderBytes = SB.toShort $ runPutS $ encodeBlockHeaderWithoutHash nh + , _workHeaderTarget = view blockTarget nh + , _workHeaderChainId = _chainId nh + } + where + adjParents = _workAdjacentParents' parents + parent = _workParent' parents + nh = newBlockHeader adjParents pldHash (Nonce 0) creationTime parent + -- -------------------------------------------------------------------------- -- -- Solved Header -- From d9436295e5a13904da6c9f03e7d15ccdc04a1c8b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 21 Jan 2025 10:06:38 -0800 Subject: [PATCH 030/378] Cut.Create: replace PayloadWithOoutputs with EncodedPayloadData --- src/Chainweb/Cut/Create.hs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 2883d6608b..511c144631 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -11,6 +11,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE BangPatterns #-} -- | -- Module: Chainweb.Cut.Create @@ -98,7 +99,7 @@ import Chainweb.Core.Brief import Chainweb.Cut import Chainweb.Cut.CutHashes import Chainweb.Difficulty -import Chainweb.Payload +import Chainweb.PayloadProvider(EncodedPayloadData(..), EncodedPayloadOutputs) import Chainweb.Time import Chainweb.Utils import Chainweb.Utils.Serialization @@ -505,30 +506,30 @@ instance Brief SolvedWork where extend :: MonadThrow m => Cut - -> PayloadWithOutputs + -> Maybe EncodedPayloadData + -> Maybe EncodedPayloadOutputs -> SolvedWork -> m (BlockHeader, Maybe CutHashes) -extend c pwo s = do - (bh, mc) <- extendCut c (_payloadWithOutputsPayloadHash pwo) s +extend c pld pwo s = do + (bh, mc) <- extendCut c s return (bh, toCutHashes bh <$> mc) where toCutHashes bh c' = cutToCutHashes Nothing c' & set cutHashesHeaders (HM.singleton (view blockHash bh) bh) & set cutHashesPayloads - (HM.singleton (view blockPayloadHash bh) (payloadWithOutputsToPayloadData pwo)) + (maybe mempty (HM.singleton (view blockPayloadHash bh)) pld) & set cutHashesLocalPayload - (Just (view blockPayloadHash bh, pwo)) + ((view blockPayloadHash bh,) <$> pwo) -- | For internal use and testing -- extendCut :: MonadThrow m => Cut - -> BlockPayloadHash -> SolvedWork -> m (BlockHeader, Maybe Cut) -extendCut c ph (SolvedWork bh) = do +extendCut c (SolvedWork bh) = do -- Fail Early: If a `BlockHeader`'s injected Nonce (and thus its POW -- Hash) is trivially incorrect, reject it. @@ -536,13 +537,6 @@ extendCut c ph (SolvedWork bh) = do unless (prop_block_pow bh) $ throwM $ InvalidSolvedHeader bh "Invalid POW hash" - -- Fail Early: check that the given payload matches the new block. - -- - unless (view blockPayloadHash bh == ph) $ throwM $ InvalidSolvedHeader bh - $ "Invalid payload hash" - <> ". Expected: " <> sshow (view blockPayloadHash bh) - <> ", Got: " <> sshow ph - -- If the `BlockHeader` is already stale and can't be appended to the -- best `Cut`, Nothing is returned -- From efbea53929c64054fe3fab35a8b2ceb7db158278 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:51 -0800 Subject: [PATCH 031/378] wip-3 (23d874999): src/Chainweb/Cut/CutHashes.hs --- src/Chainweb/Cut/CutHashes.hs | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Chainweb/Cut/CutHashes.hs b/src/Chainweb/Cut/CutHashes.hs index f1b3246480..56d893bb5d 100644 --- a/src/Chainweb/Cut/CutHashes.hs +++ b/src/Chainweb/Cut/CutHashes.hs @@ -96,16 +96,14 @@ import Chainweb.BlockHeight import Chainweb.BlockWeight import Chainweb.ChainId import Chainweb.Cut +import Chainweb.Storage.Table import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version - -import Chainweb.Payload - -import Chainweb.Storage.Table +import Chainweb.Version.Registry (fabricateVersionWithName) +import Chainweb.PayloadProvider(EncodedPayloadData(..), EncodedPayloadOutputs(..)) import P2P.Peer -import Chainweb.Version.Registry (fabricateVersionWithName) -- -------------------------------------------------------------------------- -- -- CutId @@ -260,6 +258,17 @@ instance FromJSON BlockHashWithHeight where -- Optionally, a node may attach the 'PayloadData' and/or the 'BlockHeader' for -- some of the block of the 'Cut'. -- +-- FIXME: +-- We should not misuse the cut hashes structure for passing around payloads. +-- Instead the payload provider should cache payloads for locally mined blocks +-- for a while (e.g. until the block height advanced beyond the height of the +-- payload). +-- +-- The main benefit of this data is that it allows to push the payload of new +-- blocks along with the respective cut, saving up to 50% connection overhead +-- and also reducing latencies significantly. Currently, we do not do this, +-- though. +-- data CutHashes = CutHashes { _cutHashes :: !(HM.HashMap ChainId BlockHashWithHeight) , _cutOrigin :: !(Maybe PeerInfo) @@ -270,9 +279,18 @@ data CutHashes = CutHashes , _cutHashesId :: !CutId , _cutHashesHeaders :: !(HM.HashMap BlockHash BlockHeader) -- ^ optional block headers - , _cutHashesPayloads :: !(HM.HashMap BlockPayloadHash PayloadData) + , _cutHashesPayloads :: !(HM.HashMap BlockPayloadHash EncodedPayloadData) -- ^ optional block payloads - , _cutHashesLocalPayload :: !(Maybe (BlockPayloadHash, PayloadWithOutputs)) + -- + -- This is used for locally mined cuts for which the payload is not + -- available otherwise. + -- + -- TODO: This data should actually be ranked. For now we obtain the rank + -- from entries in the _cutHashesHeader field and reject the payload if + -- we do not have the corresponding header. Alternatively we could + -- also use an unranked candidate store, but that would make pruning + -- more difficult. + , _cutHashesLocalPayload :: !(Maybe (BlockPayloadHash, EncodedPayloadOutputs)) -- ^ optional, and unused except for error reporting, outputs -- Note: we cannot trust outputs from other nodes! } From 9a316f696ecb22ecccc258cb018eb0b1bc934a64 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:51 -0800 Subject: [PATCH 032/378] wip-4 (df170909d): src/Chainweb/CutDB.hs --- src/Chainweb/CutDB.hs | 199 ++++++++++++++++++++++-------------------- 1 file changed, 104 insertions(+), 95 deletions(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index ed982400d2..a63ae532e2 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -8,16 +8,17 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE TupleSections #-} -- | -- Module: Chainweb.CutDB @@ -52,13 +53,13 @@ module Chainweb.CutDB , pruneCuts , cutDbWebBlockHeaderDb , cutDbBlockHeaderDb -, cutDbPayloadDb -, cutDbPactService +, cutDbPayloadProviders , cut , _cut , _cutStm , cutStm , awaitNewCut +, awaitNewCutStm , awaitNewCutByChainId , awaitNewBlock , awaitNewBlockStm @@ -106,14 +107,14 @@ import Data.Aeson (ToJSON) import Data.Foldable import Data.Function import Data.Functor.Of -import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS import Data.LogMessage import Data.Maybe import Data.Monoid import Data.Ord import Data.Text (Text) -import qualified Data.Text as T +import Data.Text qualified as T import Data.These import GHC.Generics hiding (to) @@ -122,24 +123,27 @@ import Numeric.Natural import Prelude hiding (lookup) -import qualified Streaming.Prelude as S +import Streaming.Prelude qualified as S import System.LogLevel -import qualified System.Random.MWC as Prob +import System.Random.MWC qualified as Prob import System.Timeout -- internal modules import Chainweb.BlockHash import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB.Internal import Chainweb.BlockHeight import Chainweb.BlockWeight -import Chainweb.BlockHeaderDB.Internal import Chainweb.ChainId import Chainweb.Cut import Chainweb.Cut.CutHashes import Chainweb.Graph -import Chainweb.Payload.PayloadStore +import Chainweb.PayloadProvider +import Chainweb.Storage.Table +import Chainweb.Storage.Table.HashMap +import Chainweb.Storage.Table.RocksDB import Chainweb.Sync.WebBlockHeaderStore import Chainweb.TreeDB import Chainweb.Utils hiding (Codec, check) @@ -147,14 +151,10 @@ import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService -import Chainweb.Storage.Table -import Chainweb.Storage.Table.HashMap -import Chainweb.Storage.Table.RocksDB import Data.PQueue +import Data.TaskMap qualified as TM -import qualified Data.TaskMap as TM import P2P.TaskQueue import Utils.Logging.Trace @@ -255,55 +255,47 @@ instance Exception CutDbStopped where -- | This is a singleton DB that contains the latest chainweb cut as only entry. -- -data CutDb tbl = CutDb +data CutDb = CutDb { _cutDbCut :: !(TVar Cut) , _cutDbQueue :: !(PQueue (Down CutHashes)) , _cutDbAsync :: !(Async ()) , _cutDbLogFunction :: !LogFunction , _cutDbHeaderStore :: !WebBlockHeaderStore - , _cutDbPayloadStore :: !(WebBlockPayloadStore tbl) + , _cutDbPayloadProviders :: !PayloadProviders , _cutDbCutStore :: !(Casify RocksDbTable CutHashes) , _cutDbQueueSize :: !Natural , _cutDbReadOnly :: !Bool , _cutDbFastForwardHeightLimit :: !(Maybe BlockHeight) } -instance HasChainwebVersion (CutDb tbl) where +instance HasChainwebVersion CutDb where _chainwebVersion = _chainwebVersion . _cutDbHeaderStore {-# INLINE _chainwebVersion #-} -cutDbPayloadDb :: Getter (CutDb tbl) (PayloadDb tbl) -cutDbPayloadDb = to $ _webBlockPayloadStoreCas . _cutDbPayloadStore -{-# INLINE cutDbPayloadDb #-} - -cutDbPactService :: Getter (CutDb tbl) WebPactExecutionService -cutDbPactService = to $ _webBlockPayloadStorePact . _cutDbPayloadStore -{-# INLINE cutDbPactService #-} - -cutDbPayloadStore :: Getter (CutDb tbl) (WebBlockPayloadStore tbl) -cutDbPayloadStore = to _cutDbPayloadStore -{-# INLINE cutDbPayloadStore #-} +cutDbPayloadProviders :: Getter CutDb PayloadProviders +cutDbPayloadProviders = to _cutDbPayloadProviders +{-# INLINE cutDbPayloadProviders #-} -- We export the 'WebBlockHeaderDb' read-only -- -cutDbWebBlockHeaderDb :: Getter (CutDb tbl) WebBlockHeaderDb +cutDbWebBlockHeaderDb :: Getter CutDb WebBlockHeaderDb cutDbWebBlockHeaderDb = to $ _webBlockHeaderStoreCas . _cutDbHeaderStore {-# INLINE cutDbWebBlockHeaderDb #-} -cutDbWebBlockHeaderStore :: Getter (CutDb tbl) WebBlockHeaderStore +cutDbWebBlockHeaderStore :: Getter CutDb WebBlockHeaderStore cutDbWebBlockHeaderStore = to _cutDbHeaderStore {-# INLINE cutDbWebBlockHeaderStore #-} -- | Access the blockerheaderdb via the cutdb for a given chain id -- -cutDbBlockHeaderDb :: HasChainId cid => cid -> Fold (CutDb tbl) BlockHeaderDb +cutDbBlockHeaderDb :: HasChainId cid => cid -> Fold CutDb BlockHeaderDb cutDbBlockHeaderDb cid = cutDbWebBlockHeaderDb . ixg (_chainId cid) -- | Get the current 'Cut', which represent the latest chainweb state. -- -- This the main API method of chainweb-consensus. -- -_cut :: CutDb tbl -> IO Cut +_cut :: CutDb -> IO Cut _cut = readTVarIO . _cutDbCut {-# INLINE _cut #-} @@ -311,50 +303,56 @@ _cut = readTVarIO . _cutDbCut -- -- This the main API method of chainweb-consensus. -- -cut :: Getter (CutDb tbl) (IO Cut) +cut :: Getter CutDb (IO Cut) cut = to _cut -addCutHashes :: CutDb tbl -> CutHashes -> IO () +addCutHashes :: CutDb -> CutHashes -> IO () addCutHashes db = pQueueInsertLimit (_cutDbQueue db) (_cutDbQueueSize db) . Down -- | An 'STM' version of '_cut'. -- -- @_cut db@ is generally more efficient than as @atomically (_cut db)@. -- -_cutStm :: CutDb tbl -> STM Cut +_cutStm :: CutDb -> STM Cut _cutStm = readTVar . _cutDbCut -- | An 'STM' version of 'cut'. -- -- @_cut db@ is generally more efficient than as @atomically (_cut db)@. -- -cutStm :: Getter (CutDb tbl) (STM Cut) +cutStm :: Getter CutDb (STM Cut) cutStm = to _cutStm -- | A common idiom to spin while waiting for a guaranteed new `Cut`, different -- from the given one. -- -awaitNewCut :: CutDb tbl -> Cut -> IO Cut -awaitNewCut cdb c = atomically $ do +awaitNewCutStm :: CutDb -> Cut -> STM Cut +awaitNewCutStm cdb c = do c' <- _cutStm cdb when (c' == c) retry return c' +-- | A common idiom to spin while waiting for a guaranteed new `Cut`, different +-- from the given one. +-- +awaitNewCut :: CutDb -> Cut -> IO Cut +awaitNewCut cdb c = atomically $ awaitNewCutStm cdb c + -- | As in `awaitNewCut`, but only updates when the specified `ChainId` has -- grown. -- -awaitNewCutByChainId :: CutDb tbl -> ChainId -> Cut -> IO Cut +awaitNewCutByChainId :: CutDb -> ChainId -> Cut -> IO Cut awaitNewCutByChainId cdb cid c = atomically $ awaitNewCutByChainIdStm cdb cid c {-# INLINE awaitNewCutByChainId #-} -- | As in `awaitNewCut`, but only updates when the header at the specified -- `ChainId` has changed, and only returns that new header. -awaitNewBlock :: CutDb tbl -> ChainId -> BlockHash -> IO BlockHeader +awaitNewBlock :: CutDb -> ChainId -> BlockHash -> IO BlockHeader awaitNewBlock cdb cid bHash = atomically $ awaitNewBlockStm cdb cid bHash -- | As in `awaitNewCut`, but only updates when the header at the specified -- `ChainId` has changed, and only returns that new header. -awaitNewBlockStm :: CutDb tbl -> ChainId -> BlockHash -> STM BlockHeader +awaitNewBlockStm :: CutDb -> ChainId -> BlockHash -> STM BlockHeader awaitNewBlockStm cdb cid bHash = do c <- _cutStm cdb case HM.lookup cid (_cutMap c) of @@ -364,7 +362,7 @@ awaitNewBlockStm cdb cid bHash = do -- | As in `awaitNewCut`, but only updates when the specified `ChainId` has -- grown. -- -awaitNewCutByChainIdStm :: CutDb tbl -> ChainId -> Cut -> STM Cut +awaitNewCutByChainIdStm :: CutDb -> ChainId -> Cut -> STM Cut awaitNewCutByChainIdStm cdb cid c = do c' <- _cutStm cdb let !b0 = HM.lookup cid $ _cutMap c @@ -391,21 +389,20 @@ pruneCuts logfun v conf curAvgBlockHeight cutHashesStore = do compactRangeRocksDb (unCasify cutHashesStore) (Nothing, Just (pruneCutHeight, 0, maxBound :: CutId)) -cutDbQueueSize :: CutDb tbl -> IO Natural +cutDbQueueSize :: CutDb -> IO Natural cutDbQueueSize = pQueueSize . _cutDbQueue withCutDb - :: CanPayloadCas tbl - => CutDbParams + :: CutDbParams -> LogFunction -> WebBlockHeaderStore - -> WebBlockPayloadStore tbl + -> PayloadProviders -> Casify RocksDbTable CutHashes - -> (forall t' . CanReadablePayloadCas t' => CutDb t' -> IO a) + -> (CutDb -> IO a) -> IO a -withCutDb config logfun headerStore payloadStore cutHashesStore a +withCutDb config logfun headerStore providers cutHashesStore a = bracket - (startCutDb config logfun headerStore payloadStore cutHashesStore) + (startCutDb config logfun headerStore providers cutHashesStore) stopCutDb a -- | Start a CutDB. This loads the initial cut from the database (falling back @@ -418,14 +415,13 @@ withCutDb config logfun headerStore payloadStore cutHashesStore a -- read-only version of the payload store. -- startCutDb - :: CanPayloadCas tbl - => CutDbParams + :: CutDbParams -> LogFunction -> WebBlockHeaderStore - -> WebBlockPayloadStore tbl + -> PayloadProviders -> Casify RocksDbTable CutHashes - -> IO (CutDb tbl) -startCutDb config logfun headerStore payloadStore cutHashesStore = mask_ $ do + -> IO CutDb +startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do logg Debug "obtain initial cut" initialCut <- readInitialCut unless (_cutDbParamsReadOnly config) $ @@ -445,7 +441,7 @@ startCutDb config logfun headerStore payloadStore cutHashesStore = mask_ $ do , _cutDbAsync = cutAsync , _cutDbLogFunction = logfun , _cutDbHeaderStore = headerStore - , _cutDbPayloadStore = payloadStore + , _cutDbPayloadProviders = providers , _cutDbQueueSize = _cutDbParamsBufferSize config , _cutDbCutStore = cutHashesStore , _cutDbReadOnly = _cutDbParamsReadOnly config @@ -458,7 +454,7 @@ startCutDb config logfun headerStore payloadStore cutHashesStore = mask_ $ do processor :: PQueue (Down CutHashes) -> TVar Cut -> IO () processor queue cutVar = runForever logfun "CutDB" $ - processCuts config logfun headerStore payloadStore cutHashesStore queue cutVar + processCuts config logfun headerStore providers cutHashesStore queue cutVar readInitialCut :: IO Cut readInitialCut = do @@ -496,7 +492,7 @@ readHighestCutHeaders v logg wbhdb cutHashesStore = withTableIterator (unCasify Left e -> throwM e Right hm -> return hm -fastForwardCutDb :: CutDb cas -> IO () +fastForwardCutDb :: CutDb -> IO () fastForwardCutDb cutDb = do highestCutHeaders <- readHighestCutHeaders v (_cutDbLogFunction cutDb) wbhdb (_cutDbCutStore cutDb) @@ -510,7 +506,7 @@ fastForwardCutDb cutDb = do -- | Stop the cut validation pipeline. -- -stopCutDb :: CutDb tbl -> IO () +stopCutDb :: CutDb -> IO () stopCutDb db = do currentCut <- readTVarIO (_cutDbCut db) unless (_cutDbReadOnly db) $ @@ -542,16 +538,15 @@ cutAvgBlockHeight v = BlockHeight . round . avgBlockHeightAtCutHeight v . _cutHe -- stores the longest cut. -- processCuts - :: CanPayloadCas tbl - => CutDbParams + :: CutDbParams -> LogFunction -> WebBlockHeaderStore - -> WebBlockPayloadStore tbl + -> PayloadProviders -> Casify RocksDbTable CutHashes -> PQueue (Down CutHashes) -> TVar Cut -> IO () -processCuts conf logFun headerStore payloadStore cutHashesStore queue cutVar = do +processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do rng <- Prob.createSystemRandom queueToStream & S.chain (\c -> loggCutId logFun Debug c "start processing") @@ -561,7 +556,7 @@ processCuts conf logFun headerStore payloadStore cutHashesStore queue cutVar = d & S.filterM (fmap not . isCurrent) & S.chain (\c -> loggCutId logFun Info c "fetching all prerequisites") - & S.mapM (cutHashesToBlockHeaderMap conf logFun headerStore payloadStore) + & S.mapM (cutHashesToBlockHeaderMap conf logFun headerStore providers) & S.catMaybes -- ignore unsuccessful values for now @@ -663,7 +658,7 @@ processCuts conf logFun headerStore payloadStore cutHashesStore queue cutVar = d -- produced faster than they are consumed from the stream, the stream skips over -- cuts and always returns the latest cut in the db. -- -cutStream :: MonadIO m => CutDb tbl -> S.Stream (Of Cut) m r +cutStream :: MonadIO m => CutDb -> S.Stream (Of Cut) m r cutStream db = liftIO (_cut db) >>= \c -> S.yield c >> go c where go cur = do @@ -678,9 +673,9 @@ cutStream db = liftIO (_cut db) >>= \c -> S.yield c >> go c -- cuts. Blocks of the same chain are sorted by block height. -- cutStreamToHeaderStream - :: forall m tbl r + :: forall m r . MonadIO m - => CutDb tbl + => CutDb -> S.Stream (Of Cut) m r -> S.Stream (Of BlockHeader) m r cutStreamToHeaderStream db s = S.for (go Nothing s) $ \(T2 p n) -> @@ -707,9 +702,9 @@ cutStreamToHeaderStream db s = S.for (go Nothing s) $ \(T2 p n) -> -- cuts. Blocks of the same chain are sorted by block height. -- cutStreamToHeaderDiffStream - :: forall m tbl r + :: forall m r . MonadIO m - => CutDb tbl + => CutDb -> S.Stream (Of Cut) m r -> S.Stream (Of (Either BlockHeader BlockHeader)) m r cutStreamToHeaderDiffStream db s = S.for (cutUpdates Nothing s) $ \(T2 p n) -> @@ -750,23 +745,22 @@ uniqueBlockNumber :: BlockHeader -> Natural uniqueBlockNumber bh = chainIdInt (_chainId bh) + int (view blockHeight bh) * order (_chainGraph bh) -blockStream :: MonadIO m => CutDb tbl -> S.Stream (Of BlockHeader) m r +blockStream :: MonadIO m => CutDb -> S.Stream (Of BlockHeader) m r blockStream db = cutStreamToHeaderStream db $ cutStream db -blockDiffStream :: MonadIO m => CutDb tbl -> S.Stream (Of (Either BlockHeader BlockHeader)) m r +blockDiffStream :: MonadIO m => CutDb -> S.Stream (Of (Either BlockHeader BlockHeader)) m r blockDiffStream db = cutStreamToHeaderDiffStream db $ cutStream db cutHashesToBlockHeaderMap - :: CanPayloadCas tbl - => CutDbParams + :: CutDbParams -> LogFunction -> WebBlockHeaderStore - -> WebBlockPayloadStore tbl + -> PayloadProviders -> CutHashes -> IO (Maybe (HM.HashMap ChainId BlockHeader)) -- ^ The 'Left' value holds missing hashes, the 'Right' value holds -- a 'Cut'. -cutHashesToBlockHeaderMap conf logfun headerStore payloadStore hs = +cutHashesToBlockHeaderMap conf logfun headerStore providers hs = trace logfun "Chainweb.CutDB.cutHashesToBlockHeaderMap" hsid 1 $ do timeout (_cutDbParamsFetchTimeout conf) go >>= \case Nothing -> do @@ -789,14 +783,17 @@ cutHashesToBlockHeaderMap conf logfun headerStore payloadStore hs = hsid = _cutId hs go = do + -- We collect candidate payloads locally in a table and provide them to + -- the payload provider by inserting them in the evluation contexts for + -- the respective blocks + -- plds <- emptyTable - casInsertBatch plds $ HM.elems $ _cutHashesPayloads hs + tableInsertBatch plds $ HM.toList $ _cutHashesPayloads hs hdrs <- emptyTable casInsertBatch hdrs $ HM.elems $ _cutHashesHeaders hs -- for better error messages on validation failure - -- must be a locally-produced payload let localPayload = _cutHashesLocalPayload hs (headers :> missing) <- S.each (HM.toList $ _cutHashes hs) @@ -816,18 +813,34 @@ cutHashesToBlockHeaderMap conf logfun headerStore payloadStore hs = origin = _cutOrigin hs priority = Priority (- int (_cutHashesHeight hs)) - tryGetBlockHeader hdrs plds localPayload cv@(cid, _) = - (Right <$> mapM (getBlockHeader headerStore payloadStore hdrs plds localPayload cid priority origin) cv) - `catch` \case - (TreeDbKeyNotFound{} :: TreeDbException BlockHeaderDb) -> - return (Left cv) - e -> throwM e + -- tryGetBlockHeader hdrs localPayload cv@(cid, _) = + -- (Right <$> mapM + -- (getBlockHeader minerInfo headerStore hdrs providers localPayload cid priority origin) cv) + -- `catch` \case + -- (TreeDbKeyNotFound{} :: TreeDbException BlockHeaderDb) -> + -- return (Left cv) + -- e -> throwM e + + tryGetBlockHeader hdrs plds localPayload cv@(cid, _) = do + fmap Right $ forM cv $ getBlockHeader + headerStore + hdrs + plds + providers + localPayload + cid + priority + origin + `catch` \case + (TreeDbKeyNotFound{} :: TreeDbException BlockHeaderDb) -> + return (Left cv) + e -> throwM e -- -------------------------------------------------------------------------- -- -- Membership Queries memberOfHeader - :: CutDb tbl + :: CutDb -> ChainId -> BlockHash -- ^ the block hash to look up (the member) @@ -844,7 +857,7 @@ memberOfHeader db cid h ctx = do chainDb = db ^?! cutDbWebBlockHeaderDb . ixg cid memberOfM - :: CutDb tbl + :: CutDb -> ChainId -> BlockHash -- ^ the block hash to look up (the member) @@ -857,7 +870,7 @@ memberOfM db cid h ctx = do where chainDb = db ^?! cutDbWebBlockHeaderDb . ixg cid -member :: CutDb tbl -> ChainId -> BlockHash -> IO Bool +member :: CutDb -> ChainId -> BlockHash -> IO Bool member db cid h = do th <- maxEntry chainDb memberOfHeader db cid h th @@ -869,13 +882,13 @@ member db cid h = do -- | 'CutDb' with type level 'ChainwebVersionName' -- -newtype CutDbT tbl (v :: ChainwebVersionT) = CutDbT (CutDb tbl) +newtype CutDbT (v :: ChainwebVersionT) = CutDbT CutDb deriving (Generic) -data SomeCutDb tbl = forall v . KnownChainwebVersionSymbol v => SomeCutDb (CutDbT tbl v) +data SomeCutDb = forall v . KnownChainwebVersionSymbol v => SomeCutDb (CutDbT v) -someCutDbVal :: ChainwebVersion -> CutDb tbl -> SomeCutDb tbl -someCutDbVal (FromSingChainwebVersion (SChainwebVersion :: Sing v)) db = SomeCutDb $ CutDbT @_ @v db +someCutDbVal :: ChainwebVersion -> CutDb -> SomeCutDb +someCutDbVal (FromSingChainwebVersion (SChainwebVersion :: Sing v)) db = SomeCutDb $ CutDbT @v db -- -------------------------------------------------------------------------- -- -- Queue Stats @@ -884,19 +897,15 @@ data QueueStats = QueueStats { _queueStatsCutQueueSize :: !Natural , _queueStatsBlockHeaderQueueSize :: !Natural , _queueStatsBlockHeaderTaskMapSize :: !Natural - , _queueStatsPayloadQueueSize :: !Natural - , _queueStatsPayloadTaskMapSize :: !Natural } deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData, ToJSON) -getQueueStats :: CutDb tbl -> IO QueueStats +getQueueStats :: CutDb -> IO QueueStats getQueueStats db = QueueStats <$> cutDbQueueSize db <*> pQueueSize (_webBlockHeaderStoreQueue $ view cutDbWebBlockHeaderStore db) <*> (int <$> TM.size (_webBlockHeaderStoreMemo $ view cutDbWebBlockHeaderStore db)) - <*> pQueueSize (_webBlockPayloadStoreQueue $ view cutDbPayloadStore db) - <*> (int <$> TM.size (_webBlockPayloadStoreMemo $ view cutDbPayloadStore db)) -- Logging loggCutId :: HasCutId c => LogFunction -> LogLevel -> c -> T.Text -> IO () From a66e6aa9d22e27185c5b75803e9a2e0a864509e9 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:51 -0800 Subject: [PATCH 033/378] wip-3 (23d874999): src/Chainweb/CutDB/RestAPI/Server.hs --- src/Chainweb/CutDB/RestAPI/Server.hs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Chainweb/CutDB/RestAPI/Server.hs b/src/Chainweb/CutDB/RestAPI/Server.hs index 2167fafd9e..9ad2eda3f1 100644 --- a/src/Chainweb/CutDB/RestAPI/Server.hs +++ b/src/Chainweb/CutDB/RestAPI/Server.hs @@ -65,7 +65,7 @@ import P2P.Peer -- -------------------------------------------------------------------------- -- -- Handlers -cutGetHandler :: CutDb tbl -> Maybe MaxRank -> IO CutHashes +cutGetHandler :: CutDb -> Maybe MaxRank -> IO CutHashes cutGetHandler db Nothing = liftIO $ cutToCutHashes Nothing <$> _cut db cutGetHandler db (Just (MaxRank (Max mar))) = liftIO $ do !c <- _cut db @@ -74,7 +74,7 @@ cutGetHandler db (Just (MaxRank (Max mar))) = liftIO $ do !c' <- limitCut (view cutDbWebBlockHeaderDb db) bh c return $! cutToCutHashes Nothing c' -cutPutHandler :: PeerDb -> CutDb tbl -> CutHashes -> Handler NoContent +cutPutHandler :: PeerDb -> CutDb -> CutHashes -> Handler NoContent cutPutHandler pdb db c = case _peerAddr <$> _cutOrigin c of Nothing -> throwError $ setErrText "Cut is missing an origin entry" err400 Just addr -> do @@ -87,39 +87,39 @@ cutPutHandler pdb db c = case _peerAddr <$> _cutOrigin c of -- Cut API Server cutServer - :: forall tbl (v :: ChainwebVersionT) + :: forall (v :: ChainwebVersionT) . PeerDb - -> CutDbT tbl v + -> CutDbT v -> Server (CutApi v) cutServer pdb (CutDbT db) = liftIO . cutGetHandler db :<|> cutPutHandler pdb db cutGetServer - :: forall tbl (v :: ChainwebVersionT) - . CutDbT tbl v + :: forall (v :: ChainwebVersionT) + . CutDbT v -> Server (CutGetApi v) cutGetServer (CutDbT db) = liftIO . cutGetHandler db -- -------------------------------------------------------------------------- -- -- Some Cut Server -someCutServerT :: PeerDb -> SomeCutDb tbl -> SomeServer -someCutServerT pdb (SomeCutDb (db :: CutDbT tbl v)) = +someCutServerT :: PeerDb -> SomeCutDb -> SomeServer +someCutServerT pdb (SomeCutDb (db :: CutDbT v)) = SomeServer (Proxy @(CutApi v)) (cutServer pdb db) -someCutServer :: ChainwebVersion -> PeerDb -> CutDb tbl -> SomeServer +someCutServer :: ChainwebVersion -> PeerDb -> CutDb -> SomeServer someCutServer v pdb = someCutServerT pdb . someCutDbVal v -someCutGetServerT :: SomeCutDb tbl -> SomeServer -someCutGetServerT (SomeCutDb (db :: CutDbT tbl v)) = +someCutGetServerT :: SomeCutDb -> SomeServer +someCutGetServerT (SomeCutDb (db :: CutDbT v)) = SomeServer (Proxy @(CutGetApi v)) (cutGetServer db) -someCutGetServer :: ChainwebVersion -> CutDb tbl -> SomeServer +someCutGetServer :: ChainwebVersion -> CutDb -> SomeServer someCutGetServer v = someCutGetServerT . someCutDbVal v -- -------------------------------------------------------------------------- -- -- Run Server -serveCutOnPort :: Port -> ChainwebVersion -> PeerDb -> CutDb tbl -> IO () +serveCutOnPort :: Port -> ChainwebVersion -> PeerDb -> CutDb -> IO () serveCutOnPort p v pdb = run (int p) . someServerApplication . someCutServer v pdb From f4b355341a8051d6b21b2cbbd913def2bf8fac71 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:51 -0800 Subject: [PATCH 034/378] wip-3 (23d874999): src/Chainweb/CutDB/Sync.hs --- src/Chainweb/CutDB/Sync.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/CutDB/Sync.hs b/src/Chainweb/CutDB/Sync.hs index 235a084a37..68f37e17e4 100644 --- a/src/Chainweb/CutDB/Sync.hs +++ b/src/Chainweb/CutDB/Sync.hs @@ -84,11 +84,10 @@ catchupStepSize :: CutHeight catchupStepSize = 100 syncSession - :: ChainwebVersion - -> PeerInfo - -> CutDb tbl + :: PeerInfo + -> CutDb -> P2pSession -syncSession v p db logg env pinf = do +syncSession p db logg env pinf = do race_ (S.mapM_ send $ S.map (cutToCutHashes (Just p)) $ cutStream db) (forever $ receive >> approximateThreadDelay 2000000 {- 2 seconds -}) @@ -102,6 +101,7 @@ syncSession v p db logg env pinf = do logg @T.Text Error "unexpectedly exited cut sync session" return False where + v = _chainwebVersion db cenv = CutClientEnv v env send c = do From 8da900521e6ce5de9b9384040a28f62384094339 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:51 -0800 Subject: [PATCH 035/378] wip-3 (23d874999): src/Chainweb/Logging/Miner.hs --- src/Chainweb/Logging/Miner.hs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/Logging/Miner.hs b/src/Chainweb/Logging/Miner.hs index 0868abad0e..7b3e771220 100644 --- a/src/Chainweb/Logging/Miner.hs +++ b/src/Chainweb/Logging/Miner.hs @@ -27,12 +27,15 @@ import GHC.Generics import Chainweb.BlockHeader import Chainweb.Time +import Chainweb.MinerReward +import Numeric.Natural data NewMinedBlock = NewMinedBlock { _minedBlockHeader :: !(ObjectEncoded BlockHeader) - , _minedBlockTrans :: {-# UNPACK #-} !Word - , _minedBlockSize :: {-# UNPACK #-} !Word -- ^ Bytes - , _minedBlockMiner :: !Text + , _minedBlockTrans :: !Natural + , _minedBlockSize :: !Natural + , _minedBlockOutputSize :: !Natural + , _minedBlockFees :: !Stu , _minedBlockDiscoveredAt :: !(Time Micros) } deriving stock (Eq, Show, Generic) @@ -42,7 +45,6 @@ data OrphanedBlock = OrphanedBlock { _orphanedHeader :: !(ObjectEncoded BlockHeader) , _orphanedBestOnCut :: !(ObjectEncoded BlockHeader) , _orphanedDiscoveredAt :: !(Time Micros) - , _orphanedMiner :: !Text , _orphanedReason :: !Text } deriving stock (Eq, Show, Generic) From 8635cfc9caac637032278762c366eafcb0fe5543 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:51 -0800 Subject: [PATCH 036/378] wip-4 (df170909d): src/Chainweb/Miner/Config.hs --- src/Chainweb/Miner/Config.hs | 77 +++++++++++------------------------- 1 file changed, 22 insertions(+), 55 deletions(-) diff --git a/src/Chainweb/Miner/Config.hs b/src/Chainweb/Miner/Config.hs index efe1bdae7c..e19038b129 100644 --- a/src/Chainweb/Miner/Config.hs +++ b/src/Chainweb/Miner/Config.hs @@ -22,18 +22,17 @@ module Chainweb.Miner.Config , pMiningConfig , miningCoordination , miningInNode +, miningMiner +, invalidMiner , validateMinerConfig , CoordinationConfig(..) , pCoordinationConfig , coordinationEnabled -, coordinationMiners , NodeMiningConfig(..) , defaultNodeMining , nodeMiningEnabled -, nodeMiner , nodeTestMiners , MinerCount(..) -, invalidMiner ) where import Configuration.Utils @@ -43,8 +42,6 @@ import Control.Monad (when) import Control.Monad.Except (throwError) import Control.Monad.Writer (tell) -import qualified Data.Set as S - import GHC.Generics (Generic) import Numeric.Natural (Natural) @@ -83,7 +80,7 @@ validateMinerConfig v c = do ] when (not (_coordinationEnabled cc)) $ throwError "In-node mining is enabled but mining coordination is disabled" - when (view minerId (_nodeMiner nmc) == "") + when (view minerId (_miningMiner c) == "") $ throwError "In-node Mining is enabled but no miner id is configured" when (_coordinationEnabled cc && isProd) $ do @@ -92,6 +89,8 @@ validateMinerConfig v c = do [ "Unsupported host architecture for mining on production networks: " <> sshow hostArch <> "." , " Supported architectures are " <> sshow supportedArchs ] + when (view minerId (_miningMiner c) == "") + $ throwError "Mining is enabled but no miner id is configured" where nmc = _miningInNode c cc = _miningCoordination c @@ -106,7 +105,9 @@ validateMinerConfig v c = do -- data MiningConfig = MiningConfig { _miningCoordination :: !CoordinationConfig - , _miningInNode :: !NodeMiningConfig } + , _miningInNode :: !NodeMiningConfig + , _miningMiner :: !Miner + } deriving stock (Eq, Show) miningCoordination :: Lens' MiningConfig CoordinationConfig @@ -115,15 +116,21 @@ miningCoordination = lens _miningCoordination (\m c -> m { _miningCoordination = miningInNode :: Lens' MiningConfig NodeMiningConfig miningInNode = lens _miningInNode (\m c -> m { _miningInNode = c }) +miningMiner :: Lens' MiningConfig Miner +miningMiner = lens _miningMiner (\m c -> m { _miningMiner = c }) + instance ToJSON MiningConfig where toJSON o = object [ "coordination" .= _miningCoordination o - , "nodeMining" .= _miningInNode o ] + , "nodeMining" .= _miningInNode o + , "miner" .= J.toJsonViaEncode (_miningMiner o) + ] instance FromJSON (MiningConfig -> MiningConfig) where parseJSON = withObject "MiningConfig" $ \o -> id <$< miningCoordination %.: "coordination" % o <*< miningInNode %.: "nodeMining" % o + <*< miningMiner ..: "miner" % o instance FromJSON MiningConfig where parseJSON v = do @@ -134,11 +141,17 @@ pMiningConfig :: MParser MiningConfig pMiningConfig = id <$< miningCoordination %:: pCoordinationConfig <*< miningInNode %:: pNodeMiningConfig + <*< miningMiner .:: pMiner "" defaultMining :: MiningConfig defaultMining = MiningConfig { _miningCoordination = defaultCoordination - , _miningInNode = defaultNodeMining } + , _miningInNode = defaultNodeMining + , _miningMiner = invalidMiner + } + +invalidMiner :: Miner +invalidMiner = Miner "" . MinerKeys $ mkKeySet [] "keys-all" -- -------------------------------------------------------------------------- -- -- Mining Coordination Config @@ -148,14 +161,6 @@ data CoordinationConfig = CoordinationConfig { _coordinationEnabled :: !Bool -- ^ Is mining coordination enabled? If not, the @/mining/@ won't even be -- present on the node. - , _coordinationMiners :: !(S.Set Miner) - -- ^ This field must contain at least one `Miner` identity in order for - -- work requests to be made. - , _coordinationReqLimit :: !Int - -- ^ The number of @/mining/work/@ requests that can be made to this node - -- in a 5 minute period. - , _coordinationUpdateStreamLimit :: !Int - -- ^ the maximum number of concurrent update streams that is supported , _coordinationUpdateStreamTimeout :: !Seconds -- ^ the duration that an update stream is kept open in seconds , _coordinationPayloadRefreshDelay :: !(TimeSpan Micros) @@ -165,16 +170,6 @@ data CoordinationConfig = CoordinationConfig coordinationEnabled :: Lens' CoordinationConfig Bool coordinationEnabled = lens _coordinationEnabled (\m c -> m { _coordinationEnabled = c }) -coordinationLimit :: Lens' CoordinationConfig Int -coordinationLimit = lens _coordinationReqLimit (\m c -> m { _coordinationReqLimit = c }) - -coordinationMiners :: Lens' CoordinationConfig (S.Set Miner) -coordinationMiners = lens _coordinationMiners (\m c -> m { _coordinationMiners = c }) - -coordinationUpdateStreamLimit :: Lens' CoordinationConfig Int -coordinationUpdateStreamLimit = - lens _coordinationUpdateStreamLimit (\m c -> m { _coordinationUpdateStreamLimit = c }) - coordinationUpdateStreamTimeout :: Lens' CoordinationConfig Seconds coordinationUpdateStreamTimeout = lens _coordinationUpdateStreamTimeout (\m c -> m { _coordinationUpdateStreamTimeout = c }) @@ -186,9 +181,6 @@ coordinationPayloadRefreshDelay = instance ToJSON CoordinationConfig where toJSON o = object [ "enabled" .= _coordinationEnabled o - , "limit" .= _coordinationReqLimit o - , "miners" .= (J.toJsonViaEncode <$> S.toList (_coordinationMiners o)) - , "updateStreamLimit" .= _coordinationUpdateStreamLimit o , "updateStreamTimeout" .= _coordinationUpdateStreamTimeout o , "payloadRefreshDelay" .= _coordinationPayloadRefreshDelay o ] @@ -196,18 +188,12 @@ instance ToJSON CoordinationConfig where instance FromJSON (CoordinationConfig -> CoordinationConfig) where parseJSON = withObject "CoordinationConfig" $ \o -> id <$< coordinationEnabled ..: "enabled" % o - <*< coordinationLimit ..: "limit" % o - <*< coordinationMiners .fromLeftMonoidalUpdate %.: "miners" % o - <*< coordinationUpdateStreamLimit ..: "updateStreamLimit" % o <*< coordinationUpdateStreamTimeout ..: "updateStreamTimeout" % o <*< coordinationPayloadRefreshDelay ..: "payloadRefreshDelay" % o defaultCoordination :: CoordinationConfig defaultCoordination = CoordinationConfig { _coordinationEnabled = False - , _coordinationMiners = mempty - , _coordinationReqLimit = 1200 - , _coordinationUpdateStreamLimit = 2000 , _coordinationUpdateStreamTimeout = 240 , _coordinationPayloadRefreshDelay = TimeSpan (Micros 15_000_000) } @@ -217,13 +203,6 @@ pCoordinationConfig = id <$< coordinationEnabled .:: enableDisableFlag % long "mining-coordination" <> help "whether to enable the mining coordination API" - <*< coordinationMiners %:: pLeftMonoidalUpdate (S.singleton <$> pMiner "") - <*< coordinationLimit .:: jsonOption - % long "mining-request-limit" - <> help "Number of /mining/work requests that can be made within a 5min period" - <*< coordinationUpdateStreamLimit .:: jsonOption - % long "mining-update-stream-limit" - <> help "maximum number of concurrent update streams that is supported" <*< coordinationUpdateStreamTimeout .:: jsonOption % long "mining-update-stream-timeout" <> help "duration that an update stream is kept open in seconds" @@ -248,9 +227,6 @@ data NodeMiningConfig = NodeMiningConfig { _nodeMiningEnabled :: !Bool -- ^ If enabled, this node will mine with a single CPU along with its -- other responsibilities. - , _nodeMiner :: !Miner - -- ^ If enabled, a `Miner` identity must be supplied in order to assign - -- mining rewards. , _nodeTestMiners :: !MinerCount -- ^ Strictly for testing. } deriving stock (Eq, Show, Generic) @@ -258,36 +234,27 @@ data NodeMiningConfig = NodeMiningConfig nodeMiningEnabled :: Lens' NodeMiningConfig Bool nodeMiningEnabled = lens _nodeMiningEnabled (\m c -> m { _nodeMiningEnabled = c }) -nodeMiner :: Lens' NodeMiningConfig Miner -nodeMiner = lens _nodeMiner (\m c -> m { _nodeMiner = c }) - nodeTestMiners :: Lens' NodeMiningConfig MinerCount nodeTestMiners = lens _nodeTestMiners (\m c -> m { _nodeTestMiners = c }) instance ToJSON NodeMiningConfig where toJSON o = object [ "enabled" .= _nodeMiningEnabled o - , "miner" .= J.toJsonViaEncode (_nodeMiner o) ] instance FromJSON (NodeMiningConfig -> NodeMiningConfig) where parseJSON = withObject "NodeMiningConfig" $ \o -> id <$< nodeMiningEnabled ..: "enabled" % o - <*< nodeMiner ..: "miner" % o pNodeMiningConfig :: MParser NodeMiningConfig pNodeMiningConfig = id <$< nodeMiningEnabled .:: enableDisableFlag % long "node-mining" <> help "ONLY FOR TESTING NETWORKS: whether to enable in node mining" - <*< nodeMiner .:: pMiner "node-" defaultNodeMining :: NodeMiningConfig defaultNodeMining = NodeMiningConfig { _nodeMiningEnabled = False - , _nodeMiner = invalidMiner , _nodeTestMiners = MinerCount 10 } -invalidMiner :: Miner -invalidMiner = Miner "" . MinerKeys $ mkKeySet [] "keys-all" From f2e4470af10a401160c72ef83f6ca583b1145629 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 18:49:56 -0800 Subject: [PATCH 037/378] wip-4 (df170909d): src/Chainweb/Miner/Coordinator.hs --- src/Chainweb/Miner/Coordinator.hs | 1150 ++++++++++++++++++++--------- 1 file changed, 816 insertions(+), 334 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 1b98071458..467897a6ea 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -4,78 +4,66 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE BlockArguments #-} -- | -- Module: Chainweb.Miner.Coordinator --- Copyright: Copyright © 2018 - 2020 Kadena LLC. +-- Copyright: Copyright © 2018 - 2024 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz , Colin Woodbury -- Stability: experimental -- --- - module Chainweb.Miner.Coordinator -( -- * Types - MiningState(..) -, miningState -, MiningStats(..) -, PrevTime(..) -, ChainChoice(..) -, PrimedWork(..) -, WorkState(..) -, MiningCoordination(..) +( -- * Mining Coordination API + MiningCoordination(..) , NoAsscociatedPayload(..) - --- * Mining API Functions +, runCoordination +, newMiningCoordination , work , solve --- ** Internal Functions -, publish -) where +-- * Internal -import Control.Concurrent -import Control.Concurrent.Async (withAsync) -import Control.Concurrent.STM (atomically, retry) -import Control.Concurrent.STM.TVar -import Control.DeepSeq (NFData) -import Control.Lens -import Control.Monad -import Control.Monad.Catch +-- * WorkState +, WorkState(..) -import Data.Aeson (ToJSON) -import qualified Data.ByteString as BS -import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS -import Data.IORef -import qualified Data.List as List -import qualified Data.Map.Strict as M -import Data.Maybe(mapMaybe) -import qualified Data.Text as T -import qualified Data.Vector as V +-- ** Payload Caches +, type PayloadCaches +, newPayloadCaches +, awaitPayloadsNext -import GHC.Generics (Generic) -import GHC.Stack +-- ** MiningState +, MiningState +, updateForCut +, updateForPayload +, updateForSolved -import System.LogLevel (LogLevel(..)) +-- ** Delivery +, randomWork --- internal modules +) where import Chainweb.BlockCreationTime -import Chainweb.BlockHash (BlockHash) +import Chainweb.BlockHash import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.ChainValue import Chainweb.Cut hiding (join) import Chainweb.Cut.Create import Chainweb.Cut.CutHashes @@ -84,17 +72,42 @@ import Chainweb.Core.Brief import Chainweb.Logger (Logger, logFunction) import Chainweb.Logging.Miner import Chainweb.Miner.Config -import Chainweb.Miner.Pact (Miner(..), MinerId(..), minerId) -import Chainweb.Payload -import Chainweb.Sync.WebBlockHeaderStore -import Chainweb.Time (Micros(..), Time(..), getCurrentTimeIntegral) +import Chainweb.Miner.PayloadCache +import Chainweb.PayloadProvider +import Chainweb.Storage.Table +import Chainweb.Time (Micros(..), getCurrentTimeIntegral) import Chainweb.Utils hiding (check) import Chainweb.Version -import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService -import Data.LogMessage (JsonLog(..), LogFunction) +import Control.Applicative +import Control.Concurrent.STM (atomically, STM, retry) +import Control.Concurrent.STM.TVar +import Control.Lens +import Control.Monad +import Control.Monad.Catch +import Control.Monad.IO.Class + +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS +import Data.LogMessage (JsonLog(..), LogFunction, LogFunctionText) +import Data.Map.Strict qualified as M +import Data.Maybe +import Data.Text qualified as T + +import GHC.Generics (Generic) +import GHC.Stack + +import Numeric.Natural + +import Streaming.Prelude qualified as S + +import System.LogLevel (LogLevel(..)) +import System.Random (randomRIO) +import Chainweb.Ranked +import Control.Concurrent.Async +import qualified Data.Vector as V +import Data.Hashable -- -------------------------------------------------------------------------- -- -- Utils @@ -116,339 +129,808 @@ lookupInCut c cid <> " Cut Hashes: " <> encodeToText (cutToCutHashes Nothing c) <> "." -- -------------------------------------------------------------------------- -- --- MiningCoordination +-- Payload Caches --- | For coordinating requests for work and mining solutions from remote Mining --- Clients. --- -data MiningCoordination logger tbl = MiningCoordination - { _coordLogger :: !logger - , _coordCutDb :: !(CutDb tbl) - , _coordState :: !(TVar MiningState) - , _coordLimit :: !Int - , _coord503s :: !(IORef Int) - , _coord403s :: !(IORef Int) - , _coordConf :: !CoordinationConfig - , _coordUpdateStreamCount :: !(IORef Int) - , _coordPrimedWork :: !(TVar PrimedWork) - } +type PayloadCaches = HM.HashMap ChainId PayloadCache --- | Precached payloads for Private Miners. This allows new work requests to be --- made as often as desired, without clogging the Pact queue. +newPayloadCaches + :: HasChainwebVersion v + => HasChainGraph v + => v + -> IO PayloadCaches +newPayloadCaches v = mapM (const (newIO depth)) cids + where + cids :: HM.HashMap ChainId () + cids = HS.toMap (chainIds v) + + -- FIXME: Make this configurable? + depth :: Natural + depth = diameter (_chainGraph v) + +-- | Await the next payload for a cut that is different from the latest payload. -- -newtype PrimedWork = - PrimedWork (HM.HashMap MinerId (HM.HashMap ChainId WorkState)) - deriving newtype (Semigroup, Monoid) - deriving stock Generic - deriving anyclass (Wrapped) +-- FIXME: can this race if some chains get new payloads at a very high rate? +-- How can we make this fair? Maybe shuffle xs? +-- +awaitPayloadsNext + :: PayloadCaches + -> Cut + -- ^ The cut for which the new payload is awaited. + -> V.Vector Int + -- ^ The hash/fingerprint of the previous value for each chain. + -> STM NewPayload +awaitPayloadsNext caches c prevs = msum (awaitChain <$> HM.toList xs) + where + xs = HM.intersectionWith (,) caches rhs + rhs = _rankedBlockHash <$> _cutHeaders c + awaitChain (ChainId cid, (ca, rh)) = do + x <- awaitLatestSTM ca rh + -- unsafeIndex relies on caches prevs being as least as long as caches + -- and chainids being a prefix of the natural numbers. + guard (hash x /= V.unsafeIndex prevs(int cid)) + return x + +-- -------------------------------------------------------------------------- -- +-- Work State + +-- | The mining work state of a chain. +-- +-- Two things must happen for work to be ready: +-- +-- 1. The payload provider must provide a 'NewPayload' and +-- 2. Consensus must provide a cut on which the chain is mineable. +-- +-- When both is available a 'WorkHeader' can be created. +-- +-- We call a chain "blocked" when it can not be mined in the current cut because +-- some adjacent header is missing for a correctly braided extension of the +-- chain. +-- +-- We call a chain "stale" when there is no payload available for extending the +-- chain with a new block. +-- +-- We call a chain "not ready" if a chain is "blocked" and "stale". We call a +-- chain "ready" when it is neither "blocked" nor "stale". +-- +-- We call a chain "solved" if new block has been mined on the chain for the +-- current cut, but the cut has not yet been updated to include the new block. +-- +-- The following demonstrates how cuts are extended: +-- +-- - n: new work +-- - p: work parent +-- - a: work adjacent parent +-- +-- @ +-- * n +-- | x | \ +-- a p a +-- | x | x | +-- * * * +-- @ +-- +-- The extension of indiviual chains in cuts is non-monotonic. It is possible +-- that the parent header remains the same but the adjacent parents change. It +-- is also possible that stale work becomes NotReady or that ready work becomes +-- blocked. Similarly, solved work can become blocked again. If the parent +-- header stayls the same the same payload may be used again. Only if the parent +-- header changes the a new (or an previously used) payload must be obtained. +-- +-- The transition function for the work state machine is as follows: +-- +-- @ +-- "" -> NotReady [label="header"]; +-- NotReady -> Blocked [label=payload]; +-- NotReady -> Stale [label=unblock]; +-- Blocked -> Ready [label=unblock]; +-- Blocked -> Blocked [label=payload]; +-- Stale -> Ready [label=payload]; +-- Stale -> NotReady [label=block]; +-- Stale -> Stale [label=unblock]; +-- Ready -> Solved [label=solve]; +-- Ready -> Blocked [label=block]; +-- Ready -> Ready [label="payload,unblock"]; +-- Solved -> Blocked [label=block]; +-- Solved -> Ready [label=unblock]; +-- Solved -> Solved [label=payload]; +-- @ +-- data WorkState - = WorkReady NewBlock - -- ^ We have work ready for the miner - | WorkAlreadyMined BlockHash - -- ^ A block with this parent has already been mined and submitted to the - -- cut pipeline - we don't want to mine it again. - | WorkStale - -- ^ No work has been produced yet with the latest parent block on this - -- chain. - deriving stock (Show) - -isWorkReady :: WorkState -> Bool -isWorkReady = \case - WorkReady {} -> True - _ -> False - --- | Data shared between the mining threads represented by `newWork` and --- `publish`. --- --- The key is hash of the current block's payload. + = WorkNotReady !RankedBlockHash + -- ^ Chain is blocked and no payload has yet been produced + + | WorkStale !RankedBlockHash !WorkParents + -- ^ The chain is unblocked but no payload has produced yet. + -- + -- Invariant: The work parents must match the ranked block hash. + + | WorkBlocked !RankedBlockHash !NewPayload + -- ^ A payload is ready but the chain is still blocked + -- + -- Invariant: The payload must match the ranked block hash. + + | WorkReady !RankedBlockHash !NewPayload !WorkParents !WorkHeader + -- ^ The chain is ready for mining + -- + -- Invariant: The payload, parents, work header must match the ranked block + -- hash. + + | WorkSolved !RankedBlockHash !NewPayload !WorkParents + -- ^ A block with this parent has already been solved and submitted to the + -- cut pipeline - we don't want to mine it again. So we wait until the + -- current cut is updated with a new parent header for this chain. + + deriving stock (Show, Eq, Generic) + +_workRankedHash :: WorkState -> RankedBlockHash +_workRankedHash (WorkNotReady rh) = rh +_workRankedHash (WorkStale rh _) = rh +_workRankedHash (WorkBlocked rh _) = rh +_workRankedHash (WorkReady rh _ _ _) = rh +_workRankedHash (WorkSolved rh _ _) = rh + +_workStateHash :: WorkState -> BlockHash +_workStateHash = _rankedBlockHashHash . _workRankedHash + +_workStateHeight :: WorkState -> BlockHeight +_workStateHeight = _rankedBlockHashHeight . _workRankedHash + +workReady + :: BlockCreationTime + -> RankedBlockHash + -> NewPayload + -> WorkParents + -> WorkState +workReady t rh pld ps' = WorkReady rh pld ps' + $ newWork t ps' + $ _newPayloadBlockPayloadHash pld + +_newPayloadRankedHash :: NewPayload -> RankedBlockHash +_newPayloadRankedHash p = + RankedBlockHash (_newPayloadParentHeight p) (_newPayloadParentHash p) + +instance Brief WorkState where + brief (WorkNotReady rh) = "WorkNotReady" <> ":" <> brief rh + brief (WorkStale rh _) = "WorkStale" <> ":" <> brief rh + brief (WorkBlocked rh _) = "WorkBlocked" <> ":" <> brief rh + brief (WorkReady rh _ _ _) = "WorkReady" <> ":" <> brief rh + brief (WorkSolved rh _ _) = "WorkSolved" <> ":" <> brief rh + +-- -------------------------------------------------------------------------- -- +-- WorkState Transition Function + +-- | Called on headers event +-- +onHeader + :: RankedBlockHash + -> WorkState + -> Maybe WorkState +onHeader rh cur + | rh == _workRankedHash cur = Nothing + | otherwise = Just $ WorkNotReady rh + +-- | Called on a work headers event. +-- +-- If the parent header changes, 'onHeader' is called first, which also resets +-- the payload. For that reason it should be also checked whether there is a +-- payload available for the new header. +-- +onParents + :: BlockCreationTime + -> Maybe WorkParents + -- Just the work parents or 'Nothing' when the chain is blocked. + -> WorkState + -> Maybe WorkState +onParents t (Just ps) cur | psRankedHash /= _workRankedHash cur = + onHeader psRankedHash cur >>= onParents t (Just ps) + where + psRankedHash = _rankedParentHash $ _workParent ps +onParents _ (Just ps') (WorkNotReady rh) = Just $ WorkStale rh ps' +onParents t (Just ps') (WorkBlocked rh pld) = Just $ workReady t rh pld ps' +onParents _ (Just ps') (WorkStale rh ps) + | ps /= ps' = Just $ WorkStale rh ps' + | otherwise = Nothing +onParents t (Just ps') (WorkReady rh pld ps _) + | ps /= ps' = Just $ workReady t rh pld ps' + | otherwise = Nothing +onParents t (Just ps') (WorkSolved rh pld ps) + | ps /= ps' = Just $ workReady t rh pld ps' + | otherwise = Nothing +onParents _ Nothing WorkNotReady{} = Nothing +onParents _ Nothing WorkBlocked{} = Nothing +onParents _ Nothing (WorkStale rh _) = Just $ WorkNotReady rh +onParents _ Nothing (WorkReady rh pld _ _) = Just $ WorkBlocked rh pld +onParents _ Nothing (WorkSolved rh pld _) = Just $ WorkBlocked rh pld + +-- | Called on a new payload event. +-- +-- If the parent header of the new payload does not match the current parent +-- header, it is ignored. Only an 'onParents' event can update the current +-- parent header. +-- +onPayload + :: BlockCreationTime + -> NewPayload + -> WorkState + -> Maybe WorkState +onPayload _ pld' cur | _newPayloadRankedParentHash pld' /= _workRankedHash cur = Nothing +onPayload _ pld' (WorkNotReady rh) = Just $ WorkBlocked rh pld' +onPayload t pld' (WorkStale rh ps) = Just $ workReady t rh pld' ps +onPayload _ pld' (WorkBlocked rh pld) + | pld /= pld' = Just $ WorkBlocked rh pld' + | otherwise = Nothing +onPayload t pld' (WorkReady rh pld ps _) + | pld /= pld' = Just $ workReady t rh pld' ps + | otherwise = Nothing +onPayload _ _ WorkSolved{} = Nothing + +-- | Called when work is solved for the chain. +-- +onSolved + :: SolvedWork + -> WorkState + -> Maybe WorkState +onSolved (SolvedWork hdr) (WorkSolved rh _ ps) + -- If we solved this header in this cut before we do not change the work + -- state, even if the payload differs. + | _rankedBlockHash hdr == rh + && view blockAdjacentHashes hdr /= _workParentsAdjacentHashes ps = Nothing +onSolved (SolvedWork hdr) (WorkReady rh pld ps _) + -- If work is currently ready for this header in this cut, we mark it solved. + | _rankedBlockHash hdr == rh + && view blockAdjacentHashes hdr /= _workParentsAdjacentHashes ps = + Just $ WorkSolved rh pld ps + -- otherwise do not change the state. +onSolved _ _ = Nothing + +-- -------------------------------------------------------------------------- -- +-- Mining State + +-- | Current Mining State on each chain. It is updated +-- +-- 1. each time a new cut is received +-- 2. each time a block is solved +-- 3. each time a new payload is received for a chain -- newtype MiningState = MiningState - { _miningState :: M.Map BlockPayloadHash (T3 Miner PayloadWithOutputs (Time Micros)) } + { _miningState :: M.Map ChainId (TVar WorkState) + } deriving stock (Generic) deriving newtype (Semigroup, Monoid) makeLenses ''MiningState --- | For logging during `MiningState` manipulation. --- -data MiningStats = MiningStats - { _statsCacheSize :: !Int - , _stats503s :: !Int - , _stats403s :: !Int - , _statsAvgTxs :: !Int - , _statsPrimedSize :: !Int } - deriving stock (Generic) - deriving anyclass (ToJSON, NFData) +type instance Index MiningState = ChainId +type instance IxValue MiningState = TVar WorkState --- | The `BlockCreationTime` of the parent of some current, "working" --- `BlockHeader`. --- -newtype PrevTime = PrevTime BlockCreationTime +instance Ixed MiningState where + ix i = miningState . ix i -data ChainChoice = Anything | TriedLast !ChainId | Suggestion !ChainId +instance IxedGet MiningState --- | Construct a new `BlockHeader` to mine on. --- -newWork - :: LogFunction - -> ChainChoice - -> Miner - -> WebBlockHeaderDb - -- ^ this is used to lookup parent headers that are not in the cut - -- itself. - -> PactExecutionService - -> TVar PrimedWork - -> Cut - -> IO (Maybe (T2 WorkHeader PayloadWithOutputs)) -newWork logFun choice eminer@(Miner mid _) hdb pact tpw c = do +instance Each MiningState MiningState (TVar WorkState) (TVar WorkState) where + each f = fmap MiningState . each f . _miningState - -- Randomly pick a chain to mine on. we no longer support the caller - -- specifying any particular one. - -- - cid <- case choice of - Anything -> randomChainIdAt c (_cutMinHeight c) - Suggestion cid' -> pure cid' - TriedLast _ -> randomChainIdAt c (_cutMinHeight c) - logFun @T.Text Debug $ "newWork: picked chain " <> toText cid - - -- wait until at least one chain has primed work. we don't wait until *our* - -- chain has primed work, because if other chains have primed work, we want - -- to loop and select one of those chains. it is not a normal situation to - -- have no chains with primed work if there are more than a couple chains. - mpw <- atomically $ do - PrimedWork pw <- readTVar tpw - mpw <- maybe retry return (HM.lookup mid pw) - guard (any isWorkReady mpw) - return mpw - let mr = T2 - <$> HM.lookup cid mpw - <*> getCutExtension c cid - - case mr of - Just (T2 WorkStale _) -> do - logFun @T.Text Debug $ "newWork: chain " <> toText cid <> " has stale work" - newWork logFun Anything eminer hdb pact tpw c - Just (T2 (WorkAlreadyMined _) _) -> do - logFun @T.Text Debug $ "newWork: chain " <> sshow cid <> " has a payload that was already mined" - newWork logFun Anything eminer hdb pact tpw c - Nothing -> do - logFun @T.Text Debug $ "newWork: chain " <> toText cid <> " not mineable" - newWork logFun Anything eminer hdb pact tpw c - Just (T2 (WorkReady newBlock) extension) -> do - let (primedParentHash, primedParentHeight, _) = newBlockParent newBlock - if primedParentHash == view blockHash (_parentHeader (_cutExtensionParent extension)) - then do - let payload = newBlockToPayloadWithOutputs newBlock - let !phash = _payloadWithOutputsPayloadHash payload - !wh <- newWorkHeader hdb extension phash - pure $ Just $ T2 wh payload - else do - -- The cut is too old or the primed work is outdated. Probably - -- the former because it the mining coordination background job - -- is updating the primed work cache regularly. We could try - -- another chain, but it's safer to just return 'Nothing' here - -- and retry with an updated cut. - -- - let !extensionParent = _parentHeader (_cutExtensionParent extension) - logFun @T.Text Info - $ "newWork: chain " <> toText cid <> " not mineable because of parent header mismatch" - <> ". Primed parent hash: " <> toText primedParentHash - <> ". Primed parent height: " <> sshow primedParentHeight - <> ". Extension parent: " <> toText (view blockHash extensionParent) - <> ". Extension height: " <> sshow (view blockHeight extensionParent) - - return Nothing +newMiningState :: Cut -> IO MiningState +newMiningState c = do + states <- forM cids $ \cid -> do + var <- newTVarIO + $ WorkNotReady + $ _rankedBlockHash + $ fromMaybe (genesisBlockHeader v cid) (HM.lookup cid (_cutMap c)) + return (cid, var) + return $ MiningState $! M.fromList states + where + v = _chainwebVersion c --- | Accepts a "solved" `BlockHeader` from some external source (e.g. a remote --- mining client), attempts to reassociate it with the current best `Cut`, and --- publishes the result to the `Cut` network. + cids :: [ChainId] + cids = HS.toList (chainIds v) + +updateStateVar :: LogFunctionText -> ChainId -> TVar WorkState -> WorkState -> IO () +updateStateVar lf cid var new = do + + -- Logging. This can race, but we don't care + cur <- readTVarIO var + lf Debug $ "update work state" + <> "; chain: " <> toText cid + <> "; cur: " <> brief cur + <> "; new: " <> brief new + + atomically $ writeTVar var new + +-- TODO: consider storing the mining state more efficiently: -- --- There are a number of "fail fast" conditions which will kill the candidate --- `BlockHeader` before it enters the Cut pipeline. +-- Do not recompute cut extensions more often than needed. -- -publish - :: LogFunction - -> CutDb tbl - -> TVar PrimedWork - -> MinerId - -> PayloadWithOutputs - -> SolvedWork +-- * store the current cut +-- * when a new cut arrive compute which chains need update + +-- | Update work state for all chains for a new cut. +-- +updateForCut + :: LogFunctionText + -> (ChainValue BlockHash -> IO BlockHeader) + -> PayloadCaches + -> MiningState + -> Cut -> IO () -publish lf cdb pwVar miner pwo s = do - c <- _cut cdb - now <- getCurrentTimeIntegral - try (extend c pwo s) >>= \case - - -- Publish CutHashes to CutDb and log success - Right (bh, Just ch) -> do - - -- reset the primed payload for this cut extension - atomically $ modifyTVar pwVar $ \(PrimedWork pw) -> - PrimedWork $! HM.adjust (HM.insert (_chainId bh) (WorkAlreadyMined (view blockParent bh))) miner pw - - addCutHashes cdb ch - - let bytes = sum . fmap (BS.length . _transactionBytes . fst) $ - _payloadWithOutputsTransactions pwo - lf Info $ JsonLog $ NewMinedBlock - { _minedBlockHeader = ObjectEncoded bh - , _minedBlockTrans = int . V.length $ _payloadWithOutputsTransactions pwo - , _minedBlockSize = int bytes - , _minedBlockMiner = _minerId miner - , _minedBlockDiscoveredAt = now - } - - -- Log Orphaned Block - Right (bh, Nothing) -> do - let !p = lookupInCut c bh - lf Info $ orphandMsg now p bh "orphaned solution" - - -- Log failure and rethrow - Left e@(InvalidSolvedHeader bh msg) -> do - let !p = lookupInCut c bh - lf Info $ orphandMsg now p bh msg - throwM e +updateForCut lf hdb caches ms c = do + t <- BlockCreationTime <$> getCurrentTimeIntegral + forM_ (M.toList $ _miningState ms) $ \(cid, var) -> + forChain t cid var (caches ^?! ix cid) where - orphandMsg now p bh msg = JsonLog OrphanedBlock - { _orphanedHeader = ObjectEncoded bh - , _orphanedBestOnCut = ObjectEncoded p - , _orphanedDiscoveredAt = now - , _orphanedMiner = _minerId miner - , _orphanedReason = msg - } + forChain t cid var cache = do + ps <- workParents hdb c cid + cur <- readTVarIO var + + -- logging + -- cs <- sizeIO cache + -- cl <- getLatestIO cache (_workRankedHash cur) + -- ch <- payloadHashesIO cache + -- lf @T.Text Debug $ "updateForCut for chain: " <> brief cid + -- <> "; cur: " <> brief cur + -- <> "; cut: " <> brief (c ^?! ixg cid) + -- <> "; parent: " <> brief (_workParent <$> ps) + -- <> "; cache size: " <> sshow cs + -- <> "; cache depth: " <> sshow (_payloadCacheDepth cache) + -- <> "; cache latest: " <> brief cl + -- <> "; cache hashes: " <> brief ch + + case onParents t ps cur of + Nothing -> return () + Just !new + + -- Check whether the parent header is still the same + | _workRankedHash new == _workRankedHash cur -> + updateStateVar lf cid var new + + -- if the parent header changed, check if a payload is available + | otherwise -> getLatestIO cache (_workRankedHash new) >>= \case + Nothing -> updateStateVar lf cid var new + Just pld -> case onPayload t pld new of + Nothing -> updateStateVar lf cid var new + Just !newnew -> updateStateVar lf cid var newnew + +updateForPayload :: LogFunctionText -> MiningState -> NewPayload -> IO () +updateForPayload lf ms pld = do + t <- BlockCreationTime <$> getCurrentTimeIntegral + cur <- readTVarIO var + + -- lf @T.Text Debug $ "updateForPayload on chain: " <> toText cid + -- <> "; cur: " <> brief cur + -- <> "; new payload: " <> brief pld + + case onPayload t pld cur of + Nothing -> return () + Just !new -> updateStateVar lf cid var new + where + cid = _chainId pld + var = ms ^?! ixg cid + +updateForSolved :: LogFunctionText -> MiningState -> SolvedWork -> IO () +updateForSolved lf ms sw = do + cur <- readTVarIO var + + -- lf @T.Text Debug $ "updateForSolved on chain: " <> toText cid + -- <> "; cur: " <> brief cur + -- <> "; sw: " <> brief sw + + case onSolved sw cur of + Nothing -> return () + Just !new -> updateStateVar lf cid var new + where + cid = _chainId sw + var = ms ^?! ixg cid + +awaitAnyReady :: MiningState -> STM WorkHeader +awaitAnyReady s = msum $ awaitWorkReady <$> _miningState s + where + awaitWorkReady :: TVar WorkState -> STM WorkHeader + awaitWorkReady var = readTVar var >>= \case + WorkReady _ _ _ w -> return w + _ -> retry -- -------------------------------------------------------------------------- -- --- Mining API +-- MiningCoordination --- | Get new work +-- | For coordinating requests for work and mining solutions from remote Mining +-- Clients. -- --- This function does not check if the miner is authorized. If the miner doesn't --- yet exist in the primed work cache it is added. +data MiningCoordination logger = MiningCoordination + { _coordLogger :: !logger + , _coordCutDb :: !CutDb + , _coordState :: !MiningState + , _coordConf :: !CoordinationConfig + , _coordPayloadCache :: !PayloadCaches + } + +newMiningCoordination + :: Logger logger + => logger + -> CoordinationConfig + -> CutDb + -> IO (MiningCoordination logger) +newMiningCoordination logger conf cdb = do + c <- _cut cdb + state <- newMiningState c + caches <- newPayloadCaches c + return $ MiningCoordination + { _coordLogger = logger + , _coordCutDb = cdb + , _coordState = state + , _coordConf = conf + , _coordPayloadCache = caches + } + +-- | Listen on the cut stream and payloads stream and update the mining state +-- accordingly. -- -work - :: forall l tbl +-- FIXME: be more concrete about the following: +-- +-- Updates are intentionally not transactional. We value low latencies and and +-- progress over consistency with respect to the latest state of the payload +-- caches or cut pipeline. However, individual payload states are internally +-- consistent. +-- +-- It can thus happen that updates to payloads and cuts are lost due to races. +-- This is supposed to be rare. +-- +runCoordination + :: forall l . Logger l - => MiningCoordination l tbl - -> Maybe ChainId - -> Miner - -> IO WorkHeader -work mr mcid m = do - T2 wh pwo <- - withAsync (logDelays False 0) $ \_ -> newWorkForCut - now <- getCurrentTimeIntegral - atomically - . modifyTVar' (_coordState mr) - . over miningState - . M.insert (_payloadWithOutputsPayloadHash pwo) - $ T3 m pwo now - return wh + => MiningCoordination l + -> IO () +runCoordination mr = do + + -- Initialize Work State for provider caches, without this isolated networks + -- fail to start mining. + initializeState + + concurrentlies_ + $ updateWork + : (updateCache <$> HM.toList caches) where - -- here we log the case that the work loop has stalled. - logDelays :: Bool -> Int -> IO () - logDelays loggedOnce n = do - if loggedOnce - then threadDelay 60_000_000 - else threadDelay 10_000_000 - let !n' = n + 1 - PrimedWork primedWork <- readTVarIO (_coordPrimedWork mr) - -- technically this is in a race with the newWorkForCut function, - -- which is likely benign when the mining loop has stalled for 10 seconds. - currentCut <- _cut cdb - let primedWorkMsg = - case HM.lookup (view minerId m) primedWork of - Nothing -> - "no primed work for miner key" <> sshow m - Just mpw -> - let chainsWithBlocks = HS.fromMap $ flip HM.mapMaybe mpw $ \case - WorkReady {} -> Just () - _ -> Nothing - in if - | HS.null chainsWithBlocks -> - "no chains have primed blocks" - | cids == chainsWithBlocks -> - "all chains have primed blocks" - | otherwise -> - "chains with primed blocks may be stalled. chains with primed work: " - <> sshow (toText <$> List.sort (HS.toList chainsWithBlocks)) - let extensibleChains = - HS.fromList $ mapMaybe (\cid -> cid <$ getCutExtension currentCut cid) $ HS.toList cids - let extensibleChainsMsg = - if HS.null extensibleChains - then "no chains are extensible in the current cut! here it is: " <> sshow currentCut - else "the following chains can be extended in the current cut: " <> sshow (toText <$> HS.toList extensibleChains) - logf @T.Text Warn $ - "findWork: stalled for " <> - ( - if loggedOnce - then "10s" - else sshow n' <> "m" - ) <> - ". " <> primedWorkMsg <> ". " <> extensibleChainsMsg - - logDelays True n' - - v = _chainwebVersion hdb - cids = chainIds v - - -- There is no strict synchronization between the primed work cache and the - -- new work selection. There is a chance that work selection picks a primed - -- work that is out of sync with the current cut. In that case we just try - -- again with a new cut. In case the cut was good but the primed work was - -- outdated, chances are that in the next attempt we pick a different chain - -- with update work or that the primed work cache caught up in the meantime. - -- - newWorkForCut = do - c' <- _cut cdb - newWork logf choice m hdb pact (_coordPrimedWork mr) c' >>= \case - Nothing -> newWorkForCut - Just x -> return x + lf :: LogFunctionText + lf = logFunction $ _coordLogger mr - logf :: LogFunction - logf = logFunction $ _coordLogger mr + caches = _coordPayloadCache mr + state = _coordState mr hdb :: WebBlockHeaderDb - hdb = view cutDbWebBlockHeaderDb cdb + hdb = view cutDbWebBlockHeaderDb (_coordCutDb mr) - choice :: ChainChoice - choice = maybe Anything Suggestion mcid + f :: ChainValue BlockHash -> IO BlockHeader + f = fmap _chainValueValue . casLookupM hdb - cdb :: CutDb tbl cdb = _coordCutDb mr - pact :: PactExecutionService - pact = _webPactExecutionService $ view cutDbPactService cdb + providers = view cutDbPayloadProviders $ _coordCutDb mr + + -- Update the payload cache with the latest payloads from the the provider + -- + updateCache (cid, cache) = runForever lf label $ do + withPayloadProvider providers cid $ \provider -> do + payloadStream provider + & S.chain (\_ -> lf Info $ "update cache on chain " <> toText cid) + & S.mapM_ (insertIO cache) + where + label = "miningCoordination.updateCache." <> toText cid + + -- Update the work state + -- + updateWork = runForever lf "miningCoordination" $ do + lf Info "start updateWork event stream" + eventStream cdb caches + & S.chain (\e -> lf Info $ "coordination event: " <> brief e) + & S.mapM_ \case + CutEvent c -> updateForCut lf f caches state c + NewPayloadEvent c -> updateForPayload lf state c + -- There is a race with solved events. Does it matter? + -- We could synchronize those by delivering those via an + -- STM variable, too. + + -- FIXME: this is probably more aggressive than needed + initializeState = do + lf Info $ "initialize mining state" + forConcurrently_ (HM.toList caches) $ \(cid, cache) -> do + lf Info $ "initialize mining state for chain " <> brief cid + pld <- withPayloadProvider providers cid latestPayloadIO + lf Info $ "got latest payload for chain " <> brief cid + insertIO cache pld + curRh <- _workRankedHash <$> readTVarIO (_miningState state ^?! ix cid) + lf Info $ "got current rh for chain " <> brief cid + l <- awaitLatestIO cache curRh + lf Info $ "got new payload for chain " <> brief cid + updateForPayload lf state l + curCut <- _cut $ cdb + updateForCut lf f caches state curCut + lf Info "done initializing mining state for all chains" + +-- | Note that this stream is lossy. It always delivers the latest available +-- item and skips over any previous items that have not been consumed. +-- +-- We want this behavior, because we want to alway operate on the latest +-- available cut and payload. +-- +-- FIXME: would it be better to use a mutable vector in an IORef (or TVar, if +-- supported)? It would probably be more efficient. +-- +eventStream + :: MonadIO m + => CutDb + -> PayloadCaches + -> S.Stream (S.Of MiningStateEvent) m r +eventStream cdb caches = do + liftIO (_cut cdb) >>= \c -> do + S.yield (CutEvent c) + + -- Initialze the vector with the previous NewPayload fingerprints with + -- all 0 hashes. + go c (V.replicate (length caches) 0) + where + go cur prevs = do + timeout <- liftIO $ registerDelay 1_000_000 + new <- liftIO $ atomically $ do + CutEvent cur <$ (readTVar timeout >>= guard) + <|> + awaitEvent cdb caches cur prevs + S.yield new + case new of + CutEvent c -> go c prevs + NewPayloadEvent p -> + go cur (V.unsafeUpd prevs [(chainIdInt (_chainId p), hash p)]) + +data MiningStateEvent + = CutEvent Cut + | NewPayloadEvent NewPayload + deriving (Show, Eq, Generic) + +instance Brief MiningStateEvent where + brief (CutEvent c) = "CutEvent::" <> brief c + brief (NewPayloadEvent p) = "NewPayloadEvent::" <> brief p + +-- | NOTE: Cut and payload event race, with precedence given to cuts. If cut +-- arrive at a rate faster than they can be consumed, no payloads are going to +-- be delivered and work will always be 'Stale'. +-- +-- On average cuts arrive at a rate of 1.5 seconds, which is plenty of time to +-- process payload events in between. However, we should monitor for this +-- condition and either warn or throttle cut processing if it ever happens. +-- +awaitEvent + :: CutDb + -> PayloadCaches + -> Cut + -> V.Vector Int + -> STM MiningStateEvent +awaitEvent cdb caches c p = + CutEvent <$> awaitNewCutStm cdb c + <|> + NewPayloadEvent <$> awaitPayloadsNext caches c p + +-- -------------------------------------------------------------------------- -- +-- Work Delivery Strategy +-- -------------------------------------------------------------------------- -- + +-- | Get Work from a random Chain. +-- +-- In a chainweb it can never happen that all chains are blocked at the same +-- time. Therefore, if there is no work ready, some chains must be stale. This +-- means that either +-- +-- 1. mining is disableled for some payload providers, +-- 2. consensus did not request new payloads from providers in the latest +-- 'syncToBlock' calls, +-- 3. some payload providers are deadlocked, or +-- 4. some payload providers are very slow in producing new payloads. +-- +randomWork :: LogFunction -> MiningState -> IO WorkHeader +randomWork logFun state = do + + -- Pick a random chain. + -- + -- This is done by picking a random start index and traversing the work + -- states for all chains in order until ready work is found. + -- + -- Before a graph transition (from when the new graph is declared until it + -- goes into effect) this code is somewhat inefficient, because the new + -- chains do already exist but are always blocked. This means that sometimes + -- the algorithm has to iterate over all new chains to find the next chain + -- that has work ready. We could prune the random range and search space, + -- but at this, point the slight, temporary overhead seems acceptable. + -- + n <- randomRIO (0, M.size m) + + -- NOTE: it is tempting to search for a matching chain within a single STM + -- transaction. However, that is problematic: the search restarts when the + -- work for a chain is updated, which could result in redundant and possibly + -- unbound spinning. Even worse, it could also skew the result due to some + -- chains being more likely to trigger a retry than others. + -- + -- The actual implemention is not transactional but the returned work will + -- still be up to date for the chain. There might be a risk for a small bias + -- towards chains for which block are produced more quickly, but we think, + -- that it is negligible. + -- + let (s0, s1) = M.splitAt n m + go (M.toList s1 <> M.toList s0) + where + m = _miningState state + + go [] = do + + logFun @T.Text Warn $ "randomWork: no work is ready. Awaiting work" + + -- We shall check for the following conditions: + -- + -- 1. No chain is ready and we haven't received neither new cuts nor + -- new payloads. This is some sort deadlock in the system. + -- + -- 2. We get new cuts (i.e. chains become unblocked and blocked), but + -- no chain becomes ready. Either payload providers are + -- misconfigured or consensus does not request payload creation. + -- + -- It is also possible that payload production is too slow and + -- chains get updated before a value payload is produced + -- + -- We shall detect the problem by waiting for the respective condition + -- and return the info from the STM loop. + -- + -- We may also retry before we fail definitely. + -- + -- There is a small risk that work gets updated to often and this check + -- spins. But this is very unlikely, because otherwise we wouldn't have + -- gotten stuck in first place. + -- + -- There is a (small) chance that the overall system got too hot, i.e. + -- block are produced too fast for this node to keep up. But the fact + -- that blocks are produced means other mining nodes can keep up. This + -- node will recover, once DA causes the system to cool down. + -- + timeoutVar <- registerDelay (int staleMiningStateDelay) + w <- atomically $ + Right <$> awaitAnyReady state <|> awaitTimeout timeoutVar + case w of + Right x -> return x + Left e -> error e -- FIXME: throw a proper exception and log what is going on + + go ((cid, var):t) = readTVarIO var >>= \case + WorkReady _ _ _ wh -> do + logFun @T.Text Debug $ "randomWork: picked chain " <> brief cid + return wh + e -> do + logFun @T.Text Info $ "randomWork: not ready for " <> brief cid + <> "; state: " <> brief e + go t + + awaitTimeout var = do + + -- this retries until the timeout triggers + readTVar var >>= guard + + -- FIXME: + -- Try to find out what happend: + -- + -- if all chains are not ready or stale we did not get any payload + -- We log a warning and retry + -- + -- if all chains are not ready or blocked we have a consensus problem + -- This is clearly an error, probably even a bug. + -- We should try to find out what happend. + -- + return $ Left "Chainweb.Miner.Coordinator.randomWork: timeout while waiting for work to become ready" + +staleMiningStateDelay :: Micros +staleMiningStateDelay = 2_000_000 + +-- | This is the legacy work delivery API +-- +work + :: forall l + . Logger l + => MiningCoordination l + -> IO WorkHeader +work mr = randomWork lf (_coordState mr) + where + lf :: LogFunction + lf = logFunction $ _coordLogger mr + +-- -------------------------------------------------------------------------- -- +-- Solve Work data NoAsscociatedPayload = NoAsscociatedPayload deriving (Show, Eq) instance Exception NoAsscociatedPayload +-- | Accepts a "solved" `BlockHeader` from some external source (e.g. a remote +-- mining client). +-- +-- It looks up the payload of the solved header from the payload cache and +-- attempts to reassociate it with the current best `Cut`. +-- +-- When the solved work is still valid it is marked as solved in the mining +-- state, logged, injected into the local cut pipline, and finally published to +-- the cut P2P network. +-- +-- There are a number of "fail fast" conditions which will kill the candidate +-- `BlockHeader` before it enters the Cut pipeline. +-- solve - :: forall l tbl + :: forall l . Logger l - => MiningCoordination l tbl + => MiningCoordination l -> SolvedWork -> IO () -solve mr solved@(SolvedWork hdr) = do - -- Fail Early: If a `BlockHeader` comes in that isn't associated with any - -- Payload we know about, reject it. - -- - MiningState ms <- readTVarIO tms - case M.lookup key ms of - Nothing -> throwM NoAsscociatedPayload - Just x -> publishWork x `finally` deleteKey - -- There is a race here, but we don't care if the same cut - -- is published twice. There is also the risk that an item - -- doesn't get deleted. Items get GCed on a regular basis by - -- the coordinator. +solve mr solved@(SolvedWork hdr) = + lookupIO cache cacheKey (view blockPayloadHash hdr) >>= \case + + Nothing -> do + ch <- payloadHashesIO cache + lf Error $ "solve: no payload for " <> brief hdr + <> "; cache key: " <> brief cacheKey + <> "; cache content: " <> brief ch + throwM NoAsscociatedPayload + -- FIXME Do we really need to restart the coordinator? + + Just np -> do + c <- _cut cdb + now <- getCurrentTimeIntegral + let pld = _newPayloadEncodedPayloadData np + let pwo = _newPayloadEncodedPayloadOutputs np + + try (extend c pld pwo solved) >>= \case + + -- Publish CutHashes to CutDb and log success + Right (bh, Just ch) -> do + updateForSolved lf (_coordState mr) solved + publish cdb ch + logMinedBlock lf bh np + + -- Log Orphaned Block + Right (bh, Nothing) -> do + let !p = lookupInCut c bh + lf Info $ orphandMsg now p bh "orphaned solution" + + -- Log failure and rethrow + Left e@(InvalidSolvedHeader bh msg) -> do + let !p = lookupInCut c bh + lf Info $ orphandMsg now p bh msg + throwM e where - key = view blockPayloadHash hdr - tms = _coordState mr + cid = _chainId solved + cdb = _coordCutDb mr + caches = _coordPayloadCache mr + cache = caches HM.! cid + cacheKey = RankedBlockHash (view blockHeight hdr - 1) (view blockParent hdr) lf :: LogFunction lf = logFunction $ _coordLogger mr - deleteKey = atomically . modifyTVar' tms . over miningState $ M.delete key - publishWork (T3 m pwo _) = - publish lf (_coordCutDb mr) (_coordPrimedWork mr) (view minerId m) pwo solved + orphandMsg now p bh msg = JsonLog OrphanedBlock + { _orphanedHeader = ObjectEncoded bh + , _orphanedBestOnCut = ObjectEncoded p + , _orphanedDiscoveredAt = now + , _orphanedReason = msg + } + +logMinedBlock + :: LogFunction + -> BlockHeader + -> NewPayload + -> IO () +logMinedBlock lf bh np = do + now <- getCurrentTimeIntegral + lf Info $ JsonLog $ NewMinedBlock + { _minedBlockHeader = ObjectEncoded bh + , _minedBlockTrans = _newPayloadTxCount np + , _minedBlockSize = _newPayloadSize np + , _minedBlockOutputSize = _newPayloadOutputSize np + , _minedBlockFees = _newPayloadFees np + , _minedBlockDiscoveredAt = now + } + +publish :: CutDb -> CutHashes -> IO () +publish cdb ch = addCutHashes cdb ch + From 34695616e5b1e4923e0012add529d5d1135cf244 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:52 -0800 Subject: [PATCH 038/378] wip-4 (df170909d): src/Chainweb/Miner/Miners.hs --- src/Chainweb/Miner/Miners.hs | 50 ++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index 0b9b3de4ad..c631ad2bd2 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -64,14 +64,15 @@ import qualified Chainweb.Mempool.Mempool as Mempool import Chainweb.Miner.Config (MinerCount(..)) import Chainweb.Miner.Coordinator import Chainweb.Miner.Core -import Chainweb.Miner.Pact import Chainweb.RestAPI.Orphans () import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version -import Data.LogMessage (LogFunction) +import Data.LogMessage (LogFunction, LogFunctionText) +import System.LogLevel +import Control.Concurrent.STM -------------------------------------------------------------------------------- -- Local Mining @@ -85,16 +86,15 @@ localTest => Logger logger => LogFunction -> ChainwebVersion - -> MiningCoordination logger tbl - -> Miner - -> CutDb tbl + -> MiningCoordination logger + -> CutDb -> MWC.GenIO -> MinerCount -> IO () -localTest lf v coord m cdb gen miners = +localTest lf v coord cdb gen miners = runForever lf "Chainweb.Miner.Miners.localTest" $ do c <- _cut cdb - wh <- work coord Nothing m + wh <- work coord let height = c ^?! ixg (_workHeaderChainId wh) . blockHeight race (awaitNewCutByChainId cdb (_workHeaderChainId wh) c) (go height wh) >>= \case @@ -136,19 +136,37 @@ mempoolNoopMiner lf chainRes = -- localPOW :: Logger logger - => LogFunction - -> MiningCoordination logger tbl - -> Miner - -> CutDb tbl + => LogFunctionText + -> MiningCoordination logger + -> CutDb -> IO () -localPOW lf coord m cdb = runForever lf "Chainweb.Miner.Miners.localPOW" $ do +localPOW lf coord cdb = runForever lf "Chainweb.Miner.Miners.localPOW" $ do c <- _cut cdb - wh <- work coord Nothing m - race (awaitNewCutByChainId cdb (_workHeaderChainId wh) c) (go wh) >>= \case - Left _ -> return () + lf Debug "request new work for localPOW miner" + wh <- work coord + let cid = _workHeaderChainId wh + lf Debug $ "run localPOW miner on chain " <> toText cid + race (awaitNewCutByChainId cdb cid c) (go wh) >>= \case + Left _ -> do + lf Debug "abondond work due to chain update" + return () Right new -> do + lf Debug $ "solved work on chain " <> toText cid solve coord new - void $ awaitNewCut cdb c + + -- There is a potential race here, if the solved block got orphaned. + -- If work isn't updated quickly enough, it can happen that the + -- miner uses an old header. We resolve that by awaiting that the + -- chain is at least as high as the solved work. + -- This can still dead-lock if for some reason the solved work is + -- invalid. + awaitHeight (_chainId new) (view solvedWorkHeight new) where go :: WorkHeader -> IO SolvedWork go = mine @Blake2s_256 (Nonce 0) + + awaitHeight cid h = atomically $ do + c <- _cutStm cdb + let h' = view blockHeight $ c ^?! ixg cid + guard (h <= h') + From 17662865cf3c87b62b0e9522e206a16157946542 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:52 -0800 Subject: [PATCH 039/378] wip-3 (23d874999): src/Chainweb/Miner/Pact.hs --- src/Chainweb/Miner/Pact.hs | 44 +++++--------------------------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/src/Chainweb/Miner/Pact.hs b/src/Chainweb/Miner/Pact.hs index 99343addfd..878efade6d 100644 --- a/src/Chainweb/Miner/Pact.hs +++ b/src/Chainweb/Miner/Pact.hs @@ -14,19 +14,16 @@ -- Maintainer: Emily Pillmore -- Stability: experimental -- --- The definition of the Pact miner and the Pact miner reward. +-- The definition of the Pact miner. -- module Chainweb.Miner.Pact ( -- * Data MinerId(..) , MinerKeys(..) , Miner(..) -, MinerRewards(..) -- * Combinators , toMinerData , fromMinerData -, readRewards -, rawMinerRewards -- * Optics , minerId , minerKeys @@ -42,22 +39,12 @@ import Control.Lens hiding ((.=)) import Control.Monad.Catch (MonadThrow) import Data.Aeson hiding (decode) -import Data.ByteString (ByteString) -import qualified Data.ByteString.Lazy as BL -import qualified Data.Csv as CSV -import Data.Decimal (Decimal) -import Data.FileEmbed (embedFile) import Data.Hashable -import Data.Map.Strict (Map) -import qualified Data.Map.Strict as M import Data.String (IsString(..)) import Data.Text (Text) -import qualified Data.Vector as V -import Data.Word -- internal modules -import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.Payload import Chainweb.Utils @@ -84,7 +71,10 @@ newtype MinerKeys = MinerKeys Pact4.KeySet -- | Miner info data consists of a miner id (text), and its keyset (a pact -- type). -- -data Miner = Miner !MinerId !MinerKeys +data Miner = Miner + { _minerMinerId :: !MinerId + , _minerMinerKeys :: !MinerKeys + } deriving stock (Eq, Ord, Show, Generic) deriving anyclass (NFData) @@ -149,27 +139,3 @@ fromMinerData :: MonadThrow m => MinerData -> m Miner fromMinerData = decodeStrictOrThrow' . _minerData {-# INLINABLE fromMinerData #-} -newtype MinerRewards = MinerRewards - { _minerRewards :: Map BlockHeight Decimal - -- ^ The map of blockheight thresholds to miner rewards - } deriving (Eq, Ord, Show, Generic) - --- | Rewards table mapping 3-month periods to their rewards --- according to the calculated exponential decay over 120 year period --- -readRewards :: MinerRewards -readRewards = - case CSV.decode CSV.NoHeader (BL.fromStrict rawMinerRewards) of - Left e -> error - $ "cannot construct miner reward map: " - <> sshow e - Right vs -> MinerRewards $ M.fromList . V.toList . V.map formatRow $ vs - where - formatRow :: (Word64, CsvDecimal) -> (BlockHeight, Decimal) - formatRow (!a,!b) = (BlockHeight $ int a, _csvDecimal b) - --- | Read in the reward csv via TH for deployment purposes. --- -rawMinerRewards :: ByteString -rawMinerRewards = $(embedFile "rewards/miner_rewards.csv") -{-# NOINLINE rawMinerRewards #-} From 9b314ff16f0e46705926d2e88a02b873b58338d7 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:52 -0800 Subject: [PATCH 040/378] wip-4 (df170909d): src/Chainweb/Miner/RestAPI.hs --- src/Chainweb/Miner/RestAPI.hs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Chainweb/Miner/RestAPI.hs b/src/Chainweb/Miner/RestAPI.hs index 393f86c3b2..3aeb4fbccb 100644 --- a/src/Chainweb/Miner/RestAPI.hs +++ b/src/Chainweb/Miner/RestAPI.hs @@ -23,14 +23,11 @@ module Chainweb.Miner.RestAPI import Data.Proxy (Proxy(..)) -import Pact.Utils.Servant - import Servant.API -- internal modules import Chainweb.Miner.Core (ChainBytes, HeaderBytes, WorkBytes) -import Chainweb.Miner.Pact (Miner) import Chainweb.RestAPI.Utils (ChainwebEndpoint(..), Reassoc, SomeApi(..)) import Chainweb.Version @@ -44,17 +41,15 @@ import Chainweb.Version -- Chainweb Node for it to be reassociated with its `Chainweb.Cut.Cut` and -- Payload, then published to the rest of the network. -- -type MiningApi_ = - "mining" :> "work" - :> QueryParam "chain" ChainId - :> ReqBody '[PactJson] Miner - :> Get '[OctetStream] WorkBytes +type MiningApi_ + = "mining" :> "work" + :> Get '[OctetStream] WorkBytes :<|> "mining" :> "solved" - :> ReqBody '[OctetStream] HeaderBytes - :> Post '[JSON] NoContent + :> ReqBody '[OctetStream] HeaderBytes + :> Post '[JSON] NoContent :<|> "mining" :> "updates" - :> ReqBody '[OctetStream] ChainBytes - :> Raw + :> ReqBody '[OctetStream] ChainBytes + :> Raw type MiningApi (v :: ChainwebVersionT) = 'ChainwebEndpoint v :> Reassoc MiningApi_ From aa128a96860918cbbe61e79cf98fce2fdb251896 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:52 -0800 Subject: [PATCH 041/378] wip-4 (df170909d): src/Chainweb/Miner/RestAPI/Client.hs --- src/Chainweb/Miner/RestAPI/Client.hs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Miner/RestAPI/Client.hs b/src/Chainweb/Miner/RestAPI/Client.hs index 0bf8c6d0d6..a848c1cc24 100644 --- a/src/Chainweb/Miner/RestAPI/Client.hs +++ b/src/Chainweb/Miner/RestAPI/Client.hs @@ -23,16 +23,15 @@ import Servant.Client (ClientM, Response, client) -- internal modules import Chainweb.Miner.Core (ChainBytes, HeaderBytes, WorkBytes) -import Chainweb.Miner.Pact (Miner) import Chainweb.Miner.RestAPI (miningApi) import Chainweb.Version -- ----------------------------------------------------------------------------- -- Mining Results -workClient :: ChainwebVersion -> Maybe ChainId -> Miner -> ClientM WorkBytes -workClient v mcid m = case clients v of - f :<|> _ -> f mcid m +workClient :: ChainwebVersion -> ClientM WorkBytes +workClient v = case clients v of + f :<|> _ -> f solvedClient :: ChainwebVersion -> HeaderBytes -> ClientM NoContent solvedClient v hbytes = case clients v of @@ -40,7 +39,7 @@ solvedClient v hbytes = case clients v of clients :: ChainwebVersion - -> (Maybe ChainId -> Miner -> ClientM WorkBytes) + -> (ClientM WorkBytes) :<|> (HeaderBytes -> ClientM NoContent) :<|> (ChainBytes -> Method -> ClientM Response) clients (FromSingChainwebVersion (SChainwebVersion :: Sing v)) = client (miningApi @v) From 0485831ae8da4353fa22e0cec22bc5b11384dd41 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:52 -0800 Subject: [PATCH 042/378] wip-4 (df170909d): src/Chainweb/Miner/RestAPI/Server.hs --- src/Chainweb/Miner/RestAPI/Server.hs | 339 ++++++++++++--------------- 1 file changed, 152 insertions(+), 187 deletions(-) diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index 26d11ac250..05674afec5 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} @@ -24,26 +25,29 @@ module Chainweb.Miner.RestAPI.Server , someMiningServer ) where +import Chainweb.Cut.Create +import Chainweb.Logger +import Chainweb.Miner.Config +import Chainweb.Miner.Coordinator +import Chainweb.Miner.Core +import Chainweb.Miner.RestAPI (MiningApi) +import Chainweb.RestAPI.Utils +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Chainweb.Version + import Control.Concurrent.STM.TVar - (TVar, readTVar, readTVarIO, registerDelay) import Control.Lens -import Control.Monad (when, unless) -import Control.Monad.Catch (bracket, try, catches) -import qualified Control.Monad.Catch as E +import Control.Monad +import Control.Monad.Catch (try, catches) +import Control.Monad.Catch qualified as E import Control.Monad.Except (throwError) import Control.Monad.IO.Class (liftIO) import Control.Monad.STM import Data.Binary.Builder (fromByteString) -import qualified Data.HashMap.Strict as HM -import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef, writeIORef) -import qualified Data.Map.Strict as M import Data.Proxy (Proxy(..)) -import qualified Data.Set as S -import qualified Data.Vector as V -import Network.HTTP.Types.Status -import Network.Wai (responseLBS) import Network.Wai.EventSource (ServerEvent(..), eventSourceAppIO) import Servant.API @@ -52,52 +56,24 @@ import Servant.Server import System.LogLevel import System.Random --- internal modules - -import Chainweb.Cut.Create -import Chainweb.Logger -import Chainweb.Miner.Config -import Chainweb.Miner.Coordinator -import Chainweb.Miner.Core -import Chainweb.Miner.Pact -import Chainweb.Miner.RestAPI (MiningApi) -import Chainweb.Pact.Types(BlockInProgress(..), Transactions(..)) -import Chainweb.Payload -import Chainweb.RestAPI.Utils -import Chainweb.Utils -import Chainweb.Utils.Serialization -import Chainweb.Version -import Chainweb.WebPactExecutionService - -- -------------------------------------------------------------------------- -- -- Work Handler workHandler :: Logger l - => MiningCoordination l tbl - -> Maybe ChainId - -> Miner + => MiningCoordination l -> Handler WorkBytes -workHandler mr mcid m@(Miner (MinerId mid) _) = do - MiningState ms <- liftIO . readTVarIO $ _coordState mr - when (M.size ms > _coordLimit mr) $ do - liftIO $ atomicModifyIORef' (_coord503s mr) (\c -> (c + 1, ())) - throwError $ setErrText "Too many work requests" err503 - let !conf = _coordConf mr - !primed = S.member m $ _coordinationMiners conf - unless primed $ do - liftIO $ atomicModifyIORef' (_coord403s mr) (\c -> (c + 1, ())) - throwError $ setErrText ("Unauthorized Miner: " <> mid) err403 - wh <- liftIO $ work mr mcid m +workHandler mr = do + wh <- liftIO $ work mr return $ WorkBytes $ runPutS $ encodeWorkHeader wh -- -------------------------------------------------------------------------- -- -- Solved Handler solvedHandler - :: forall l tbl + :: forall l . Logger l - => MiningCoordination l tbl + => MiningCoordination l -> HeaderBytes -> Handler NoContent solvedHandler mr (HeaderBytes bytes) = do @@ -122,33 +98,122 @@ solvedHandler mr (HeaderBytes bytes) = do -- | Whether the work is outdated and should be thrown out immediately, -- or there's just fresher work available and the old work is still valid. -data WorkChange = WorkOutdated | WorkRefreshed | WorkRegressed +-- +-- NOTE: this does not provide any information about whether the chain is ready +-- or not. +-- +-- FIXME: +-- +-- Exposing this information to mining clients is problematic. It assumes that +-- clients are able to pick an optimal mining strategy (which should represent a +-- Nash equilibrium). For instance, if clients request new work each time any +-- event is received for a chain, Clients will spent less time mining chains +-- that receive many 'WorkRefreshed' events, because on each event a new chain +-- is selected uniformily. The currently used difficulty adjustement algorithm +-- assumes that all chains receive and equal amount of hash power. Hence, the +-- system will converge towards a state where chains that receive more hash +-- power are being blocked more often and miners are more likely the race on +-- chains that receive less hash power. As a consequence the number of orphans +-- is going to increase. +-- +-- While it is desirable to provide sufficient information to allow smart and +-- educated miners to pick their own chain selection strategy, the API should +-- ensure that uninformed miners are going to use a reasonable default strategy +-- that distributes hash power uniformily accross all chains. +-- +-- Unlike WorkOutdated events, the distribution of WorkRefreshed events is not +-- generated by the randomized mining process. Instead WorkRefreshed depends on +-- the rate at which transactions are sent to the mempool for the respective +-- chain. Beside of introducing random byzantine skew in the hash power +-- distribution and increasing orphan block counts, this condition can also be +-- exploited explicitely by a malicious actor. +-- +-- (Note that uniform distribution of hash power accross all chains is not the +-- optimal strategy in a collaborative model. In a collaborative model miners +-- could reduce the numbers of orphans be giving a slight preference to chains +-- with a lower block height in the current cut. However, to the best of our +-- knowledge that strategy does not represent an equilibrium in an adversarial +-- model. Uniform distribution is the best strategy that we are aware of for an +-- adversariable setting.) +-- +data WorkChange + = WorkOutdated + -- ^ The current work for the chain is invalid. + -- + -- The mining client should request new work. If the chain is currently + -- blocked it will be excluded in the selection of the new work. + -- + | WorkRefreshed + -- ^ The current work for the chain is still valid, but newer work is + -- available. + -- deriving stock (Show, Eq) +-- | This event triggers when the previous work got outdated +-- +awaitWorkChange + :: MiningState + -> ChainId + -> TVar Bool + -- ^ Timer + -> TVar WorkState + -- ^ Previous Work State + -> IO (Maybe WorkChange) +awaitWorkChange ms cid timer prevVar = go + where + go = do + -- await a change in the work state of the chain + r <- atomically $ readTVar timer >>= \case + True -> return Nothing + False -> do + prev <- readTVar prevVar + cur <- readTVar (ms ^?! ixg cid) + -- TODO: this guard is potentially somewhat expense. However, + -- ideally in most cases it should be possible to establish + -- equality by pointer equality. + guard (prev /= cur) + writeTVar prevVar cur + return $ Just (prev, cur) + + -- check result + case r of + Nothing -> return Nothing + Just (WorkReady prh ppld pps pwh, WorkReady crh cpld cps cwh) + | prh /= crh -> return $ Just WorkOutdated + | pps /= cps -> return $ Just WorkOutdated + | ppld /= cpld -> return $ Just WorkRefreshed + | pwh /= cwh -> return $ Just WorkRefreshed + | otherwise -> go + Just (WorkReady{}, _) -> return $ Just WorkOutdated + _ -> go + +-- | +-- +-- FIXME: the that current API design is broken in several ways. The main issues +-- are: +-- +-- 1. Lack of synchronization between the updates stream and the GET work API. +-- Events from the update stream race against work returned by GET work. +-- 2. It does not support mining clients in deploying a stable and optimal +-- mining strategy. Any use of the WorkRefreshed event requires non-trivial +-- client side logic to guarantee a uniform distribution of hash power +-- across chains. +-- A simple strategy for clients could be to ignore the WorkRefreshed events +-- and instead request new work at a fixed rate. +-- +-- FIXME: consider sending the WorkRefreshed Event at a fixed rate on each +-- chain. +-- +-- FIXME: update openAPI spec for mining API to reflect the "Refreshed Block" +-- event. +-- updatesHandler :: Logger l - => MiningCoordination l tbl + => MiningCoordination l -> ChainBytes -> Tagged Handler Application -updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> withLimit resp $ do - watchedChain <- runGetS decodeChainId cbytes - (watchedMiner, blockOnChain) <- atomically $ do - PrimedWork pw <- readTVar (_coordPrimedWork mr) - -- we check if `watchedMiner` has new work. we ignore - -- all other miner IDs, because we don't know which miner - -- is asking for updates, and if we watched all of them at once - -- we'd send too many messages. - -- this is deliberately partial, primed work will always have - -- at least one miner or that's an error - let (watchedMiner, minerBlocks) = HM.toList pw ^?! _head - -- and that miner will always have this chain(?) - -- if this chain doesn't exist yet just wait - blockOnChain <- do - case minerBlocks ^? ix watchedChain of - Just (WorkReady newBlock) -> return newBlock - _ -> retry - return (watchedMiner, blockOnChain) - blockOnChainRef <- newIORef (WorkReady blockOnChain) +updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> do + cid <- runGetS decodeChainId cbytes -- An update stream is closed after @timeout@ seconds. We add some jitter to -- availablility of streams is uniformily distributed over time and not @@ -157,141 +222,41 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> withLimit resp $ d jitter <- randomRIO @Double (0.9, 1.1) timer <- registerDelay (round $ jitter * realToFrac timeout * 1_000_000) - eventSourceAppIO (go timer watchedChain watchedMiner blockOnChainRef) req resp + curWork <- readTVarIO (_coordState mr ^?! ixg cid) + prevVar <- newTVarIO curWork + + eventSourceAppIO (go timer cid prevVar) req resp where timeout = _coordinationUpdateStreamTimeout $ _coordConf mr - -- | A nearly empty `ServerEvent` that signals the work on this chain has - -- changed, and how. - -- - eventForWorkChangeType :: WorkChange -> ServerEvent - eventForWorkChangeType WorkOutdated = ServerEvent (Just $ fromByteString "New Cut") Nothing [] - eventForWorkChangeType WorkRefreshed = ServerEvent (Just $ fromByteString "Refreshed Block") Nothing [] - -- this is only different from WorkRefreshed for logging - eventForWorkChangeType WorkRegressed = ServerEvent (Just $ fromByteString "Refreshed Block") Nothing [] - - go :: TVar Bool -> ChainId -> MinerId -> IORef WorkState -> IO ServerEvent - go timer watchedChain watchedMiner blockOnChainRef = do - lastBlockOnChain <- readIORef blockOnChainRef - - -- await either a timeout or a new event - maybeNewBlock <- atomically $ do - t <- readTVar timer - if t - then return Nothing - else Just <$> awaitNewPrimedWork watchedChain watchedMiner lastBlockOnChain - - case maybeNewBlock of - Nothing -> return CloseEvent - Just (workChange, currentBlockOnChain) -> do - writeIORef blockOnChainRef currentBlockOnChain + go :: TVar Bool -> ChainId -> TVar WorkState -> IO ServerEvent + go timer cid prevVar = do + awaitWorkChange (_coordState mr) cid timer prevVar >>= \case + Nothing -> do + logFunctionText logger Debug $ + "sent close event to miner on chain " <> toText cid + return CloseEvent + Just WorkOutdated -> do logFunctionText logger Debug $ - "sent update to miner on chain " <> toText watchedChain <> ": " <> sshow workChange - when (workChange == WorkRegressed) $ - logFunctionText logger Warn $ - "miner block regressed: " <> sshow currentBlockOnChain - return (eventForWorkChangeType workChange) + "sent work outdated event to miner on chain " <> toText cid + return $ ServerEvent (Just $ fromByteString "New Cut") Nothing [] + Just WorkRefreshed -> do + logFunctionText logger Debug $ + "sent work outdated event to miner on chain " <> toText cid + return $ ServerEvent (Just $ fromByteString "Refreshed Block") Nothing [] where - logger = addLabel ("chain", toText watchedChain) (_coordLogger mr) - - count = _coordUpdateStreamCount mr - - awaitNewPrimedWork watchedChain watchedMiner lastBlockOnChain = do - PrimedWork pw <- readTVar (_coordPrimedWork mr) - let currentBlockOnChain = pw ^?! ix watchedMiner . ix watchedChain - case (lastBlockOnChain, currentBlockOnChain) of - - -- there was no work, and that hasn't changed. - (WorkStale, WorkStale) -> retry - (WorkAlreadyMined _, WorkAlreadyMined _) -> retry - - (WorkReady (NewBlockInProgress (ForPact4 lastBip)), WorkReady (NewBlockInProgress (ForPact4 currentBip))) - | lastPh <- _blockInProgressParentHeader lastBip - , currentPh <- _blockInProgressParentHeader currentBip - , lastPh /= currentPh -> - -- we've got a new block on a new parent, we must've missed - -- the update where the old block became outdated. - -- miner should restart - return (WorkOutdated, currentBlockOnChain) - - | lastTlen <- V.length (_transactionPairs $ _blockInProgressTransactions lastBip) - , currentTlen <- V.length (_transactionPairs $ _blockInProgressTransactions currentBip) - , lastTlen /= currentTlen -> - if currentTlen < lastTlen - then - -- our refreshed block somehow has less transactions, - -- but the same parent header, log this as a bizarre case - return (WorkRegressed, currentBlockOnChain) - else - -- we've got a block that's been extended with new transactions - -- miner should restart - return (WorkRefreshed, currentBlockOnChain) - - -- no apparent change - | otherwise -> retry - - (WorkReady (NewBlockInProgress (ForPact5 lastBip)), WorkReady (NewBlockInProgress (ForPact5 currentBip))) - | lastPh <- _blockInProgressParentHeader lastBip - , currentPh <- _blockInProgressParentHeader currentBip - , lastPh /= currentPh -> - -- we've got a new block on a new parent, we must've missed - -- the update where the old block became outdated. - -- miner should restart - return (WorkOutdated, currentBlockOnChain) - - | lastTlen <- V.length (_transactionPairs $ _blockInProgressTransactions lastBip) - , currentTlen <- V.length (_transactionPairs $ _blockInProgressTransactions currentBip) - , lastTlen /= currentTlen -> - if currentTlen < lastTlen - then - -- our refreshed block somehow has less transactions, - -- but the same parent header, log this as a bizarre case - return (WorkRegressed, currentBlockOnChain) - else - -- we've got a block that's been extended with new transactions - -- miner should restart - return (WorkRefreshed, currentBlockOnChain) - - -- no apparent change - | otherwise -> retry - (WorkReady (NewBlockPayload lastPh lastPwo), WorkReady (NewBlockPayload currentPh currentPwo)) - | lastPh /= currentPh -> - -- we've got a new block on a new parent, we must've missed - -- the update where the old block became outdated. - -- miner should restart. - return (WorkOutdated, currentBlockOnChain) - - | _payloadWithOutputsPayloadHash lastPwo /= _payloadWithOutputsPayloadHash currentPwo -> - -- this should be impossible because NewBlockPayload is for - -- when Pact is off so blocks can't be refreshed, but we've got - -- a different block with the same parent, so the miner should restart. - return (WorkRefreshed, currentBlockOnChain) - - -- no apparent change - | otherwise -> retry - (WorkReady _, WorkReady _) -> - error "awaitNewPrimedWork: impossible: NewBlockInProgress replaced by a NewBlockPayload" - - _ -> return (WorkOutdated, currentBlockOnChain) - - withLimit resp inner = bracket - (atomicModifyIORef' count $ \x -> (x - 1, x - 1)) - (const $ atomicModifyIORef' count $ \x -> (x + 1, ())) - (\x -> if x <= 0 then ret503 resp else inner) - - ret503 resp = do - resp $ responseLBS status503 [] "No more update streams available currently. Retry later." + logger = addLabel ("chain", toText cid) (_coordLogger mr) -- -------------------------------------------------------------------------- -- -- Mining API Server miningServer - :: forall l tbl (v :: ChainwebVersionT) + :: forall l (v :: ChainwebVersionT) . Logger l - => MiningCoordination l tbl + => MiningCoordination l -> Server (MiningApi v) miningServer mr = workHandler mr :<|> solvedHandler mr :<|> updatesHandler mr -someMiningServer :: Logger l => ChainwebVersion -> MiningCoordination l tbl -> SomeServer +someMiningServer :: Logger l => ChainwebVersion -> MiningCoordination l -> SomeServer someMiningServer (FromSingChainwebVersion (SChainwebVersion :: Sing vT)) mr = SomeServer (Proxy @(MiningApi vT)) $ miningServer mr From 5ab0c476aca07623c80473f8f03c980f1f2d7a02 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:52 -0800 Subject: [PATCH 043/378] wip-3 (23d874999): src/Chainweb/Pact/PactService.hs --- src/Chainweb/Pact/PactService.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index b39fcc6bb8..2b5834cc39 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -180,13 +180,11 @@ withPactService -> IO (T2 a PactServiceState) withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config act = do Checkpointer.withCheckpointerResources checkpointerLogger (_pactModuleCacheLimit config) sqlenv (_pactPersistIntraBlockWrites config) ver cid $ \checkpointer -> do - let !rs = readRewards let !pse = PactServiceEnv { _psMempoolAccess = Nothing , _psCheckpointer = checkpointer , _psPdb = pdb , _psBlockHeaderDb = bhDb - , _psMinerRewards = rs , _psReorgLimit = _pactReorgLimit config , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout config , _psOnFatalError = defaultOnFatalError (logFunctionText chainwebLogger) @@ -198,6 +196,7 @@ withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config , _psEnableLocalTimeout = _pactEnableLocalTimeout config , _psTxFailuresCounter = txFailuresCounter , _psTxTimeLimit = _pactTxTimeLimit config + , _psMiner = _pactMiner config } !pst = PactServiceState mempty @@ -342,7 +341,7 @@ serviceRequests memPoolAccess reqQ = go trace logFn "Chainweb.Pact.PactService.execNewBlock" () 1 $ tryOne "execNewBlock" statusRef $ - execNewBlock memPoolAccess _newBlockMiner _newBlockFill _newBlockParent + execNewBlock memPoolAccess _newBlockFill _newBlockParent go ContinueBlockMsg (ContinueBlockReq bip) -> do trace logFn "Chainweb.Pact.PactService.execContinueBlock" @@ -482,11 +481,13 @@ serviceRequests memPoolAccess reqQ = go execNewBlock :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) => MemPoolAccess - -> Miner -> NewBlockFill -> ParentHeader -> PactServiceM logger tbl (Historical (ForSomePactVersion BlockInProgress)) -execNewBlock mpAccess miner fill newBlockParent = pactLabel "execNewBlock" $ do +execNewBlock mpAccess fill newBlockParent = pactLabel "execNewBlock" $ do + miner <- view psMiner >>= \case + Nothing -> internalError "Chainweb.Pact.PactService.execNewBlock: Mining is disabled. Please provide a valid miner in the pact service configuration" + Just x -> return x let pHeight = view blockHeight $ _parentHeader newBlockParent let pHash = view blockHash $ _parentHeader newBlockParent logInfoPact $ "(parent height = " <> sshow pHeight <> ")" From 6a1367e976f2ffba703a4e4ef084490e88fa09b3 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 30 Dec 2024 12:59:23 -0800 Subject: [PATCH 044/378] wip-4 (df170909d): src/Chainweb/Pact/RestAPI/Server.hs --- src/Chainweb/Pact/RestAPI/Server.hs | 51 ++++++++++++++--------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index 80f7ea94ea..93a0b0f8fa 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -143,10 +143,11 @@ import qualified Pact.Core.Gas as Pact5 -- -------------------------------------------------------------------------- -- data PactServerData logger tbl = PactServerData - { _pactServerDataCutDb :: !(CutDB.CutDb tbl) + { _pactServerDataCutDb :: !CutDB.CutDb , _pactServerDataMempool :: !(MempoolBackend Pact4.UnparsedTransaction) , _pactServerDataLogger :: !logger , _pactServerDataPact :: !PactExecutionService + , _pactServerDataPayloadDb :: !(PayloadDb tbl) } newtype PactServerData_ (v :: ChainwebVersionT) (c :: ChainIdT) logger tbl @@ -194,21 +195,21 @@ pactServer d = logger = _pactServerDataLogger d pact = _pactServerDataPact d cdb = _pactServerDataCutDb d + pdb = _pactServerDataPayloadDb d pactApiHandlers = sendHandler logger mempool - :<|> pollHandler logger cdb cid pact mempool - :<|> listenHandler logger cdb cid pact mempool + :<|> pollHandler logger cdb pdb cid pact mempool + :<|> listenHandler logger cdb pdb cid pact mempool :<|> localHandler logger pact - pactSpvHandler = spvHandler logger cdb cid - pactSpv2Handler = spv2Handler logger cdb cid + pactSpvHandler = spvHandler logger cdb pdb pact cid + pactSpv2Handler = spv2Handler logger cdb pdb pact cid somePactServer :: SomePactServerData -> SomeServer somePactServer (SomePactServerData (db :: PactServerData_ v c logger tbl)) = SomeServer (Proxy @(PactServiceApi v c)) (pactServer @v @c $ _unPactServerData db) - somePactServers :: CanReadablePayloadCas tbl => Logger logger @@ -300,18 +301,18 @@ sendHandler logger mempool (Pact4.SubmitBatch cmds) = Handler $ do pollHandler :: (HasCallStack, CanReadablePayloadCas tbl, Logger logger) => logger - -> CutDB.CutDb tbl + -> CutDB.CutDb + -> PayloadDb tbl -> ChainId -> PactExecutionService -> MempoolBackend Pact4.UnparsedTransaction -> Maybe ConfirmationDepth -> Pact5.PollRequest -> Handler Pact5.PollResponse -pollHandler logger cdb cid pact mem confDepth (Pact5.PollRequest request) = do +pollHandler logger cdb pdb cid pact mem confDepth (Pact5.PollRequest request) = do liftIO $! logg Info $ PactCmdLogPoll $ fmap Pact5.requestKeyToB64Text request Pact5.PollResponse <$!> liftIO (internalPoll logger pdb bdb mem pact confDepth request) where - pdb = view CutDB.cutDbPayloadDb cdb bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb logg = logFunctionJson (setComponent "poll-handler" logger) @@ -322,17 +323,17 @@ pollHandler logger cdb cid pact mem confDepth (Pact5.PollRequest request) = do listenHandler :: (CanReadablePayloadCas tbl, Logger logger) => logger - -> CutDB.CutDb tbl + -> CutDB.CutDb + -> PayloadDb tbl -> ChainId -> PactExecutionService -> MempoolBackend Pact4.UnparsedTransaction -> Pact5.ListenRequest -> Handler Pact5.ListenResponse -listenHandler logger cdb cid pact mem (Pact5.ListenRequest key) = do +listenHandler logger cdb pdb cid pact mem (Pact5.ListenRequest key) = do liftIO $ logg Info $ PactCmdLogListen $ Pact5.requestKeyToB64Text key liftIO (registerDelay defaultTimeout) >>= runListen where - pdb = view CutDB.cutDbPayloadDb cdb bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb logg = logFunctionJson (setComponent "listen-handler" logger) runListen :: TVar Bool -> Handler Pact5.ListenResponse @@ -426,12 +427,13 @@ localHandler logger pact preflight sigVerify rewindDepth cmd = do spvHandler :: forall tbl l - . ( Logger l - , CanReadablePayloadCas tbl - ) + . Logger l + => CanReadablePayloadCas tbl => l - -> CutDB.CutDb tbl + -> CutDB.CutDb -- ^ cut db + -> PayloadDb tbl + -> PactExecutionService -> ChainId -- ^ the chain id of the source chain id used in the -- execution of a cross-chain-transfer. @@ -441,7 +443,7 @@ spvHandler -- Also contains the request key of of the cross-chain transfer -- tx request. -> Handler TransactionOutputProofB64 -spvHandler l cdb cid (SpvRequest rk (Pact4.ChainId ptid)) = do +spvHandler l cdb pdb pe cid (SpvRequest rk (Pact4.ChainId ptid)) = do validateRequestKey rk liftIO $! logg (sshow ph) @@ -471,10 +473,8 @@ spvHandler l cdb cid (SpvRequest rk (Pact4.ChainId ptid)) = do return $! b64 p where - pe = _webPactExecutionService $ view CutDB.cutDbPactService cdb ph = Pact4.fromUntypedHash $ Pact4.unRequestKey rk bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb - pdb = view CutDB.cutDbPayloadDb cdb b64 = TransactionOutputProofB64 . encodeB64UrlNoPaddingText . BSL8.toStrict @@ -490,12 +490,13 @@ spvHandler l cdb cid (SpvRequest rk (Pact4.ChainId ptid)) = do spv2Handler :: forall tbl l - . ( Logger l - , CanReadablePayloadCas tbl - ) + . Logger l + => CanReadablePayloadCas tbl => l - -> CutDB.CutDb tbl + -> CutDB.CutDb -- ^ CutDb contains the cut, payload, and block db + -> PayloadDb tbl + -> PactExecutionService -> ChainId -- ^ ChainId of the target -> Spv2Request @@ -504,7 +505,7 @@ spv2Handler -- Also contains the request key of of the cross-chain transfer -- tx request. -> Handler SomePayloadProof -spv2Handler l cdb cid r = case _spvSubjectIdType sid of +spv2Handler l cdb pdb pe cid r = case _spvSubjectIdType sid of SpvSubjectResult | _spv2ReqAlgorithm r /= SpvSHA512t_256 -> toErr $ "Algorithm " <> sshow r <> " is not supported with SPV result proofs." @@ -541,10 +542,8 @@ spv2Handler l cdb cid r = case _spvSubjectIdType sid of sid = _spv2ReqSubjectIdentifier r rk = _spvSubjectIdReqKey sid - pe = _webPactExecutionService $ view CutDB.cutDbPactService cdb ph = Pact4.unRequestKey rk bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb - pdb = view CutDB.cutDbPayloadDb cdb logg = logFunctionJson (setComponent "spv-handler" l) Info . PactCmdLogSpv From 8f5958f71424a3e155c2add611bd23a6f006159d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:52 -0800 Subject: [PATCH 045/378] wip-3 (23d874999): src/Chainweb/Pact/Service/BlockValidation.hs --- src/Chainweb/Pact/Service/BlockValidation.hs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Pact/Service/BlockValidation.hs b/src/Chainweb/Pact/Service/BlockValidation.hs index 9137e762f1..8f548c48b8 100644 --- a/src/Chainweb/Pact/Service/BlockValidation.hs +++ b/src/Chainweb/Pact/Service/BlockValidation.hs @@ -37,7 +37,6 @@ import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Mempool.Mempool (InsertError) -import Chainweb.Miner.Pact import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Payload @@ -52,12 +51,11 @@ import qualified Pact.Types.ChainMeta as Pact4 import Data.Text (Text) import qualified Pact.Types.Command as Pact4 -newBlock :: Miner -> NewBlockFill -> ParentHeader -> PactQueue -> IO (Historical (ForSomePactVersion BlockInProgress)) -newBlock mi fill parent reqQ = do +newBlock :: NewBlockFill -> ParentHeader -> PactQueue -> IO (Historical (ForSomePactVersion BlockInProgress)) +newBlock fill parent reqQ = do let !msg = NewBlockMsg NewBlockReq - { _newBlockMiner = mi - , _newBlockFill = fill + { _newBlockFill = fill , _newBlockParent = parent } submitRequestAndWait reqQ msg From e7ebff3822ae5bea94399bb1d6f34d746b426e81 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:52 -0800 Subject: [PATCH 046/378] wip-3 (23d874999): src/Chainweb/Pact/Types.hs --- src/Chainweb/Pact/Types.hs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index c7c4d4e589..0a4139f128 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -43,7 +43,6 @@ module Chainweb.Pact.Types , psCheckpointer , psPdb , psBlockHeaderDb - , psMinerRewards , psReorgLimit , psPreInsertCheckTimeout , psOnFatalError @@ -55,6 +54,7 @@ module Chainweb.Pact.Types , psEnableLocalTimeout , psTxFailuresCounter , psTxTimeLimit + , psMiner -- -- * Pact Service State , PactServiceState(..) @@ -493,7 +493,6 @@ data PactServiceEnv logger tbl = PactServiceEnv , _psCheckpointer :: !(Checkpointer logger) , _psPdb :: !(PayloadDb tbl) , _psBlockHeaderDb :: !BlockHeaderDb - , _psMinerRewards :: !MinerRewards , _psPreInsertCheckTimeout :: !Micros -- ^ Maximum allowed execution time for the transactions validation. , _psReorgLimit :: !RewindLimit @@ -509,6 +508,7 @@ data PactServiceEnv logger tbl = PactServiceEnv , _psEnableLocalTimeout :: !Bool , _psTxFailuresCounter :: !(Maybe (Counter "txFailures")) , _psTxTimeLimit :: !(Maybe Micros) + , _psMiner :: !(Maybe Miner) } makeLenses ''PactServiceEnv @@ -567,6 +567,9 @@ data PactServiceConfig = PactServiceConfig -- ^ *Only affects Pact5* -- Maximum allowed execution time for a single transaction. -- If 'Nothing', it's a function of the BlockGasLimit. + -- + -- FIXME: this seems dangerous. It could fork the chain! + , _pactMiner :: !(Maybe Miner) } deriving (Eq,Show) @@ -586,6 +589,7 @@ testPactServiceConfig = PactServiceConfig , _pactEnableLocalTimeout = False , _pactPersistIntraBlockWrites = DoNotPersistIntraBlockWrites , _pactTxTimeLimit = Nothing + , _pactMiner = Nothing } -- | This default value is only relevant for testing. In a chainweb-node the @GasLimit@ @@ -1032,8 +1036,7 @@ instance Show (RequestMsg r) where data NewBlockReq = NewBlockReq - { _newBlockMiner :: !Miner - , _newBlockFill :: !NewBlockFill + { _newBlockFill :: !NewBlockFill -- ^ whether to fill this block with transactions; if false, the block -- will be empty. , _newBlockParent :: !ParentHeader From e7a4131d1bf6bb63aa68b36c4eafb7ae9e854453 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 30 Dec 2024 12:59:23 -0800 Subject: [PATCH 047/378] wip-4 (df170909d): src/Chainweb/RestAPI.hs --- src/Chainweb/RestAPI.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index bb2aa56518..37f4ab3722 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -146,7 +146,7 @@ serveSocketTls settings certChain key = runTLSSocket tlsSettings settings -- functions that run a chainweb server. -- data ChainwebServerDbs t tbl = ChainwebServerDbs - { _chainwebServerCutDb :: !(Maybe (CutDb tbl)) + { _chainwebServerCutDb :: !(Maybe CutDb) , _chainwebServerBlockHeaderDbs :: ![(ChainId, BlockHeaderDb)] , _chainwebServerMempools :: ![(ChainId, MempoolBackend t)] , _chainwebServerPayloadDbs :: ![(ChainId, PayloadDb tbl)] @@ -362,7 +362,7 @@ someServiceApiServer => ChainwebVersion -> ChainwebServerDbs t tbl -> [(ChainId, PactAPI.PactServerData logger tbl)] - -> Maybe (MiningCoordination logger tbl) + -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) -> PayloadBatchLimit @@ -392,7 +392,7 @@ serviceApiApplication => ChainwebVersion -> ChainwebServerDbs t tbl -> [(ChainId, PactAPI.PactServerData logger tbl)] - -> Maybe (MiningCoordination logger tbl) + -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) -> PayloadBatchLimit @@ -411,7 +411,7 @@ serveServiceApiSocket -> ChainwebVersion -> ChainwebServerDbs t tbl -> [(ChainId, PactAPI.PactServerData logger tbl)] - -> Maybe (MiningCoordination logger tbl) + -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) -> PayloadBatchLimit From 65c5e470f0d0d67b6d447a6e45d815335a2fa96b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Jan 2025 11:57:39 -0800 Subject: [PATCH 048/378] wip-5 (5c71825e1): src/Chainweb/RestAPI.hs --- src/Chainweb/RestAPI.hs | 73 ++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index 37f4ab3722..1f30deee36 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -95,7 +95,6 @@ import qualified Chainweb.Miner.RestAPI.Server as Mining import qualified Chainweb.Pact.RestAPI.Server as PactAPI import Chainweb.Payload.PayloadStore import Chainweb.Payload.RestAPI -import Chainweb.Payload.RestAPI.Server import Chainweb.RestAPI.Backup import Chainweb.RestAPI.Config import Chainweb.RestAPI.Health @@ -145,21 +144,21 @@ serveSocketTls settings certChain key = runTLSSocket tlsSettings settings -- | Datatype for collectively passing all storage backends to -- functions that run a chainweb server. -- -data ChainwebServerDbs t tbl = ChainwebServerDbs +data ChainwebServerDbs t = ChainwebServerDbs { _chainwebServerCutDb :: !(Maybe CutDb) , _chainwebServerBlockHeaderDbs :: ![(ChainId, BlockHeaderDb)] , _chainwebServerMempools :: ![(ChainId, MempoolBackend t)] - , _chainwebServerPayloadDbs :: ![(ChainId, PayloadDb tbl)] + , _chainwebServerPayloads :: ![(ChainId, SomeServer)] , _chainwebServerPeerDbs :: ![(NetworkId, PeerDb)] } deriving (Generic) -emptyChainwebServerDbs :: ChainwebServerDbs t tbl +emptyChainwebServerDbs :: ChainwebServerDbs t emptyChainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Nothing , _chainwebServerBlockHeaderDbs = [] , _chainwebServerMempools = [] - , _chainwebServerPayloadDbs = [] + , _chainwebServerPayloads = [] , _chainwebServerPeerDbs = [] } @@ -211,19 +210,18 @@ chainwebServiceMiddlewares someChainwebServer :: Show t - => CanReadablePayloadCas tbl => ChainwebConfiguration - -> ChainwebServerDbs t tbl + -> ChainwebServerDbs t -> SomeServer someChainwebServer config dbs = maybe mempty (someCutServer v cutPeerDb) cuts - <> somePayloadServers v p2pPayloadBatchLimit payloads + <> mconcat (snd <$> payloads) <> someP2pBlockHeaderDbServers v blocks <> Mempool.someMempoolServers v mempools <> someP2pServers v peers <> someGetConfigServer config where - payloads = _chainwebServerPayloadDbs dbs + payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs cuts = _chainwebServerCutDb dbs peers = _chainwebServerPeerDbs dbs @@ -237,20 +235,19 @@ someChainwebServer config dbs = -- someChainwebServerWithHashesAndSpvApi :: Show t - => CanReadablePayloadCas tbl => ChainwebConfiguration - -> ChainwebServerDbs t tbl + -> ChainwebServerDbs t -> SomeServer someChainwebServerWithHashesAndSpvApi config dbs = maybe mempty (someCutServer v cutPeerDb) cuts - <> somePayloadServers v p2pPayloadBatchLimit payloads - <> someBlockHeaderDbServers v blocks payloads + <> mconcat (snd <$> payloads) + <> someBlockHeaderDbServers v blocks <> Mempool.someMempoolServers v mempools <> someP2pServers v peers <> someGetConfigServer config <> maybe mempty (someSpvServers v) cuts where - payloads = _chainwebServerPayloadDbs dbs + payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs cuts = _chainwebServerCutDb dbs peers = _chainwebServerPeerDbs dbs @@ -263,9 +260,8 @@ someChainwebServerWithHashesAndSpvApi config dbs = chainwebApplication :: Show t - => CanReadablePayloadCas tbl => ChainwebConfiguration - -> ChainwebServerDbs t tbl + -> ChainwebServerDbs t -> Application chainwebApplication config dbs = chainwebP2pMiddlewares @@ -278,9 +274,8 @@ chainwebApplication config dbs -- chainwebApplicationWithHashesAndSpvApi :: Show t - => CanReadablePayloadCas tbl => ChainwebConfiguration - -> ChainwebServerDbs t tbl + -> ChainwebServerDbs t -> Application chainwebApplicationWithHashesAndSpvApi config dbs = chainwebP2pMiddlewares @@ -289,29 +284,26 @@ chainwebApplicationWithHashesAndSpvApi config dbs serveChainwebOnPort :: Show t - => CanReadablePayloadCas tbl => Port -> ChainwebConfiguration - -> ChainwebServerDbs t tbl + -> ChainwebServerDbs t -> IO () serveChainwebOnPort p c dbs = run (int p) $ chainwebApplication c dbs serveChainweb :: Show t - => CanReadablePayloadCas tbl => Settings -> ChainwebConfiguration - -> ChainwebServerDbs t tbl + -> ChainwebServerDbs t -> IO () serveChainweb s c dbs = runSettings s $ chainwebApplication c dbs serveChainwebSocket :: Show t - => CanReadablePayloadCas tbl => Settings -> Socket -> ChainwebConfiguration - -> ChainwebServerDbs t tbl + -> ChainwebServerDbs t -> Middleware -> IO () serveChainwebSocket settings sock c dbs m = @@ -319,13 +311,12 @@ serveChainwebSocket settings sock c dbs m = serveChainwebSocketTls :: Show t - => CanReadablePayloadCas tbl => Settings -> X509CertChainPem -> X509KeyPem -> Socket -> ChainwebConfiguration - -> ChainwebServerDbs t tbl + -> ChainwebServerDbs t -> Middleware -> IO () serveChainwebSocketTls settings certChain key sock c dbs m = @@ -357,65 +348,59 @@ servePeerDbSocketTls settings certChain key sock v nid pdb m = someServiceApiServer :: Show t - => CanReadablePayloadCas tbl => Logger logger => ChainwebVersion - -> ChainwebServerDbs t tbl - -> [(ChainId, PactAPI.PactServerData logger tbl)] + -> ChainwebServerDbs t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) -> PayloadBatchLimit -> SomeServer -someServiceApiServer v dbs pacts mr (HeaderStream hs) backupEnv pbl = +someServiceApiServer v dbs mr (HeaderStream hs) backupEnv pbl = someHealthCheckServer <> maybe mempty (someBackupServer v) backupEnv <> maybe mempty (someNodeInfoServer v) cuts - <> PactAPI.somePactServers v pacts + <> mempty -- PactAPI.somePactServers v <> maybe mempty (Mining.someMiningServer v) mr -- <> maybe mempty (someSpvServers v) cuts -- AFAIK currently not used -- GET Cut, Payload, and Headers endpoints <> maybe mempty (someCutGetServer v) cuts - <> somePayloadServers v pbl payloads - <> someBlockHeaderDbServers v blocks payloads -- TODO make max limits configurable + <> mconcat (snd <$> payloads) + <> someBlockHeaderDbServers v blocks <> maybe mempty (someBlockStreamServer v) (bool Nothing cuts hs) where cuts = _chainwebServerCutDb dbs - payloads = _chainwebServerPayloadDbs dbs + payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs serviceApiApplication :: Show t - => CanReadablePayloadCas tbl => Logger logger => ChainwebVersion - -> ChainwebServerDbs t tbl - -> [(ChainId, PactAPI.PactServerData logger tbl)] + -> ChainwebServerDbs t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) -> PayloadBatchLimit -> Application -serviceApiApplication v dbs pacts mr hs be pbl +serviceApiApplication v dbs mr hs be pbl = chainwebServiceMiddlewares . someServerApplication - $ someServiceApiServer v dbs pacts mr hs be pbl + $ someServiceApiServer v dbs mr hs be pbl serveServiceApiSocket :: Show t - => CanReadablePayloadCas tbl => Logger logger => Settings -> Socket -> ChainwebVersion - -> ChainwebServerDbs t tbl - -> [(ChainId, PactAPI.PactServerData logger tbl)] + -> ChainwebServerDbs t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) -> PayloadBatchLimit -> Middleware -> IO () -serveServiceApiSocket s sock v dbs pacts mr hs be pbl m = - runSettingsSocket s sock $ m $ serviceApiApplication v dbs pacts mr hs be pbl +serveServiceApiSocket s sock v dbs mr hs be pbl m = + runSettingsSocket s sock $ m $ serviceApiApplication v dbs mr hs be pbl From 5da550b89a4eb5dc680e4302e795e6f40f962ba8 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 13 Jan 2025 23:46:53 -0800 Subject: [PATCH 049/378] wip-6 (1d630fd6d): src/Chainweb/RestAPI.hs --- src/Chainweb/RestAPI.hs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index 1f30deee36..5cd65d51b1 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -49,14 +49,6 @@ module Chainweb.RestAPI , someServiceApiServer , serviceApiApplication , serveServiceApiSocket - --- * Chainweb API Client - --- ** BlockHeaderDb API Client -, module Chainweb.BlockHeaderDB.RestAPI.Client - --- ** P2P API Client -, module P2P.Node.RestAPI.Client ) where import Control.Monad (guard) @@ -80,11 +72,10 @@ import System.Clock import Chainweb.Backup import Chainweb.BlockHeaderDB -import Chainweb.BlockHeaderDB.RestAPI.Client import Chainweb.BlockHeaderDB.RestAPI.Server import Chainweb.ChainId import Chainweb.Chainweb.Configuration -import Chainweb.Chainweb.MinerResources (MiningCoordination) +import Chainweb.Miner.Coordinator (MiningCoordination) import Chainweb.CutDB import Chainweb.CutDB.RestAPI.Server import Chainweb.HostAddress @@ -92,8 +83,7 @@ import Chainweb.Logger (Logger) import Chainweb.Mempool.Mempool (MempoolBackend) import qualified Chainweb.Mempool.RestAPI.Server as Mempool import qualified Chainweb.Miner.RestAPI.Server as Mining -import qualified Chainweb.Pact.RestAPI.Server as PactAPI -import Chainweb.Payload.PayloadStore +-- import qualified Chainweb.Pact.RestAPI.Server as PactAPI import Chainweb.Payload.RestAPI import Chainweb.RestAPI.Backup import Chainweb.RestAPI.Config @@ -108,7 +98,6 @@ import Chainweb.Version import Network.X509.SelfSigned import P2P.Node.PeerDB -import P2P.Node.RestAPI.Client import P2P.Node.RestAPI.Server -- -------------------------------------------------------------------------- -- From f23052fef16fe3d64ef24969757401ddcf17914d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:53 -0800 Subject: [PATCH 050/378] wip-3 (23d874999): src/Chainweb/RestAPI/Config.hs --- src/Chainweb/RestAPI/Config.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Chainweb/RestAPI/Config.hs b/src/Chainweb/RestAPI/Config.hs index d150c5daea..457be75065 100644 --- a/src/Chainweb/RestAPI/Config.hs +++ b/src/Chainweb/RestAPI/Config.hs @@ -54,8 +54,7 @@ someGetConfigServer config = SomeServer (Proxy @GetConfigApi) $ return $ set (configP2p . p2pConfigPeer . peerConfigKeyFile) Nothing -- Miner Info - $ set (configMining . miningCoordination . coordinationMiners) mempty - $ set (configMining . miningInNode . nodeMiner) invalidMiner + $ set (configMining . miningMiner) invalidMiner -- Service API port $ set (configServiceApi . serviceApiConfigPort) 0 From 7951c5bb8c93bd90d6e2408faaf3a6c483d415e0 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:53 -0800 Subject: [PATCH 051/378] wip-3 (23d874999): src/Chainweb/RestAPI/NodeInfo.hs --- src/Chainweb/RestAPI/NodeInfo.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/RestAPI/NodeInfo.hs b/src/Chainweb/RestAPI/NodeInfo.hs index 60de9b0aa8..f7d6e76403 100644 --- a/src/Chainweb/RestAPI/NodeInfo.hs +++ b/src/Chainweb/RestAPI/NodeInfo.hs @@ -45,7 +45,7 @@ type NodeInfoApi = "info" :> Get '[JSON] NodeInfo someNodeInfoApi :: SomeApi someNodeInfoApi = SomeApi (Proxy @NodeInfoApi) -someNodeInfoServer :: ChainwebVersion -> CutDb tbl -> SomeServer +someNodeInfoServer :: ChainwebVersion -> CutDb -> SomeServer someNodeInfoServer v c = SomeServer (Proxy @NodeInfoApi) (nodeInfoHandler v $ someCutDbVal v c) @@ -78,8 +78,8 @@ data NodeInfo = NodeInfo deriving (Show, Eq, Generic) deriving anyclass (ToJSON, FromJSON) -nodeInfoHandler :: ChainwebVersion -> SomeCutDb tbl -> Server NodeInfoApi -nodeInfoHandler v (SomeCutDb (CutDbT db :: CutDbT cas v)) = do +nodeInfoHandler :: ChainwebVersion -> SomeCutDb -> Server NodeInfoApi +nodeInfoHandler v (SomeCutDb (CutDbT db :: CutDbT v)) = do curCut <- liftIO $ _cut db let ch = cutToCutHashes Nothing curCut let curHeight = maximum $ map _bhwhHeight $ HM.elems $ _cutHashes ch From 2682c40c2ac9c8f3f198ec11c6f34c4948b98cc1 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:53 -0800 Subject: [PATCH 052/378] wip-4 (df170909d): src/Chainweb/SPV/CreateProof.hs --- src/Chainweb/SPV/CreateProof.hs | 232 +++++++++++++++++++------------- 1 file changed, 142 insertions(+), 90 deletions(-) diff --git a/src/Chainweb/SPV/CreateProof.hs b/src/Chainweb/SPV/CreateProof.hs index b954fdf633..182723d67c 100644 --- a/src/Chainweb/SPV/CreateProof.hs +++ b/src/Chainweb/SPV/CreateProof.hs @@ -18,7 +18,7 @@ module Chainweb.SPV.CreateProof ( createTransactionProof , createTransactionProof_ -, createTransactionProof' +, createTransactionProofMax , createTransactionOutputProof , createTransactionOutputProof_ , createTransactionOutputProof' @@ -57,9 +57,53 @@ import Chainweb.TreeDB import Chainweb.Utils import Chainweb.Version import Chainweb.WebBlockHeaderDB +import Chainweb.PayloadProvider import Chainweb.Storage.Table +-- -------------------------------------------------------------------------- -- +-- FIXME +-- +-- With the introduction of non-uniform payload providers the architecture for +-- proof creation and verification must change. +-- +-- CutDb will not include access to payloads. The logic for creating payload +-- proofs will be implemented in the payload provider. +-- +-- +-- We have two options: +-- +-- 1. Make proof creation a consensus API. +-- +-- Pros: Consensus calls into the payload provider. The payload provider does +-- not need to initiate calls to the consensus API. +-- +-- Cons: The frontend API for proof creation is current exposed in the +-- payload provider API. The mapping from consensus to providers is on to +-- many and a consensus component is not generally aware of all payload +-- providers. This requires handling the case when a proof is requested for +-- an unsupported chain. +-- +-- 2. Keep proof creation as payload provider API. +-- +-- Pros: The API remains where it currently lives. Users don't need to +-- communicate directly with the consensus API. The is a consensus component +-- for each payload provider. Hence, requests can always be supported. +-- +-- Cons: Payload providers need to call into the consensus API. For that +-- providers need to be aware of the conensus component. +-- +-- The second solution seems more natural, but that relies on the fact that it +-- would generally seem more natural if payload providers depended/used +-- consensus. However, this would require that consensus could accept blocks +-- without validating the payload, which would require that either payload +-- validation is not relevant for consensus or block producers provided succinct +-- proofs for validation. Since this is not the case this argument becomes +-- irrelevant. +-- +-- The first solution is in alignment with the overall architecture of the +-- conensus protocol. + -- -------------------------------------------------------------------------- -- -- Create Transaction Proof @@ -73,8 +117,7 @@ import Chainweb.Storage.Table -- createTransactionProof :: HasCallStack - => CanReadablePayloadCas tbl - => CutDb tbl + => CutDb -- ^ Block Header Database -> ChainId -- ^ target chain. The proof asserts that the subject is included in @@ -86,18 +129,19 @@ createTransactionProof -> Int -- ^ The index of the transaction in the block -> IO (TransactionProof SHA512t_256) -createTransactionProof cutDb = +createTransactionProof cutDb tcid scid = createTransactionProof_ (view cutDbWebBlockHeaderDb cutDb) - (view cutDbPayloadDb cutDb) + (view cutDbPayloadProviders cutDb ^?! ixg scid) + tcid + scid -- | Version without CutDb dependency -- createTransactionProof_ :: HasCallStack - => CanReadablePayloadCas tbl => WebBlockHeaderDb - -> PayloadDb tbl + -> SomePayloadProvider -> ChainId -- ^ target chain. The proof asserts that the subject is included in -- this chain @@ -108,21 +152,21 @@ createTransactionProof_ -> Int -- ^ The index of the transaction in the block -> IO (TransactionProof SHA512t_256) -createTransactionProof_ headerDb payloadDb tcid scid bh i = do +createTransactionProof_ headerDb provider tcid scid bh i = do trgHeader <- minimumTrgHeader headerDb tcid scid bh TransactionProof tcid - <$> createPayloadProof_ transactionProofPrefix headerDb payloadDb tcid scid bh i trgHeader + <$> createPayloadProof_ transactionProofPrefix headerDb provider tcid scid bh i trgHeader -- | Creates a witness that a transaction is included in a chain of a chainweb. -- --- The target header is the current maximum block header in the target chain. --- Note, that this header may not yet be confirmed and may thus be volatile. +-- NOTE: The target header is the current maximum block header in the target +-- chain. Note, that this header may not yet be confirmed and may thus be +-- volatile. -- -createTransactionProof' +createTransactionProofMax :: HasCallStack - => CanReadablePayloadCas tbl - => CutDb tbl + => CutDb -- ^ Block Header Database -> ChainId -- ^ target chain. The proof asserts that the subject is included in @@ -134,35 +178,43 @@ createTransactionProof' -> Int -- ^ The index of the transaction in the block -> IO (TransactionProof SHA512t_256) -createTransactionProof' cutDb tcid scid bh i = TransactionProof tcid +createTransactionProofMax cutDb tcid scid bh i = TransactionProof tcid <$> createPayloadProof transactionProofPrefix cutDb tcid scid bh i transactionProofPrefix - :: CanReadablePayloadCas tbl - => Int + :: Int -> BlockHeight - -> PayloadDb tbl - -> BlockPayload + -> SomePayloadProvider + -> BlockPayloadHash -> IO PayloadProofPrefix -transactionProofPrefix i bh db payload = do - -- 1. TX proof - let - lookupOld = tableLookup - (_oldTransactionDbBlockTransactionsTbl $ _transactionDb db) - (_blockPayloadTransactionsHash payload) - lookupNew = tableLookup - (_newTransactionDbBlockTransactionsTbl $ _transactionDb db) - (bh, _blockPayloadTransactionsHash payload) - Just txs <- runMaybeT $ MaybeT lookupNew <|> MaybeT lookupOld - -- TODO: use the transaction tree cache - let (!subj, pos, t) = bodyTree @_ @ChainwebHashTag txs i - -- FIXME use log - let !tree = (pos, t) - -- we blindly trust the ix - - -- 2. Payload proof - let !proof = tree N.:| [headerTree_ @BlockTransactionsHash payload] - return (subj, proof) +transactionProofPrefix i bh provider ph = + error "Chainweb.SPV.CreateProof.transactionProofPrefix: FIXME: not yet implemented" +-- -- 1. TX proof +-- let +-- lookupOld = tableLookup +-- (_oldTransactionDbBlockTransactionsTbl $ _transactionDb provider) +-- (_blockPayloadTransactionsHash payload) +-- lookupNew = tableLookup +-- (_newTransactionDbBlockTransactionsTbl $ _transactionDb provider) +-- (bh, _blockPayloadTransactionsHash payload) +-- Just txs <- runMaybeT $ MaybeT lookupNew <|> MaybeT lookupOld +-- -- TODO: use the transaction tree cache +-- let (!subj, pos, t) = bodyTree @_ @ChainwebHashTag txs i +-- -- FIXME use log +-- let !tree = (pos, t) +-- -- we blindly trust the ix +-- +-- -- 2. Payload proof +-- let !proof = tree N.:| [headerTree_ @BlockTransactionsHash payload] +-- return (subj, proof) +-- +-- payload = do +-- Just pd <- lookupPayloadDataWithHeight payloadDb (Just $ view blockHeight txHeader) (view blockPayloadHash txHeader) +-- return $ BlockPayload +-- { _blockPayloadTransactionsHash = view payloadDataTransactionsHash pd +-- , _blockPayloadOutputsHash = view payloadDataOutputsHash pd +-- , _blockPayloadPayloadHash = view payloadDataPayloadHash pd +-- } -- -------------------------------------------------------------------------- -- -- Creates Output Proof @@ -177,8 +229,7 @@ transactionProofPrefix i bh db payload = do -- createTransactionOutputProof :: HasCallStack - => CanReadablePayloadCas tbl - => CutDb tbl + => CutDb -- ^ Block Header Database -> ChainId -- ^ target chain. The proof asserts that the subject is included in @@ -190,19 +241,20 @@ createTransactionOutputProof -> Int -- ^ The index of the transaction in the block -> IO (TransactionOutputProof SHA512t_256) -createTransactionOutputProof cutDb = +createTransactionOutputProof cutDb tcid scid = createTransactionOutputProof_ (view cutDbWebBlockHeaderDb cutDb) - (view cutDbPayloadDb cutDb) + (view cutDbPayloadProviders cutDb ^?! ixg scid) + tcid + scid -- | Version without CutDb dependency -- createTransactionOutputProof_ :: HasCallStack - => CanReadablePayloadCas tbl => WebBlockHeaderDb - -> PayloadDb tbl + -> SomePayloadProvider -- ^ Block Header Database -> ChainId -- ^ target chain. The proof asserts that the subject is included in @@ -214,10 +266,10 @@ createTransactionOutputProof_ -> Int -- ^ The index of the transaction in the block -> IO (TransactionOutputProof SHA512t_256) -createTransactionOutputProof_ headerDb payloadDb tcid scid bh i = do +createTransactionOutputProof_ headerDb provider tcid scid bh i = do trgHeader <- minimumTrgHeader headerDb tcid scid bh TransactionOutputProof tcid - <$> createPayloadProof_ outputProofPrefix headerDb payloadDb tcid scid bh i trgHeader + <$> createPayloadProof_ outputProofPrefix headerDb provider tcid scid bh i trgHeader -- | Creates a witness that a transaction is included in a chain of a chainweb. @@ -227,8 +279,7 @@ createTransactionOutputProof_ headerDb payloadDb tcid scid bh i = do -- createTransactionOutputProof' :: HasCallStack - => CanReadablePayloadCas tbl - => CutDb tbl + => CutDb -- ^ Block Header Database -> ChainId -- ^ target chain. The proof asserts that the subject is included in @@ -245,34 +296,42 @@ createTransactionOutputProof' cutDb tcid scid bh i <$> createPayloadProof outputProofPrefix cutDb tcid scid bh i outputProofPrefix - :: CanReadablePayloadCas tbl - => Int + :: Int -- ^ transaction index -> BlockHeight - -> PayloadDb tbl - -> BlockPayload + -> SomePayloadProvider + -> BlockPayloadHash -> IO PayloadProofPrefix -outputProofPrefix i bh db payload = do - -- 1. TX proof - let - lookupOld = tableLookup - (_oldBlockOutputsTbl blockOutputs) - (_blockPayloadOutputsHash payload) - lookupNew = tableLookup - (_newBlockOutputsTbl blockOutputs) - (bh, _blockPayloadOutputsHash payload) - Just outs <- runMaybeT $ MaybeT lookupNew <|> MaybeT lookupOld - -- TODO: use the transaction tree cache - let (!subj, pos, t) = bodyTree @_ @ChainwebHashTag outs i - -- FIXME use log - let tree = (pos, t) - -- we blindly trust the ix - - -- 2. Payload proof - let !proof = tree N.:| [headerTree_ @BlockOutputsHash payload] - return (subj, proof) - where - blockOutputs = _payloadCacheBlockOutputs $ _payloadCache db +outputProofPrefix i bh provider ph = do + error "Chainweb.SPV.CreateProof.outputProofPrefix: FIXME: not yet implemented" +-- -- 1. TX proof +-- let +-- lookupOld = tableLookup +-- (_oldBlockOutputsTbl blockOutputs) +-- (_blockPayloadOutputsHash payload) +-- lookupNew = tableLookup +-- (_newBlockOutputsTbl blockOutputs) +-- (bh, _blockPayloadOutputsHash payload) +-- Just outs <- runMaybeT $ MaybeT lookupNew <|> MaybeT lookupOld +-- -- TODO: use the transaction tree cache +-- let (!subj, pos, t) = bodyTree @_ @ChainwebHashTag outs i +-- -- FIXME use log +-- let tree = (pos, t) +-- -- we blindly trust the ix +-- +-- -- 2. Payload proof +-- let !proof = tree N.:| [headerTree_ @BlockOutputsHash payload] +-- return (subj, proof) +-- where +-- blockOutputs = _payloadCacheBlockOutputs $ _payloadCache db +-- +-- payload = do +-- Just pd <- lookupPayloadDataWithHeight payloadDb (Just $ view blockHeight txHeader) (view blockPayloadHash txHeader) +-- return $ BlockPayload +-- { _blockPayloadTransactionsHash = view payloadDataTransactionsHash pd +-- , _blockPayloadOutputsHash = view payloadDataOutputsHash pd +-- , _blockPayloadPayloadHash = view payloadDataPayloadHash pd +-- } -- -------------------------------------------------------------------------- -- -- Internal Proof Creation @@ -287,9 +346,8 @@ type PayloadProofPrefix = -- createPayloadProof :: HasCallStack - => CanReadablePayloadCas tbl - => (Int -> BlockHeight -> PayloadDb tbl -> BlockPayload -> IO PayloadProofPrefix) - -> CutDb tbl + => (Int -> BlockHeight -> SomePayloadProvider -> BlockPayloadHash -> IO PayloadProofPrefix) + -> CutDb -- ^ Block Header Database -> ChainId -- ^ target chain. The proof asserts that the subject is included in @@ -303,10 +361,10 @@ createPayloadProof -> IO (MerkleProof SHA512t_256) createPayloadProof getPrefix cutDb tcid scid txHeight txIx = do trgHeadHeader <- maxEntry trgChain - createPayloadProof_ getPrefix headerDb payloadDb tcid scid txHeight txIx trgHeadHeader + createPayloadProof_ getPrefix headerDb provider tcid scid txHeight txIx trgHeadHeader where headerDb = view cutDbWebBlockHeaderDb cutDb - payloadDb = view cutDbPayloadDb cutDb + provider = view cutDbPayloadProviders cutDb ^?! ixg scid trgChain = headerDb ^?! ixg tcid -- | Creates a witness that a transaction is included in a chain of a chainweb @@ -314,10 +372,9 @@ createPayloadProof getPrefix cutDb tcid scid txHeight txIx = do -- createPayloadProof_ :: HasCallStack - => CanReadablePayloadCas tbl - => (Int -> BlockHeight -> PayloadDb tbl -> BlockPayload -> IO PayloadProofPrefix) + => (Int -> BlockHeight -> SomePayloadProvider -> BlockPayloadHash -> IO PayloadProofPrefix) -> WebBlockHeaderDb - -> PayloadDb tbl + -> SomePayloadProvider -> ChainId -- ^ target chain. The proof asserts that the subject is included in -- this chain @@ -330,7 +387,7 @@ createPayloadProof_ -> BlockHeader -- ^ the target header of the proof -> IO (MerkleProof SHA512t_256) -createPayloadProof_ getPrefix headerDb payloadDb tcid scid txHeight txIx trgHeader = do +createPayloadProof_ getPrefix headerDb provider tcid scid txHeight txIx trgHeader = do -- -- 1. TransactionTree -- 2. BlockPayload @@ -381,23 +438,18 @@ createPayloadProof_ getPrefix headerDb payloadDb tcid scid txHeight txIx trgHead , _spvExceptionTargetHeight = view blockHeight trgHeader } - Just pd <- lookupPayloadDataWithHeight payloadDb (Just $ view blockHeight txHeader) (view blockPayloadHash txHeader) - let payload = BlockPayload - { _blockPayloadTransactionsHash = view payloadDataTransactionsHash pd - , _blockPayloadOutputsHash = view payloadDataOutputsHash pd - , _blockPayloadPayloadHash = view payloadDataPayloadHash pd - } + let payloadHash = (view blockPayloadHash txHeader) -- ----------------------------- -- -- 1. Payload Proofs (TXs and Payload) - (subj, prefix) <- getPrefix txIx txHeight payloadDb payload + (subj, prefix) <- getPrefix txIx txHeight provider payloadHash -- ----------------------------- -- -- 2. BlockHeader proof -- - unless (view blockPayloadHash txHeader == _blockPayloadPayloadHash payload) + unless (view blockPayloadHash txHeader == payloadHash) $ throwM $ SpvExceptionInconsistentPayloadData { _spvExceptionMsg = "The stored payload hash doesn't match the the db index" , _spvExceptionMsgPayloadHash = view blockPayloadHash txHeader From bda0792ca79e5e744418cacbe0a7ffc80dc8f49e Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 30 Dec 2024 12:59:23 -0800 Subject: [PATCH 053/378] wip-4 (df170909d): src/Chainweb/SPV/RestAPI/Server.hs --- src/Chainweb/SPV/RestAPI/Server.hs | 45 +++++++++++++----------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/Chainweb/SPV/RestAPI/Server.hs b/src/Chainweb/SPV/RestAPI/Server.hs index 9bd9c59244..90e02548d3 100644 --- a/src/Chainweb/SPV/RestAPI/Server.hs +++ b/src/Chainweb/SPV/RestAPI/Server.hs @@ -40,7 +40,6 @@ import Servant import Chainweb.BlockHeight import Chainweb.ChainId import Chainweb.CutDB -import Chainweb.Payload.PayloadStore import Chainweb.RestAPI.Utils import Chainweb.SPV import Chainweb.SPV.CreateProof @@ -54,8 +53,7 @@ import Data.Singletons -- SPV Transaction Proof Handler spvGetTransactionProofHandler - :: CanReadablePayloadCas tbl - => CutDb tbl + :: CutDb -> ChainId -- ^ the target chain of the proof. This is the chain for which -- inclusion is proved. @@ -77,8 +75,7 @@ spvGetTransactionProofHandler db tcid scid bh i = -- SPV Transaction Output Proof Handler spvGetTransactionOutputProofHandler - :: CanReadablePayloadCas tbl - => CutDb tbl + :: CutDb -> ChainId -- ^ the target chain of the proof. This is the chain for which inclusion -- is proved. @@ -101,10 +98,9 @@ spvGetTransactionOutputProofHandler db tcid scid bh i = -- SPV API Server spvServer - :: forall tbl v (c :: ChainIdT) - . CanReadablePayloadCas tbl - => KnownChainIdSymbol c - => CutDbT tbl v + :: forall v (c :: ChainIdT) + . KnownChainIdSymbol c + => CutDbT v -> Server (SpvApi v c) spvServer (CutDbT db) = spvGetTransactionProofHandler db tcid @@ -116,40 +112,37 @@ spvServer (CutDbT db) -- Application for a single Chain spvApp - :: forall tbl v c - . CanReadablePayloadCas tbl - => KnownChainwebVersionSymbol v + :: forall v c + . KnownChainwebVersionSymbol v => KnownChainIdSymbol c - => CutDbT tbl v + => CutDbT v -> Application -spvApp db = serve (Proxy @(SpvApi v c)) (spvServer @tbl @v @c db) +spvApp db = serve (Proxy @(SpvApi v c)) (spvServer @v @c db) spvApiLayout - :: forall tbl v c + :: forall v c . KnownChainwebVersionSymbol v => KnownChainIdSymbol c - => CutDbT tbl v + => CutDbT v -> IO () spvApiLayout _ = T.putStrLn $ layout (Proxy @(SpvApi v c)) someSpvServer - :: forall tbl c - . CanReadablePayloadCas tbl - => KnownChainIdSymbol c - => SomeCutDb tbl + :: forall c + . KnownChainIdSymbol c + => SomeCutDb -> SomeServer -someSpvServer (SomeCutDb (db :: CutDbT tbl v)) - = SomeServer (Proxy @(SpvApi v c)) (spvServer @tbl @v @c db) +someSpvServer (SomeCutDb (db :: CutDbT v)) + = SomeServer (Proxy @(SpvApi v c)) (spvServer @v @c db) -- -------------------------------------------------------------------------- -- -- Multichain Server someSpvServers - :: CanReadablePayloadCas tbl - => ChainwebVersion - -> CutDb tbl + :: ChainwebVersion + -> CutDb -> SomeServer someSpvServers v db = mconcat $ flip fmap cids $ \(FromSingChainId (SChainId :: Sing c)) -> - someSpvServer @_ @c (someCutDbVal v db) + someSpvServer @c (someCutDbVal v db) where cids = toList $ chainIds db From 8838f67859182753c9bc185a0e79e275ff1d9d79 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:53 -0800 Subject: [PATCH 054/378] wip-3 (23d874999): src/Chainweb/SPV/VerifyProof.hs --- src/Chainweb/SPV/VerifyProof.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/SPV/VerifyProof.hs b/src/Chainweb/SPV/VerifyProof.hs index ee5d3de238..9d0074f5ec 100644 --- a/src/Chainweb/SPV/VerifyProof.hs +++ b/src/Chainweb/SPV/VerifyProof.hs @@ -60,7 +60,7 @@ runTransactionProof (TransactionProof _ p) -- fork of the target chain. -- verifyTransactionProof - :: CutDb tbl + :: CutDb -> TransactionProof SHA512t_256 -> IO Transaction verifyTransactionProof cutDb proof@(TransactionProof cid p) = do @@ -78,7 +78,7 @@ verifyTransactionProof cutDb proof@(TransactionProof cid p) = do -- chain. -- verifyTransactionProofAt - :: CutDb tbl + :: CutDb -> TransactionProof SHA512t_256 -> BlockHash -> IO Transaction @@ -123,7 +123,7 @@ runTransactionOutputProof (TransactionOutputProof _ p) -- fork of the target chain. -- verifyTransactionOutputProof - :: CutDb tbl + :: CutDb -> TransactionOutputProof SHA512t_256 -> IO TransactionOutput verifyTransactionOutputProof cutDb proof@(TransactionOutputProof cid p) = do @@ -141,7 +141,7 @@ verifyTransactionOutputProof cutDb proof@(TransactionOutputProof cid p) = do -- chain. -- verifyTransactionOutputProofAt - :: CutDb tbl + :: CutDb -> TransactionOutputProof SHA512t_256 -> BlockHash -> IO TransactionOutput From b9aad59315fabcc68c0552eeca2471a616ffcd60 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 18:49:57 -0800 Subject: [PATCH 055/378] wip-3 (23d874999): src/Chainweb/Sync/WebBlockHeaderStore.hs --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 513 ++++++++++++++--------- 1 file changed, 314 insertions(+), 199 deletions(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 887e669996..c0f23fadee 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -3,6 +3,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NumericUnderscores #-} @@ -13,6 +14,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} + {-# OPTIONS_GHC -fno-warn-orphans #-} -- | @@ -29,6 +31,7 @@ module Chainweb.Sync.WebBlockHeaderStore ( WebBlockHeaderStore(..) , newWebBlockHeaderStore , getBlockHeader +, forkInfoForHeader -- * , WebBlockPayloadStore(..) @@ -40,55 +43,46 @@ module Chainweb.Sync.WebBlockHeaderStore , PactExecutionService(..) ) where -import Control.Concurrent.Async -import Control.Lens -import Control.Monad -import Control.Monad.Catch - -import Data.Foldable -import Data.Hashable -import qualified Data.Text as T - -import GHC.Generics - -import qualified Network.HTTP.Client as HTTP - -import Servant.Client - -import System.LogLevel - --- internal modules - import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeader.Validation import Chainweb.BlockHeaderDB import Chainweb.BlockHeaderDB.RemoteDB -import Chainweb.BlockHeight import Chainweb.ChainId import Chainweb.ChainValue +import Chainweb.Difficulty (WindowWidth(..)) +import Chainweb.MinerReward (blockMinerReward) import Chainweb.Payload import Chainweb.Payload.PayloadStore -import Chainweb.Payload.RestAPI.Client +import Chainweb.PayloadProvider +import Chainweb.Storage.Table import Chainweb.Time import Chainweb.TreeDB -import qualified Chainweb.TreeDB as TDB +import Chainweb.TreeDB qualified as TDB import Chainweb.Utils import Chainweb.Version +import Chainweb.Version.Utils (diameterAt) import Chainweb.WebBlockHeaderDB import Chainweb.WebPactExecutionService - +import Control.Concurrent.Async +import Control.Lens +import Control.Monad +import Control.Monad.Catch +import Data.Foldable +import Data.Hashable import Data.LogMessage import Data.PQueue import Data.TaskMap - +import Data.Text qualified as T +import GHC.Generics (Generic) +import GHC.Stack +import Network.HTTP.Client qualified as HTTP import P2P.Peer import P2P.TaskQueue - +import Servant.Client +import System.LogLevel import Utils.Logging.Trace -import Chainweb.Storage.Table - -- -------------------------------------------------------------------------- -- -- Response Timeout Constants @@ -194,92 +188,180 @@ memoInsert cas m k a = tableLookup cas k >>= \case return v (Just !x) -> return x --- | Query a payload either from the local store, or the origin, or P2P network. +-- -- | Query a payload either from the local store, or the origin, or P2P network. +-- -- +-- -- The payload is only queried and not inserted into the local store. We want to +-- -- insert it only after it got validate by pact in order to avoid accumlation of +-- -- garbage. +-- -- +-- getBlockPayload +-- :: CanReadablePayloadCas tbl +-- => Cas candidateCas PayloadData +-- => WebBlockPayloadStore tbl +-- -> candidateCas +-- -> Priority +-- -> Maybe PeerInfo +-- -- ^ Peer from with the BlockPayloadHash originated, if available. +-- -> BlockHeader +-- -- ^ The BlockHeader for which the payload is requested +-- -> IO PayloadData +-- getBlockPayload s candidateStore priority maybeOrigin h = do +-- logfun Debug $ "getBlockPayload: " <> sshow h +-- tableLookup candidateStore payloadHash >>= \case +-- Just !x -> return x +-- Nothing -> lookupPayloadWithHeight cas (Just $ view blockHeight h) payloadHash >>= \case +-- Just !x -> return $! payloadWithOutputsToPayloadData x +-- Nothing -> memo memoMap payloadHash $ \k -> +-- pullOrigin (view blockHeight h) k maybeOrigin >>= \case +-- Nothing -> do +-- t <- queryPayloadTask (view blockHeight h) k +-- pQueueInsert queue t +-- awaitTask t +-- (Just !x) -> return x -- --- The payload is only queried and not inserted into the local store. We want to --- insert it only after it got validate by pact in order to avoid accumlation of --- garbage. +-- where +-- v = _chainwebVersion h +-- payloadHash = view blockPayloadHash h +-- cid = _chainId h -- -getBlockPayload - :: CanReadablePayloadCas tbl - => Cas candidateCas PayloadData - => WebBlockPayloadStore tbl - -> candidateCas - -> Priority - -> Maybe PeerInfo - -- ^ Peer from with the BlockPayloadHash originated, if available. - -> BlockHeader - -- ^ The BlockHeader for which the payload is requested - -> IO PayloadData -getBlockPayload s candidateStore priority maybeOrigin h = do - logfun Debug $ "getBlockPayload: " <> sshow h - tableLookup candidateStore payloadHash >>= \case - Just !x -> return x - Nothing -> lookupPayloadWithHeight cas (Just $ view blockHeight h) payloadHash >>= \case - Just !x -> return $! payloadWithOutputsToPayloadData x - Nothing -> memo memoMap payloadHash $ \k -> - pullOrigin (view blockHeight h) k maybeOrigin >>= \case - Nothing -> do - t <- queryPayloadTask (view blockHeight h) k - pQueueInsert queue t - awaitTask t - (Just !x) -> return x - - where - v = _chainwebVersion h - payloadHash = view blockPayloadHash h - cid = _chainId h - - mgr = _webBlockPayloadStoreMgr s - cas = _webBlockPayloadStoreCas s - memoMap = _webBlockPayloadStoreMemo s - queue = _webBlockPayloadStoreQueue s +-- mgr = _webBlockPayloadStoreMgr s +-- cas = _webBlockPayloadStoreCas s +-- memoMap = _webBlockPayloadStoreMemo s +-- queue = _webBlockPayloadStoreQueue s +-- +-- logfun :: LogLevel -> T.Text -> IO () +-- logfun = _webBlockPayloadStoreLogFunction s +-- +-- traceLogfun :: LogMessage a => LogLevel -> a -> IO () +-- traceLogfun = _webBlockPayloadStoreLogFunction s +-- +-- taskMsg k msg = "payload task " <> sshow k <> " @ " <> sshow (view blockHash h) <> ": " <> msg +-- +-- traceLabel subfun = +-- "Chainweb.Sync.WebBlockHeaderStore.getBlockPayload." <> subfun +-- +-- -- | Try to pull a block payload from the given origin peer +-- -- +-- pullOrigin :: BlockHeight -> BlockPayloadHash -> Maybe PeerInfo -> IO (Maybe PayloadData) +-- pullOrigin _ k Nothing = do +-- logfun Debug $ taskMsg k "no origin" +-- return Nothing +-- pullOrigin _ k (Just origin) = do +-- let originEnv = setResponseTimeout pullOriginResponseTimeout $ peerInfoClientEnv mgr origin +-- logfun Debug $ taskMsg k "lookup origin" +-- !r <- trace traceLogfun (traceLabel "pullOrigin") k 0 +-- $ runClientM (payloadClient v cid k Nothing) originEnv +-- case r of +-- (Right !x) -> do +-- logfun Debug $ taskMsg k "received from origin" +-- return $ Just x +-- Left (e :: ClientError) -> do +-- logfun Debug $ taskMsg k $ "failed to receive from origin: " <> sshow e +-- return Nothing +-- +-- -- | Query a block payload via the task queue +-- -- +-- queryPayloadTask :: BlockHeight -> BlockPayloadHash -> IO (Task ClientEnv PayloadData) +-- queryPayloadTask bh k = newTask (sshow k) priority $ \logg env -> do +-- logg @T.Text Debug $ taskMsg k "query remote block payload" +-- let taskEnv = setResponseTimeout taskResponseTimeout env +-- !r <- trace traceLogfun (traceLabel "queryPayloadTask") k (let Priority i = priority in i) +-- $ runClientM (payloadClient v cid k (Just bh)) taskEnv +-- case r of +-- (Right !x) -> do +-- logg @T.Text Debug $ taskMsg k "received remote block payload" +-- return x +-- Left (e :: ClientError) -> do +-- logg @T.Text Debug $ taskMsg k $ "failed: " <> sshow e +-- throwM e - logfun :: LogLevel -> T.Text -> IO () - logfun = _webBlockPayloadStoreLogFunction s +-- -------------------------------------------------------------------------- -- +-- Consensus State - traceLogfun :: LogMessage a => LogLevel -> a -> IO () - traceLogfun = _webBlockPayloadStoreLogFunction s +-- | For a given latest BlockHeader return the state of consensus +-- +-- THIS IS NOT FINAL! +-- +-- TODO: +-- This should eventually become a consensus component on its own. It probably +-- needs to depend on the cut db (and it is not clear how that would fit in this +-- module). +-- +-- It should take into consideration the current hash power, weight, and +-- difficulty. It should probably also take into consideration historical hash +-- power, like the all time max. +-- +-- For instance, the confirmation depth should approach infinite as the honest +-- hash power degrades towards 50% (of the all time max). +-- +-- For now we use +-- +-- - safe: 6 times of the graph diameter block heights, ~ 9 min +-- - final: 4 epochs, 120 * 4 block heights, ~ 4 hours +-- +consensusState + :: HasCallStack + => WebBlockHeaderDb + -> BlockHeader + -> IO ConsensusState +consensusState wdb hdr = do + db <- getWebBlockHeaderDb wdb hdr + safeHdr <- fromJuste <$> seekAncestor db hdr safeHeight + finalHdr <- fromJuste <$> seekAncestor db hdr finalHeight + return ConsensusState + { _consensusStateLatest = syncStateOfBlockHeader hdr + , _consensusStateSafe = syncStateOfBlockHeader safeHdr + , _consensusStateFinal = syncStateOfBlockHeader finalHdr + } + where + WindowWidth w = _versionWindow (_chainwebVersion hdr) + finalHeight = int @Int @_ $ max 0 (int height - int w * 4) + safeHeight = int @Int @_ $ max 0 (int height - 6 * int diam) + height = view blockHeight hdr + diam = diameterAt hdr height - taskMsg k msg = "payload task " <> sshow k <> " @ " <> sshow (view blockHash h) <> ": " <> msg +-- -------------------------------------------------------------------------- -- +-- ForkInfo For Header - traceLabel subfun = - "Chainweb.Sync.WebBlockHeaderStore.getBlockPayload." <> subfun +-- | Compute ForkInfo Object for a single newly added BlockHeader +-- +forkInfoForHeader + :: WebBlockHeaderDb + -> BlockHeader + -> Maybe EncodedPayloadData + -> IO ForkInfo +forkInfoForHeader wdb hdr pldData + + -- FIXME do we need this case??? We never add genesis headers... + | isGenesisBlockHeader hdr = do + state <- consensusState wdb hdr + return $ ForkInfo + { _forkInfoTrace = [] + , _forkInfoBasePayloadHash = _latestPayloadHash state + , _forkInfoTargetState = state + , _forkInfoNewBlockCtx = Just nbctx + } + + | otherwise = do + phdr <- ParentHeader <$> lookupParentHeader wdb hdr + + -- TargetState + state <- consensusState wdb hdr + return $ ForkInfo + { _forkInfoTrace = [blockHeaderToEvaluationCtx phdr pld pldData] + , _forkInfoBasePayloadHash = view blockPayloadHash (_parentHeader phdr) + , _forkInfoTargetState = state + , _forkInfoNewBlockCtx = Just nbctx + } + where + pld = view blockPayloadHash hdr - -- | Try to pull a block payload from the given origin peer - -- - pullOrigin :: BlockHeight -> BlockPayloadHash -> Maybe PeerInfo -> IO (Maybe PayloadData) - pullOrigin _ k Nothing = do - logfun Debug $ taskMsg k "no origin" - return Nothing - pullOrigin bh k (Just origin) = do - let originEnv = setResponseTimeout pullOriginResponseTimeout $ peerInfoClientEnv mgr origin - logfun Debug $ taskMsg k "lookup origin" - !r <- trace traceLogfun (traceLabel "pullOrigin") k 0 - $ runClientM (payloadClient v cid k (Just bh)) originEnv - case r of - (Right !x) -> do - logfun Debug $ taskMsg k "received from origin" - return $ Just x - Left (e :: ClientError) -> do - logfun Debug $ taskMsg k $ "failed to receive from origin: " <> sshow e - return Nothing - - -- | Query a block payload via the task queue - -- - queryPayloadTask :: BlockHeight -> BlockPayloadHash -> IO (Task ClientEnv PayloadData) - queryPayloadTask bh k = newTask (sshow k) priority $ \logg env -> do - logg @T.Text Debug $ taskMsg k "query remote block payload" - let taskEnv = setResponseTimeout taskResponseTimeout env - !r <- trace traceLogfun (traceLabel "queryPayloadTask") k (let Priority i = priority in i) - $ runClientM (payloadClient v cid k (Just bh)) taskEnv - case r of - (Right !x) -> do - logg @T.Text Debug $ taskMsg k "received remote block payload" - return x - Left (e :: ClientError) -> do - logg @T.Text Debug $ taskMsg k $ "failed: " <> sshow e - throwM e + nbctx = NewBlockCtx + { _newBlockCtxMinerReward = blockMinerReward v (height + 1) + , _newBlockCtxParentCreationTime = view blockCreationTime hdr + } + height = view blockHeight hdr + v = _chainwebVersion hdr -- -------------------------------------------------------------------------- -- -- Obtain, Validate, and Store BlockHeaders @@ -299,19 +381,36 @@ instance Exception GetBlockHeaderFailure -- iterative algorithm is preferable. -- getBlockHeaderInternal - :: CanPayloadCas tbl - => BlockHeaderCas candidateHeaderCas - => PayloadDataCas candidatePayloadCas + :: BlockHeaderCas candidateHeaderCas + -- ^ CandidateHeaderCas is a content addressed store for BlockHeaders + => ReadableTable candidatePldTbl BlockPayloadHash EncodedPayloadData => WebBlockHeaderStore - -> WebBlockPayloadStore tbl + -- ^ Block Header Store for all Chains -> candidateHeaderCas - -> candidatePayloadCas - -> Maybe (BlockPayloadHash, PayloadWithOutputs) + -- ^ Ephemeral store for block headers under consideration + -> candidatePldTbl + -> PayloadProviders + -> Maybe (BlockPayloadHash, EncodedPayloadOutputs) + -- ^ Payload and Header data for the block, in case that it is + -- available. -> Priority + -- ^ Priority of P2P tasks for querying headers and payloads -> Maybe PeerInfo + -- ^ Origin hint for the block header and related data. -> ChainValue BlockHash + -- ^ The hash of the block that is processed, tagged by the chain -> IO (ChainValue BlockHeader) -getBlockHeaderInternal headerStore payloadStore candidateHeaderCas candidatePayloadCas localPayload priority maybeOrigin h = do + -- ^ If validation is successful the added block header is returned +getBlockHeaderInternal + headerStore + candidateHeaderCas + candidatePldTbl + providers + localPayload + priority + maybeOrigin + h + = do logg Debug $ "getBlockHeaderInternal: " <> sshow h !bh <- memoInsert cas memoMap h $ \k@(ChainValue cid k') -> do @@ -359,9 +458,9 @@ getBlockHeaderInternal headerStore payloadStore candidateHeaderCas candidatePayl <> ": " <> sshow p getBlockHeaderInternal headerStore - payloadStore candidateHeaderCas - candidatePayloadCas + candidatePldTbl + providers localPayload priority maybeOrigin' @@ -377,80 +476,96 @@ getBlockHeaderInternal headerStore payloadStore candidateHeaderCas candidatePayl <> ": " <> sshow p void $ getBlockHeaderInternal headerStore - payloadStore candidateHeaderCas - candidatePayloadCas + candidatePldTbl + providers localPayload priority maybeOrigin' p - chainDb <- getWebBlockHeaderDb (_webBlockHeaderStoreCas headerStore) header + chainDb <- getWebBlockHeaderDb wdb header validateInductiveChainM (tableLookup chainDb) header - !p <- runConcurrently - -- query payload - $ Concurrently - (getBlockPayload payloadStore candidatePayloadCas priority maybeOrigin' header) - - -- query parent (recursively) + -- Get the Payload Provider and + withPayloadProvider providers cid $ \provider -> do + let hints = Hints <$> maybeOrigin' + pld <- tableLookup candidatePldTbl (view blockPayloadHash header) + finfo <- forkInfoForHeader wdb header pld + + runConcurrently + -- instruct the payload provider to fetch payload data and prepare + -- validation. + $ Concurrently + (prefetchPayloads provider hints finfo + -- TODO (_rankedBlockPayloadHash header) + ) + + -- query parent (recursively) + -- + <* queryParent (view blockParent <$> chainValue header) + + -- query adjacent parents (recursively) + <* mconcat (queryAdjacentParent <$> adjParents header) + + -- TODO Above recursive calls are potentially long running + -- computations. In particular pact validation can take significant + -- amounts of time. We may try make these calls tail recursive by + -- providing a continuation. This would allow earlier garbage + -- collection of some stack resources. + -- + -- This requires to provide a CPS version of memoInsert. + + logg Debug $ taskMsg k $ "getBlockHeaderInternal got pre-requesites for " <> sshow h + + -- ------------------------------------------------------------------ -- + -- Validation + + -- 1. Validate Parents and Adjacent Parents -- - <* queryParent (view blockParent <$> chainValue header) - - -- query adjacent parents (recursively) - <* mconcat (queryAdjacentParent <$> adjParents header) + -- Existence and validity of parents and adjacent parents is guaranteed + -- in the dependency resolution code above. - -- TODO Above recursive calls are potentially long running - -- computations. In particular pact validation can take significant - -- amounts of time. We may try make these calls tail recursive by - -- providing a continuation. This would allow earlier garbage - -- collection of some stack resources. + -- 2. Validate BlockHeader -- - -- This requires to provide a CPS version of memoInsert. - - logg Debug $ taskMsg k $ "getBlockHeaderInternal got pre-requesites for " <> sshow h + -- Single chain properties are currently validated when the block header + -- is inserted into the block header db. - -- ------------------------------------------------------------------ -- - -- Validation - - -- 1. Validate Parents and Adjacent Parents - -- - -- Existence and validity of parents and adjacent parents is guaranteed - -- in the dependency resolution code above. + -- 3. Validate Braiding + -- + -- Currently, we allow blocks here that are not part of a valid + -- braiding. However, those block won't make it into cuts, because the + -- cut processor uses 'joinIntoHeavier' to combine an external cut with + -- the local cut, which guarantees that only blocks with valid braiding + -- are referenced by local cuts. + -- + -- TODO: check braiding and reject blocks without valid braiding here. - -- 2. Validate BlockHeader - -- - -- Single chain properties are currently validated when the block header - -- is inserted into the block header db. + -- 4. Validate block payload + -- + -- Pact validation is done in the context of a particular header. Just + -- because the payload does already exist in the store doesn't mean that + -- validation succeeds in the context of a particular block header. + -- + -- If we reach this point in the code we are certain that the header + -- isn't yet in the block header database and thus we still must + -- validate the payload for this block header. + -- - -- 3. Validate Braiding - -- - -- Currently, we allow blocks here that are not part of a valid - -- braiding. However, those block won't make it into cuts, because the - -- cut processor uses 'joinIntoHeavier' to combine an external cut with - -- the local cut, which guarantees that only blocks with valid braiding - -- are referenced by local cuts. - -- - -- TODO: check braiding and reject blocks without valid braiding here. + logg Debug $ taskMsg k $ + "getBlockHeaderInternal validate payload for " <> sshow h - -- 4. Validate block payload - -- - -- Pact validation is done in the context of a particular header. Just - -- because the payload does already exist in the store doesn't mean that - -- validation succeeds in the context of a particular block header. - -- - -- If we reach this point in the code we are certain that the header - -- isn't yet in the block header database and thus we still must - -- validate the payload for this block header. - -- + r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do + logg Warn $ taskMsg k $ "getBlockHeaderInternal pact validation for " <> sshow h <> " failed with :" <> sshow e + throwM e + unless (r == _forkInfoTargetState finfo) $ do + throwM $ GetBlockHeaderFailure $ "unexpected result state" + <> "; expected: " <> sshow (_forkInfoTargetState finfo) + <> "; actual: " <> sshow r - logg Debug $ taskMsg k $ "getBlockHeaderInternal validate payload for " <> sshow h <> ": " <> sshow p - validateAndInsertPayload header p `catch` \(e :: SomeException) -> do - logg Warn $ taskMsg k $ "getBlockHeaderInternal pact validation for " <> sshow h <> " failed with :" <> sshow e - throwM e - logg Debug $ taskMsg k "getBlockHeaderInternal pact validation succeeded" + logg Debug $ taskMsg k "getBlockHeaderInternal pact validation succeeded" - logg Debug $ taskMsg k $ "getBlockHeaderInternal return header " <> sshow h - return $! chainValue header + logg Debug $ taskMsg k $ "getBlockHeaderInternal return header " <> sshow h + return $! chainValue header logg Debug $ "getBlockHeaderInternal: got block header for " <> sshow h return bh @@ -461,6 +576,7 @@ getBlockHeaderInternal headerStore payloadStore candidateHeaderCas candidatePayl memoMap = _webBlockHeaderStoreMemo headerStore queue = _webBlockHeaderStoreQueue headerStore v = _chainwebVersion cas + wdb = _webBlockHeaderStoreCas headerStore logfun :: LogFunction logfun = _webBlockHeaderStoreLogFunction headerStore @@ -473,23 +589,23 @@ getBlockHeaderInternal headerStore payloadStore candidateHeaderCas candidatePayl traceLabel subfun = "Chainweb.Sync.WebBlockHeaderStore.getBlockHeaderInternal." <> subfun - pact = _pactValidateBlock - $ _webPactExecutionService - $ _webBlockPayloadStorePact payloadStore - - validateAndInsertPayload :: BlockHeader -> PayloadData -> IO () - validateAndInsertPayload hdr p = do - let payload = case localPayload of - Just (hsh, pwo) - | hsh == view blockPayloadHash hdr - -> CheckablePayloadWithOutputs pwo - _ -> CheckablePayload p - outs <- trace logfun - (traceLabel "pact") - (view blockHash hdr) - (length (view payloadDataTransactions p)) - $ pact hdr payload - addNewPayload (_webBlockPayloadStoreCas payloadStore) (view blockHeight hdr) outs + -- pact = _pactValidateBlock + -- $ _webPactExecutionService + -- $ _webBlockPayloadStorePact payloadStore + + -- validateAndInsertPayload :: BlockHeader -> PayloadData -> IO () + -- validateAndInsertPayload hdr p = do + -- let payload = case localPayload of + -- Just (hsh, pwo) + -- | hsh == view blockPayloadHash hdr + -- -> CheckablePayloadWithOutputs pwo + -- _ -> CheckablePayload p + -- outs <- trace logfun + -- (traceLabel "pact") + -- (view blockHash hdr) + -- (length (view payloadDataTransactions p)) + -- $ pact hdr payload + -- addNewPayload (_webBlockPayloadStoreCas payloadStore) (view blockHeight hdr) outs queryBlockHeaderTask ck@(ChainValue cid k) = newTask (sshow ck) priority $ \l env -> chainValue <$> do @@ -575,28 +691,27 @@ newWebPayloadStore mgr pact payloadDb logfun = do payloadDb payloadMemo payloadTaskQueue logfun mgr pact getBlockHeader - :: CanPayloadCas tbl - => BlockHeaderCas candidateHeaderCas - => PayloadDataCas candidatePayloadCas + :: BlockHeaderCas candidateHeaderCas + => ReadableTable candidatePldTbl BlockPayloadHash EncodedPayloadData => WebBlockHeaderStore - -> WebBlockPayloadStore tbl -> candidateHeaderCas - -> candidatePayloadCas - -> Maybe (BlockPayloadHash, PayloadWithOutputs) + -> candidatePldTbl + -> PayloadProviders + -> Maybe (BlockPayloadHash, EncodedPayloadOutputs) -> ChainId -> Priority -> Maybe PeerInfo -> BlockHash -> IO BlockHeader -getBlockHeader headerStore payloadStore candidateHeaderCas candidatePayloadCas localPayload cid priority maybeOrigin h +getBlockHeader headerStore candidateHeaderCas candidatePldTbl providers localPayload cid priority maybeOrigin h = ((\(ChainValue _ b) -> b) <$> go) `catch` \(TaskFailed _es) -> throwM $ TreeDbKeyNotFound @BlockHeaderDb h where go = getBlockHeaderInternal headerStore - payloadStore candidateHeaderCas - candidatePayloadCas + candidatePldTbl + providers localPayload priority maybeOrigin From de6f8689dd8532a2cb320eb8526f14aca6fc8d26 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:31:53 -0800 Subject: [PATCH 056/378] wip-3 (23d874999): src/Chainweb/WebPactExecutionService.hs --- src/Chainweb/WebPactExecutionService.hs | 61 +++++++++++++++++++++---- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/src/Chainweb/WebPactExecutionService.hs b/src/Chainweb/WebPactExecutionService.hs index 0e689c1775..64017900d7 100644 --- a/src/Chainweb/WebPactExecutionService.hs +++ b/src/Chainweb/WebPactExecutionService.hs @@ -16,6 +16,7 @@ module Chainweb.WebPactExecutionService , NewBlock(..) , newBlockToPayloadWithOutputs , newBlockParent + , newBlockToNewPayload ) where import Control.Lens @@ -33,7 +34,6 @@ import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainId import Chainweb.Mempool.Mempool (InsertError) -import Chainweb.Miner.Pact import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types @@ -52,10 +52,27 @@ import qualified Pact.Types.Command as Pact4 import qualified Pact.Types.ChainMeta as Pact4 import Data.Text (Text) import Chainweb.BlockCreationTime (BlockCreationTime) +import Chainweb.PayloadProvider +import qualified Data.ByteString as B -- -------------------------------------------------------------------------- -- -- PactExecutionService +-- +-- FIXME: This section does not belong into this Module. It should be in pact +-- service. This module is node supposed to implement functionality of pact +-- service but only to be a proxy for the Pact service API. +-- FIXME: PactExecutionService is the public API of Pact service. Why does the +-- API leak the internals of unfinished blocks? In what situation would the +-- caller care about NewBlockInProgress? If it is for testing it should be +-- exposed through an internal type. +-- +-- FIXME: Make sure that PayloadNumber is strictly monotonic +-- +-- FIXME: compute total gas +-- +-- Currently, blocks are finalized only within the miner, which seems wrong. +-- data NewBlock = NewBlockInProgress !(ForSomePactVersion BlockInProgress) | NewBlockPayload !ParentHeader !PayloadWithOutputs @@ -80,6 +97,31 @@ instance HasChainId NewBlock where _chainId (NewBlockInProgress (ForSomePactVersion _ bip)) = _chainId bip _chainId (NewBlockPayload ph _) = _chainId ph +newBlockToNewPayload :: NewBlock -> NewPayload +newBlockToNewPayload nb = NewPayload + { _newPayloadChainwebVersion = _chainwebVersion nb + , _newPayloadChainId = _chainId nb + , _newPayloadParentHeight = height + , _newPayloadParentHash = h + , _newPayloadBlockPayloadHash = _payloadWithOutputsPayloadHash pwo + , _newPayloadEncodedPayloadData = Just epd + , _newPayloadEncodedPayloadOutputs = Just epo + , _newPayloadNumber = 0 -- FIXME + , _newPayloadTxCount = int $ length txs + , _newPayloadSize = int $ sum $ B.length . _transactionBytes . fst <$> txs + , _newPayloadOutputSize = int $ sum $ B.length . _transactionOutputBytes . snd <$> txs + , _newPayloadFees = 0 -- FIXME + } + where + (h, height, _) = newBlockParent nb + pwo = newBlockToPayloadWithOutputs nb + txs = _payloadWithOutputsTransactions pwo + + epd = EncodedPayloadData $ encodeToByteString $ payloadWithOutputsToPayloadData pwo + epo = EncodedPayloadOutputs $ encodeToByteString $ pwo + +-- -------------------------------------------------------------------------- -- + -- | Service API for interacting with a single or multi-chain ("Web") pact service. -- Thread-safe to be called from multiple threads. Backend is queue-backed on a per-chain -- basis. @@ -92,10 +134,9 @@ data PactExecutionService = PactExecutionService -- ^ Validate block payload data by running through pact service. , _pactNewBlock :: !( ChainId -> - Miner -> NewBlockFill -> ParentHeader -> - IO (Historical NewBlock) + IO (Historical NewPayload) ) , _pactContinueBlock :: !( forall pv. @@ -161,10 +202,9 @@ newtype WebPactExecutionService = WebPactExecutionService _webPactNewBlock :: WebPactExecutionService -> ChainId - -> Miner -> NewBlockFill -> ParentHeader - -> IO (Historical NewBlock) + -> IO (Historical NewPayload) _webPactNewBlock = _pactNewBlock . _webPactExecutionService {-# INLINE _webPactNewBlock #-} @@ -197,7 +237,7 @@ mkWebPactExecutionService -> WebPactExecutionService mkWebPactExecutionService hm = WebPactExecutionService $ PactExecutionService { _pactValidateBlock = \h pd -> withChainService (_chainId h) $ \p -> _pactValidateBlock p h pd - , _pactNewBlock = \cid m fill parent -> withChainService cid $ \p -> _pactNewBlock p cid m fill parent + , _pactNewBlock = \cid fill parent -> withChainService cid $ \p -> _pactNewBlock p cid fill parent , _pactContinueBlock = \cid bip -> withChainService cid $ \p -> _pactContinueBlock p cid bip , _pactLocal = \_pf _sv _rd _ct -> throwM $ userError "Chainweb.WebPactExecutionService.mkPactExecutionService: No web-level local execution supported" , _pactLookup = \cid cd txs -> withChainService cid $ \p -> _pactLookup p cid cd txs @@ -219,8 +259,11 @@ mkPactExecutionService mkPactExecutionService q = PactExecutionService { _pactValidateBlock = \h pd -> do validateBlock h pd q - , _pactNewBlock = \_ m fill parent -> do - fmap NewBlockInProgress <$> newBlock m fill parent q + , _pactNewBlock = \_ fill parent -> do + -- FIXME: This seems wrong. This should really be implemented in pact. + -- PactExecutionService is supposed to be a thin an lightweight wrapper. + nb <- newBlock fill parent q + return $ newBlockToNewPayload . NewBlockInProgress <$> nb , _pactContinueBlock = \_ bip -> do continueBlock bip q , _pactLocal = \pf sv rd ct -> @@ -243,7 +286,7 @@ mkPactExecutionService q = PactExecutionService emptyPactExecutionService :: HasCallStack => PactExecutionService emptyPactExecutionService = PactExecutionService { _pactValidateBlock = \_ _ -> pure emptyPayload - , _pactNewBlock = \_ _ _ _ -> throwM (userError "emptyPactExecutionService: attempted `newBlock` call") + , _pactNewBlock = \_ _ _ -> throwM (userError "emptyPactExecutionService: attempted `newBlock` call") , _pactContinueBlock = \_ _ -> throwM (userError "emptyPactExecutionService: attempted `continueBlock` call") , _pactLocal = \_ _ _ _ -> throwM (userError "emptyPactExecutionService: attempted `local` call") , _pactLookup = \_ _ _ -> return $! HM.empty From e0c73aef37fb4c87cfd33dc883c117fa6f53cb9e Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 13 Jan 2025 23:46:53 -0800 Subject: [PATCH 057/378] wip-6 (1d630fd6d): src/P2P/Node.hs --- src/P2P/Node.hs | 163 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 108 insertions(+), 55 deletions(-) diff --git a/src/P2P/Node.hs b/src/P2P/Node.hs index 078c5d4200..48830c90f5 100644 --- a/src/P2P/Node.hs +++ b/src/P2P/Node.hs @@ -11,6 +11,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module: P2P.Node @@ -40,11 +41,17 @@ module P2P.Node , withPeerDb -- * P2P Node +, P2pNodeParameters(..) +, P2pNode , p2pCreateNode , p2pStartNode +, p2pStartNodeInactive , p2pStopNode +, p2pRunNode , guardPeerDb , getNewPeerManager +, setActive +, setInactive -- * Logging and Monitoring @@ -123,7 +130,6 @@ import Data.LogMessage import Network.X509.SelfSigned -import P2P.Node.Configuration import P2P.Node.PeerDB import P2P.Node.RestAPI.Client import P2P.Peer @@ -205,7 +211,6 @@ makeLenses ''P2pSessionInfo -- data P2pNode = P2pNode { _p2pNodeNetworkId :: !NetworkId - , _p2pNodeChainwebVersion :: !ChainwebVersion , _p2pNodePeerInfo :: !PeerInfo , _p2pNodePeerDb :: !PeerDb , _p2pNodeSessions :: !(TVar (M.Map PeerInfo (P2pSessionInfo, Async (Maybe Bool)))) @@ -220,8 +225,15 @@ data P2pNode = P2pNode , _p2pNodeDoPeerSync :: !Bool -- ^ Synchronize peers at start of each session. Note, that this is -- expensive. + , _p2pNodePrivate :: !Bool + -- ^ Whether this node is private + , _p2pNodeMaxSessionCount :: !Natural + , _p2pNodeSessionTimeout :: !Seconds } +_p2pNodeChainwebVersion :: P2pNode -> ChainwebVersion +_p2pNodeChainwebVersion = _chainwebVersion . _p2pNodePeerDb + instance HasChainwebVersion P2pNode where _chainwebVersion = _p2pNodeChainwebVersion @@ -312,6 +324,13 @@ nodeGeometric :: HasCallStack => P2pNode -> Double -> IO Int nodeGeometric node = nodeRandom node . geometric {-# INLINE nodeGeometric #-} +-- | Set a node active, causing it to initiating new sessions. +-- +setActive :: P2pNode -> STM () +setActive node = writeTVar (_p2pNodeActive node) True + +-- | Set a node inactive. An inactive node does not initiate any new sessions. +-- setInactive :: P2pNode -> STM () setInactive node = writeTVar (_p2pNodeActive node) False @@ -506,10 +525,9 @@ syncFromPeer node info = do -- @O(_p2pConfigActivePeerCount conf)@ -- findNextPeer - :: P2pConfiguration - -> P2pNode + :: P2pNode -> IO PeerEntry -findNextPeer conf node = do +findNextPeer node = do candidates <- awaitCandidates -- random circular shift of a set @@ -528,7 +546,6 @@ findNextPeer conf node = do -- this ix expensive but lazy and only forced if p0 is empty let p2 = L.groupBy ((==) `on` _peerEntrySuccessiveFailures) p1 - -- Choose the category to pick from -- -- In 95% of all cases are peer is selected from the highest priority, if possible. @@ -575,7 +592,7 @@ findNextPeer conf node = do -- Retry if there are more active sessions than the maximum number -- of sessions -- - check (int sessionCount < _p2pConfigMaxSessionCount conf) + check (int sessionCount < _p2pNodeMaxSessionCount node) let addrs = S.fromList (_peerAddr <$> M.keys sessions) @@ -612,9 +629,9 @@ findNextPeer conf node = do -- | This can loop forever if there are no peers available for the respective -- network. -- -newSession :: P2pConfiguration -> P2pNode -> IO () -newSession conf node = do - newPeer <- findNextPeer conf node +newSession :: P2pNode -> IO () +newSession node = do + newPeer <- findNextPeer node let newPeerInfo = _peerEntryInfo newPeer logg node Debug $ "Selected new peer " <> encodeToText newPeerInfo <> ", " @@ -629,7 +646,7 @@ newSession conf node = do -- FIXME there are better ways to prevent the node from spinning -- if no suitable (non-failing node) is available. -- cf. GitHub issue #117 - newSession conf node + newSession node True -> do logg node Debug $ "Connected to new peer " <> showInfo newPeerInfo let env = peerClientEnv node newPeerInfo @@ -647,11 +664,11 @@ newSession conf node = do logg node Debug $ "Started peer session " <> showSessionId newPeerInfo newSes loggFun node Info $ JsonLog info where - TimeSpan timeoutMs = secondsToTimeSpan @Double (_p2pConfigSessionTimeout conf) + TimeSpan timeoutMs = secondsToTimeSpan @Double (_p2pNodeSessionTimeout node) peerDb = _p2pNodePeerDb node syncFromPeer_ pinfo - | _p2pConfigPrivate conf = return True + | _p2pNodePrivate node = return True | _p2pNodeDoPeerSync node = syncFromPeer node pinfo | otherwise = return True @@ -739,20 +756,23 @@ waitAnySession node = do startPeerDb :: ChainwebVersion -> HS.HashSet NetworkId - -> P2pConfiguration + -> Bool + -- ^ Whether this node is private + -> [PeerInfo] + -- ^ Set of statically known peers. -> IO PeerDb -startPeerDb v nids conf = do +startPeerDb v nids isPrivate knownPeers = do !peerDb <- newEmptyPeerDb v forM_ nids $ \nid -> - peerDbInsertPeerInfoList_ True nid (_p2pConfigKnownPeers conf) peerDb - return $ if _p2pConfigPrivate conf + peerDbInsertPeerInfoList_ True nid knownPeers peerDb + return $ if isPrivate then makePeerDbPrivate peerDb else peerDb -- | Stop a 'PeerDb', possibly persisting the db to a file. -- -stopPeerDb :: P2pConfiguration -> PeerDb -> IO () -stopPeerDb _ _ = return () +stopPeerDb :: PeerDb -> IO () +stopPeerDb _ = return () {-# INLINE stopPeerDb #-} -- | Run a computation with a PeerDb @@ -760,57 +780,82 @@ stopPeerDb _ _ = return () withPeerDb :: ChainwebVersion -> HS.HashSet NetworkId - -> P2pConfiguration + -> Bool + -- ^ Whether this node is private + -> [PeerInfo] + -- ^ Set of statically known peers -> (PeerDb -> IO a) -> IO a -withPeerDb v nids conf = bracket (startPeerDb v nids conf) (stopPeerDb conf) +withPeerDb v nids isPrivate knownPeers = + bracket (startPeerDb v nids isPrivate knownPeers) stopPeerDb -- -------------------------------------------------------------------------- -- -- Create -p2pCreateNode - :: ChainwebVersion - -> NetworkId - -> Peer - -> LogFunction - -> PeerDb - -> HTTP.Manager - -> Bool - -> P2pSession - -> IO P2pNode -p2pCreateNode cv nid peer logfun db mgr doPeerSync session = do +data P2pNodeParameters = P2pNodeParameters + { _p2pNodeParamsNetworkId :: !NetworkId + , _p2pNodeParamsMyPeerInfo :: !PeerInfo + , _p2pNodeParamsLogFunction :: !LogFunction + , _p2pNodeParamsPeerDb :: !PeerDb + , _p2pNodeParamsManager :: !HTTP.Manager + , _p2pNodeParamsDoPeerSync :: !Bool + -- ^ whether to synchronize peers on session startup + , _p2pNodeParamsIsPrivate :: !Bool + -- ^ whether the node is private + , _p2pNodeParamsMaxSessionCount :: !Natural + -- ^ Maximum Session count + , _p2pNodeParamsSessionTimeout :: !Seconds + -- ^ Session timeout + , _p2pNodeParamsSession :: !P2pSession + } + +instance HasChainwebVersion P2pNodeParameters where + _chainwebVersion = _chainwebVersion . _p2pNodeParamsPeerDb + +p2pCreateNode :: P2pNodeParameters -> IO P2pNode +p2pCreateNode params = do -- intialize P2P State sessionsVar <- newTVarIO mempty statsVar <- newTVarIO emptyP2pNodeStats rngVar <- newIORef =<< R.newStdGen - activeVar <- newTVarIO True - let !s = P2pNode - { _p2pNodeNetworkId = nid - , _p2pNodeChainwebVersion = cv - , _p2pNodePeerInfo = myInfo - , _p2pNodePeerDb = db - , _p2pNodeSessions = sessionsVar - , _p2pNodeManager = mgr - , _p2pNodeLogFunction = logfun - , _p2pNodeStats = statsVar - , _p2pNodeClientSession = session - , _p2pNodeRng = rngVar - , _p2pNodeActive = activeVar - , _p2pNodeDoPeerSync = doPeerSync - } - - logfun @T.Text Debug "created node" - return s + activeVar <- newTVarIO False + logfun Debug "created node" + return P2pNode + { _p2pNodeNetworkId = _p2pNodeParamsNetworkId params + , _p2pNodePeerInfo = _p2pNodeParamsMyPeerInfo params + , _p2pNodePeerDb = _p2pNodeParamsPeerDb params + , _p2pNodeSessions = sessionsVar + , _p2pNodeManager = _p2pNodeParamsManager params + , _p2pNodeLogFunction = _p2pNodeParamsLogFunction params + , _p2pNodeStats = statsVar + , _p2pNodeClientSession = _p2pNodeParamsSession params + , _p2pNodeRng = rngVar + , _p2pNodeActive = activeVar + , _p2pNodeDoPeerSync = _p2pNodeParamsDoPeerSync params + , _p2pNodePrivate = _p2pNodeParamsIsPrivate params + , _p2pNodeMaxSessionCount = _p2pNodeParamsMaxSessionCount params + , _p2pNodeSessionTimeout = _p2pNodeParamsSessionTimeout params + } where - myInfo = _peerInfo peer + logfun :: LogLevel -> T.Text -> IO () + logfun = _p2pNodeParamsLogFunction params -- -------------------------------------------------------------------------- -- -- Run P2P Node -p2pStartNode :: P2pConfiguration -> P2pNode -> IO () -p2pStartNode conf node = concurrently_ - (runForever (logg node) "P2P.Node.awaitSessions" $ awaitSessions node) - (runForever (logg node) "P2P.Node.newSessions" $ newSession conf node) +p2pStartNodeInactive :: P2pNode -> IO () +p2pStartNodeInactive node = do + atomically (setInactive node) + concurrently_ + (runForever (logg node) "P2P.Node.awaitSessions" $ awaitSessions node) + (runForever (logg node) "P2P.Node.newSessions" $ newSession node) + +p2pStartNode :: P2pNode -> IO () +p2pStartNode node = do + atomically (setActive node) + concurrently_ + (runForever (logg node) "P2P.Node.awaitSessions" $ awaitSessions node) + (runForever (logg node) "P2P.Node.newSessions" $ newSession node) p2pStopNode :: P2P.Node.P2pNode -> IO () p2pStopNode node = do @@ -819,3 +864,11 @@ p2pStopNode node = do readTVar (_p2pNodeSessions node) mapM_ (uninterruptibleCancel . snd) sessions logg node Info "stopped node" + +-- | Activate and run a node. +-- +-- The node is stopped when an asynchronoous exception is raised in the thread. +-- +p2pRunNode :: P2pNode -> IO () +p2pRunNode n = finally (p2pStartNode n) (p2pStopNode n) + From 4ecf53402804d4c27dfeb869f438c6808a27d661 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 19 Jan 2025 01:21:32 -0800 Subject: [PATCH 058/378] wip-8 (42a4d5a06): src/Chainweb/RestAPI/Utils.hs --- src/Chainweb/RestAPI/Utils.hs | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Chainweb/RestAPI/Utils.hs b/src/Chainweb/RestAPI/Utils.hs index 902b7b8dc1..8fb6905228 100644 --- a/src/Chainweb/RestAPI/Utils.hs +++ b/src/Chainweb/RestAPI/Utils.hs @@ -22,6 +22,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE RankNTypes #-} #ifndef CURRENT_PACKAGE_VERSION #define CURRENT_PACKAGE_VERSION "UNKNOWN" @@ -98,10 +99,13 @@ module Chainweb.RestAPI.Utils , SupportedRespBodyContentType , SetRespBodyContentType +-- * Debugging Tools +, Traced(..) ) where import Control.Exception (Exception, throw) import Control.Monad.Catch (bracket) +import Control.Monad.IO.Class import Data.Aeson import qualified Data.ByteString.Lazy as BSL @@ -124,6 +128,8 @@ import Network.Wai.Handler.Warp (HostPreference) import Servant.API hiding (addHeader) import Servant.Client import Servant.Server +import Servant.Server.Internal.Delayed +import Servant.Server.Internal.DelayedIO -- internal modules import Chainweb.ChainId @@ -452,6 +458,11 @@ instance Semigroup SomeServer where SomeServer (Proxy :: Proxy a) a <> SomeServer (Proxy :: Proxy b) b = SomeServer (Proxy @(a :<|> b)) (a :<|> b) + -- SomeServer (Proxy :: Proxy a) (a :: Server a) <> SomeServer (Proxy :: Proxy b) (b :: Server b) + -- = SomeServer + -- (Proxy @(Traced "left" a :<|> Traced "right" b)) + -- (a :<|> b) + instance Monoid SomeServer where mappend = (<>) mempty = SomeServer (Proxy @EmptyAPI) emptyServer @@ -532,3 +543,30 @@ deallocateSocket (_, sock) = N.close sock withSocket :: Port -> HostPreference -> ((Port, N.Socket) -> IO a) -> IO a withSocket port interface = bracket (allocateSocket port interface) deallocateSocket +-- -------------------------------------------------------------------------- -- +-- Debugging + +-- | Quick And Dirty runtime debugging of Servant routing +-- +-- FIXME: it may be cleaner to use annotations in the style of Servant's +-- 'Summary' API feature. +-- +newtype Traced (s :: Symbol) api = Traced api + +enabledTraced :: Bool +enabledTraced = False + +instance (KnownSymbol s, HasServer api ctx) => HasServer (Traced s api) ctx where + type ServerT (Traced s api) m = ServerT api m + hoistServerWithContext _ pctx f s = hoistServerWithContext (Proxy @api) pctx f s + route _ ctx dl + | enabledTraced = route (Proxy @api) ctx $ addParameterCheck + (const <$> dl) + (DelayedIO $ liftIO $ putStrLn (symbolVal @s Proxy)) + | otherwise = route (Proxy @api) ctx dl + +instance HasClient m api => HasClient m (Traced s api) where + type Client m (Traced s api) = Client m api + clientWithRoute pm _ r = clientWithRoute pm (Proxy @api) r + hoistClientMonad pm _ f c = hoistClientMonad pm (Proxy @api) f c + From 58bb4d1eaef42c683d0b85fe0a36b2b56c26d1de Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 9 Jan 2025 11:59:24 -0800 Subject: [PATCH 059/378] [CI] temporarily disable buid of tests and tools in applications workflow --- .github/workflows/applications.yml | 88 +++++++++++++++--------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index e1aba3f86e..f1104d3dc0 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -358,26 +358,27 @@ jobs: run: cabal build chainweb --only-dependencies - name: Build chainweb library run: cabal build --ghc-options=-j2 lib:chainweb - - name: Build chainweb applications - run: | - cabal build -j2 --ghc-options=-j2 \ - chainweb:bench:bench \ - exe:b64 \ - exe:calculate-release \ - exe:compact \ - exe:db-checksum \ - exe:ea \ - exe:genconf \ - exe:header-dump \ - exe:known-graphs \ - exe:pact-diff \ - exe:run-nodes \ - exe:tx-list \ - test:chainweb-tests \ - test:compaction-tests \ - test:multi-node-network-tests \ - test:remote-tests \ - test:chainweb-storage-tests + # # Temporarily disabled on lars/pp/* branches + # - name: Build chainweb applications + # run: | + # cabal build -j2 --ghc-options=-j2 \ + # chainweb:bench:bench \ + # exe:b64 \ + # exe:calculate-release \ + # exe:compact \ + # exe:db-checksum \ + # exe:ea \ + # exe:genconf \ + # exe:header-dump \ + # exe:known-graphs \ + # exe:pact-diff \ + # exe:run-nodes \ + # exe:tx-list \ + # test:chainweb-tests \ + # test:compaction-tests \ + # test:multi-node-network-tests \ + # test:remote-tests \ + # test:chainweb-storage-tests - name: Build chainweb-node application run: cabal build -j2 --ghc-options=-j2 chainweb-node:exe:chainweb-node @@ -388,36 +389,35 @@ jobs: echo "Git working tree is not clean. The build changed some file that is checked into git." 1>&2 exit 1 fi - - name: Run ea and verify consistency of genesis headers - run: | - cabal run ea - if ! git diff --exit-code -- src/Chainweb/BlockHeader/Genesis/ src/Chainweb/Pact/Transactions/ ; then - echo "Inconsistent genesis headers detected. Did you forget to run ea?" 1>&2 - exit 1 - fi + + # Temporarily disabled (it seems that ea depends on chainweb-test-utils) + # - name: Run ea and verify consistency of genesis headers + # run: | + # cabal run ea + # if ! git diff --exit-code -- src/Chainweb/BlockHeader/Genesis/ src/Chainweb/Pact/Transactions/ ; then + # echo "Inconsistent genesis headers detected. Did you forget to run ea?" 1>&2 + # exit 1 + # fi # Archive Artifacts - name: Prepare artifacts run: | mkdir -p artifacts/chainweb - cp $(cabal list-bin b64) artifacts/chainweb - cp $(cabal list-bin bench) artifacts/chainweb - cp $(cabal list-bin calculate-release) artifacts/chainweb cp $(cabal list-bin chainweb-node) artifacts/chainweb - cp $(cabal list-bin chainweb-storage-tests) artifacts/chainweb - cp $(cabal list-bin chainweb-tests) artifacts/chainweb - cp $(cabal list-bin compact) artifacts/chainweb - cp $(cabal list-bin compaction-tests) artifacts/chainweb - cp $(cabal list-bin db-checksum) artifacts/chainweb - cp $(cabal list-bin ea) artifacts/chainweb - cp $(cabal list-bin genconf) artifacts/chainweb - cp $(cabal list-bin header-dump) artifacts/chainweb - cp $(cabal list-bin known-graphs) artifacts/chainweb - cp $(cabal list-bin multi-node-network-tests) artifacts/chainweb - cp $(cabal list-bin pact-diff) artifacts/chainweb - cp $(cabal list-bin remote-tests) artifacts/chainweb - cp $(cabal list-bin run-nodes) artifacts/chainweb - cp $(cabal list-bin tx-list) artifacts/chainweb + # cp $(cabal list-bin chainweb-storage-tests) artifacts/chainweb + # cp $(cabal list-bin chainweb-tests) artifacts/chainweb + # cp $(cabal list-bin compact) artifacts/chainweb + # cp $(cabal list-bin compaction-tests) artifacts/chainweb + # cp $(cabal list-bin db-checksum) artifacts/chainweb + # cp $(cabal list-bin ea) artifacts/chainweb + # cp $(cabal list-bin genconf) artifacts/chainweb + # cp $(cabal list-bin header-dump) artifacts/chainweb + # cp $(cabal list-bin known-graphs) artifacts/chainweb + # cp $(cabal list-bin multi-node-network-tests) artifacts/chainweb + # cp $(cabal list-bin pact-diff) artifacts/chainweb + # cp $(cabal list-bin remote-tests) artifacts/chainweb + # cp $(cabal list-bin run-nodes) artifacts/chainweb + # cp $(cabal list-bin tx-list) artifacts/chainweb cp README.md artifacts/chainweb cp CHANGELOG.md artifacts/chainweb cp LICENSE artifacts/chainweb From b58127ef20e51f73144441118f43de3899e01a5a Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 1 Jan 2025 18:32:11 -0800 Subject: [PATCH 060/378] WIP minimal payload provider --- chainweb.cabal | 3 + src/Chainweb/MerkleUniverse.hs | 7 + src/Chainweb/PayloadProvider/Minimal.hs | 524 ++++++++++++++++++ .../PayloadProvider/Minimal/Payload.hs | 424 ++++++++++++++ .../PayloadProvider/Minimal/PayloadDB.hs | 269 +++++++++ 5 files changed, 1227 insertions(+) create mode 100644 src/Chainweb/PayloadProvider/Minimal.hs create mode 100644 src/Chainweb/PayloadProvider/Minimal/Payload.hs create mode 100644 src/Chainweb/PayloadProvider/Minimal/PayloadDB.hs diff --git a/chainweb.cabal b/chainweb.cabal index bddd96e0c9..f7d6eb71e6 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -226,6 +226,9 @@ library , Chainweb.Payload.RestAPI.Client , Chainweb.PayloadProvider , Chainweb.PayloadProvider.Initialization + , Chainweb.PayloadProvider.Minimal + , Chainweb.PayloadProvider.Minimal.Payload + , Chainweb.PayloadProvider.Minimal.PayloadDB , Chainweb.PayloadProvider.P2P , Chainweb.PayloadProvider.P2P.RestAPI , Chainweb.PayloadProvider.P2P.RestAPI.Client diff --git a/src/Chainweb/MerkleUniverse.hs b/src/Chainweb/MerkleUniverse.hs index 7e39d84866..00b916812d 100644 --- a/src/Chainweb/MerkleUniverse.hs +++ b/src/Chainweb/MerkleUniverse.hs @@ -97,6 +97,10 @@ data ChainwebHashTag | BlockEventsHashTag | RequestKeyTag | PactEventTag + + -- Minimal Payload Provider + | MinimalPayloadTag + deriving (Show, Eq) instance MerkleUniverse ChainwebHashTag where @@ -127,6 +131,9 @@ instance MerkleUniverse ChainwebHashTag where type MerkleTagVal ChainwebHashTag 'RequestKeyTag = 0x0032 type MerkleTagVal ChainwebHashTag 'PactEventTag = 0x0034 + -- Minimal Payload Provider + type MerkleTagVal ChainwebHashTag 'MinimalPayloadTag = 0x0035 + instance HashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Void where type Tag Void = 'VoidTag toMerkleNode = \case diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs new file mode 100644 index 0000000000..e3aca71c65 --- /dev/null +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -0,0 +1,524 @@ +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TypeAbstractions #-} + +-- | +-- Module: Chainweb.IdleProvider +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- A minimal payload provider. +-- +-- No user provided payload processing is supported. +-- +-- Miner reward payout must be recorded in the block payload hash. Ideally, this +-- would be done in a way such that payloads could be validated just from the +-- evaluation context and the payload hash, without the need to persist and +-- synchronize additional payload data. An obvious way to do this would be to +-- use the miner pk account as payload hash. However, that would be unsafe by +-- allowing miner to attack the Chainweb SPV proofs by proving arbitrary facts. +-- +-- At the moment, Chainweb SPV proofs do not generally witness provenance with +-- respect to a particular block header. If that information is needed in a +-- particular proof it must be provided on application level in the proof claim, +-- which means that it is hidden behind a Chainweb MerkleLog tag. With the +-- current proof format, there is no way for consumers of SPV proofs to verify +-- that a proof claim was made in the context of a particular payload provider. +-- Consequently, _all_ data in the preimage of a Merkle root must be tagged and +-- preimages of all Merkle roots in the Chainweb Merkle tree must be verified +-- down to the tag level by miners. +-- +-- This means that Validation of a block header can be selfcontained only if all +-- data in the preimage of the payload hash is deterministically derived from +-- the header data. +-- +-- There is no way to encode the miner account in the block header in a +-- practical way. Therefore, the requirement to include the miner account (or +-- some data that is derived from the miner account) in the preimage of the +-- payload hash implies that header validation cannot be selfcontained and we +-- need to introduce payloads that are persisted and synchronized between nodes. +-- +-- Additional Remarks: +-- +-- More generally, the above also means that if a node user decides to not +-- validate payloads on all chains, that they blindly trust the miners in the +-- system to validate all payloads on all chains each time they verify an SPV +-- proof. In particular, it means that a majority of miners *must* validate all +-- payloads on all chains! +-- +-- This may be actually a strong argument why it is good that over 50% of the +-- chains are blocked most of the time. One can probably establish a Ramsey +-- style argument that a each chain must be covered by a majority of miners or +-- otherwise some miners would risk to have their devices blocked sometimes. +-- +module Chainweb.PayloadProvider.Minimal +( MinimalProviderConfig(..) +, defaultMinimalProviderConfig +, validateMinimalProviderConfig +, pMinimalProviderConfig +, MinimalPayloadProvider +, minimalPayloadDb +, minimalPayloadQueue +, newMinimalPayloadProvider +) where + +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.Logger +import Chainweb.MinerReward +import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.Minimal.Payload +import Chainweb.PayloadProvider.Minimal.PayloadDB qualified as PDB +import Chainweb.PayloadProvider.P2P +import Chainweb.PayloadProvider.P2P qualified as Rest +import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest +import Chainweb.Ranked +import Chainweb.Storage.Table +import Chainweb.Storage.Table.Map +import Chainweb.Storage.Table.RocksDB +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Chainweb.Version +import Configuration.Utils +import Control.Concurrent.Async +import Control.Concurrent.STM +import Control.Lens hiding ((.=)) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.Except (throwError) +import Data.ByteString qualified as B +import Data.HashSet qualified as HS +import Data.LogMessage (LogFunction, LogFunctionText) +import Data.PQueue (PQueue) +import Data.Text qualified as T +import GHC.Generics (Generic) +import Network.HTTP.Client qualified as HTTP +import Numeric.Natural +import P2P.TaskQueue +import Servant.Client +import System.LogLevel + +-- -------------------------------------------------------------------------- -- + +data MinimalProviderConfig = MinimalProviderConfig + { _mpcRedeemChain :: !ChainId + , _mpcRedeemAccount :: !Account + } + deriving (Show, Eq, Generic) + +makeLenses ''MinimalProviderConfig + +defaultMinimalProviderConfig :: MinimalProviderConfig +defaultMinimalProviderConfig = MinimalProviderConfig + { _mpcRedeemChain = unsafeChainId 0 + , _mpcRedeemAccount = invalidAccount + -- Most likely this account is invalid on all providers. If the account + -- is invalid on the redeem chain, the reward can't be claimed. So, most + -- likely, this burns the miner rewards. + } + +instance ToJSON MinimalProviderConfig where + toJSON o = object + [ "redeemChain" .= _mpcRedeemChain o + , "redeemAccount" .= _mpcRedeemAccount o + ] + +instance FromJSON MinimalProviderConfig where + parseJSON = withObject "MinimalProviderConfig" $ \o -> + MinimalProviderConfig + <$> o .: "redeemChain" + <*> o .: "redeemAccount" + +instance FromJSON (MinimalProviderConfig -> MinimalProviderConfig) where + parseJSON = withObject "MinimalProviderConfig" $ \o -> id + <$< mpcRedeemChain ..: "redeemChain" % o + <*< mpcRedeemAccount ..: "redeemAccount" % o + +pMinimalProviderConfig :: MParser MinimalProviderConfig +pMinimalProviderConfig = id + <$< mpcRedeemChain .:: pChainId + <*< mpcRedeemAccount .:: pAccount + where + pChainId :: OptionParser ChainId + pChainId = unsafeChainId <$> option auto + % long "default-redeem-chain" + <> help "chain on which block rewards from the default payload provider can be claimed" + + pAccount :: OptionParser Account + pAccount = option textReader + % long "default-redeem-account" + <> help "a valid account on the redeem chain for the default payload provider" + +validateMinimalProviderConfig + :: HasChainwebVersion v + => v + -> ConfigValidation MinimalProviderConfig [] +validateMinimalProviderConfig v o + | HS.member rcid (chainIds v) = return () + | otherwise = do + throwError $ mconcat + [ "The provided redeem chain for the default payload provider is not a valid chain in the current chainweb" + , " Provided value " <> sshow rcid + ] + where + rcid = _mpcRedeemChain o + +-- -------------------------------------------------------------------------- -- + +data MinimalPayloadProvider = MinimalPayloadProvider + { _minimalChainwebVersion :: !ChainwebVersion + , _minimalChainId :: !ChainId + , _minimalPayloadVar :: !(TMVar NewPayload) + , _minimalRedeemChain :: !ChainId + , _minimalMinerInfo :: !Account + , _minimalPayloadStore :: !(PayloadStore (PDB.PayloadDb RocksDbTable) Payload) + , _minimalCandidatePayloads :: !(MapTable RankedBlockPayloadHash Payload) + -- ^ FIXME: should this be moved into the Payload Store? + -- + -- For now we just prune after each successful syncToBlock. + , _minimalLogger :: LogFunction + } + +minimalPayloadDb :: Getter MinimalPayloadProvider (PDB.PayloadDb RocksDbTable) +minimalPayloadDb = to (_payloadStoreTable . _minimalPayloadStore) + +minimalPayloadQueue :: Getter MinimalPayloadProvider (PQueue (Task ClientEnv Payload)) +minimalPayloadQueue = to (_payloadStoreQueue . _minimalPayloadStore) + +newMinimalPayloadProvider + :: Logger logger + => HasChainwebVersion v + => HasChainId c + => logger + -> v + -> c + -> RocksDb + -> HTTP.Manager + -> MinimalProviderConfig + -> IO MinimalPayloadProvider +newMinimalPayloadProvider logger v c rdb mgr conf + | payloadProviderTypeForChain v c /= MinimalProvider = + error "Chainweb.PayloadProvider.Minimal.configuration: chain does not use minimal provider" + | otherwise = do + SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal v + SomeChainIdT @c _ <- return $ someChainIdVal c + let payloadClient h = Rest.payloadClient @v @c @'MinimalProvider h + + pdb <- PDB.initPayloadDb $ PDB.configuration v c rdb + store <- newPayloadStore mgr (logFunction pldStoreLogger) pdb payloadClient + var <- newEmptyTMVarIO + candidates <- emptyTable + logFunctionText logger Info "minimal payload provider started" + return MinimalPayloadProvider + { _minimalChainwebVersion = _chainwebVersion v + , _minimalChainId = _chainId c + , _minimalPayloadVar = var + , _minimalRedeemChain = _mpcRedeemChain conf + , _minimalMinerInfo = _mpcRedeemAccount conf + , _minimalPayloadStore = store + , _minimalCandidatePayloads = candidates + , _minimalLogger = logFunction logger + } + where + pldStoreLogger = addLabel ("sub-component", "payloadStore") logger + +instance HasChainwebVersion MinimalPayloadProvider where + _chainwebVersion = _minimalChainwebVersion + +instance HasChainId MinimalPayloadProvider where + _chainId = _minimalChainId + +instance + ReadableTable MinimalPayloadProvider RankedBlockPayloadHash Payload + where + tableLookup = tableLookup . _minimalPayloadStore + tableLookupBatch' s = tableLookupBatch' (_minimalPayloadStore s) + tableMember = tableMember . _minimalPayloadStore + +instance + Table MinimalPayloadProvider RankedBlockPayloadHash Payload + where + tableInsert = tableInsert . _minimalPayloadStore + tableInsertBatch s = tableInsertBatch (_minimalPayloadStore s) + tableDelete s = tableDelete (_minimalPayloadStore s) + tableDeleteBatch s = tableDeleteBatch (_minimalPayloadStore s) + +-- -------------------------------------------------------------------------- -- +-- Validate Payload + +data PayloadValidationFailure + = PayloadInvalidChainwebVersion !(Expected ChainwebVersion) !(Actual ChainwebVersion) + | PayloadInvalidChainId !(Expected ChainId) !(Actual ChainId) + | PayloadInvalidMinerReward !(Expected MinerReward) !(Actual MinerReward) + | PayloadInvalidHeight !(Expected BlockHeight) !(Actual BlockHeight) + | PayloadInvalidHash !(Expected BlockPayloadHash) !(Actual BlockPayloadHash) + | PayloadInvalidPayloadData !(Expected EncodedPayloadData) !(Actual EncodedPayloadData) + deriving (Show, Eq, Generic) + +instance Exception PayloadValidationFailure + +-- | Validate a Payload against a given 'EvaluationCtx'. +-- +-- Notes: +-- +-- The type witnesses that ChainId values are valid for the latest chainweb +-- graph. +-- +-- The Account type witnesses that the size of the account data is within the +-- required limits. +-- +-- _evaluationCtxParentCreationTime: there's no notion of time in the minimal +-- payload provider. +-- +-- _evaluationCtxParentHash: the parent hash is not reflected in the payload. +-- Payloads uniquly identify a block via version, chainid, and height. +-- +validatePayload + :: forall m + . MonadThrow m + => MinimalPayloadProvider + -> Payload + -> EvaluationCtx + -> m () +validatePayload p pld ctx = do + checkEq PayloadInvalidChainwebVersion + (_chainwebVersion p) + (_chainwebVersion pld) + checkEq PayloadInvalidChainId + (_chainId p) + (_chainId pld) + checkEq PayloadInvalidHeight + (_evaluationCtxParentHeight ctx + 1) + (view payloadBlockHeight pld) + checkEq PayloadInvalidMinerReward + (_evaluationCtxMinerReward ctx) + (view payloadMinerReward pld) + checkEq PayloadInvalidHash + (_evaluationCtxPayloadHash ctx) + (view payloadHash pld) + case _evaluationCtxPayloadData ctx of + Nothing -> return () + Just x -> checkEq PayloadInvalidPayloadData x (encodedPayloadData pld) + where + checkEq + :: Eq a + => (Expected a -> Actual a -> PayloadValidationFailure) + -> a + -> a + -> m () + checkEq failure expected actual = + unless (expected == actual) $ + throwM $ failure (Expected expected) (Actual actual) + +encodedPayloadData :: Payload -> EncodedPayloadData +encodedPayloadData = EncodedPayloadData . runPutS . encodePayload + +encodedPayloadDataSize :: EncodedPayloadData -> Natural +encodedPayloadDataSize (EncodedPayloadData bs) = int $ B.length bs + +decodePayloadData :: MonadThrow m => EncodedPayloadData -> m Payload +decodePayloadData (EncodedPayloadData bs) = runGetS decodePayload bs + +-- -------------------------------------------------------------------------- -- +-- Payload Provider API + +instance PayloadProvider MinimalPayloadProvider where + prefetchPayloads = minimalPrefetchPayloads + syncToBlock = minimalSyncToBlock + latestPayloadSTM = minimalLatestPayloadStm + latestPayloadIO = minimalLatestPayloadIO + +-- | Fetch a payload for an evaluation context and insert it into the candidate +-- table. +-- +getPayloadForContext + :: MinimalPayloadProvider + -> Maybe Hints + -> EvaluationCtx + -> IO Payload +getPayloadForContext p h ctx = do + insertPayloadData (_evaluationCtxPayloadData ctx) + pld <- Rest.getPayload + (_minimalPayloadStore p) + (_minimalCandidatePayloads p) + (Priority $ negate $ int $ _evaluationCtxParentHeight ctx) + (_hintsOrigin <$> h) + (_evaluationCtxRankedPayloadHash ctx) + casInsert (_minimalCandidatePayloads p) pld + return pld + where + insertPayloadData Nothing = return () + insertPayloadData (Just epld) = case decodePayloadData epld of + Right pld -> casInsert (_minimalCandidatePayloads p) pld + Left e -> do + lf Warn $ "failed to decode encoded payload from evaluation ctx: " <> sshow e + + lf :: LogFunctionText + lf = _minimalLogger p + +-- | Concurrently fetch all payloads in an evaluation context and insert them +-- into the candidate table. +-- +-- This version blocks until all payloads are fetched (or a timeout occurs). +-- +-- Should we also expose a version that is fire-and-forget? +-- +minimalPrefetchPayloads + :: MinimalPayloadProvider + -> Maybe Hints + -> ForkInfo + -> IO () +minimalPrefetchPayloads p h i = do + logg p Info "prefetch payloads" + mapConcurrently_ (getPayloadForContext p h) $ _forkInfoTrace i + +-- | +-- +-- NOTE: +-- +-- The mimimal Payload Provider is special in that it is stateless. Validation +-- of a payload does depend only on the evaluation context and not on the +-- history of the chain. Similarly, new block production does depend only on the +-- block height and not on current latest block. +-- +-- This means that 'syncToBlock' does not need to resolve reorgs. It also means +-- that it succeeds on an empty ForkInfo trace. (Otherwise it would succeed only +-- if the requested target state matched the current state.) However, it also +-- means, that the content of the payload db could be incomplete or even +-- inconsistent by containing payloads from non-canonical forks at some block +-- heights. +-- +-- If it is a requirement that the payload provider offers access to the full +-- and consistent history via the service API, the implementation of +-- 'syncToBlock' must be adjusted to resolve reorgs and guarantee a seemless +-- history. +-- +minimalSyncToBlock + :: MinimalPayloadProvider + -> Maybe Hints + -> ForkInfo + -> IO ConsensusState +minimalSyncToBlock p h i = do + logg p Info "syncToBlock called" + validatePayloads p h i + + -- FIXME: is this right place to prune the candidate store? + pruneCandidates p (_forkInfoTargetState i) + + -- Produce new block + case _forkInfoNewBlockCtx i of + Nothing -> return () + Just ctx -> do + logg p Info $ "create new payload for sync state: " <> sshow latestState + atomically + $ writeTMVar (_minimalPayloadVar p) + $ makeNewPayload p latestState ctx + return $ _forkInfoTargetState i + where + latestState = _consensusStateLatest $ _forkInfoTargetState i + +logg :: MinimalPayloadProvider -> LogLevel -> T.Text -> IO () +logg p l t = _minimalLogger p l t + +makeNewPayload + :: MinimalPayloadProvider + -> SyncState + -> NewBlockCtx + -> NewPayload +makeNewPayload p latest ctx = NewPayload + { _newPayloadChainwebVersion = _chainwebVersion p + , _newPayloadChainId = _chainId p + , _newPayloadParentHeight = _syncStateHeight latest + , _newPayloadParentHash = _syncStateBlockHash latest + , _newPayloadBlockPayloadHash = view payloadHash pld + , _newPayloadEncodedPayloadData = Just epld + , _newPayloadEncodedPayloadOutputs = Nothing + , _newPayloadNumber = 0 -- there will only be a single output per parent + , _newPayloadTxCount = 0 + , _newPayloadSize = encodedPayloadDataSize epld + , _newPayloadOutputSize = 0 + , _newPayloadFees = 0 + } + where + pld = newPayload p p + (_syncStateHeight latest + 1) + (_newBlockCtxMinerReward ctx) + (_minimalRedeemChain p) + (_minimalMinerInfo p) + epld = encodedPayloadData pld + +-- | Fetches and validates all missing payloads for a 'ForkInfo' value. +-- +-- Throws PayloadValidationFailure if a payload is not valid. +-- +-- FIXME: is it fine that we can end up with a payload database where +-- some earlier payloads are missing? This would also mean that the respective +-- payloads have not been validated. This is probably fine with the current cut +-- pipeline, but this might not always be the case in the future. +-- +validatePayloads + :: MinimalPayloadProvider + -> Maybe Hints + -> ForkInfo + -> IO () +validatePayloads p h i= mapConcurrently_ go (_forkInfoTrace i) + where + go ctx = do + pld <- getPayloadForContext p h ctx + validatePayload p pld ctx + casInsert (_minimalPayloadStore p) pld + +minimalLatestPayloadIO :: MinimalPayloadProvider -> IO NewPayload +minimalLatestPayloadIO = atomically . readTMVar . _minimalPayloadVar + +minimalLatestPayloadStm :: MinimalPayloadProvider -> STM NewPayload +minimalLatestPayloadStm = readTMVar . _minimalPayloadVar + +-- | The depth at which the candidate cache is pruned. We prune after each +-- successful syncToBlock call. Most of the time this cache is very small and +-- pruning should be very fast. +-- +-- * In normal operation, that just extends the chain, the a pruning depth of 0 is +-- fine +-- * For "normal" reorgs the diameter of the graph is sufficient. +-- +-- FIXME Make this depend on the catchup step size. +-- +-- * Remove items from this cache that are in the validated database. +-- +-- * For reorgs that are due to network forks/partitions the depth of the +-- catchup would be optimal, which is bounded by the reorg limit. For now we +-- ignore that case. We want a solution where we don't have permanent cost +-- overhead for this exceptional scenario. +-- +candidatePruningDepth :: MinimalPayloadProvider -> BlockHeight -> BlockHeight +candidatePruningDepth p h = int $ diameter (chainGraphAt (_chainwebVersion p) h) + +pruneCandidates :: MinimalPayloadProvider -> ConsensusState -> IO () +pruneCandidates p s = deleteLt (_minimalCandidatePayloads p) lrh + { _rankedHeight = h - candidatePruningDepth p h + } + where + lrh = latestRankedBlockPayloadHash s + h = _rankedHeight lrh + diff --git a/src/Chainweb/PayloadProvider/Minimal/Payload.hs b/src/Chainweb/PayloadProvider/Minimal/Payload.hs new file mode 100644 index 0000000000..8a89ee9c64 --- /dev/null +++ b/src/Chainweb/PayloadProvider/Minimal/Payload.hs @@ -0,0 +1,424 @@ +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} + +-- | +-- Module: Chainweb.PayloadProvider.Minimal.Payload +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.Minimal.Payload +( Account +, account +, invalidAccount +, Payload +, newPayload +, payloadMinerReward +, payloadChainwebVersion +, payloadChainId +, payloadBlockHeight +, payloadRedeemChain +, payloadRedeemAccount +, payloadHash +, genesisPayload +, encodePayload +, decodePayload +, type PayloadCas +, proof +, runProof +) where + +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.Crypto.MerkleLog +import Chainweb.MerkleLogHash +import Chainweb.MerkleUniverse +import Chainweb.MinerReward +import Chainweb.Storage.Table +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Chainweb.Version +import Chainweb.Version.Registry +import Control.Lens (Getter) +import Control.Lens.Getter (to) +import Control.Monad +import Control.Monad.Catch +import Data.Aeson +import Data.Aeson.Types (Pair) +import Data.ByteString.Short qualified as BS +import Data.Function +import Data.Hashable +import Data.MerkleLog (MerkleProof, runMerkleProof) +import Data.Void +import Data.Word +import GHC.Generics (Generic) +import GHC.Stack +import Numeric.Natural + +-- -------------------------------------------------------------------------- -- +-- Account Info + +-- | Maximum allowed account size. This must be verified by the payload +-- provider. +-- +-- Omitting to enforce this would be DoS attack vector. +-- +maxAccountSize :: Integral n => n +maxAccountSize = 512 +{-# INLINE maxAccountSize #-} + +data AccountToLargeException = + AccountTooLarge (Actual Natural) (Expected Natural) + deriving (Show, Eq, Generic) + +instance Exception AccountToLargeException + +accountTooLarge :: Integral n => n -> AccountToLargeException +accountTooLarge actual = + AccountTooLarge (Actual (int actual)) (Expected maxAccountSize) + +-- | Account Information in a payload provider specific format. +-- +-- NOTE: The maximum length is 512 bytes. This must be enforced during payload +-- validation. It is implied by 'decodeAccount' and the 'FromJSON' instance. +-- +newtype Account = Account BS.ShortByteString + deriving (Show, Eq, Ord, Generic) + deriving (ToJSON, FromJSON) via (JsonTextRepresentation "Account" Account) + +-- | Creates a new Account value and checks that the size is within the +-- acceptable limits. +-- +account :: MonadThrow m => BS.ShortByteString -> m Account +account bs + | BS.length bs > maxAccountSize = + throwM $ accountTooLarge (BS.length bs) + | otherwise = return $ Account bs + +encodeAccount :: HasCallStack => Account -> Put +encodeAccount (Account bs) = do + let l = BS.length bs + void $ when (l > int (maxBound @Word16)) $ + error "Chainweb.PayloadProvider.Minimal.encodePayload: account is too large" + putWord16le (int l) + putShortByteString bs +{-# INLINE encodeAccount #-} + +decodeAccount :: Get Account +decodeAccount = do + l <- int <$> getWord16le + bs <- getShortByteString l + account bs +{-# INLINE decodeAccount #-} + +instance HasTextRepresentation Account where + toText (Account bs) = encodeB64UrlNoPaddingText $ BS.fromShort bs + fromText = account . BS.toShort <=< decodeB64UrlNoPaddingText + {-# INLINEABLE toText #-} + {-# INLINEABLE fromText #-} + +-- | An account that _may_ be invalid. It is not guaranteed to be invalid! +-- +-- This assumed to be an invalid account on all providers. However, do not +-- count on it! +-- +invalidAccount :: Account +invalidAccount = Account "" + +-- -------------------------------------------------------------------------- -- + +-- | Payload for the Minimal Payload Provider +-- +-- Its sole purpose is to enable payout of miner rewards. +-- +-- Payload validation witnesses that the payload properties match the respective +-- values frm the evaluation context for the block. +-- +-- The output of payload validation is the payload itself. The BlockPayloadHash +-- is the Merkle root of this structure. +-- +data Payload = Payload + -- Provenance data: + -- (must be consistent with the evaluation context) + { _payloadChainwebVersion :: !ChainwebVersionCode + -- ^ The chainweb version of the block + , _payloadChainId :: !ChainId + -- ^ The chain of the block + , _payloadBlockHeight :: !BlockHeight + -- ^ The height of the block. + , _payloadMinerReward :: !MinerReward + -- ^ The miner reward for the block in Stu. + + -- Miner data: + , _payloadRedeemChain :: !ChainId + -- ^ The unique chain on which the payload can redeemed. + -- If the chain does not (yet) exist, the miner reward is inaccessible. + , _payloadRedeemAccount :: !Account + -- ^ A key that enables the payout of the miner reward on the target + -- chain. The format of the key depends on '_payloadRedeemChain'. + + -- Synthetic field + , _payloadHash :: {- Lazy -} BlockPayloadHash + -- ^ Merkle root for this payload. It is used as BlockPayloadHash + -- in the block. + } + deriving (Show, Generic) + +instance HasChainwebVersion Payload where + _chainwebVersion = lookupVersionByCode . _payloadChainwebVersion + +instance HasChainId Payload where + _chainId = _payloadChainId + +-- -------------------------------------------------------------------------- -- + +instance Hashable Payload where + hashWithSalt s = hashWithSalt s . _payloadHash + {-# INLINEABLE hashWithSalt #-} + +instance Eq Payload where + (==) = (==) `on` _payloadHash + +instance Ord Payload where + compare = compare `on` (\h -> (_payloadBlockHeight h, _payloadHash h)) + +instance IsCasValue Payload where + type CasKeyType Payload = RankedBlockPayloadHash + casKey p = RankedBlockPayloadHash (_payloadBlockHeight p) (_payloadHash p) + {-# INLINE casKey #-} + +type PayloadCas tbl = Cas tbl Payload + +-- -------------------------------------------------------------------------- -- +-- Internal Block Hash Computation + +-- | Compute the hash of a payload. +-- +-- Does not force '_payloadHash' in the input +-- +computeHash :: Payload -> BlockPayloadHash +computeHash h = BlockPayloadHash $ MerkleLogHash $ computeMerkleLogRoot h +{-# INLINE computeHash #-} + +-- -------------------------------------------------------------------------- -- +-- Merkle Proofs +-- +-- Example: +-- +-- ghci> Just p = proof @BlockHeight pld +-- ghci> runProof p == blockPayloadHash pld +-- True +-- ghci> proofSubject @_ @_ @BlockHeight p +-- BlockHeight 6373 + +-- | Creates a Merkle proof for a header property of a Payload. +-- +proof + :: forall a m + . MonadThrow m + => a ~ ChainwebMerkleHashAlgorithm + -- => HasHeader a ChainwebHashTag c (MkLogType a ChainwebHashTag Payload) + => Payload + -> m (MerkleProof a) +proof = headerProof @Payload +{-# INLINE proof #-} + +-- | Runs a proof. Returns the BlockPayloadHash of the payload for which +-- inclusion is proven. +-- +runProof :: MerkleProof ChainwebMerkleHashAlgorithm -> BlockPayloadHash +runProof = BlockPayloadHash . MerkleLogHash . runMerkleProof +{-# INLINE runProof #-} + +-- -------------------------------------------------------------------------- -- +-- JSON Serialization + +-- | JSON properties +-- +-- The JSON serialization also includes the block hash. +-- +properties :: KeyValue e kv => Payload -> [kv] +properties o = + [ "chainwebVersion" .= _payloadChainwebVersion o + , "chainId" .= _payloadChainId o + , "blockHeight" .= _payloadBlockHeight o + , "minerReward" .= _payloadMinerReward o + , "redeemChain" .= _payloadRedeemChain o + , "redeemAccount" .= _payloadRedeemAccount o + , "hash" .= _payloadHash o + ] +{-# INLINE properties #-} +{-# SPECIALIZE properties :: Payload -> [Series] #-} +{-# SPECIALIZE properties :: Payload -> [Pair] #-} + +instance ToJSON Payload where + toEncoding = pairs . mconcat . properties + toJSON = object . properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON Payload where + parseJSON v = do + pld <- flip (withObject "Payload") v $ \o -> Payload + <$> o .: "chainwebVersion" + <*> o .: "chainId" + <*> o .: "blockHeight" + <*> o .: "minerReward" + <*> o .: "redeemChain" + <*> o .: "redeemAccount" + <*> pure (error "Chainweb.PayloadProvider.Minimal: _payloadHash") + return pld + { _payloadHash = computeHash pld + } + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Serialization + +-- | Serialization +-- +-- (this does not include the payload hash) +-- +encodePayload :: HasCallStack => Payload -> Put +encodePayload pld = do + encodeChainwebVersionCode (_payloadChainwebVersion pld) + encodeChainId (_payloadChainId pld) + encodeBlockHeight (_payloadBlockHeight pld) + encodeMinerReward (_payloadMinerReward pld) + encodeChainId (_payloadRedeemChain pld) + encodeAccount (_payloadRedeemAccount pld) + +decodePayload :: HasCallStack => Get Payload +decodePayload = do + pld <- Payload + <$> decodeChainwebVersionCode + <*> decodeChainId + <*> decodeBlockHeight + <*> decodeMinerReward + <*> decodeChainId + <*> decodeAccount + <*> pure (error "Chainweb.PayloadProvider.Minimal.decodePayload: attempt decode hash") + return pld { _payloadHash = computeHash pld } + +-- -------------------------------------------------------------------------- -- + +genesisPayload + :: HasCallStack + => HasChainwebVersion v + => HasChainId cid + => v + -> cid + -> Payload +genesisPayload v cid + | payloadProviderTypeForChain v cid /= MinimalProvider = + error "Chainweb.PayloadProvider.Minimal.Payload.genesisPayload: chain does not use minimal provider" + | otherwise = pld + where + genHeight = genesisBlockHeight (_chainwebVersion v) (_chainId cid) + pld = Payload + { _payloadChainwebVersion = _versionCode (_chainwebVersion v) + , _payloadChainId = _chainId cid + , _payloadBlockHeight = genHeight + , _payloadMinerReward = MinerReward 0 + -- genesis blocks are not mined, hence there is no reward. + , _payloadRedeemChain = _chainId cid + -- There is nothing to redeem in a genesis block. + , _payloadRedeemAccount = Account mempty + -- There is nothing to redeem in a genesis block. Even if this would + -- be valid Account on some chain, the redeem chain is fixed to be + -- the chain of the block itself and the reward is 0. + , _payloadHash = computeHash pld + } + +newPayload + :: HasCallStack + => HasChainwebVersion v + => HasChainId cid + => v + -> cid + -> BlockHeight + -> MinerReward + -> ChainId + -> Account + -> Payload +newPayload v c h r rc acc = pld + where + pld = Payload + { _payloadChainwebVersion = _versionCode (_chainwebVersion v) + , _payloadChainId = _chainId c + , _payloadBlockHeight = h + , _payloadMinerReward = r + , _payloadRedeemChain = rc + , _payloadRedeemAccount = acc + , _payloadHash = computeHash pld + } + +-- -------------------------------------------------------------------------- -- +-- MerkleLog Entry + +instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Payload + where + type Tag @ChainwebHashTag Payload = MinimalPayloadTag + toMerkleNode = encodeMerkleInputNode encodePayload + fromMerkleNode = decodeMerkleInputNode decodePayload + {-# INLINE toMerkleNode #-} + {-# INLINE fromMerkleNode #-} + +-- -------------------------------------------------------------------------- -- +-- MerkleLog Instance + +instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag Payload where + + -- /IMPORTANT/ a types must occur at most once in this list + type MerkleLogHeader Payload = '[Payload] + type MerkleLogBody Payload = Void + + toLog h = newMerkleLog @ChainwebMerkleHashAlgorithm entries + where + entries = h :+: emptyBody + + fromLog l = pld + { _payloadHash = computeHash pld + } + where + (pld :+: _) = _merkleLogEntries l + +-- -------------------------------------------------------------------------- -- +-- Getter + +payloadMinerReward :: Getter Payload MinerReward +payloadMinerReward = to _payloadMinerReward + +payloadChainwebVersion :: Getter Payload ChainwebVersionCode +payloadChainwebVersion = to _payloadChainwebVersion + +payloadChainId :: Getter Payload ChainId +payloadChainId = to _payloadChainId + +payloadBlockHeight :: Getter Payload BlockHeight +payloadBlockHeight = to _payloadBlockHeight + +payloadRedeemChain :: Getter Payload ChainId +payloadRedeemChain = to _payloadRedeemChain + +payloadRedeemAccount :: Getter Payload Account +payloadRedeemAccount = to _payloadRedeemAccount + +payloadHash :: Getter Payload BlockPayloadHash +payloadHash = to _payloadHash + diff --git a/src/Chainweb/PayloadProvider/Minimal/PayloadDB.hs b/src/Chainweb/PayloadProvider/Minimal/PayloadDB.hs new file mode 100644 index 0000000000..f5930f4d25 --- /dev/null +++ b/src/Chainweb/PayloadProvider/Minimal/PayloadDB.hs @@ -0,0 +1,269 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeSynonymInstances #-} + +-- | +-- Module: Chainweb.PayloadProvider.Minimal.PayloadDB +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.Minimal.PayloadDB +( Configuration +, configuration +, PayloadDb +, initPayloadDb +, closePayloadDb +, withPayloadDb +, insertPayloadDb +, type RankedPayloadCas +) where + +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.MerkleUniverse +import Chainweb.PayloadProvider.Minimal.Payload +import Chainweb.Storage.Table +import Chainweb.Storage.Table.RocksDB +import Chainweb.Utils hiding (Codec) +import Chainweb.Utils.Serialization +import Chainweb.Version +import Control.DeepSeq (NFData) +import Control.Lens (view) +import Control.Monad.Catch +import Data.Aeson +import Data.ByteString qualified as B +import Data.Hashable (Hashable) +import Data.Text.Encoding qualified as T +import GHC.Generics (Generic) +import Numeric.Additive +import GHC.Stack + +-- -------------------------------------------------------------------------- -- +-- Exceptions + +type PayloadNotFoundException = PayloadNotFoundException_ ChainwebMerkleHashAlgorithm + +newtype PayloadNotFoundException_ a = PayloadNotFoundException (BlockPayloadHash_ a) + deriving (Show, Eq, Ord, Generic) + deriving anyclass (NFData, Hashable) + +instance Exception PayloadNotFoundException + +-- -------------------------------------------------------------------------- -- +-- | Configuration of the payload DB for the minimal payload provider +-- + +data Configuration = Configuration + { _configChainwebVersion' :: !ChainwebVersion + , _configChainId' :: !ChainId + , _configRocksDb' :: !RocksDb + } + +_configRocksDb :: Configuration -> RocksDb +_configRocksDb = _configRocksDb' + +configuration + :: HasCallStack + => HasChainwebVersion v + => HasChainId c + => v + -> c + -> RocksDb + -> Configuration +configuration v c rdb + | payloadProviderTypeForChain v c /= MinimalProvider = + error "Chainweb.PayloadProvider.Minimal.PayloadDB.configuration: chain does not use minimal provider" + | otherwise = Configuration + { _configChainwebVersion' = _chainwebVersion v + , _configChainId' = _chainId c + , _configRocksDb' = rdb + } + +instance HasChainwebVersion Configuration where + _chainwebVersion = _configChainwebVersion' + +instance HasChainId Configuration where + _chainId = _configChainId' + +-- -------------------------------------------------------------------------- -- +-- Ranked Block Payload (only used internally) + +newtype RankedPayload = RankedPayload { _getRankedPayload :: Payload } + deriving (Show, Generic) + deriving newtype (Eq, Ord, Hashable, ToJSON, FromJSON) + +instance IsCasValue RankedPayload where + type CasKeyType RankedPayload = RankedBlockPayloadHash + casKey (RankedPayload bh) + = RankedBlockPayloadHash (view payloadBlockHeight bh) (view payloadHash bh) + {-# INLINE casKey #-} + +type RankedPayloadCas tbl = Cas tbl RankedPayload + +-- -------------------------------------------------------------------------- -- +-- BlockRank (only used internally) + +newtype BlockRank = BlockRank { _getBlockRank :: BlockHeight } + deriving (Show, Generic) + deriving anyclass (NFData) + deriving newtype + ( Eq, Ord, Hashable, ToJSON, FromJSON + , AdditiveSemigroup, AdditiveAbelianSemigroup, AdditiveMonoid + , Num, Integral, Real, Enum + ) + +-- -------------------------------------------------------------------------- -- +-- Internal + +encodeRankedPayload :: RankedPayload -> B.ByteString +encodeRankedPayload = runPutS . encodePayload . _getRankedPayload +{-# INLINE encodeRankedPayload #-} + +decodeRankedPayload :: MonadThrow m => B.ByteString -> m RankedPayload +decodeRankedPayload bs = RankedPayload <$> runGetS decodePayload bs +{-# INLINE decodeRankedPayload #-} + +-- -------------------------------------------------------------------------- -- +-- Payload DB + +type PayloadDb tbl = PayloadDb_ ChainwebMerkleHashAlgorithm tbl + +data PayloadDb_ a tbl = PayloadDb + { _payloadDbChainId' :: !ChainId + , _payloadDbChainwebVersion' :: !ChainwebVersion + , _payloadDbTable' :: !(tbl RankedBlockPayloadHash RankedPayload) + } + +_payloadDbTable :: PayloadDb_ a tbl -> tbl RankedBlockPayloadHash RankedPayload +_payloadDbTable = _payloadDbTable' + +instance HasChainId (PayloadDb_ a tbl) where + _chainId = _payloadDbChainId' + {-# INLINE _chainId #-} + +instance HasChainwebVersion (PayloadDb_ a tbl) where + _chainwebVersion = _payloadDbChainwebVersion' + {-# INLINE _chainwebVersion #-} + +instance ReadableTable1 tbl => ReadableTable (PayloadDb_ a tbl) RankedBlockPayloadHash Payload where + tableLookup db k = fmap _getRankedPayload <$> tableLookup (_payloadDbTable db) k + {-# INLINE tableLookup #-} + +instance Table1 tbl => Table (PayloadDb_ a tbl) RankedBlockPayloadHash Payload where + tableInsert db k v = tableInsert (_payloadDbTable db) k (RankedPayload v) + tableDelete db k = tableDelete @_ @_ @RankedPayload (_payloadDbTable db) k + {-# INLINE tableInsert #-} + {-# INLINE tableDelete #-} + +-- -------------------------------------------------------------------------- -- +-- Initialization + +-- | Initialize a database handle +-- +initPayloadDb :: Configuration -> IO (PayloadDb_ a RocksDbTable) +initPayloadDb config = do + -- Add genesis payload + dbAddChecked db (genesisPayload config config) + return db + where + cid = _chainId config + cidNs = T.encodeUtf8 (toText cid) + + payloadTable = newTable + (_configRocksDb config) + (Chainweb.Storage.Table.RocksDB.Codec encodeRankedPayload decodeRankedPayload) + (Codec (runPutS . encodeRankedBlockPayloadHash) (runGetS decodeRankedBlockPayloadHash)) + ["MinimalProvider", cidNs, "payload"] + + !db = PayloadDb + { _payloadDbChainId' = cid + , _payloadDbChainwebVersion' = _chainwebVersion config + , _payloadDbTable' = payloadTable + } + +-- | Close a database handle and release all resources +-- +closePayloadDb :: PayloadDb_ a tbl -> IO () +closePayloadDb _ = return () + +withPayloadDb + :: RocksDb + -> ChainwebVersion + -> ChainId + -> (PayloadDb_ a RocksDbTable -> IO b) + -> IO b +withPayloadDb db v cid = bracket start closePayloadDb + where + start = initPayloadDb $ configuration v cid db + +-- -------------------------------------------------------------------------- -- +-- Validated Payload + +-- NOTE: the constructor of this type is intentionally NOT exported. Value of +-- this type must be only created via functions from this module. +-- +newtype ValidatedPayload = ValidatedPayload Payload + deriving (Show, Eq, Generic) + +_validatedPayload :: ValidatedPayload -> Payload +_validatedPayload (ValidatedPayload h) = h +{-# INLINE _validatedPayload #-} + +-- -------------------------------------------------------------------------- -- +-- Insertions + +insertPayloadDb :: Table1 tbl => PayloadDb_ a tbl -> ValidatedPayload -> IO () +insertPayloadDb db = dbAddChecked db . _validatedPayload +{-# INLINE insertPayloadDb #-} + +-- unsafeInsertPayloadDb :: Table1 tbl => PayloadDb_ a tbl -> Payload -> IO () +-- unsafeInsertPayloadDb = dbAddChecked +-- {-# INLINE unsafeInsertPayloadDb #-} + +-- -------------------------------------------------------------------------- -- +-- Insert +-- +-- Only functions in this section are allowed to modify values of the Db type. + +-- | A a new entry to the database +-- +-- Updates all indices. +-- +dbAddChecked + :: Table1 tbl -- Cas (tbl RankedBlockPayloadHash Payload) Payload + => PayloadDb_ a tbl + -> Payload + -> IO () +dbAddChecked db e = + unlessM (tableMember db ek) dbAddCheckedInternal + where + ek = RankedBlockPayloadHash + (view payloadBlockHeight e) + (view payloadHash e) + + -- Internal helper methods + + -- TODO add validation and check that parent exists (except for height 0). + + -- | Unchecked addition + -- + -- ASSUMES that + -- + -- * Item is not yet in database + -- + dbAddCheckedInternal = casInsert (_payloadDbTable db) (RankedPayload e) + From 55c878add78cd5be00dc799081feb17c5738e83a Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Jan 2025 11:57:38 -0800 Subject: [PATCH 061/378] wip-5 (5c71825e1): src/Chainweb/Chainweb.hs --- chainweb.cabal | 1 + src/Chainweb/Chainweb.hs | 541 +++++++++--------- .../Mempool/InMem/ValidatingConfig.hs | 99 ++++ 3 files changed, 378 insertions(+), 263 deletions(-) create mode 100644 src/Chainweb/Mempool/InMem/ValidatingConfig.hs diff --git a/chainweb.cabal b/chainweb.cabal index f7d6eb71e6..0f7dc78967 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -197,6 +197,7 @@ library , Chainweb.Mempool.Consensus , Chainweb.Mempool.CurrentTxs , Chainweb.Mempool.InMem + , Chainweb.Mempool.InMem.ValidatingConfig , Chainweb.Mempool.InMemTypes , Chainweb.Mempool.Mempool , Chainweb.Mempool.P2pConfig diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index daf133f173..5951f08af0 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -60,7 +60,6 @@ module Chainweb.Chainweb , chainwebSocket , chainwebPeer , chainwebPayloadProviders -, chainwebPactData , chainwebThrottler , chainwebPutPeerThrottler , chainwebMempoolThrottler @@ -107,10 +106,9 @@ import Control.Concurrent.MVar (MVar, readMVar) import Control.DeepSeq import Control.Lens hiding ((.=), (<.>)) import Control.Monad -import Control.Monad.Catch (fromException) +import Control.Monad.Catch (fromException, MonadThrow (throwM)) import Data.Foldable -import Data.Function (on) import qualified Data.HashMap.Strict as HM import Data.List (isPrefixOf, sortBy) import qualified Data.List as L @@ -138,7 +136,6 @@ import System.LogLevel -- internal modules import Chainweb.Backup -import Chainweb.BlockHeader import Chainweb.BlockHeaderDB (BlockHeaderDb) import Chainweb.ChainId import Chainweb.Chainweb.ChainResources @@ -173,6 +170,7 @@ import Chainweb.Version import Chainweb.Version.Guards import Chainweb.WebBlockHeaderDB import Chainweb.WebPactExecutionService +import Chainweb.Mempool.InMem.ValidatingConfig import Chainweb.Storage.Table.RocksDB @@ -185,21 +183,28 @@ import P2P.Peer import qualified Pact.Types.ChainMeta as P import qualified Pact.Types.Command as P import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.Minimal +import Chainweb.RestAPI.Utils (SomeServer) +import Control.Exception + +import Chainweb.Sync.WebBlockHeaderStore +import Chainweb.BlockHeader +import qualified Data.HashSet as HS -- -------------------------------------------------------------------------- -- -- Chainweb Resources -data Chainweb logger tbl = Chainweb +data Chainweb logger = Chainweb { _chainwebHostAddress :: !HostAddress , _chainwebChains :: !(HM.HashMap ChainId (ChainResources logger)) - , _chainwebCutResources :: !(CutResources logger) + , _chainwebCutResources :: !CutResources , _chainwebMiner :: !(Maybe (MinerResources logger)) , _chainwebCoordinator :: !(Maybe (MiningCoordination logger)) , _chainwebLogger :: !logger , _chainwebPeer :: !(PeerResources logger) , _chainwebPayloadProviders :: !PayloadProviders , _chainwebManager :: !HTTP.Manager - , _chainwebPactData :: ![(ChainId, PactServerData logger tbl)] + -- , _chainwebPactData :: ![(ChainId, PactServerData logger tbl)] , _chainwebThrottler :: !(Throttle Address) , _chainwebPutPeerThrottler :: !(Throttle Address) , _chainwebMempoolThrottler :: !(Throttle Address) @@ -210,10 +215,10 @@ data Chainweb logger tbl = Chainweb makeLenses ''Chainweb -chainwebSocket :: Getter (Chainweb logger t) Socket +chainwebSocket :: Getter (Chainweb logger) Socket chainwebSocket = chainwebPeer . peerResSocket -instance HasChainwebVersion (Chainweb logger t) where +instance HasChainwebVersion (Chainweb logger) where _chainwebVersion = _chainwebVersion . _chainwebCutResources {-# INLINE _chainwebVersion #-} @@ -231,15 +236,15 @@ withChainweb -> (StartedChainweb logger -> IO ()) -> IO () withChainweb c logger rocksDb pactDbDir backupDir resetDb inner = - withPeerResources v (view configP2p confWithBootstraps) logger $ \logger' peer -> + withPeerResources v (_configP2p confWithBootstraps) logger $ \logger' peerRes -> withSocket serviceApiPort serviceApiHost $ \serviceSock -> do let conf' = confWithBootstraps - & set configP2p (_peerResConfig peer) + & set configP2p (_peerResConfig peerRes) & set (configServiceApi . serviceApiConfigPort) (fst serviceSock) withChainwebInternal conf' logger' - peer + peerRes serviceSock rocksDb pactDbDir @@ -259,82 +264,11 @@ withChainweb c logger rocksDb pactDbDir backupDir resetDb inner = | otherwise = configP2p . p2pConfigKnownPeers %~ (\x -> L.nub $ x <> _versionBootstraps v) $ c --- TODO: The type InMempoolConfig contains parameters that should be --- configurable as well as parameters that are determined by the chainweb --- version or the chainweb protocol. These should be separated in to two --- different types. - -validatingMempoolConfig - :: ChainId - -> ChainwebVersion - -> Mempool.GasLimit - -> Mempool.GasPrice - -> MVar PactExecutionService - -> Mempool.InMemConfig Pact4.UnparsedTransaction -validatingMempoolConfig cid v gl gp mv = Mempool.InMemConfig - { Mempool._inmemTxCfg = txcfg - , Mempool._inmemTxBlockSizeLimit = gl - , Mempool._inmemTxMinGasPrice = gp - , Mempool._inmemMaxRecentItems = maxRecentLog - , Mempool._inmemPreInsertPureChecks = preInsertSingle - , Mempool._inmemPreInsertBatchChecks = preInsertBatch - , Mempool._inmemCurrentTxsSize = currentTxsSize - } - where - txcfg = Mempool.pact4TransactionConfig - -- The mempool doesn't provide a chain context to the codec which means - -- that the latest version of the parser is used. - - maxRecentLog = 2048 - - currentTxsSize = 1024 * 1024 -- ~16MB per mempool - -- 1M items is is sufficient for supporing about 12 TPS per chain, which - -- is about 360 tx per block. Larger TPS values would result in false - -- negatives in the set. - - -- | Validation: Is this TX associated with the correct `ChainId`? - -- - preInsertSingle :: Pact4.UnparsedTransaction -> Either Mempool.InsertError Pact4.UnparsedTransaction - preInsertSingle tx = do - let !pay = Pact4.payloadObj . P._cmdPayload $ tx - pcid = P._pmChainId $ P._pMeta pay - sigs = P._cmdSigs tx - ver = P._pNetworkId pay - if | not $ assertParseChainId pcid -> Left $ Mempool.InsertErrorOther "Unparsable ChainId" - | not $ assertChainId cid pcid -> Left Mempool.InsertErrorMetadataMismatch - | not $ assertSigSize sigs -> Left $ Mempool.InsertErrorOther "Too many signatures" - | not $ assertNetworkId v ver -> Left Mempool.InsertErrorMetadataMismatch - | otherwise -> Right tx - - -- | Validation: All checks that should occur before a TX is inserted into - -- the mempool. A rejection at this stage means that something is - -- fundamentally wrong/illegal with the TX, and that it should be rejected - -- completely and not gossiped to other peers. - -- - -- We expect this to be called in two places: once when a new Pact - -- Transaction is submitted via the @send@ endpoint, and once when a new TX - -- is gossiped to us from a peer's mempool. - -- - preInsertBatch - :: V.Vector (T2 Mempool.TransactionHash Pact4.UnparsedTransaction) - -> IO (V.Vector (Either (T2 Mempool.TransactionHash Mempool.InsertError) - (T2 Mempool.TransactionHash Pact4.UnparsedTransaction))) - preInsertBatch txs - | V.null txs = return V.empty - | otherwise = do - pex <- readMVar mv - rs <- _pactPreInsertCheck pex cid (V.map ssnd txs) - pure $ alignWithV f rs txs - where - f (These r (T2 h t)) = case r of - Just e -> Left (T2 h e) - Nothing -> Right (T2 h t) - f (That (T2 h _)) = Left (T2 h $ Mempool.InsertErrorOther "preInsertBatch: align mismatch 0") - f (This _) = Left (T2 (Mempool.TransactionHash "") (Mempool.InsertErrorOther "preInsertBatch: align mismatch 1")) - - data StartedChainweb logger where - StartedChainweb :: (CanReadablePayloadCas cas, Logger logger) => !(Chainweb logger cas) -> StartedChainweb logger + StartedChainweb + :: (Logger logger) + => !(Chainweb logger) + -> StartedChainweb logger Replayed :: !Cut -> !(Maybe Cut) -> StartedChainweb logger data ChainwebStatus @@ -365,7 +299,7 @@ withChainwebInternal -> Bool -> (StartedChainweb logger -> IO ()) -> IO () -withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir resetDb inner = do +withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir resetDb inner = do unless (_configOnlySyncPact conf || _configReadOnlyReplay conf) $ initializePayloadDb v payloadDb @@ -399,7 +333,9 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re concurrentWith -- initialize chains concurrently (\cid x -> do - let mcfg = validatingMempoolConfig cid v (_configBlockGasLimit conf) (_configMinGasPrice conf) + -- let mcfg = validatingMempoolConfig cid v (_configBlockGasLimit conf) (_configMinGasPrice conf) + + -- FIXME: shouldn't this be done in a configuration validation? -- NOTE: the gas limit may be set based on block height in future, so this approach may not be valid. let maxGasLimit = fromIntegral <$> maxBlockGasLimit v maxBound case maxGasLimit of @@ -410,16 +346,20 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re , "maximum for this chain; the maximum will be used instead" ] _ -> return () + + -- Initialize all chain resources, including payload providers withChainResources + (chainLogger cid) v cid rocksDb - (chainLogger cid) - mcfg - payloadDb + (_peerResManager peerRes) pactDbDir (pactConfig maxGasLimit) - txFailuresCounter + (_peerResConfig peerRes) + myInfo + peerDb + defaultMinimalProviderConfig x ) @@ -430,6 +370,32 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re ) cidsList where + v = _configChainwebVersion conf + + cids :: HS.HashSet ChainId + cids = chainIds v + + cidsList :: [ChainId] + cidsList = toList cids + + mgr :: HTTP.Manager + mgr = _peerResManager peerRes + + payloadDb :: PayloadDb RocksDbTable + payloadDb = newPayloadDb rocksDb + + peer :: Peer + peer = _peerResPeer peerRes + + myInfo :: PeerInfo + myInfo = _peerInfo peer + + peerDb :: PeerDb + peerDb = _peerResDb peerRes + + p2pConfig :: P2pConfiguration + p2pConfig = _peerResConfig peerRes + pactConfig maxGasLimit = PactServiceConfig { _pactReorgLimit = _configReorgLimit conf , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf @@ -437,8 +403,8 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re , _pactResetDb = resetDb , _pactAllowReadsInLocal = _configAllowReadsInLocal conf , _pactUnlimitedInitialRewind = - isJust (_cutDbParamsInitialHeightLimit cutConfig) || - isJust (_cutDbParamsInitialCutFile cutConfig) + isJust (_cutDbParamsInitialHeightLimit cutDbParams) || + isJust (_cutDbParamsInitialCutFile cutDbParams) , _pactNewBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) , _pactLogGas = _configLogGas conf , _pactModuleCacheLimit = _configModuleCacheLimit conf @@ -453,41 +419,63 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re , _pactMiner = Nothing } + -- FIXME: make this configurable + cutDbParams :: CutDbParams + cutDbParams = (defaultCutDbParams v $ _cutFetchTimeout cutConf) + { _cutDbParamsLogLevel = Info + , _cutDbParamsTelemetryLevel = Info + , _cutDbParamsInitialHeightLimit = _cutInitialBlockHeightLimit cutConf + , _cutDbParamsFastForwardHeightLimit = _cutFastForwardBlockHeightLimit cutConf + , _cutDbParamsReadOnly = _configOnlySyncPact conf || _configReadOnlyReplay conf + } + where + cutConf = _configCuts conf + + -- Logger + + backupLogger :: logger + backupLogger = addLabel ("component", "backup") logger + pruningLogger :: T.Text -> logger pruningLogger l = addLabel ("sub-component", l) $ setComponent "database-pruning" logger - cidsList :: [ChainId] - cidsList = toList cids - - payloadDb :: PayloadDb RocksDbTable - payloadDb = newPayloadDb rocksDb - - chainLogger :: ChainId -> logger - chainLogger cid = addLabel ("chain", toText cid) logger + chainLogger :: HasChainId c => c -> logger + chainLogger cid = addLabel ("chain", toText (_chainId cid)) logger initLogger :: logger initLogger = setComponent "init" logger + providerLogger :: HasChainId p => HasPayloadProviderType p => p -> logger + providerLogger p = addLabel ("provider", toText (_payloadProviderType p)) + $ chainLogger p + logg :: LogFunctionText logg = logFunctionText initLogger + chainLogg :: HasChainId c => c -> LogFunctionText + chainLogg = logFunctionText . chainLogger + + providerLogg :: HasChainId p => HasPayloadProviderType p => p -> LogFunctionText + providerLogg = logFunctionText . providerLogger + -- Initialize global resources + -- TODO: Can this be moved to a top-level function or broken down a bit to + -- avoid excessive indentation? + global :: HM.HashMap ChainId (ChainResources logger) -> IO () global cs = do let !webchain = mkWebBlockHeaderDb v (HM.map _chainResBlockHeaderDb cs) - -- FIXME FIXME FIXME -- !pact = mkWebPactExecutionService (HM.map _chainResPact cs) - providers = error "Chainweb.Chainweb.withChainwebInternal.global: provider initialization not yet implemented" + !providers = payloadProvidersForAllChains cs !cutLogger = setComponent "cut" logger - !mgr = _peerResManager peer logg Debug "start initializing cut resources" logFunctionJson logger Info InitializingCutResources - withCutResources cutConfig peer cutLogger rocksDb webchain providers mgr $ \cuts -> do + withCutResources cutLogger cutDbParams p2pConfig myInfo peerDb rocksDb webchain providers mgr $ \cuts -> do logg Debug "finished initializing cut resources" let !mLogger = setComponent "miner" logger @@ -516,142 +504,158 @@ withChainwebInternal conf logger peer serviceSock rocksDb pactDbDir backupDir re _ -> cs if _configReadOnlyReplay conf - then do - logFunctionJson logger Info PactReplayInProgress - -- note that we don't use the "initial cut" from cutdb because its height depends on initialBlockHeightLimit. - highestCut <- - unsafeMkCut v <$> readHighestCutHeaders v (logFunctionText logger) webchain (cutHashesTable rocksDb) - lowerBoundCut <- - tryLimitCut webchain (fromMaybe 0 $ _cutInitialBlockHeightLimit $ _configCuts conf) highestCut - upperBoundCut <- forM (_cutFastForwardBlockHeightLimit $ _configCuts conf) $ \upperBound -> - tryLimitCut webchain upperBound highestCut - let - replayOneChain :: (ChainResources logger, (BlockHeader, Maybe BlockHeader)) -> IO () - replayOneChain (cr, (l, u)) = do - let chainPact = _chainResPact cr - let logCr = logFunctionText - $ addLabel ("component", "pact") - $ addLabel ("sub-component", "init") - $ _chainResLogger cr - void $ _pactReadOnlyReplay chainPact l u - logCr Info "pact db synchronized" - let bounds = - HM.intersectionWith (,) - pactSyncChains - (HM.mapWithKey - (\cid bh -> - (bh, (HM.! cid) . _cutMap <$> upperBoundCut)) - (_cutMap lowerBoundCut) - ) - mapConcurrently_ replayOneChain bounds - logg Info "finished fast forward replay" - logFunctionJson logger Info PactReplaySuccessful - inner $ Replayed lowerBoundCut upperBoundCut - else if _configOnlySyncPact conf - then do - initialCut <- _cut mCutDb - logg Info "start synchronizing Pact DBs to initial cut" - logFunctionJson logger Info InitialSyncInProgress - synchronizePactDb pactSyncChains initialCut - logg Info "finished synchronizing Pact DBs to initial cut" - logFunctionJson logger Info PactReplayInProgress - logg Info "start replaying Pact DBs to fast forward cut" - fastForwardCutDb mCutDb - newCut <- _cut mCutDb - synchronizePactDb pactSyncChains newCut - logg Info "finished replaying Pact DBs to fast forward cut" - logFunctionJson logger Info PactReplaySuccessful - inner $ Replayed initialCut (Just newCut) - else do - initialCut <- _cut mCutDb - logg Info "start synchronizing Pact DBs to initial cut" - logFunctionJson logger Info InitialSyncInProgress - synchronizePactDb pactSyncChains initialCut - logg Info "finished synchronizing Pact DBs to initial cut" - withPactData cs cuts $ \pactData -> do - logg Debug "start initializing miner resources" - logFunctionJson logger Info InitializingMinerResources - - withMiningCoordination mLogger mConf mCutDb $ \mc -> - - -- Miner resources are used by the test-miner when in-node - -- mining is configured or by the mempool noop-miner (which - -- keeps the mempool updated) in production setups. - -- - withMinerResources mLogger (_miningInNode mConf) cs mCutDb mc $ \m -> do - logFunctionJson logger Info ChainwebStarted - logg Debug "finished initializing miner resources" - let !haddr = _peerConfigAddr $ _p2pConfigPeer $ _configP2p conf - inner $ StartedChainweb Chainweb - { _chainwebHostAddress = haddr - , _chainwebChains = cs - , _chainwebCutResources = cuts - , _chainwebMiner = m - , _chainwebCoordinator = mc - , _chainwebLogger = logger - , _chainwebPeer = peer - , _chainwebPayloadProviders = providers - , _chainwebManager = mgr - , _chainwebPactData = pactData - , _chainwebThrottler = throttler - , _chainwebPutPeerThrottler = putPeerThrottler - , _chainwebMempoolThrottler = mempoolThrottler - , _chainwebConfig = conf - , _chainwebServiceSocket = serviceSock - , _chainwebBackup = BackupEnv - { _backupRocksDb = rocksDb - , _backupDir = backupDir - , _backupPactDbDir = pactDbDir - , _backupChainIds = cids - , _backupLogger = backupLogger + then do + -- FIXME implement replay in payload provider + error "Chainweb.Chainweb.withChainwebInternal: pact replay is not supported" + -- logFunctionJson logger Info PactReplayInProgress + -- -- note that we don't use the "initial cut" from cutdb because its height depends on initialBlockHeightLimit. + -- highestCut <- + -- unsafeMkCut v <$> readHighestCutHeaders v (logFunctionText logger) webchain (cutHashesTable rocksDb) + -- lowerBoundCut <- + -- tryLimitCut webchain (fromMaybe 0 $ _cutInitialBlockHeightLimit $ _configCuts conf) highestCut + -- upperBoundCut <- forM (_cutFastForwardBlockHeightLimit $ _configCuts conf) $ \upperBound -> + -- tryLimitCut webchain upperBound highestCut + -- let + -- replayOneChain :: (ChainResources logger, (BlockHeader, Maybe BlockHeader)) -> IO () + -- replayOneChain (cr, (l, u)) = do + -- let chainPact = _chainResPact cr + -- let logCr = logFunctionText + -- $ addLabel ("component", "pact") + -- $ addLabel ("sub-component", "init") + -- $ _chainResLogger cr + -- void $ _pactReadOnlyReplay chainPact l u + -- logCr Info "pact db synchronized" + -- let bounds = + -- HM.intersectionWith (,) + -- pactSyncChains + -- (HM.mapWithKey + -- (\cid bh -> + -- (bh, (HM.! cid) . _cutMap <$> upperBoundCut)) + -- (_cutMap lowerBoundCut) + -- ) + -- mapConcurrently_ replayOneChain bounds + -- logg Info "finished fast forward replay" + -- logFunctionJson logger Info PactReplaySuccessful + -- inner $ Replayed lowerBoundCut upperBoundCut + else + if _configOnlySyncPact conf + then do + error "Chainweb.Chainweb.withChainwebInternal: pact replay is not supported" + -- initialCut <- _cut mCutDb + -- logg Info "start synchronizing Pact DBs to initial cut" + -- logFunctionJson logger Info InitialSyncInProgress + -- synchronizePactDb pactSyncChains initialCut + -- logg Info "finished synchronizing Pact DBs to initial cut" + -- logFunctionJson logger Info PactReplayInProgress + -- logg Info "start replaying Pact DBs to fast forward cut" + -- fastForwardCutDb mCutDb + -- newCut <- _cut mCutDb + -- synchronizePactDb pactSyncChains newCut + -- logg Info "finished replaying Pact DBs to fast forward cut" + -- logFunctionJson logger Info PactReplaySuccessful + -- inner $ Replayed initialCut (Just newCut) + else do + initialCut <- _cut mCutDb + + -- synchronize payload providers. this also initializes + -- mining. + synchronizeProviders webchain providers initialCut + + -- FIXME: synchronize all payload providers + -- logg Info "start synchronizing Pact DBs to initial cut" + -- logFunctionJson logger Info InitialSyncInProgress + -- synchronizePactDb pactSyncChains initialCut + -- logg Info "finished synchronizing Pact DBs to initial cut" + + + -- withPactData cs cuts $ \pactData -> do + do + logg Debug "start initializing miner resources" + logFunctionJson logger Info InitializingMinerResources + + withMiningCoordination mLogger mConf mCutDb $ \mc -> + + -- Miner resources are used by the test-miner when in-node + -- mining is configured or by the mempool noop-miner (which + -- keeps the mempool updated) in production setups. + -- + withMinerResources mLogger (_miningInNode mConf) cs mCutDb mc $ \m -> do + logFunctionJson logger Info ChainwebStarted + logg Debug "finished initializing miner resources" + let !haddr = _peerConfigAddr $ _p2pConfigPeer $ _configP2p conf + inner $ StartedChainweb Chainweb + { _chainwebHostAddress = haddr + , _chainwebChains = cs + , _chainwebCutResources = cuts + , _chainwebMiner = m + , _chainwebCoordinator = mc + , _chainwebLogger = logger + , _chainwebPeer = peerRes + , _chainwebPayloadProviders = providers + , _chainwebManager = mgr + -- , _chainwebPactData = pactData + , _chainwebThrottler = throttler + , _chainwebPutPeerThrottler = putPeerThrottler + , _chainwebMempoolThrottler = mempoolThrottler + , _chainwebConfig = conf + , _chainwebServiceSocket = serviceSock + , _chainwebBackup = BackupEnv + { _backupRocksDb = rocksDb + , _backupDir = backupDir + , _backupPactDbDir = pactDbDir + , _backupChainIds = cids + , _backupLogger = backupLogger + } } - } - - withPactData - :: HM.HashMap ChainId (ChainResources logger) - -> CutResources logger - -> ([(ChainId, PactServerData logger tbl)] -> IO b) - -> IO b - withPactData cs cuts m = do - let l = sortBy (compare `on` fst) (HM.toList cs) - m $ l <&> fmap (\cr -> PactServerData - { _pactServerDataCutDb = _cutResCutDb cuts - , _pactServerDataMempool = _chainResMempool cr - , _pactServerDataLogger = _chainResLogger cr - , _pactServerDataPact = _chainResPact cr - , _pactServerDataPayloadDb = _chainResPayloadDb cr - }) - - v = _configChainwebVersion conf - cids = chainIds v - backupLogger = addLabel ("component", "backup") logger - -- FIXME: make this configurable - cutConfig :: CutDbParams - cutConfig = (defaultCutDbParams v $ _cutFetchTimeout cutConf) - { _cutDbParamsLogLevel = Info - , _cutDbParamsTelemetryLevel = Info - , _cutDbParamsInitialHeightLimit = _cutInitialBlockHeightLimit cutConf - , _cutDbParamsFastForwardHeightLimit = _cutFastForwardBlockHeightLimit cutConf - , _cutDbParamsReadOnly = _configOnlySyncPact conf || _configReadOnlyReplay conf - } - where - cutConf = _configCuts conf - - synchronizePactDb :: HM.HashMap ChainId (ChainResources logger) -> Cut -> IO () - synchronizePactDb cs targetCut = do - mapConcurrently_ syncOne $ - HM.intersectionWith (,) (_cutMap targetCut) cs + synchronizeProviders :: WebBlockHeaderDb -> PayloadProviders -> Cut -> IO () + synchronizeProviders wbh providers c = do + mapConcurrently_ syncOne (_cutHeaders c) where - syncOne :: (BlockHeader, ChainResources logger) -> IO () - syncOne (bh, cr) = do - let pact = _chainResPact cr - let logCr = logFunctionText - $ addLabel ("component", "pact") - $ addLabel ("sub-component", "init") - $ _chainResLogger cr - void $ _pactSyncToBlock pact bh - logCr Debug "pact db synchronized" + syncOne hdr = withPayloadProvider providers hdr $ \provider -> do + providerLogg provider Info $ + "sync payload provider to " + <> sshow (view blockHeight hdr) + <> ":" <> sshow (view blockHash hdr) + finfo <- forkInfoForHeader wbh hdr Nothing + providerLogg provider Debug $ "syncToBlock with fork info " <> sshow finfo + r <- syncToBlock provider Nothing finfo `catch` \(e :: SomeException) -> do + providerLogg provider Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e + throwM e + unless (r == _forkInfoTargetState finfo) $ do + providerLogg provider Error $ "unexpectedResult" + error "Chainweb.Chainweb.synchronizeProviders: unexpected result state" + -- FIXME + + -- synchronizePactDb :: HM.HashMap ChainId (ChainResources logger) -> Cut -> IO () + -- synchronizePactDb cs targetCut = do + -- mapConcurrently_ syncOne $ + -- HM.intersectionWith (,) (_cutMap targetCut) cs + -- where + -- syncOne :: (BlockHeader, ChainResources logger) -> IO () + -- syncOne (bh, cr) = do + -- let pact = _chainResPact cr + -- let logCr = logFunctionText + -- $ addLabel ("component", "pact") + -- $ addLabel ("sub-component", "init") + -- $ _chainResLogger cr + -- void $ _pactSyncToBlock pact bh + -- logCr Debug "pact db synchronized" + + -- withPactData + -- :: HM.HashMap ChainId (ChainResources logger) + -- -> CutResources + -- -> ([(ChainId, PactServerData logger tbl)] -> IO b) + -- -> IO b + -- withPactData cs cuts m = do + -- let l = sortBy (compare `on` fst) (HM.toList cs) + -- m $ l <&> fmap (\cr -> PactServerData + -- { _pactServerDataCutDb = _cutResCutDb cuts + -- , _pactServerDataMempool = _chainResMempool cr + -- , _pactServerDataLogger = _chainResLogger cr + -- , _pactServerDataPact = _chainResPact cr + -- , _pactServerDataPayloadDb = _chainResPayloadDb cr + -- }) -- -------------------------------------------------------------------------- -- -- Throttling @@ -704,14 +708,15 @@ makeLenses ''NowServing -- | Starts server and runs all network clients -- runChainweb - :: forall logger tbl + :: forall logger . Logger logger - => CanReadablePayloadCas tbl - => Chainweb logger tbl + => Chainweb logger -> ((NowServing -> NowServing) -> IO ()) -> IO () runChainweb cw nowServing = do logg Debug "start chainweb node" + + -- Create OpenAPI Validation Middlewars mkValidationMiddleware <- interleaveIO $ OpenAPIValidation.mkValidationMiddleware (_chainwebLogger cw) (_chainwebVersion cw) (_chainwebManager cw) p2pValidationMiddleware <- @@ -729,7 +734,7 @@ runChainweb cw nowServing = do concurrentlies_ - -- 1. Start serving Rest API + -- 1. Start serving P2P Rest API [ (if tls then serve else servePlain) $ httpLog . throttle (_chainwebPutPeerThrottler cw) @@ -741,7 +746,7 @@ runChainweb cw nowServing = do -- 2. Start Clients (with a delay of 500ms) , threadDelay 500000 >> clients - -- 3. Start serving local API + -- 3. Start serving local service API , threadDelay 500000 >> do serveServiceApi $ serviceHttpLog @@ -758,7 +763,8 @@ runChainweb cw nowServing = do mpClients <- mempoolSyncClients concurrentlies_ $ concat [ miner - , cutNetworks mgr (_chainwebCutResources cw) + , cutNetworks (_chainwebCutResources cw) + , runP2pNodesOfAllChains chainVals , mpClients ] @@ -776,22 +782,30 @@ runChainweb cw nowServing = do proj :: forall a . (ChainResources logger -> a) -> [(ChainId, a)] proj f = chains & mapped . _2 %~ f + peerDb = _peerResDb (_chainwebPeer cw) + + -- FIXME export the SomeServer instead of DBs? + -- I.e. the handler would be created in the chain resource. + -- chainDbsToServe :: [(ChainId, BlockHeaderDb)] chainDbsToServe = proj _chainResBlockHeaderDb mempoolsToServe :: [(ChainId, Mempool.MempoolBackend Pact4.UnparsedTransaction)] - mempoolsToServe = proj _chainResMempool + -- mempoolsToServe = proj _chainResMempool + mempoolsToServe = [] - peerDb = _peerResDb (_chainwebPeer cw) + pactDbsToServe :: [(ChainId, PactServerData logger tbl)] + -- pactDbsToServe = _chainwebPactData cw + pactDbsToServe = [] - memP2pToServe :: [(NetworkId, PeerDb)] - memP2pToServe = (\(i, _) -> (MempoolNetwork i, peerDb)) <$> chains + memP2pPeersToServe :: [(NetworkId, PeerDb)] + -- memP2pToServe = (\(i, _) -> (MempoolNetwork i, peerDb)) <$> chains + memP2pPeersToServe = [] - payloadDbsToServe :: [(ChainId, PayloadDb tbl)] - payloadDbsToServe = itoList (view chainwebPayloadDb cw <$ _chainwebChains cw) - - pactDbsToServe :: [(ChainId, PactServerData logger tbl)] - pactDbsToServe = _chainwebPactData cw + -- TODO use the peerDbs from the respective chains + -- (even though those are currently all the same) + payloadP2pPeersToServe :: [(NetworkId, PeerDb)] + payloadP2pPeersToServe = (\(i, _) -> (ChainNetwork i, peerDb)) <$> chains loggServerError msg (Just r) e = "HTTP server error (" <> msg <> "): " <> sshow e <> ". Request: " <> sshow r @@ -840,8 +854,9 @@ runChainweb cw nowServing = do { _chainwebServerCutDb = Just cutDb , _chainwebServerBlockHeaderDbs = chainDbsToServe , _chainwebServerMempools = mempoolsToServe - , _chainwebServerPayloadDbs = payloadDbsToServe - , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) : memP2pToServe + , _chainwebServerPayloads = payloadsToServeOnP2pApi chains + , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) + : memP2pPeersToServe <> payloadP2pPeersToServe } mw) (monitorConnectionsClosedByClient clientClosedConnectionsCounter) @@ -859,8 +874,8 @@ runChainweb cw nowServing = do { _chainwebServerCutDb = Just cutDb , _chainwebServerBlockHeaderDbs = chainDbsToServe , _chainwebServerMempools = mempoolsToServe - , _chainwebServerPayloadDbs = payloadDbsToServe - , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) : memP2pToServe + , _chainwebServerPayloads = payloadsToServeOnP2pApi chains + , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) : memP2pPeersToServe } mw) (monitorConnectionsClosedByClient clientClosedConnectionsCounter) @@ -917,10 +932,9 @@ runChainweb cw nowServing = do { _chainwebServerCutDb = Just cutDb , _chainwebServerBlockHeaderDbs = chainDbsToServe , _chainwebServerMempools = mempoolsToServe - , _chainwebServerPayloadDbs = payloadDbsToServe - , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) : memP2pToServe + , _chainwebServerPayloads = payloadsToServeOnServiceApi chains + , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) : memP2pPeersToServe } - pactDbsToServe (_chainwebCoordinator cw) (HeaderStream . _configHeaderStream $ _chainwebConfig cw) (_chainwebBackup cw <$ guard backupApiEnabled) @@ -938,7 +952,7 @@ runChainweb cw nowServing = do cutDb = _cutResCutDb $ _chainwebCutResources cw cutPeerDb :: PeerDb - cutPeerDb = _peerResDb $ _cutResPeer $ _chainwebCutResources cw + cutPeerDb = _cutResPeerDb $ _chainwebCutResources cw miner :: [IO ()] miner = maybe [] (\m -> [ runMiner (_chainwebVersion cw) m ]) $ _chainwebMiner cw @@ -968,3 +982,4 @@ runChainweb cw nowServing = do enabled conf = do logg Info "Mempool p2p sync enabled" return $ map (runMempoolSyncClient mgr conf (_chainwebPeer cw)) chainVals + diff --git a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs new file mode 100644 index 0000000000..179634c82f --- /dev/null +++ b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs @@ -0,0 +1,99 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE OverloadedStrings #-} + +-- | +-- Module: Chainweb.Mempool.InMem.ValidatingConfig +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.Mempool.InMem.ValidatingConfig +( validatingMempoolConfig +) where + +import Chainweb.ChainId +import Chainweb.Mempool.InMemTypes +import Chainweb.Mempool.Mempool +import Chainweb.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact4.Validations +import Chainweb.Utils +import Chainweb.Version +import Chainweb.WebPactExecutionService +import Control.Concurrent +import Data.These +import Data.Vector qualified as V +import Pact.Types.ChainMeta +import Pact.Types.Command + +validatingMempoolConfig + :: ChainId + -> ChainwebVersion + -> GasLimit + -> GasPrice + -> MVar PactExecutionService + -> InMemConfig Pact4.UnparsedTransaction +validatingMempoolConfig cid v gl gp mv = InMemConfig + { _inmemTxCfg = txcfg + , _inmemTxBlockSizeLimit = gl + , _inmemTxMinGasPrice = gp + , _inmemMaxRecentItems = maxRecentLog + , _inmemPreInsertPureChecks = preInsertSingle + , _inmemPreInsertBatchChecks = preInsertBatch + , _inmemCurrentTxsSize = currentTxsSize + } + where + txcfg = pact4TransactionConfig + -- The mempool doesn't provide a chain context to the codec which means + -- that the latest version of the parser is used. + + maxRecentLog = 2048 + + currentTxsSize = 1024 * 1024 -- ~16MB per mempool + -- 1M items is is sufficient for supporing about 12 TPS per chain, which + -- is about 360 tx per block. Larger TPS values would result in false + -- negatives in the set. + + -- | Validation: Is this TX associated with the correct `ChainId`? + -- + preInsertSingle :: Pact4.UnparsedTransaction -> Either InsertError Pact4.UnparsedTransaction + preInsertSingle tx = do + let !pay = Pact4.payloadObj . _cmdPayload $ tx + pcid = _pmChainId $ _pMeta pay + sigs = _cmdSigs tx + ver = _pNetworkId pay + if | not $ assertParseChainId pcid -> Left $ InsertErrorOther "Unparsable ChainId" + | not $ assertChainId cid pcid -> Left InsertErrorMetadataMismatch + | not $ assertSigSize sigs -> Left $ InsertErrorOther "Too many signatures" + | not $ assertNetworkId v ver -> Left InsertErrorMetadataMismatch + | otherwise -> Right tx + + -- | Validation: All checks that should occur before a TX is inserted into + -- the mempool. A rejection at this stage means that something is + -- fundamentally wrong/illegal with the TX, and that it should be rejected + -- completely and not gossiped to other peers. + -- + -- We expect this to be called in two places: once when a new Pact + -- Transaction is submitted via the @send@ endpoint, and once when a new TX + -- is gossiped to us from a peer's mempool. + -- + preInsertBatch + :: V.Vector (T2 TransactionHash Pact4.UnparsedTransaction) + -> IO (V.Vector (Either (T2 TransactionHash InsertError) + (T2 TransactionHash Pact4.UnparsedTransaction))) + preInsertBatch txs + | V.null txs = return V.empty + | otherwise = do + pex <- readMVar mv + rs <- _pactPreInsertCheck pex cid (V.map ssnd txs) + pure $ alignWithV f rs txs + where + f (These r (T2 h t)) = case r of + Just e -> Left (T2 h e) + Nothing -> Right (T2 h t) + f (That (T2 h _)) = Left (T2 h $ InsertErrorOther "preInsertBatch: align mismatch 0") + f (This _) = Left (T2 (TransactionHash "") (InsertErrorOther "preInsertBatch: align mismatch 1")) + + From 146b99e19a713922d67d6c39b33ef2f044b49315 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Jan 2025 11:57:39 -0800 Subject: [PATCH 062/378] wip-5 (5c71825e1): src/Chainweb/Chainweb/ChainResources.hs --- src/Chainweb/Chainweb/ChainResources.hs | 418 ++++++++++++++++++++---- 1 file changed, 361 insertions(+), 57 deletions(-) diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 913fdd3083..01007e56f9 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -1,14 +1,19 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ViewPatterns #-} -- | -- Module: Chainweb.Chainweb.ChainResources @@ -22,52 +27,261 @@ module Chainweb.Chainweb.ChainResources ( ChainResources(..) , chainResBlockHeaderDb -, chainResMempool , chainResLogger -, chainResPact +, chainResPayloadProvider , withChainResources -) where +, payloadsToServeOnP2pApi +, payloadsToServeOnServiceApi +, payloadProvidersForAllChains +, runP2pNodesOfAllChains -import Control.Concurrent.MVar -import Control.Lens hiding ((.=), (<.>)) - -import Data.Maybe +-- * Payload Provider +, ProviderResources(..) +, withPayloadProviderResources +, providerResPayloadProvider +, providerResServiceApi +, providerResP2pApiResources +-- * Payload Provider P2P Resources +, PayloadP2pResources(..) +, payloadP2pResources +, runPayloadP2pNodes -import Prelude hiding (log) - --- internal modules +-- * Payload Provider Service API Resources +, PayloadServiceApiResources(..) +, payloadServiceApiResources +) where import Chainweb.BlockHeaderDB +import Chainweb.BlockPayloadHash import Chainweb.ChainId +import Chainweb.Chainweb.Configuration (ServiceApiConfig(_serviceApiPayloadBatchLimit)) import Chainweb.Logger -import qualified Chainweb.Mempool.Consensus as MPCon -import qualified Chainweb.Mempool.InMem as Mempool -import qualified Chainweb.Mempool.InMemTypes as Mempool -import Chainweb.Mempool.Mempool (MempoolBackend) -import Chainweb.Pact.Service.PactInProcApi import Chainweb.Pact.Types -import Chainweb.Payload.PayloadStore -import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.Minimal +import Chainweb.PayloadProvider.P2P.RestAPI +import Chainweb.PayloadProvider.P2P.RestAPI.Server +import Chainweb.RestAPI.NetworkID +import Chainweb.RestAPI.Utils +import Chainweb.Storage.Table +import Chainweb.Storage.Table.RocksDB +import Chainweb.Utils import Chainweb.Version -import Chainweb.WebPactExecutionService +import Control.Lens hiding ((.=), (<.>)) +import Data.Maybe +import Data.PQueue (PQueue) +import Data.Text qualified as T +import Network.HTTP.Client qualified as HTTP +import P2P.Node +import P2P.Node.Configuration +import P2P.Node.PeerDB (PeerDb) +import P2P.Peer (PeerInfo) +import P2P.Session +import P2P.TaskQueue +import Prelude hiding (log) +import Data.HashMap.Strict qualified as HM +import Data.Foldable +import Data.Singletons + +-- -------------------------------------------------------------------------- -- +-- Payload P2P Network Resources +-- +-- The following is the default implementation that works for the current +-- providers. It is not necessarily true that all payload providers use this. + +-- | Payload P2P network resources. +-- +-- This includes both client and server components. +-- +data PayloadP2pResources = PayloadP2pResources + { _payloadResPeerDb :: !PeerDb + -- ^ The respective Peer resources for the payload P2P network + , _payloadResP2pNode :: !P2pNode + -- ^ The P2P Network for fetching payloads + -- + -- The network doesn't restrict the API network endpoints that are used + -- in the client sessions. + , _payloadResP2pApi :: !SomeApi + -- ^ API endpoints that are included in the node P2P API + , _payloadResP2pServer :: !SomeServer + -- ^ API endpoints that are are served by the node P2P API + } + +instance HasChainwebVersion PayloadP2pResources where + _chainwebVersion = _chainwebVersion . _payloadResPeerDb + +payloadP2pResources + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) logger tbl + . Logger logger + => ReadableTable tbl RankedBlockPayloadHash (PayloadType p) + => KnownChainwebVersionSymbol v + => KnownChainIdSymbol c + => IsPayloadProvider p + => logger + -> P2pConfiguration + -> PeerInfo + -> PeerDb + -> tbl + -- Payload Table + -> PQueue (Task ClientEnv (PayloadType p)) + -- ^ task queue for scheduling tasks with the task server + -> HTTP.Manager + -> IO PayloadP2pResources +payloadP2pResources logger p2pConfig myInfo peerDb tbl queue mgr = do + p2pNode <- mkP2pNode False "payload" (session 10 queue) + -- FIXME add a node for regularly synchronizing peers + return $ PayloadP2pResources + { _payloadResPeerDb = peerDb + , _payloadResP2pNode = p2pNode + , _payloadResP2pApi = SomeApi (payloadApi @v @c @p) + , _payloadResP2pServer = somePayloadServer @_ @v @c @p 20 tbl + } + where + p2pLogger = addLabel ("sub-component", "p2p") logger + + mkP2pNode :: Bool -> T.Text -> P2pSession -> IO P2pNode + mkP2pNode doPeerSync label s = p2pCreateNode $ P2pNodeParameters + { _p2pNodeParamsMyPeerInfo = myInfo + , _p2pNodeParamsSession = s + , _p2pNodeParamsSessionTimeout = _p2pConfigSessionTimeout p2pConfig + , _p2pNodeParamsMaxSessionCount = _p2pConfigMaxSessionCount p2pConfig + , _p2pNodeParamsIsPrivate = _p2pConfigPrivate p2pConfig + , _p2pNodeParamsDoPeerSync = doPeerSync + , _p2pNodeParamsManager = mgr + , _p2pNodeParamsPeerDb = peerDb + , _p2pNodeParamsLogFunction = logFunction (addLabel ("session", label) p2pLogger) + , _p2pNodeParamsNetworkId = CutNetwork + } + +-- | IO actions for running Payload P2p Nodes +-- +runPayloadP2pNodes :: PayloadP2pResources -> [IO ()] +runPayloadP2pNodes r = [ p2pRunNode (_payloadResP2pNode r) ] + +-- -------------------------------------------------------------------------- -- +-- Payload Service API Resources + +data PayloadServiceApiResources = PayloadServiceApiResources + { _payloadResServiceApi :: !SomeApi + , _payloadResServiceServer :: !SomeServer + } + +payloadServiceApiResources + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) tbl + . IsPayloadProvider p + => KnownChainwebVersionSymbol v + => KnownChainIdSymbol c + => ReadableTable tbl RankedBlockPayloadHash (PayloadType p) + => ServiceApiConfig + -> tbl + -> PayloadServiceApiResources +payloadServiceApiResources config pdb = PayloadServiceApiResources + { _payloadResServiceApi = SomeApi (payloadApi @v @c @p) + , _payloadResServiceServer = somePayloadServer @_ @v @c @p batchLimit pdb + } + where + batchLimit = int $ _serviceApiPayloadBatchLimit config -import Chainweb.Storage.Table.RocksDB -import Chainweb.Counter + +-- -------------------------------------------------------------------------- -- +-- Payload Provider Resources + +-- | Payload Provider Resources +-- +data ProviderResources = ProviderResources + { _providerResPayloadProvider :: !SomePayloadProvider + , _providerResServiceApi :: !(Maybe PayloadServiceApiResources) + , _providerResP2pApiResources :: !(Maybe PayloadP2pResources) + } + +makeLenses ''ProviderResources + +instance HasChainwebVersion ProviderResources where + _chainwebVersion = _chainwebVersion . _providerResPayloadProvider + {-# INLINE _chainwebVersion #-} + +instance HasChainId ProviderResources where + _chainId = _chainId . _providerResPayloadProvider + {-# INLINE _chainId #-} + + -- FIXME + -- initialize payload store + -- payloadStore <- newWebPayloadStore mgr pact payloadDb (logFunction logger) + -- Where is this done? The queue is used by the P2p Session and + -- the Payload Provider. + +withPayloadProviderResources + :: Logger logger + => HasChainwebVersion v + => HasChainId c + => logger + -> v + -> c + -> P2pConfiguration + -> PeerInfo + -> PeerDb + -> RocksDb + -> HTTP.Manager + -> MinimalProviderConfig + -> (ProviderResources -> IO a) + -> IO a +withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr mpConfig inner = do + SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal v + SomeChainIdT @c' _ <- return $ someChainIdVal c + withSomeSing provider $ \case + SMinimalProvider -> do + + -- FIXME this should be better abstracted. + -- Should we put the api and server into the payload provider + -- itself? Would it be an issue if the payload provider would keep a + -- reference to it? + -- + -- It would allow the server to be integrated more closely with the + -- provider. + + p <- newMinimalPayloadProvider logger v c rdb mgr mpConfig + let pdb = view minimalPayloadDb p + let queue = view minimalPayloadQueue p + p2pRes <- payloadP2pResources @v' @c' @'MinimalProvider + logger p2pConfig myInfo peerDb pdb queue mgr + inner ProviderResources + { _providerResPayloadProvider = SomePayloadProvider p + , _providerResServiceApi = Nothing + , _providerResP2pApiResources = Just p2pRes + } + + SPactProvider -> + error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for Pact" + SEvmProvider @n _ -> + error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for EVM" + where + provider :: PayloadProviderType + provider = payloadProviderTypeForChain v c -- -------------------------------------------------------------------------- -- -- Single Chain Resources +-- | FIXME: What do we need to include here to support Rest API? +-- data ChainResources logger = ChainResources { _chainResBlockHeaderDb :: !BlockHeaderDb , _chainResLogger :: !logger - , _chainResMempool :: !(MempoolBackend Pact4.UnparsedTransaction) - , _chainResPact :: PactExecutionService - -- , _chainResPayloadDb :: _ + , _chainResPayloadProvider :: !ProviderResources } makeLenses ''ChainResources +_chainResP2pApiResources + :: ChainResources logger + -> Maybe PayloadP2pResources +_chainResP2pApiResources = _providerResP2pApiResources . _chainResPayloadProvider + +_chainResServiceApiResources + :: ChainResources logger + -> Maybe PayloadServiceApiResources +_chainResServiceApiResources = _providerResServiceApi . _chainResPayloadProvider + instance HasChainwebVersion (ChainResources logger) where _chainwebVersion = _chainwebVersion . _chainResBlockHeaderDb {-# INLINE _chainwebVersion #-} @@ -76,43 +290,133 @@ instance HasChainId (ChainResources logger) where _chainId = _chainId . _chainResBlockHeaderDb {-# INLINE _chainId #-} --- | Intializes all local Chain resources, but doesn't start any networking. --- withChainResources :: Logger logger - => CanReadablePayloadCas tbl - => ChainwebVersion - -> ChainId + => HasChainwebVersion v + => HasChainId c + => logger + -> v + -> c -> RocksDb - -> logger - -> (MVar PactExecutionService -> Mempool.InMemConfig Pact4.UnparsedTransaction) - -> PayloadDb tbl + -> HTTP.Manager -> FilePath - -- ^ database directory for checkpointer + -- ^ database directory for pact databases -> PactServiceConfig - -> Counter "txFailures" + -> P2pConfiguration + -> PeerInfo + -> PeerDb + -> MinimalProviderConfig + -- ^ FIXME create a a type that bundles different provider configs -> (ChainResources logger -> IO a) -> IO a -withChainResources - v cid rdb logger mempoolCfg0 payloadDb pactDbDir pactConfig txFailuresCounter inner = - withBlockHeaderDb rdb v cid $ \cdb -> do - pexMv <- newEmptyMVar - let mempoolCfg = mempoolCfg0 pexMv - Mempool.withInMemoryMempool (setComponent "mempool" logger) mempoolCfg v $ \mempool -> do - mpc <- MPCon.mkMempoolConsensus mempool cdb $ Just payloadDb - withPactService v cid logger (Just txFailuresCounter) mpc cdb - payloadDb pactDbDir pactConfig $ \requestQ -> do - let pex = pes requestQ - putMVar pexMv pex - - -- run inner - inner $ ChainResources - { _chainResBlockHeaderDb = cdb - , _chainResLogger = logger - , _chainResMempool = mempool - , _chainResPact = pex - } - where - pes requestQ - | v ^. versionCheats . disablePact = emptyPactExecutionService - | otherwise = mkPactExecutionService requestQ +withChainResources logger v c rdb mgr _pactDbDir _pConf p2pConf myInfo peerDb mConf inner = + + -- This uses the the CutNetwork for fetching block headers. + withBlockHeaderDb rdb (_chainwebVersion v) (_chainId c) $ \cdb -> do + + -- Payload Providers are using per chain payload networks for fetching + -- block headers. + withPayloadProviderResources + logger v c p2pConf myInfo peerDb rdb mgr mConf $ \provider -> do + + inner ChainResources + { _chainResBlockHeaderDb = cdb + , _chainResPayloadProvider = provider + , _chainResLogger = logger + } + +-- | Return P2P Payload Servers for all chains +-- +payloadsToServeOnP2pApi + :: [(ChainId, ChainResources logger)] + -> [(ChainId, SomeServer)] +payloadsToServeOnP2pApi chains = catMaybes + $ mapM (fmap _payloadResP2pServer . _chainResP2pApiResources) + <$> chains + +-- | Return Service API Payload Servers for all chains +-- +payloadsToServeOnServiceApi + :: [(ChainId, ChainResources logger)] + -> [(ChainId, SomeServer)] +payloadsToServeOnServiceApi chains = catMaybes + $ mapM (fmap _payloadResServiceServer . _chainResServiceApiResources) + <$> chains + +-- | Return the payload providers for all chains +-- +payloadProvidersForAllChains + :: HM.HashMap ChainId (ChainResources logger) + -> PayloadProviders +payloadProvidersForAllChains chains = PayloadProviders + $ (_providerResPayloadProvider . _chainResPayloadProvider) + <$> chains + +-- | Returns actions for running the P2P nodes for all chains. +-- +runP2pNodesOfAllChains + :: Foldable l + => l (ChainResources logger) + -> [IO ()] +runP2pNodesOfAllChains + = fmap p2pRunNode + . fmap _payloadResP2pNode + . catMaybes + . fmap _providerResP2pApiResources + . fmap _chainResPayloadProvider + . toList + +-- -------------------------------------------------------------------------- -- +-- ATTIC (mostly pact related) +-- +-- FIXME: use the following to create withPact initialization function for +-- pact chains + +-- data PactResources logger = PactResources +-- { _pactResBlockHeaderDb :: !BlockHeaderDb +-- , _pactResLogger :: !logger +-- , _pactResMempool :: !(MempoolBackend Pact4.UnparsedTransaction) +-- , _pactResPact :: PactExecutionService +-- , _pactResPayloadDb :: _ +-- } + +-- | Intializes all local Chain resources, but doesn't start any networking. +-- +-- withChainResources +-- :: Logger logger +-- => CanReadablePayloadCas tbl +-- => ChainwebVersion +-- -> ChainId +-- -> RocksDb +-- -> logger +-- -> (MVar PactExecutionService -> Mempool.InMemConfig Pact4.UnparsedTransaction) +-- -> PayloadDb tbl +-- -> FilePath +-- -- ^ database directory for checkpointer +-- -> PactServiceConfig +-- -> Counter "txFailures" +-- -> (ChainResources logger -> IO a) +-- -> IO a +-- withChainResources +-- v cid rdb logger mempoolCfg0 payloadDb pactDbDir pactConfig txFailuresCounter inner = +-- withBlockHeaderDb rdb v cid $ \cdb -> do +-- pexMv <- newEmptyMVar +-- let mempoolCfg = mempoolCfg0 pexMv +-- Mempool.withInMemoryMempool (setComponent "mempool" logger) mempoolCfg v $ \mempool -> do +-- mpc <- MPCon.mkMempoolConsensus mempool cdb $ Just payloadDb +-- withPactService v cid logger (Just txFailuresCounter) mpc cdb +-- payloadDb pactDbDir pactConfig $ \requestQ -> do +-- let pex = pes requestQ +-- putMVar pexMv pex +-- +-- -- run inner +-- inner $ ChainResources +-- { _chainResBlockHeaderDb = cdb +-- , _chainResLogger = logger +-- , _chainResMempool = mempool +-- , _chainResPact = pex +-- } +-- where +-- pes requestQ +-- | v ^. versionCheats . disablePact = emptyPactExecutionService +-- | otherwise = mkPactExecutionService requestQ From 681b16fd4417e0199c46cab269c8d849b9f49bee Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 9 Jan 2025 01:54:12 -0800 Subject: [PATCH 063/378] Switch Development version to use Minimal payload provider on all chains --- src/Chainweb/Version/Development.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/Version/Development.hs b/src/Chainweb/Version/Development.hs index 4ebcbf5201..1cdd598193 100644 --- a/src/Chainweb/Version/Development.hs +++ b/src/Chainweb/Version/Development.hs @@ -64,5 +64,5 @@ devnet = ChainwebVersion (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) , _versionQuirks = noQuirks , _versionServiceDate = Nothing - , _versionPayloadProviderTypes = AllChains PactProvider + , _versionPayloadProviderTypes = AllChains MinimalProvider } From a5a5adaae22bb27f29c68379371e160a966a4e78 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 13 Jan 2025 23:46:52 -0800 Subject: [PATCH 064/378] wip-6 (1d630fd6d): node/src/ChainwebNode.hs --- node/src/ChainwebNode.hs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 79c009ef68..bb3a5ad100 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -92,7 +92,7 @@ import Chainweb.Logging.Config import Chainweb.Logging.Miner import Chainweb.Mempool.Consensus (ReintroducedTxsLog) import Chainweb.Mempool.InMemTypes (MempoolStats(..)) -import Chainweb.Miner.Coordinator (MiningStats) +-- import Chainweb.Miner.Coordinator (MiningStats) import Chainweb.Pact.Backend.DbCache (DbCacheStats) import Chainweb.Pact.Service.PactQueue (PactQueueStats) import Chainweb.Pact.RestAPI.Server (PactCmdLog(..)) @@ -213,7 +213,7 @@ runMonitorLoop actionLabel logger = runForeverThrottled 10 -- 10 bursts in case of failure (10 * mega) -- allow restart every 10 seconds in case of failure -runCutMonitor :: Logger logger => logger -> CutDb tbl -> IO () +runCutMonitor :: Logger logger => logger -> CutDb -> IO () runCutMonitor logger db = L.withLoggerLabel ("component", "cut-monitor") logger $ \l -> runMonitorLoop "ChainwebNode.runCutMonitor" l $ do logFunctionJson l Info . cutToCutHashes Nothing @@ -244,21 +244,19 @@ instance ToJSON BlockUpdate where {-# INLINE toEncoding #-} {-# INLINE toJSON #-} -runBlockUpdateMonitor :: CanReadablePayloadCas tbl => Logger logger => logger -> CutDb tbl -> IO () +runBlockUpdateMonitor :: Logger logger => logger -> CutDb -> IO () runBlockUpdateMonitor logger db = L.withLoggerLabel ("component", "block-update-monitor") logger $ \l -> runMonitorLoop "ChainwebNode.runBlockUpdateMonitor" l $ do blockDiffStream db & S.mapM toUpdate & S.mapM_ (logFunctionJson l Info) where - payloadDb = view cutDbPayloadDb db - txCount :: BlockHeader -> IO Int - txCount bh = do - bp <- lookupPayloadDataWithHeight payloadDb (Just $ view blockHeight bh) (view blockPayloadHash bh) >>= \case - Nothing -> error "block payload not found" - Just x -> return x - return $ length $ view payloadDataTransactions bp + txCount bh = return (-1) + -- bp <- lookupPayloadDataWithHeight (Just $ view blockHeight bh) (view blockPayloadHash bh) >>= \case + -- Nothing -> error "block payload not found" + -- Just x -> return x + -- return $ length $ view payloadDataTransactions bp toUpdate :: Either BlockHeader BlockHeader -> IO BlockUpdate toUpdate (Right bh) = BlockUpdate @@ -295,7 +293,7 @@ runRtsMonitor logger = L.withLoggerLabel ("component", "rts-monitor") logger go logFunctionJson logger Info stats approximateThreadDelay 60_000_000 {- 1 minute -} -runQueueMonitor :: Logger logger => logger -> CutDb tbl -> IO () +runQueueMonitor :: Logger logger => logger -> CutDb -> IO () runQueueMonitor logger cutDb = L.withLoggerLabel ("component", "queue-monitor") logger go where go l = do @@ -409,8 +407,8 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do $ mkTelemetryLogger @NewMinedBlock mgr teleLogConfig orphanedBlockBackend <- managed $ mkTelemetryLogger @OrphanedBlock mgr teleLogConfig - miningStatsBackend <- managed - $ mkTelemetryLogger @MiningStats mgr teleLogConfig +-- miningStatsBackend <- managed +-- $ mkTelemetryLogger @MiningStats mgr teleLogConfig requestLogBackend <- managed $ mkTelemetryLogger @RequestResponseLog mgr teleLogConfig queueStatsBackend <- managed @@ -447,7 +445,7 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do , logHandler endpointBackend , logHandler newBlockBackend , logHandler orphanedBlockBackend - , logHandler miningStatsBackend + -- , logHandler miningStatsBackend , logHandler requestLogBackend , logHandler queueStatsBackend , logHandler reintroBackend From 789a0454f0169754f2d51924f50c0af2a3535e2b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 13 Jan 2025 23:46:52 -0800 Subject: [PATCH 065/378] wip-6 (1d630fd6d): src/Chainweb/Chainweb/CheckReachability.hs --- src/Chainweb/Chainweb/CheckReachability.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Chainweb/Chainweb/CheckReachability.hs b/src/Chainweb/Chainweb/CheckReachability.hs index 1480ef00f5..1c72e2afd2 100644 --- a/src/Chainweb/Chainweb/CheckReachability.hs +++ b/src/Chainweb/Chainweb/CheckReachability.hs @@ -51,6 +51,7 @@ import Chainweb.Utils import Chainweb.Version import P2P.Node.PeerDB +import P2P.Node.RestAPI.Client import P2P.Peer -- -------------------------------------------------------------------------- -- @@ -127,6 +128,10 @@ checkReachability sock mgr v logger pdb peers peer threshold = do withPeerDbServer inner = withAsync servePeerDb $ const inner + -- Is the really what we want? I think, the other side is trying to connect + -- to the cut endpoint. It probably does not matter, because only the + -- response headers are checked. + -- servePeerDb = servePeerDbSocketTls serverSettings (_peerCertificateChain peer) From 2bf1a1272363806d77b44c322e7a0a03ba7dc1fb Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 13 Jan 2025 23:46:52 -0800 Subject: [PATCH 066/378] wip-6 (1d630fd6d): src/Chainweb/Chainweb/CutResources.hs --- src/Chainweb/Chainweb/CutResources.hs | 171 +++++++------------------- 1 file changed, 45 insertions(+), 126 deletions(-) diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 994d3982df..27c0241c8a 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -21,28 +21,19 @@ -- should be kept after intialization of the node is complete. -- module Chainweb.Chainweb.CutResources -( CutSyncResources(..) -, CutResources(..) -, cutsCutDb +( CutResources(..) , withCutResources , cutNetworks ) where -import Control.Lens hiding ((.=), (<.>)) -import Control.Monad import Control.Monad.Catch -import qualified Data.Text as T - import Prelude hiding (log) import qualified Network.HTTP.Client as HTTP -import System.LogLevel - -- internal modules -import Chainweb.Chainweb.PeerResources import Chainweb.CutDB import qualified Chainweb.CutDB.Sync as C import Chainweb.Logger @@ -54,156 +45,84 @@ import Chainweb.WebBlockHeaderDB import Chainweb.Storage.Table.RocksDB import P2P.Node +import P2P.Node.Configuration import P2P.Peer -import P2P.Session import P2P.TaskQueue import Chainweb.PayloadProvider +import qualified Data.Text as T +import P2P.Session (P2pSession) +import P2P.Node.PeerDB (PeerDb) -- -------------------------------------------------------------------------- -- -- Cuts Resources -data CutSyncResources logger = CutSyncResources - { _cutResSyncSession :: !P2pSession - , _cutResSyncLogger :: !logger - } - -data CutResources logger = CutResources - { _cutResCutConfig :: !CutDbParams - , _cutResPeer :: !(PeerResources logger) +data CutResources = CutResources + { _cutResPeerDb :: !PeerDb , _cutResCutDb :: !CutDb - , _cutResLogger :: !logger - , _cutResCutSync :: !(CutSyncResources logger) - , _cutResHeaderSync :: !(CutSyncResources logger) + , _cutResCutP2pNode :: !P2pNode + -- ^ P2P Network for pushing and synchronizing cuts. + , _cutResHeaderP2pNode :: !P2pNode + -- ^ P2P Network for fetching block headers on demand via a task queue. } -makeLensesFor - [ ("_cutResCutDb", "cutsCutDb") - ] ''CutResources - -instance HasChainwebVersion (CutResources logger) where +instance HasChainwebVersion CutResources where _chainwebVersion = _chainwebVersion . _cutResCutDb {-# INLINE _chainwebVersion #-} withCutResources :: Logger logger - => CutDbParams - -> PeerResources logger - -> logger + => logger + -> CutDbParams + -> P2pConfiguration + -> PeerInfo + -> PeerDb -> RocksDb -> WebBlockHeaderDb -> PayloadProviders -> HTTP.Manager - -> (CutResources logger -> IO a) + -> (CutResources -> IO a) -> IO a -withCutResources cutDbParams peer logger rdb webchain providers mgr f = do +withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain providers mgr f = do -- initialize blockheader store headerStore <- newWebBlockHeaderStore mgr webchain (logFunction logger) - -- FIXME - -- initialize payload store - -- payloadStore <- newWebPayloadStore mgr pact payloadDb (logFunction logger) - -- initialize cutHashes store let cutHashesStore = cutHashesTable rdb - withCutDb cutDbParams (logFunction logger) headerStore providers cutHashesStore $ \cutDb -> + withCutDb cutDbParams (logFunction logger) headerStore providers cutHashesStore $ \cutDb -> do + cutP2pNode <- mkP2pNode True "cut" $ + C.syncSession myInfo cutDb + headerP2pNode <- mkP2pNode False "header" $ + session 10 (_webBlockHeaderStoreQueue headerStore) f $ CutResources - { _cutResCutConfig = cutDbParams - , _cutResPeer = peer + { _cutResPeerDb = peerDb , _cutResCutDb = cutDb - , _cutResLogger = logger - , _cutResCutSync = CutSyncResources - { _cutResSyncSession = C.syncSession v (_peerInfo $ _peerResPeer peer) cutDb - , _cutResSyncLogger = addLabel ("sync", "cut") syncLogger - } - , _cutResHeaderSync = CutSyncResources - { _cutResSyncSession = session 10 (_webBlockHeaderStoreQueue headerStore) - , _cutResSyncLogger = addLabel ("sync", "header") syncLogger - } - - -- FIXME - -- , _cutResPayloadSync = CutSyncResources - -- { _cutResSyncSession = session 10 (_webBlockPayloadStoreQueue payloadStore) - -- , _cutResSyncLogger = addLabel ("sync", "payload") syncLogger - -- } - + , _cutResCutP2pNode = cutP2pNode + , _cutResHeaderP2pNode = headerP2pNode } where - v = _chainwebVersion webchain syncLogger = addLabel ("sub-component", "sync") logger + mkP2pNode :: Bool -> T.Text -> P2pSession -> IO P2pNode + mkP2pNode doPeerSync label s = p2pCreateNode $ P2pNodeParameters + { _p2pNodeParamsMyPeerInfo = myInfo + , _p2pNodeParamsSession = s + , _p2pNodeParamsSessionTimeout = _p2pConfigSessionTimeout p2pConfig + , _p2pNodeParamsMaxSessionCount = _p2pConfigMaxSessionCount p2pConfig + , _p2pNodeParamsIsPrivate = _p2pConfigPrivate p2pConfig + , _p2pNodeParamsDoPeerSync = doPeerSync + , _p2pNodeParamsManager = mgr + , _p2pNodeParamsPeerDb = peerDb + , _p2pNodeParamsLogFunction = logFunction (addLabel ("sync", label) syncLogger) + , _p2pNodeParamsNetworkId = CutNetwork + } + -- | The networks that are used by the cut DB. -- -cutNetworks - :: Logger logger - => HTTP.Manager - -> CutResources logger - -> [IO ()] -cutNetworks mgr cuts = - [ runCutNetworkCutSync mgr cuts - , runCutNetworkHeaderSync mgr cuts --- , runCutNetworkPayloadSync mgr cuts +cutNetworks :: CutResources -> [IO ()] +cutNetworks cuts = + [ p2pRunNode (_cutResCutP2pNode cuts) + , p2pRunNode (_cutResHeaderP2pNode cuts) ] --- | P2P Network for pushing Cuts --- -runCutNetworkCutSync - :: Logger logger - => HTTP.Manager - -> CutResources logger - -> IO () -runCutNetworkCutSync mgr c - = mkCutNetworkSync mgr True c "cut sync" $ _cutResCutSync c - --- | P2P Network for Block Headers --- -runCutNetworkHeaderSync - :: Logger logger - => HTTP.Manager - -> CutResources logger - -> IO () -runCutNetworkHeaderSync mgr c - = mkCutNetworkSync mgr False c "block header sync" $ _cutResHeaderSync c - --- | P2P Network for Block Payloads --- --- runCutNetworkPayloadSync --- :: Logger logger --- => HTTP.Manager --- -> CutResources logger --- -> IO () --- runCutNetworkPayloadSync mgr c --- = mkCutNetworkSync mgr False c "block payload sync" $ _cutResPayloadSync c - --- | P2P Network for Block Payloads --- --- This uses the 'CutNetwork' for syncing peers. The network doesn't restrict --- the API network endpoints that are used in the client sessions. --- -mkCutNetworkSync - :: Logger logger - => HTTP.Manager - -> Bool - -- ^ Do peer synchronization - -> CutResources logger - -> T.Text - -> CutSyncResources logger - -> IO () -mkCutNetworkSync mgr doPeerSync cuts label cutSync = bracket create destroy $ \n -> - p2pStartNode (_peerResConfig $ _cutResPeer cuts) n - where - v = _chainwebVersion cuts - peer = _peerResPeer $ _cutResPeer cuts - logger = _cutResSyncLogger cutSync - peerDb = _peerResDb $ _cutResPeer cuts - s = _cutResSyncSession cutSync - - create = do - !n <- p2pCreateNode v CutNetwork peer (logFunction logger) peerDb mgr doPeerSync s - logFunctionText logger Debug $ label <> ": initialized" - return n - - destroy n = do - p2pStopNode n - logFunctionText logger Info $ label <> ": stopped" From 0bb9e6582cebb664a368bfd9bf8ad9898d15a71d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 13 Jan 2025 23:46:52 -0800 Subject: [PATCH 067/378] wip-6 (1d630fd6d): src/Chainweb/Chainweb/PeerResources.hs --- src/Chainweb/Chainweb/PeerResources.hs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Chainweb/PeerResources.hs b/src/Chainweb/Chainweb/PeerResources.hs index e823821fc4..4416e325a0 100644 --- a/src/Chainweb/Chainweb/PeerResources.hs +++ b/src/Chainweb/Chainweb/PeerResources.hs @@ -102,6 +102,10 @@ data PeerResources logger = PeerResources , _peerLogger :: !logger } +instance HasChainwebVersion (PeerResources logger) where + _chainwebVersion = _chainwebVersion . _peerResDb + {-# INLINE _chainwebVersion #-} + makeLenses ''PeerResources -- | Allocate Peer resources. All P2P networks of a chainweb node share a single @@ -238,7 +242,8 @@ withPeerSocket conf act = withSocket port interface $ \(p, s) -> -- Run PeerDb for a Chainweb Version startPeerDb_ :: ChainwebVersion -> P2pConfiguration -> IO PeerDb -startPeerDb_ v = startPeerDb v nids +startPeerDb_ v c = + startPeerDb v nids (_p2pConfigPrivate c) (_p2pConfigKnownPeers c) where nids = HS.singleton CutNetwork `HS.union` HS.map MempoolNetwork cids @@ -246,7 +251,7 @@ startPeerDb_ v = startPeerDb v nids cids = chainIds v withPeerDb_ :: ChainwebVersion -> P2pConfiguration -> (PeerDb -> IO a) -> IO a -withPeerDb_ v conf = bracket (startPeerDb_ v conf) (stopPeerDb conf) +withPeerDb_ v conf = bracket (startPeerDb_ v conf) stopPeerDb -- -------------------------------------------------------------------------- -- -- Connection Manager From f9e4065a834d3761aa43eecd4afe0c96b072e358 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 14 Jan 2025 01:02:37 -0800 Subject: [PATCH 068/378] Chainweb.PayloadProvider.P2P.RestAPI for Minimal --- src/Chainweb/PayloadProvider/P2P/RestAPI.hs | 113 +++++++++++++------- 1 file changed, 77 insertions(+), 36 deletions(-) diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs index 0be6c9c8ae..e87f2ba951 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs @@ -1,22 +1,25 @@ -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeApplications #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ExistentialQuantification #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE TypeOperators #-} + {-# OPTIONS_GHC -Wno-orphans #-} -{-# LANGUAGE DerivingVia #-} -{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE LambdaCase #-} -- | -- Module: Chainweb.PayloadProvider.P2P.RestAPI @@ -50,24 +53,26 @@ module Chainweb.PayloadProvider.P2P.RestAPI import Chainweb.BlockHeader import Chainweb.BlockHeaderDB.RestAPI () import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash import Chainweb.ChainId +import Chainweb.Payload qualified as Pact +import Chainweb.PayloadProvider.Minimal.Payload qualified as Minimal +import Chainweb.Ranked import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils +import Chainweb.Utils +import Chainweb.Utils.Serialization (runPutL, putWord32le, runGetEitherL, getWord32le) import Chainweb.Version +import Control.Monad import Control.Monad.Identity import Data.Aeson import Data.Kind +import Data.Maybe import Data.Proxy -import Numeric.Natural -import Servant.API - -import Chainweb.PayloadProvider.Minimal.Payload qualified as Minimal -import Chainweb.Utils.Serialization (runPutL, putWord32le) -import Chainweb.Utils -import Chainweb.Payload qualified as Pact -import Chainweb.BlockPayloadHash import GHC.Generics (Generic) -import Chainweb.Ranked +import GHC.TypeNats +import Servant.API +import Data.Singletons -- -------------------------------------------------------------------------- -- -- Constants @@ -79,6 +84,29 @@ newtype PayloadBatchLimit = PayloadBatchLimit Natural deriving newtype (Ord, Enum, Num, Real, Integral, ToJSON, FromJSON) -- -------------------------------------------------------------------------- -- +-- IsPayload Provider Class +-- +-- NOTE: +-- +-- Currently, this class is only available for provider P2P. It might be useful +-- more generally for the implementation of payload providers. However, not all +-- payload providers use payload data that is visible to the chainweb-node +-- beacon. Therefore PayloadData concepts must not leak to the ChainwebVersion +-- level and the definition of the PayloadProviderType tag. Also, the +-- 'PayloadProvider' class as well as the moduel "Chainweb.PayloadProvider" must +-- not depend on it. +-- +-- TODO: +-- +-- - Todo rename payload that is visible to the chainweb-node beacon to +-- something like "PayloadData" or "PayloadInfo" to indicate that it does not +-- represent the complete block payload +-- +-- - In the module hierachy, distinguishy more clearly between general Payload +-- Provider features, that concern all payload providers, and auxiliary +-- payload provider features, that serve as optional utils to implement +-- aspects of payload providers that are hosted in the chainweb-node beacon. +-- -- | Class of Payload Provider APIs -- @@ -219,56 +247,69 @@ payloadApi = Proxy -- | Dispatch provider specific APIs -- -- FIXME: Should we split this up and move it into the scope of the respective --- payload provider. +-- payload providers. Otherwise this module has to depend on the payload +-- providers. -- somePayloadApi :: IsPayloadProvider 'MinimalProvider => IsPayloadProvider 'PactProvider - -- => IsPayloadProvider 'EvmProvider => ChainwebVersion -> ChainId -> SomeApi somePayloadApi v c = runIdentity $ do SomeChainwebVersionT (_ :: Proxy v') <- return $ someChainwebVersionVal v SomeChainIdT (_ :: Proxy c') <- return $ someChainIdVal c - case provider of - MinimalProvider -> + withSomeSing (payloadProviderTypeForChain v c) $ \case + SMinimalProvider -> return $! SomeApi (payloadApi @v' @c' @'MinimalProvider) - PactProvider -> + SPactProvider -> return $! SomeApi (payloadApi @v' @c' @'PactProvider) - EvmProvider -> + SEvmProvider @n _ -> error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: IsPayloadProvider not implemented for EVM" - -- return $! SomeApi (payloadApi @v' @c' @'EvmProvider) - where - provider :: PayloadProviderType - provider = payloadProviderTypeForChain v c somePayloadApis :: ChainwebVersion -> [ChainId] -> SomeApi somePayloadApis v = mconcat . fmap (somePayloadApi v) -- -------------------------------------------------------------------------- -- -- Implementations of IsPayloadProvider +-- -------------------------------------------------------------------------- -- +-- | IsPayloadProvider instance for the Minimal Payload provider +-- instance IsPayloadProvider MinimalProvider where type PayloadType MinimalProvider = Minimal.Payload type PayloadBatchType MinimalProvider = [Minimal.Payload] - p2pPayloadBatchLimit = 20 -- FIXME + batch = catMaybes instance MimeRender OctetStream Minimal.Payload where mimeRender _ = runPutL . Minimal.encodePayload + {-# INLINE mimeRender #-} + +instance MimeUnrender OctetStream Minimal.Payload where + mimeUnrender _ = runGetEitherL Minimal.decodePayload + {-# INLINE mimeUnrender #-} instance MimeRender OctetStream [Minimal.Payload] where mimeRender _ ps = runPutL $ do putWord32le (int $ length ps) mapM_ Minimal.encodePayload ps --- FIXME: fix the following instance to conform with the current API (or even --- better fix the current bad binary encoding of payload data). +instance MimeUnrender OctetStream [Minimal.Payload] where + mimeUnrender _ = runGetEitherL $ do + l <- int <$> getWord32le + replicateM l Minimal.decodePayload + {-# INLINE mimeUnrender #-} +-- | IsPayloadProvider instance for the Pact Payload provider +-- +-- FIXME: fix this instance to conform with the current API (or even +-- better fix the current bad binary encoding of payload data). +-- instance IsPayloadProvider PactProvider where type PayloadType PactProvider = Pact.PayloadData type PayloadBatchType PactProvider = Pact.PayloadDataList - p2pPayloadBatchLimit = 20 -- FIXME + batch = Pact.PayloadDataList . catMaybes + From c4f49a948c54ebd338736f4a65f2e9502d9fe687 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 9 Jan 2025 12:00:57 -0800 Subject: [PATCH 069/378] wip-8 (42a4d5a06): src/Chainweb/Chainweb.hs --- src/Chainweb/Chainweb.hs | 101 +++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 5951f08af0..8d7d667520 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -7,6 +7,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NumericUnderscores #-} @@ -102,25 +103,25 @@ import Configuration.Utils hiding (Error, Lens', disabled) import Control.Concurrent (threadDelay) import Control.Concurrent.Async -import Control.Concurrent.MVar (MVar, readMVar) import Control.DeepSeq +import Control.Exception import Control.Lens hiding ((.=), (<.>)) import Control.Monad -import Control.Monad.Catch (fromException, MonadThrow (throwM)) +import Control.Monad.Catch (MonadThrow (throwM)) import Data.Foldable -import qualified Data.HashMap.Strict as HM -import Data.List (isPrefixOf, sortBy) -import qualified Data.List as L +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS +import Data.List (isPrefixOf) +import Data.List qualified as L +import Data.LogMessage (LogFunctionText) import Data.Maybe -import qualified Data.Text as T -import Data.These (These(..)) -import qualified Data.Vector as V +import Data.Text qualified as T import GHC.Generics -import qualified Network.HTTP.Client as HTTP -import qualified Network.HTTP2.Client as HTTP2 +import Network.HTTP.Client qualified as HTTP +import Network.HTTP2.Client qualified as HTTP2 import Network.Socket (Socket) import Network.Wai import Network.Wai.Handler.Warp hiding (Port) @@ -136,12 +137,12 @@ import System.LogLevel -- internal modules import Chainweb.Backup +import Chainweb.BlockHeader import Chainweb.BlockHeaderDB (BlockHeaderDb) import Chainweb.ChainId import Chainweb.Chainweb.ChainResources import Chainweb.Chainweb.Configuration import Chainweb.Chainweb.CutResources -import Chainweb.Chainweb.MempoolSyncClient import Chainweb.Chainweb.MinerResources import Chainweb.Chainweb.PeerResources import Chainweb.Chainweb.PruneChainDatabase @@ -150,47 +151,32 @@ import Chainweb.Cut import Chainweb.CutDB import Chainweb.HostAddress import Chainweb.Logger -import qualified Chainweb.Mempool.InMemTypes as Mempool -import qualified Chainweb.Mempool.Mempool as Mempool +import Chainweb.Mempool.InMem.ValidatingConfig +import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Config -import qualified Chainweb.OpenAPIValidation as OpenAPIValidation +import Chainweb.OpenAPIValidation qualified as OpenAPIValidation import Chainweb.Pact.Backend.Types(IntraBlockPersistence(..)) import Chainweb.Pact.RestAPI.Server (PactServerData(..)) import Chainweb.Pact.Types (PactServiceConfig(..)) -import Chainweb.Pact4.Validations +import Chainweb.Pact4.Transaction qualified as Pact4 import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.PayloadProvider import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID -import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Storage.Table.RocksDB +import Chainweb.Sync.WebBlockHeaderStore import Chainweb.Utils import Chainweb.Utils.RequestLog import Chainweb.Version import Chainweb.Version.Guards import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService -import Chainweb.Mempool.InMem.ValidatingConfig - -import Chainweb.Storage.Table.RocksDB - -import Data.LogMessage (LogFunctionText) import P2P.Node.Configuration import P2P.Node.PeerDB (PeerDb) import P2P.Peer -import qualified Pact.Types.ChainMeta as P -import qualified Pact.Types.Command as P -import Chainweb.PayloadProvider -import Chainweb.PayloadProvider.Minimal -import Chainweb.RestAPI.Utils (SomeServer) -import Control.Exception - -import Chainweb.Sync.WebBlockHeaderStore -import Chainweb.BlockHeader -import qualified Data.HashSet as HS - -- -------------------------------------------------------------------------- -- -- Chainweb Resources @@ -301,9 +287,6 @@ withChainwebInternal -> IO () withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir resetDb inner = do - unless (_configOnlySyncPact conf || _configReadOnlyReplay conf) $ - initializePayloadDb v payloadDb - -- Garbage Collection -- performed before PayloadDb and BlockHeaderDb used by other components logFunctionJson logger Info PruningDatabases @@ -453,9 +436,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir logg :: LogFunctionText logg = logFunctionText initLogger - chainLogg :: HasChainId c => c -> LogFunctionText - chainLogg = logFunctionText . chainLogger - providerLogg :: HasChainId p => HasPayloadProviderType p => p -> LogFunctionText providerLogg = logFunctionText . providerLogger @@ -786,6 +766,7 @@ runChainweb cw nowServing = do -- FIXME export the SomeServer instead of DBs? -- I.e. the handler would be created in the chain resource. + -- Similar to how it is done with the payload provider APIs. -- chainDbsToServe :: [(ChainId, BlockHeaderDb)] chainDbsToServe = proj _chainResBlockHeaderDb @@ -840,8 +821,21 @@ runChainweb cw nowServing = do logFunctionCounter (_chainwebLogger cw) Info . (:[]) =<< roll clientClosedConnectionsCounter + chainwebServerDbs :: ChainwebServerDbs Pact4.UnparsedTransaction + chainwebServerDbs = ChainwebServerDbs + { _chainwebServerCutDb = Just cutDb + , _chainwebServerBlockHeaderDbs = chainDbsToServe + , _chainwebServerMempools = mempoolsToServe + , _chainwebServerPayloads = payloadsToServeOnP2pApi chains + , _chainwebServerPeerDbs + = (CutNetwork, cutPeerDb) + : memP2pPeersToServe + <> payloadP2pPeersToServe + } + serve :: Middleware -> IO () serve mw = do + clientClosedConnectionsCounter <- newCounter concurrently_ (serveChainwebSocketTls @@ -850,14 +844,7 @@ runChainweb cw nowServing = do (_peerKey $ _peerResPeer $ _chainwebPeer cw) (_peerResSocket $ _chainwebPeer cw) (_chainwebConfig cw) - ChainwebServerDbs - { _chainwebServerCutDb = Just cutDb - , _chainwebServerBlockHeaderDbs = chainDbsToServe - , _chainwebServerMempools = mempoolsToServe - , _chainwebServerPayloads = payloadsToServeOnP2pApi chains - , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) - : memP2pPeersToServe <> payloadP2pPeersToServe - } + chainwebServerDbs mw) (monitorConnectionsClosedByClient clientClosedConnectionsCounter) @@ -870,14 +857,9 @@ runChainweb cw nowServing = do (serverSettings clientClosedConnectionsCounter) (_peerResSocket $ _chainwebPeer cw) (_chainwebConfig cw) - ChainwebServerDbs - { _chainwebServerCutDb = Just cutDb - , _chainwebServerBlockHeaderDbs = chainDbsToServe - , _chainwebServerMempools = mempoolsToServe - , _chainwebServerPayloads = payloadsToServeOnP2pApi chains - , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) : memP2pPeersToServe - } - mw) + chainwebServerDbs + mw + ) (monitorConnectionsClosedByClient clientClosedConnectionsCounter) -- Request size limit for the service API @@ -933,7 +915,10 @@ runChainweb cw nowServing = do , _chainwebServerBlockHeaderDbs = chainDbsToServe , _chainwebServerMempools = mempoolsToServe , _chainwebServerPayloads = payloadsToServeOnServiceApi chains - , _chainwebServerPeerDbs = (CutNetwork, cutPeerDb) : memP2pPeersToServe + + -- We do not want to serve peer APIs on the service API. + -- If at all we could serve the GET endpoints. + , _chainwebServerPeerDbs = [] } (_chainwebCoordinator cw) (HeaderStream . _configHeaderStream $ _chainwebConfig cw) @@ -981,5 +966,7 @@ runChainweb cw nowServing = do return [] enabled conf = do logg Info "Mempool p2p sync enabled" - return $ map (runMempoolSyncClient mgr conf (_chainwebPeer cw)) chainVals + -- return $ map (runMempoolSyncClient mgr conf (_chainwebPeer cw)) chainVals + logg Warn "Overwriting mempool p2p sync client configuration. It is currently not supported" + return [] From c9b538a9d0312d2dff1cd06e4a7d4619b96ad5a3 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 9 Jan 2025 11:42:53 -0800 Subject: [PATCH 070/378] WIP test-utils --- test/lib/Chainweb/Test/Utils.hs | 61 +++++++++++++++------------------ 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index 2600a34b8c..a49f4a2951 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -576,12 +576,11 @@ starBlockHeaderDbs n dbs = do testHost :: String testHost = "localhost" -data TestClientEnv t tbl = TestClientEnv +data TestClientEnv t = TestClientEnv { _envClientEnv :: !ClientEnv - , _envCutDb :: !(Maybe (CutDb tbl)) + , _envCutDb :: !(Maybe CutDb) , _envBlockHeaderDbs :: ![(ChainId, BlockHeaderDb)] , _envMempools :: ![(ChainId, MempoolBackend t)] - , _envPayloadDbs :: ![(ChainId, PayloadDb tbl)] , _envPeerDbs :: ![(NetworkId, P2P.PeerDb)] , _envVersion :: !ChainwebVersion } @@ -590,26 +589,25 @@ pattern BlockHeaderDbsTestClientEnv :: ClientEnv -> [(ChainId, BlockHeaderDb)] -> ChainwebVersion - -> TestClientEnv t tbl + -> TestClientEnv t pattern BlockHeaderDbsTestClientEnv { _cdbEnvClientEnv, _cdbEnvBlockHeaderDbs, _cdbEnvVersion } - = TestClientEnv _cdbEnvClientEnv Nothing _cdbEnvBlockHeaderDbs [] [] [] _cdbEnvVersion + = TestClientEnv _cdbEnvClientEnv Nothing _cdbEnvBlockHeaderDbs [] [] _cdbEnvVersion pattern PeerDbsTestClientEnv :: ClientEnv -> [(NetworkId, P2P.PeerDb)] -> ChainwebVersion - -> TestClientEnv t tbl + -> TestClientEnv t pattern PeerDbsTestClientEnv { _pdbEnvClientEnv, _pdbEnvPeerDbs, _pdbEnvVersion } - = TestClientEnv _pdbEnvClientEnv Nothing [] [] [] _pdbEnvPeerDbs _pdbEnvVersion + = TestClientEnv _pdbEnvClientEnv Nothing [] [] _pdbEnvPeerDbs _pdbEnvVersion pattern PayloadTestClientEnv :: ClientEnv - -> CutDb tbl - -> [(ChainId, PayloadDb tbl)] + -> CutDb -> ChainwebVersion - -> TestClientEnv t tbl -pattern PayloadTestClientEnv { _pEnvClientEnv, _pEnvCutDb, _pEnvPayloadDbs, _eEnvVersion } - = TestClientEnv _pEnvClientEnv (Just _pEnvCutDb) [] [] _pEnvPayloadDbs [] _eEnvVersion + -> TestClientEnv t +pattern PayloadTestClientEnv { _pEnvClientEnv, _pEnvCutDb, _eEnvVersion } + = TestClientEnv _pEnvClientEnv (Just _pEnvCutDb) [] [] [] _eEnvVersion withTestAppServer :: Bool @@ -691,16 +689,15 @@ withChainwebTestServer shouldValidateSpec tls v app = close sock clientEnvWithChainwebTestServer - :: forall t tbl + :: forall t . Show t => ToJSON t => FromJSON t - => CanReadablePayloadCas tbl => ShouldValidateSpec -> Bool -> ChainwebVersion - -> ChainwebServerDbs t tbl - -> ResourceT IO (TestClientEnv t tbl) + -> ChainwebServerDbs t + -> ResourceT IO (TestClientEnv t) clientEnvWithChainwebTestServer shouldValidateSpec tls v dbs = do -- FIXME: Hashes API got removed from the P2P API. We use an application that -- includes this API for testing. We should create comprehensive tests for the @@ -717,20 +714,18 @@ clientEnvWithChainwebTestServer shouldValidateSpec tls v dbs = do (_chainwebServerCutDb dbs) (_chainwebServerBlockHeaderDbs dbs) (_chainwebServerMempools dbs) - (_chainwebServerPayloadDbs dbs) (_chainwebServerPeerDbs dbs) v withPeerDbsServer :: Show t - => CanReadablePayloadCas tbl => ToJSON t => FromJSON t => ShouldValidateSpec -> Bool -> ChainwebVersion -> [(NetworkId, P2P.PeerDb)] - -> ResourceT IO (TestClientEnv t tbl) + -> ResourceT IO (TestClientEnv t) withPeerDbsServer shouldValidateSpec tls v peerDbs = clientEnvWithChainwebTestServer shouldValidateSpec tls v emptyChainwebServerDbs @@ -739,25 +734,21 @@ withPeerDbsServer shouldValidateSpec tls v peerDbs = withPayloadServer :: Show t - => CanReadablePayloadCas tbl => ToJSON t => FromJSON t => ShouldValidateSpec -> Bool -> ChainwebVersion - -> CutDb tbl - -> [(ChainId, PayloadDb tbl)] - -> ResourceT IO (TestClientEnv t tbl) -withPayloadServer shouldValidateSpec tls v cutDb payloadDbs = + -> CutDb + -> ResourceT IO (TestClientEnv t) +withPayloadServer shouldValidateSpec tls v cutDb = clientEnvWithChainwebTestServer shouldValidateSpec tls v emptyChainwebServerDbs - { _chainwebServerPayloadDbs = payloadDbs - , _chainwebServerCutDb = Just cutDb + { _chainwebServerCutDb = Just cutDb } withBlockHeaderDbsServer :: Show t - => CanReadablePayloadCas tbl => ToJSON t => FromJSON t => ShouldValidateSpec @@ -765,7 +756,7 @@ withBlockHeaderDbsServer -> ChainwebVersion -> [(ChainId, BlockHeaderDb)] -> [(ChainId, MempoolBackend t)] - -> ResourceT IO (TestClientEnv t tbl) + -> ResourceT IO (TestClientEnv t) withBlockHeaderDbsServer shouldValidateSpec tls v chainDbs mempools = clientEnvWithChainwebTestServer shouldValidateSpec tls v emptyChainwebServerDbs @@ -1065,7 +1056,7 @@ node rdb rawLogger nowServingRef peerInfoVar conf pactDbDir backupDir nid = do bootStrapPort = view (chainwebServiceSocket . _1) cw putMVar peerInfoVar (bootStrapInfo, bootStrapPort) - poisonDeadBeef cw + -- poisonDeadBeef cw runChainweb cw (atomically . modifyTVar' nowServingRef) `finally` do logFunctionText logger Info "write sample data" logFunctionText logger Info "shutdown node" @@ -1074,10 +1065,13 @@ node rdb rawLogger nowServingRef peerInfoVar conf pactDbDir backupDir nid = do where logger = addLabel ("node", sshow nid) rawLogger - poisonDeadBeef cw = mapM_ poison crs - where - crs = map snd $ HashMap.toList $ view chainwebChains cw - poison cr = mempoolAddToBadList (view chainResMempool cr) (V.singleton deadbeef) + -- poisonDeadBeef cw = mapM_ poison crs + -- where + -- crs :: [ChainResources logger] + -- crs = map snd $ HashMap.toList $ view chainwebChains cw + + -- poison :: ChainResources logger -> IO () + -- poison cr = mempoolAddToBadList (view chainResMempool cr) (V.singleton deadbeef) data NodeDbDirs = NodeDbDirs { nodePactDbDir :: FilePath @@ -1128,7 +1122,6 @@ config ver n = defaultChainwebConfiguration ver where miner = NodeMiningConfig { _nodeMiningEnabled = True - , _nodeMiner = noMiner , _nodeTestMiners = MinerCount n } From 4dc91f79405b8233f0907eef573b455c71b5c563 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:15:52 -0800 Subject: [PATCH 071/378] [FIXME] add dependencies. TODO move to correct commits. --- chainweb.cabal | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index 0f7dc78967..6e1dc53927 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -383,7 +383,7 @@ library , async >= 2.2 , attoparsec >= 0.13 , base >= 4.12 && < 5 - , base16-bytestring >= 0.1 + , base16-bytestring >= 1.0 , base64-bytestring-kadena == 0.1 , binary >= 0.8 , bytestring >= 0.10.12 @@ -433,6 +433,7 @@ library , mwc-probability >= 2.0 , mwc-random >= 0.13 , network >= 3.1.2 + , network-uri >= 2.6 , optparse-applicative >= 0.14 , pact >= 4.2.0.1 , pact-json >= 0.1 @@ -540,7 +541,7 @@ library chainweb-test-utils , aeson >= 2.2 , async >= 2.2 , base >= 4.12 && < 5 - , base16-bytestring >= 0.1 + , base16-bytestring >= 1.0 , bytestring >= 0.10.12 , case-insensitive >= 1.2 , chainweb-storage >= 0.1 @@ -695,7 +696,7 @@ test-suite chainweb-tests , aeson >= 2.2 , async >= 2.2 , base >= 4.12 && < 5 - , base16-bytestring >= 0.1 + , base16-bytestring >= 1.0 , base64-bytestring-kadena == 0.1 , byteslice >= 0.2.12 , bytesmith >= 0.3.10 From a76958e54f7ba0ee10d18e930c756ed0a59b5b3a Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 14 Jan 2025 01:03:28 -0800 Subject: [PATCH 072/378] Chainweb.PayloadProvider.P2P.RestAPI for EVM --- src/Chainweb/PayloadProvider/P2P/RestAPI.hs | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs index e87f2ba951..68e5232c45 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs @@ -56,6 +56,7 @@ import Chainweb.BlockHeight import Chainweb.BlockPayloadHash import Chainweb.ChainId import Chainweb.Payload qualified as Pact +import Chainweb.PayloadProvider.EVM.Header qualified as EVM import Chainweb.PayloadProvider.Minimal.Payload qualified as Minimal import Chainweb.Ranked import Chainweb.RestAPI.Orphans () @@ -69,6 +70,7 @@ import Data.Aeson import Data.Kind import Data.Maybe import Data.Proxy +import Ethereum.RLP qualified as EVM import GHC.Generics (Generic) import GHC.TypeNats import Servant.API @@ -265,7 +267,7 @@ somePayloadApi v c = runIdentity $ do SPactProvider -> return $! SomeApi (payloadApi @v' @c' @'PactProvider) SEvmProvider @n _ -> - error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: IsPayloadProvider not implemented for EVM" + return $! SomeApi (payloadApi @v' @c' @('EvmProvider n)) somePayloadApis :: ChainwebVersion -> [ChainId] -> SomeApi somePayloadApis v = mconcat . fmap (somePayloadApi v) @@ -312,4 +314,31 @@ instance IsPayloadProvider PactProvider where p2pPayloadBatchLimit = 20 -- FIXME batch = Pact.PayloadDataList . catMaybes +-- | IsPayloadProvider instance for the Pact Payload provider +-- +instance IsPayloadProvider (EvmProvider n) where + type PayloadType (EvmProvider n) = EVM.Header + type PayloadBatchType (EvmProvider n) = HeaderList + p2pPayloadBatchLimit = 20 -- FIXME + batch = HeaderList . catMaybes + +newtype HeaderList = HeaderList { _headerList :: [EVM.Header] } + deriving (Show, Eq, Generic) + deriving newtype (ToJSON, FromJSON, EVM.RLP) + +instance MimeRender OctetStream EVM.Header where + mimeRender _ = EVM.putRlpLazyByteString + {-# INLINE mimeRender #-} + +instance MimeUnrender OctetStream EVM.Header where + mimeUnrender _ = EVM.getLazy EVM.getRlp + {-# INLINE mimeUnrender #-} + +instance MimeRender OctetStream HeaderList where + mimeRender _ = EVM.putRlpLazyByteString + {-# INLINE mimeRender #-} + +instance MimeUnrender OctetStream HeaderList where + mimeUnrender _ = EVM.getLazy EVM.getRlp + {-# INLINE mimeUnrender #-} From 55895ac4604870942d2054f8999f6ac986f2b20e Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:15:53 -0800 Subject: [PATCH 073/378] Add Chainweb.PayloadProvider.EVM.Utils --- chainweb.cabal | 1 + src/Chainweb/PayloadProvider/EVM/Utils.hs | 236 ++++++++++++++++++++++ 2 files changed, 237 insertions(+) create mode 100644 src/Chainweb/PayloadProvider/EVM/Utils.hs diff --git a/chainweb.cabal b/chainweb.cabal index 6e1dc53927..b27c8287aa 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -226,6 +226,7 @@ library , Chainweb.Payload.RestAPI.Server , Chainweb.Payload.RestAPI.Client , Chainweb.PayloadProvider + , Chainweb.PayloadProvider.EVM.Utils , Chainweb.PayloadProvider.Initialization , Chainweb.PayloadProvider.Minimal , Chainweb.PayloadProvider.Minimal.Payload diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs new file mode 100644 index 0000000000..3b27a81d8c --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -0,0 +1,236 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.Utils +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.EVM.Utils +( ChainId(..) +, Randao(..) +, BlockValue(..) +, _blockValueStu +, DefaultBlockParameter(..) + +-- * Misc Utils +, fromHexQuanity +, fromHexBytes +, nullHash +, nullBlockHash +, decodeRlpM +) where + +import Chainweb.BlockHash qualified as Chainweb +import Chainweb.Utils +import Chainweb.Utils.Serialization (runPutS, runGetS) + +import Control.Monad.Catch + +import Data.Aeson +import Data.ByteString qualified as B +import Data.ByteString.Base16 qualified as B16 +import Data.ByteString.Short qualified as BS +import Data.Hashable (Hashable) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import Data.Text.Read qualified as T + +import Ethereum.Misc +import Ethereum.RLP (RLP, get, getRlp) +import Ethereum.Transaction (Wei (..)) +import Ethereum.Utils hiding (int) + +import Foreign.Storable (Storable) + +import GHC.Generics (Generic) + +import GHC.TypeLits + +import Text.Printf +import Chainweb.MinerReward + +-- -------------------------------------------------------------------------- -- +-- Utils (should be moved to the ethereum package) + +-- copy and past from the Ethereum.Utils + +fromHexQuanity :: HexQuantity a -> a +fromHexQuanity (HexQuantity a) = a + +fromHexBytes :: HexBytes a -> a +fromHexBytes (HexBytes a) = a + +nullHash :: Keccak256Hash +nullHash = Keccak256Hash $ replicateN 32 + +nullBlockHash :: BlockHash +nullBlockHash = BlockHash nullHash + +deriving instance Functor HexQuantity +deriving instance Functor HexBytes + +-- The implementation should be moved to the Ethereum.Utils +instance HasTextRepresentation (HexQuantity Natural) where + toText (HexQuantity a) = T.pack $ printf @(Natural -> String) "0x%x" a + fromText t = do + T.hexadecimal <$> strip0x t >>= \case + Right (x, "") -> return $ HexQuantity x + Right (x, _) -> throwM $ TextFormatException + $ "pending characters after parsing " <> sshow x + Left e -> throwM $ TextFormatException (T.pack e) + {-# INLINE toText #-} + {-# INLINE fromText #-} + +instance HasTextRepresentation BlockNumber where + toText (BlockNumber a) = toText (HexQuantity a) + fromText t = do + HexQuantity n <- fromText t + return $ BlockNumber n + {-# INLINE toText #-} + {-# INLINE fromText #-} + +instance HasTextRepresentation (HexBytes B.ByteString) where + toText (HexBytes a) = T.decodeUtf8 ("0x" <> B16.encode a) + fromText t = (B16.decode . T.encodeUtf8 <$> strip0x t) >>= \case + Left e -> throwM $ TextFormatException $ T.pack e + Right x -> return $ HexBytes x + {-# INLINE toText #-} + {-# INLINE fromText #-} + +instance HasTextRepresentation (HexBytes BS.ShortByteString) where + toText = toText . fmap BS.fromShort + fromText t = fmap BS.toShort <$> fromText t + {-# INLINE toText #-} + {-# INLINE fromText #-} + +instance KnownNat n => HasTextRepresentation (HexBytes (BytesN n)) where + toText = toText . fmap bytes + fromText t = do + HexBytes bs <- fromText t + case bytesN bs of + Right x -> return (HexBytes x) + Left e -> throwM $ TextFormatException $ sshow e + {-# INLINE toText #-} + {-# INLINE fromText #-} + +instance HasTextRepresentation Keccak256Hash where + toText = toText . HexBytes . bytes + fromText = fmap (Keccak256Hash . fromHexBytes) . fromText + {-# INLINE toText #-} + {-# INLINE fromText #-} + +instance HasTextRepresentation BlockHash where + toText = toText . HexBytes . bytes + fromText = fmap BlockHash . fromText + {-# INLINE toText #-} + {-# INLINE fromText #-} + +-- -------------------------------------------------------------------------- -- +-- RLP Encoding Tools + +decodeRlpM + :: MonadThrow m + => RLP a + => B.ByteString -> m a +decodeRlpM bs = case get getRlp bs of + Left e -> throwM $ DecodeException $ (T.pack e) + Right x -> return x +{-# INLINE decodeRlpM #-} + +-- -------------------------------------------------------------------------- -- +-- HexBytes representation for Chainweb BlockHash + +instance ToJSON (HexBytes Chainweb.BlockHash) where + toEncoding (HexBytes a) = toEncoding (HexBytes $ runPutS $ Chainweb.encodeBlockHash a) + toJSON (HexBytes a) = toJSON (HexBytes $ runPutS $ Chainweb.encodeBlockHash a) + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON (HexBytes Chainweb.BlockHash) where + parseJSON v = HexBytes <$> do + HexBytes b <- parseJSON @(HexBytes (BytesN 32)) v + case runGetS Chainweb.decodeBlockHash (bytes b) of + Right x -> return x + Left e -> fail (sshow e) + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- ChainId + +newtype ChainId = ChainId { _chainId :: Natural } + deriving (Show, Eq, Ord, Generic) + deriving (ToJSON, FromJSON) via (HexQuantity Natural) + +-- -------------------------------------------------------------------------- -- +-- Randao + +-- | RANDAO is a pseudorandom value generated by validators on the Ethereum +-- consensus layer. +-- +-- 32 bytes [cf. yellow paper 4.4.3 (44)] +-- +newtype Randao = Randao (BytesN 32) + deriving (Show, Eq, Ord) + deriving newtype (RLP, Bytes, Storable, Hashable) + deriving ToJSON via (HexBytes (BytesN 32)) + deriving FromJSON via (HexBytes (BytesN 32)) + +newtype BlockValue = BlockValue { _blockValue :: Wei } + deriving (Show, Eq) + deriving newtype (RLP) + deriving (ToJSON, FromJSON) via (HexQuantity Word256) + +_blockValueStu :: BlockValue -> Stu +_blockValueStu (BlockValue (Wei v)) = Stu (int v) + +-- -------------------------------------------------------------------------- -- +-- Default Block Parameter + +-- | Default block parameter +-- +-- cf. https://ethereum.org/en/developers/docs/apis/json-rpc/#default-block +-- +data DefaultBlockParameter + = DefaultBlockEarliest + | DefaultBlockLatest + | DefaultBlockPending + | DefaultBlockSafe + | DefaultBlockFinalized + | DefaultBlockNumber !BlockNumber + deriving (Show, Eq, Generic) + deriving (ToJSON, FromJSON) via (JsonTextRepresentation "DefaultBlockParameter" DefaultBlockParameter) + +instance HasTextRepresentation DefaultBlockParameter where + toText DefaultBlockEarliest = "earliest" + toText DefaultBlockLatest = "latest" + toText DefaultBlockPending = "pending" + toText DefaultBlockSafe = "safe" + toText DefaultBlockFinalized = "finalized" + toText (DefaultBlockNumber (BlockNumber n)) = toText (HexQuantity n) + {-# INLINE toText #-} + + fromText t = case t of + "latest" -> return DefaultBlockLatest + "earliest" -> return DefaultBlockEarliest + "pending" -> return DefaultBlockPending + "safe" -> return DefaultBlockSafe + "finalized" -> return DefaultBlockFinalized + x -> DefaultBlockNumber . BlockNumber . fromHexQuanity <$> fromText x + {-# INLINE fromText #-} + From 63d91c59d55614a484a8d27c00feca56a621c973 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 15 Jan 2025 19:15:36 -0800 Subject: [PATCH 074/378] HasTextRepresentation instance for EVM Address --- src/Chainweb/PayloadProvider/EVM/Utils.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs index 3b27a81d8c..d69d582039 100644 --- a/src/Chainweb/PayloadProvider/EVM/Utils.hs +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -141,6 +141,12 @@ instance HasTextRepresentation BlockHash where {-# INLINE toText #-} {-# INLINE fromText #-} +instance HasTextRepresentation Address where + toText = toText . HexBytes . bytes + fromText = fmap (Address . fromHexBytes) . fromText + {-# INLINE toText #-} + {-# INLINE fromText #-} + -- -------------------------------------------------------------------------- -- -- RLP Encoding Tools From 19b489b494b4f881c63ba41fe25b22e351f3d047 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:15:53 -0800 Subject: [PATCH 075/378] Add ETH RPC API modules --- chainweb.cabal | 3 + src/Chainweb/PayloadProvider/EVM/EngineAPI.hs | 1205 +++++++++++++++++ src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs | 186 +++ src/Chainweb/PayloadProvider/EVM/JsonRPC.hs | 375 +++++ 4 files changed, 1769 insertions(+) create mode 100644 src/Chainweb/PayloadProvider/EVM/EngineAPI.hs create mode 100644 src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs create mode 100644 src/Chainweb/PayloadProvider/EVM/JsonRPC.hs diff --git a/chainweb.cabal b/chainweb.cabal index b27c8287aa..5b81a9147a 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -226,6 +226,9 @@ library , Chainweb.Payload.RestAPI.Server , Chainweb.Payload.RestAPI.Client , Chainweb.PayloadProvider + , Chainweb.PayloadProvider.EVM.EngineAPI + , Chainweb.PayloadProvider.EVM.EthRpcAPI + , Chainweb.PayloadProvider.EVM.JsonRPC , Chainweb.PayloadProvider.EVM.Utils , Chainweb.PayloadProvider.Initialization , Chainweb.PayloadProvider.Minimal diff --git a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs new file mode 100644 index 0000000000..56c69cd1ab --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs @@ -0,0 +1,1205 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE DeriveFunctor #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE BangPatterns #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.EngineAPI +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- Ethereum Engine API: +-- +-- https://ethereum.org/en/developers/docs/apis/json-rpc/ +-- https://github.com/ethereum/execution-apis +-- https://ethereum.github.io/execution-apis/api-documentation/ +-- +-- There are currently four versions of the Engine API: +-- +-- - Paris: First version that is used during the "merge". It covers several +-- details of the transition from PoW to PoS that are not relevant in the +-- context of Chainweb. +-- +-- - Shanghai: Introduces support for Withdrawals and adds +-- - WithdrawalsV1 +-- - ExecutionPayloadBodyV1 +-- - engine_getPayloadBodiesByHashV1 +-- - engine_getPayloadBodiesByRangeV1 +-- +-- - Cancun: Introduces support for Blobs and adds +-- - BlobsBundleV1 +-- - BlobAndProofV1 +-- - engine_getBlobsV1 +-- +-- - Prague: Introduces support for ExecutionRequests +-- +-- The implementation does not cover all features from all versions. +-- +-- IMPORTANT NOTE: +-- +-- Consensus Layer client software MUST wait for a specified timeout before +-- aborting the call. In such an event, the Consensus Layer client software +-- SHOULD retry the call when it is needed to keep progressing. +-- +-- Consensus Layer client software MAY wait for response longer than it is +-- specified by the timeout parameter. +-- +module Chainweb.PayloadProvider.EVM.EngineAPI +( WithdrawalV1(..) +, withdrawlsRoot +, ForkchoiceStateV1(..) +, DefaultBlockParameter(..) +, EngineServerErrors(..) +, EngineErrors(..) +, PayloadId(..) +, PayloadStatusV1(..) +, TransactionBytes(..) +, transactionsRoot + +-- * Payload Attributes +, PayloadAttributesV1(..) +, PayloadAttributesV2(..) +, PayloadAttributesV3(..) +, PayloadStatusStatus(..) + +-- * Forkchoice Update +, ForkchoiceUpdatedV3Request(..) +, ForkchoiceUpdatedV1Response(..) + +-- * Execution Payload +, ExecutionPayloadV1(..) +, ExecutionPayloadV2(..) +, ExecutionPayloadV3(..) + +-- * Get Payload Response +, Blob(..) +, KzgProof(..) +, KzgCommitment(..) +, BlobsBundleV1(..) +, BlockValue(..) +, GetPayloadV2Response(..) +, GetPayloadV3Response(..) + +-- * Authentication and Client Context +, JwtSecret(..) +, jwtToken +, getJwtToken +, mkSimpleEngineCtx +, mkEngineCtx + +-- * Engine API Methods +, type Engine_GetPayloadV2 +, type Engine_GetPayloadV3 +, type Engine_ForkchoiceUpdatedV3 +) where + +import Chainweb.PayloadProvider.EVM.Header +import Chainweb.PayloadProvider.EVM.JsonRPC +import Chainweb.PayloadProvider.EVM.Utils +import Chainweb.Utils +import Control.Monad.Catch +import Crypto.Hash.Algorithms (SHA256) +import Crypto.MAC.HMAC +import Data.Aeson +import Data.Bifunctor +import Data.ByteArray qualified as BA +import Data.ByteString.Short qualified as BS +import Data.Foldable +import Data.Hashable (Hashable) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import Data.Time.Clock.POSIX (getPOSIXTime, POSIXTime) +import Ethereum.Misc +import Ethereum.RLP (RLP (..), putRlpByteString, getRlpL, putRlpL, label) +import Ethereum.Trie +import Ethereum.Utils hiding (int) +import Foreign.Storable (Storable) +import GHC.Generics (Generic) +import GHC.TypeLits +import Network.HTTP.Client qualified as HTTP +import Network.URI +import Network.URI.Static (uri) +import System.IO.Unsafe (unsafePerformIO) +import Data.Word + +-- -------------------------------------------------------------------------- -- +-- Forkchoice State V1 + +-- | Forkchoice state object V1 +-- +-- This structure encapsulates the fork choice state. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#forkchoicestatev1 +-- +data ForkchoiceStateV1 = ForkchoiceStateV1 + { _forkchoiceHeadBlockHash :: !BlockHash + -- ^ headBlockHash: DATA, 32 Bytes - block hash of the head of the + -- canonical chain + , _forkchoiceSafeBlockHash :: !BlockHash + -- ^ safeBlockHash: DATA, 32 Bytes - the "safe" block hash of the + -- canonical chain under certain synchrony and honesty assumptions. This + -- value MUST be either equal to or an ancestor of headBlockHash + , _forkchoiceFinalizedBlockHash :: !BlockHash + -- ^ finalizedBlockHash: DATA, 32 Bytes - block hash of the most recent + -- finalized block + } + deriving (Show, Eq, Generic) + +instance ToJSON ForkchoiceStateV1 where + toEncoding o = pairs + $ "headBlockHash" .= _forkchoiceHeadBlockHash o + <> "safeBlockHash" .= _forkchoiceSafeBlockHash o + <> "finalizedBlockHash" .= _forkchoiceFinalizedBlockHash o + {-# INLINE toEncoding #-} + + toJSON o = object + [ "headBlockHash" .= _forkchoiceHeadBlockHash o + , "safeBlockHash" .= _forkchoiceSafeBlockHash o + , "finalizedBlockHash" .= _forkchoiceFinalizedBlockHash o + ] + {-# INLINE toJSON #-} + +instance FromJSON ForkchoiceStateV1 where + parseJSON = withObject "ForkchoiceStateV1" $ \o -> ForkchoiceStateV1 + <$> o .: "headBlockHash" + <*> o .: "safeBlockHash" + <*> o .: "finalizedBlockHash" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Payload Status Status + +-- | Payload validation status +-- +-- Set of possible values is restricted to +-- "VALID" | "INVALID" | "SYNCING" | "ACCEPTED" | "INVALID_BLOCK_HASH" +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadstatusv1 +-- +data PayloadStatusStatus + = Valid + | Invalid + | Syncing + | Accepted + | InvalidBlockHash + deriving (Show, Eq, Generic, Enum, Bounded) + deriving (ToJSON, FromJSON) via (JsonTextRepresentation "PayloadStatusStatus" PayloadStatusStatus) + +instance HasTextRepresentation PayloadStatusStatus where + toText Valid = "VALID" + toText Invalid = "INVALID" + toText Syncing = "SYNCING" + toText Accepted = "ACCEPTED" + toText InvalidBlockHash = "INVALID_BLOCK_HASH" + {-# INLINE toText #-} + + fromText "VALID" = return Valid + fromText "INVALID" = return Invalid + fromText "SYNCING" = return Syncing + fromText "ACCEPTED" = return Accepted + fromText "INVALID_BLOCK_HASH" = return InvalidBlockHash + fromText x = throwM $ TextFormatException $ "Invalid PayloadStatusStatus: " <> x + {-# INLINE fromText #-} + +-- -------------------------------------------------------------------------- -- +-- Payload Status V1 + +-- | Payload Status V1 +-- +-- This structure contains the result of processing a payload. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadstatusv1 +-- +data PayloadStatusV1 = PayloadStatusV1 + { _payloadStatusV1Status :: !PayloadStatusStatus + -- ^ Status of the payload + , _payloadStatusV1LatestValidHash :: !(Maybe BlockHash) + -- ^ DATA|null, 32 Bytes - the hash of the most recent valid block in + -- the branch defined by payload and its ancestors + , _payloadStatusV1ValidationError :: !(Maybe T.Text) + -- ^ String|null - a message providing additional details on the + -- validation error if the payload is classified as INVALID or + -- INVALID_BLOCK_HASH. + } + deriving (Eq, Show, Generic) + +payloadStatusV1Properties + :: KeyValue e kv + => PayloadStatusV1 + -> [kv] +payloadStatusV1Properties o = + [ "status" .= _payloadStatusV1Status o + , "latestValidHash" .= _payloadStatusV1LatestValidHash o + , "validationError" .= _payloadStatusV1ValidationError o + ] +{-# INLINE payloadStatusV1Properties #-} + +instance ToJSON PayloadStatusV1 where + toEncoding = pairs . mconcat . payloadStatusV1Properties + toJSON = object . payloadStatusV1Properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON PayloadStatusV1 where + parseJSON = withObject "PayloadStatusV1" $ \o -> PayloadStatusV1 + <$> o .: "status" + <*> o .: "latestValidHash" + <*> o .: "validationError" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Withdrawal V1 + +-- | Withdrawal object V1 +-- +-- This structure maps onto the validator withdrawal object from the beacon +-- chain spec. +-- +-- Note: the amount value is represented on the beacon chain as a little-endian +-- value in units of Gwei, whereas the amount in this structure MUST be +-- converted to a big-endian value in units of Gwei. +-- +-- https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#withdrawalv1 +-- +data WithdrawalV1 = WithdrawalV1 + { _withdrawalIndex :: !Word64 + -- ^ index: QUANTITY, 64 Bits + , _withdrawalValidatorIndex :: !Word64 + -- ^ validatorIndex: QUANTITY, 64 Bits + , _withdrawalAddress :: !Address + -- ^ address: DATA, 20 Bytes + , _withdrawalAmount :: !Word64 + -- ^ amount: QUANTITY, 64 Bits + } + deriving (Show, Eq, Generic) + +instance ToJSON WithdrawalV1 where + toEncoding o = pairs + $ "index" .= HexQuantity (_withdrawalIndex o) + <> "validatorIndex" .= HexQuantity (_withdrawalValidatorIndex o) + <> "address" .= _withdrawalAddress o + <> "amount" .= HexQuantity (_withdrawalAmount o) + {-# INLINE toEncoding #-} + + toJSON o = object + [ "index" .= HexQuantity (_withdrawalIndex o) + , "validatorIndex" .= HexQuantity (_withdrawalValidatorIndex o) + , "address" .= _withdrawalAddress o + , "amount" .= HexQuantity (_withdrawalAmount o) + ] + {-# INLINE toJSON #-} + +instance FromJSON WithdrawalV1 where + parseJSON = withObject "Withdrawal" $ \o -> WithdrawalV1 + <$> fmap fromHexQuanity (o .: "index") + <*> fmap fromHexQuanity (o .: "validatorIndex") + <*> o .: "address" + <*> fmap fromHexQuanity (o .: "amount") + {-# INLINE parseJSON #-} + +instance RLP WithdrawalV1 where + putRlp w = putRlpL + [ putRlp $ _withdrawalIndex w + , putRlp $ _withdrawalValidatorIndex w + , putRlp $ _withdrawalAddress w + , putRlp $ _withdrawalAmount w + ] + + getRlp = label "WithdrawalV1" $ getRlpL $ + WithdrawalV1 + <$> getRlp -- index + <*> getRlp -- validator index + <*> getRlp -- address + <*> getRlp -- amount + +withdrawlsRoot :: [WithdrawalV1] -> WithdrawalsRoot +withdrawlsRoot l = unsafePerformIO $ do + store <- mkHashMapStore + Trie (Keccak256Hash !t) <- trie (_trieStoreAdd store) + $ bimap putRlpByteString putRlpByteString <$> zip [0::Natural ..] l + return (WithdrawalsRoot t) + +-- -------------------------------------------------------------------------- -- +-- Blobs Bundle V1 + +-- | EIP-4844 KZG Commitment +-- +newtype KzgCommitment = KzgCommitment { _kzgCommitment :: BytesN 48 } + deriving (Show, Eq, Ord) + deriving newtype (RLP, Bytes, Storable, Hashable) + deriving (ToJSON, FromJSON) via (HexBytes (BytesN 48)) + +-- | EIP-4844 KZG Proof +-- +newtype KzgProof = KzgProof { _kzgProof :: BytesN 48 } + deriving (Show, Eq, Ord) + deriving newtype (RLP, Bytes, Storable, Hashable) + deriving (ToJSON, FromJSON) via (HexBytes (BytesN 48)) + +-- | EIP-4844 Blob +-- +-- size: FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT = 4096 * 32 = 131072 +-- +newtype Blob = Blob { _blob :: BytesN 131072 } + deriving (Show, Eq, Ord) + deriving newtype (RLP, Bytes, Storable, Hashable) + deriving (ToJSON, FromJSON) via (HexBytes (BytesN 131072)) + +-- | Blobs Bundle V1 +-- +-- * commitments: Array of DATA - Array of KZGCommitment as defined in EIP-4844, +-- 48 bytes each (DATA). +-- * proofs: Array of DATA - Array of KZGProof as defined in EIP-4844, 48 bytes +-- each (DATA). +-- * blobs: Array of DATA - Array of blobs, each blob is FIELD_ELEMENTS_PER_BLOB +-- * BYTES_PER_FIELD_ELEMENT = 4096 * 32 = 131072 bytes (DATA) representing a +-- SSZ-encoded Blob as defined in EIP-4844 +-- +-- All of the above three arrays MUST be of same length. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#blobsbundlev1 +-- +data BlobsBundleV1 = BlobsBundleV1 + { _blobsBundleV1Commitments :: ![KzgCommitment] + -- ^ commitments: Array of DATA - Array of KZGCommitment as defined in + -- EIP-4844, 48 bytes each (DATA). + , _blobsBundleV1Proofs :: ![KzgProof] + -- ^ proofs: Array of DATA - Array of KZGProof as defined in EIP-4844, + -- 48 bytes each (DATA). + , _blobsBundleV1Blobs :: ![Blob] + -- ^ blobs: Array of DATA - Array of blobs, each blob is + -- FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT = 4096 * 32 = 131072 + -- bytes (DATA) representing a SSZ-encoded Blob as defined in EIP-4844 + } + deriving (Show, Eq, Generic) + +blobsBundleV1Properties + :: KeyValue e kv + => BlobsBundleV1 + -> [kv] +blobsBundleV1Properties o = + [ "commitments" .= _blobsBundleV1Commitments o + , "proofs" .= _blobsBundleV1Proofs o + , "blobs" .= _blobsBundleV1Blobs o + ] + +instance ToJSON BlobsBundleV1 where + toEncoding = pairs . mconcat . blobsBundleV1Properties + toJSON = object . blobsBundleV1Properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON BlobsBundleV1 where + parseJSON = withObject "BlobsBundleV1" $ \o -> BlobsBundleV1 + <$> o .: "commitments" + <*> o .: "proofs" + <*> o .: "blobs" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Execution Payload V1 + +newtype TransactionBytes = TransactionBytes + { _transactionBytes :: BS.ShortByteString } + deriving (Show, Eq, Ord, Generic) + deriving newtype (Hashable, Bytes) + +instance ToJSON TransactionBytes where + toEncoding (TransactionBytes a) = toEncoding (HexBytes a) + toJSON (TransactionBytes a) = toJSON (HexBytes a) + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON TransactionBytes where + parseJSON v = TransactionBytes <$> do + HexBytes b <- parseJSON v + return b + {-# INLINE parseJSON #-} + +transactionsRoot :: [TransactionBytes] -> TransactionsRoot +transactionsRoot l = unsafePerformIO $ do + store <- mkHashMapStore + Trie !t <- trie (_trieStoreAdd store) + $ bimap putRlpByteString bytes <$> zip [0::Natural ..] l + return (TransactionsRoot t) + +-- | Execution Payload V1 +-- +-- This structure maps on the ExecutionPayload structure of the beacon chain +-- spec. +-- +-- * parentHash: DATA, 32 Bytes +-- * feeRecipient: DATA, 20 Bytes +-- * stateRoot: DATA, 32 Bytes +-- * receiptsRoot: DATA, 32 Bytes +-- * logsBloom: DATA, 256 Bytes +-- * prevRandao: DATA, 32 Bytes +-- * blockNumber: QUANTITY, 64 Bits +-- * gasLimit: QUANTITY, 64 Bits +-- * gasUsed: QUANTITY, 64 Bits +-- * timestamp: QUANTITY, 64 Bits +-- * extraData: DATA, 0 to 32 Bytes +-- * baseFeePerGas: QUANTITY, 256 Bits +-- * blockHash: DATA, 32 Bytes +-- * transactions: Array of DATA - Array of transaction objects, each object is +-- a byte list (DATA) representing TransactionType || TransactionPayload or +-- LegacyTransaction as defined in EIP-2718 +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#executionpayloadv1 +-- +data ExecutionPayloadV1 = ExecutionPayloadV1 + { _executionPayloadV1ParentHash :: !ParentHash + -- ^ parentHash: DATA, 32 Bytes + , _executionPayloadV1FeeRecipient :: !Beneficiary + -- ^ feeRecipient: DATA, 20 Bytes + , _executionPayloadV1StateRoot :: !StateRoot + -- ^ stateRoot: DATA, 32 Bytes + , _executionPayloadV1ReceiptsRoot :: !ReceiptsRoot + -- ^ receiptsRoot: DATA, 32 Bytes + , _executionPayloadV1LogsBloom :: !Bloom + -- ^ logsBloom: DATA, 256 Bytes + , _executionPayloadV1PrevRandao :: !Randao + -- ^ prevRandao: DATA, 32 Bytes + , _executionPayloadV1BlockNumber :: !BlockNumber + -- ^ blockNumber: QUANTITY, 64 Bits + , _executionPayloadV1GasLimit :: !GasLimit + -- ^ gasLimit: QUANTITY, 64 Bits + , _executionPayloadV1GasUsed :: !GasUsed + -- ^ gasUsed: QUANTITY, 64 Bits + , _executionPayloadV1Timestamp :: !Timestamp + -- ^ timestamp: QUANTITY, 64 Bits + , _executionPayloadV1ExtraData :: !ExtraData + -- ^ extraData: DATA, 0 to 32 Bytes + , _executionPayloadV1BaseFeePerGas :: !BaseFeePerGas + -- ^ baseFeePerGas: QUANTITY, 256 Bits + , _executionPayloadV1BlockHash :: !BlockHash + -- ^ blockHash: DATA, 32 Bytes + , _executionPayloadV1Transactions :: ![TransactionBytes] + -- ^ transactions: Array of DATA - Array of transaction objects, each + -- object is a byte list (DATA) representing TransactionType || + -- TransactionPayload or LegacyTransaction as defined in EIP-2718 + } + deriving (Show, Eq, Generic) + +executionPayloadV1Properties + :: KeyValue e kv + => ExecutionPayloadV1 + -> [kv] +executionPayloadV1Properties o = + [ "parentHash" .= _executionPayloadV1ParentHash o + , "feeRecipient" .= _executionPayloadV1FeeRecipient o + , "stateRoot" .= _executionPayloadV1StateRoot o + , "receiptsRoot" .= _executionPayloadV1ReceiptsRoot o + , "logsBloom" .= _executionPayloadV1LogsBloom o + , "prevRandao" .= _executionPayloadV1PrevRandao o + , "blockNumber" .= _executionPayloadV1BlockNumber o + , "gasLimit" .= _executionPayloadV1GasLimit o + , "gasUsed" .= _executionPayloadV1GasUsed o + , "timestamp" .= _executionPayloadV1Timestamp o + , "extraData" .= _executionPayloadV1ExtraData o + , "baseFeePerGas" .= _executionPayloadV1BaseFeePerGas o + , "blockHash" .= _executionPayloadV1BlockHash o + , "transactions" .= _executionPayloadV1Transactions o + ] + +instance ToJSON ExecutionPayloadV1 where + toEncoding = pairs . mconcat . executionPayloadV1Properties + toJSON = object . executionPayloadV1Properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON ExecutionPayloadV1 where + parseJSON = withObject "ExecutionPayloadV2" $ \o -> ExecutionPayloadV1 + <$> o .: "parentHash" + <*> o .: "feeRecipient" + <*> o .: "stateRoot" + <*> o .: "receiptsRoot" + <*> o .: "logsBloom" + <*> o .: "prevRandao" + <*> o .: "blockNumber" + <*> o .: "gasLimit" + <*> o .: "gasUsed" + <*> o .: "timestamp" + <*> o .: "extraData" + <*> o .: "baseFeePerGas" + <*> o .: "blockHash" + <*> o .: "transactions" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Execution Payload V2 + +-- | Execution Payload V2 +-- +-- This structure has the syntax of ExecutionPayloadV1 and appends a single +-- field: withdrawals. +-- +-- * withdrawals: Array of WithdrawalV1 - Array of withdrawals, each object is +-- an OBJECT containing the fields of a WithdrawalV1 structure. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#executionpayloadv2 +-- +data ExecutionPayloadV2 = ExecutionPayloadV2 + { _executionPayloadV1 :: !ExecutionPayloadV1 + , _executionPayloadV2Withdrawals :: ![WithdrawalV1] + -- ^ withdrawals: Array of WithdrawalV1 - Array of withdrawals, each + -- object is an OBJECT containing the fields of a WithdrawalV1 structure. + } + deriving (Show, Eq, Generic) + +executionPayloadV2Properties + :: KeyValue e kv + => ExecutionPayloadV2 + -> [kv] +executionPayloadV2Properties o = + executionPayloadV1Properties (_executionPayloadV1 o) <> + [ "withdrawals" .= _executionPayloadV2Withdrawals o + ] +{-# INLINABLE executionPayloadV2Properties #-} + +instance ToJSON ExecutionPayloadV2 where + toEncoding = pairs . mconcat . executionPayloadV2Properties + toJSON = object . executionPayloadV2Properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON ExecutionPayloadV2 where + parseJSON = withObject "ExecutionPayloadV2" $ \o -> ExecutionPayloadV2 + <$> parseJSON (Object o) + <*> o .: "withdrawals" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Execution Payload V3 + +-- | Execution Payload V3a +-- +-- This structure has the syntax of ExecutionPayloadV2 and appends the new +-- fields: blobGasUsed and excessBlobGas. +-- +-- * blobGasUsed: QUANTITY, 64 Bits +-- * excessBlobGas: QUANTITY, 64 Bits +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#executionpayloadv3 +-- +data ExecutionPayloadV3 = ExecutionPayloadV3 + { _executionPayloadV2 :: !ExecutionPayloadV2 + , _executionPayloadV3BlobGasUsed :: !BlobGasUsed + -- ^ QUANTITY, 64 Bits + , _executionPayloadV3ExcessBlobGas :: !ExcessBlobGas + -- ^ QUANTITY, 64 Bits + } + deriving (Show, Eq, Generic) + +executionPayloadV3Properties + :: KeyValue e kv + => ExecutionPayloadV3 + -> [kv] +executionPayloadV3Properties o = + executionPayloadV2Properties (_executionPayloadV2 o) <> + [ "blobGasUsed" .= _executionPayloadV3BlobGasUsed o + , "excessBlobGas" .= _executionPayloadV3ExcessBlobGas o + ] +{-# INLINABLE executionPayloadV3Properties #-} + +instance ToJSON ExecutionPayloadV3 where + toEncoding = pairs . mconcat . executionPayloadV3Properties + toJSON = object . executionPayloadV3Properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON ExecutionPayloadV3 where + parseJSON = withObject "ExecutionPayloadV3" $ \o -> ExecutionPayloadV3 + <$> parseJSON (Object o) + <*> o .: "blobGasUsed" + <*> o .: "excessBlobGas" + +-- -------------------------------------------------------------------------- -- +-- Payload Attributes V1 + +-- | Payload Attributes V1 +-- +-- This structure contains the attributes required to initiate a payload build +-- process in the context of an engine_forkchoiceUpdated call. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadattributesv1 +-- +data PayloadAttributesV1 = PayloadAttributesV1 + { _payloadAttributesV1Timestamp :: !Timestamp + -- ^ timestamp: QUANTITY, 64 Bits - value for the timestamp field of the + -- new payload + , _payloadAttributesV1PrevRandao :: !Randao + -- ^ prevRandao: DATA, 32 Bytes - value for the prevRandao field of the + -- new payload. + , _payloadAttributesV1SuggestedFeeRecipient :: !Address + -- ^ suggestedFeeRecipient: DATA, 20 Bytes - suggested value for the + -- feeRecipient field of the new payload + } + deriving (Show, Eq, Generic) + +payloadAttributesV1Properties + :: KeyValue e kv + => PayloadAttributesV1 + -> [kv] +payloadAttributesV1Properties o = + [ "timestamp" .= _payloadAttributesV1Timestamp o + , "prevRandao" .= _payloadAttributesV1PrevRandao o + , "suggestedFeeRecipient" .= _payloadAttributesV1SuggestedFeeRecipient o + ] + +instance ToJSON PayloadAttributesV1 where + toEncoding = pairs . mconcat . payloadAttributesV1Properties + toJSON = object . payloadAttributesV1Properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON PayloadAttributesV1 where + parseJSON = withObject "PayloadAttributesV1" $ \o -> PayloadAttributesV1 + <$> o .: "timestamp" + <*> o .: "prevRandao" + <*> o .: "suggestedFeeRecipient" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Payload Atributes V2 + +-- | Payload Attributes V2 +-- +-- This structure has the syntax of PayloadAttributesV1 and appends a single +-- field: withdrawals +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#payloadattributesv2 +-- +data PayloadAttributesV2 = PayloadAttributesV2 + { _payloadAttributesV1 :: !PayloadAttributesV1 + , _payloadAttributesV2Withdrawals :: ![WithdrawalV1] + -- ^ withdrawals: Array of WithdrawalV1 - Array of withdrawals, each + -- object is an OBJECT containing the fields of a WithdrawalV1 + -- structure. + } + deriving (Show, Eq, Generic) + +payloadAttributesV2Properties + :: KeyValue e kv + => PayloadAttributesV2 + -> [kv] +payloadAttributesV2Properties o = + payloadAttributesV1Properties (_payloadAttributesV1 o) <> + [ "withdrawals" .= _payloadAttributesV2Withdrawals o + ] + +instance ToJSON PayloadAttributesV2 where + toEncoding = pairs . mconcat . payloadAttributesV2Properties + toJSON = object . payloadAttributesV2Properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON PayloadAttributesV2 where + parseJSON = withObject "PayloadAttributesV2" $ \o -> PayloadAttributesV2 + <$> parseJSON (Object o) + <*> o .: "withdrawals" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Payload Attributes V3 + +-- | Payload Attributes Object V3 +-- +-- This structure has the syntax of PayloadAttributesV2 and appends a single +-- field: parentBeaconBlockRoot. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3 +-- +data PayloadAttributesV3 = PayloadAttributesV3 + { _payloadAttributesV2 :: PayloadAttributesV2 + , _payloadAttributesV3parentBeaconBlockRoot :: !ParentBeaconBlockRoot + -- ^ parentBeaconBlockRoot: DATA, 32 Bytes - Root of the parent beacon + -- block. + } + deriving (Show, Eq, Generic) + +payloadAttributeV3Properties + :: KeyValue e kv + => PayloadAttributesV3 + -> [kv] +payloadAttributeV3Properties o = + payloadAttributesV2Properties (_payloadAttributesV2 o) <> + [ "parentBeaconBlockRoot" .= _payloadAttributesV3parentBeaconBlockRoot o + ] + +instance ToJSON PayloadAttributesV3 where + toEncoding = pairs . mconcat . payloadAttributeV3Properties + toJSON = object . payloadAttributeV3Properties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON PayloadAttributesV3 where + parseJSON = withObject "PayloadAttributesV3" $ \o -> PayloadAttributesV3 + <$> parseJSON (Object o) + <*> o .: "parentBeaconBlockRoot" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Payload Id + +-- | Payload Id +-- +-- payloadId: DATA|null, 8 Bytes - identifier of the payload build process or +-- null +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#request-2 +-- +newtype PayloadId = PayloadId { _payloadId :: BytesN 8 } + deriving (Show, Eq, Ord, Generic) + deriving (ToJSON, FromJSON) via JsonTextRepresentation "PayloadId" PayloadId + +instance HasTextRepresentation PayloadId where + toText = toText . HexBytes . _payloadId + fromText = fmap (PayloadId . fromHexBytes) . fromText + +-- -------------------------------------------------------------------------- -- +-- Errors + +-- | Engine Errors +-- +-- * -32700 | Parse error | Invalid JSON was received by the server. +-- * -32600 | Invalid Request | The JSON sent is not a valid Request object. +-- * -32601 | Method not found | The method does not exist / is not available. +-- * -32602 | Invalid params | Invalid method parameter(s). +-- * -32603 | Internal error | Internal JSON-RPC error. +-- * -32000 | Server error | Generic client error while processing request. +-- * -38001 | Unknown payload | Payload does not exist / is not available. +-- * -38002 | Invalid forkchoice state | Forkchoice state is invalid / inconsistent. +-- * -38003 | Invalid payload attributes | Payload attributes are invalid / inconsistent. +-- * -38004 | Too large request | Number of requested entities is too large. +-- * -38005 | Unsupported fork | Payload belongs to a fork that is not supported. +-- +-- Each error returns a null data value, except -32000 which returns the data +-- object with a err member that explains the error encountered. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#errors +-- +data EngineErrors + = UnknownPayload + | InvalidForkChoiceState + | InvalidPayloadAttributes + | TooLargeRequest + | UnsupportedFork + deriving (Show, Eq, Generic, Enum, Bounded) + +instance HasErrorCode EngineErrors where + toErrorCode UnknownPayload = -38001 + toErrorCode InvalidForkChoiceState = -38002 + toErrorCode InvalidPayloadAttributes = -38003 + toErrorCode TooLargeRequest = -38004 + toErrorCode UnsupportedFork = -38005 + + fromErrorCode (-38001) = pure UnknownPayload + fromErrorCode (-38002) = pure InvalidForkChoiceState + fromErrorCode (-38003) = pure InvalidPayloadAttributes + fromErrorCode (-38004) = pure TooLargeRequest + fromErrorCode (-38005) = pure UnsupportedFork + fromErrorCode n = throwM $ UnknownErrorCodeException n + +data EngineServerErrors + = EngineServerError + deriving (Show, Eq, Generic, Enum, Bounded) + +instance HasErrorCode EngineServerErrors where + toErrorCode EngineServerError = -32000 + fromErrorCode (-32000) = pure EngineServerError + fromErrorCode n = throwM $ UnknownErrorCodeException n + +-- -------------------------------------------------------------------------- -- +-- Forkchoice Update V2 (Shanghai) + +-- | Engine Forkchoice Updated V2 +-- +-- timeout: 8s +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_forkchoiceupdatedv2 +-- +instance JsonRpcMethod "engine_forkchoiceUpdatedV2" where + type MethodRequest "engine_forkchoiceUpdatedV2" = ForkchoiceUpdatedV2Request + type MethodResponse "engine_forkchoiceUpdatedV2" = ForkchoiceUpdatedV1Response + type ServerErrors "engine_forkchoiceUpdatedV2" = EngineServerErrors + type ApplicationErrors "engine_forkchoiceUpdatedV2" = EngineErrors + responseTimeoutMs = Just 8000 + methodErrors = + [ ApplicationError InvalidForkChoiceState + , ApplicationError InvalidPayloadAttributes + ] + +-- | Engine ForkchoiceUpdatedV2 Request +-- +-- timeout: 8s +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#request-1 +-- +data ForkchoiceUpdatedV2Request = ForkchoiceUpdatedV2Request + { _forkchoiceUpdatedV2RequestState :: !ForkchoiceStateV1 + -- ^ forkchoiceState: Object - instance of ForkchoiceStateV1 + , _forkchoiceUpdatedV2RequestPayloadAttributes :: !(Maybe PayloadAttributesV2) + -- ^ payloadAttributes: Object|null - PayloadAttributesV2 or null. + -- + -- Client software MUST return -32602: Invalid params error if the wrong + -- version of the structure is used in the method call. + } + deriving (Show, Eq, Generic) + +instance ToJSON ForkchoiceUpdatedV2Request where + toEncoding o = toEncoding + ( _forkchoiceUpdatedV2RequestState o + , _forkchoiceUpdatedV2RequestPayloadAttributes o + ) + toJSON o = toJSON + ( _forkchoiceUpdatedV2RequestState o + , _forkchoiceUpdatedV2RequestPayloadAttributes o + ) + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON ForkchoiceUpdatedV2Request where + parseJSON = withArray "ForkchoiceUpdatedV2Request" $ \v -> do + case toList v of + [a, b] -> ForkchoiceUpdatedV2Request <$> parseJSON a <*> parseJSON b + l -> fail $ "invalid ForkchoiceUpdatedV2Request: expected 2 parameters but got " <> show (length l) + {-# INLINE parseJSON #-} + + +-- | Engine ForkchoiceUpdatedV1 response +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#response-1 +-- +-- error: code and message set in case an exception happens while the validating +-- payload, updating the forkchoice or initiating the payload build process. +-- +data ForkchoiceUpdatedV1Response = ForkchoiceUpdateV1Response + { _forkchoiceUpdatedV1ResponsePayloadStatus :: !PayloadStatusV1 + -- ^ payloadStatus: PayloadStatusV1; values of the status field in the + -- context of this method are restricted to the following subset: + -- "VALID" "INVALID" "SYNCING" + , _forkchoiceUpdatedV1ResponsePayloadId :: !(Maybe PayloadId) + -- ^ payloadId: DATA|null, 8 Bytes - identifier of the payload build + -- process or null + } + deriving (Show, Eq, Generic) + +instance ToJSON ForkchoiceUpdatedV1Response where + toEncoding o = pairs + $ "payloadStatus" .= _forkchoiceUpdatedV1ResponsePayloadStatus o + <> "payloadId" .= _forkchoiceUpdatedV1ResponsePayloadId o + {-# INLINE toEncoding #-} + + toJSON o = object + [ "payloadStatus" .= _forkchoiceUpdatedV1ResponsePayloadStatus o + , "payloadId" .= _forkchoiceUpdatedV1ResponsePayloadId o + ] + {-# INLINE toJSON #-} + +instance FromJSON ForkchoiceUpdatedV1Response where + parseJSON = withObject "ForkchoiceUpdatedV1Response" $ \o -> ForkchoiceUpdateV1Response + <$> o .: "payloadStatus" + <*> o .: "payloadId" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Forkchoice Update V3 (Cancun) + +type Engine_ForkchoiceUpdatedV3 = "engine_forkchoiceUpdatedV3" + +-- | Engine Forkchoice Updated V3 +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_forkchoiceupdatedv3 +-- +-- Client software MUST verify that forkchoiceState matches the +-- ForkchoiceStateV1 structure and return -32602: Invalid params on failure. +-- +instance JsonRpcMethod "engine_forkchoiceUpdatedV3" where + type MethodRequest "engine_forkchoiceUpdatedV3" = ForkchoiceUpdatedV3Request + type MethodResponse "engine_forkchoiceUpdatedV3" = ForkchoiceUpdatedV1Response + type ServerErrors "engine_forkchoiceUpdatedV3" = EngineServerErrors + type ApplicationErrors "engine_forkchoiceUpdatedV3" = EngineErrors + responseTimeoutMs = Just 8000 + methodErrors = + [ InvalidParams + , ApplicationError InvalidForkChoiceState + , ApplicationError InvalidPayloadAttributes + , ApplicationError UnsupportedFork + ] + +-- | Engine ForkchoiceUpdatedV3 Request +-- +-- timeout: 8s +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#request-1 +-- +data ForkchoiceUpdatedV3Request = ForkchoiceUpdatedV3Request + { _forkchoiceUpdatedV3RequestState :: !ForkchoiceStateV1 + -- ^ forkchoiceState: ForkchoiceStateV1. + , _forkchoiceUpdatedV3RequestPayloadAttributes :: !(Maybe PayloadAttributesV3) + -- ^ payloadAttributes: Object|null - Instance of PayloadAttributesV3 or null. + } + deriving (Show, Eq, Generic) + +instance ToJSON ForkchoiceUpdatedV3Request where + toEncoding o = toEncoding + ( _forkchoiceUpdatedV3RequestState o + , _forkchoiceUpdatedV3RequestPayloadAttributes o + ) + toJSON o = toJSON + ( _forkchoiceUpdatedV3RequestState o + , _forkchoiceUpdatedV3RequestPayloadAttributes o + ) + +instance FromJSON ForkchoiceUpdatedV3Request where + parseJSON = withArray "ForkchoiceUpdatedV3Request" $ \v -> do + case toList v of + [a, b] -> ForkchoiceUpdatedV3Request <$> parseJSON a <*> parseJSON b + l -> fail $ "invalid ForkchoiceUpdatedV2Request: expected 2 parameters but got " <> show (length l) + +-- -------------------------------------------------------------------------- -- +-- Engine Get Payload V2 (Shanghai) + +type Engine_GetPayloadV2 = "engine_getPayloadV2" + +-- | Engine Get Payload V2 +-- +-- timeout: 1s +-- +-- error: code and message set in case an exception happens while getting the +-- payload. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_getpayloadv2 +-- +instance JsonRpcMethod "engine_getPayloadV2" where + type MethodRequest "engine_getPayloadV2" = PayloadId + type MethodResponse "engine_getPayloadV2" = GetPayloadV2Response + type ServerErrors "engine_getPayloadV2" = EngineServerErrors + type ApplicationErrors "engine_getPayloadV2" = EngineErrors + responseTimeoutMs = Just 1000 + methodErrors = + [ ApplicationError UnknownPayload + ] + +-- | Engine Get Payload V2 Response +-- +-- error: code and message set in case an exception happens while getting the +-- payload. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#response-2 +-- +data GetPayloadV2Response = GetPayloadV2Response + { _getPayloadV2ResponseExecutionPayload :: !ExecutionPayloadV2 + -- ^ executionPayload: ExecutionPayloadV2 + , _getPayloadV2ResponseBlockValue :: !BlockValue + -- ^ blockValue : QUANTITY, 256 Bits - The expected value to be received + -- by the feeRecipient in wei + } + deriving (Show, Eq, Generic) + +getPayloadV2ResponseProperties + :: KeyValue e kv + => GetPayloadV2Response + -> [kv] +getPayloadV2ResponseProperties o = + [ "executionPayload" .= _getPayloadV2ResponseExecutionPayload o + , "blockValue" .= _getPayloadV2ResponseBlockValue o + ] + +instance ToJSON GetPayloadV2Response where + toEncoding = pairs . mconcat . getPayloadV2ResponseProperties + toJSON = object . getPayloadV2ResponseProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON GetPayloadV2Response where + parseJSON = withObject "GetPayloadV2Response" $ \o -> GetPayloadV2Response + <$> o .: "executionPayload" + <*> o .: "blockValue" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Engine Get Payload V3 (Cancun) + +type Engine_GetPayloadV3 = "engine_getPayloadV3" + +-- | Engine Get Payload V3 +-- +-- The response of this method is extended with BlobsBundleV1 containing the +-- blobs, their respective KZG commitments and proofs corresponding to the +-- versioned_hashes included in the blob transactions of the execution payload. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_getpayloadv3 +-- +instance JsonRpcMethod "engine_getPayloadV3" where + type MethodRequest "engine_getPayloadV3" = [PayloadId] + type MethodResponse "engine_getPayloadV3" = GetPayloadV3Response + type ServerErrors "engine_getPayloadV3" = EngineServerErrors + type ApplicationErrors "engine_getPayloadV3" = EngineErrors + responseTimeoutMs = Just 1000 + methodErrors = + [ ApplicationError UnknownPayload + , ApplicationError UnsupportedFork + ] + +-- | Engine Get Payload V2 Response +-- +-- error: code and message set in case an exception happens while getting the +-- payload. +-- +-- * blobsBundle: BlobsBundleV1 - Bundle with data corresponding to blob +-- transactions included into executionPayload +-- * shouldOverrideBuilder : BOOLEAN - Suggestion from the execution layer to +-- use this executionPayload instead of an externally provided one +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#response-2 +-- +data GetPayloadV3Response = GetPayloadV3Response + { _getPayloadV3ResponseExecutionPayload :: !ExecutionPayloadV3 + -- ^ executionPayload: ExecutionPayloadV2 + , _getPayloadV3ResponseBlockValue :: !BlockValue + -- ^ blockValue : QUANTITY, 256 Bits - The expected value to be received + -- by the feeRecipient in wei + , _getPayloadV3ResponseBlobsBundle :: !BlobsBundleV1 + -- ^ blobsBundle: BlobsBundleV1 - Bundle with data corresponding to blob + -- transactions included into executionPayload + , _getPayloadV3ResponseShouldOverrideBuilder :: !Bool + -- ^ shouldOverrideBuilder : BOOLEAN - Suggestion from the execution layer to + -- use this executionPayload instead of an externally provided one + } + deriving (Show, Eq, Generic) + +getPayloadV3ResponseProperties + :: KeyValue e kv + => GetPayloadV3Response + -> [kv] +getPayloadV3ResponseProperties o = + [ "executionPayload" .= _getPayloadV3ResponseExecutionPayload o + , "blockValue" .= _getPayloadV3ResponseBlockValue o + , "blobsBundle" .= _getPayloadV3ResponseBlobsBundle o + , "shouldOverrideBuilder" .= _getPayloadV3ResponseShouldOverrideBuilder o + ] + +instance ToJSON GetPayloadV3Response where + toEncoding = pairs . mconcat . getPayloadV3ResponseProperties + toJSON = object . getPayloadV3ResponseProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON GetPayloadV3Response where + parseJSON = withObject "GetPayloadV3Response" $ \o -> GetPayloadV3Response + <$> o .: "executionPayload" + <*> o .: "blockValue" + <*> o .: "blobsBundle" + <*> o .: "shouldOverrideBuilder" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Engine New Payload V1 (Paris) + +-- | Engine New Payload V1 +-- +-- The purpose of this method to inform the execution layer about a new payload +-- without demanding validation in case that it is not extending the current +-- head of the canonical chain. In any case, as long as the hash is valid and +-- ancestors are present, the payload is accepted and cached for possible future +-- validation. +-- +-- timeout: 8s + +-- -------------------------------------------------------------------------- -- +-- Authentication + +-- | JSON Web Token (JWT) HMAC+SHA256 (HS256) Secret +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md#jwt-specifications +-- +newtype JwtSecret = JwtSecret { _jwtSecret :: BytesN 32 } + deriving (Show, Eq, Ord, Generic) + deriving (ToJSON, FromJSON) via (JsonTextRepresentation "JwtSecret" JwtSecret) + +instance HasTextRepresentation JwtSecret where + toText = T.drop 2 . toText . HexBytes . _jwtSecret + fromText = fmap (JwtSecret . fromHexBytes) . fromText . ("0x" <>) + +-- | JSON Web Token (JWT) used by the Ethereum Engine API +-- +-- The only mandatory claim is the "iat" claim. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md#jwt-claims +-- +getJwtToken :: JwtSecret -> IO T.Text +getJwtToken secret = jwtToken secret <$> getPOSIXTime + +jwtToken :: JwtSecret -> POSIXTime -> T.Text +jwtToken (JwtSecret secret) timestamp = + T.intercalate "." [header, claim, signature] + where + header = b64 "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" + claim = b64 $ "{\"iat\":" <> sshow (round @_ @Natural timestamp) <> "}" + signature = b64 + $ BA.convert + $ hmac @_ @_ @SHA256 (bytes secret) + $ T.encodeUtf8 + $ T.intercalate "." [header, claim] + b64 = encodeB64UrlNoPaddingText + +-- | Default Engine Context +-- +mkSimpleEngineCtx :: JwtSecret -> IO JsonRpcHttpCtx +mkSimpleEngineCtx secret = do + mgr <- HTTP.newManager HTTP.defaultManagerSettings + return $ JsonRpcHttpCtx + { _jsonRpcHttpCtxManager = mgr + , _jsonRpcHttpCtxURI = [uri|http://localhost:8551|] + , _jsonRpcHttpCtxMakeBearerToken = Just (getJwtToken secret) + } + +-- | Create a new Engine Context +-- +-- This initializes a new connection manager for this context. Hence, there +-- should only ever be a single context used with a given URI. +-- +mkEngineCtx :: JwtSecret -> URI -> IO JsonRpcHttpCtx +mkEngineCtx secret u = do + mgr <- HTTP.newManager HTTP.defaultManagerSettings + return $ JsonRpcHttpCtx + { _jsonRpcHttpCtxManager = mgr + , _jsonRpcHttpCtxURI = u + , _jsonRpcHttpCtxMakeBearerToken = Just (getJwtToken secret) + } + +-- -------------------------------------------------------------------------- -- +-- Example +-- +-- >>> Just s = fromText @JwtSecret "10b45e8907ab12dd750f688733e73cf433afadfd2f270e5b75a6b8fff22dd352" +-- >>> ctx <- mkSimpleEngineCtx s +-- >>> Just hdr <- callMethodHttp @"eth_getBlockByNumber" ctx (DefaultBlockLatest, False) +-- >>> let b = blockHash hdr +-- >>> callMethodHttp @"engine_forkchoiceUpdatedV3" ctx $ ForkchoiceUpdatedV3Request (ForkchoiceStateV1 b b b) Nothing +-- diff --git a/src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs b/src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs new file mode 100644 index 0000000000..3345e4140e --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs @@ -0,0 +1,186 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} + +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.EthRpcAPI +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.EVM.EthRpcAPI +( mkRpcCtx +, SyncingStatus(..) + +-- * RPC Methods +, type Eth_ChainId +, type Eth_BlockNumber +, type Eth_GetBlockByNumber +, type Eth_GetBlockByHash +, type Eth_Syncing +, type Eth_Call +) where + +import Chainweb.PayloadProvider.EVM.Header +import Chainweb.PayloadProvider.EVM.JsonRPC +import Chainweb.PayloadProvider.EVM.Utils + +import Control.Applicative + +import Data.Aeson +import Data.ByteString qualified as B +import Data.Void + +import Ethereum.Misc +import Ethereum.Transaction (Transaction) +import Ethereum.Utils + +import GHC.Generics + +import Network.HTTP.Client qualified as HTTP +import Network.URI.Static (uri) + +-- -------------------------------------------------------------------------- -- +-- Errors + +-- * -39001: Unknown block + +-- -------------------------------------------------------------------------- -- + +-- | Eth Block Number +-- +instance JsonRpcMethod "eth_blockNumber" where + type MethodRequest "eth_blockNumber" = Maybe Void + type MethodResponse "eth_blockNumber" = BlockNumber + type ServerErrors "eth_blockNumber" = Int + type ApplicationErrors "eth_blockNumber" = Int + responseTimeoutMs = Nothing + methodErrors = [] + +type Eth_BlockNumber = "eth_blockNumber" + +instance JsonRpcMethod "eth_chainId" where + type MethodRequest "eth_chainId" = Maybe Void + type MethodResponse "eth_chainId" = ChainId + type ServerErrors "eth_chainId" = Int + type ApplicationErrors "eth_chainId" = Int + responseTimeoutMs = Nothing + methodErrors = [] + +type Eth_ChainId = "eth_chainId" + +-- | Get ETH block header by its block number +-- +-- The second ('Bool') parameter indicates whether the full payload of the block +-- is returned or just its header. +-- +instance JsonRpcMethod "eth_getBlockByNumber" where + type MethodRequest "eth_getBlockByNumber" = (DefaultBlockParameter, Bool) + type MethodResponse "eth_getBlockByNumber" = Maybe Header + type ServerErrors "eth_getBlockByNumber" = Int + type ApplicationErrors "eth_getBlockByNumber" = Int + responseTimeoutMs = Nothing + methodErrors = [] + +type Eth_GetBlockByNumber = "eth_getBlockByNumber" + +instance JsonRpcMethod "eth_getBlockByHash" where + type MethodRequest "eth_getBlockByHash" = (BlockHash, Bool) + type MethodResponse "eth_getBlockByHash" = Maybe Header + type ServerErrors "eth_getBlockByHash" = Int + type ApplicationErrors "eth_getBlockByHash" = Int + responseTimeoutMs = Nothing + methodErrors = [] + +type Eth_GetBlockByHash = "eth_getBlockByHash" + +-- -------------------------------------------------------------------------- -- +-- | Returns the current sync status or false + +-- | Returns an object with data about the sync status or false +-- +instance JsonRpcMethod "eth_syncing" where + type MethodRequest "eth_syncing" = Maybe Void + type MethodResponse "eth_syncing" = SyncingStatus + type ServerErrors "eth_syncing" = Int + type ApplicationErrors "eth_syncing" = Int + responseTimeoutMs = Nothing + methodErrors = [] + +type Eth_Syncing = "eth_syncing" + +data SyncingStatus + = SyncingStatus + { _syncingStatusStartingBlock :: BlockNumber + , _syncingStatusCurrentBlock :: BlockNumber + , _syncingStatusHighestBlock :: BlockNumber + } + | SyncingStatusFalse + deriving (Show, Eq, Ord, Generic) + +instance ToJSON SyncingStatus where + toEncoding SyncingStatusFalse = toEncoding False + toEncoding o = pairs + ( "startingBlock" .= _syncingStatusStartingBlock o + <> "currentBlock" .= _syncingStatusCurrentBlock o + <> "highestBlock" .= _syncingStatusHighestBlock o + ) + toJSON SyncingStatusFalse = toJSON False + toJSON o = object + [ "startingBlock" .= _syncingStatusStartingBlock o + , "currentBlock" .= _syncingStatusCurrentBlock o + , "highestBlock" .= _syncingStatusHighestBlock o + ] + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON SyncingStatus where + parseJSON v = notSyncing v <|> syncStatus v + where + notSyncing = withBool "SyncingStatus" $ \b -> if not b + then return SyncingStatusFalse + else fail "expected 'false' or object" + syncStatus = withObject "SyncingStatus" $ \o -> SyncingStatus + <$> o .: "startingBlock" + <*> o .: "currentBlock" + <*> o .: "highestBlock" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- + +instance JsonRpcMethod "eth_call" where + type MethodRequest "eth_call" = (Transaction, DefaultBlockParameter) + type MethodResponse "eth_call" = HexBytes B.ByteString + type ServerErrors "eth_call" = Int + type ApplicationErrors "eth_call" = Int + responseTimeoutMs = Nothing + methodErrors = [] + +type Eth_Call = "eth_call" + +-- -------------------------------------------------------------------------- -- +-- | Default Engine Context +-- + +mkRpcCtx :: IO JsonRpcHttpCtx +mkRpcCtx = do + mgr <- HTTP.newManager HTTP.defaultManagerSettings + return $ JsonRpcHttpCtx + { _jsonRpcHttpCtxManager = mgr + , _jsonRpcHttpCtxURI = [uri|http://localhost:8545|] + , _jsonRpcHttpCtxMakeBearerToken = Nothing + } + +-- -------------------------------------------------------------------------- -- +-- Example + +-- ghci> rpcCtx <- mkRpcCtx +-- ghci> Just hdr <- callMethodHttp @"eth_getBlockByNumber" rpcCtx (DefaultBlockLatest, False) +-- diff --git a/src/Chainweb/PayloadProvider/EVM/JsonRPC.hs b/src/Chainweb/PayloadProvider/EVM/JsonRPC.hs new file mode 100644 index 0000000000..098d4e0e30 --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/JsonRPC.hs @@ -0,0 +1,375 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE LambdaCase #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.JsonRPC +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- JSON RPC 2.0 over HTTP +-- +-- This is a simplified implementation of JSON RPC 2.0 for use with the +-- Ethereum JSON RPC API. It does not support all features of general JSON RPC. +-- In particular this implementation does not support "Notifications". +-- +-- For the full JSON RPC 2.0 specification see: +-- https://www.jsonrpc.org/specification +-- For the Ethereum JSON RPC API see: +-- https://ethereum.org/en/developers/docs/apis/json-rpc/ +-- +module Chainweb.PayloadProvider.EVM.JsonRPC +( Message(..) +, UnknownErrorCodeException(..) +, HasErrorCode(..) +, ErrorCode(..) +, Error(..) +, Response(..) +, JsonRpcMethodConstraint +, JsonRpcMethod(..) +, type JsonRpcResponse +, type JsonRpcMessage +, JsonRpcHttpCtx(..) +, methodText +, callMethodHttp +) where + +import Control.DeepSeq (NFData) +import Control.Monad +import Control.Monad.Catch + +import Data.Aeson hiding (Error) +import Data.Kind +import Data.Text qualified as T + +import Ethereum.Utils + +import GHC.Generics (Generic) +import GHC.TypeLits +import qualified Network.HTTP.Client as HTTP +import qualified Network.HTTP.Types as HTTP +import qualified Data.ByteString.Lazy as BL +import Chainweb.Utils (EncodingException(DecodeException)) +import Data.Typeable (Typeable) +import System.Random (randomRIO) +import Network.URI +import qualified Data.Text.Encoding as T +import Control.Applicative + +-- -------------------------------------------------------------------------- -- +-- JSON RPC 2.0 Message + +-- | JSON RPC 2.0 Message for use with the Ethereum JSON RPC API. +-- +-- A rpc call is represented by sending a Request object to a Server. +-- +-- The type parameter must be an instance of 'ToJSON' and 'FromJSON'. For the +-- generic case 'Value' can be used. If the value is supposed to be omitted the +-- type 'Maybe Void' can be used. An empty parameter list can be represented as +-- '()'. An option value can be represented as 'Maybe a'. +-- +data Message v = Message + { _messageId :: !(Maybe Natural) + -- ^ An identifier established by the Client that MUST contain a String, + -- Number, or NULL value if included. If it is not included it is + -- assumed to be a notification. The value SHOULD normally not be Null + -- and Numbers SHOULD NOT contain fractional parts. + -- + -- A Notification is a Request object without an "id" member. A Request + -- object that is a Notification signifies the Client's lack of interest + -- in the corresponding Response object, and as such no Response object + -- needs to be returned to the client. The Server MUST NOT reply to a + -- Notification, including those that are within a batch request. + , _messageMethod :: !T.Text + -- ^ A String containing the name of the method to be invoked. Method + -- names that begin with the word rpc followed by a period character + -- (U+002E or ASCII 46) are reserved for rpc-internal methods and + -- extensions and MUST NOT be used for anything else. + , _messageParams :: !v + -- ^ A Structured value that holds the parameter values to be used + -- during the invocation of the method. This member MAY be omitted. + } + deriving (Show, Eq, Generic) + deriving anyclass (NFData) + +instance ToJSON v => ToJSON (Message v) where + toEncoding o = pairs + $ "jsonrpc" .= ("2.0" :: T.Text) + <> "id" .= _messageId o + <> "method" .= _messageMethod o + <> "params" .= _messageParams o + {-# INLINE toEncoding #-} + + toJSON o = object + [ "jsonrpc" .= ("2.0" :: T.Text) + , "id" .= _messageId o + , "method" .= _messageMethod o + , "params" .= _messageParams o + ] + {-# INLINE toJSON #-} + +instance FromJSON v => FromJSON (Message v) where + parseJSON = withObject "Message" $ \o -> do + version <- o .: "jsonrpc" >>= withText "jsonrpc" return + unless (version == "2.0") $ fail "invalid jsonrpc version" + Message + <$> o .:! "id" + <*> o .: "method" + <*> o .: "params" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- JSON RPC 2.0 Response Errors + +newtype UnknownErrorCodeException = UnknownErrorCodeException Int + deriving (Show, Eq) + +instance Exception UnknownErrorCodeException + +-- | JSON RPC 2.0 Error Codes +-- +-- The first type parameter is the type of server errors. The second type is the +-- type of application errors. For the generic case 'Int' can be used for both +-- parameters. +-- +data ErrorCode s a + = ParseError + | InvalidRequest + | MethodNotFound + | InvalidParams + | InternalError + | ServerError s + | ApplicationError a + deriving (Show, Eq, Generic) + deriving anyclass (NFData) + +class HasErrorCode e where + toErrorCode :: e -> Int + fromErrorCode :: MonadThrow m => Int -> m e + +instance HasErrorCode Int where + toErrorCode = id + fromErrorCode = return + {-# INLINE toErrorCode #-} + {-# INLINE fromErrorCode #-} + +instance (HasErrorCode s, HasErrorCode a) => HasErrorCode (ErrorCode s a) where + toErrorCode ParseError = -32700 + toErrorCode InvalidRequest = -32600 + toErrorCode MethodNotFound = -32601 + toErrorCode InvalidParams = -32602 + toErrorCode InternalError = -32603 + toErrorCode (ServerError s) = toErrorCode s + toErrorCode (ApplicationError a) = toErrorCode a + fromErrorCode (-32700) = return ParseError + fromErrorCode (-32600) = return InvalidRequest + fromErrorCode (-32601) = return MethodNotFound + fromErrorCode (-32602) = return InvalidParams + fromErrorCode (-32603) = return InternalError + fromErrorCode n + | (-32099) <= n && n <= (-32000) = ServerError <$> fromErrorCode n + | otherwise = ApplicationError <$> fromErrorCode n + {-# INLINE toErrorCode #-} + {-# INLINE fromErrorCode #-} + +instance (HasErrorCode s, HasErrorCode a) => ToJSON (ErrorCode s a) where + toEncoding = toEncoding . toErrorCode + toJSON = toJSON . toErrorCode + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance (HasErrorCode s, HasErrorCode a) => FromJSON (ErrorCode s a) where + parseJSON = withScientific "ErrorCode" $ \n -> do + n' <- parseJSON @Int (Number n) + case fromErrorCode n' of + Just e -> return e + Nothing -> fail $ "unrecognized error code: " <> show n' + {-# INLINE parseJSON #-} + +data Error e a = Error + { _errorCode :: !(ErrorCode e a) + , _errorMessage :: !T.Text + , _errorData :: !(Maybe Value) + } + deriving (Show, Eq, Generic) + deriving anyclass (NFData) + +instance (Show e, Typeable e, Show a, Typeable a) => Exception (Error e a) + +instance (HasErrorCode s, HasErrorCode a) => ToJSON (Error s a) where + toEncoding o = pairs + $ "code" .= _errorCode o + <> "message" .= _errorMessage o + <> "data" .= _errorData o + {-# INLINE toEncoding #-} + + toJSON o = object + [ "code" .= _errorCode o + , "message" .= _errorMessage o + , "data" .= _errorData o + ] + {-# INLINE toJSON #-} + +instance (HasErrorCode s, HasErrorCode a) => FromJSON (Error s a) where + parseJSON = withObject "Error" $ \o -> Error + <$> o .: "code" + <*> o .: "message" + <*> o .:? "data" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- JSON RPC 2.0 Response + +data Response s a v = Response + { _responseId :: !Int + , _responseResult :: !(Either (Error s a) v) + } + deriving (Show, Eq, Generic) + deriving anyclass (NFData) + +instance (HasErrorCode s, HasErrorCode a, ToJSON v) => ToJSON (Response s a v) where + toEncoding o = pairs + $ "jsonrpc" .= ("2.0" :: T.Text) + <> "id" .= _responseId o + <> case _responseResult o of + Left e -> "error" .= e + Right v -> "result" .= v + {-# INLINE toEncoding #-} + + toJSON o = object + [ "jsonrpc" .= ("2.0" :: T.Text) + , "id" .= _responseId o + , case _responseResult o of + Left e -> "error" .= e + Right v -> "result" .= v + ] + {-# INLINE toJSON #-} + +instance (HasErrorCode s, HasErrorCode a, FromJSON v) => FromJSON (Response s a v) where + parseJSON = withObject "Response" $ \o -> do + version <- o .: "jsonrpc" >>= withText "jsonrpc" return + unless (version == "2.0") $ fail "invalid jsonrpc version" + Response + <$> o .: "id" + <*> (parseResult o <|> parseError o) + where + parseError o = Left <$> o .: "error" + parseResult o = Right <$> o .: "result" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- JSON RPC 2.0 Method + +type JsonRpcMethodConstraint (a :: Symbol) = + ( KnownSymbol a + , ToJSON (MethodRequest a) + , ToJSON (MethodResponse a) + , FromJSON (MethodRequest a) + , FromJSON (MethodResponse a) + , HasErrorCode (ServerErrors a) + , HasErrorCode (ApplicationErrors a) + , Show (ServerErrors a) + , Show (ApplicationErrors a) + , Typeable (ServerErrors a) + , Typeable (ApplicationErrors a) + ) + +-- | We could implement function overloading by making the request type +-- parameter of the class. +-- +class JsonRpcMethodConstraint a => JsonRpcMethod (a :: Symbol) where + type MethodRequest a :: Type + type MethodResponse a :: Type + type ServerErrors a :: Type + type ApplicationErrors a :: Type + methodErrors :: [ErrorCode (ServerErrors a) (ApplicationErrors a)] + responseTimeoutMs :: Maybe Natural + +type JsonRpcResponse m = Response (ServerErrors m) (ApplicationErrors m) (MethodResponse m) +type JsonRpcMessage m = Message (MethodRequest m) + +methodText :: forall a . JsonRpcMethod a => T.Text +methodText = T.pack $ symbolVal_ @a +{-# INLINE methodText #-} + +mkMessage + :: forall (a :: Symbol) + . JsonRpcMethod a + => Int + -> MethodRequest a + -> JsonRpcMessage a +mkMessage i p = Message + { _messageId = Just $ fromIntegral i + , _messageMethod = methodText @a + , _messageParams = p + } + +-- -------------------------------------------------------------------------- -- +-- JSON RPC Context + +data JsonRpcHttpCtx = JsonRpcHttpCtx + { _jsonRpcHttpCtxManager :: HTTP.Manager + , _jsonRpcHttpCtxURI :: URI + , _jsonRpcHttpCtxMakeBearerToken :: Maybe (IO T.Text) + } + +-- -------------------------------------------------------------------------- -- + +-- | Call a JSON RPC 2.0 method over HTTP +-- +-- FIXME: Proper Error Handling +-- +callMethodHttp + :: forall (m :: Symbol) + . JsonRpcMethod m + => JsonRpcHttpCtx + -> MethodRequest m + -> IO (MethodResponse m) +callMethodHttp ctx m = do + req_ <- HTTP.requestFromURI (_jsonRpcHttpCtxURI ctx) + mid <- randomRIO (1, maxBound) + authHeader <- sequence (_jsonRpcHttpCtxMakeBearerToken ctx) >>= \case + Nothing -> return [] + Just t -> return [("Authorization", "Bearer " <> T.encodeUtf8 t)] + let msg = encode $ mkMessage @m mid m + let req = req_ + { HTTP.method = "POST" + , HTTP.requestBody = HTTP.RequestBodyLBS msg + , HTTP.responseTimeout = timeout + , HTTP.requestHeaders = + [ ("Content-Type", "application/json") + , ("Accept", "application/json") + ] + <> authHeader + } + resp <- HTTP.httpLbs req (_jsonRpcHttpCtxManager ctx) + unless (HTTP.statusIsSuccessful $ HTTP.responseStatus resp) $ + throwM + $ HTTP.HttpExceptionRequest req + $ HTTP.StatusCodeException resp { HTTP.responseBody = () } + (BL.toStrict $ HTTP.responseBody resp) + case eitherDecode $ HTTP.responseBody resp of + Left e -> throwM $ DecodeException + $ T.pack e <> " -- " <> T.decodeUtf8 (BL.toStrict (HTTP.responseBody resp)) + Right (Response _ r :: JsonRpcResponse m) -> case r of + Left e -> throwM e + Right v -> return v + where + timeout = case responseTimeoutMs @m of + Nothing -> HTTP.responseTimeoutDefault + Just ms -> HTTP.responseTimeoutMicro $ int $ ms * 1000 From a09e66dfaecc56f998f6676710e70cc23c473c8b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 20:15:53 -0800 Subject: [PATCH 076/378] Add PayloadProvider.EVM module --- chainweb.cabal | 1 + src/Chainweb/PayloadProvider/EVM.hs | 1243 +++++++++++++++++++++++++++ 2 files changed, 1244 insertions(+) create mode 100644 src/Chainweb/PayloadProvider/EVM.hs diff --git a/chainweb.cabal b/chainweb.cabal index 5b81a9147a..4d5aa5dca6 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -226,6 +226,7 @@ library , Chainweb.Payload.RestAPI.Server , Chainweb.Payload.RestAPI.Client , Chainweb.PayloadProvider + , Chainweb.PayloadProvider.EVM , Chainweb.PayloadProvider.EVM.EngineAPI , Chainweb.PayloadProvider.EVM.EthRpcAPI , Chainweb.PayloadProvider.EVM.JsonRPC diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs new file mode 100644 index 0000000000..1bbf07e573 --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -0,0 +1,1243 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TemplateHaskell #-} + +{-# OPTIONS_GHC -Wprepositive-qualified-module #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.EVM +( EvmProviderConfig(..) +, defaultEvmProviderConfig + +-- * EVM Payload Provider Implementation +, EvmPayloadProvider(..) +, withEvmPayloadProvider +, evmPayloadDb +, evmPayloadQueue + +-- * Payload Provider API +, evmSyncToBlock + +) where + +import Chainweb.BlockCreationTime +import Chainweb.BlockHash qualified as Chainweb +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.ChainId +import Chainweb.Logger +import Chainweb.MinerReward +import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.EVM.EngineAPI +import Chainweb.PayloadProvider.EVM.EthRpcAPI +import Chainweb.PayloadProvider.EVM.Header qualified as EVM +import Chainweb.PayloadProvider.EVM.HeaderDB qualified as EvmDB +import Chainweb.PayloadProvider.EVM.JsonRPC (JsonRpcHttpCtx, callMethodHttp) +import Chainweb.PayloadProvider.EVM.JsonRPC qualified as JsonRpc +import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) +import Chainweb.PayloadProvider.EVM.Utils qualified as EVM +import Chainweb.PayloadProvider.EVM.Utils qualified as Utils +import Chainweb.PayloadProvider.P2P +import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest +import Chainweb.Storage.Table +import Chainweb.Storage.Table.HashMap +import Chainweb.Storage.Table.RocksDB +import Chainweb.Time +import Chainweb.Utils +import Chainweb.Version +import Control.Concurrent +import Control.Concurrent.Async +import Control.Concurrent.STM +import Control.Lens hiding ((.=)) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.IO.Class +import Data.ByteString.Short qualified as BS +import Data.LogMessage +import Data.Maybe +import Data.PQueue +import Data.Singletons +import Data.Text qualified as T +import Ethereum.Misc +import Ethereum.Misc qualified as EVM +import Ethereum.Misc qualified as Ethereum +import Ethereum.RLP +import GHC.Generics (Generic) +import GHC.TypeNats (fromSNat) +import Network.HTTP.Client qualified as HTTP +import Network.URI +import Network.URI.Static +import P2P.Session (ClientEnv) +import P2P.TaskQueue +import System.LogLevel +import Configuration.Utils + +-- -------------------------------------------------------------------------- -- +-- Types (to keep the code clean and avoid confusion) + +-- Might be a usecase for backpack, if we want to go down that route ... +-- +-- Though doing it directly is fine, too. + +type Payload = EVM.Header +type PayloadDb tbl = EvmDB.HeaderDb tbl + +initPayloadDb :: EvmDB.Configuration -> IO (EvmDB.HeaderDb_ a RocksDbTable) +initPayloadDb = EvmDB.initHeaderDb + +payloadDbConfiguration + :: HasChainwebVersion v + => HasChainId c + => v + -> c + -> RocksDb + -> Payload + -> EvmDB.Configuration +payloadDbConfiguration = EvmDB.configuration + +-- Or maybe we could bring into scope the IsPayloadProviderClass? That would +-- help with other issues, too. +-- +-- type Payload = PayloadType EvmPayloadProvider + +-- -------------------------------------------------------------------------- -- + +-- FIXME +-- +data EvmProviderConfig = EvmProviderConfig + { _evmConfEngineUri :: !URI + , _evmConfEngineJwtSecret :: !JwtSecret + , _evmConfMinerInfo :: !(Maybe EVM.Address) + } + deriving (Show, Eq, Generic) + +makeLenses ''EvmProviderConfig + +defaultEvmProviderConfig :: EvmProviderConfig +defaultEvmProviderConfig = EvmProviderConfig + { _evmConfEngineUri = [uri|http://localhost:8551|] + , _evmConfEngineJwtSecret = unsafeFromText "10b45e8907ab12dd750f688733e73cf433afadfd2f270e5b75a6b8fff22dd352" + -- FIXME + , _evmConfMinerInfo = Nothing + } + +instance ToJSON EvmProviderConfig where + toJSON o = object + [ "engineUri" .= _evmConfEngineUri o + , "engineJwtSecret" .= _evmConfEngineJwtSecret o + , "minerAddress" .= _evmConfMinerInfo o + ] + +instance FromJSON (EvmProviderConfig -> EvmProviderConfig) where + parseJSON = withObject "EvmProviderConfig" $ \o -> id + <$< evmConfEngineUri ..: "engineUri" % o + <*< evmConfEngineJwtSecret ..: "engineJwtSecret" % o + <*< evmConfMinerInfo ..: "minerAddress" % o + +pEvmProviderConfig :: MParser EvmProviderConfig +pEvmProviderConfig = id + <$< _evmConfEngineUri .:: pHostAddress + <*< _evmConfEngineJwtSecret .:: pJwtSecret + <*< _evmConfMinerInfo .:: pMinerInfo + + + +-- -------------------------------------------------------------------------- -- +-- EVM Payload Provider + +-- | EVM Payload Provider +-- +-- The EVM EL has to perform the following Chainweb specific validation tasks +-- on each EL header: +-- +-- 1. The miner reward in coinbase is correct +-- 2. The payload hash is the Merkle root of the EL header. +-- 3. EL time = CL parent time +-- +data EvmPayloadProvider logger = EvmPayloadProvider + { _evmChainwebVersion :: !ChainwebVersion + , _evmChainId :: !ChainId + , _evmLogger :: !logger + , _evmState :: !(TVar ConsensusState) + -- ^ The current sync state of the EVM EL. + + , _evmPayloadStore :: !(PayloadStore (PayloadDb RocksDbTable) Payload) + -- ^ The BlockPayloadHash in the ConsensusState is different from the + -- EVM BlockHash that the EVM knows about. + -- + -- For new blocks that are not yet known to the EVM we need to provide + -- the EVM BlockHash. We compute this hash from the respective EVM + -- Header. The header is queried via the Chainweb consensus P2P (which + -- is also used to synchronize Pact payloads). + -- + -- After validating the EVM header against the corresponding evaluation + -- context we call engine_forkchoiceUpdated on it which synchronizes the + -- EVM to the respective block. After that we can obtain the EVM header + -- of the canonical chain from the EVM via JSON RPC API via the block + -- number. + -- + -- However, it is not clear how we can obtain non-canonical EVM headers + -- without knowing their EVM block hashes. This is needed to resolve + -- reorgs without having the synchronize the headers again from the P2P + -- network. For that reason we also stored the EVM headers (redundantly) + -- in the Payload Provider. + -- + -- Strictly, just storing the mapping from Chainweb BlockPayloadHash to + -- the EVM BlockHash would be sufficient to resolve reorgs (for headers + -- which had been validated against the evaluation context before). But + -- for simplicity we store the full EVM Header indexed by block height + -- and block payload hash. + -- + -- In the future we may consider teaching the EVM about Chainweb + -- BlockPayloadHashes. + , _evmCandidatePayloads :: !(HashMapTable RankedBlockPayloadHash Payload) + -- ^ FIXME: should this be moved into the Payload Store? + -- + -- For now we just prune after each successful syncToBlock. Consensus + -- has its own candidate cache with a broader (cut processing) scope. + + , _evmEngineCtx :: !JsonRpcHttpCtx + -- ^ The JSON RPC context that provides the connection the Engine API of + -- the EVM. + + -- Mining: + , _evmMinerInfo :: !(Maybe Ethereum.Address) + , _evmPayloadId :: !(TMVar (T2 SyncState PayloadId)) + , _evmPayloadVar :: !(TMVar (T2 EVM.BlockHash NewPayload)) + } + +stateIO :: EvmPayloadProvider logger -> IO ConsensusState +stateIO = readTVarIO . _evmState + +evmPayloadDb :: Getter (EvmPayloadProvider l) (PayloadDb RocksDbTable) +evmPayloadDb = to (_payloadStoreTable . _evmPayloadStore) + +evmPayloadQueue :: Getter (EvmPayloadProvider l) (PQueue (Task ClientEnv Payload)) +evmPayloadQueue = to (_payloadStoreQueue . _evmPayloadStore) + +instance HasChainwebVersion (EvmPayloadProvider logger) where + _chainwebVersion = _evmChainwebVersion + +instance HasChainId (EvmPayloadProvider logger) where + _chainId = _evmChainId + +-- | Expose the /local/ payload table. +-- +-- NOTE, this does /not/ look for payloads in P2P network. Data available in +-- this table is guaranteed to be fully validated, both by the execution client +-- as well as against the evaluation context. +-- +instance + ReadableTable (EvmPayloadProvider logger) RankedBlockPayloadHash Payload + where + tableLookup = tableLookup . _evmPayloadStore + tableLookupBatch' s = tableLookupBatch' (_evmPayloadStore s) + tableMember = tableMember . _evmPayloadStore + +-- | Expose the /local/ payload table. +-- +-- NOTE, this does /not/ look for payloads in P2P network. Data available in +-- this table is guaranteed to be fully validated, both by the execution client +-- as well as against the evaluation context. +-- +instance + Table (EvmPayloadProvider logger) RankedBlockPayloadHash Payload + where + tableInsert = tableInsert . _evmPayloadStore + tableInsertBatch s = tableInsertBatch (_evmPayloadStore s) + tableDelete s = tableDelete (_evmPayloadStore s) + tableDeleteBatch s = tableDeleteBatch (_evmPayloadStore s) + +lookupConsensusState + :: ReadableTable tbl RankedBlockPayloadHash Payload + => tbl + -> ConsensusState + -> IO (Maybe ForkchoiceStateV1) +lookupConsensusState p cs = do + r <- tableLookupBatch p + [ latestRankedBlockPayloadHash cs + , safeRankedBlockPayloadHash cs + , finalRankedBlockPayloadHash cs + ] + case r of + [Nothing, _, _] -> return Nothing + [Just l, Just s, Just f] -> return $ Just ForkchoiceStateV1 + { _forkchoiceHeadBlockHash = EVM._hdrHash l + , _forkchoiceSafeBlockHash = EVM._hdrHash s + , _forkchoiceFinalizedBlockHash = EVM._hdrHash f + } + x -> error "corrupted database" -- FIXME throw proper error + + -- FIXME: I guess this can happen with shallow nodes? + -- The invariant is that if a block is in the db all predecessors + -- are valid, but they are not necessarily available. + +-- -------------------------------------------------------------------------- -- +-- Exceptions + +data EvmExecutionEngineException + = EvmChainIdMissmatch (Expected EVM.ChainId) (Actual EVM.ChainId) + deriving (Show, Eq, Generic) + +instance Exception EvmExecutionEngineException + +-- | Raised when an EVM header ca nnot be found +data EvmHeaderNotFoundException + = EvmHeaderNotFoundByNumber DefaultBlockParameter + | EvmHeaderNotFoundByHash EVM.BlockHash + | EvmHeaderNotFoundByPayloadHash BlockPayloadHash + deriving (Eq, Show, Generic) +instance Exception EvmHeaderNotFoundException + +data InvalidEvmState + = EvmGenesisHeaderNotFound + deriving (Eq, Show, Generic) +instance Exception InvalidEvmState + +newtype UnexpectedForkchoiceUpdatedResponseException + = UnexpectedForkchoiceUpdatedResponseException PayloadStatusV1 + deriving (Eq, Show, Generic) +instance Exception UnexpectedForkchoiceUpdatedResponseException + +newtype ForkchoiceUpdatedTimeoutException = ForkchoiceUpdatedTimeoutException Micros + deriving (Eq, Show, Generic) +instance Exception ForkchoiceUpdatedTimeoutException + +-- | Thrown on an invalid payload status. +-- +newtype InvalidPayloadException = InvalidPayloadException T.Text + deriving (Eq, Show, Generic) +instance Exception InvalidPayloadException + +-- -------------------------------------------------------------------------- -- + +-- | Initializes a new EVM Payload provider +-- +-- Note, that an exception is raised if the execution client is not available. +-- +-- The function blocks while trying to connect to the execution client. It is +-- therefor advisable that this function is called asynchronously. +-- +-- FIXME: +-- Currently the Genesis Header is pull from the execution client. For +-- production chainweb versions the hash of the genesis header or even the full +-- header should be hard-coded in chainweb node and we should check on startup +-- if it matches with the header from the execution client. +-- +withEvmPayloadProvider + :: Logger logger + => HasChainwebVersion v + => HasChainId c + => logger + -> v + -> c + -> RocksDb + -> HTTP.Manager + -- ^ P2P Network manager. This is supposed to be shared among all P2P + -- network clients. + -- + -- It is /not/ used for communication with the execution engine client. + -> EvmProviderConfig + -> (EvmPayloadProvider logger -> IO a) + -> IO a +withEvmPayloadProvider logger v c rdb mgr conf f + | FromSing @_ @p (SEvmProvider ecid) <- payloadProviderTypeForChain v c = do + engineCtx <- mkEngineCtx (_evmConfEngineJwtSecret conf) (_evmConfEngineUri conf) + + SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal v + SomeChainIdT @c _ <- return $ someChainIdVal c + let pldCli h = Rest.payloadClient @v @c @p h + + genPld <- checkExecutionClient engineCtx (EVM.ChainId (fromSNat ecid)) + pdb <- initPayloadDb $ payloadDbConfiguration v c rdb genPld + store <- newPayloadStore mgr (logFunction pldStoreLogger) pdb pldCli + pldVar <- newEmptyTMVarIO + pldIdVar <- newEmptyTMVarIO + candidates <- emptyTable + stateVar <- newTVarIO (genesisState v c) + let p = EvmPayloadProvider + { _evmChainwebVersion = _chainwebVersion v + , _evmChainId = _chainId c + , _evmLogger = providerLogger + , _evmState = stateVar + , _evmPayloadStore = store + , _evmCandidatePayloads = candidates + , _evmEngineCtx = engineCtx + , _evmMinerInfo = _evmConfMinerInfo conf + , _evmPayloadId = pldIdVar + , _evmPayloadVar = pldVar + } + + result <- race (payloadListener p) $ do + logFunctionText providerLogger Info $ + "EVM payload provider started for Ethereum network id " <> sshow ecid + f p + case result of + Left () -> error "Chainweb.PayloadProvider.EVM.withEvmPayloadProvider: runForever (payloadListener p) exited unexpectedly" + Right x -> return x + + | otherwise = + error "Chainweb.PayloadProvider.Evm.configuration: chain does not use EVM provider" + where + providerLogger = setComponent "payload-provider" + $ addLabel ("provider", "evm") logger + pldStoreLogger = addLabel ("sub-component", "payloadStore") providerLogger + +payloadListener :: Logger logger => EvmPayloadProvider logger -> IO () +payloadListener p = runForever lf "EVM Provider Payload Listener" $ + awaitNewPayload p + where + lf = logFunction (_evmLogger p) + +-- | Checks the availability of the Execution Client +-- +-- - asserts API availability +-- - asserts the EVM chain id matches the chainweb version and chainweb chain id +-- - asserts that the Genesis header is available +-- +-- Returns the genesis header. +-- +checkExecutionClient + :: JsonRpcHttpCtx + -> EVM.ChainId + -- ^ expected Ethereum Network ID + -> IO Payload +checkExecutionClient ctx expectedEcid = do + ecid <- callMethodHttp @Eth_ChainId ctx Nothing + unless (expectedEcid == ecid) $ + throwM $ EvmChainIdMissmatch (Expected expectedEcid) (Actual ecid) + callMethodHttp @Eth_GetBlockByNumber ctx (DefaultBlockNumber 0, False) >>= \case + Nothing -> throwM EvmGenesisHeaderNotFound + Just h -> return h + +-- -------------------------------------------------------------------------- -- + +-- queryEngineState :: JsonRpcHttpCtx -> IO (Maybe ForkchoiceStateV1) +-- queryEngineState ctx = runConcurrently $ runMaybeT $ ForkchoiceStateV1 +-- <$> MaybeT (Concurrently $ getHashByNumber ctx DefaultBlockLatest) +-- <*> MaybeT (Concurrently $ getHashByNumber ctx DefaultBlockSafe) +-- <*> MaybeT (Concurrently $ getHashByNumber ctx DefaultBlockFinalized) + +-- -------------------------------------------------------------------------- -- +-- Engine Calls + +getHashByNumber + :: JsonRpcHttpCtx + -> DefaultBlockParameter + -> IO (Maybe EVM.BlockHash) +getHashByNumber ctx p = fmap EVM._hdrHash <$> + callMethodHttp @Eth_GetBlockByNumber ctx (p, False) + +-- | Obtains a block by number. It is an exception if the block can not be +-- found. +-- +-- Returns: EVM Header +-- +-- Throws: +-- +-- * EvmHeaderNotFoundException +-- * Eth RPC failures +-- * JSON RPC failures +-- +getBlockAtNumber + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdf + -> EVM.BlockNumber + -> m Payload +getBlockAtNumber p n = do + r <- liftIO $ JsonRpc.callMethodHttp + @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockNumber n, False) + case r of + Just h -> return h + Nothing -> throwM $ EvmHeaderNotFoundByNumber (DefaultBlockNumber n) + +-- | Returns the EVM genesis block. +-- +-- Returns: EVM Header +-- +-- Throws: +-- +-- * InvalidEvmState +-- * Eth RPC failures +-- * JSON RPC failures +-- +getGenesisHeader + :: MonadThrow m + => MonadCatch m + => MonadIO m + => EvmPayloadProvider pdf + -> m Payload +getGenesisHeader p = try (getBlockAtNumber p 0) >>= \case + Left (EvmHeaderNotFoundByNumber _) -> throwM EvmGenesisHeaderNotFound + Left e -> throwM e + Right x -> return x + +-- | Obtains a block by EVM block hash. It is an exception if the block can not +-- be found. +-- +-- Returns: EVM Header +-- +-- Throws: +-- +-- * EvmHeaderNotFoundException +-- * Eth RPC failures +-- * JSON RPC failures +-- +getBlockByHash + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdf + -> EVM.BlockHash + -> m Payload +getBlockByHash p h = do + r <- liftIO $ JsonRpc.callMethodHttp + @Eth_GetBlockByHash (_evmEngineCtx p) (h, False) + case r of + Just hdr -> return hdr + Nothing -> throwM $ EvmHeaderNotFoundByHash h + +-- | Updates the forkchoice state of the EVM. Does not initiate payload +-- production. +-- +-- This must be called only after validating the evaluation context for all +-- blocks that lead from the current state to the target state. +-- +-- When new payload production was requested the state of the payload id +-- variable is updated. Otherwise the state of the payload id variable is set to +-- empty. +-- +-- Returns: new EVM BlockHash +-- +-- Throws: +-- +-- * ForkchoiceUpdatedTimeoutException +-- * UnexpectedForkchoiceUpdatedResponseException +-- * InvalidPayloadException +-- +-- * Engine RPC failures +-- * Eth RPC failures +-- * JSON RPC failure +-- +forkchoiceUpdate + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdb + -> Micros + -- ^ Timeout in microseconds + -> Maybe (Chainweb.BlockHash, NewBlockCtx) + -- ^ Whether to start new payload production on to of the target block. + -> ForkchoiceStateV1 + -- ^ the requested fork choice state + -> m Payload +forkchoiceUpdate p t ph state = go t >>= getBlockByHash p + where + waitTime = Micros 500_000 + payloadAttributes = case ph of + Nothing -> Nothing + Just (h, nctx) -> Just $ mkPayloadAttributes p h nctx + go remaining + | remaining <= 0 = throwM $ ForkchoiceUpdatedTimeoutException t + | otherwise = do + r <- liftIO $ JsonRpc.callMethodHttp @Engine_ForkchoiceUpdatedV3 (_evmEngineCtx p) + (ForkchoiceUpdatedV3Request state payloadAttributes) + + -- FIXME: update payload ID variable + + case _forkchoiceUpdatedV1ResponsePayloadStatus r of + PayloadStatusV1 Valid (Just x) Nothing -> return x + PayloadStatusV1 Invalid Nothing (Just e) -> throwM $ InvalidPayloadException e + PayloadStatusV1 Syncing Nothing Nothing -> do + -- wait 500ms + liftIO $ threadDelay $ int waitTime + go (remaining - waitTime) + e -> throwM $ UnexpectedForkchoiceUpdatedResponseException e + +mkPayloadAttributes + :: EvmPayloadProvider logger + -> Chainweb.BlockHash + -- ^ The block hash of the block on which the new payload is created + -- This is available to the payload provider in the target consensus + -- state of the fork info of a syncToBlock call. + -> NewBlockCtx + -- The new block context from the fork info value. + -> PayloadAttributesV3 +mkPayloadAttributes p ph nctx = PayloadAttributesV3 + { _payloadAttributesV3parentBeaconBlockRoot = EVM.chainwebBlockHashToBeaconBlockRoot ph + , _payloadAttributesV2 = PayloadAttributesV2 + { _payloadAttributesV2Withdrawals = [withdrawal] + , _payloadAttributesV1 = PayloadAttributesV1 + { _payloadAttributesV1Timestamp = et + , _payloadAttributesV1SuggestedFeeRecipient = minerAddress + , _payloadAttributesV1PrevRandao = randao + } + } + } + where + MinerReward reward = _newBlockCtxMinerReward nctx + Just minerAddress = _evmMinerInfo p + withdrawal = WithdrawalV1 + { _withdrawalValidatorIndex = 0 + , _withdrawalIndex = 0 + , _withdrawalAmount = int $ reward + , _withdrawalAddress = minerAddress + } + + -- FIXME: are there an assumptions about this value? + randao = Utils.Randao (Ethereum.encodeLeN 0) + + -- Ethereum timestamps are measured in /seconds/ since POSIX epoch. Chainweb + -- block creation times are measured in /micro-seconds/ since POSIX epoch. + -- + -- Chainweb requires that block creation times are strictly monotonic. + -- This means that blocks may be less than one second appart and rounding + -- would result in two or more consequecutive Ethereum headers having the + -- same time. Is that legal? + -- + et = ethTimestamp (_newBlockCtxParentCreationTime nctx) + +-- FIXME +ethTimestamp :: BlockCreationTime -> Timestamp +ethTimestamp (BlockCreationTime (Time (TimeSpan (Micros t)))) = + EVM.Timestamp $ int $ (t `div` 1000000) + +-- -------------------------------------------------------------------------- -- + +-- fetchAndVerifyEvmHeader +-- :: EvmPayloadProvider pdb +-- -> EvaluationCtx +-- -> IO Payload +-- fetchAndVerifyEvmHeader p evalCtx = do +-- where + +-- | Obtain the EVM header for all entries of the Evaluation Ctx. +-- +-- The header is first looked up in the local payload. If it is not available +-- locally it is fetched in the P2P network, validated against the evaluation +-- context, and inserted into the database. +-- +-- getEvmHeader +-- :: EvmPayloadProvider pdb +-- -> RankedBlockPayloadHash +-- -> IO Payload +-- getEvmHeader p h = +-- tableLookup (_evmPayloadDb evm) (Just $ EVM._blockHeight h) payloadHash >>= \case +-- Nothing -> do +-- p <- fetchPayloadFromRemote +-- validateCtx evalCtx p +-- return p +-- Just p -> return p + +-- -------------------------------------------------------------------------- -- +-- Await New Payload + +data EvmNewPayloadExeception + = BlobsNotSupported + | InvalidNewPayloadHeight (Expected BlockHeight) (Actual BlockHeight) + | InconsistentNewPayloadFees + { _inconsistentPayloadBlockValue :: !BlockValue + , _inconsistentPayloadFees :: !Stu + } + | InconsistenNewPayloadHash (Expected EVM.BlockHash) (Actual EVM.BlockHash) + deriving (Show, Eq, Generic) + +instance Exception EvmNewPayloadExeception + +newPayloadRate :: Int +newPayloadRate = 1_000_000 + +-- | If a payload id is available, new payloads for it. +-- +awaitNewPayload :: EvmPayloadProvider logger -> IO () +awaitNewPayload p = do + T2 sstate pid <- atomically $ readTMVar (_evmPayloadId p) + resp <- JsonRpc.callMethodHttp @Engine_GetPayloadV3 ctx pid + + -- Response data + let v3 = _getPayloadV3ResponseExecutionPayload resp + v2 = _executionPayloadV2 v3 + v1 = _executionPayloadV1 v2 + h = EVM.numberToHeight $ _executionPayloadV1BlockNumber v1 + newEvmBlockHash = _executionPayloadV1BlockHash v1 + + -- check that this is a new payload + T2 curEvmBlockHash cur <- atomically $ readTMVar (_evmPayloadVar p) + unless (curEvmBlockHash == newEvmBlockHash) $ do + -- check that Blobs bundle is empty + unless (null $ _blobsBundleV1Blobs $ _getPayloadV3ResponseBlobsBundle resp) $ + throwM BlobsNotSupported + + -- Check that the block height matches the expected height + unless ( (_syncStateHeight sstate + 1) == h) $ + throwM $ InvalidNewPayloadHeight (Expected (_syncStateHeight sstate + 1)) (Actual h) + + -- Check that the fees of the execution paylod match the block value of the + -- response. + -- + unless (EVM._blockValueStu (_getPayloadV3ResponseBlockValue resp) == fees v1) $ + throwM InconsistentNewPayloadFees + { _inconsistentPayloadBlockValue = _getPayloadV3ResponseBlockValue resp + , _inconsistentPayloadFees = fees v1 + } + + let pld = executionPayloadV3ToHeader (_syncStateBlockHash sstate) v3 + + -- Check that the computed block hash matches the hash from the response + unless (newEvmBlockHash == EVM._hdrHash pld) $ + throwM $ InconsistenNewPayloadHash + (Expected newEvmBlockHash) + (Actual (EVM._hdrHash pld)) + + -- The actual payload header is included in the NewBlock structure in + -- as EncodedPayloadData. + atomically $ writeTMVar (_evmPayloadVar p) $ T2 newEvmBlockHash NewPayload + { _newPayloadTxCount = int $ length (_executionPayloadV1Transactions v1) + , _newPayloadSize = int $ sum $ (BS.length . _transactionBytes) + <$> (_executionPayloadV1Transactions v1) + , _newPayloadParentHeight = _syncStateHeight sstate + , _newPayloadParentHash = _syncStateBlockHash sstate + , _newPayloadOutputSize = 0 + , _newPayloadNumber = _newPayloadNumber cur + 1 + , _newPayloadFees = fees v1 + , _newPayloadEncodedPayloadOutputs = Nothing + , _newPayloadEncodedPayloadData = Just (EncodedPayloadData $ putRlpByteString pld) + , _newPayloadChainwebVersion = v + , _newPayloadChainId = cid + , _newPayloadBlockPayloadHash = EVM._hdrPayloadHash pld + } + threadDelay newPayloadRate + where + ctx = _evmEngineCtx p + v = _chainwebVersion p + cid = _chainId p + + fees v1 = Stu $ bf * gu + where + EVM.BaseFeePerGas bf = _executionPayloadV1BaseFeePerGas v1 + GasUsed gu = _executionPayloadV1GasUsed v1 + +executionPayloadV3ToHeader + :: Chainweb.BlockHash + -> ExecutionPayloadV3 + -> Payload +executionPayloadV3ToHeader phdr v3 = hdr + { EVM._hdrHash = EVM.computeBlockHash hdr + , EVM._hdrPayloadHash = EVM.computeBlockPayloadHash hdr + } + where + v2 = _executionPayloadV2 v3 + v1 = _executionPayloadV1 v2 + hdr = EVM.Header + { _hdrParentHash = _executionPayloadV1ParentHash v1 + , _hdrOmmersHash = EVM.ommersHash + , _hdrBeneficiary = _executionPayloadV1FeeRecipient v1 + , _hdrStateRoot = _executionPayloadV1StateRoot v1 + , _hdrTransactionsRoot = transactionsRoot (_executionPayloadV1Transactions v1) + , _hdrReceiptsRoot = _executionPayloadV1ReceiptsRoot v1 + , _hdrLogsBloom = _executionPayloadV1LogsBloom v1 + , _hdrDifficulty = EVM.difficulty + , _hdrNumber = _executionPayloadV1BlockNumber v1 + , _hdrGasLimit = _executionPayloadV1GasLimit v1 + , _hdrGasUsed = _executionPayloadV1GasUsed v1 + , _hdrTimestamp = _executionPayloadV1Timestamp v1 + , _hdrExtraData = _executionPayloadV1ExtraData v1 + , _hdrPrevRandao = _executionPayloadV1PrevRandao v1 + , _hdrNonce = EVM.nonce + , _hdrBaseFeePerGas = _executionPayloadV1BaseFeePerGas v1 + , _hdrWithdrawalsRoot = withdrawlsRoot (_executionPayloadV2Withdrawals v2) + , _hdrBlobGasUsed = _executionPayloadV3BlobGasUsed v3 + , _hdrExcessBlobGas = _executionPayloadV3ExcessBlobGas v3 + , _hdrParentBeaconBlockRoot = EVM.chainwebBlockHashToBeaconBlockRoot phdr + , _hdrHash = error "Chainweb.PayloadProvider.EVM.executionPayloadV3ToHeader: _hdrHash" + , _hdrPayloadHash = error "Chainweb.PayloadProvider.executionPayloadV3ToHeader: _hdrPayloadHash" + } + +-- -------------------------------------------------------------------------- -- +-- Sync To Block + +-- | Timeout for evmSyncToBlock +-- +forkchoiceUpdatedTimeout :: Micros +forkchoiceUpdatedTimeout = 3_000_000 + +-- | Synchronize the EL to the given block. +-- +-- This function must *must* validate that all EL-headers up to the target block +-- are valid. +-- +-- In order to validate any EL header we need to map the Chainweb block hash +-- and/or payload hash to the respective Eth block hash. This requires that the +-- EVM provider must synchronize payload informatin along with the block headers +-- that is sufficient for this lookup. +-- +-- Also, the evaluation context is not known to the EVM and thus it can not tell +-- if a block is invalid with respect to the evluation context. It is our job to +-- make sure that those payloads are all consistent with the evluation context. +-- We must do this check /before/ we actually let the EVM update its state, so +-- that (1.) users don't observe invalid states and (2.) we can recover from a +-- bad block (the EVM is monotonic for the canoncial chain). +-- +-- The EVM EL header structure provides sufficient information to validate an EL +-- block against the evluation context. +-- +-- If we are given a singleton evaluation context along with the respective EL +-- header we +-- +-- 1. validate that the contex is consistent with the EL header and +-- 2. request the EVM to sync to that header, which will cause the EVM to query +-- respective full execution payload and transition to the new state. +-- +-- If we validate more than a single blokc at a time, the EL queries the +-- respective EL payloads autonomously. We *must* ensure ahead of calling the +-- EVM that any full execution payload satifies the respective evaluation +-- context. +-- +-- We can do this as follows: +-- +-- 1. Eth block hash of the previous latest valid state is contained in any root +-- of the the target state (either the eth hash or Chainweb block hash or +-- Chainweb payload hash), +-- 2. Each Eth header +-- TODO +-- +-- Hard requirements are that +-- +-- 1. We keep track what EL blocks have already been validated against their +-- respecitve evaluation context. +-- 2. We never only ask for validation of blocks for which we have the EL header +-- available. +-- +evmSyncToBlock + :: Logger logger + => EvmPayloadProvider logger + -> Maybe Hints + -> ForkInfo + -> IO ConsensusState +evmSyncToBlock p hints forkInfo = do + curState <- stateIO p + newState <- if trgState == curState + then + -- The local sync state of the EVM EL is already at the target block. + return curState + else do + -- Otherwise we'll take a look at the forkinfo trace + + -- lookup the fork info base payload hash + let rankedBaseHash = _forkInfoBaseRankedPayloadHash forkInfo + tableLookup p rankedBaseHash >>= \case + + -- If we don't know that base, there's nothing we can do + Nothing -> return curState + + Just basePld -> case trace of + -- Case of an empty trace: + -- + -- This succeeds only when we evaluated the target state before. + -- If it is in our database can attempt to sync to it, which may + -- either succeed or fail. NOTE we must query the local store + -- directly and not use the P2P network. + -- + [] -> do + -- Lookup the target hash the local database + lookupConsensusState p trgState >>= \case + -- This case should not be possible because we looked up + -- the base already above. Anyways, we return state. + Nothing -> return curState + Just fcs -> do + -- In the case of an empty context we only care about + -- success or failure + _ <- liftIO $ forkchoiceUpdate p forkchoiceUpdatedTimeout doNewPld fcs + -- FIXME handle the case that the update fails + return trgState -- FIXME + + l -> do + -- in case of a non-empty trace, we lookup the first entry in + -- the trace. (FIXME We could be smarted and search for the + -- latest know payload, but we skip that optimization for now) + -- + -- If we don't know the first entry, we fail. Otherwise, we + -- validate all headers in the context. If validation succeeds + -- we call syncToBlock. If that succeeds we add the headers to + -- the database. + -- + -- It could also make sense to check the empty trace case first + -- and skip any ctx validation. + + unknowns' <- dropWhile (isJust . snd) . zip l + <$> tableLookupBatch p (_evaluationCtxRankedPayloadHash <$> l) + + -- assert db invariant + unless (all (isNothing . snd) unknowns') $ + error "Chainweb.PayloadProviders.EVM.syncToBlock: detected corrupted payload database" + + let unknowns = fst <$> unknowns' + + -- fetch all unkown payloads + -- + -- FIXME do the right thing here. Ideally, fetch all + -- unknowns in batches without redundant local lookups. Then + -- validate all payloads together before sending them to the + -- EVM and inserting them into the DB. + -- + + plds <- forM unknowns $ \ctx -> do + pld <- getPayloadForContext p hints ctx + -- FIXME FIXME FIXME + validatePayload p pld ctx + return (_evaluationCtxRankedPayloadHash ctx, pld) + + lookupConsensusState p trgState >>= \case + Nothing -> return curState + Just fcs -> do + r <- forkchoiceUpdate p forkchoiceUpdatedTimeout doNewPld fcs + if + | EVM._hdrPayloadHash r == _latestPayloadHash trgState -> do + mapM_ (uncurry (tableInsert (_evmPayloadStore p))) plds + return trgState + | EVM._hdrPayloadHash r == _latestPayloadHash curState -> + return curState + | otherwise -> + -- FIXME do something smarter here + error "Chainweb.PayloadProvider.EVM.syncToBlock: failed to update EVM state" + + -- TODO cleeanup. In particular prune candidate store + pruneCandidates p + return newState + where + trgState = _forkInfoTargetState forkInfo + trgLatest = _consensusStateLatest trgState + trgLatestHeight = _syncStateHeight trgLatest + trgLatestNumber = EVM.heightToNumber trgLatestHeight + trgLatestPayloadHash = _syncStateBlockPayloadHash trgLatest + trace = _forkInfoTrace forkInfo + doNewPld = (_latestBlockHash trgState,) <$> _forkInfoNewBlockCtx forkInfo + +pruneCandidates :: EvmPayloadProvider logger -> IO () +pruneCandidates p = do + -- FIXME + -- + -- Use a ranked map as candidate store which can be easily pruned + -- We could use something similar to the payload cache + return () + + +-- | Fetch the payload for a given evaluation context. +-- +-- FIXME: +-- This is inefficient for various reasons: +-- +-- 1. When we call this function we did already establish which payloads are +-- available localy and have been validated before. There is no need to try +-- to look them up locally again. (Although, it is probably pretty cheap) +-- 2. This function fetches payloads one by one. Instead we should query them +-- in chunks using the existing batch endpoints. (This is usually only +-- relevant during catchup. Otherwise we anyways go block by block.) +-- 3. With the EVM provider we can validate the payloads with respect to the +-- evaluation contexts in batches before sending them to the execution +-- client.contexts in batches before sending them to the execution client. +-- +getPayloadForContext + :: Logger logger + => EvmPayloadProvider logger + -> Maybe Hints + -> EvaluationCtx + -> IO Payload +getPayloadForContext p h ctx = do + mapM_ insertPayloadData (_evaluationCtxPayloadData ctx) + pld <- getPayload + (_evmPayloadStore p) + (_evmCandidatePayloads p) + (Priority $ negate $ int $ _evaluationCtxParentHeight ctx) + (_hintsOrigin <$> h) + (_evaluationCtxRankedPayloadHash ctx) + tableInsert (_evmCandidatePayloads p) rh pld + return pld + where + rh = _evaluationCtxRankedPayloadHash ctx + + insertPayloadData epld = case decodePayloadData epld of + Right pld -> tableInsert (_evmCandidatePayloads p) rh pld + Left e -> do + lf Warn $ "failed to decode encoded payload from evaluation ctx: " <> sshow e + + lf :: LogFunctionText + lf = logFunction (_evmLogger p) + +-- | FIXME: +-- +-- Do the actual validation. In particular, validate the whole chain of payloads +-- from the context in one go. +-- +validatePayload + :: EvmPayloadProvider logger + -> Payload + -> EvaluationCtx + -> IO () +validatePayload p pld ctx = return () + +-- -------------------------------------------------------------------------- -- +-- Payload Provider API Instance + +instance Logger logger => PayloadProvider (EvmPayloadProvider logger) where + + -- FIXME + prefetchPayloads _ _ _ = return () + syncToBlock = evmSyncToBlock + latestPayloadSTM p = ssnd <$> readTMVar (_evmPayloadVar p) + +-- -------------------------------------------------------------------------- -- +-- Utils + +_latestNumber :: ConsensusState -> EVM.BlockNumber +_latestNumber = EVM.heightToNumber . _latestHeight + +_safeNumber :: ConsensusState -> EVM.BlockNumber +_safeNumber = EVM.heightToNumber . _safeHeight + +_finalNumber :: ConsensusState -> EVM.BlockNumber +_finalNumber = EVM.heightToNumber . _finalHeight + +encodedPayloadData :: Payload -> EncodedPayloadData +encodedPayloadData = EncodedPayloadData . putRlpByteString + +decodePayloadData :: MonadThrow m => EncodedPayloadData -> m Payload +decodePayloadData (EncodedPayloadData bs) = decodeRlpM bs + + +-- -------------------------------------------------------------------------- -- +-- ATTIC +-- -------------------------------------------------------------------------- -- + +-- -------------------------------------------------------------------------- -- +-- Utils for querying EVM block details for a consensus state + +-- | Get the header for the latest block in a consensus state from the EVM. +-- +-- Throws: +-- +-- * EvmHeaderNotFoundException with +-- * EvmHeaderNotFoundByNumber if there is no block for the given number. This +-- usually means that consensus state references a block that has not been +-- validated before. +-- * EvmHeaderNotFoundByPayloadHash if the block on the given number does not +-- match the block in the payload state. This usually means that the +-- referenced block is on different fork. +-- +getLatestHdr + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdb + -> ConsensusState + -> m Payload +getLatestHdr p s = do + hdr <- getBlockAtNumber p (_latestNumber s) + unless (view EVM.hdrPayloadHash hdr == _latestPayloadHash s) $ + throwM $ EvmHeaderNotFoundByPayloadHash (_latestPayloadHash s) + return hdr + +-- | Get the header for the safe block in a consensus state. +-- +-- Throws: +-- +-- * EvmHeaderNotFoundException with +-- * EvmHeaderNotFoundByNumber if there is no block for the given number. This +-- usually means that consensus state references a block that has not been +-- validated before. This can happen only if the latest block has not been +-- evaluted yet neither. +-- * EvmHeaderNotFoundByPayloadHash if the block on the given number does not +-- match the block in the payload state. This usually means that the +-- referenced block is on different fork. For this to happen the latest +-- block must be on a different fork, too. +-- +getSafeHdr + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdb + -> ConsensusState + -> m Payload +getSafeHdr p s = do + hdr <- getBlockAtNumber p (_safeNumber s) + unless (view EVM.hdrPayloadHash hdr == _safePayloadHash s) $ + throwM $ EvmHeaderNotFoundByPayloadHash (_safePayloadHash s) + return hdr + +-- | Get the header for the final block in a consensus state. +-- +-- Throws: +-- +-- * EvmHeaderNotFoundException with +-- * EvmHeaderNotFoundByNumber if there is no block for the given number. This +-- usually means that consensus state references a block that has not been +-- validated before. This can happen only if the latest and safe blocks have +-- not been evaluted yet neither. +-- * EvmHeaderNotFoundByPayloadHash if the block on the given number does not +-- match the block in the payload state. This usually means that the +-- referenced block is on different fork. For this to happen the latest and +-- safe block must be on a different fork, too. +-- +getFinalHdr + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdb + -> ConsensusState + -> m Payload +getFinalHdr p s = do + hdr <- getBlockAtNumber p (_finalNumber s) + unless (view EVM.hdrPayloadHash hdr == _finalPayloadHash s) $ + throwM $ EvmHeaderNotFoundByPayloadHash (_finalPayloadHash s) + return hdr + +-- | Get the hash for the latest block in a consensus state. +-- +-- Cf. 'getLatestHeader' for details. +-- +getLatestHash + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdb + -> ConsensusState + -> m EVM.BlockHash +getLatestHash p = fmap (view EVM.hdrHash) . getLatestHdr p + +-- | Get the hash for the safe block in a consensus state. +-- +-- Cf. 'getSafeHeader' for details. +-- +getSafeHash + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdb + -> ConsensusState + -> m EVM.BlockHash +getSafeHash p = fmap (view EVM.hdrHash) . getSafeHdr p + +-- | Get the hash for the final block in a consensus state. +-- +-- Cf. 'getFinalHeader' for details. +-- +getFinalHash + :: MonadThrow m + => MonadIO m + => EvmPayloadProvider pdb + -> ConsensusState + -> m EVM.BlockHash +getFinalHash p = fmap (view EVM.hdrHash) . getFinalHdr p + +-- -------------------------------------------------------------------------- -- +-- Old Notes + +-- instance PayloadProvider (EvmPayloadProvider payloadDb) where +-- +-- syncToBlock evm maybeOrigin forkInfo = do +-- currentSyncState <- readTVarIO evm._evmState +-- if currentSyncState == targetSyncState +-- then return (Just currentSyncState) +-- else do +-- ps <- traverse fetchAndVerifyEvmHeader forkInfo._forkInfoTrace +-- let maybeTargetCtx = NonEmpty.last <$> NonEmpty.nonEmpty forkInfo._forkInfoTrace +-- -- TODO: check EvaluationCtx against EVM headers +-- -- TODO: also check that the ctxs form a chain ending in the target +-- case maybeTargetCtx of +-- -- there was not enough information in the trace to change blocks +-- Nothing -> return (Just currentSyncState) +-- Just targetCtx -> do +-- tableLookup evm._evmPayloadDb targetSyncState >>= \case +-- -- TODO: fetch EVM headers here later? +-- Nothing -> error "missing EVM header in payloaddb" +-- Just evmHeader -> do +-- let evmHash = EVM.blockHash evmHeader +-- resp <- JsonRpc.callMethodHttp +-- @"engine_forkchoiceUpdatedV3" evm._evmEngineCtx (forkChoiceRequest evmHash) +-- +-- case resp._forkchoiceUpdatedV1ResponsePayloadStatus._payloadStatusV1Status of +-- Valid -> do +-- listenToPayloadId resp._forkchoiceUpdatedV1ResponsePayloadId +-- return (Just targetSyncState) +-- Invalid -> return Nothing +-- Syncing -> waitForSync evmHash +-- Accepted -> error "invalid" +-- InvalidBlockHash -> return Nothing +-- where +-- targetSyncState = forkInfo._forkInfoTarget +-- forkChoiceRequest evmHash = ForkchoiceUpdatedV3Request +-- { _forkchoiceUpdatedV3RequestState = ForkchoiceStateV1 evmHash evmHash evmHash +-- , _forkchoiceUpdatedV3RequestPayloadAttributes = +-- evm._evmMinerInfo <&> \minerAddress -> PayloadAttributesV3 +-- { _payloadAttributesV3parentBeaconBlockRoot = +-- EVM.chainwebBlockHashToBeaconBlockRoot targetSyncState._rankedBlockHash +-- , _payloadAttributesV2 = PayloadAttributesV2 +-- -- TODO: add withdrawals for paying miners block rewards? +-- { _payloadAttributesV2Withdrawals = [] +-- , _payloadAttributesV1 = PayloadAttributesV1 +-- -- TODO: add real timestamp? +-- { _payloadAttributesV1Timestamp = Ethereum.Timestamp 0 +-- -- TODO: use random number derived from PoW hash? +-- , _payloadAttributesV1PrevRandao = Utils.Randao (Ethereum.encodeLeN 0) +-- -- TODO: does this suffice to pay miners gas fees? +-- , _payloadAttributesV1SuggestedFeeRecipient = minerAddress +-- } +-- } +-- } +-- } +-- fetchAndVerifyEvmHeader :: EvaluationCtx -> IO Payload +-- fetchAndVerifyEvmHeader evalCtx = do +-- tableLookup evm.payloadDb (Just $ view blockHeight h) payloadHash >>= \case +-- Nothing -> do +-- p <- fetchPayloadFromRemote +-- validateCtx evalCtx p +-- return p +-- Just p -> return p +-- waitForSync evmHash = JsonRpc.callMethodHttp @"eth_syncing" evm._evmEngineCtx Nothing >>= \case +-- SyncingStatusFalse -> do +-- resp <- JsonRpc.callMethodHttp @"engine_forkchoiceUpdatedV3" evm._evmEngineCtx (forkChoiceRequest evmHash) +-- case resp._forkchoiceUpdatedV1ResponsePayloadStatus._payloadStatusV1Status of +-- Valid -> do +-- listenToPayloadId resp._forkchoiceUpdatedV1ResponsePayloadId +-- return $ Just forkInfo._forkInfoTarget +-- Invalid -> return Nothing +-- Syncing -> error "that syncing feeling..." +-- Accepted -> error "invalid" +-- InvalidBlockHash -> return Nothing +-- +-- SyncingStatus +-- { _syncingStatusStartingBlock +-- , _syncingStatusCurrentBlock = Ethereum.BlockNumber current +-- , _syncingStatusHighestBlock = Ethereum.BlockNumber highest +-- } -> do +-- -- todo: use the current speed to make an estimate here +-- approximateThreadDelay (max 10000 (int highest - int current * 10000)) +-- waitForSync evmHash +-- listenToPayloadId = \case +-- Just x | isJust evm._evmMinerInfo -> +-- atomically $ writeTMVar evm._evmPayloadId x +-- Nothing | isNothing evm._evmMinerInfo -> +-- return () +-- pid -> error +-- $ "mining is: " <> maybe "disabled" (\_ -> "enabled") evm._evmMinerInfo From f51d53636b39a2bf344f96b6f9ba103a3a251342 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 15 Jan 2025 19:16:47 -0800 Subject: [PATCH 077/378] PayloadProvider.EVM: lots of wip --- src/Chainweb/PayloadProvider/EVM.hs | 1073 ++++++++++++++++----------- 1 file changed, 635 insertions(+), 438 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 1bbf07e573..3668447315 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -23,6 +23,8 @@ {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -Wprepositive-qualified-module #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} -- | -- Module: Chainweb.PayloadProvider.EVM @@ -34,6 +36,7 @@ module Chainweb.PayloadProvider.EVM ( EvmProviderConfig(..) , defaultEvmProviderConfig +, pEvmProviderConfig -- * EVM Payload Provider Implementation , EvmPayloadProvider(..) @@ -46,12 +49,12 @@ module Chainweb.PayloadProvider.EVM ) where -import Chainweb.BlockCreationTime import Chainweb.BlockHash qualified as Chainweb import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.BlockPayloadHash import Chainweb.ChainId +import Chainweb.Core.Brief import Chainweb.Logger import Chainweb.MinerReward import Chainweb.PayloadProvider @@ -60,25 +63,27 @@ import Chainweb.PayloadProvider.EVM.EthRpcAPI import Chainweb.PayloadProvider.EVM.Header qualified as EVM import Chainweb.PayloadProvider.EVM.HeaderDB qualified as EvmDB import Chainweb.PayloadProvider.EVM.JsonRPC (JsonRpcHttpCtx, callMethodHttp) -import Chainweb.PayloadProvider.EVM.JsonRPC qualified as JsonRpc +import Chainweb.PayloadProvider.EVM.JsonRPC qualified as RPC import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) import Chainweb.PayloadProvider.EVM.Utils qualified as EVM import Chainweb.PayloadProvider.EVM.Utils qualified as Utils import Chainweb.PayloadProvider.P2P import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest +import Chainweb.Ranked import Chainweb.Storage.Table -import Chainweb.Storage.Table.HashMap +import Chainweb.Storage.Table.Map import Chainweb.Storage.Table.RocksDB import Chainweb.Time import Chainweb.Utils import Chainweb.Version +import Configuration.Utils hiding (Error) import Control.Concurrent import Control.Concurrent.Async import Control.Concurrent.STM +import Control.Exception import Control.Lens hiding ((.=)) import Control.Monad -import Control.Monad.Catch -import Control.Monad.IO.Class +import Control.Monad.Catch (MonadThrow, throwM) import Data.ByteString.Short qualified as BS import Data.LogMessage import Data.Maybe @@ -97,7 +102,6 @@ import Network.URI.Static import P2P.Session (ClientEnv) import P2P.TaskQueue import System.LogLevel -import Configuration.Utils -- -------------------------------------------------------------------------- -- -- Types (to keep the code clean and avoid confusion) @@ -128,13 +132,45 @@ payloadDbConfiguration = EvmDB.configuration -- type Payload = PayloadType EvmPayloadProvider -- -------------------------------------------------------------------------- -- +-- Configuration + +prefixLongCid :: HasName f => ChainId -> String -> Mod f a +prefixLongCid cid = prefixLong (Just p) + where + p = "chain-" <> T.unpack (toText cid) + +suffixHelpCid :: ChainId -> String -> Mod f a +suffixHelpCid cid = suffixHelp (Just s) + where + s = "chain-" <> T.unpack (toText cid) + +pJwtSecret :: ChainId -> OptionParser JwtSecret +pJwtSecret cid = textOption + % prefixLongCid cid "jwt-secret" + <> suffixHelpCid cid "JWT secret for the EVM Engine API" + +pMinerAddress :: ChainId -> OptionParser EVM.Address +pMinerAddress cid = textOption + % prefixLongCid cid "miner-address" + <> suffixHelpCid cid "Miner address for new EVM blocks" + +newtype EngineUri = EngineUri { _engineUri :: URI } + deriving (Show, Eq, Generic) + deriving (ToJSON, FromJSON) via (JsonTextRepresentation "EngineUri" EngineUri) + +instance HasTextRepresentation EngineUri where + toText (EngineUri u) = toText u + fromText = fmap EngineUri . fromText + +pEngineUri :: ChainId -> OptionParser EngineUri +pEngineUri cid = textOption + % prefixLongCid cid "uri" + <> suffixHelpCid cid "EVM Engine URI" --- FIXME --- data EvmProviderConfig = EvmProviderConfig - { _evmConfEngineUri :: !URI + { _evmConfEngineUri :: !EngineUri , _evmConfEngineJwtSecret :: !JwtSecret - , _evmConfMinerInfo :: !(Maybe EVM.Address) + , _evmConfMinerAddress :: !(Maybe EVM.Address) } deriving (Show, Eq, Generic) @@ -142,32 +178,35 @@ makeLenses ''EvmProviderConfig defaultEvmProviderConfig :: EvmProviderConfig defaultEvmProviderConfig = EvmProviderConfig - { _evmConfEngineUri = [uri|http://localhost:8551|] - , _evmConfEngineJwtSecret = unsafeFromText "10b45e8907ab12dd750f688733e73cf433afadfd2f270e5b75a6b8fff22dd352" - -- FIXME - , _evmConfMinerInfo = Nothing + { _evmConfEngineUri = EngineUri [uri|http://localhost:8551|] + , _evmConfEngineJwtSecret = unsafeFromText "0000000000000000000000000000000000000000000000000000000000000000" + , _evmConfMinerAddress = Nothing } instance ToJSON EvmProviderConfig where toJSON o = object [ "engineUri" .= _evmConfEngineUri o , "engineJwtSecret" .= _evmConfEngineJwtSecret o - , "minerAddress" .= _evmConfMinerInfo o + , "minerAddress" .= _evmConfMinerAddress o ] +instance FromJSON EvmProviderConfig where + parseJSON = withObject "EvmProviderConfig" $ \o -> EvmProviderConfig + <$> o .: "engineUri" + <*> o .: "engineJwtSecret" + <*> o .: "minerAddress" + instance FromJSON (EvmProviderConfig -> EvmProviderConfig) where parseJSON = withObject "EvmProviderConfig" $ \o -> id <$< evmConfEngineUri ..: "engineUri" % o <*< evmConfEngineJwtSecret ..: "engineJwtSecret" % o - <*< evmConfMinerInfo ..: "minerAddress" % o - -pEvmProviderConfig :: MParser EvmProviderConfig -pEvmProviderConfig = id - <$< _evmConfEngineUri .:: pHostAddress - <*< _evmConfEngineJwtSecret .:: pJwtSecret - <*< _evmConfMinerInfo .:: pMinerInfo - + <*< evmConfMinerAddress ..: "minerAddress" % o +pEvmProviderConfig :: ChainId -> MParser EvmProviderConfig +pEvmProviderConfig cid = id + <$< evmConfEngineUri .:: pEngineUri cid + <*< evmConfEngineJwtSecret .:: pJwtSecret cid + <*< evmConfMinerAddress .:: fmap Just % pMinerAddress cid -- -------------------------------------------------------------------------- -- -- EVM Payload Provider @@ -185,9 +224,6 @@ data EvmPayloadProvider logger = EvmPayloadProvider { _evmChainwebVersion :: !ChainwebVersion , _evmChainId :: !ChainId , _evmLogger :: !logger - , _evmState :: !(TVar ConsensusState) - -- ^ The current sync state of the EVM EL. - , _evmPayloadStore :: !(PayloadStore (PayloadDb RocksDbTable) Payload) -- ^ The BlockPayloadHash in the ConsensusState is different from the -- EVM BlockHash that the EVM knows about. @@ -217,25 +253,57 @@ data EvmPayloadProvider logger = EvmPayloadProvider -- -- In the future we may consider teaching the EVM about Chainweb -- BlockPayloadHashes. - , _evmCandidatePayloads :: !(HashMapTable RankedBlockPayloadHash Payload) + , _evmCandidatePayloads :: !(MapTable RankedBlockPayloadHash Payload) -- ^ FIXME: should this be moved into the Payload Store? -- - -- For now we just prune after each successful syncToBlock. Consensus - -- has its own candidate cache with a broader (cut processing) scope. + -- At the moment this is not really used at all. , _evmEngineCtx :: !JsonRpcHttpCtx -- ^ The JSON RPC context that provides the connection the Engine API of -- the EVM. + , _evmMinerAddress :: !(Maybe Ethereum.Address) + -- ^ The miner address. If this is Nothing, payload creation is + -- disabled. + + -- Internal State: + + , _evmState :: !(TVar (T2 ConsensusState (Maybe NewBlockCtx))) + -- ^ The current consensus state and new block ctx of the EVM. + -- + -- The new block context is included so that new payload ID can be + -- requested in case the previous one got lost. + + , _evmPayloadId :: !(TMVar PayloadId) + -- ^ The payload ID along with the respective SyncState to which it + -- corresponds. If this is set it always corresponds to the current + -- consensus state. - -- Mining: - , _evmMinerInfo :: !(Maybe Ethereum.Address) - , _evmPayloadId :: !(TMVar (T2 SyncState PayloadId)) , _evmPayloadVar :: !(TMVar (T2 EVM.BlockHash NewPayload)) + -- ^ The most recent new payload + , _evmLock :: !(MVar ()) + -- ^ Not sure if we really need this. Users could race, but not sure + -- whether we actually care... + -- + -- FIXME: if we actually need or want this, we should probably use a + -- queue, or even better preempt earlier operations. } -stateIO :: EvmPayloadProvider logger -> IO ConsensusState +stateIO :: EvmPayloadProvider logger -> IO (T2 ConsensusState (Maybe NewBlockCtx)) stateIO = readTVarIO . _evmState +latestStateIO :: EvmPayloadProvider logger -> IO SyncState +latestStateIO = fmap (_consensusStateLatest . sfst) . stateIO + +isPayloadRequestedIO :: EvmPayloadProvider logger -> IO Bool +isPayloadRequestedIO p = case _evmMinerAddress p of + Nothing -> return False + Just _ -> (isJust . ssnd) <$> stateIO p + +newBlockCtxIO :: EvmPayloadProvider logger -> IO (Maybe NewBlockCtx) +newBlockCtxIO p = case _evmMinerAddress p of + Nothing -> return Nothing + Just _ -> ssnd <$> stateIO p + evmPayloadDb :: Getter (EvmPayloadProvider l) (PayloadDb RocksDbTable) evmPayloadDb = to (_payloadStoreTable . _evmPayloadStore) @@ -275,35 +343,77 @@ instance tableDelete s = tableDelete (_evmPayloadStore s) tableDeleteBatch s = tableDeleteBatch (_evmPayloadStore s) +-- | +-- +-- IMPORTANT NOTE: this takes into account the candidate store. This means +-- results can't be trusted to be evaluated. +-- +-- Candidates must be inserted in the candidate store only after they are +-- validated against the evaluation context. +-- lookupConsensusState :: ReadableTable tbl RankedBlockPayloadHash Payload => tbl -> ConsensusState + -> [(RankedBlockPayloadHash, Payload)] -> IO (Maybe ForkchoiceStateV1) -lookupConsensusState p cs = do - r <- tableLookupBatch p +lookupConsensusState p cs plds = do + r0 <- tableLookupBatch p [ latestRankedBlockPayloadHash cs , safeRankedBlockPayloadHash cs , finalRankedBlockPayloadHash cs ] - case r of - [Nothing, _, _] -> return Nothing + + -- Fill in payloads that are missing in the database from the candidate + -- payloads. + -- + case go <$> (zip [lrh, srh, frh] r0) of + [Nothing, _, _] -> do + return Nothing [Just l, Just s, Just f] -> return $ Just ForkchoiceStateV1 { _forkchoiceHeadBlockHash = EVM._hdrHash l , _forkchoiceSafeBlockHash = EVM._hdrHash s , _forkchoiceFinalizedBlockHash = EVM._hdrHash f } - x -> error "corrupted database" -- FIXME throw proper error - + x -> error $ "corrupted database: " <> sshow x + -- FIXME throw proper error + -- -- FIXME: I guess this can happen with shallow nodes? -- The invariant is that if a block is in the db all predecessors -- are valid, but they are not necessarily available. + where + go (k, Nothing) = lookup k plds + go (_, x) = x + lrh = latestRankedBlockPayloadHash cs + srh = safeRankedBlockPayloadHash cs + frh = finalRankedBlockPayloadHash cs + +loggS + :: Logger logger + => EvmPayloadProvider logger + -> T.Text + -- ^ sub-component name + -> LogLevel + -> T.Text + -> IO () +loggS p s l t = logFunctionText logger l t + where + logger = _evmLogger p & addLabel ("sub-component", s) + +logg + :: Logger logger + => EvmPayloadProvider logger + -> LogLevel + -> T.Text + -> IO () +logg p l t = logFunctionText (_evmLogger p) l t -- -------------------------------------------------------------------------- -- -- Exceptions data EvmExecutionEngineException = EvmChainIdMissmatch (Expected EVM.ChainId) (Actual EVM.ChainId) + | EvmInvalidGensisHeader (Expected BlockPayloadHash) (Actual BlockPayloadHash) deriving (Show, Eq, Generic) instance Exception EvmExecutionEngineException @@ -332,7 +442,7 @@ instance Exception ForkchoiceUpdatedTimeoutException -- | Thrown on an invalid payload status. -- -newtype InvalidPayloadException = InvalidPayloadException T.Text +data InvalidPayloadException = InvalidPayloadException !EVM.BlockHash !(Maybe T.Text) deriving (Eq, Show, Generic) instance Exception InvalidPayloadException @@ -346,10 +456,9 @@ instance Exception InvalidPayloadException -- therefor advisable that this function is called asynchronously. -- -- FIXME: --- Currently the Genesis Header is pull from the execution client. For --- production chainweb versions the hash of the genesis header or even the full --- header should be hard-coded in chainweb node and we should check on startup --- if it matches with the header from the execution client. +-- +-- Verify that the genesis headers form the execution client match what is +-- stored in the chainweb version. -- withEvmPayloadProvider :: Logger logger @@ -369,34 +478,38 @@ withEvmPayloadProvider -> IO a withEvmPayloadProvider logger v c rdb mgr conf f | FromSing @_ @p (SEvmProvider ecid) <- payloadProviderTypeForChain v c = do - engineCtx <- mkEngineCtx (_evmConfEngineJwtSecret conf) (_evmConfEngineUri conf) + engineCtx <- mkEngineCtx (_evmConfEngineJwtSecret conf) (_engineUri $ _evmConfEngineUri conf) SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal v SomeChainIdT @c _ <- return $ someChainIdVal c let pldCli h = Rest.payloadClient @v @c @p h - genPld <- checkExecutionClient engineCtx (EVM.ChainId (fromSNat ecid)) + genPld <- checkExecutionClient v c engineCtx (EVM.ChainId (fromSNat ecid)) + logFunctionText logger Info $ "genesis payload block hash: " <> sshow (EVM._hdrPayloadHash genPld) + logFunctionText logger Debug $ "genesis payload from execution client: " <> sshow genPld pdb <- initPayloadDb $ payloadDbConfiguration v c rdb genPld store <- newPayloadStore mgr (logFunction pldStoreLogger) pdb pldCli pldVar <- newEmptyTMVarIO pldIdVar <- newEmptyTMVarIO candidates <- emptyTable - stateVar <- newTVarIO (genesisState v c) + stateVar <- newTVarIO (T2 (genesisState v c) Nothing) + lock <- newMVar () let p = EvmPayloadProvider { _evmChainwebVersion = _chainwebVersion v , _evmChainId = _chainId c - , _evmLogger = providerLogger + , _evmLogger = logger , _evmState = stateVar , _evmPayloadStore = store , _evmCandidatePayloads = candidates , _evmEngineCtx = engineCtx - , _evmMinerInfo = _evmConfMinerInfo conf + , _evmMinerAddress = _evmConfMinerAddress conf , _evmPayloadId = pldIdVar , _evmPayloadVar = pldVar + , _evmLock = lock } result <- race (payloadListener p) $ do - logFunctionText providerLogger Info $ + logg p Info $ "EVM payload provider started for Ethereum network id " <> sshow ecid f p case result of @@ -406,15 +519,18 @@ withEvmPayloadProvider logger v c rdb mgr conf f | otherwise = error "Chainweb.PayloadProvider.Evm.configuration: chain does not use EVM provider" where - providerLogger = setComponent "payload-provider" - $ addLabel ("provider", "evm") logger - pldStoreLogger = addLabel ("sub-component", "payloadStore") providerLogger + pldStoreLogger = addLabel ("sub-component", "payloadStore") logger payloadListener :: Logger logger => EvmPayloadProvider logger -> IO () -payloadListener p = runForever lf "EVM Provider Payload Listener" $ - awaitNewPayload p +payloadListener p = case (_evmMinerAddress p) of + Nothing -> do + lf Info "New payload creation is disabled." + return () + Just addr -> runForeverThrottled lf "EVM Provider Payload Listener" 5 10000 $ do + lf Info $ "Start payload listener with miner address " <> toText addr + awaitNewPayload p where - lf = logFunction (_evmLogger p) + lf = loggS p "payloadListener" -- | Checks the availability of the Execution Client -- @@ -425,105 +541,32 @@ payloadListener p = runForever lf "EVM Provider Payload Listener" $ -- Returns the genesis header. -- checkExecutionClient - :: JsonRpcHttpCtx + :: HasChainwebVersion v + => HasChainId c + => v + -> c + -> JsonRpcHttpCtx -> EVM.ChainId -- ^ expected Ethereum Network ID -> IO Payload -checkExecutionClient ctx expectedEcid = do +checkExecutionClient v c ctx expectedEcid = do ecid <- callMethodHttp @Eth_ChainId ctx Nothing unless (expectedEcid == ecid) $ throwM $ EvmChainIdMissmatch (Expected expectedEcid) (Actual ecid) callMethodHttp @Eth_GetBlockByNumber ctx (DefaultBlockNumber 0, False) >>= \case Nothing -> throwM EvmGenesisHeaderNotFound - Just h -> return h - --- -------------------------------------------------------------------------- -- - --- queryEngineState :: JsonRpcHttpCtx -> IO (Maybe ForkchoiceStateV1) --- queryEngineState ctx = runConcurrently $ runMaybeT $ ForkchoiceStateV1 --- <$> MaybeT (Concurrently $ getHashByNumber ctx DefaultBlockLatest) --- <*> MaybeT (Concurrently $ getHashByNumber ctx DefaultBlockSafe) --- <*> MaybeT (Concurrently $ getHashByNumber ctx DefaultBlockFinalized) + Just h -> do + unless (EVM._hdrPayloadHash h == expectedGenesisHeader) $ + throwM $ EvmInvalidGensisHeader + (Expected expectedGenesisHeader) + (Actual $ EVM._hdrPayloadHash h) + return h + where + expectedGenesisHeader = genesisBlockPayloadHash (_chainwebVersion v) (_chainId c) -- -------------------------------------------------------------------------- -- -- Engine Calls -getHashByNumber - :: JsonRpcHttpCtx - -> DefaultBlockParameter - -> IO (Maybe EVM.BlockHash) -getHashByNumber ctx p = fmap EVM._hdrHash <$> - callMethodHttp @Eth_GetBlockByNumber ctx (p, False) - --- | Obtains a block by number. It is an exception if the block can not be --- found. --- --- Returns: EVM Header --- --- Throws: --- --- * EvmHeaderNotFoundException --- * Eth RPC failures --- * JSON RPC failures --- -getBlockAtNumber - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdf - -> EVM.BlockNumber - -> m Payload -getBlockAtNumber p n = do - r <- liftIO $ JsonRpc.callMethodHttp - @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockNumber n, False) - case r of - Just h -> return h - Nothing -> throwM $ EvmHeaderNotFoundByNumber (DefaultBlockNumber n) - --- | Returns the EVM genesis block. --- --- Returns: EVM Header --- --- Throws: --- --- * InvalidEvmState --- * Eth RPC failures --- * JSON RPC failures --- -getGenesisHeader - :: MonadThrow m - => MonadCatch m - => MonadIO m - => EvmPayloadProvider pdf - -> m Payload -getGenesisHeader p = try (getBlockAtNumber p 0) >>= \case - Left (EvmHeaderNotFoundByNumber _) -> throwM EvmGenesisHeaderNotFound - Left e -> throwM e - Right x -> return x - --- | Obtains a block by EVM block hash. It is an exception if the block can not --- be found. --- --- Returns: EVM Header --- --- Throws: --- --- * EvmHeaderNotFoundException --- * Eth RPC failures --- * JSON RPC failures --- -getBlockByHash - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdf - -> EVM.BlockHash - -> m Payload -getBlockByHash p h = do - r <- liftIO $ JsonRpc.callMethodHttp - @Eth_GetBlockByHash (_evmEngineCtx p) (h, False) - case r of - Just hdr -> return hdr - Nothing -> throwM $ EvmHeaderNotFoundByHash h - -- | Updates the forkchoice state of the EVM. Does not initiate payload -- production. -- @@ -547,113 +590,209 @@ getBlockByHash p h = do -- * JSON RPC failure -- forkchoiceUpdate - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdb + :: Logger logger + => EvmPayloadProvider logger -> Micros -- ^ Timeout in microseconds - -> Maybe (Chainweb.BlockHash, NewBlockCtx) - -- ^ Whether to start new payload production on to of the target block. -> ForkchoiceStateV1 -- ^ the requested fork choice state - -> m Payload -forkchoiceUpdate p t ph state = go t >>= getBlockByHash p + -> Maybe PayloadAttributesV3 + -> IO (Maybe PayloadId) +forkchoiceUpdate p t fcs attr = go t where + lf = loggS p "forkchoiceUpdate" waitTime = Micros 500_000 - payloadAttributes = case ph of - Nothing -> Nothing - Just (h, nctx) -> Just $ mkPayloadAttributes p h nctx go remaining - | remaining <= 0 = throwM $ ForkchoiceUpdatedTimeoutException t + | remaining <= 0 = do + lf Warn $ "forkchoiceUpdate timed out while EVM is syncing" + throwM $ ForkchoiceUpdatedTimeoutException t | otherwise = do - r <- liftIO $ JsonRpc.callMethodHttp @Engine_ForkchoiceUpdatedV3 (_evmEngineCtx p) - (ForkchoiceUpdatedV3Request state payloadAttributes) + lf Info $ briefJson (ForkchoiceUpdatedV3Request fcs attr) + r <- try @(RPC.Error EngineServerErrors EngineErrors) $ + RPC.callMethodHttp @Engine_ForkchoiceUpdatedV3 (_evmEngineCtx p) + (ForkchoiceUpdatedV3Request fcs attr) + case r of + Right s -> case _forkchoiceUpdatedV1ResponsePayloadStatus s of + + -- Syncing: retry + PayloadStatusV1 Syncing Nothing Nothing -> do + -- wait 500ms + lf Warn $ "EVM is SYNCING. Waiting for " <> sshow waitTime <> " microseconds" + threadDelay $ int waitTime + go (remaining - waitTime) + + -- If the status is VALID, the latest hash is what was requested. + -- + -- In particular, if the status is VALID the return latest hash + -- is never an ancestor of the requested hash. + -- + -- If payload creation was requested the payload id is null only + -- if no payload attributes were provided. (If the payload + -- attributes are invalid an error is returned, even thought the + -- forkchoice state *is* updated.) + -- + -- IMPORTANT NOTE: + -- + -- The Engine API specification demands that, if the requested + -- hash was not the latest hash on the (selected) canonical + -- chain this returns the latest hash on the canonical chain. It + -- will not initiate payload creation. This behavior is not + -- acceptable for Chainweb consensus. + -- + -- Therefore will have to patch the EVM client such that the + -- latest hash will be the requested hash and payload production + -- is initiated if it was requested. + -- + PayloadStatusV1 Valid (Just _) Nothing -> + return (_forkchoiceUpdatedV1ResponsePayloadId s) + + -- FIXME: + -- + -- If the status is INVALID the latest valid hash is always + -- returned. The validationError message is optional. + -- + PayloadStatusV1 Invalid (Just h) e -> + throwM $ InvalidPayloadException h e + + -- This includes + -- - PayloadStatusV1 Invalid Nothing _ + e -> + throwM $ UnexpectedForkchoiceUpdatedResponseException e + + -- According to Engine Spec [8, point 7] + -- {error: {code: -38003, message: "Invalid payload attributes"}} + -- is returned even when forkchoiceState has been updated, but + -- payload attributes were invalid. + -- + -- NOTE: we must MUST address this case and update the state! + -- + Left (RPC.Error (RPC.ApplicationError InvalidPayloadAttributes) msg Nothing) -> do + + -- FIXME: + -- how to we signal this to caller, in particular, + -- consensus? If this goes undetected mining may get stuck. + -- If this is due to a timestamp being the same as the + -- previous block, we'll have to figure out what to do about + -- it. Maybe patching reth? + -- + -- For now we log an Error. + -- + T2 cur _ <- stateIO p + lf Error $ msg <> ": forkchoiceUpdate succeeded but no payload is produced, because of invalid payload attributes. This is most likely due to a non-increasing timestamp" + lf Error $ encodeToText $ object + [ "forkchoiceState" .= fcs + , "payloadAttributes" .= attr + , "previousState" .= cur + , "response" .= r + ] + return Nothing - -- FIXME: update payload ID variable + Left e -> throwM e - case _forkchoiceUpdatedV1ResponsePayloadStatus r of - PayloadStatusV1 Valid (Just x) Nothing -> return x - PayloadStatusV1 Invalid Nothing (Just e) -> throwM $ InvalidPayloadException e - PayloadStatusV1 Syncing Nothing Nothing -> do - -- wait 500ms - liftIO $ threadDelay $ int waitTime - go (remaining - waitTime) - e -> throwM $ UnexpectedForkchoiceUpdatedResponseException e +-- | Calls forkchoiceUpdate and update the provider state. +-- +-- NOTE: This must be called only for consensus state that have been validated +-- against the evaluation context. In particular, this is the case for the +-- @_evmState@ and for any payload in the payload db. +-- +-- NOTE: If the result status is VALID the returned latest valid hash is always +-- the requested for the requested block. A different latest valid hash may be +-- returned if and only if the result status is INVALID. +-- +updateEvm + :: Logger logger + => EvmPayloadProvider logger + -> ConsensusState + -- ^ the requested fork choice state. This state *MUST* have been + -- validated with respect to the evaluation context. + -> Maybe NewBlockCtx + -> [(RankedBlockPayloadHash, Payload)] + -- ^ Payloads that are added to the database if EVM validation succeeds. + -- These payloads must already be validated with respect to the + -- evaluation context. + -- + -- FIXME: add a newtype wrapper for (Pre-)Validated Payloads and guard + -- forkchoiceUpdate and database insertion by it. + -- + -> IO () +updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case + Nothing -> do + lf Info $ "Consensus state lookup returned nothing for" <> briefJson state + return () + Just fcs -> do + pt <- parentTimestamp + pid <- forkchoiceUpdate p forkchoiceUpdatedTimeout fcs (attr pt) + + -- forkchoiceUpdate throws if it does not succeed. + + -- add new payloads to payload database + lf Info $ "new payloads added to database: " <> sshow (length plds) + mapM_ (uncurry (tableInsert (_evmPayloadStore p))) plds + + -- Update State and Payload Id: + -- There is a race here: If we fail updating the variable + -- the EVM is ahead. That could cause payload updates to + -- fail and possibly stale mining. + lf Info "update state and payload ID variable" + void $ atomically $ do + -- update state + writeTVar (_evmState p) (T2 state nctx) + -- update payloadId + case pid of + Nothing -> void $ tryTakeTMVar (_evmPayloadId p) + Just x -> writeTMVar (_evmPayloadId p) x + where + lf = loggS p "updateEvm" + attr pt = mkPayloadAttributes (_latestBlockHash state) pt + <$> _evmMinerAddress p + <*> nctx + + -- This is available either from the provided new payloads or from the + -- database. In any case, it /must/ be a source that has been validated with + -- respect to the evaluation context. + parentTimestamp = do + let lrh = latestRankedBlockPayloadHash state + hdr <- case lookup lrh plds of + Just x -> return x + Nothing -> tableLookup p lrh >>= \case + Nothing -> error $ "Chainweb.PayloadProvider.EVM.updateEvm: failed to find EVM payload header for target " <> sshow lrh + Just x -> return x + return $ EVM._hdrTimestamp hdr mkPayloadAttributes - :: EvmPayloadProvider logger - -> Chainweb.BlockHash - -- ^ The block hash of the block on which the new payload is created - -- This is available to the payload provider in the target consensus - -- state of the fork info of a syncToBlock call. + :: Chainweb.BlockHash + -- ^ ParentBeaconBlockRoo, i.e. the Chainweb block hash of the parent of + -- the new block. + -> Timestamp + -- ^ The timestamp of the /parent/ EVM header. + -> EVM.Address -> NewBlockCtx - -- The new block context from the fork info value. -> PayloadAttributesV3 -mkPayloadAttributes p ph nctx = PayloadAttributesV3 +mkPayloadAttributes ph parentTimestamp addr nctx = PayloadAttributesV3 { _payloadAttributesV3parentBeaconBlockRoot = EVM.chainwebBlockHashToBeaconBlockRoot ph , _payloadAttributesV2 = PayloadAttributesV2 { _payloadAttributesV2Withdrawals = [withdrawal] , _payloadAttributesV1 = PayloadAttributesV1 { _payloadAttributesV1Timestamp = et - , _payloadAttributesV1SuggestedFeeRecipient = minerAddress + , _payloadAttributesV1SuggestedFeeRecipient = addr , _payloadAttributesV1PrevRandao = randao } } } where MinerReward reward = _newBlockCtxMinerReward nctx - Just minerAddress = _evmMinerInfo p withdrawal = WithdrawalV1 { _withdrawalValidatorIndex = 0 , _withdrawalIndex = 0 , _withdrawalAmount = int $ reward - , _withdrawalAddress = minerAddress + , _withdrawalAddress = addr } -- FIXME: are there an assumptions about this value? + -- I guess, for now this fine -- better than something that looks random. randao = Utils.Randao (Ethereum.encodeLeN 0) - -- Ethereum timestamps are measured in /seconds/ since POSIX epoch. Chainweb - -- block creation times are measured in /micro-seconds/ since POSIX epoch. - -- - -- Chainweb requires that block creation times are strictly monotonic. - -- This means that blocks may be less than one second appart and rounding - -- would result in two or more consequecutive Ethereum headers having the - -- same time. Is that legal? - -- - et = ethTimestamp (_newBlockCtxParentCreationTime nctx) - --- FIXME -ethTimestamp :: BlockCreationTime -> Timestamp -ethTimestamp (BlockCreationTime (Time (TimeSpan (Micros t)))) = - EVM.Timestamp $ int $ (t `div` 1000000) - --- -------------------------------------------------------------------------- -- - --- fetchAndVerifyEvmHeader --- :: EvmPayloadProvider pdb --- -> EvaluationCtx --- -> IO Payload --- fetchAndVerifyEvmHeader p evalCtx = do --- where - --- | Obtain the EVM header for all entries of the Evaluation Ctx. --- --- The header is first looked up in the local payload. If it is not available --- locally it is fetched in the P2P network, validated against the evaluation --- context, and inserted into the database. --- --- getEvmHeader --- :: EvmPayloadProvider pdb --- -> RankedBlockPayloadHash --- -> IO Payload --- getEvmHeader p h = --- tableLookup (_evmPayloadDb evm) (Just $ EVM._blockHeight h) payloadHash >>= \case --- Nothing -> do --- p <- fetchPayloadFromRemote --- validateCtx evalCtx p --- return p --- Just p -> return p + et = EVM.timestamp parentTimestamp (_newBlockCtxParentCreationTime nctx) -- -------------------------------------------------------------------------- -- -- Await New Payload @@ -673,67 +812,44 @@ instance Exception EvmNewPayloadExeception newPayloadRate :: Int newPayloadRate = 1_000_000 +newPayloadTimeout :: Int +newPayloadTimeout = 30_000_000 + -- | If a payload id is available, new payloads for it. -- -awaitNewPayload :: EvmPayloadProvider logger -> IO () +-- This is called only if payload creation is enabled in the configuration. +-- +awaitNewPayload :: Logger logger => EvmPayloadProvider logger -> IO () awaitNewPayload p = do - T2 sstate pid <- atomically $ readTMVar (_evmPayloadId p) - resp <- JsonRpc.callMethodHttp @Engine_GetPayloadV3 ctx pid - - -- Response data - let v3 = _getPayloadV3ResponseExecutionPayload resp - v2 = _executionPayloadV2 v3 - v1 = _executionPayloadV1 v2 - h = EVM.numberToHeight $ _executionPayloadV1BlockNumber v1 - newEvmBlockHash = _executionPayloadV1BlockHash v1 - - -- check that this is a new payload - T2 curEvmBlockHash cur <- atomically $ readTMVar (_evmPayloadVar p) - unless (curEvmBlockHash == newEvmBlockHash) $ do - -- check that Blobs bundle is empty - unless (null $ _blobsBundleV1Blobs $ _getPayloadV3ResponseBlobsBundle resp) $ - throwM BlobsNotSupported - - -- Check that the block height matches the expected height - unless ( (_syncStateHeight sstate + 1) == h) $ - throwM $ InvalidNewPayloadHeight (Expected (_syncStateHeight sstate + 1)) (Actual h) - - -- Check that the fees of the execution paylod match the block value of the - -- response. - -- - unless (EVM._blockValueStu (_getPayloadV3ResponseBlockValue resp) == fees v1) $ - throwM InconsistentNewPayloadFees - { _inconsistentPayloadBlockValue = _getPayloadV3ResponseBlockValue resp - , _inconsistentPayloadFees = fees v1 - } - - let pld = executionPayloadV3ToHeader (_syncStateBlockHash sstate) v3 - - -- Check that the computed block hash matches the hash from the response - unless (newEvmBlockHash == EVM._hdrHash pld) $ - throwM $ InconsistenNewPayloadHash - (Expected newEvmBlockHash) - (Actual (EVM._hdrHash pld)) - - -- The actual payload header is included in the NewBlock structure in - -- as EncodedPayloadData. - atomically $ writeTMVar (_evmPayloadVar p) $ T2 newEvmBlockHash NewPayload - { _newPayloadTxCount = int $ length (_executionPayloadV1Transactions v1) - , _newPayloadSize = int $ sum $ (BS.length . _transactionBytes) - <$> (_executionPayloadV1Transactions v1) - , _newPayloadParentHeight = _syncStateHeight sstate - , _newPayloadParentHash = _syncStateBlockHash sstate - , _newPayloadOutputSize = 0 - , _newPayloadNumber = _newPayloadNumber cur + 1 - , _newPayloadFees = fees v1 - , _newPayloadEncodedPayloadOutputs = Nothing - , _newPayloadEncodedPayloadData = Just (EncodedPayloadData $ putRlpByteString pld) - , _newPayloadChainwebVersion = v - , _newPayloadChainId = cid - , _newPayloadBlockPayloadHash = EVM._hdrPayloadHash pld - } - threadDelay newPayloadRate + lf Debug "await new payload ID" + awaitPid >>= \case + Nothing -> do + lf Warn "timeout while waiting for new payloadID" + + -- DEBUG + T2 state bctx <- stateIO p + pld <- latestPayloadIO p + lf Warn $ briefJson state + lf Warn $ briefJson bctx + lf Warn $ briefJson pld + -- FIXME + -- We are probalby stuck. What can we do? Call forkchoiceUpdate + -- again? Or we just do nothing? Maybe it should be the job of + -- consensus to reissue a syncToBlock and get things going again? + -- + -- Well, let's just give it a try. + -- + -- Again, there is a race here, but we are already in trouble. So, + -- let's just ignore it for now. + -- T2 s nbctx <- readTVarIO (_evmState p) + -- updateEvm p s nbctx [] + + -- FIXME FIXME FIXME but let's also make sure that we actally need + -- this whole timeout thing. + + Just x -> go x where + lf = loggS p "awaitNewPayload" ctx = _evmEngineCtx p v = _chainwebVersion p cid = _chainId p @@ -743,6 +859,96 @@ awaitNewPayload p = do EVM.BaseFeePerGas bf = _executionPayloadV1BaseFeePerGas v1 GasUsed gu = _executionPayloadV1GasUsed v1 + -- Wait for payload from the execution client + -- FIXME not sure if the timeout is a good idea... + awaitPid = do + timeout <- registerDelay newPayloadTimeout + atomically $ + Nothing <$ (readTVar timeout >>= guard) + <|> + Just <$> takeTMVar (_evmPayloadId p) + + -- process the new payload + go pid = do + + lf Debug $ "got payload ID " <> encodeToText [pid] + resp <- RPC.callMethodHttp @Engine_GetPayloadV3 ctx [pid] + lf Debug $ "got execution payload for payload ID " <> toText pid + + -- FIXME if this fails with unknown payload, we need to issues a new + -- forkchoiceUpdate in order to obtain a new payload. Otherwise mining gets + -- stuck. + + -- Response data + let v3 = _getPayloadV3ResponseExecutionPayload resp + v2 = _executionPayloadV2 v3 + v1 = _executionPayloadV1 v2 + h = EVM.numberToHeight $ _executionPayloadV1BlockNumber v1 + newEvmBlockHash = _executionPayloadV1BlockHash v1 + + -- check that this is a new payload + atomically (tryReadTMVar (_evmPayloadVar p)) >>= \case + Just (T2 curEvmBlockHash _) + | curEvmBlockHash == newEvmBlockHash -> do + lf Info $ "the new execution payload is the same as the current payload. No update." + return () + x -> do + lf Debug $ "checking new execution payload for " <> toText pid + <> "; execution payload: " <> briefJson resp + + sstate <- latestStateIO p + + -- get revision number (zero if this is the first payload) + let n = maybe 0 (succ . _newPayloadNumber . ssnd) x + + -- check that Blobs bundle is empty + unless (null $ _blobsBundleV1Blobs $ _getPayloadV3ResponseBlobsBundle resp) $ + throwM BlobsNotSupported + + -- Check that the block height matches the expected height + unless ((_syncStateHeight sstate + 1) == h) $ + throwM $ InvalidNewPayloadHeight (Expected (_syncStateHeight sstate + 1)) (Actual h) + + -- Check that the fees of the execution paylod match the block + -- value of the response. + unless (EVM._blockValueStu (_getPayloadV3ResponseBlockValue resp) == fees v1) $ + throwM InconsistentNewPayloadFees + { _inconsistentPayloadBlockValue = _getPayloadV3ResponseBlockValue resp + , _inconsistentPayloadFees = fees v1 + } + + let pld = executionPayloadV3ToHeader (_syncStateBlockHash sstate) v3 + + -- Check that the computed block hash matches the hash from the + -- response + unless (newEvmBlockHash == EVM._hdrHash pld) $ do + lf Warn $ brief (_syncStateBlockHash sstate, _syncStateHeight sstate) + throwM $ InconsistenNewPayloadHash + (Expected newEvmBlockHash) + (Actual (EVM._hdrHash pld)) + + lf Info $ "got new payload " <> briefJson pld + + -- The actual payload header is included in the NewBlock + -- structure in as EncodedPayloadData. + lf Debug $ "write new payload to payload var for evm hash " <> briefJson newEvmBlockHash + atomically $ writeTMVar (_evmPayloadVar p) $ T2 newEvmBlockHash NewPayload + { _newPayloadTxCount = int $ length (_executionPayloadV1Transactions v1) + , _newPayloadSize = int $ sum $ (BS.length . _transactionBytes) + <$> (_executionPayloadV1Transactions v1) + , _newPayloadParentHeight = _syncStateHeight sstate + , _newPayloadParentHash = _syncStateBlockHash sstate + , _newPayloadBlockPayloadHash = EVM._hdrPayloadHash pld + , _newPayloadOutputSize = 0 + , _newPayloadNumber = n + , _newPayloadFees = fees v1 + , _newPayloadEncodedPayloadOutputs = Nothing + , _newPayloadEncodedPayloadData = Just (EncodedPayloadData $ putRlpByteString pld) + , _newPayloadChainwebVersion = v + , _newPayloadChainId = cid + } + threadDelay newPayloadRate + executionPayloadV3ToHeader :: Chainweb.BlockHash -> ExecutionPayloadV3 @@ -782,6 +988,9 @@ executionPayloadV3ToHeader phdr v3 = hdr -- -------------------------------------------------------------------------- -- -- Sync To Block +withLock :: MVar () -> IO a -> IO a +withLock l a = withMVar l (const a) + -- | Timeout for evmSyncToBlock -- forkchoiceUpdatedTimeout :: Micros @@ -840,42 +1049,47 @@ evmSyncToBlock -> Maybe Hints -> ForkInfo -> IO ConsensusState -evmSyncToBlock p hints forkInfo = do - curState <- stateIO p - newState <- if trgState == curState - then - -- The local sync state of the EVM EL is already at the target block. - return curState +evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do + T2 curState _ <- stateIO p + lf Debug $ "current state: " <> briefJson curState <> "; target state: " <> briefJson trgState + if trgState == curState + then do + + -- We are are already at the target state, but maybe payload production + -- is requested. + -- + -- FIXME do this only when new payload building is requested + -- + lf Info $ "current state requested: " <> brief (_consensusStateLatest curState) + updateEvm p curState pctx [] + else do -- Otherwise we'll take a look at the forkinfo trace -- lookup the fork info base payload hash + -- + -- NOTE we must query the local store directly and not use the P2P + -- network. + -- let rankedBaseHash = _forkInfoBaseRankedPayloadHash forkInfo tableLookup p rankedBaseHash >>= \case -- If we don't know that base, there's nothing we can do - Nothing -> return curState + Nothing -> do + lf Warn $ "unknown base " <> brief rankedBaseHash + lf Info $ sshow forkInfo - Just basePld -> case trace of + Just _ -> case trace of -- Case of an empty trace: -- - -- This succeeds only when we evaluated the target state before. - -- If it is in our database can attempt to sync to it, which may - -- either succeed or fail. NOTE we must query the local store - -- directly and not use the P2P network. + -- The base is the same as the target. The table lookup could + -- only succeed if we evaluated the target state before. If it + -- is in our database we can attempt to sync to it, which may + -- either succeed or fail. -- [] -> do - -- Lookup the target hash the local database - lookupConsensusState p trgState >>= \case - -- This case should not be possible because we looked up - -- the base already above. Anyways, we return state. - Nothing -> return curState - Just fcs -> do - -- In the case of an empty context we only care about - -- success or failure - _ <- liftIO $ forkchoiceUpdate p forkchoiceUpdatedTimeout doNewPld fcs - -- FIXME handle the case that the update fails - return trgState -- FIXME + lf Debug $ "empty trace" + updateEvm p trgState pctx [] l -> do -- in case of a non-empty trace, we lookup the first entry in @@ -899,9 +1113,11 @@ evmSyncToBlock p hints forkInfo = do let unknowns = fst <$> unknowns' + lf Debug $ "unkown blocks in context: " <> sshow (length unknowns) + -- fetch all unkown payloads -- - -- FIXME do the right thing here. Ideally, fetch all + -- FIXME do the right thing here. Ideally, fetch all -- unknowns in batches without redundant local lookups. Then -- validate all payloads together before sending them to the -- EVM and inserting them into the DB. @@ -913,40 +1129,52 @@ evmSyncToBlock p hints forkInfo = do validatePayload p pld ctx return (_evaluationCtxRankedPayloadHash ctx, pld) - lookupConsensusState p trgState >>= \case - Nothing -> return curState - Just fcs -> do - r <- forkchoiceUpdate p forkchoiceUpdatedTimeout doNewPld fcs - if - | EVM._hdrPayloadHash r == _latestPayloadHash trgState -> do - mapM_ (uncurry (tableInsert (_evmPayloadStore p))) plds - return trgState - | EVM._hdrPayloadHash r == _latestPayloadHash curState -> - return curState - | otherwise -> - -- FIXME do something smarter here - error "Chainweb.PayloadProvider.EVM.syncToBlock: failed to update EVM state" + lf Debug $ "fetched payloads for unkowns: " <> sshow (length plds) + + updateEvm p trgState pctx plds + + -- remove plds from canidate cache (anything in the db + -- shoudl be removed) + + newState <- sfst <$> stateIO p + lf Debug $ "done validating payloads. New state is " <> briefJson newState -- TODO cleeanup. In particular prune candidate store pruneCandidates p - return newState + sfst <$> stateIO p where + lf = loggS p "syncToBlock" trgState = _forkInfoTargetState forkInfo - trgLatest = _consensusStateLatest trgState - trgLatestHeight = _syncStateHeight trgLatest - trgLatestNumber = EVM.heightToNumber trgLatestHeight - trgLatestPayloadHash = _syncStateBlockPayloadHash trgLatest trace = _forkInfoTrace forkInfo - doNewPld = (_latestBlockHash trgState,) <$> _forkInfoNewBlockCtx forkInfo + pctx = _forkInfoNewBlockCtx forkInfo + +-- | The depth at which the candidate cache is pruned. We prune after each +-- successful syncToBlock call. Most of the time this cache is very small and +-- pruning should be very fast. +-- +-- * In normal operation, that just extends the chain, the a pruning depth of 0 is +-- fine +-- * For "normal" reorgs the diameter of the graph is sufficient. +-- +-- FIXME Make this depend on the catchup step size. +-- +-- * Remove items from this cache that are in the validated database. +-- +-- * For reorgs that are due to network forks/partitions the depth of the +-- catchup would be optimal, which is bounded by the reorg limit. For now we +-- ignore that case. We want a solution where we don't have permanent cost +-- overhead for this exceptional scenario. +-- +candidatePruningDepth :: EvmPayloadProvider logger -> BlockHeight -> BlockHeight +candidatePruningDepth p h = int $ diameter (chainGraphAt (_chainwebVersion p) h) pruneCandidates :: EvmPayloadProvider logger -> IO () pruneCandidates p = do - -- FIXME - -- - -- Use a ranked map as candidate store which can be easily pruned - -- We could use something similar to the payload cache - return () - + lrh <- latestRankedBlockPayloadHash . sfst <$> stateIO p + let h = _rankedHeight lrh + deleteLt (_evmCandidatePayloads p) lrh + { _rankedHeight = h - candidatePruningDepth p h + } -- | Fetch the payload for a given evaluation context. -- @@ -988,7 +1216,7 @@ getPayloadForContext p h ctx = do lf Warn $ "failed to decode encoded payload from evaluation ctx: " <> sshow e lf :: LogFunctionText - lf = logFunction (_evmLogger p) + lf = loggS p "getPayloadForContext" -- | FIXME: -- @@ -1035,6 +1263,78 @@ decodePayloadData (EncodedPayloadData bs) = decodeRlpM bs -- ATTIC -- -------------------------------------------------------------------------- -- +-- -------------------------------------------------------------------------- -- +-- Engine Calls + +getHashByNumber + :: JsonRpcHttpCtx + -> DefaultBlockParameter + -> IO (Maybe EVM.BlockHash) +getHashByNumber ctx p = fmap EVM._hdrHash <$> + callMethodHttp @Eth_GetBlockByNumber ctx (p, False) + +-- | Obtains a block by EVM block hash. It is an exception if the block can not +-- be found. +-- +-- Returns: EVM Header +-- +-- Throws: +-- +-- * EvmHeaderNotFoundException +-- * Eth RPC failures +-- * JSON RPC failures +-- +getBlockByHash + :: EvmPayloadProvider pdf + -> EVM.BlockHash + -> IO Payload +getBlockByHash p h = do + r <- RPC.callMethodHttp + @Eth_GetBlockByHash (_evmEngineCtx p) (h, False) + case r of + Just hdr -> return hdr + Nothing -> throwM $ EvmHeaderNotFoundByHash h + +-- | Obtains a block by number. It is an exception if the block can not be +-- found. +-- +-- Returns: EVM Header +-- +-- Throws: +-- +-- * EvmHeaderNotFoundException +-- * Eth RPC failures +-- * JSON RPC failures +-- +getBlockAtNumber + :: EvmPayloadProvider pdf + -> EVM.BlockNumber + -> IO Payload +getBlockAtNumber p n = do + r <- RPC.callMethodHttp + @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockNumber n, False) + case r of + Just h -> return h + Nothing -> throwM $ EvmHeaderNotFoundByNumber (DefaultBlockNumber n) + +-- | Returns the EVM genesis block. +-- +-- Returns: EVM Header +-- +-- Throws: +-- +-- * InvalidEvmState +-- * Eth RPC failures +-- * JSON RPC failures +-- +getGenesisHeader + :: EvmPayloadProvider pdf + -> IO Payload +getGenesisHeader p = try (getBlockAtNumber p 0) >>= \case + Left (EvmHeaderNotFoundByNumber _) -> throwM EvmGenesisHeaderNotFound + Left e -> throwM e + Right x -> return x + -- -------------------------------------------------------------------------- -- -- Utils for querying EVM block details for a consensus state @@ -1051,11 +1351,9 @@ decodePayloadData (EncodedPayloadData bs) = decodeRlpM bs -- referenced block is on different fork. -- getLatestHdr - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdb + :: EvmPayloadProvider pdb -> ConsensusState - -> m Payload + -> IO Payload getLatestHdr p s = do hdr <- getBlockAtNumber p (_latestNumber s) unless (view EVM.hdrPayloadHash hdr == _latestPayloadHash s) $ @@ -1077,11 +1375,9 @@ getLatestHdr p s = do -- block must be on a different fork, too. -- getSafeHdr - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdb + :: EvmPayloadProvider pdb -> ConsensusState - -> m Payload + -> IO Payload getSafeHdr p s = do hdr <- getBlockAtNumber p (_safeNumber s) unless (view EVM.hdrPayloadHash hdr == _safePayloadHash s) $ @@ -1103,11 +1399,9 @@ getSafeHdr p s = do -- safe block must be on a different fork, too. -- getFinalHdr - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdb + :: EvmPayloadProvider pdb -> ConsensusState - -> m Payload + -> IO Payload getFinalHdr p s = do hdr <- getBlockAtNumber p (_finalNumber s) unless (view EVM.hdrPayloadHash hdr == _finalPayloadHash s) $ @@ -1119,11 +1413,9 @@ getFinalHdr p s = do -- Cf. 'getLatestHeader' for details. -- getLatestHash - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdb + :: EvmPayloadProvider pdb -> ConsensusState - -> m EVM.BlockHash + -> IO EVM.BlockHash getLatestHash p = fmap (view EVM.hdrHash) . getLatestHdr p -- | Get the hash for the safe block in a consensus state. @@ -1131,11 +1423,9 @@ getLatestHash p = fmap (view EVM.hdrHash) . getLatestHdr p -- Cf. 'getSafeHeader' for details. -- getSafeHash - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdb + :: EvmPayloadProvider pdb -> ConsensusState - -> m EVM.BlockHash + -> IO EVM.BlockHash getSafeHash p = fmap (view EVM.hdrHash) . getSafeHdr p -- | Get the hash for the final block in a consensus state. @@ -1143,101 +1433,8 @@ getSafeHash p = fmap (view EVM.hdrHash) . getSafeHdr p -- Cf. 'getFinalHeader' for details. -- getFinalHash - :: MonadThrow m - => MonadIO m - => EvmPayloadProvider pdb + :: EvmPayloadProvider pdb -> ConsensusState - -> m EVM.BlockHash + -> IO EVM.BlockHash getFinalHash p = fmap (view EVM.hdrHash) . getFinalHdr p --- -------------------------------------------------------------------------- -- --- Old Notes - --- instance PayloadProvider (EvmPayloadProvider payloadDb) where --- --- syncToBlock evm maybeOrigin forkInfo = do --- currentSyncState <- readTVarIO evm._evmState --- if currentSyncState == targetSyncState --- then return (Just currentSyncState) --- else do --- ps <- traverse fetchAndVerifyEvmHeader forkInfo._forkInfoTrace --- let maybeTargetCtx = NonEmpty.last <$> NonEmpty.nonEmpty forkInfo._forkInfoTrace --- -- TODO: check EvaluationCtx against EVM headers --- -- TODO: also check that the ctxs form a chain ending in the target --- case maybeTargetCtx of --- -- there was not enough information in the trace to change blocks --- Nothing -> return (Just currentSyncState) --- Just targetCtx -> do --- tableLookup evm._evmPayloadDb targetSyncState >>= \case --- -- TODO: fetch EVM headers here later? --- Nothing -> error "missing EVM header in payloaddb" --- Just evmHeader -> do --- let evmHash = EVM.blockHash evmHeader --- resp <- JsonRpc.callMethodHttp --- @"engine_forkchoiceUpdatedV3" evm._evmEngineCtx (forkChoiceRequest evmHash) --- --- case resp._forkchoiceUpdatedV1ResponsePayloadStatus._payloadStatusV1Status of --- Valid -> do --- listenToPayloadId resp._forkchoiceUpdatedV1ResponsePayloadId --- return (Just targetSyncState) --- Invalid -> return Nothing --- Syncing -> waitForSync evmHash --- Accepted -> error "invalid" --- InvalidBlockHash -> return Nothing --- where --- targetSyncState = forkInfo._forkInfoTarget --- forkChoiceRequest evmHash = ForkchoiceUpdatedV3Request --- { _forkchoiceUpdatedV3RequestState = ForkchoiceStateV1 evmHash evmHash evmHash --- , _forkchoiceUpdatedV3RequestPayloadAttributes = --- evm._evmMinerInfo <&> \minerAddress -> PayloadAttributesV3 --- { _payloadAttributesV3parentBeaconBlockRoot = --- EVM.chainwebBlockHashToBeaconBlockRoot targetSyncState._rankedBlockHash --- , _payloadAttributesV2 = PayloadAttributesV2 --- -- TODO: add withdrawals for paying miners block rewards? --- { _payloadAttributesV2Withdrawals = [] --- , _payloadAttributesV1 = PayloadAttributesV1 --- -- TODO: add real timestamp? --- { _payloadAttributesV1Timestamp = Ethereum.Timestamp 0 --- -- TODO: use random number derived from PoW hash? --- , _payloadAttributesV1PrevRandao = Utils.Randao (Ethereum.encodeLeN 0) --- -- TODO: does this suffice to pay miners gas fees? --- , _payloadAttributesV1SuggestedFeeRecipient = minerAddress --- } --- } --- } --- } --- fetchAndVerifyEvmHeader :: EvaluationCtx -> IO Payload --- fetchAndVerifyEvmHeader evalCtx = do --- tableLookup evm.payloadDb (Just $ view blockHeight h) payloadHash >>= \case --- Nothing -> do --- p <- fetchPayloadFromRemote --- validateCtx evalCtx p --- return p --- Just p -> return p --- waitForSync evmHash = JsonRpc.callMethodHttp @"eth_syncing" evm._evmEngineCtx Nothing >>= \case --- SyncingStatusFalse -> do --- resp <- JsonRpc.callMethodHttp @"engine_forkchoiceUpdatedV3" evm._evmEngineCtx (forkChoiceRequest evmHash) --- case resp._forkchoiceUpdatedV1ResponsePayloadStatus._payloadStatusV1Status of --- Valid -> do --- listenToPayloadId resp._forkchoiceUpdatedV1ResponsePayloadId --- return $ Just forkInfo._forkInfoTarget --- Invalid -> return Nothing --- Syncing -> error "that syncing feeling..." --- Accepted -> error "invalid" --- InvalidBlockHash -> return Nothing --- --- SyncingStatus --- { _syncingStatusStartingBlock --- , _syncingStatusCurrentBlock = Ethereum.BlockNumber current --- , _syncingStatusHighestBlock = Ethereum.BlockNumber highest --- } -> do --- -- todo: use the current speed to make an estimate here --- approximateThreadDelay (max 10000 (int highest - int current * 10000)) --- waitForSync evmHash --- listenToPayloadId = \case --- Just x | isJust evm._evmMinerInfo -> --- atomically $ writeTMVar evm._evmPayloadId x --- Nothing | isNothing evm._evmMinerInfo -> --- return () --- pid -> error --- $ "mining is: " <> maybe "disabled" (\_ -> "enabled") evm._evmMinerInfo From d50a7252d77290f7c3734d5c6e4c397e84c30145 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 19 Nov 2024 22:57:19 -0800 Subject: [PATCH 078/378] Add Ethereum Cacun EL Header --- chainweb.cabal | 1 + src/Chainweb/MerkleUniverse.hs | 44 +- src/Chainweb/PayloadProvider/EVM/Header.hs | 821 +++++++++++++++++++++ 3 files changed, 865 insertions(+), 1 deletion(-) create mode 100644 src/Chainweb/PayloadProvider/EVM/Header.hs diff --git a/chainweb.cabal b/chainweb.cabal index 4d5aa5dca6..aedeb1a57e 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -229,6 +229,7 @@ library , Chainweb.PayloadProvider.EVM , Chainweb.PayloadProvider.EVM.EngineAPI , Chainweb.PayloadProvider.EVM.EthRpcAPI + , Chainweb.PayloadProvider.EVM.Header , Chainweb.PayloadProvider.EVM.JsonRPC , Chainweb.PayloadProvider.EVM.Utils , Chainweb.PayloadProvider.Initialization diff --git a/src/Chainweb/MerkleUniverse.hs b/src/Chainweb/MerkleUniverse.hs index 00b916812d..6d3c0bbb5c 100644 --- a/src/Chainweb/MerkleUniverse.hs +++ b/src/Chainweb/MerkleUniverse.hs @@ -101,6 +101,27 @@ data ChainwebHashTag -- Minimal Payload Provider | MinimalPayloadTag + -- Ethereum EL + | EthParentHashTag + | EthOmmersHashTag + | EthBeneficiaryTag + | EthStateRootTag + | EthTransactionsRootTag + | EthReceiptsRootTag + | EthBloomTag + | EthDifficultyTag + | EthBlockNumberTag + | EthGasLimitTag + | EthGasUsedTag + | EthTimestampTag + | EthExtraDataTag + | EthRandaoTag + | EthNonceTag + | EthBaseFeePerGasTag + | EthWithdrawalsRootTag + | EthBlobGasUsedTag + | EthExcessBlobGasTag + | EthParentBeaconBlockRootTag deriving (Show, Eq) instance MerkleUniverse ChainwebHashTag where @@ -134,6 +155,28 @@ instance MerkleUniverse ChainwebHashTag where -- Minimal Payload Provider type MerkleTagVal ChainwebHashTag 'MinimalPayloadTag = 0x0035 + -- Ethereum EL + type MerkleTagVal ChainwebHashTag 'EthParentHashTag = 0x0040 + type MerkleTagVal ChainwebHashTag 'EthOmmersHashTag = 0x0041 + type MerkleTagVal ChainwebHashTag 'EthBeneficiaryTag = 0x0042 + type MerkleTagVal ChainwebHashTag 'EthStateRootTag = 0x0043 + type MerkleTagVal ChainwebHashTag 'EthTransactionsRootTag = 0x0044 + type MerkleTagVal ChainwebHashTag 'EthReceiptsRootTag = 0x0045 + type MerkleTagVal ChainwebHashTag 'EthBloomTag = 0x0046 + type MerkleTagVal ChainwebHashTag 'EthDifficultyTag = 0x0047 + type MerkleTagVal ChainwebHashTag 'EthBlockNumberTag = 0x0048 + type MerkleTagVal ChainwebHashTag 'EthGasLimitTag = 0x0049 + type MerkleTagVal ChainwebHashTag 'EthGasUsedTag = 0x004a + type MerkleTagVal ChainwebHashTag 'EthTimestampTag = 0x004b + type MerkleTagVal ChainwebHashTag 'EthExtraDataTag = 0x004c + type MerkleTagVal ChainwebHashTag 'EthRandaoTag = 0x004d + type MerkleTagVal ChainwebHashTag 'EthNonceTag = 0x004e + type MerkleTagVal ChainwebHashTag 'EthBaseFeePerGasTag = 0x004f + type MerkleTagVal ChainwebHashTag 'EthWithdrawalsRootTag = 0x0050 + type MerkleTagVal ChainwebHashTag 'EthBlobGasUsedTag = 0x0051 + type MerkleTagVal ChainwebHashTag 'EthExcessBlobGasTag = 0x0052 + type MerkleTagVal ChainwebHashTag 'EthParentBeaconBlockRootTag = 0x0053 + instance HashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Void where type Tag Void = 'VoidTag toMerkleNode = \case @@ -183,4 +226,3 @@ data MerkleRootMismatch = MerkleRootMismatch deriving (Show, Eq, Ord, Generic, NFData) instance Exception MerkleRootMismatch - diff --git a/src/Chainweb/PayloadProvider/EVM/Header.hs b/src/Chainweb/PayloadProvider/EVM/Header.hs new file mode 100644 index 0000000000..e409fb0d34 --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/Header.hs @@ -0,0 +1,821 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE NumericUnderscores #-} + +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.Header +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- Execution Layer Header For Cacun Hardfork +-- +-- For the specification up to the Shanghai Hardfork see: +-- +-- [Yellow Paper (SHANGHAI VERSION f3553dd – 2024-11-04), 4.4 The Block](https://ethereum.github.io/yellowpaper/paper.pdf) +-- +-- For the Cacun specificic addition see: +-- +-- [python execution-specs](https://github.com/ethereum/execution-specs/blob/master/src/ethereum/cancun/blocks.py) +-- +module Chainweb.PayloadProvider.EVM.Header +( Header(..) +, ommersHash +, difficulty +, nonce +, headerProof +, runHeaderProof +, BaseFeePerGas(..) +, BlobGasUsed(..) +, ExcessBlobGas(..) +, WithdrawalsRoot(..) +, ParentBeaconBlockRoot(..) +, chainwebBlockHashToBeaconBlockRoot +, beaconBlockRootToChainwebBlockHash +, computeBlockHash +, computeBlockPayloadHash + +-- * Misc + +, type HeaderCas +, heightToNumber +, numberToHeight +, _hdrHeight +, timestamp + +-- * Getter +, hdrParentHash +, hdrOmmersHash +, hdrBeneficiary +, hdrStateRoot +, hdrTransactionsRoot +, hdrReceiptsRoot +, hdrLogsBloom +, hdrDifficulty +, hdrNumber +, hdrGasLimit +, hdrGasUsed +, hdrTimestamp +, hdrExtraData +, hdrPrevRandao +, hdrNonce +, hdrBaseFeePerGas +, hdrWithdrawalsRoot +, hdrHeight +, hdrHash +, hdrPayloadHash +) where + +import Chainweb.BlockCreationTime qualified as Chainweb +import Chainweb.BlockHash qualified as Chainweb +import Chainweb.BlockHeight qualified as Chainweb +import Chainweb.BlockPayloadHash +import Chainweb.Crypto.MerkleLog hiding (headerProof) +import Chainweb.Crypto.MerkleLog qualified as MerkleLog +import Chainweb.MerkleLogHash +import Chainweb.MerkleUniverse +import Chainweb.PayloadProvider.EVM.Utils +import Chainweb.Storage.Table +import Chainweb.Time +import Chainweb.Utils (sshow) +import Chainweb.Utils.Serialization (runGetS) +import Chainweb.Utils.Serialization qualified as Chainweb +import Control.Lens (Getter, to) +import Control.Monad.Catch +import Data.Aeson +import Data.Aeson.Types (Pair) +import Data.ByteString.Short qualified as BS +import Data.ByteString.Short qualified as SBS +import Data.Function +import Data.Hashable (Hashable(..)) +import Data.MerkleLog +import Data.Ratio ((%)) +import Data.Text qualified as T +import Data.Void +import Ethereum.Misc +import Ethereum.RLP +import Ethereum.Utils +import Foreign.Storable +import GHC.Generics (Generic) +import GHC.TypeNats + +-- -------------------------------------------------------------------------- +-- Utils + +numberToHeight :: BlockNumber -> Chainweb.BlockHeight +numberToHeight n = fromIntegral n + +heightToNumber :: Chainweb.BlockHeight -> BlockNumber +heightToNumber = fromIntegral + +-- -------------------------------------------------------------------------- -- + +-- | Compute the Ethereum Timestamp for a block headder +-- +-- In chainweb the payload time is the block creation of the parent header. It +-- must be strictly monotonically increasing at microsecond precision. +-- +-- The EVM requires that timestamps of EVM payload headers are stricly +-- monotonically increasing at second precision with respect to the parent +-- relation. +-- +-- Chainweb also requires that all data in the preimage of the block payload +-- hash is derived deterministcally from the current payload provider state (as +-- captured by preimage of the previous block payload hash), the user +-- transactions, and the chainweb parent header (the ParentBeaconBlockRoot). +-- +-- In chainweb it can happen that blocks are less than 1s appart. The +-- constraints for timestamps are satisfied by using the smallest integral value +-- that is larger or equal than the block creation time of the chainweb parent +-- timestamp (the ParentBeaconBlockRoot) and strictly larger than the timestamp +-- of the EVM parent header. +-- +timestamp + :: Timestamp + -- ^ Timestamp of the EVM parent header + -> Chainweb.BlockCreationTime + -- ^ BlockCreationTime of the ParentBeaconBlockRoot + -> Timestamp +timestamp (Timestamp e) (Chainweb.BlockCreationTime (Time (TimeSpan (Micros t)))) = + Timestamp $ max (e + 1) cwTimestamp + where + cwTimestamp = ceiling (t % 1_000_000) + +-- ------- ------------------------------------------------------------------- -- +-- Header Fields for Paris Hardfork +-- +-- The mixHash field was repurposed in the Paris hardfork to store the previous +-- RANDAO mix. + +-- | Base Fee Per Gas of a Block +-- +-- A natural number [cf. yellow paper 4.4.3 (44)] +-- +newtype BaseFeePerGas = BaseFeePerGas Natural + deriving (Show, Eq) + deriving newtype (RLP) + deriving ToJSON via (HexQuantity Natural) + deriving FromJSON via (HexQuantity Natural) + +-- -------------------------------------------------------------------------- -- +-- Header Fields for Shanghai Hardfork + +-- | Withdrawals Root +-- +-- 32 bytes [cf. yellow paper 4.4.3 (44)] +-- +newtype WithdrawalsRoot = WithdrawalsRoot (BytesN 32) + deriving (Show, Eq) + deriving newtype (RLP, Bytes, Storable, Hashable) + deriving ToJSON via (HexBytes (BytesN 32)) + deriving FromJSON via (HexBytes (BytesN 32)) + +-- -------------------------------------------------------------------------- -- +-- Header Fields for Cacun Hardfork + +-- | Parent Beacon Block Root +-- +-- Since Cacun Hardfork +-- +newtype ParentBeaconBlockRoot = ParentBeaconBlockRoot (BytesN 32) + deriving (Show, Eq) + deriving newtype (RLP, Bytes, Storable, Hashable) + deriving ToJSON via (HexBytes (BytesN 32)) + deriving FromJSON via (HexBytes (BytesN 32)) + +chainwebBlockHashToBeaconBlockRoot :: Chainweb.BlockHash -> ParentBeaconBlockRoot +chainwebBlockHashToBeaconBlockRoot bh = ParentBeaconBlockRoot + (unsafeBytesN $ SBS.toShort $ Chainweb.runPutS (Chainweb.encodeBlockHash bh)) + +beaconBlockRootToChainwebBlockHash :: ParentBeaconBlockRoot -> Chainweb.BlockHash +beaconBlockRootToChainwebBlockHash bs = + case runGetS @_ @Chainweb.BlockHash Chainweb.decodeBlockHash (bytes bs) of + Left e -> error $ "Chainweb.PayloadProvider.EVM.Header.beaconBlockRootToChainwebBlockHash. invalid beacon block root: " <> sshow e + Right x -> x + +-- | Gas Used for Blob +-- +-- Since Cacun Hardfork +-- +newtype BlobGasUsed = BlobGasUsed Natural + deriving (Show, Eq) + deriving newtype (RLP) + deriving ToJSON via (HexQuantity Natural) + deriving FromJSON via (HexQuantity Natural) + +-- | Excess Blob Gas +-- +-- Since Cacun Hardfork +-- +newtype ExcessBlobGas = ExcessBlobGas Natural + deriving (Show, Eq) + deriving newtype (RLP) + deriving ToJSON via (HexQuantity Natural) + deriving FromJSON via (HexQuantity Natural) + +-- -------------------------------------------------------------------------- -- +-- Header + +-- | Execution Layer Header +-- +-- For the specification up to the Shanghai Hardfork see: +-- +-- [Yellow Paper (SHANGHAI VERSION f3553dd – 2024-11-04), 4.4 The Block](https://ethereum.github.io/yellowpaper/paper.pdf) +-- +-- For the Cacun specificic addition see: +-- +-- [python execution-specs](https://github.com/ethereum/execution-specs/blob/master/src/ethereum/cancun/blocks.py) +-- +-- +data Header = Header + { _hdrParentHash :: !ParentHash + -- ^ The Keccak 256-bit hash of the parent block’s header, in its + -- entirety; formally \(H_p\). + , _hdrOmmersHash :: !OmmersHash + -- ^ A 256-bit hash field that is now deprecated due to the replacement + -- of proof of work consensus. It is now to a constant, KEC(RLP(())); + -- formally \(H_o\). + , _hdrBeneficiary :: !Beneficiary + -- ^ The 160-bit address to which priority fees from this block are + -- transferred; formally \(H_c\). + , _hdrStateRoot :: !StateRoot + -- ^ The Keccak 256-bit hash of the root node of the state trie, after + -- all transactions and withdrawals are executed and finalisations + -- applied; formally \(H_r\). + , _hdrTransactionsRoot :: !TransactionsRoot + -- ^ The Keccak 256-bit hash of the root node of the trie structure + -- populated with each transaction in the transactions list portion of + -- the block; formally \(H_t\). + , _hdrReceiptsRoot :: !ReceiptsRoot + -- ^ The Keccak 256-bit hash of the root node of the trie structure + -- populated with the receipts of each transaction in the transactions + -- list portion of the block; formally \(H_e\). + , _hdrLogsBloom :: !Bloom + -- ^ The Bloom filter composed from indexable information (logger + -- address and log topics) contained in each log entry from the receipt + -- of each transaction in the transactions list; formally \(H_b\). + , _hdrDifficulty :: !Difficulty + -- ^ A scalar field that is now deprecated due to the replacement of + -- proof of work consensus. It is set to 0; formally \(H_d\). + , _hdrNumber :: !BlockNumber + -- ^ A scalar value equal to the number of ancestor blocks. The genesis + -- block has a number of zero; formally \(H_i\). + , _hdrGasLimit :: !GasLimit + -- ^ A scalar value equal to the current limit of gas expenditure per + -- block; formally \(H_l\). + , _hdrGasUsed :: !GasUsed + -- ^ A scalar value equal to the total gas used in transactions in this + -- block; formally \(H_g\). + , _hdrTimestamp :: !Timestamp + -- ^ A scalar value equal to the reasonable output of Unix’s time() at + -- this block’s inception; formally \(H_s\). + , _hdrExtraData :: !ExtraData + -- ^ An arbitrary byte array containing data relevant to this block. + -- This must be 32 bytes or fewer; formally \(H_x\). + , _hdrPrevRandao :: !Randao + -- ^ the latest RANDAO mix7 of the post beacon state of the previous + -- block; formally \(H_a\). + , _hdrNonce :: !Nonce + -- ^ A 64-bit value that is now deprecated due to the replacement of + -- proof of work consensus. It is set to 0x0000000000000000; formally + -- \(H_n\). + -- + -- In parts of the API the nonce is interpreded as a Word64 scalar. In + -- RLP serialization it is considered as bytes. Hence, we store it as + -- bytes. Note, however, that it is used in reversed byte order in the + -- Ethhash computation. + , _hdrBaseFeePerGas :: !BaseFeePerGas + -- ^ A scalar value equal to the amount of wei that is burned for each + -- unit of gas consumed; formally \(H_f\). + , _hdrWithdrawalsRoot :: !WithdrawalsRoot + -- ^ The Keccak 256-bit hash of the root node of the trie structure + -- populated with each withdrawal operations pushed by the consensus + -- layer for this block; formally \(H_w\). + + -- The following fields are from the Cacun hard fork which is not yet + -- described in the Yellow Paper. + -- + -- cf. https://github.com/ethereum/execution-specs/blob/master/src/ethereum/cancun/blocks.py + + , _hdrBlobGasUsed :: !BlobGasUsed + , _hdrExcessBlobGas :: !ExcessBlobGas + , _hdrParentBeaconBlockRoot :: !ParentBeaconBlockRoot + + -- synthetic fields + , _hdrHash :: {- Lazy -} BlockHash + -- ^ Ethereum Execution Header Block Hash + , _hdrPayloadHash :: {- Lazy -} BlockPayloadHash + -- ^ Chainweb BlockPayloadHash + } + deriving (Show, Generic) + +instance Hashable Header where + hashWithSalt s = hashWithSalt s . _hdrPayloadHash + {-# INLINEABLE hashWithSalt #-} + +instance Eq Header where + (==) = (==) `on` _hdrPayloadHash + +instance Ord Header where + compare = compare `on` (\h -> (_hdrNumber h, _hdrPayloadHash h)) + +instance IsCasValue Header where + type CasKeyType Header = BlockPayloadHash + casKey = _hdrPayloadHash + {-# INLINE casKey #-} + +type HeaderCas tbl = Cas tbl Header + +_hdrHeight :: Header -> Chainweb.BlockHeight +_hdrHeight = numberToHeight . _hdrNumber + +instance Hashable BlockHash where + hashWithSalt s h = let BlockHash r = h in hashWithSalt s r + {-# INLINEABLE hashWithSalt #-} + +-- -------------------------------------------------------------------------- -- +-- Internal Block Hash Computation + +-- | Does not force _hdrHash or _hdrPayloadHash +-- +computeBlockHash :: Header -> BlockHash +computeBlockHash = BlockHash . keccak256 . putRlpByteString +{-# INLINE computeBlockHash #-} + +-- | Does not force _hdrHash or _hdrPayloadHash +-- +computeBlockPayloadHash :: Header -> BlockPayloadHash +computeBlockPayloadHash h = BlockPayloadHash $ MerkleLogHash $ computeMerkleLogRoot h +{-# INLINE computeBlockPayloadHash #-} + +-- -------------------------------------------------------------------------- -- +-- Merkle Proofs +-- +-- Example: +-- +-- ghci> Just p = headerProof @BlockNumber hdr +-- ghci> runHeaderProof p == blockPayloadHash hdr +-- True +-- ghci> proofSubject @_ @_ @BlockNumber p +-- BlockNumber 6373 + +-- | Creates a Merkle proof for a header property of an EVM execution header. +-- +headerProof + :: forall c a m + . MonadThrow m + => a ~ ChainwebMerkleHashAlgorithm + => HasHeader a ChainwebHashTag c (MkLogType a ChainwebHashTag Header) + => Header + -> m (MerkleProof a) +headerProof = MerkleLog.headerProof @c +{-# INLINE headerProof #-} + +-- | Runs a header proof. Returns the BlockPayloadHash of the EVM execution +-- header for which inclusion is proven. +-- +runHeaderProof :: MerkleProof ChainwebMerkleHashAlgorithm -> BlockPayloadHash +runHeaderProof = BlockPayloadHash . MerkleLogHash . runMerkleProof +{-# INLINE runHeaderProof #-} + +-- -------------------------------------------------------------------------- -- +-- Constant Values after Paris Hardfork + +-- | Since the Paris Hardfork the nonce is KEC(RLP(())) +-- +-- [cf. 4.4.4 (57)](https://ethereum.github.io/yellowpaper/paper.pdf) +-- +ommersHash :: OmmersHash +ommersHash = OmmersHash $ keccak256 $ putRlpByteString () +{-# INLINE ommersHash #-} + +-- | Since the Paris Hardfork the difficulty is 0. +-- +-- [cf. 4.4.4 (58)](https://ethereum.github.io/yellowpaper/paper.pdf) +-- +difficulty :: Difficulty +difficulty = Difficulty 0 +{-# INLINE difficulty #-} + +-- | Since the Paris Hardfork the nonce is 0x0000000000000000. +-- +-- [cf. 4.4.4 (59)](https://ethereum.github.io/yellowpaper/paper.pdf) +-- +nonce :: Nonce +nonce = Nonce $ replicateN 0 +{-# INLINE nonce #-} + +-- -------------------------------------------------------------------------- -- +-- JSON Serialization + +-- | These are the property names of the respective RPC API properties +-- +-- The JSON serialization also includes the block hash. +-- +headerProperties :: KeyValue e kv => Header -> [kv] +headerProperties o = + [ "parentHash" .= _hdrParentHash o + , "sha3Uncles" .= _hdrOmmersHash o + , "miner" .= _hdrBeneficiary o + , "stateRoot" .= _hdrStateRoot o + , "transactionsRoot" .= _hdrTransactionsRoot o + , "receiptsRoot" .= _hdrReceiptsRoot o + , "logsBloom" .= _hdrLogsBloom o + , "difficulty" .= _hdrDifficulty o + , "number" .= _hdrNumber o + , "gasLimit" .= _hdrGasLimit o + , "gasUsed" .= _hdrGasUsed o + , "timestamp" .= _hdrTimestamp o + , "extraData" .= _hdrExtraData o + , "mixHash" .= _hdrPrevRandao o -- legacy property name for prev randao + , "nonce" .= _hdrNonce o + , "baseFeePerGas" .= _hdrBaseFeePerGas o + , "withdrawalsRoot" .= _hdrWithdrawalsRoot o + , "blobGasUsed" .= _hdrBlobGasUsed o + , "excessBlobGas" .= _hdrExcessBlobGas o + , "parentBeaconBlockRoot" .= _hdrParentBeaconBlockRoot o + , "hash" .= _hdrHash o + ] +{-# INLINE headerProperties #-} +{-# SPECIALIZE headerProperties :: Header -> [Series] #-} +{-# SPECIALIZE headerProperties :: Header -> [Pair] #-} + +instance ToJSON Header where + toEncoding = pairs . mconcat . headerProperties + toJSON = object . headerProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON Header where + parseJSON v = do + hdr <- flip (withObject "ConsensusHeader") v $ \o -> Header + <$> o .: "parentHash" + <*> o .: "sha3Uncles" + <*> o .: "miner" + <*> o .: "stateRoot" + <*> o .: "transactionsRoot" + <*> o .: "receiptsRoot" + <*> o .: "logsBloom" + <*> o .: "difficulty" + <*> o .: "number" + <*> o .: "gasLimit" + <*> o .: "gasUsed" + <*> o .: "timestamp" + <*> o .: "extraData" + <*> o .: "mixHash" -- legacy property name for prev randao + <*> o .: "nonce" + <*> o .: "baseFeePerGas" + <*> o .: "withdrawalsRoot" + <*> o .: "blobGasUsed" + <*> o .: "excessBlobGas" + <*> o .: "parentBeaconBlockRoot" + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: _hdrHash") + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: _hdrPayloadHash") + return hdr + { _hdrHash = computeBlockHash hdr + , _hdrPayloadHash = computeBlockPayloadHash hdr + } + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Serialization + +-- | RLP Encoding for Execution Layer Header +-- +-- For the specification up to the Shanghai Hardfork see: +-- +-- [cf. Yellow Paper 4.4.3 (44)](https://ethereum.github.io/yellowpaper/paper.pdf) +-- +-- LH(H) ≡ (Hp,Ho,Hc,Hr,Ht,He,Hb,Hd,Hi,Hl,Hg,Hs,Hx,Ha,Hn,Hf,Hw) +-- +instance RLP Header where + putRlp hdr = putRlpL + [ putRlp $ _hdrParentHash hdr + , putRlp $ _hdrOmmersHash hdr + , putRlp $ _hdrBeneficiary hdr + , putRlp $ _hdrStateRoot hdr + , putRlp $ _hdrTransactionsRoot hdr + , putRlp $ _hdrReceiptsRoot hdr + , putRlp $ _hdrLogsBloom hdr + , putRlp $ _hdrDifficulty hdr + , putRlp $ _hdrNumber hdr + , putRlp $ _hdrGasLimit hdr + , putRlp $ _hdrGasUsed hdr + , putRlp $ _hdrTimestamp hdr + , putRlp $ _hdrExtraData hdr + , putRlp $ _hdrPrevRandao hdr + , putRlp $ _hdrNonce hdr + , putRlp $ _hdrBaseFeePerGas hdr + , putRlp $ _hdrWithdrawalsRoot hdr + + -- Cancun Hardfork + , putRlp $ _hdrBlobGasUsed hdr + , putRlp $ _hdrExcessBlobGas hdr + , putRlp $ _hdrParentBeaconBlockRoot hdr + ] + + getRlp = label "Header" $ do + hdr <- getRlpL $ Header + <$> getRlp -- parent hash + <*> getRlp -- ommers hash + <*> getRlp -- beneficiary + <*> getRlp -- state root + <*> getRlp -- transactions root + <*> getRlp -- receipts root + <*> getRlp -- logs bloom + <*> getRlp -- difficulty + <*> getRlp -- number + <*> getRlp -- gas limit + <*> getRlp -- gas used + <*> getRlp -- timestamp + <*> getRlp -- extra data + <*> getRlp -- prev randao + <*> getRlp -- nonce + <*> getRlp -- base fee per gas + <*> getRlp -- withdrawals root + + -- Cancun Hardfork + <*> getRlp -- blob gas used + <*> getRlp -- excess blob gas + <*> getRlp -- parent beacon block root + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: _hdrHash") + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: _hdrPayloadHash") + return hdr + { _hdrHash = computeBlockHash hdr + , _hdrPayloadHash = computeBlockPayloadHash hdr + } + + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +-- -------------------------------------------------------------------------- -- +-- MerkleLog Entries + +newtype RlpMerkleLogEntry (tag :: ChainwebHashTag) t = RlpMerkleLogEntry t + deriving newtype RLP +instance + ( KnownNat (MerkleTagVal ChainwebHashTag tag) + , MerkleHashAlgorithm a + , RLP t + ) + => IsMerkleLogEntry a ChainwebHashTag (RlpMerkleLogEntry tag t) where + type Tag (RlpMerkleLogEntry tag t) = tag + toMerkleNode = InputNode . putRlpByteString + fromMerkleNode (InputNode bs) = case get getRlp bs of + Left e -> throwM $ MerkleLogDecodeException (T.pack e) + Right x -> Right x + fromMerkleNode (TreeNode _) = throwM expectedInputNodeException + +deriving via (RlpMerkleLogEntry 'EthParentHashTag ParentHash) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ParentHash + +deriving via (RlpMerkleLogEntry 'EthOmmersHashTag OmmersHash) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag OmmersHash + +deriving via (RlpMerkleLogEntry 'EthBeneficiaryTag Beneficiary) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Beneficiary + +deriving via (RlpMerkleLogEntry 'EthStateRootTag StateRoot) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag StateRoot + +deriving via (RlpMerkleLogEntry 'EthTransactionsRootTag TransactionsRoot) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag TransactionsRoot + +deriving via (RlpMerkleLogEntry 'EthReceiptsRootTag ReceiptsRoot) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ReceiptsRoot + +deriving via (RlpMerkleLogEntry 'EthBloomTag Bloom) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Bloom + +deriving via (RlpMerkleLogEntry 'EthDifficultyTag Difficulty) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Difficulty + +deriving via (RlpMerkleLogEntry 'EthBlockNumberTag BlockNumber) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag BlockNumber + +deriving via (RlpMerkleLogEntry 'EthGasLimitTag GasLimit) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag GasLimit + +deriving via (RlpMerkleLogEntry 'EthGasUsedTag GasUsed) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag GasUsed + +deriving via (RlpMerkleLogEntry 'EthTimestampTag Timestamp) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Timestamp + +deriving via (RlpMerkleLogEntry 'EthExtraDataTag ExtraData) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ExtraData + +deriving via (RlpMerkleLogEntry 'EthRandaoTag Randao) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Randao + +deriving via (RlpMerkleLogEntry 'EthNonceTag Nonce) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Nonce + +deriving via (RlpMerkleLogEntry 'EthBaseFeePerGasTag BaseFeePerGas) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag BaseFeePerGas + +deriving via (RlpMerkleLogEntry 'EthWithdrawalsRootTag WithdrawalsRoot) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag WithdrawalsRoot + +deriving via (RlpMerkleLogEntry 'EthBlobGasUsedTag BlobGasUsed) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag BlobGasUsed + +deriving via (RlpMerkleLogEntry 'EthExcessBlobGasTag ExcessBlobGas) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ExcessBlobGas + +deriving via (RlpMerkleLogEntry 'EthParentBeaconBlockRootTag ParentBeaconBlockRoot) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ParentBeaconBlockRoot + +-- -------------------------------------------------------------------------- -- +-- MerkleLog Instance + +instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag Header where + + -- /IMPORTANT/ a types must occur at most once in this list + type MerkleLogHeader Header = + '[ ParentHash + , OmmersHash + , Beneficiary + , StateRoot + , TransactionsRoot + , ReceiptsRoot + , Bloom + , Difficulty + , BlockNumber + , GasLimit + , GasUsed + , Timestamp + , ExtraData + , Randao + , Nonce + , BaseFeePerGas + , WithdrawalsRoot + , BlobGasUsed + , ExcessBlobGas + , ParentBeaconBlockRoot + ] + type MerkleLogBody Header = Void + + toLog h = newMerkleLog @ChainwebMerkleHashAlgorithm entries + where + entries + = _hdrParentHash h + :+: _hdrOmmersHash h + :+: _hdrBeneficiary h + :+: _hdrStateRoot h + :+: _hdrTransactionsRoot h + :+: _hdrReceiptsRoot h + :+: _hdrLogsBloom h + :+: _hdrDifficulty h + :+: _hdrNumber h + :+: _hdrGasLimit h + :+: _hdrGasUsed h + :+: _hdrTimestamp h + :+: _hdrExtraData h + :+: _hdrPrevRandao h + :+: _hdrNonce h + :+: _hdrBaseFeePerGas h + :+: _hdrWithdrawalsRoot h + :+: _hdrBlobGasUsed h + :+: _hdrExcessBlobGas h + :+: _hdrParentBeaconBlockRoot h + :+: emptyBody + + fromLog l = hdr + { _hdrHash = computeBlockHash hdr + , _hdrPayloadHash = computeBlockPayloadHash hdr + } + where + hdr = Header + { _hdrParentHash = hParentHash + , _hdrOmmersHash = hOmmersHash + , _hdrBeneficiary = hBeneficiary + , _hdrStateRoot = hStateRoot + , _hdrTransactionsRoot = hTransactionsRoot + , _hdrReceiptsRoot = hReceiptsRoot + , _hdrLogsBloom = hLogsBloom + , _hdrDifficulty = hDifficulty + , _hdrNumber = hNumber + , _hdrGasLimit = hGasLimit + , _hdrGasUsed = hGasUsed + , _hdrTimestamp = hTimestamp + , _hdrExtraData = hExtraData + , _hdrPrevRandao = hPrevRandao + , _hdrNonce = hNonce + , _hdrBaseFeePerGas = hBaseFeePerGas + , _hdrWithdrawalsRoot = hWithdrawalsRoot + , _hdrBlobGasUsed = hBlobGasUsed + , _hdrExcessBlobGas = hExcessBlobGas + , _hdrParentBeaconBlockRoot = hParentBeaconBlockRoot + , _hdrHash = error "Chainweb.PayloadProvider.EVM.Header: _hdrHash" + , _hdrPayloadHash = error "Chainweb.PayloadProvider.EVM.Header: _hdrPayloadHash" + } + ( hParentHash + :+: hOmmersHash + :+: hBeneficiary + :+: hStateRoot + :+: hTransactionsRoot + :+: hReceiptsRoot + :+: hLogsBloom + :+: hDifficulty + :+: hNumber + :+: hGasLimit + :+: hGasUsed + :+: hTimestamp + :+: hExtraData + :+: hPrevRandao + :+: hNonce + :+: hBaseFeePerGas + :+: hWithdrawalsRoot + :+: hBlobGasUsed + :+: hExcessBlobGas + :+: hParentBeaconBlockRoot + :+: _ + ) = _merkleLogEntries l + +-- -------------------------------------------------------------------------- -- +-- Getter + +hdrParentHash :: Getter Header ParentHash +hdrParentHash = to _hdrParentHash + +hdrOmmersHash :: Getter Header OmmersHash +hdrOmmersHash = to _hdrOmmersHash + +hdrBeneficiary :: Getter Header Beneficiary +hdrBeneficiary = to _hdrBeneficiary + +hdrStateRoot :: Getter Header StateRoot +hdrStateRoot = to _hdrStateRoot + +hdrTransactionsRoot :: Getter Header TransactionsRoot +hdrTransactionsRoot = to _hdrTransactionsRoot + +hdrReceiptsRoot :: Getter Header ReceiptsRoot +hdrReceiptsRoot = to _hdrReceiptsRoot + +hdrLogsBloom :: Getter Header Bloom +hdrLogsBloom = to _hdrLogsBloom + +hdrDifficulty :: Getter Header Difficulty +hdrDifficulty = to _hdrDifficulty + +hdrNumber :: Getter Header BlockNumber +hdrNumber = to _hdrNumber + +hdrGasLimit :: Getter Header GasLimit +hdrGasLimit = to _hdrGasLimit + +hdrGasUsed :: Getter Header GasUsed +hdrGasUsed = to _hdrGasUsed + +hdrTimestamp :: Getter Header Timestamp +hdrTimestamp = to _hdrTimestamp + +hdrExtraData :: Getter Header ExtraData +hdrExtraData = to _hdrExtraData + +hdrPrevRandao :: Getter Header Randao +hdrPrevRandao = to _hdrPrevRandao + +hdrNonce :: Getter Header Nonce +hdrNonce = to _hdrNonce + +hdrBaseFeePerGas :: Getter Header BaseFeePerGas +hdrBaseFeePerGas = to _hdrBaseFeePerGas + +hdrWithdrawalsRoot :: Getter Header WithdrawalsRoot +hdrWithdrawalsRoot = to _hdrWithdrawalsRoot + +hdrHeight :: Getter Header Chainweb.BlockHeight +hdrHeight = to _hdrHeight + +hdrHash :: Getter Header BlockHash +hdrHash = to _hdrHash + +hdrPayloadHash :: Getter Header BlockPayloadHash +hdrPayloadHash = to _hdrPayloadHash + From bdb901f25f657e62c7458d8fc726abc907840349 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Dec 2024 16:16:00 -0800 Subject: [PATCH 079/378] add EVM HeaderDB --- chainweb.cabal | 1 + src/Chainweb/PayloadProvider/EVM/HeaderDB.hs | 298 +++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 src/Chainweb/PayloadProvider/EVM/HeaderDB.hs diff --git a/chainweb.cabal b/chainweb.cabal index aedeb1a57e..8e0fa5aec5 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -230,6 +230,7 @@ library , Chainweb.PayloadProvider.EVM.EngineAPI , Chainweb.PayloadProvider.EVM.EthRpcAPI , Chainweb.PayloadProvider.EVM.Header + , Chainweb.PayloadProvider.EVM.HeaderDB , Chainweb.PayloadProvider.EVM.JsonRPC , Chainweb.PayloadProvider.EVM.Utils , Chainweb.PayloadProvider.Initialization diff --git a/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs b/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs new file mode 100644 index 0000000000..85dee72525 --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs @@ -0,0 +1,298 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.HeaderDB.Internal +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- FIXME: this module is very similar to the respective module for the minimal +-- payload provider. Maybe we can unify both? +-- +module Chainweb.PayloadProvider.EVM.HeaderDB +( +-- * EVM Header DB + Configuration(..) +, configuration +, type HeaderDb +, HeaderDb_(..) +, initHeaderDb +, closeHeaderDb +, withHeaderDb + +, type RankedHeaderCas + +-- * Insertion +, insertHeaderDb +, unsafeInsertHeaderDb + +-- * Internal Types +, RankedHeader(..) +, BlockRank(..) +, encodeRankedHeader +, decodeRankedHeader +) where + +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.ChainId +import Chainweb.MerkleUniverse +import Chainweb.PayloadProvider.EVM.Header +import Chainweb.Storage.Table +import Chainweb.Storage.Table.RocksDB +import Chainweb.Utils hiding (Codec) +import Chainweb.Utils.Serialization +import Chainweb.Version +import Control.DeepSeq +import Control.Lens hiding (children) +import Control.Monad +import Control.Monad.Catch +import Data.Aeson +import Data.ByteString qualified as B +import Data.Function +import Data.Hashable +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import Ethereum.RLP +import GHC.Generics +import Numeric.Additive +import Prelude hiding (lookup) +import GHC.Stack +import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) + +-- -------------------------------------------------------------------------- -- +-- Exceptions + +-- type HeaderNotFoundException = HeaderNotFoundException_ ChainwebMerkleHashAlgorithm +-- +-- newtype HeaderNotFoundException_ a = HeaderNotFoundException (BlockPayloadHash_ a) +-- deriving (Show, Eq, Ord, Generic) +-- deriving anyclass (NFData, Hashable) +-- +-- instance Exception HeaderNotFoundException + +-- -------------------------------------------------------------------------- -- +-- | Configuration of the EVM Header DB. +-- + +data Configuration = Configuration + { _configChainwebVersion :: !ChainwebVersion + , _configChainId :: ChainId + , _configGenesis :: !Header + , _configRocksDb :: !RocksDb + } + +configuration + :: HasCallStack + => HasChainwebVersion v + => HasChainId c + => v + -> c + -> RocksDb + -> Header + -> Configuration +configuration v c rdb gen + | not isEvmProvider = + error "Chainweb.PayloadProvider.Evm.HeaderDB.configuration: chain does not use evm provider" + | otherwise = Configuration + { _configChainwebVersion = _chainwebVersion v + , _configChainId = _chainId c + , _configRocksDb = rdb + , _configGenesis = gen + } + where + isEvmProvider = case payloadProviderTypeForChain v c of + EvmProvider{} -> True + _ -> False + +instance HasChainwebVersion Configuration where + _chainwebVersion = _configChainwebVersion + +instance HasChainId Configuration where + _chainId = _configChainId + +-- -------------------------------------------------------------------------- -- +-- Ranked Block Header (only used internally) + +newtype RankedHeader = RankedHeader { _getRankedHeader :: Header } + deriving (Show, Generic) + deriving newtype (Eq, Ord, Hashable, ToJSON, FromJSON, RLP) + +instance IsCasValue RankedHeader where + type CasKeyType RankedHeader = RankedBlockPayloadHash + casKey (RankedHeader bh) + = RankedBlockPayloadHash (view hdrHeight bh) (view hdrPayloadHash bh) + {-# INLINE casKey #-} + +type RankedHeaderCas tbl = Cas tbl RankedHeader + +-- -------------------------------------------------------------------------- -- +-- BlockRank (only used internally) + +newtype BlockRank = BlockRank { _getBlockRank :: BlockHeight } + deriving (Show, Generic) + deriving anyclass (NFData) + deriving newtype + ( Eq, Ord, Hashable, ToJSON, FromJSON + , AdditiveSemigroup, AdditiveAbelianSemigroup, AdditiveMonoid + , Num, Integral, Real, Enum + ) + +-- -------------------------------------------------------------------------- -- +-- Internal + +encodeRankedHeader :: RankedHeader -> B.ByteString +encodeRankedHeader = putRlpByteString . _getRankedHeader +{-# INLINE encodeRankedHeader #-} + +decodeRankedHeader :: MonadThrow m => B.ByteString -> m RankedHeader +decodeRankedHeader = decodeRlpM +{-# INLINE decodeRankedHeader #-} + +-- -------------------------------------------------------------------------- -- +-- Header DB + +type HeaderDb tbl = HeaderDb_ ChainwebMerkleHashAlgorithm tbl + +data HeaderDb_ a tbl = HeaderDb + { _headerDbChainId :: !ChainId + , _headerDbChainwebVersion :: !ChainwebVersion + , _headerDbTable :: !(tbl RankedBlockPayloadHash RankedHeader) + } + +instance HasChainId (HeaderDb_ a tbl) where + _chainId = _headerDbChainId + {-# INLINE _chainId #-} + +instance HasChainwebVersion (HeaderDb_ a tbl) where + _chainwebVersion = _headerDbChainwebVersion + {-# INLINE _chainwebVersion #-} + +instance ReadableTable1 tbl => ReadableTable (HeaderDb_ a tbl) RankedBlockPayloadHash Header where + tableLookup db k = fmap _getRankedHeader <$> tableLookup (_headerDbTable db) k + {-# INLINE tableLookup #-} + +instance Table1 tbl => Table (HeaderDb_ a tbl) RankedBlockPayloadHash Header where + tableInsert db k v = tableInsert (_headerDbTable db) k (RankedHeader v) + tableDelete db k = tableDelete @_ @_ @RankedHeader (_headerDbTable db) k + {-# INLINE tableInsert #-} + {-# INLINE tableDelete #-} + +-- -------------------------------------------------------------------------- -- +-- Initialization + +-- | Initialize a database handle +-- +initHeaderDb :: Configuration -> IO (HeaderDb_ a RocksDbTable) +initHeaderDb config = do + dbAddChecked db (_configGenesis config) + return db + where + cid = _configChainId config + cidNs = T.encodeUtf8 (toText cid) + + headerTable = newTable + (_configRocksDb config) + (Codec encodeRankedHeader decodeRankedHeader) + (Codec (runPutS . encodeRankedBlockPayloadHash) (runGetS decodeRankedBlockPayloadHash)) + ["EvmHeader", cidNs, "header"] + + !db = HeaderDb + { _headerDbChainId = cid + , _headerDbChainwebVersion = _configChainwebVersion config + , _headerDbTable = headerTable + } + +-- | Close a database handle and release all resources +-- +closeHeaderDb :: HeaderDb_ a tbl -> IO () +closeHeaderDb _ = return () + +withHeaderDb + :: RocksDb + -> ChainwebVersion + -> ChainId + -> Header + -> (HeaderDb_ a RocksDbTable -> IO b) + -> IO b +withHeaderDb db v cid genesisHeader = bracket start closeHeaderDb + where + start = initHeaderDb Configuration + { _configChainwebVersion = v + , _configChainId = cid + , _configGenesis = genesisHeader + , _configRocksDb = db + } + +-- -------------------------------------------------------------------------- -- +-- Validated Header + +-- NOTE: the constructor of this type is intentionally NOT exported. Value of +-- this type must be only created via functions from this module. +-- +newtype ValidatedHeader = ValidatedHeader Header + deriving (Show, Eq, Generic) + +_validatedHeader :: ValidatedHeader -> Header +_validatedHeader (ValidatedHeader h) = h +{-# INLINE _validatedHeader #-} + +-- -------------------------------------------------------------------------- -- +-- Insertions + +insertHeaderDb :: Table1 tbl => HeaderDb_ a tbl -> ValidatedHeader -> IO () +insertHeaderDb db = dbAddChecked db . _validatedHeader +{-# INLINE insertHeaderDb #-} + +unsafeInsertHeaderDb :: Table1 tbl => HeaderDb_ a tbl -> Header -> IO () +unsafeInsertHeaderDb = dbAddChecked +{-# INLINE unsafeInsertHeaderDb #-} + +-- -------------------------------------------------------------------------- -- +-- Insert +-- +-- Only functions in this section are allowed to modify values of the Db type. + +-- | A a new entry to the database +-- +-- Updates all indices. +-- +dbAddChecked + :: Table1 tbl -- Cas (tbl RankedBlockPayloadHash Header) Header + => HeaderDb_ a tbl + -> Header + -> IO () +dbAddChecked db e = + unlessM (tableMember db ek) dbAddCheckedInternal + where + ek = RankedBlockPayloadHash (int $ _hdrNumber e) (view hdrPayloadHash e) + + -- Internal helper methods + + -- TODO add validation and check that parent exists (except for height 0). + + -- | Unchecked addition + -- + -- ASSUMES that + -- + -- * Item is not yet in database + -- + dbAddCheckedInternal = casInsert (_headerDbTable db) (RankedHeader e) + From 439ff06608755f6429db4038eee3268656b3ac18 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 14 Jan 2025 00:25:47 -0800 Subject: [PATCH 080/378] ... evm chainresources --- src/Chainweb/Chainweb/ChainResources.hs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 01007e56f9..936a4cbc3d 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -82,6 +82,7 @@ import P2P.TaskQueue import Prelude hiding (log) import Data.HashMap.Strict qualified as HM import Data.Foldable +import Chainweb.PayloadProvider.EVM import Data.Singletons -- -------------------------------------------------------------------------- -- @@ -253,12 +254,28 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr mpConfig SPactProvider -> error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for Pact" + SEvmProvider @n _ -> - error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for EVM" + -- This assumes that the respective execution client is available + -- and answering API requests. + -- It also starts to awaiting and devlivering new payloads if mining + -- is enabled. + withEvmPayloadProvider logger v c rdb mgr evmConfig $ \p -> do + let pdb = view evmPayloadDb p + let queue = view evmPayloadQueue p + p2pRes <- payloadP2pResources @v' @c' @('EvmProvider n) + logger p2pConfig myInfo peerDb pdb queue mgr + inner ProviderResources + { _providerResPayloadProvider = SomePayloadProvider p + , _providerResServiceApi = Nothing + , _providerResP2pApiResources = Just p2pRes + } where provider :: PayloadProviderType provider = payloadProviderTypeForChain v c + evmConfig = defaultEvmProviderConfig + -- -------------------------------------------------------------------------- -- -- Single Chain Resources From 667aa9809393983fb2862ebe026117605c5ca529 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 21 Jan 2025 09:28:00 -0800 Subject: [PATCH 081/378] Payload provider configuraton --- src/Chainweb/Chainweb.hs | 6 +- src/Chainweb/Chainweb/ChainResources.hs | 57 ++--- src/Chainweb/Chainweb/Configuration.hs | 277 +++++++++++++++++++++--- 3 files changed, 281 insertions(+), 59 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 8d7d667520..5ebef79a20 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -342,7 +342,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir (_peerResConfig peerRes) myInfo peerDb - defaultMinimalProviderConfig + (_configPayloadProviders conf) x ) @@ -430,8 +430,8 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir initLogger = setComponent "init" logger providerLogger :: HasChainId p => HasPayloadProviderType p => p -> logger - providerLogger p = addLabel ("provider", toText (_payloadProviderType p)) - $ chainLogger p + providerLogger p = chainLogger p + & addLabel ("provider", toText (_payloadProviderType p)) logg :: LogFunctionText logg = logFunctionText initLogger diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 936a4cbc3d..693efa1296 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -25,7 +25,9 @@ -- Allocate chainweb resources for individual chains -- module Chainweb.Chainweb.ChainResources -( ChainResources(..) +( +-- * Chain Resources + ChainResources(..) , chainResBlockHeaderDb , chainResLogger , chainResPayloadProvider @@ -55,10 +57,12 @@ module Chainweb.Chainweb.ChainResources import Chainweb.BlockHeaderDB import Chainweb.BlockPayloadHash import Chainweb.ChainId -import Chainweb.Chainweb.Configuration (ServiceApiConfig(_serviceApiPayloadBatchLimit)) +import Chainweb.Chainweb.Configuration + -- FIXME this module should not depend on the global configuration import Chainweb.Logger import Chainweb.Pact.Types import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.EVM import Chainweb.PayloadProvider.Minimal import Chainweb.PayloadProvider.P2P.RestAPI import Chainweb.PayloadProvider.P2P.RestAPI.Server @@ -69,8 +73,11 @@ import Chainweb.Storage.Table.RocksDB import Chainweb.Utils import Chainweb.Version import Control.Lens hiding ((.=), (<.>)) +import Data.Foldable +import Data.HashMap.Strict qualified as HM import Data.Maybe import Data.PQueue (PQueue) +import Data.Singletons import Data.Text qualified as T import Network.HTTP.Client qualified as HTTP import P2P.Node @@ -80,10 +87,6 @@ import P2P.Peer (PeerInfo) import P2P.Session import P2P.TaskQueue import Prelude hiding (log) -import Data.HashMap.Strict qualified as HM -import Data.Foldable -import Chainweb.PayloadProvider.EVM -import Data.Singletons -- -------------------------------------------------------------------------- -- -- Payload P2P Network Resources @@ -184,7 +187,6 @@ payloadServiceApiResources config pdb = PayloadServiceApiResources where batchLimit = int $ _serviceApiPayloadBatchLimit config - -- -------------------------------------------------------------------------- -- -- Payload Provider Resources @@ -206,12 +208,6 @@ instance HasChainId ProviderResources where _chainId = _chainId . _providerResPayloadProvider {-# INLINE _chainId #-} - -- FIXME - -- initialize payload store - -- payloadStore <- newWebPayloadStore mgr pact payloadDb (logFunction logger) - -- Where is this done? The queue is used by the P2p Session and - -- the Payload Provider. - withPayloadProviderResources :: Logger logger => HasChainwebVersion v @@ -224,10 +220,10 @@ withPayloadProviderResources -> PeerDb -> RocksDb -> HTTP.Manager - -> MinimalProviderConfig + -> PayloadProviderConfig -> (ProviderResources -> IO a) -> IO a -withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr mpConfig inner = do +withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs inner = do SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal v SomeChainIdT @c' _ <- return $ someChainIdVal c withSomeSing provider $ \case @@ -241,7 +237,8 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr mpConfig -- It would allow the server to be integrated more closely with the -- provider. - p <- newMinimalPayloadProvider logger v c rdb mgr mpConfig + let config = _payloadProviderConfigMinimal configs + p <- newMinimalPayloadProvider logger v c rdb mgr config let pdb = view minimalPayloadDb p let queue = view minimalPayloadQueue p p2pRes <- payloadP2pResources @v' @c' @'MinimalProvider @@ -252,15 +249,22 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr mpConfig , _providerResP2pApiResources = Just p2pRes } - SPactProvider -> + SPactProvider -> do + _config <- case HM.lookup cid (_payloadProviderConfigPact configs) of + Just x -> return x + Nothing -> error $ "Chainweb.Chainweb.ChainResources.withPayloadProviderResources: missing payload provider configuration for chain " <> sshow cid error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for Pact" - SEvmProvider @n _ -> + SEvmProvider @n _ -> do + config <- case HM.lookup cid (_payloadProviderConfigEvm configs) of + Just x -> return x + Nothing -> error $ "Chainweb.Chainweb.ChainResources.withPayloadProviderResources: missing payload provider configuration for chain " <> sshow cid + -- This assumes that the respective execution client is available -- and answering API requests. -- It also starts to awaiting and devlivering new payloads if mining -- is enabled. - withEvmPayloadProvider logger v c rdb mgr evmConfig $ \p -> do + withEvmPayloadProvider logger v c rdb mgr config $ \p -> do let pdb = view evmPayloadDb p let queue = view evmPayloadQueue p p2pRes <- payloadP2pResources @v' @c' @('EvmProvider n) @@ -271,11 +275,10 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr mpConfig , _providerResP2pApiResources = Just p2pRes } where + cid = _chainId c provider :: PayloadProviderType provider = payloadProviderTypeForChain v c - evmConfig = defaultEvmProviderConfig - -- -------------------------------------------------------------------------- -- -- Single Chain Resources @@ -322,11 +325,10 @@ withChainResources -> P2pConfiguration -> PeerInfo -> PeerDb - -> MinimalProviderConfig - -- ^ FIXME create a a type that bundles different provider configs + -> PayloadProviderConfig -> (ChainResources logger -> IO a) -> IO a -withChainResources logger v c rdb mgr _pactDbDir _pConf p2pConf myInfo peerDb mConf inner = +withChainResources logger v c rdb mgr _pactDbDir _pConf p2pConf myInfo peerDb configs inner = -- This uses the the CutNetwork for fetching block headers. withBlockHeaderDb rdb (_chainwebVersion v) (_chainId c) $ \cdb -> do @@ -334,13 +336,18 @@ withChainResources logger v c rdb mgr _pactDbDir _pConf p2pConf myInfo peerDb mC -- Payload Providers are using per chain payload networks for fetching -- block headers. withPayloadProviderResources - logger v c p2pConf myInfo peerDb rdb mgr mConf $ \provider -> do + providerLogger v c p2pConf myInfo peerDb rdb mgr configs $ \provider -> do inner ChainResources { _chainResBlockHeaderDb = cdb , _chainResPayloadProvider = provider , _chainResLogger = logger } + where + providerType = payloadProviderTypeForChain v c + providerLogger = logger + & setComponent "payload-provider" + & addLabel ("provider", toText providerType) -- | Return P2P Payload Servers for all chains -- diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 0f27adee78..39cf589ec2 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} @@ -20,8 +21,22 @@ -- module Chainweb.Chainweb.Configuration ( + +-- * Placeholder for Pact Provider Config + PactProviderConfig(..) +, defaultPactProviderConfig + +-- * Payload Provider Config +, PayloadProviderConfig(..) +, payloadProviderConfigMinimal +, payloadProviderConfigPact +, payloadProviderConfigEvm +, defaultPayloadProviderConfig +, minimalPayloadProviderConfig +, validatePayloadProviderConfig + -- * Throttling Configuration - ThrottlingConfig(..) +, ThrottlingConfig(..) , throttlingRate , throttlingPeerRate , throttlingMempoolRate @@ -77,53 +92,231 @@ module Chainweb.Chainweb.Configuration ) where +import Chainweb.BlockHeight +import Chainweb.Difficulty +import Chainweb.HostAddress +import Chainweb.Mempool.Mempool qualified as Mempool +import Chainweb.Mempool.P2pConfig +import Chainweb.Miner.Config +import Chainweb.Pact.Backend.DbCache (DbCacheLimitBytes) +import Chainweb.Pact.Types (RewindLimit(..)) +import Chainweb.Pact.Types (defaultReorgLimit, defaultModuleCacheLimit, defaultPreInsertCheckTimeout) +import Chainweb.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) +import Chainweb.PayloadProvider.EVM (EvmProviderConfig, defaultEvmProviderConfig, pEvmProviderConfig) +import Chainweb.PayloadProvider.Minimal (MinimalProviderConfig, defaultMinimalProviderConfig, pMinimalProviderConfig) +import Chainweb.Time hiding (second) +import Chainweb.Utils +import Chainweb.Version +import Chainweb.Version.Development +import Chainweb.Version.Mainnet +import Chainweb.Version.RecapDevelopment +import Chainweb.Version.Registry import Configuration.Utils hiding (Error, Lens', disabled) - import Control.Lens hiding ((.=), (<.>)) import Control.Monad import Control.Monad.Catch (MonadThrow, throwM) import Control.Monad.Except import Control.Monad.Writer - +import Data.Aeson.Key qualified as K +import Data.Aeson.KeyMap qualified as KM +import Data.Bifunctor import Data.Foldable -import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS +import Data.List qualified as L import Data.Maybe -import qualified Data.Text as T - +import Data.Text qualified as T +import Data.Text.Read qualified as T import GHC.Generics hiding (from) - import Network.Wai.Handler.Warp hiding (Port) - import Numeric.Natural (Natural) +import P2P.Node.Configuration +import Pact.JSON.Encode qualified as J +import Prelude hiding (log) +import System.Directory -import qualified Pact.JSON.Encode as J +-- -------------------------------------------------------------------------- -- +-- Payload Provider Configuration -import Prelude hiding (log) +-- | Placeholder for PactProviderConfig +-- +data PactProviderConfig = PactProviderConfig + deriving (Show, Eq, Generic) -import System.Directory +instance ToJSON PactProviderConfig where + toJSON PactProviderConfig = object [] --- internal modules +instance FromJSON PactProviderConfig where + parseJSON = withObject "PactProviderConfig" $ \_ -> pure PactProviderConfig -import Chainweb.BlockHeight -import Chainweb.Difficulty -import Chainweb.HostAddress -import qualified Chainweb.Mempool.Mempool as Mempool -import Chainweb.Mempool.P2pConfig -import Chainweb.Miner.Config -import Chainweb.Pact.Types (defaultReorgLimit, defaultModuleCacheLimit, defaultPreInsertCheckTimeout) -import Chainweb.Pact.Types (RewindLimit(..)) -import Chainweb.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) -import Chainweb.Utils -import Chainweb.Version -import Chainweb.Version.Development -import Chainweb.Version.RecapDevelopment -import Chainweb.Version.Mainnet -import Chainweb.Version.Registry -import Chainweb.Time +instance FromJSON (PactProviderConfig -> PactProviderConfig) where + parseJSON = withObject "PactProviderConfig" $ \_ -> pure id -import P2P.Node.Configuration -import Chainweb.Pact.Backend.DbCache (DbCacheLimitBytes) +defaultPactProviderConfig :: PactProviderConfig +defaultPactProviderConfig = PactProviderConfig + +-- | Payload Provider Configurations +-- +-- There is only a single Default Minimal Payload Provider Configuration. +-- +data PayloadProviderConfig = PayloadProviderConfig + { _payloadProviderConfigMinimal :: !MinimalProviderConfig + , _payloadProviderConfigPact :: !(HM.HashMap ChainId PactProviderConfig) + , _payloadProviderConfigEvm :: !(HM.HashMap ChainId EvmProviderConfig) + } + deriving (Show, Eq, Generic) + +makeLenses ''PayloadProviderConfig + +-- | This has to depend on the Chainweb Version +-- +defaultPayloadProviderConfig :: ChainwebVersion -> PayloadProviderConfig +defaultPayloadProviderConfig v = PayloadProviderConfig + { _payloadProviderConfigMinimal = defaultMinimalProviderConfig + , _payloadProviderConfigPact = pacts + , _payloadProviderConfigEvm = evms + } + where + (pacts, evms) = go (toList $ chainIds v) + go :: [ChainId] -> (HM.HashMap ChainId PactProviderConfig, HM.HashMap ChainId EvmProviderConfig) + go [] = (mempty, mempty) + go (h:t) = case payloadProviderTypeForChain v h of + MinimalProvider -> go t + PactProvider -> first (HM.insert h defaultPactProviderConfig) $ go t + EvmProvider _ -> second (HM.insert h defaultEvmProviderConfig) $ go t + + +minimalPayloadProviderConfig :: MinimalProviderConfig -> PayloadProviderConfig +minimalPayloadProviderConfig m = PayloadProviderConfig + { _payloadProviderConfigMinimal = m + , _payloadProviderConfigPact = mempty + , _payloadProviderConfigEvm = mempty + } + +validatePayloadProviderConfig :: ChainwebVersion -> ConfigValidation PayloadProviderConfig [] +validatePayloadProviderConfig v conf = do + go (toList $ chainIds v) + where + go [] = return () + go (c:t) = go t <* case payloadProviderTypeForChain v c of + PactProvider -> unless (HM.member c (_payloadProviderConfigPact conf)) $ + if (HM.member c (_payloadProviderConfigEvm conf)) + then throwError $ mconcat $ + [ "Wrong payload provdider type configuration for chain " <> sshow c + , ". Expected Pact but found EVM" + ] <> msg + else throwError $ mconcat + $ "Missing Pact payload provider configuration for chain " <> sshow c + : msg + EvmProvider _ -> unless (HM.member c (_payloadProviderConfigEvm conf)) $ + if (HM.member c (_payloadProviderConfigPact conf)) + then throwError $ mconcat $ + [ "Wrong payload provdider type configuration for chain " <> sshow c + , ". Expected EVM but found Pact" + ] <> msg + else throwError $ mconcat + $ "Missing EVM payload provider configuration for chain " <> sshow c + : msg + MinimalProvider -> return () + msg = + [ ". In order to use chainweb-node with chainweb version " <> sshow v + , " you must provide a configuraton for all payload providers except" + , " the chains that use the default payload provider." + , " The following is the default payload provider configuration for" + , " chainweb version " <> sshow v + , ": " <> encodeToText (defaultPayloadProviderConfig v) + ] + +instance ToJSON PayloadProviderConfig where + toJSON o = object + $ ("default" .= _payloadProviderConfigMinimal o) + : others + where + pacts = + [ key c .= tag "pact" v + | (c, v) <- HM.toList (_payloadProviderConfigPact o) + ] + evms = + [ key c .= tag "evm" v + | (c, v) <- HM.toList (_payloadProviderConfigEvm o) + ] + others = L.sort $ pacts <> evms + + tag :: ToJSON v => T.Text -> v -> Value + tag t v = case toJSON v of + Object l -> Object $ KM.insert "type" (toJSON t) l + x -> x + + key :: ChainId -> Key + key cid = K.fromText $ "chain-" <> toText cid + +-- | NOTE: This creates unsafe ChainIds. The result should only be used after +-- validation against the chainweb version. +-- +instance FromJSON PayloadProviderConfig where + parseJSON = withObject "PayloadProviderConfig" $ \o -> do + minimal <- o .: "default" + ifoldlM go (minimalPayloadProviderConfig minimal) (KM.toMapText o) + where + parseKey k = case T.stripPrefix "chain-" k of + Nothing -> fail $ "failed to parse chain key: " <> sshow k + Just x -> case T.decimal x of + Left e -> fail $ "failed to parse chain value: " <> e + Right (n, "") -> return $ unsafeChainId n + Right _ -> fail $ "trailng garabage when parsing chain value: " <> sshow x + + go "default" c = const (return c) + go k c = withObject "ProviderConfig for Chain" $ \o -> do + cid <- parseKey k + (o .: "type") >>= \case + "pact" -> do + x <- parseJSON (Object o) + return $ c & payloadProviderConfigPact . at cid .~ Just x + "evm" -> do + x <- parseJSON (Object o) + return $ c & payloadProviderConfigEvm . at cid .~ Just x + (x :: T.Text) -> fail $ "unknown payload provider type: " <> sshow x + +-- | FIXME: test this instance. +-- +instance FromJSON (PayloadProviderConfig -> PayloadProviderConfig) where + parseJSON = withObject "PayloadProviderConfig" $ \o -> do + updateMinimal <- payloadProviderConfigMinimal %.: "default" % o + ifoldlM go updateMinimal (KM.toMapText o) + where + parseKey k = case T.stripPrefix "chain-" k of + Nothing -> fail $ "failed to parse chain key: " <> sshow k + Just x -> case T.decimal x of + Left e -> fail $ "failed to parse chain value: " <> e + Right (n, "") -> return $ unsafeChainId n + Right _ -> fail $ "trailng garabage when parsing chain value: " <> sshow x + + go "default" c = const (return c) + go k c = withObject "ProviderConfig for Chain" $ \o -> do + cid <- parseKey k + (o .: "type") >>= \case + "pact" -> do + x <- parseJSON (Object o) + return $ (payloadProviderConfigPact . at cid %~ x) . c + "evm" -> do + x <- parseJSON (Object o) + return $ (payloadProviderConfigEvm . at cid %~ x) . c + (x :: T.Text) -> fail $ "unknown payload provider type: " <> sshow x + +pPayloadProviderConfig :: MParser PayloadProviderConfig +pPayloadProviderConfig = id + <$< payloadProviderConfigMinimal %:: pMinimalProviderConfig + <*< pevm + where + cids = [ unsafeChainId i | i <- [0..20]] + -- FIXME this is is really ugly and also clutters the help message. + -- At least use the largest know graph. Ideally, we would use the + -- chainweb version -- but we don't know it yet. + -- + -- FIXME: at the very least we should hide the all but the first options + -- from the help message! + pevm = foldr (\a b -> a . b) id <$> traverse go cids + go cid = (payloadProviderConfigEvm . ix cid %~) <$> (pEvmProviderConfig cid) -- -------------------------------------------------------------------------- -- -- Throttling Configuration @@ -407,6 +600,7 @@ data ChainwebConfiguration = ChainwebConfiguration , _configModuleCacheLimit :: !DbCacheLimitBytes -- ^ module cache size limit in bytes , _configEnableLocalTimeout :: !Bool + , _configPayloadProviders :: PayloadProviderConfig } deriving (Show, Eq, Generic) makeLenses ''ChainwebConfiguration @@ -422,6 +616,7 @@ validateChainwebConfiguration c = do unless (c ^. chainwebVersion . versionDefaults . disablePeerValidation) $ validateP2pConfiguration (_configP2p c) validateChainwebVersion (_configChainwebVersion c) + validatePayloadProviderConfig (_configChainwebVersion c) (_configPayloadProviders c) validateChainwebVersion :: ConfigValidation ChainwebVersion [] validateChainwebVersion v = do @@ -467,6 +662,21 @@ defaultChainwebConfiguration v = ChainwebConfiguration , _configBackup = defaultBackupConfig , _configModuleCacheLimit = defaultModuleCacheLimit , _configEnableLocalTimeout = False + , _configPayloadProviders = minimalPayloadProviderConfig defaultMinimalProviderConfig + -- Similar to bootstrap-peers, there is no default configuration that + -- is valid accross chainweb versions. + -- + -- We have to options: + -- 1. require that users explicitely configure all payload providers + -- that they want to use (FIXME: implement support for opting out of + -- payload providers for some chains, and force miners to keep them + -- all) + -- 2. use the default value for mainnet. That configuration will simply + -- fail validation on other networks. + -- + -- However, even on mainnet users will most likely have to provide an + -- explicit configuration once we have external providers on mainet. At + -- that point it probably makes sense to use the first option. } instance ToJSON ChainwebConfiguration where @@ -494,6 +704,7 @@ instance ToJSON ChainwebConfiguration where , "backup" .= _configBackup o , "moduleCacheLimit" .= _configModuleCacheLimit o , "enableLocalTimeout" .= _configEnableLocalTimeout o + , "payloadProviders" .= _configPayloadProviders o ] instance FromJSON ChainwebConfiguration where @@ -525,6 +736,7 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where <*< configBackup %.: "backup" % o <*< configModuleCacheLimit ..: "moduleCacheLimit" % o <*< configEnableLocalTimeout ..: "enableLocalTimeout" % o + <*< configPayloadProviders %.: "payloadProviders" % o pChainwebConfiguration :: MParser ChainwebConfiguration pChainwebConfiguration = id @@ -586,6 +798,9 @@ pChainwebConfiguration = id % long "enable-local-timeout" <> help "Enable timeout support on /local endpoints" + -- FIXME support payload providers + <*< configPayloadProviders %:: pPayloadProviderConfig + parseVersion :: MParser ChainwebVersion parseVersion = constructVersion <$> optional From 8f4972b77a90f44bf7a8a39b03c0322838fbeb25 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 14 Jan 2025 09:09:06 -0800 Subject: [PATCH 082/378] Use EVM on chain 0 in Development --- chainweb.cabal | 3 + src/Chainweb/Chainweb/Configuration.hs | 3 +- src/Chainweb/Pact/PactService.hs | 3 +- src/Chainweb/Payload/PayloadStore.hs | 11 +- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 105 +++++++++++++++++++ src/Chainweb/PayloadProvider/Pact/Genesis.hs | 78 ++++++++++++++ src/Chainweb/Version.hs | 20 ++-- src/Chainweb/Version/Development.hs | 29 +++-- src/Chainweb/Version/EvmDevelopment.hs | 98 +++++++++++++++++ src/Chainweb/Version/Mainnet.hs | 46 ++++---- src/Chainweb/Version/RecapDevelopment.hs | 28 +++-- src/Chainweb/Version/Registry.hs | 5 +- src/Chainweb/Version/Testnet04.hs | 26 ++++- src/Chainweb/Version/Testnet05.hs | 28 +++-- 14 files changed, 421 insertions(+), 62 deletions(-) create mode 100644 src/Chainweb/PayloadProvider/EVM/Genesis.hs create mode 100644 src/Chainweb/PayloadProvider/Pact/Genesis.hs create mode 100644 src/Chainweb/Version/EvmDevelopment.hs diff --git a/chainweb.cabal b/chainweb.cabal index 8e0fa5aec5..a5b4ac3cb5 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -229,6 +229,7 @@ library , Chainweb.PayloadProvider.EVM , Chainweb.PayloadProvider.EVM.EngineAPI , Chainweb.PayloadProvider.EVM.EthRpcAPI + , Chainweb.PayloadProvider.EVM.Genesis , Chainweb.PayloadProvider.EVM.Header , Chainweb.PayloadProvider.EVM.HeaderDB , Chainweb.PayloadProvider.EVM.JsonRPC @@ -237,6 +238,7 @@ library , Chainweb.PayloadProvider.Minimal , Chainweb.PayloadProvider.Minimal.Payload , Chainweb.PayloadProvider.Minimal.PayloadDB + , Chainweb.PayloadProvider.Pact.Genesis , Chainweb.PayloadProvider.P2P , Chainweb.PayloadProvider.P2P.RestAPI , Chainweb.PayloadProvider.P2P.RestAPI.Client @@ -280,6 +282,7 @@ library , Chainweb.VerifierPlugin.Hyperlane.Utils , Chainweb.Version , Chainweb.Version.Development + , Chainweb.Version.EvmDevelopment , Chainweb.Version.Guards , Chainweb.Version.Mainnet , Chainweb.Version.RecapDevelopment diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 39cf589ec2..01b7ef31bb 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -108,6 +108,7 @@ import Chainweb.Time hiding (second) import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Development +import Chainweb.Version.EvmDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.RecapDevelopment import Chainweb.Version.Registry @@ -627,7 +628,7 @@ validateChainwebVersion v = do , sshow (_versionName v) ] where - isDevelopment = _versionCode v `elem` [_versionCode dv | dv <- [recapDevnet, devnet]] + isDevelopment = _versionCode v `elem` [_versionCode dv | dv <- [recapDevnet, devnet, evmDevnet]] validateBackupConfig :: ConfigValidation BackupConfig [] validateBackupConfig c = diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 2b5834cc39..15ab1fda94 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -145,6 +145,7 @@ import Chainweb.Pact.PactService.Checkpointer (SomeBlockM(..)) import qualified Pact.Core.StableEncoding as Pact5 import Control.Monad.Cont (evalContT) import qualified Data.List.NonEmpty as NonEmpty +import Chainweb.PayloadProvider.Pact.Genesis (genesisPayload) runPactService @@ -252,7 +253,7 @@ initialPayloadState initialPayloadState v cid | v ^. versionCheats . disablePact = pure () | otherwise = initializeCoinContract v cid $ - v ^?! versionGenesis . genesisBlockPayload . atChain cid + genesisPayload v ^?! atChain cid initializeCoinContract :: forall tbl logger. (CanReadablePayloadCas tbl, Logger logger) diff --git a/src/Chainweb/Payload/PayloadStore.hs b/src/Chainweb/Payload/PayloadStore.hs index 3ff57ca078..2e1c59a348 100644 --- a/src/Chainweb/Payload/PayloadStore.hs +++ b/src/Chainweb/Payload/PayloadStore.hs @@ -95,6 +95,7 @@ import Chainweb.Version import Chainweb.Storage.Table import Chainweb.BlockHeight +import Chainweb.PayloadProvider.Pact.Genesis -- -------------------------------------------------------------------------- -- -- Exceptions @@ -331,8 +332,14 @@ initializePayloadDb -> IO () initializePayloadDb v db = traverse_ initForChain $ chainIds v where - initForChain cid = - addNewPayload db (genesisBlockHeight v cid) $ v ^?! versionGenesis . genesisBlockPayload . atChain cid + initForChain cid + | provider /= PactProvider = + error "Chainweb.Payload.PayloadStore.initializePayloadDb: this module must only be used by Pact" + | otherwise = + addNewPayload db (genesisBlockHeight v cid) $ genesisPayload v ^?! atChain cid + where + provider = payloadProviderTypeForChain v cid + -- -------------------------------------------------------------------------- -- -- Insert new Payload diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs new file mode 100644 index 0000000000..060103d421 --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -0,0 +1,105 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedStrings #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.Genesis +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.EVM.Genesis +( genesisBlocks +) where +import Chainweb.Version +import Chainweb.PayloadProvider.EVM.Header +import Chainweb.Version.EvmDevelopment +import Chainweb.Utils +import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) + +-- -------------------------------------------------------------------------- -- +-- Genesis Blocks +-- +-- EVM Development headers for chain-0 and chain-1. +-- +-- NOTE that the header value does not depend on the chainId/netId. +-- +-- { +-- "hash": "0x557c3fc4b87c8bce45002e8fe4db07546324caec6459cb67c64389110c9fd942", +-- "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", +-- "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", +-- "miner": "0x0000000000000000000000000000000000000000", +-- "stateRoot": "0x7d2b86c493412a586f03e4aee1ab94837c6a8fff21ff7c6045dc1330d5b0d49e", +-- "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", +-- "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", +-- "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", +-- "difficulty": "0x0", +-- "number": "0x0", +-- "gasLimit": "0x1c9c380", +-- "gasUsed": "0x0", +-- "timestamp": "0x6490fdd2", +-- "totalDifficulty": "0x0", +-- "extraData": "0x", +-- "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", +-- "nonce": "0x0000000000000000", +-- "baseFeePerGas": "0x3b9aca00", +-- "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", +-- "blobGasUsed": "0x0", +-- "excessBlobGas": "0x0", +-- "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", +-- "uncles": [], +-- "transactions": [], +-- "size": "0x247", +-- "withdrawals": [] +-- } +-- +-- block payload hash: -EffZgInrtrokWVn5iqO2fuTbtG2-noDdhXr0pHQA4E + +-- | Genesis Headers for the EVM Payload Provider +-- +-- NOTE: These headers must match the block payload hashes in the respective +-- Chainweb.Version.* modules. +-- +-- How to get the headers: +-- +-- 1. Run the EVM with the chain specification for the network: +-- +-- @ +-- docker compose up -d chainweb-evm-chain0 +-- @ +-- +-- 2. Query the EVM genesis header with `eth_getBlockByNumber` at height 0 +-- +-- @ +-- docker compose run --rm -ti curl -sL http://chainweb-evm-chain0:8545 -XPOST -H 'content-type:application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x0", false]}' | +-- jq -rc '.result' | +-- sed -e 's/"/\\"/g' +-- @ +-- +-- 3. In ghci bind the output value to constant @a@ and +-- +-- @ +-- hdr <- decodeOrThrow @IO @E.Header a +-- -- print block payload hash +-- encodeToText $ E._hdrPayloadHash hdr +-- -- encode header to base64 +-- encodeB64UrlNoPaddingText $ putRlpByteString hdr +-- @ +-- +genesisBlocks + :: HasChainwebVersion v + => HasChainId c + => v + -> c + -> Header +genesisBlocks v c = go (_chainwebVersion v) (_chainId c) + where + -- Ethereum NetworkID 1789 + go EvmDevelopment (ChainId 0) = f "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoH0rhsSTQSpYbwPkruGrlIN8ao__If98YEXcEzDVsNSeoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 1) = f "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoH0rhsSTQSpYbwPkruGrlIN8ao__If98YEXcEzDVsNSeoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go _ _ = error "requested genesis block for unsupported chain" + + f t = case decodeB64UrlNoPaddingText t >>= decodeRlpM of + Left e -> error $ "Chainweb.PayloadProvider.EVM.genesisBlocks: invalid genesis block: " <> sshow e + Right x -> x + diff --git a/src/Chainweb/PayloadProvider/Pact/Genesis.hs b/src/Chainweb/PayloadProvider/Pact/Genesis.hs new file mode 100644 index 0000000000..2ec2de7b65 --- /dev/null +++ b/src/Chainweb/PayloadProvider/Pact/Genesis.hs @@ -0,0 +1,78 @@ +{-# LANGUAGE ImportQualifiedPost #-} + +-- | +-- Module: Chainweb.PayloadProvider.Pact.Genesis +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.Pact.Genesis +( genesisPayload +) where + +import Chainweb.Version +import Chainweb.Version.Mainnet +import Chainweb.Version.Testnet04 +import Chainweb.Version.Testnet05 +import Chainweb.Version.Development +import Chainweb.Version.RecapDevelopment +import Chainweb.Payload +import Data.HashMap.Strict qualified as HM + +import qualified Chainweb.BlockHeader.Genesis.Mainnet0Payload as MN0 +import qualified Chainweb.BlockHeader.Genesis.Mainnet1Payload as MN1 +import qualified Chainweb.BlockHeader.Genesis.Mainnet2Payload as MN2 +import qualified Chainweb.BlockHeader.Genesis.Mainnet3Payload as MN3 +import qualified Chainweb.BlockHeader.Genesis.Mainnet4Payload as MN4 +import qualified Chainweb.BlockHeader.Genesis.Mainnet5Payload as MN5 +import qualified Chainweb.BlockHeader.Genesis.Mainnet6Payload as MN6 +import qualified Chainweb.BlockHeader.Genesis.Mainnet7Payload as MN7 +import qualified Chainweb.BlockHeader.Genesis.Mainnet8Payload as MN8 +import qualified Chainweb.BlockHeader.Genesis.Mainnet9Payload as MN9 +import qualified Chainweb.BlockHeader.Genesis.Mainnet10to19Payload as MNKAD +import qualified Chainweb.BlockHeader.Genesis.Development0Payload as DN0 +import qualified Chainweb.BlockHeader.Genesis.Development1to19Payload as DNN +import qualified Chainweb.BlockHeader.Genesis.Testnet040Payload as T04N0 +import qualified Chainweb.BlockHeader.Genesis.Testnet041to19Payload as T04NN +import qualified Chainweb.BlockHeader.Genesis.Testnet050Payload as T05N0 +import qualified Chainweb.BlockHeader.Genesis.Testnet051to19Payload as T05NN +import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment0Payload as RDN0 +import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment1to9Payload as RDNN +import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload as RDNKAD +import Chainweb.Utils + +genesisPayload :: ChainwebVersion -> ChainMap PayloadWithOutputs +genesisPayload Mainnet01 = OnChains $ HM.fromList $ concat + [ [ (unsafeChainId 0, MN0.payloadBlock) + , (unsafeChainId 1, MN1.payloadBlock) + , (unsafeChainId 2, MN2.payloadBlock) + , (unsafeChainId 3, MN3.payloadBlock) + , (unsafeChainId 4, MN4.payloadBlock) + , (unsafeChainId 5, MN5.payloadBlock) + , (unsafeChainId 6, MN6.payloadBlock) + , (unsafeChainId 7, MN7.payloadBlock) + , (unsafeChainId 8, MN8.payloadBlock) + , (unsafeChainId 9, MN9.payloadBlock) + ] + , [(unsafeChainId i, MNKAD.payloadBlock) | i <- [10..19]] + ] +genesisPayload Testnet04 = OnChains $ HM.fromList $ concat + [ [(unsafeChainId 0, T04N0.payloadBlock)] + , [(unsafeChainId i, T04NN.payloadBlock) | i <- [1..19]] + ] +genesisPayload Testnet05 = OnChains $ HM.fromList $ concat + [ [(unsafeChainId 0, T05N0.payloadBlock)] + , [(unsafeChainId i, T05NN.payloadBlock) | i <- [1..19]] + ] +genesisPayload Development = OnChains $ HM.fromList $ concat + [ [(unsafeChainId 0, DN0.payloadBlock)] + , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] + ] +genesisPayload RecapDevelopment = OnChains $ HM.fromList $ concat + [ [(unsafeChainId 0, RDN0.payloadBlock)] + , [(unsafeChainId i, RDNN.payloadBlock) | i <- [1..9]] + , [(unsafeChainId i, RDNKAD.payloadBlock) | i <- [10..19]] + ] +genesisPayload v = error $ + "Chainweb.PayloadProvider.Pact.Genesis.genesisPayload: unsupported chainweb version: " <> sshow v diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 42a5bd81e7..98349f9b2c 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -566,7 +566,10 @@ data ChainwebVersion , _versionBootstraps :: [PeerInfo] -- ^ The locations of the bootstrap peers. , _versionGenesis :: VersionGenesis - -- ^ The information used to construct the genesis blocks. + -- ^ The information used to construct the genesis Headers. This + -- includes the BlockPayloadHashes. The gensis payloads themself are + -- constructed in the respectice payload providers. Chainweb.Version + -- must not depend on payload provider specific modules. , _versionCheats :: VersionCheats -- ^ Whether to disable any core functionality. , _versionDefaults :: VersionDefaults @@ -634,9 +637,17 @@ data VersionCheats = VersionCheats data VersionGenesis = VersionGenesis { _genesisBlockTarget :: ChainMap HashTarget - , _genesisBlockPayload :: ChainMap PayloadWithOutputs - -- ^ FIXME: This is currently only for the Pact Payload Provider + , _genesisBlockPayload :: ChainMap BlockPayloadHash , _genesisTime :: ChainMap BlockCreationTime + -- ^ the value must match the time value in the respective genesis + -- blocks of the payload provider (assuming that the payload provider + -- has a notion of time). In theory this shoudl be /after/ the time in + -- the genesis payloads, because payload times are the times of the + -- parent header. Using the same time, in theory, creates a problem for + -- the payload for the next block after the genesis block, because it + -- would use the same time as the genesis blocks. However, Pact does not + -- care and the EVM payload provider adjusts the payload time forward , + -- which means that it does not match exactly the parent creation time. } deriving stock (Generic, Eq) deriving anyclass NFData @@ -650,14 +661,11 @@ makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionCheats makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionDefaults makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionQuirks --- | FIXME: This is currently only for the Pact Payload Provider --- genesisBlockPayloadHash :: ChainwebVersion -> ChainId -> BlockPayloadHash genesisBlockPayloadHash v cid = v ^?! versionGenesis . genesisBlockPayload . atChain cid - . to _payloadWithOutputsPayloadHash -------------------------------------------------------------------------- -- -- Type level ChainwebVersion diff --git a/src/Chainweb/Version/Development.hs b/src/Chainweb/Version/Development.hs index 1cdd598193..1f9413ed36 100644 --- a/src/Chainweb/Version/Development.hs +++ b/src/Chainweb/Version/Development.hs @@ -20,9 +20,6 @@ import Chainweb.Version import Pact.Types.Verifier -import qualified Chainweb.BlockHeader.Genesis.Development0Payload as DN0 -import qualified Chainweb.BlockHeader.Genesis.Development1to19Payload as DNN - pattern Development :: ChainwebVersion pattern Development <- ((== devnet) -> True) where Development = devnet @@ -42,9 +39,27 @@ devnet = ChainwebVersion , _versionGenesis = VersionGenesis { _genesisBlockTarget = AllChains $ HashTarget (maxBound `div` 100_000) , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] - , _genesisBlockPayload = onChains $ concat - [ [(unsafeChainId 0, DN0.payloadBlock)] - , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] + , _genesisBlockPayload = onChains + [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") + , (unsafeChainId 1, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 2, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 3, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 4, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 5, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 6, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 7, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 8, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 9, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 10, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 11, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 12, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 13, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 14, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 15, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 16, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 17, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") ] } @@ -64,5 +79,5 @@ devnet = ChainwebVersion (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) , _versionQuirks = noQuirks , _versionServiceDate = Nothing - , _versionPayloadProviderTypes = AllChains MinimalProvider + , _versionPayloadProviderTypes = AllChains PactProvider } diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs new file mode 100644 index 0000000000..c567c3aad4 --- /dev/null +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -0,0 +1,98 @@ +{-# language LambdaCase #-} +{-# language NumericUnderscores #-} +{-# language OverloadedStrings #-} +{-# language PatternSynonyms #-} +{-# language QuasiQuotes #-} +{-# language ViewPatterns #-} + +module Chainweb.Version.EvmDevelopment +( evmDevnet +, pattern EvmDevelopment +) where + +import qualified Data.Set as Set + +import Chainweb.BlockCreationTime +import Chainweb.ChainId +import Chainweb.Difficulty +import Chainweb.Graph +import Chainweb.Time +import Chainweb.Utils +import Chainweb.Utils.Rule +import Chainweb.Version + +import Pact.Types.Verifier + +pattern EvmDevelopment :: ChainwebVersion +pattern EvmDevelopment <- ((== evmDevnet) -> True) where + EvmDevelopment = evmDevnet + +evmDevnet :: ChainwebVersion +evmDevnet = ChainwebVersion + { _versionCode = ChainwebVersionCode 0x0000_000a + , _versionName = ChainwebVersionName "evm-development" + , _versionForks = tabulateHashMap $ \case + -- TODO: for now, Pact 5 is never enabled on EVM devnet. + -- this will change as it stabilizes. + Pact5Fork -> AllChains ForkNever + _ -> AllChains ForkAtGenesis + , _versionUpgrades = AllChains mempty + , _versionGraphs = Bottom (minBound, twentyChainGraph) + , _versionBlockDelay = BlockDelay 30_000_000 + , _versionWindow = WindowWidth 120 + , _versionHeaderBaseSizeBytes = 318 - 110 + , _versionBootstraps = [] + , _versionGenesis = VersionGenesis + { _genesisBlockTarget = AllChains $ HashTarget (maxBound `div` 500_000) + , _genesisTime = onChains + $ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) + : (unsafeChainId 1, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) + : [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |]) | i <- [2..19] ] + , _genesisBlockPayload = onChains $ + [ (unsafeChainId 0, unsafeFromText "-EffZgInrtrokWVn5iqO2fuTbtG2-noDdhXr0pHQA4E") + , (unsafeChainId 1, unsafeFromText "-EffZgInrtrokWVn5iqO2fuTbtG2-noDdhXr0pHQA4E") + , (unsafeChainId 2, unsafeFromText "Gnh6QWze67ODyy4BoV4ZOeih72e_Cqos2BJM41sMgVc") + , (unsafeChainId 3, unsafeFromText "Ta08GYak3xnTr0HvJq9e37RTigd56N7m2aj_cxI1oC0") + , (unsafeChainId 4, unsafeFromText "eliqzAQ0JGxPD_73dwO7mXsX_tEOz6HJuLsDNJxqSd4") + , (unsafeChainId 5, unsafeFromText "F7-cmj0XXGKxjKm-dDSmMSpD9jwCjzrdQmwQgsjPj2g") + , (unsafeChainId 6, unsafeFromText "VK7rBExdlAUo9maErP19WJSgVMTc37xpEa_VXWELF74") + , (unsafeChainId 7, unsafeFromText "CnAxuzvToxp-bNZ_lnhCAJCEU4hzXuNJmGRMJx5bBWE") + , (unsafeChainId 8, unsafeFromText "abMl-fqTLY1EmiiFILE_orgsbAB_kKAshAx-zIQFoEM") + , (unsafeChainId 9, unsafeFromText "o5G8VKfB7I1Qv_Y8paCFHIS6vZuMYUgYBtV6-fDqzYA") + , (unsafeChainId 10, unsafeFromText "1DJiGUHIrXDNS7M3RJlUorw_07gjLRetb1q9tlt7aZs") + , (unsafeChainId 11, unsafeFromText "ot-tryHcaC7uvHkT1MqYsbLDV1ApZa2KGvjzddIonYU") + , (unsafeChainId 12, unsafeFromText "MjGGd7Osb09dEE2mfpDFISrAfWNnDqEqEYuRm5nFXSs") + , (unsafeChainId 13, unsafeFromText "ljaDzDMfFMOO4AZYC63_KIPAINZwRXUzKZ5FyB1pDjs") + , (unsafeChainId 14, unsafeFromText "W9lvCyH_NAJx89mnCQcwl5OknI89IM_5rn_TR6KkobQ") + , (unsafeChainId 15, unsafeFromText "PGKV488wnfsEgv28CtuAT16JNWmMRB-42TDMIr4jRGQ") + , (unsafeChainId 16, unsafeFromText "8J1yMti75X1Gnjn2AEpWMw-8nzOK6ysHo5c4SBIiNGo") + , (unsafeChainId 17, unsafeFromText "XlIsxdG3YnbxapDq71wY85-ghlIK3c5vfD_WEXgKcRM") + , (unsafeChainId 18, unsafeFromText "968Xg-0Jqm1nTgFi69or2yuFprmg7_SDKcrcWIItW74") + , (unsafeChainId 19, unsafeFromText "7CRqrZPgJ9JYuCWAQtO2bqxlFDUw_i2Hlk52duk8A0s") + ] + } + + -- still the *default* block gas limit is set, see + -- defaultChainwebConfiguration._configBlockGasLimit + , _versionMaxBlockGasLimit = Bottom (minBound, Nothing) + , _versionCheats = VersionCheats + { _disablePow = True + , _fakeFirstEpochStart = True + , _disablePact = False + } + , _versionDefaults = VersionDefaults + { _disablePeerValidation = True + , _disableMempoolSync = False + } + , _versionVerifierPluginNames = AllChains $ Bottom + (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) + , _versionQuirks = noQuirks + , _versionServiceDate = Nothing + + -- FIXME make this safe for graph changes + , _versionPayloadProviderTypes = onChains + $ (unsafeChainId 0, EvmProvider 1789) + : (unsafeChainId 1, EvmProvider 1790) + : [ (unsafeChainId i, MinimalProvider) | i <- [2..19] ] + } + diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index 5b2533f336..d7289ab8ef 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -25,17 +25,6 @@ import P2P.BootstrapNodes import Pact.Types.Runtime (Gas(..)) import Pact.Types.Verifier -import qualified Chainweb.BlockHeader.Genesis.Mainnet0Payload as MN0 -import qualified Chainweb.BlockHeader.Genesis.Mainnet1Payload as MN1 -import qualified Chainweb.BlockHeader.Genesis.Mainnet2Payload as MN2 -import qualified Chainweb.BlockHeader.Genesis.Mainnet3Payload as MN3 -import qualified Chainweb.BlockHeader.Genesis.Mainnet4Payload as MN4 -import qualified Chainweb.BlockHeader.Genesis.Mainnet5Payload as MN5 -import qualified Chainweb.BlockHeader.Genesis.Mainnet6Payload as MN6 -import qualified Chainweb.BlockHeader.Genesis.Mainnet7Payload as MN7 -import qualified Chainweb.BlockHeader.Genesis.Mainnet8Payload as MN8 -import qualified Chainweb.BlockHeader.Genesis.Mainnet9Payload as MN9 -import qualified Chainweb.BlockHeader.Genesis.Mainnet10to19Payload as MNKAD import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 import qualified Chainweb.Pact.Transactions.CoinV4Transactions as CoinV4 import qualified Chainweb.Pact.Transactions.CoinV5Transactions as CoinV5 @@ -171,20 +160,27 @@ mainnet = ChainwebVersion , [(unsafeChainId i, mainnet20InitialHashTarget) | i <- [10..19]] ] , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-10-30T00:01:00.0 |] - , _genesisBlockPayload = OnChains $ HM.fromList $ concat - [ - [ (unsafeChainId 0, MN0.payloadBlock) - , (unsafeChainId 1, MN1.payloadBlock) - , (unsafeChainId 2, MN2.payloadBlock) - , (unsafeChainId 3, MN3.payloadBlock) - , (unsafeChainId 4, MN4.payloadBlock) - , (unsafeChainId 5, MN5.payloadBlock) - , (unsafeChainId 6, MN6.payloadBlock) - , (unsafeChainId 7, MN7.payloadBlock) - , (unsafeChainId 8, MN8.payloadBlock) - , (unsafeChainId 9, MN9.payloadBlock) - ] - , [(unsafeChainId i, MNKAD.payloadBlock) | i <- [10..19]] + , _genesisBlockPayload = onChains + [ ( unsafeChainId 0, unsafeFromText "k1H3DsInAPvJ0W_zPxnrpkeSNdPUT0S9U8bqDLG739o") + , ( unsafeChainId 1, unsafeFromText "kClp_Tw7keCLXMfaCyjH-gToAGmLvRQqiNRmhWUCbxs") + , ( unsafeChainId 2, unsafeFromText "4DVp1dkSLYkFD0zbFF7e5Ba0ezKmnZhEVzlc5ZqqAT0") + , ( unsafeChainId 3, unsafeFromText "fCHKOS9SF9G3Gy5iGZnOmQxjGYf32yM8PklrcBD_dmQ") + , ( unsafeChainId 4, unsafeFromText "cTZ_29TkjXDlG_GlW8iB-va3mofpt_UjthLcOoqtlaQ") + , ( unsafeChainId 5, unsafeFromText "MS_wu92H4GVPCziB6g8rOAc3x2uyegXH4Yl_0TfpI7U") + , ( unsafeChainId 6, unsafeFromText "DVs3k9omI_WHMOJ8tUZ1Bt9Q1DdNmqC3V51iZDGmWwM") + , ( unsafeChainId 7, unsafeFromText "Faa4TQZGFkFumLfHnMIJBAu_PSmGY4F-YraLVFrxr7Y") + , ( unsafeChainId 8, unsafeFromText "YWgbJ4K5VET4WnG3H0Y0HPgzZ_qSnTgqxyB1kpMLJTQ") + , ( unsafeChainId 9, unsafeFromText "w_6Spsw-jdCi9hBTlM6v0C1P7XoglU_AqNG9rcOwAZ0") + , ( unsafeChainId 10, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 11, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 12, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 13, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 14, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 15, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 16, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 17, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 18, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") + , ( unsafeChainId 19, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") ] } , _versionUpgrades = chainZip HM.union diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index 5f6b75451b..22a6add81f 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -22,9 +22,6 @@ import Chainweb.Version import Pact.Types.Verifier -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment0Payload as RDN0 -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment1to9Payload as RDNN -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload as RDNKAD import qualified Chainweb.Pact.Transactions.RecapDevelopmentTransactions as RecapDevnet import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 import qualified Chainweb.Pact.Transactions.CoinV4Transactions as CoinV4 @@ -103,10 +100,27 @@ recapDevnet = ChainwebVersion , [(unsafeChainId i, HashTarget 0x0000088f99632cadf39b0db7655be62cb7dbc84ebbd9a90e5b5756d3e7d9196c) | i <- [10..19]] ] , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] - , _genesisBlockPayload = onChains $ concat - [ [(unsafeChainId 0, RDN0.payloadBlock)] - , [(unsafeChainId i, RDNN.payloadBlock) | i <- [1..9]] - , [(unsafeChainId i, RDNKAD.payloadBlock) | i <- [10..19]] + , _genesisBlockPayload = onChains + [ (unsafeChainId 0, unsafeFromText "5TWTF5R6Vc85vWHqcklTY91ljkV6mJ1wYfDJShooTCw") + , (unsafeChainId 1, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 2, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 3, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 4, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 5, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 6, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 7, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 8, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 9, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") + , (unsafeChainId 10, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 11, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 12, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 13, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 14, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 15, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 16, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 17, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 18, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") + , (unsafeChainId 19, unsafeFromText "OXFBWONFKY7fTY4MH3GaOOELd_cPCMn9GpIOPBYMsvM") ] } diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index 29fae5b818..5b6b83baf7 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -46,6 +46,7 @@ import GHC.Stack import Chainweb.Version import Chainweb.Version.Development +import Chainweb.Version.EvmDevelopment import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.Testnet04 @@ -138,6 +139,7 @@ lookupVersionByCode code notRegistered | code == _versionCode recapDevnet = "recapDevnet version used but not registered, remember to do so after it's configured. " <> perhaps | code == _versionCode devnet = "devnet version used but not registered, remember to do so after it's configured. " <> perhaps + | code == _versionCode evmDevnet = "EVM devnet version used but not registered, remember to do so after it's configured. " <> perhaps | otherwise = "version not registered with code " <> show code <> ", have you seen Chainweb.Test.TestVersions.testVersions?" perhaps = "Perhaps you are attempting to run a different devnet version than a previous run, and you need to delete your db directory before restarting devnet with the new version?" @@ -158,6 +160,7 @@ lookupVersionByName name notRegistered | name == _versionName recapDevnet = "recapDevnet version used but not registered, remember to do so after it's configured" | name == _versionName devnet = "devnet version used but not registered, remember to do so after it's configured" + | name == _versionName evmDevnet = "EVM devnet version used but not registered, remember to do so after it's configured" | otherwise = "version not registered with name " <> show name <> ", have you seen Chainweb.Test.TestVersions.testVersions?" fabricateVersionWithName :: HasCallStack => ChainwebVersionName -> ChainwebVersion @@ -166,7 +169,7 @@ fabricateVersionWithName name = -- | Versions known to us by name. knownVersions :: [ChainwebVersion] -knownVersions = [mainnet, testnet04, testnet05, recapDevnet, devnet] +knownVersions = [mainnet, testnet04, testnet05, recapDevnet, devnet, evmDevnet] -- | Look up a known version by name, usually with `m` instantiated to some -- configuration parser monad. diff --git a/src/Chainweb/Version/Testnet04.hs b/src/Chainweb/Version/Testnet04.hs index e4ddce9373..b82c79673d 100644 --- a/src/Chainweb/Version/Testnet04.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -40,8 +40,6 @@ import qualified Chainweb.Pact.Transactions.Mainnet7Transactions as MN7 import qualified Chainweb.Pact.Transactions.Mainnet8Transactions as MN8 import qualified Chainweb.Pact.Transactions.Mainnet9Transactions as MN9 import qualified Chainweb.Pact.Transactions.MainnetKADTransactions as MNKAD -import qualified Chainweb.BlockHeader.Genesis.Testnet040Payload as PN0 -import qualified Chainweb.BlockHeader.Genesis.Testnet041to19Payload as PNN -- | Initial hash target for testnet04 20-chain transition. Based on the following -- header from recap devnet running with 5 GPUs hash power. Using this target unchanged @@ -151,9 +149,27 @@ testnet04 = ChainwebVersion , [(unsafeChainId i, testnet20InitialHashTarget) | i <- [10..19]] ] , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] - , _genesisBlockPayload = OnChains $ HM.fromList $ concat - [ [(unsafeChainId 0, PN0.payloadBlock)] - , [(unsafeChainId i, PNN.payloadBlock) | i <- [1..19]] + , _genesisBlockPayload = onChains + [ (unsafeChainId 0, unsafeFromText "nfYm3e_fk2ICws0Uowos6OMuqfFg5Nrl_zqXVx9v_ZQ") + , (unsafeChainId 1, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 2, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 3, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 4, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 5, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 6, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 7, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 8, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 9, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 10, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 11, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 12, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 13, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 14, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 15, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 16, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 17, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 18, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") + , (unsafeChainId 19, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") ] } , _versionUpgrades = chainZip HM.union diff --git a/src/Chainweb/Version/Testnet05.hs b/src/Chainweb/Version/Testnet05.hs index c9e13512d2..89991180a5 100644 --- a/src/Chainweb/Version/Testnet05.hs +++ b/src/Chainweb/Version/Testnet05.hs @@ -22,9 +22,6 @@ import P2P.BootstrapNodes import Pact.Types.Verifier -import qualified Chainweb.BlockHeader.Genesis.Testnet050Payload as PN0 -import qualified Chainweb.BlockHeader.Genesis.Testnet051to19Payload as PNN - pattern Testnet05 :: ChainwebVersion pattern Testnet05 <- ((== testnet05) -> True) where Testnet05 = testnet05 @@ -82,10 +79,27 @@ testnet05 = ChainwebVersion [ [(unsafeChainId i, maxTarget) | i <- [0..19]] ] , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] - , _genesisBlockPayload = OnChains $ HM.fromList $ concat - [ [ (unsafeChainId 0, PN0.payloadBlock) - ] - , [(unsafeChainId i, PNN.payloadBlock) | i <- [1..19]] + , _genesisBlockPayload = onChains + [ (unsafeChainId 0, unsafeFromText "Gbu_Tf-PJP2VyptN3m0AnTsXRfiFpnxV8iWZcimPZq4") + , (unsafeChainId 1, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 2, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 3, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 4, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 5, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 6, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 7, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 8, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 9, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 10, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 11, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 12, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 13, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 14, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 15, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 16, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 17, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 18, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") + , (unsafeChainId 19, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") ] } , _versionUpgrades = AllChains mempty From f33cfc5738c71c16a7bf09c6d05fcaf6713f4885 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 20 Jan 2025 01:55:25 -0800 Subject: [PATCH 083/378] Temporarily disable block free consistency check --- src/Chainweb/PayloadProvider/EVM.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 3668447315..7d57a86d5a 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -911,11 +911,12 @@ awaitNewPayload p = do -- Check that the fees of the execution paylod match the block -- value of the response. - unless (EVM._blockValueStu (_getPayloadV3ResponseBlockValue resp) == fees v1) $ - throwM InconsistentNewPayloadFees - { _inconsistentPayloadBlockValue = _getPayloadV3ResponseBlockValue resp - , _inconsistentPayloadFees = fees v1 - } + -- FIXME FIXME FIXME + -- unless (EVM._blockValueStu (_getPayloadV3ResponseBlockValue resp) == fees v1) $ + -- throwM InconsistentNewPayloadFees + -- { _inconsistentPayloadBlockValue = _getPayloadV3ResponseBlockValue resp + -- , _inconsistentPayloadFees = fees v1 + -- } let pld = executionPayloadV3ToHeader (_syncStateBlockHash sstate) v3 From 1af85c09b54137203ae4ed5fb1ea44c2d1933b55 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 28 Jan 2025 02:48:02 -0800 Subject: [PATCH 084/378] WIP on mocked up SPV for EVM Events --- chainweb.cabal | 1 + src/Chainweb/PayloadProvider.hs | 45 ++++++ src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs | 30 ++++ src/Chainweb/PayloadProvider/EVM/SPV.hs | 146 ++++++++++++++++++ src/Chainweb/RestAPI.hs | 2 +- src/Chainweb/SPV.hs | 73 +++++++++ src/Chainweb/SPV/RestAPI.hs | 27 +++- src/Chainweb/SPV/RestAPI/Server.hs | 40 ++++- 8 files changed, 360 insertions(+), 4 deletions(-) create mode 100644 src/Chainweb/PayloadProvider/EVM/SPV.hs diff --git a/chainweb.cabal b/chainweb.cabal index a5b4ac3cb5..991d913a63 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -233,6 +233,7 @@ library , Chainweb.PayloadProvider.EVM.Header , Chainweb.PayloadProvider.EVM.HeaderDB , Chainweb.PayloadProvider.EVM.JsonRPC + , Chainweb.PayloadProvider.EVM.SPV , Chainweb.PayloadProvider.EVM.Utils , Chainweb.PayloadProvider.Initialization , Chainweb.PayloadProvider.Minimal diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index cf388d7a39..11ffd1df0e 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -71,6 +71,12 @@ module Chainweb.PayloadProvider , payloadProviders , withPayloadProvider +-- * SPV +, TransactionIndex(..) +, EventIndex(..) +, XEventId(..) +, SpvProof(..) + -- * Utils -- ** Consensus State Accessors @@ -806,6 +812,9 @@ class (HasChainwebVersion p, HasChainId p) => PayloadProvider p where latestPayloadIO :: p -> IO NewPayload latestPayloadIO = atomically . latestPayloadSTM + -- FIXME FIXME FIXME + eventProof :: p -> XEventId -> IO SpvProof + nextPayloadStm :: PayloadProvider p => p -> NewPayload -> STM NewPayload nextPayloadStm p cur = do new <- latestPayloadSTM p @@ -826,6 +835,42 @@ payloadStream p = do S.yield n go n +-- -------------------------------------------------------------------------- -- +-- SPV + +newtype TransactionIndex = TransactionIndex Natural + deriving (Show, Eq, Ord, Generic) + deriving newtype (FromJSON, ToJSON, Num, Enum, Real, Integral) + +instance HasTextRepresentation TransactionIndex where + toText (TransactionIndex n) = toText n + fromText = fmap TransactionIndex <$> fromText + +newtype EventIndex = EventIndex Natural + deriving (Show, Eq, Ord, Generic) + deriving newtype (FromJSON, ToJSON, Num, Enum, Real, Integral) + +instance HasTextRepresentation EventIndex where + toText (EventIndex n) = toText n + fromText = fmap EventIndex <$> fromText + +-- | A way to identify cross chain events. +-- +-- It is not required that this is payload provider independent. It is just +-- convenient. +-- +data XEventId = XEventId + { _xEventBlockHeight :: !BlockHeight + , _xEventTransactionIndex :: !TransactionIndex + , _xEventEventIndex :: !EventIndex + } + deriving (Show, Eq, Generic) + +-- | Preliminary Type for SPV Event Proofs. +-- +newtype SpvProof = SpvProof Value + deriving (Show, Eq, Generic) + -- -------------------------------------------------------------------------- -- -- Some Payload Provider diff --git a/src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs b/src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs index 3345e4140e..a1f28c6340 100644 --- a/src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs +++ b/src/Chainweb/PayloadProvider/EVM/EthRpcAPI.hs @@ -26,6 +26,8 @@ module Chainweb.PayloadProvider.EVM.EthRpcAPI , type Eth_GetBlockByHash , type Eth_Syncing , type Eth_Call +, type Eth_GetBlockReceipts +, type Eth_GetLogs ) where import Chainweb.PayloadProvider.EVM.Header @@ -46,6 +48,8 @@ import GHC.Generics import Network.HTTP.Client qualified as HTTP import Network.URI.Static (uri) +import Ethereum.Receipt +import Data.Tuple -- -------------------------------------------------------------------------- -- -- Errors @@ -165,6 +169,32 @@ instance JsonRpcMethod "eth_call" where type Eth_Call = "eth_call" +-- -------------------------------------------------------------------------- -- + +instance JsonRpcMethod "eth_getBlockReceipts" where + type MethodRequest "eth_getBlockReceipts" = Solo DefaultBlockParameter + type MethodResponse "eth_getBlockReceipts" = [RpcReceipt] + type ServerErrors "eth_getBlockReceipts" = Int + type ApplicationErrors "eth_getBlockReceipts" = Int + responseTimeoutMs = Nothing + methodErrors = [] + +type Eth_GetBlockReceipts = "eth_getBlockReceipts" + +-- -------------------------------------------------------------------------- -- +-- eth_getLogs + +instance JsonRpcMethod "eth_getLogs" where + type MethodRequest "eth_getLogs" = [Value] -- FIXME FIXME FIXME + type MethodResponse "eth_getLogs" = [RpcLogEntry] + type ServerErrors "eth_getLogs" = Int + type ApplicationErrors "eth_getLogs" = Int + responseTimeoutMs = Nothing + methodErrors = [] + +type Eth_GetLogs = "eth_getLogs" + + -- -------------------------------------------------------------------------- -- -- | Default Engine Context -- diff --git a/src/Chainweb/PayloadProvider/EVM/SPV.hs b/src/Chainweb/PayloadProvider/EVM/SPV.hs new file mode 100644 index 0000000000..8e0a257476 --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/SPV.hs @@ -0,0 +1,146 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} + +{-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE DerivingVia #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.SPV +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.EVM.SPV +( XLogData(..) +, parseXLogData +) where + +import Chainweb.BlockHeight +import Chainweb.ChainId +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Chainweb.Version +import Control.Exception +import Control.Monad.Catch +import Data.ByteString qualified as B +import Data.ByteString.Short qualified as BS +import Data.Text qualified as T +import Data.Word +import Ethereum.Misc +import Ethereum.Receipt +import GHC.Generics (Generic) +import GHC.TypeNats +import Data.Aeson +import Ethereum.Utils (HexBytes(..)) + +-- -------------------------------------------------------------------------- -- +-- Utils + +-- TODO: move to ethereum package +dropN :: KnownNat m => KnownNat n => n <= m => BytesN m -> BytesN n +dropN @m @n b = unsafeBytesN @n (BS.drop (int d) (_getBytesN b)) + where + d = natVal_ @m - natVal_ @n + +-- TODO: move to ethereum package +deriving newtype instance Bytes LogData + +-- -------------------------------------------------------------------------- +-- Exceptions + +data XChainException + = InvalidLogData T.Text + deriving (Show, Eq) + +instance Exception XChainException + +-- -------------------------------------------------------------------------- -- +-- Event Signature + +newtype EventId = EventId (BytesN 32) + deriving (Show, Eq, Generic, Bytes) + +xChainInitializedSignature :: B.ByteString +xChainInitializedSignature = "CrosschainInitialized(uint32,address,uint64,bytes)" + +xChainInitializedId :: EventId +xChainInitializedId = EventId + $ _getKeccak256Hash + $ keccak256 + $ xChainInitializedSignature + +-- -------------------------------------------------------------------------- -- +-- Crosschain Operation Name + +newtype XChainOperationName = XChainOperationName Word64 + deriving (Show, Eq, Generic) + deriving newtype (ToJSON, FromJSON) + +-- -------------------------------------------------------------------------- -- +-- X-Chain Message Type + +newtype XChainData = XChainData B.ByteString + deriving (Show, Eq, Generic) + deriving (ToJSON, FromJSON) via (HexBytes B.ByteString) + +-- -------------------------------------------------------------------------- -- +-- LogData For Crosschain Event + +-- @ +-- event CrosschainInitialized( +-- uint32 indexed targetChainId, +-- address indexed targetContractAddress, +-- uint64 indexed crosschainOperationName, +-- bytes crosschainData +-- ) +-- @ + +-- | X-Chain LogData +-- +data XLogData = XLogData + { _xLogDataOperationName :: !XChainOperationName + , _xLogDataSenderAddress :: !Address + , _xLogDataTargetChain :: !ChainId + , _xLogDataTargetContract :: !Address + , _xLogDataMessage :: !XChainData + } + deriving (Show, Eq, Generic) + +parseXLogData + :: MonadThrow m + => ChainwebVersion + -> BlockHeight + -> LogEntry + -> m XLogData +parseXLogData v h e = do + (LogTopic t0, LogTopic t1, LogTopic t2, LogTopic t3) <- getTopics + + -- FIXME FIXME FIXME + -- Check the event signatgure! + + targetChain <- mkChainId v h =<< runGetS decodeWordBe (bytes $ dropN @32 @4 t1) + operationName <- XChainOperationName <$> runGetS decodeWordBe (bytes $ dropN @32 @8 t3) + + return XLogData + { _xLogDataOperationName = operationName + , _xLogDataSenderAddress = _logEntryAddress e + , _xLogDataTargetChain = targetChain + , _xLogDataTargetContract = Address $ dropN $ t2 + , _xLogDataMessage = XChainData $ bytes $ _logEntryData e + } + where + getTopics = case _logEntryTopics e of + (t0 : t1 : t2 : t3: _ ) -> return (t0, t1, t2, t3) + l -> throwM $ InvalidLogData $ + "expected at least four topics but got " <> sshow (length l) + diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index 5cd65d51b1..add005a8a4 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -351,7 +351,7 @@ someServiceApiServer v dbs mr (HeaderStream hs) backupEnv pbl = <> maybe mempty (someNodeInfoServer v) cuts <> mempty -- PactAPI.somePactServers v <> maybe mempty (Mining.someMiningServer v) mr - -- <> maybe mempty (someSpvServers v) cuts -- AFAIK currently not used + <> maybe mempty (someSpvServers v) cuts -- AFAIK currently not used -- GET Cut, Payload, and Headers endpoints <> maybe mempty (someCutGetServer v) cuts diff --git a/src/Chainweb/SPV.hs b/src/Chainweb/SPV.hs index 3c58fb1b71..12f555a6a6 100644 --- a/src/Chainweb/SPV.hs +++ b/src/Chainweb/SPV.hs @@ -23,6 +23,9 @@ module Chainweb.SPV , proofChainId , TransactionOutputProof(..) , outputProofChainId +, EventProof(..) +, eventProofChainId +, FakeEventProof(..) ) where import Control.Applicative @@ -212,3 +215,73 @@ instance FromJSON (TransactionOutputProof SHA512t_256) where -- outputProofChainId :: Getter (TransactionOutputProof a) ChainId outputProofChainId = to (\(TransactionOutputProof cid _) -> cid) + +-- -------------------------------------------------------------------------- -- +-- Event Proofs + +-- | Witness that a event is included in the head of a chain in a chainweb. +-- +data EventProof a = EventProof + !ChainId + -- ^ the target chain of the proof, i.e the chain which contains + -- the root of the proof. + !(MerkleProof a) + -- ^ the Merkle proof blob, which contains both the proof object and + -- the subject. + deriving (Show, Eq) + +instance ToJSON (EventProof SHA512t_256) where + toJSON (EventProof cid p) = object $ proofProperties cid p + toEncoding (EventProof cid p) = pairs . mconcat $ proofProperties cid p + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance FromJSON (EventProof SHA512t_256) where + parseJSON = parseProof "EventProof" EventProof + {-# INLINE parseJSON #-} + +-- | Getter into the chain id of a 'EventProof' +-- +eventProofChainId :: Getter (EventProof a) ChainId +eventProofChainId = to (\(EventProof cid _) -> cid) + +-- -------------------------------------------------------------------------- -- +-- Fake Event Proof + +-- | Fake event proof +-- +data FakeEventProof = FakeEventProof + !ChainId + -- ^ the target chain of the proof, i.e the chain which contains + -- the root of the proof. + !Value + -- ^ the Merkle proof blob, which contains both the proof object and + -- the subject. + deriving (Show, Eq) + +instance ToJSON FakeEventProof where + toJSON = object . fakeEventProofProperties + toEncoding = pairs . mconcat . fakeEventProofProperties + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance FromJSON FakeEventProof where + parseJSON = withObject "FakeEventProof" $ \o -> FakeEventProof + <$> o .: "chain" + <*> o .: "subject" + {-# INLINE parseJSON #-} + +fakeEventProofProperties + :: forall e kv + . KeyValue e kv + => FakeEventProof + -> [kv] +fakeEventProofProperties (FakeEventProof cid v) = + [ "chain" .= cid + , "object" .= obj "SNAKEOIL" + , "subject" .= v + , "algorithm" .= ("SHA512t_256" :: T.Text) + ] + where + obj = encodeB64UrlNoPaddingText + diff --git a/src/Chainweb/SPV/RestAPI.hs b/src/Chainweb/SPV/RestAPI.hs index dc7d742890..aaa19df614 100644 --- a/src/Chainweb/SPV/RestAPI.hs +++ b/src/Chainweb/SPV/RestAPI.hs @@ -23,6 +23,10 @@ module Chainweb.SPV.RestAPI , SpvGetTransactionOutputProofApi , spvGetTransactionOutputProofApi +-- * Event Proof API +, SpvGetEventProofApi +, spvGetEventProofApi + -- * SPV API , SpvApi , spvApi @@ -85,11 +89,32 @@ spvGetTransactionOutputProofApi . Proxy (SpvGetTransactionOutputProofApi v c) spvGetTransactionOutputProofApi = Proxy +-- -------------------------------------------------------------------------- -- +-- GET Event Output Proof + +type SpvGetEventProofApi_ + = "spv" + :> "chain" :> Capture "spvChain" ChainId + :> "height" :> Capture "spvHeight" BlockHeight + :> "transaction" :> Capture "spvTransactionactionIndex" Natural + :> "event" :> Capture "spvEventIndex" Natural + :> Get '[JSON] FakeEventProof + +type SpvGetEventProofApi (v :: ChainwebVersionT) (c :: ChainIdT) + = 'ChainwebEndpoint v :> ChainEndpoint c :> SpvGetEventProofApi_ + +spvGetEventProofApi + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) + . Proxy (SpvGetEventProofApi v c) +spvGetEventProofApi = Proxy + -- -------------------------------------------------------------------------- -- -- SPV API type SpvApi v c - = SpvGetTransactionProofApi v c :<|> SpvGetTransactionOutputProofApi v c + = SpvGetEventProofApi v c + -- :<|>SpvGetTransactionProofApi v c + -- :<|> SpvGetTransactionOutputProofApi v c spvApi :: forall (v :: ChainwebVersionT) (c :: ChainIdT) . Proxy (SpvApi v c) spvApi = Proxy diff --git a/src/Chainweb/SPV/RestAPI/Server.hs b/src/Chainweb/SPV/RestAPI/Server.hs index 90e02548d3..0b97c4c4c4 100644 --- a/src/Chainweb/SPV/RestAPI/Server.hs +++ b/src/Chainweb/SPV/RestAPI/Server.hs @@ -48,6 +48,8 @@ import Chainweb.Utils import Chainweb.Version import Data.Singletons +import Chainweb.PayloadProvider +import Control.Lens (view) -- -------------------------------------------------------------------------- -- -- SPV Transaction Proof Handler @@ -94,6 +96,39 @@ spvGetTransactionOutputProofHandler db tcid scid bh i = liftIO $ createTransactionOutputProof db tcid scid bh (int i) -- FIXME: add proper error handling +-- -------------------------------------------------------------------------- -- +-- SPV Event Output Proof Handler + +spvGetEventProofHandler + :: CutDb + -> ChainId + -- ^ the target chain of the proof. This is the chain for which inclusion + -- is proved. + -> ChainId + -- ^ the source chain of the proof. This is the chain where the proof + -- subject, the transaction output for which inclusion is proven, is + -- located. + -> BlockHeight + -- ^ the block height of the proof subject, the transaction output for + -- which inclusion is proven. + -> Natural + -- ^ the index of the proof subject, the transaction output for which + -- inclusion is proven. + -> Natural + -- ^ The event index in the transaction + -> Handler FakeEventProof +spvGetEventProofHandler db tcid scid bh i e = liftIO $ do + withPayloadProvider providers scid $ \p -> do + (SpvProof v) <- liftIO $ eventProof p xevent + return $ FakeEventProof tcid v + where + providers = view cutDbPayloadProviders db + xevent = XEventId + { _xEventBlockHeight = bh + , _xEventTransactionIndex = int i + , _xEventEventIndex = int e + } + -- -------------------------------------------------------------------------- -- -- SPV API Server @@ -103,8 +138,9 @@ spvServer => CutDbT v -> Server (SpvApi v c) spvServer (CutDbT db) - = spvGetTransactionProofHandler db tcid - :<|> spvGetTransactionOutputProofHandler db tcid + = spvGetEventProofHandler db tcid + -- = spvGetTransactionProofHandler db tcid + -- :<|> spvGetTransactionOutputProofHandler db tcid where tcid = fromSing (sing :: Sing c) From 96cb1a95f68d6635e8a785e1cdf9d6b758f6f19a Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 28 Jan 2025 11:28:16 -0800 Subject: [PATCH 085/378] ... wip spv --- src/Chainweb/PayloadProvider/EVM.hs | 104 +++++++++++++++++++++++- src/Chainweb/PayloadProvider/EVM/SPV.hs | 12 ++- 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 7d57a86d5a..f06a1f3d49 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -57,13 +57,14 @@ import Chainweb.ChainId import Chainweb.Core.Brief import Chainweb.Logger import Chainweb.MinerReward -import Chainweb.PayloadProvider +import Chainweb.PayloadProvider hiding (TransactionIndex) import Chainweb.PayloadProvider.EVM.EngineAPI import Chainweb.PayloadProvider.EVM.EthRpcAPI import Chainweb.PayloadProvider.EVM.Header qualified as EVM import Chainweb.PayloadProvider.EVM.HeaderDB qualified as EvmDB import Chainweb.PayloadProvider.EVM.JsonRPC (JsonRpcHttpCtx, callMethodHttp) import Chainweb.PayloadProvider.EVM.JsonRPC qualified as RPC +import Chainweb.PayloadProvider.EVM.SPV import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) import Chainweb.PayloadProvider.EVM.Utils qualified as EVM import Chainweb.PayloadProvider.EVM.Utils qualified as Utils @@ -85,15 +86,18 @@ import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Catch (MonadThrow, throwM) import Data.ByteString.Short qualified as BS +import Data.List qualified as L import Data.LogMessage import Data.Maybe import Data.PQueue import Data.Singletons import Data.Text qualified as T +import Data.Tuple import Ethereum.Misc import Ethereum.Misc qualified as EVM import Ethereum.Misc qualified as Ethereum import Ethereum.RLP +import Ethereum.Receipt import GHC.Generics (Generic) import GHC.TypeNats (fromSNat) import Network.HTTP.Client qualified as HTTP @@ -102,6 +106,7 @@ import Network.URI.Static import P2P.Session (ClientEnv) import P2P.TaskQueue import System.LogLevel +import Data.Function -- -------------------------------------------------------------------------- -- -- Types (to keep the code clean and avoid confusion) @@ -426,6 +431,13 @@ data EvmHeaderNotFoundException deriving (Eq, Show, Generic) instance Exception EvmHeaderNotFoundException +data LogEntryNotFoundException + = InvalidTransactionIndex XEventId + | InvalidEventIndex XEventId + | AmbiguousLogEntries XEventId [RpcLogEntry] + deriving (Eq, Show, Generic) +instance Exception LogEntryNotFoundException + data InvalidEvmState = EvmGenesisHeaderNotFound deriving (Eq, Show, Generic) @@ -1240,6 +1252,95 @@ instance Logger logger => PayloadProvider (EvmPayloadProvider logger) where prefetchPayloads _ _ _ = return () syncToBlock = evmSyncToBlock latestPayloadSTM p = ssnd <$> readTMVar (_evmPayloadVar p) + eventProof = getSpvProof + +-- -------------------------------------------------------------------------- -- +-- SPV + +-- | Obtains all RpcReceipts for a block. It is an exception if the block can +-- not be found. +-- +-- Returns: a list of EVM RpcReceipts +-- +-- Throws: +-- +-- * EvmHeaderNotFoundException +-- * Eth RPC failures +-- * JSON RPC failures +-- +-- FIXME: filter for topic +-- +getLogEntries + :: EvmPayloadProvider logger + -> EVM.BlockNumber + -> IO [RpcLogEntry] +getLogEntries p n = do + callMethodHttp + @Eth_GetLogs (_evmEngineCtx p) + [ object + [ "fromBlock" .= DefaultBlockNumber n + , "toBlock" .= DefaultBlockNumber n + ] + ] + +getLogEntry + :: EvmPayloadProvider logger + -> XEventId + -> IO LogEntry +getLogEntry p e = do + -- it would be nice if we could just use the long entry index, but that is + -- over the whole block and not just the tx + logs <- L.groupBy ((==) `on` _rpcLogEntryTransactionIndex) + <$> getLogEntries p (int $ _xEventBlockHeight e) + case logs L.!? (int $ _xEventTransactionIndex e) of + Nothing -> throwM $ InvalidTransactionIndex e + Just tx -> case tx L.!? (int $ _xEventEventIndex e) of + Nothing -> throwM $ InvalidEventIndex e + Just l -> return $ fromRpcLogEntry l + + -- case filter ftx logs of + -- [] -> throwM $ InvalidTransactionIndex e + -- txLogs -> case filter fev txLogs of + -- [] -> throwM $ InvalidEventIndex e + -- [l] -> return $ fromRpcLogEntry l + -- ls -> throwM $ AmbiguousLogEntries e ls + -- r <- getBlockReceipts p (int $ _xEventBlockHeight e) + -- case r L.!? (int $ _xEventTransactionIndex e) of + -- Nothing -> throwM $ InvalidTransactionIndex e + -- Just tx -> case _rpcReceiptLogs tx L.!? (int $ _xEventEventIndex e) of + -- Nothing -> throwM $ InvalidEventIndex e + -- Just l -> return $ fromRpcLogEntry l + -- where + -- ftx RpcLogEntry { _rpcLogEntryTransactionIndex = TransactionIndex l } = + -- l == int (_xEventTransactionIndex e) + -- fev RpcLogEntry { _rpcLogEntryLogIndex = TransactionIndex l } = + -- l == int (_xEventEventIndex e) + +getSpvProof + :: Logger logger + => EvmPayloadProvider logger + -> XEventId + -> IO SpvProof +getSpvProof p e = do + le <- getLogEntry p e + lf Info $ "got logEntry: " <> encodeToText le + ld <- parseXLogData (_chainwebVersion p) (_xEventBlockHeight e) le + lf Info $ "got logData: " <> sshow ld + return $ SpvProof $ object + [ "origin" .= object + [ "chainId" .= _chainId p + , "contract" .= _xLogDataSenderAddress ld + , "height" .= _xEventBlockHeight e + , "transactionIdx" .= _xEventTransactionIndex e + , "eventIdx" .= _xEventEventIndex e + ] + , "targetChainId" .= _xLogDataTargetChain ld + , "targetContract" .= _xLogDataTargetContract ld + , "operationName" .= _xLogDataOperationName ld + , "data" .= _xLogDataMessage ld + ] + where + lf = loggS p "getSpvProof" -- -------------------------------------------------------------------------- -- -- Utils @@ -1259,7 +1360,6 @@ encodedPayloadData = EncodedPayloadData . putRlpByteString decodePayloadData :: MonadThrow m => EncodedPayloadData -> m Payload decodePayloadData (EncodedPayloadData bs) = decodeRlpM bs - -- -------------------------------------------------------------------------- -- -- ATTIC -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/PayloadProvider/EVM/SPV.hs b/src/Chainweb/PayloadProvider/EVM/SPV.hs index 8e0a257476..017586f00b 100644 --- a/src/Chainweb/PayloadProvider/EVM/SPV.hs +++ b/src/Chainweb/PayloadProvider/EVM/SPV.hs @@ -9,9 +9,11 @@ {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -Wno-orphans #-} {-# LANGUAGE DerivingVia #-} +{-# LANGUAGE KindSignatures #-} -- | -- Module: Chainweb.PayloadProvider.EVM.SPV @@ -47,8 +49,14 @@ import Ethereum.Utils (HexBytes(..)) -- Utils -- TODO: move to ethereum package -dropN :: KnownNat m => KnownNat n => n <= m => BytesN m -> BytesN n -dropN @m @n b = unsafeBytesN @n (BS.drop (int d) (_getBytesN b)) +dropN + :: forall (m :: Natural) (n :: Natural) + . KnownNat m + => KnownNat n + => n <= m + => BytesN m + -> BytesN n +dropN b = unsafeBytesN @n (BS.drop (int d) (_getBytesN b)) where d = natVal_ @m - natVal_ @n From 926f6e44a4bae7a8226da3be19b8c730e79bfabf Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 28 Jan 2025 14:13:39 -0800 Subject: [PATCH 086/378] Add Address32 to EVM.Utils --- src/Chainweb/PayloadProvider/EVM/Utils.hs | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs index d69d582039..08a8d9ef7a 100644 --- a/src/Chainweb/PayloadProvider/EVM/Utils.hs +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -25,6 +25,8 @@ module Chainweb.PayloadProvider.EVM.Utils ( ChainId(..) , Randao(..) , BlockValue(..) +, Address32(..) +, toAddress32 , _blockValueStu , DefaultBlockParameter(..) @@ -183,6 +185,28 @@ newtype ChainId = ChainId { _chainId :: Natural } deriving (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via (HexQuantity Natural) +-- -------------------------------------------------------------------------- -- +-- Address 32 + +newtype Address32 = Address32 (BytesN 32) + deriving (Show, Eq, Ord) + deriving newtype (RLP, Bytes, Storable) + deriving (FromJSON, ToJSON) via (HexBytes (BytesN 32)) + +instance HasTextRepresentation Address32 where + toText = toText . HexBytes . bytes + fromText = fmap (Address32 . fromHexBytes) . fromText + {-# INLINE toText #-} + {-# INLINE fromText #-} + +toAddress32 + :: Address + -> Address32 +toAddress32 (Address b) = Address32 (appendN zeroN b) + +zeroN :: KnownNat n => BytesN n +zeroN = replicateN 0 + -- -------------------------------------------------------------------------- -- -- Randao From 5a3813b558d8f9989c1b90e5d7bf91815d01fed3 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 28 Jan 2025 14:14:08 -0800 Subject: [PATCH 087/378] Use Address32 in XLogData --- src/Chainweb/PayloadProvider/EVM/SPV.hs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/SPV.hs b/src/Chainweb/PayloadProvider/EVM/SPV.hs index 017586f00b..78ebf74487 100644 --- a/src/Chainweb/PayloadProvider/EVM/SPV.hs +++ b/src/Chainweb/PayloadProvider/EVM/SPV.hs @@ -44,6 +44,7 @@ import GHC.Generics (Generic) import GHC.TypeNats import Data.Aeson import Ethereum.Utils (HexBytes(..)) +import Chainweb.PayloadProvider.EVM.Utils hiding (ChainId) -- -------------------------------------------------------------------------- -- -- Utils @@ -117,9 +118,9 @@ newtype XChainData = XChainData B.ByteString -- data XLogData = XLogData { _xLogDataOperationName :: !XChainOperationName - , _xLogDataSenderAddress :: !Address + , _xLogDataSenderAddress :: !Address32 , _xLogDataTargetChain :: !ChainId - , _xLogDataTargetContract :: !Address + , _xLogDataTargetContract :: !Address32 , _xLogDataMessage :: !XChainData } deriving (Show, Eq, Generic) @@ -141,9 +142,9 @@ parseXLogData v h e = do return XLogData { _xLogDataOperationName = operationName - , _xLogDataSenderAddress = _logEntryAddress e + , _xLogDataSenderAddress = toAddress32 $ _logEntryAddress e , _xLogDataTargetChain = targetChain - , _xLogDataTargetContract = Address $ dropN $ t2 + , _xLogDataTargetContract = Address32 t2 , _xLogDataMessage = XChainData $ bytes $ _logEntryData e } where From ed0a6dfb6f3435c9bfece741f786b634df0f45f5 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 28 Jan 2025 22:41:25 -0800 Subject: [PATCH 088/378] Fix query for EVM log entries --- src/Chainweb/PayloadProvider/EVM.hs | 30 +++++++---------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index f06a1f3d49..3a010a9e04 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1290,31 +1290,15 @@ getLogEntry getLogEntry p e = do -- it would be nice if we could just use the long entry index, but that is -- over the whole block and not just the tx - logs <- L.groupBy ((==) `on` _rpcLogEntryTransactionIndex) - <$> getLogEntries p (int $ _xEventBlockHeight e) - case logs L.!? (int $ _xEventTransactionIndex e) of - Nothing -> throwM $ InvalidTransactionIndex e - Just tx -> case tx L.!? (int $ _xEventEventIndex e) of + logs <- getLogEntries p (int $ _xEventBlockHeight e) + case filter ftx logs of + [] -> throwM $ InvalidTransactionIndex e + tx -> case tx L.!? (int $ _xEventEventIndex e) of Nothing -> throwM $ InvalidEventIndex e Just l -> return $ fromRpcLogEntry l - - -- case filter ftx logs of - -- [] -> throwM $ InvalidTransactionIndex e - -- txLogs -> case filter fev txLogs of - -- [] -> throwM $ InvalidEventIndex e - -- [l] -> return $ fromRpcLogEntry l - -- ls -> throwM $ AmbiguousLogEntries e ls - -- r <- getBlockReceipts p (int $ _xEventBlockHeight e) - -- case r L.!? (int $ _xEventTransactionIndex e) of - -- Nothing -> throwM $ InvalidTransactionIndex e - -- Just tx -> case _rpcReceiptLogs tx L.!? (int $ _xEventEventIndex e) of - -- Nothing -> throwM $ InvalidEventIndex e - -- Just l -> return $ fromRpcLogEntry l - -- where - -- ftx RpcLogEntry { _rpcLogEntryTransactionIndex = TransactionIndex l } = - -- l == int (_xEventTransactionIndex e) - -- fev RpcLogEntry { _rpcLogEntryLogIndex = TransactionIndex l } = - -- l == int (_xEventEventIndex e) + where + ftx RpcLogEntry { _rpcLogEntryTransactionIndex = TransactionIndex l } = + l == int (_xEventTransactionIndex e) getSpvProof :: Logger logger From 976a00acd22b94c7ea17231e2cd7fbe3d81add47 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 3 Feb 2025 19:15:24 -0800 Subject: [PATCH 089/378] Add unfreeze argument to docker file --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 1c5ae6e11e..4ea3f03289 100644 --- a/Dockerfile +++ b/Dockerfile @@ -163,6 +163,8 @@ EOF FROM chainweb-build AS chainweb-build-ctx ARG TARGETPLATFORM +ARG UNFREEZE +ENV UNFREEZE=$UNFREEZE # RUN git clone --filter=tree:0 https://github.com/kadena-io/chainweb-node # WORKDIR /chainweb/chainweb-node COPY . . @@ -175,6 +177,7 @@ if [ -d ".git" ] && ! [ -f "/tools/wip" ] && ! git diff --exit-code; then \ exit 1 ; \ fi EOF +RUN [ -z "$UNFREEZE" ] || rm -f cabal.project.freeze RUN sh /tools/check-git-clean.sh || touch /tools/wip # ############################################################################ # From e282775df62b485a243ed240b8cd2a393d1098e6 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 8 Feb 2025 09:50:57 -0800 Subject: [PATCH 090/378] cwtool for EVM gensis header information for defining of chainweb versions --- cwtools/cwtools.cabal | 19 +++++++++++ cwtools/evm-genesis/Main.hs | 64 +++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 cwtools/evm-genesis/Main.hs diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index f5dd4c8f96..9e5e225ca1 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -332,3 +332,22 @@ executable tx-list , text , unordered-containers , yet-another-logger + +executable evm-genesis + import: warning-flags, debugging-flags + default-language: Haskell2010 + ghc-options: + -threaded + -rtsopts + hs-source-dirs: evm-genesis + main-is: Main.hs + build-depends: + , chainweb + , ethereum + , bytestring + , aeson + , text + , base + , network-uri + , http-client + diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs new file mode 100644 index 0000000000..71be41f437 --- /dev/null +++ b/cwtools/evm-genesis/Main.hs @@ -0,0 +1,64 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TypeApplications #-} + +-- | +-- Module: evm-genesis.Main +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Main +( main +) where + +import Chainweb.PayloadProvider.EVM.EngineAPI qualified as E +import Chainweb.PayloadProvider.EVM.EthRpcAPI qualified as E +import Chainweb.PayloadProvider.EVM.Header qualified as E +import Chainweb.PayloadProvider.EVM.JsonRPC qualified as E +import Chainweb.Utils +import Data.String +import Data.Text.IO qualified as T +import Ethereum.Misc qualified as E +import Ethereum.RLP qualified as E +import Network.HTTP.Client qualified as HTTP +import Network.URI +import System.Environment + +main :: IO () +main = do + -- read uri form command line + uri <- getArgs >>= \case + [str] -> fromText (fromString str) + l -> error $ "unexpected CLI arguments " <> show l + + -- query header + ctx <- mkRpcCtx uri + hdr <- getBlockAtNumber ctx 0 + + -- print block payload hash + T.putStrLn $ encodeToText $ E._hdrPayloadHash hdr + -- encode header to base64 + T.putStrLn $ encodeB64UrlNoPaddingText $ E.putRlpByteString hdr + +getBlockAtNumber + :: E.JsonRpcHttpCtx + -> E.BlockNumber + -> IO E.Header +getBlockAtNumber ctx n = do + r <- E.callMethodHttp + @E.Eth_GetBlockByNumber ctx (E.DefaultBlockNumber n, False) + case r of + Just h -> return h + Nothing -> error $ "Block not found: " <> show (E.DefaultBlockNumber n) + +mkRpcCtx :: URI -> IO E.JsonRpcHttpCtx +mkRpcCtx u = do + mgr <- HTTP.newManager HTTP.defaultManagerSettings + return $ E.JsonRpcHttpCtx + { E._jsonRpcHttpCtxManager = mgr + , E._jsonRpcHttpCtxURI = u + , E._jsonRpcHttpCtxMakeBearerToken = Nothing + } + From 7708f2f689fad3b06b2bb6dbec51412f065d5a71 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 10 Feb 2025 01:11:51 -0800 Subject: [PATCH 091/378] update evm genesis blocks --- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 23 +++++++-------------- src/Chainweb/Version/EvmDevelopment.hs | 4 ++-- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 060103d421..c6506489c3 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -68,22 +68,10 @@ import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) -- docker compose up -d chainweb-evm-chain0 -- @ -- --- 2. Query the EVM genesis header with `eth_getBlockByNumber` at height 0 +-- 2. Query the EVM genesis header and compute block payload hash and header: -- -- @ --- docker compose run --rm -ti curl -sL http://chainweb-evm-chain0:8545 -XPOST -H 'content-type:application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["0x0", false]}' | --- jq -rc '.result' | --- sed -e 's/"/\\"/g' --- @ --- --- 3. In ghci bind the output value to constant @a@ and --- --- @ --- hdr <- decodeOrThrow @IO @E.Header a --- -- print block payload hash --- encodeToText $ E._hdrPayloadHash hdr --- -- encode header to base64 --- encodeB64UrlNoPaddingText $ putRlpByteString hdr +-- cabal run cwtools:exe:evm-genesis -- http://localhost:8545 -- @ -- genesisBlocks @@ -95,8 +83,11 @@ genesisBlocks genesisBlocks v c = go (_chainwebVersion v) (_chainId c) where -- Ethereum NetworkID 1789 - go EvmDevelopment (ChainId 0) = f "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoH0rhsSTQSpYbwPkruGrlIN8ao__If98YEXcEzDVsNSeoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - go EvmDevelopment (ChainId 1) = f "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoH0rhsSTQSpYbwPkruGrlIN8ao__If98YEXcEzDVsNSeoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 0) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKubUszUzclOu3VLZ6Oz-L6Nph9pRr7Ilc6Sy0N9diB6oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + -- Ethereum NetworkID 1790 + go EvmDevelopment (ChainId 1) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKtIkoG2zy4Z3rQ8TVs8INePmK-1cYAD8aBvkf-57uIVoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" go _ _ = error "requested genesis block for unsupported chain" f t = case decodeB64UrlNoPaddingText t >>= decodeRlpM of diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index c567c3aad4..b6681bac1f 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -49,8 +49,8 @@ evmDevnet = ChainwebVersion : (unsafeChainId 1, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) : [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |]) | i <- [2..19] ] , _genesisBlockPayload = onChains $ - [ (unsafeChainId 0, unsafeFromText "-EffZgInrtrokWVn5iqO2fuTbtG2-noDdhXr0pHQA4E") - , (unsafeChainId 1, unsafeFromText "-EffZgInrtrokWVn5iqO2fuTbtG2-noDdhXr0pHQA4E") + [ (unsafeChainId 0, unsafeFromText "cRFtAlQ2aQn3xzMlieY3UGixp2a7z2eXdl4N4w6HRLg") + , (unsafeChainId 1, unsafeFromText "1lkrseazRVRa4-TYmVV2XcOjyY3Ba0KA-IX4r1bwwtI") , (unsafeChainId 2, unsafeFromText "Gnh6QWze67ODyy4BoV4ZOeih72e_Cqos2BJM41sMgVc") , (unsafeChainId 3, unsafeFromText "Ta08GYak3xnTr0HvJq9e37RTigd56N7m2aj_cxI1oC0") , (unsafeChainId 4, unsafeFromText "eliqzAQ0JGxPD_73dwO7mXsX_tEOz6HJuLsDNJxqSd4") From 0bbaa2c64e6dd2689c5e5a67f13c6a18c02bb3da Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 10 Feb 2025 01:12:41 -0800 Subject: [PATCH 092/378] move dropN to EVM.Utils --- src/Chainweb/PayloadProvider/EVM/SPV.hs | 22 +--------------- src/Chainweb/PayloadProvider/EVM/Utils.hs | 32 ++++++++++++++++------- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/SPV.hs b/src/Chainweb/PayloadProvider/EVM/SPV.hs index 78ebf74487..6eab9f8d5b 100644 --- a/src/Chainweb/PayloadProvider/EVM/SPV.hs +++ b/src/Chainweb/PayloadProvider/EVM/SPV.hs @@ -35,34 +35,14 @@ import Chainweb.Version import Control.Exception import Control.Monad.Catch import Data.ByteString qualified as B -import Data.ByteString.Short qualified as BS import Data.Text qualified as T import Data.Word import Ethereum.Misc import Ethereum.Receipt import GHC.Generics (Generic) -import GHC.TypeNats +import Chainweb.PayloadProvider.EVM.Utils hiding (ChainId) import Data.Aeson import Ethereum.Utils (HexBytes(..)) -import Chainweb.PayloadProvider.EVM.Utils hiding (ChainId) - --- -------------------------------------------------------------------------- -- --- Utils - --- TODO: move to ethereum package -dropN - :: forall (m :: Natural) (n :: Natural) - . KnownNat m - => KnownNat n - => n <= m - => BytesN m - -> BytesN n -dropN b = unsafeBytesN @n (BS.drop (int d) (_getBytesN b)) - where - d = natVal_ @m - natVal_ @n - --- TODO: move to ethereum package -deriving newtype instance Bytes LogData -- -------------------------------------------------------------------------- -- Exceptions diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs index 08a8d9ef7a..e6ebbdaa64 100644 --- a/src/Chainweb/PayloadProvider/EVM/Utils.hs +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -6,11 +6,13 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fno-warn-orphans #-} @@ -36,14 +38,14 @@ module Chainweb.PayloadProvider.EVM.Utils , nullHash , nullBlockHash , decodeRlpM +, dropN ) where import Chainweb.BlockHash qualified as Chainweb +import Chainweb.MinerReward import Chainweb.Utils import Chainweb.Utils.Serialization (runPutS, runGetS) - import Control.Monad.Catch - import Data.Aeson import Data.ByteString qualified as B import Data.ByteString.Base16 qualified as B16 @@ -52,20 +54,15 @@ import Data.Hashable (Hashable) import Data.Text qualified as T import Data.Text.Encoding qualified as T import Data.Text.Read qualified as T - import Ethereum.Misc import Ethereum.RLP (RLP, get, getRlp) +import Ethereum.Receipt import Ethereum.Transaction (Wei (..)) -import Ethereum.Utils hiding (int) - +import Ethereum.Utils hiding (int, natVal_) import Foreign.Storable (Storable) - import GHC.Generics (Generic) - import GHC.TypeLits - import Text.Printf -import Chainweb.MinerReward -- -------------------------------------------------------------------------- -- -- Utils (should be moved to the ethereum package) @@ -149,6 +146,23 @@ instance HasTextRepresentation Address where {-# INLINE toText #-} {-# INLINE fromText #-} +-- -------------------------------------------------------------------------- -- +-- Bytes + +dropN + :: forall (m :: Natural) (n :: Natural) + . KnownNat m + => KnownNat n + => n <= m + => BytesN m + -> BytesN n +dropN b = unsafeBytesN @n (BS.drop (int d) (_getBytesN b)) + where + d = natVal_ @m - natVal_ @n + +-- TODO: move to ethereum package +deriving newtype instance Bytes LogData + -- -------------------------------------------------------------------------- -- -- RLP Encoding Tools From 602589a65c7dadeaf0b1802ce4b0c076a420106c Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 5 Mar 2025 13:32:37 -0500 Subject: [PATCH 093/378] pact pp Change-Id: Id00000008b2eb65d7acbc4409bab832d1b2f4e5c --- chainweb.cabal | 2 + src/Chainweb/BlockHeader.hs | 8 +- src/Chainweb/BlockHeader/Internal.hs | 84 ++- src/Chainweb/BlockHeader/Validation.hs | 56 +- src/Chainweb/Core/Brief.hs | 3 +- src/Chainweb/Cut/Create.hs | 22 +- src/Chainweb/Pact/Backend/Utils.hs | 36 +- src/Chainweb/Pact/PactService.hs | 509 +++++++----------- src/Chainweb/Pact/PactService/Checkpointer.hs | 6 + .../Pact/PactService/Checkpointer/Internal.hs | 65 +-- src/Chainweb/Pact/Types.hs | 22 +- src/Chainweb/Payload/PayloadStore.hs | 14 + src/Chainweb/PayloadProvider.hs | 59 +- src/Chainweb/PayloadProvider/EVM.hs | 7 +- src/Chainweb/PayloadProvider/Pact.hs | 60 +++ 15 files changed, 469 insertions(+), 484 deletions(-) create mode 100644 src/Chainweb/PayloadProvider/Pact.hs diff --git a/chainweb.cabal b/chainweb.cabal index 991d913a63..51085aa84d 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -239,6 +239,7 @@ library , Chainweb.PayloadProvider.Minimal , Chainweb.PayloadProvider.Minimal.Payload , Chainweb.PayloadProvider.Minimal.PayloadDB + , Chainweb.PayloadProvider.Pact , Chainweb.PayloadProvider.Pact.Genesis , Chainweb.PayloadProvider.P2P , Chainweb.PayloadProvider.P2P.RestAPI @@ -456,6 +457,7 @@ library , pem >=0.2 , primitive >= 0.7.1.0 , random >= 1.2 + , resource-pool >= 0.4 , rocksdb-haskell-kadena >= 1.1.0 , safe-exceptions >= 0.1 , scheduler >= 1.4 diff --git a/src/Chainweb/BlockHeader.hs b/src/Chainweb/BlockHeader.hs index 81b56772ba..ca25caf6a3 100644 --- a/src/Chainweb/BlockHeader.hs +++ b/src/Chainweb/BlockHeader.hs @@ -11,12 +11,8 @@ module Chainweb.BlockHeader ( -- * Newtype wrappers for function parameters - I.ParentHeader(..) -, I.parentHeader -, I.parentHeaderHash -, I._rankedParentHash -, I.rankedParentHash -, I.ParentCreationTime(..) + I.Parent(..) +, I._Parent -- * Block Payload Hash , I.BlockPayloadHash diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index f1583b6bf5..2054bef8f0 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -23,6 +23,9 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DeriveFoldable #-} +{-# LANGUAGE DeriveTraversable #-} -- | -- Module: Chainweb.BlockHeader @@ -42,12 +45,8 @@ module Chainweb.BlockHeader.Internal ( -- * Newtype wrappers for function parameters - ParentHeader(..) -, parentHeader -, parentHeaderHash -, _rankedParentHash -, rankedParentHash -, ParentCreationTime(..) + Parent(..) +, _Parent -- * Block Payload Hash , BlockPayloadHash @@ -438,8 +437,8 @@ isLastInEpoch h = case effectiveWindow h of -- all chainweb version. Emergency DAs are enabled (and have occured) only on -- mainnet01 for cut heights smaller than 80,000. -- -slowEpoch :: ParentHeader -> BlockCreationTime -> Bool -slowEpoch (ParentHeader p) (BlockCreationTime ct) = actual > (expected * 5) +slowEpoch :: Parent BlockHeader -> BlockCreationTime -> Bool +slowEpoch (Parent p) (BlockCreationTime ct) = actual > (expected * 5) where EpochStartTime es = _blockEpochStart p v = _chainwebVersion p @@ -462,9 +461,9 @@ slowEpoch (ParentHeader p) (BlockCreationTime ct) = actual > (expected * 5) -- transition. -- powTarget - :: ParentHeader + :: Parent BlockHeader -- ^ parent header - -> HM.HashMap ChainId ParentHeader + -> HM.HashMap ChainId (Parent BlockHeader) -- ^ adjacent Parents -> BlockCreationTime -- ^ block creation time of new block @@ -473,7 +472,7 @@ powTarget -- -> HashTarget -- ^ POW target of new block -powTarget p@(ParentHeader ph) as bct = case effectiveWindow ph of +powTarget p@(Parent ph) as bct = case effectiveWindow ph of Nothing -> maxTarget Just w -- Emergency DA, legacy @@ -493,7 +492,7 @@ powTarget p@(ParentHeader ph) as bct = case effectiveWindow ph of | otherwise = avgTarget $ adjustForParent w <$> (p : HM.elems as) - adjustForParent w (ParentHeader a) + adjustForParent w (Parent a) = adjust (_versionBlockDelay ver) w (toEpochStart a .-. _blockEpochStart a) (_blockTarget a) toEpochStart = EpochStartTime . _bct . _blockCreationTime @@ -506,9 +505,9 @@ powTarget p@(ParentHeader ph) as bct = case effectiveWindow ph of -- | Compute the epoch start value for a new BlockHeader -- epochStart - :: ParentHeader + :: Parent BlockHeader -- ^ parent header - -> HM.HashMap ChainId ParentHeader + -> HM.HashMap ChainId (Parent BlockHeader) -- ^ Adjacent parents of the block. It is not checked whether the -- set of adjacent parents conforms with the current graph. -> BlockCreationTime @@ -518,7 +517,7 @@ epochStart -- -> EpochStartTime -- ^ epoch start time of new block -epochStart ph@(ParentHeader p) adj (BlockCreationTime bt) +epochStart ph@(Parent p) adj (BlockCreationTime bt) | Nothing <- effectiveWindow p = _blockEpochStart p -- A special case for starting a new devnet, to compensate the inaccurate @@ -601,9 +600,9 @@ epochStart ph@(ParentHeader p) adj (BlockCreationTime bt) -- The result is guaranteed to be non-empty -- adjCreationTimes = fmap (_blockCreationTime) - $ HM.insert cid (_parentHeader ph) + $ HM.insert cid (unwrapParent ph) $ HM.filter (not . isGenesisBlockHeader) - $ fmap _parentHeader adj + $ fmap unwrapParent adj parentIsFirstOnNewChain = _blockHeight p > 1 && _blockHeight p == genesisHeight ver cid + 1 @@ -612,40 +611,16 @@ epochStart ph@(ParentHeader p) adj (BlockCreationTime bt) -- -------------------------------------------------------------------------- -- -- Newtype wrappers for function parameters -newtype ParentCreationTime = ParentCreationTime - { _parentCreationTime :: BlockCreationTime } - deriving stock (Show, Eq, Ord, Generic) - deriving anyclass (NFData) - deriving newtype (ToJSON, FromJSON, Hashable, LeftTorsor) - -newtype ParentHeader = ParentHeader - { _parentHeader :: BlockHeader } - deriving (Show, Eq, Ord, Generic) - deriving anyclass (NFData) - -parentHeader :: Lens' ParentHeader BlockHeader -parentHeader = lens _parentHeader $ \_ hdr -> ParentHeader hdr - -parentHeaderHash :: Getter ParentHeader BlockHash -parentHeaderHash = parentHeader . blockHash - -_rankedParentHash :: ParentHeader -> RankedBlockHash -_rankedParentHash = _rankedBlockHash . _parentHeader - -rankedParentHash :: Getter ParentHeader RankedBlockHash -rankedParentHash = parentHeader . rankedBlockHash - -instance HasChainId ParentHeader where - _chainId = _chainId . _parentHeader - {-# INLINE _chainId #-} - -instance HasChainwebVersion ParentHeader where - _chainwebVersion = _chainwebVersion . _parentHeader - {-# INLINE _chainwebVersion #-} +newtype Parent h = Parent { unwrapParent :: h } + deriving stock (Show, Functor, Foldable, Traversable, Eq, Ord, Generic) + deriving newtype (NFData, ToJSON, FromJSON, Hashable, LeftTorsor) -instance HasChainGraph ParentHeader where - _chainGraph = _chainGraph . _parentHeader - {-# INLINE _chainGraph #-} +instance HasChainId h => HasChainId (Parent h) where + _chainId = _chainId . unwrapParent +instance HasChainwebVersion h => HasChainwebVersion (Parent h) where + _chainwebVersion = _chainwebVersion . unwrapParent +instance HasChainGraph h => HasChainGraph (Parent h) where + _chainGraph = _chainGraph . unwrapParent isGenesisBlockHeader :: BlockHeader -> Bool isGenesisBlockHeader b = @@ -1083,7 +1058,7 @@ instance IsBlockHeader BlockHeader where -- but might be worth it! -- newBlockHeader - :: HM.HashMap ChainId ParentHeader + :: HM.HashMap ChainId (Parent BlockHeader) -- ^ Adjacent parent hashes. The hash and the PoW target of these are -- needed for construction the new header. -> BlockPayloadHash @@ -1093,10 +1068,10 @@ newBlockHeader -- nonce is valid with respect to the target. -> BlockCreationTime -- ^ Creation time of the block. - -> ParentHeader + -> Parent BlockHeader -- ^ parent block header -> BlockHeader -newBlockHeader adj pay nonce t p@(ParentHeader b) = +newBlockHeader adj pay nonce t p@(Parent b) = fromLog @ChainwebMerkleHashAlgorithm $ newMerkleLog $ mkFeatureFlags :+: t @@ -1114,7 +1089,7 @@ newBlockHeader adj pay nonce t p@(ParentHeader b) = cid = _chainId p v = _chainwebVersion p target = powTarget p adj t - adjHashes = BlockHashRecord $ (_blockHash . _parentHeader) <$> adj + adjHashes = BlockHashRecord $ (_blockHash . unwrapParent) <$> adj -- -------------------------------------------------------------------------- -- -- TreeDBEntry instance @@ -1206,3 +1181,4 @@ rankedBlockPayloadHash :: Getter BlockHeader RankedBlockPayloadHash rankedBlockPayloadHash = to _rankedBlockPayloadHash {-# INLINE rankedBlockPayloadHash #-} +makePrisms ''Parent diff --git a/src/Chainweb/BlockHeader/Validation.hs b/src/Chainweb/BlockHeader/Validation.hs index 1dbb06d230..ea126d0545 100644 --- a/src/Chainweb/BlockHeader/Validation.hs +++ b/src/Chainweb/BlockHeader/Validation.hs @@ -177,8 +177,8 @@ _validatedHeaders (ValidatedHeaders hs) = hs -- data InvalidValidationParameters - = InvalidChainStepParameters ParentHeader BlockHeader - | InvalidWebStepParameters (HM.HashMap ChainId ParentHeader) ChainStep + = InvalidChainStepParameters (Parent BlockHeader) BlockHeader + | InvalidWebStepParameters (HM.HashMap ChainId (Parent BlockHeader)) ChainStep deriving (Show, Eq, Ord, Generic) instance Exception InvalidValidationParameters where @@ -193,10 +193,10 @@ instance Exception InvalidValidationParameters where -- -- NOTE: the constructor of this type is intentionally NOT exported. -- -data ChainStep = ChainStep ParentHeader BlockHeader +data ChainStep = ChainStep (Parent BlockHeader) BlockHeader deriving (Show, Eq, Ord, Generic) -_chainStepParent :: ChainStep -> ParentHeader +_chainStepParent :: ChainStep -> Parent BlockHeader _chainStepParent (ChainStep p _) = p {-# INLINE _chainStepParent #-} @@ -206,13 +206,13 @@ _chainStepHeader (ChainStep _ h) = h chainStep :: MonadThrow m - => ParentHeader + => Parent BlockHeader -- ^ Parent block header. The genesis header is considered its own parent. -> BlockHeader -- ^ Block header under scrutiny -> m ChainStep chainStep p b - | view blockParent b == view blockHash (_parentHeader p) + | view blockParent b == view blockHash (unwrapParent p) = return $ ChainStep p b | otherwise = throwM $ InvalidChainStepParameters p b @@ -225,12 +225,12 @@ chainStep p b -- -- NOTE: the constructor of this type is intentionally NOT exported. -- -data WebStep = WebStep (HM.HashMap ChainId ParentHeader) ChainStep +data WebStep = WebStep (HM.HashMap ChainId (Parent BlockHeader)) ChainStep deriving (Show, Eq, Ord, Generic) webStep :: MonadThrow m - => HM.HashMap ChainId ParentHeader + => HM.HashMap ChainId (Parent BlockHeader) -> ChainStep -> m WebStep webStep as hp@(ChainStep _ h) = WebStep @@ -242,10 +242,10 @@ webStep as hp@(ChainStep _ h) = WebStep f cid a = case HM.lookup cid as of Nothing -> throwM $ InvalidWebStepParameters as hp Just x - | view blockHash (_parentHeader x) == a -> return x + | view blockHash (unwrapParent x) == a -> return x | otherwise -> throwM $ InvalidWebStepParameters as hp -_webStepAdjs :: WebStep -> HM.HashMap ChainId ParentHeader +_webStepAdjs :: WebStep -> HM.HashMap ChainId (Parent BlockHeader) _webStepAdjs (WebStep as _) = as {-# INLINE _webStepAdjs #-} @@ -257,7 +257,7 @@ _webStepHeader :: WebStep -> BlockHeader _webStepHeader (WebStep _ p) = _chainStepHeader p {-# INLINE _webStepHeader #-} -_webStepParent :: WebStep -> ParentHeader +_webStepParent :: WebStep -> Parent BlockHeader _webStepParent (WebStep _ p) = _chainStepParent p {-# INLINE _webStepParent #-} @@ -267,8 +267,8 @@ _webStepParent (WebStep _ p) = _chainStepParent p -- | A data type that describes a failure of validating a block header. -- data ValidationFailure = ValidationFailure - { _validateFailureParent :: !(Maybe ParentHeader) - , _validateFailureAdjecents :: !(Maybe (HM.HashMap ChainId ParentHeader)) + { _validateFailureParent :: !(Maybe (Parent BlockHeader)) + , _validateFailureAdjecents :: !(Maybe (HM.HashMap ChainId (Parent BlockHeader))) , _validateFailureHeader :: !BlockHeader , _validationFailureFailures :: ![ValidationFailureType] } @@ -290,8 +290,8 @@ instance Show ValidationFailure where = T.unpack $ "Validation failure" <> ". Description: " <> T.unlines (map description ts) <> ". Header: " <> encodeToText (ObjectEncoded e) - <> maybe "" (\p' -> ". Parent: " <> encodeToText (ObjectEncoded $ _parentHeader p')) p - <> maybe "" (\as' -> ". Adjacents: " <> encodeToText (ObjectEncoded . _parentHeader <$> as')) as + <> maybe "" (\p' -> ". Parent: " <> encodeToText (ObjectEncoded $ unwrapParent p')) p + <> maybe "" (\as' -> ". Adjacents: " <> encodeToText (ObjectEncoded . unwrapParent <$> as')) as where description t = case t of MissingParent -> "Parent isn't in the database" @@ -545,9 +545,9 @@ validateBlockParentExists -> BlockHeader -> m (Either ValidationFailureType ChainStep) validateBlockParentExists lookupParent h - | isGenesisBlockHeader h = return $ Right $ ChainStep (ParentHeader h) h + | isGenesisBlockHeader h = return $ Right $ ChainStep (Parent h) h | otherwise = lookupParent (view blockParent h) >>= \case - (Just !p) -> return $ Right $ ChainStep (ParentHeader p) h + (Just !p) -> return $ Right $ ChainStep (Parent p) h Nothing -> return $ Left MissingParent -- | Validate that the parent and all adjacent parents exist with the given @@ -568,9 +568,9 @@ validateAllParentsExist lookupParent h = runExceptT $ WebStep v = _chainwebVersion h f c ph | genesisParentBlockHash v c == ph = return - $ ParentHeader $ genesisBlockHeader v c + $ Parent $ genesisBlockHeader v c | otherwise = lift (lookupParent $ ChainValue c ph) >>= \case - (Just !p) -> return $ ParentHeader p + (Just !p) -> return $ Parent p Nothing -> throwError MissingAdjacentParent -- -------------------------------------------------------------------------- -- @@ -726,23 +726,23 @@ prop_block_adjacent_chainIds b -- Single chain inductive properties prop_block_height :: ChainStep -> Bool -prop_block_height (ChainStep (ParentHeader p) b) +prop_block_height (ChainStep (Parent p) b) | isGenesisBlockHeader b = view blockHeight b == view blockHeight p | otherwise = view blockHeight b == view blockHeight p + 1 prop_block_chainwebVersion :: ChainStep -> Bool -prop_block_chainwebVersion (ChainStep (ParentHeader p) b) = +prop_block_chainwebVersion (ChainStep (Parent p) b) = view blockChainwebVersion p == view blockChainwebVersion b prop_block_weight :: ChainStep -> Bool -prop_block_weight (ChainStep (ParentHeader p) b) +prop_block_weight (ChainStep (Parent p) b) | isGenesisBlockHeader b = view blockWeight b == view blockWeight p | otherwise = view blockWeight b == expectedWeight where expectedWeight = int (targetToDifficulty (view blockTarget b)) + view blockWeight p prop_block_chainId :: ChainStep -> Bool -prop_block_chainId (ChainStep (ParentHeader p) b) +prop_block_chainId (ChainStep (Parent p) b) = view blockChainId p == view blockChainId b -- -------------------------------------------------------------------------- -- @@ -756,21 +756,21 @@ prop_block_epoch :: WebStep -> Bool prop_block_epoch (WebStep as (ChainStep p b)) | oldDaGuard (_chainwebVersion b) (_chainId b) (view blockHeight b) = view blockEpochStart b <= EpochStartTime (_bct $ view blockCreationTime b) - && view blockEpochStart (_parentHeader p) <= view blockEpochStart b + && view blockEpochStart (unwrapParent p) <= view blockEpochStart b && view blockEpochStart b == epochStart p as (view blockCreationTime b) | otherwise = view blockEpochStart b <= EpochStartTime (_bct $ view blockCreationTime b) && view blockEpochStart b == epochStart p as (view blockCreationTime b) prop_block_creationTime :: WebStep -> Bool -prop_block_creationTime (WebStep as (ChainStep (ParentHeader p) b)) +prop_block_creationTime (WebStep as (ChainStep (Parent p) b)) | isGenesisBlockHeader b = view blockCreationTime b == view blockCreationTime p | oldDaGuard (_chainwebVersion b) (_chainId b) (view blockHeight b) = view blockCreationTime b > view blockCreationTime p | otherwise = view blockCreationTime b > view blockCreationTime p - && all (\x -> view blockCreationTime b > view blockCreationTime (_parentHeader x)) as + && all (\x -> view blockCreationTime b > view blockCreationTime (unwrapParent x)) as -- | The chainId index of the adjacent parents of the header and the blocks -- in the webstep reference the same hashes and the chain Ids in of the @@ -787,7 +787,7 @@ prop_block_adjacent_parents (WebStep as (ChainStep _ b)) -- chainId indexes in web adjadent parent record references the -- genesis block parent hashes | otherwise - = adjsHashes == (view blockHash . _parentHeader <$> as) + = adjsHashes == (view blockHash . unwrapParent <$> as) -- chainId indexes in web adjadent parent record and web step are -- referencing the same hashes && iall (\cid h -> cid == _chainId h) as @@ -799,7 +799,7 @@ prop_block_adjacent_parents (WebStep as (ChainStep _ b)) prop_block_adjacent_parents_version :: WebStep -> Bool prop_block_adjacent_parents_version (WebStep as (ChainStep _ b)) - = all ((== v) . view blockChainwebVersion . _parentHeader) as + = all ((== v) . view blockChainwebVersion . unwrapParent) as where v = view blockChainwebVersion b diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index a19a9f2383..fc3c4964bd 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -76,7 +76,7 @@ instance Brief ChainId where brief = toText instance Brief BlockHash where brief = toTextShort instance Brief BlockPayloadHash where brief = toTextShort instance Brief BlockHeader where brief = brief . view blockHash -instance Brief ParentHeader where brief = brief . _parentHeader +instance Brief (Parent BlockHeader) where brief = brief . unwrapParent instance Brief BlockHashWithHeight where brief a = brief (_bhwhHeight a) <> ":" <> brief (_bhwhHash a) @@ -114,4 +114,3 @@ briefValue (Object o) = Object (briefValue <$> o) briefValue (Array a) = Array (briefValue <$> a) briefValue (String t) = String (toTextShort t) briefValue n = n - diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 511c144631..0b7bc1ffa3 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -126,7 +126,7 @@ data CutExtension = CutExtension -- -- This is overly restrictive, since the same cut extension can be -- valid for more than one cut. It's fine for now. - , _cutExtensionParent' :: !ParentHeader + , _cutExtensionParent' :: !(Parent BlockHeader) -- ^ The header onto which the new block is created. It is expected -- that this header is contained in the cut. , _cutExtensionAdjacentHashes' :: !BlockHashRecord @@ -145,12 +145,12 @@ cutExtensionCut = cutExtensionCut' -- | The header onto which the new block is created. -- -_cutExtensionParent :: CutExtension -> ParentHeader +_cutExtensionParent :: CutExtension -> Parent BlockHeader _cutExtensionParent = _cutExtensionParent' -- | The header onto which the new block is created. -- -cutExtensionParent :: Lens' CutExtension ParentHeader +cutExtensionParent :: Lens' CutExtension (Parent BlockHeader) cutExtensionParent = cutExtensionParent' _cutExtensionAdjacentHashes :: CutExtension -> BlockHashRecord @@ -211,7 +211,7 @@ getCutExtension c cid = do return CutExtension { _cutExtensionCut' = c - , _cutExtensionParent' = ParentHeader p + , _cutExtensionParent' = Parent p , _cutExtensionAdjacentHashes' = as } where @@ -368,7 +368,7 @@ getAdjacentParentHeaders => Applicative m => (ChainValue BlockHash -> m BlockHeader) -> CutExtension - -> m (HM.HashMap ChainId ParentHeader) + -> m (HM.HashMap ChainId (Parent BlockHeader)) getAdjacentParentHeaders hdb extension = itraverse select . _getBlockHashRecord @@ -377,7 +377,7 @@ getAdjacentParentHeaders hdb extension c = _cutExtensionCut extension select cid h = case c ^? ixg cid of - Just ch -> ParentHeader <$> if view blockHash ch == h + Just ch -> Parent <$> if view blockHash ch == h then pure ch else hdb (ChainValue cid h) @@ -390,9 +390,9 @@ getAdjacentParentHeaders hdb extension -- data WorkParents = WorkParents - { _workParent' :: !ParentHeader + { _workParent' :: !(Parent BlockHeader) -- ^ The header onto which the new block is created. - , _workAdjacentParents' :: !(HM.HashMap ChainId ParentHeader) + , _workAdjacentParents' :: !(HM.HashMap ChainId (Parent BlockHeader)) -- ^ The adjacent hashes for the new block. These must be at the same -- height as the parent and must be have a pairwise valid braiding among -- each other and with the parent. @@ -402,15 +402,15 @@ data WorkParents = WorkParents } deriving (Show, Eq, Generic) -_workParent :: WorkParents -> ParentHeader +_workParent :: WorkParents -> Parent BlockHeader _workParent = _workParent' -workParent :: Getter WorkParents ParentHeader +workParent :: Getter WorkParents (Parent BlockHeader) workParent = to _workParent _workParentsAdjacentHashes :: WorkParents -> BlockHashRecord _workParentsAdjacentHashes = BlockHashRecord - . fmap (view parentHeaderHash) + . fmap (view (_Parent . blockHash)) . _workAdjacentParents' workParentsAdjacentHashes :: Getter WorkParents BlockHashRecord diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 16f0c214f5..d65509b171 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -53,6 +53,7 @@ module Chainweb.Pact.Backend.Utils -- * SQLite runners , withSqliteDb , startSqliteDb + , startReadSqliteDb , stopSqliteDb , withSQLiteConnection , openSQLiteConnection @@ -64,7 +65,6 @@ module Chainweb.Pact.Backend.Utils ) where import Control.Exception (SomeAsyncException, evaluate) -import Control.Lens import Control.Monad import Control.Monad.Catch import Control.Monad.State.Strict @@ -100,7 +100,7 @@ import Chainweb.Utils import Chainweb.BlockHash import Chainweb.BlockHeight import Database.SQLite3.Direct hiding (open2) -import GHC.Stack (HasCallStack) +import GHC.Stack import qualified Data.ByteString.Short as SB import qualified Data.Vector as V import qualified Data.HashMap.Strict as HashMap @@ -109,9 +109,9 @@ import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString as BS import Chainweb.Pact.Backend.Types import Data.HashSet (HashSet) -import Chainweb.BlockHeader import qualified Data.HashSet as HashSet import Data.Text (Text) +import Chainweb.BlockHeader -- -------------------------------------------------------------------------- -- -- SQ3.Utf8 Encodings @@ -265,6 +265,18 @@ startSqliteDb cid logger dbDir doResetDb = do resetDb = removeDirectoryRecursive dbDir sqliteFile = dbDir chainDbFileName cid +startReadSqliteDb + :: Logger logger + => ChainId + -> logger + -> FilePath + -> IO SQLiteEnv +startReadSqliteDb cid logger dbDir = do + logFunctionText logger Debug $ "(read-only) opening sqlitedb named " <> T.pack sqliteFile + openSQLiteConnection sqliteFile chainwebPragmas + where + sqliteFile = dbDir chainDbFileName cid + chainDbFileName :: ChainId -> FilePath chainDbFileName cid = fold [ "pact-v1-chain-" @@ -366,13 +378,13 @@ doLookupSuccessful db curHeight hashes = do return $! T3 txhash' (fromIntegral blockheight) blockhash' go _ = fail "impossible" -getEndTxId :: Text -> SQLiteEnv -> Maybe ParentHeader -> IO (Historical Pact4.TxId) +getEndTxId :: Text -> SQLiteEnv -> Maybe (Parent RankedBlockHash) -> IO (Historical Pact4.TxId) getEndTxId msg sql pc = case pc of Nothing -> return (Historical 0) - Just (ParentHeader ph) -> getEndTxId' msg sql (view blockHeight ph) (view blockHash ph) + Just ph -> getEndTxId' msg sql (_rankedBlockHashHeight <$> ph) (_rankedBlockHashHash <$> ph) -getEndTxId' :: Text -> SQLiteEnv -> BlockHeight -> BlockHash -> IO (Historical Pact4.TxId) -getEndTxId' msg sql bh bhsh = do +getEndTxId' :: Text -> SQLiteEnv -> Parent BlockHeight -> Parent BlockHash -> IO (Historical Pact4.TxId) +getEndTxId' msg sql (Parent bh) (Parent bhsh) = do r <- Pact4.qry sql "SELECT endingtxid FROM BlockHistory WHERE blockheight = ? and hash = ?;" [ Pact4.SInt $ fromIntegral bh @@ -389,12 +401,12 @@ getEndTxId' msg sql bh bhsh = do -- Returns the ending txid of the input parent header. rewindDbTo :: SQLiteEnv - -> Maybe ParentHeader + -> Maybe (Parent RankedBlockHash) -> IO Pact4.TxId rewindDbTo db Nothing = do rewindDbToGenesis db return 0 -rewindDbTo db mh@(Just (ParentHeader ph)) = do +rewindDbTo db mh@(Just ph) = do !historicalEndingTxId <- getEndTxId "rewindDbToBlock" db mh endingTxId <- case historicalEndingTxId of NoHistory -> @@ -404,7 +416,7 @@ rewindDbTo db mh@(Just (ParentHeader ph)) = do <> sshow ph Historical endingTxId -> return endingTxId - rewindDbToBlock db (view blockHeight ph) endingTxId + rewindDbToBlock db (_rankedBlockHashHeight <$> ph) endingTxId return endingTxId -- rewind before genesis, delete all user tables and all rows in all tables @@ -430,10 +442,10 @@ rewindDbToGenesis db = do -- block. rewindDbToBlock :: Database - -> BlockHeight + -> Parent BlockHeight -> Pact4.TxId -> IO () -rewindDbToBlock db bh endingTxId = do +rewindDbToBlock db (Parent bh) endingTxId = do tableMaintenanceRowsVersionedSystemTables droppedtbls <- dropTablesAtRewind vacuumTablesAtRewind droppedtbls diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 15ab1fda94..83a6786228 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -14,6 +14,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedRecordDot #-} -- | -- Module: Chainweb.Pact.PactService @@ -37,7 +38,6 @@ module Chainweb.Pact.PactService , execHistoricalLookup , execReadOnlyReplay , execSyncToBlock - , runPactService , withPactService , execNewGenesisBlock ) where @@ -101,7 +101,6 @@ import Chainweb.Miner.Pact import Chainweb.Pact.PactService.Pact4.ExecBlock import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact4 -import Chainweb.Pact.Service.PactQueue (PactQueue, getNextRequest) import Chainweb.Pact.Types import Chainweb.Pact4.SPV qualified as Pact4 import Chainweb.Pact5.SPV qualified as Pact5 @@ -146,27 +145,14 @@ import qualified Pact.Core.StableEncoding as Pact5 import Control.Monad.Cont (evalContT) import qualified Data.List.NonEmpty as NonEmpty import Chainweb.PayloadProvider.Pact.Genesis (genesisPayload) +import Chainweb.PayloadProvider +import Data.Function +import Chainweb.Storage.Table +import qualified Chainweb.Storage.Table.Map as MapTable +import Chainweb.PayloadProvider.P2P +import P2P.TaskQueue (Priority(..)) -runPactService - :: Logger logger - => CanReadablePayloadCas tbl - => ChainwebVersion - -> ChainId - -> logger - -> Maybe (Counter "txFailures") - -> PactQueue - -> MemPoolAccess - -> BlockHeaderDb - -> PayloadDb tbl - -> SQLiteEnv - -> PactServiceConfig - -> IO () -runPactService ver cid chainwebLogger txFailuresCounter reqQ mempoolAccess bhDb pdb sqlenv config = - void $ withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config $ do - initialPayloadState ver cid - serviceRequests mempoolAccess reqQ - withPactService :: (Logger logger, CanReadablePayloadCas tbl) => ChainwebVersion @@ -181,10 +167,12 @@ withPactService -> IO (T2 a PactServiceState) withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config act = do Checkpointer.withCheckpointerResources checkpointerLogger (_pactModuleCacheLimit config) sqlenv (_pactPersistIntraBlockWrites config) ver cid $ \checkpointer -> do + candidatePdb <- MapTable.emptyTable let !pse = PactServiceEnv { _psMempoolAccess = Nothing , _psCheckpointer = checkpointer , _psPdb = pdb + , _psCandidatePdb = candidatePdb , _psBlockHeaderDb = bhDb , _psReorgLimit = _pactReorgLimit config , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout config @@ -287,8 +275,8 @@ initializeCoinContract v cid pwo = do logWarnPact "initializeCoinContract: Starting from genesis." validateGenesis where - validateGenesis = void $! - execValidateBlock mempty genesisHeader (CheckablePayloadWithOutputs pwo) + validateGenesis = void $! undefined + -- execValidateBlock mempty genesisHeader (CheckablePayloadWithOutputs pwo) genesisHeader :: BlockHeader genesisHeader = genesisBlockHeader v cid @@ -312,173 +300,6 @@ lookupBlockHeader bhash ctx = do throwM $ BlockHeaderLookupFailure $ "failed lookup of parent header in " <> ctx <> ": " <> sshow e --- | Loop forever, serving Pact execution requests and reponses from the queues -serviceRequests - :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) - => MemPoolAccess - -> PactQueue - -> PactServiceM logger tbl () -serviceRequests memPoolAccess reqQ = go - where - go :: PactServiceM logger tbl () - go = do - PactServiceEnv{_psLogger} <- ask - logDebugPact "serviceRequests: wait" - SubmittedRequestMsg msg statusRef <- liftIO $ getNextRequest reqQ - requestId <- liftIO $ UUID.toText <$> UUID.nextRandom - let - logFn :: LogFunction - logFn = logFunction $ addLabel ("pact-request-id", requestId) _psLogger - logDebugPact $ "serviceRequests: " <> sshow msg - case msg of - CloseMsg -> - tryOne "execClose" statusRef $ return () - LocalMsg (LocalReq localRequest preflight sigVerify rewindDepth) -> do - trace logFn "Chainweb.Pact.PactService.execLocal" () 0 $ - tryOne "execLocal" statusRef $ - execLocal localRequest preflight sigVerify rewindDepth - go - NewBlockMsg NewBlockReq {..} -> do - trace logFn "Chainweb.Pact.PactService.execNewBlock" - () 1 $ - tryOne "execNewBlock" statusRef $ - execNewBlock memPoolAccess _newBlockFill _newBlockParent - go - ContinueBlockMsg (ContinueBlockReq bip) -> do - trace logFn "Chainweb.Pact.PactService.execContinueBlock" - () 1 $ - tryOne "execContinueBlock" statusRef $ - execContinueBlock memPoolAccess bip - go - ValidateBlockMsg ValidateBlockReq {..} -> do - tryOne "execValidateBlock" statusRef $ - fmap fst $ trace' logFn "Chainweb.Pact.PactService.execValidateBlock" - (\_ -> _valBlockHeader) - (\(_, g) -> fromIntegral g) - (execValidateBlock memPoolAccess _valBlockHeader _valCheckablePayload) - go - LookupPactTxsMsg (LookupPactTxsReq confDepth txHashes) -> do - trace logFn "Chainweb.Pact.PactService.execLookupPactTxs" () - (length txHashes) $ - tryOne "execLookupPactTxs" statusRef $ - execLookupPactTxs confDepth txHashes - go - PreInsertCheckMsg (PreInsertCheckReq txs) -> do - trace logFn "Chainweb.Pact.PactService.execPreInsertCheckReq" () - (length txs) $ - tryOne "execPreInsertCheckReq" statusRef $ - execPreInsertCheckReq txs - go - BlockTxHistoryMsg (BlockTxHistoryReq bh d) -> do - trace logFn "Chainweb.Pact.PactService.execBlockTxHistory" bh 1 $ - tryOne "execBlockTxHistory" statusRef $ - execBlockTxHistory bh d - go - HistoricalLookupMsg (HistoricalLookupReq bh d k) -> do - trace logFn "Chainweb.Pact.PactService.execHistoricalLookup" bh 1 $ - tryOne "execHistoricalLookup" statusRef $ - execHistoricalLookup bh d k - go - SyncToBlockMsg SyncToBlockReq {..} -> do - trace logFn "Chainweb.Pact.PactService.execSyncToBlock" _syncToBlockHeader 1 $ - tryOne "syncToBlockBlock" statusRef $ - execSyncToBlock _syncToBlockHeader - go - ReadOnlyReplayMsg ReadOnlyReplayReq {..} -> do - trace logFn "Chainweb.Pact.PactService.execReadOnlyReplay" (_readOnlyReplayLowerBound, _readOnlyReplayUpperBound) 1 $ - tryOne "readOnlyReplayBlock" statusRef $ - execReadOnlyReplay _readOnlyReplayLowerBound _readOnlyReplayUpperBound - go - - tryOne - :: forall a. Text - -> TVar (RequestStatus a) - -> PactServiceM logger tbl a - -> PactServiceM logger tbl () - tryOne which statusRef act = - evalPactOnThread - `catches` - [ Handler $ \(e :: SomeException) -> do - logErrorPact $ mconcat - [ "Received exception running pact service (" - , which - , "): " - , sshow e - ] - liftIO $ throwIO e - ] - where - -- here we start a thread to service the request - evalPactOnThread :: PactServiceM logger tbl () - evalPactOnThread = do - maybeException <- withPactState $ \run -> do - goLock <- newEmptyMVar - finishedLock <- newEmptyMVar - -- fork a thread to service the request - bracket - (mask_ $ forkIOWithUnmask $ \restore -> - -- We wrap this whole block in `tryAsync` because we - -- want to ignore `RequestCancelled` exceptions that - -- occur while we are waiting on `takeMVar goLock`. - -- - -- Otherwise we get logs like `chainweb-node: - -- RequestCancelled`. - -- - -- We don't actually care about whether or not - -- `RequestCancelled` was encountered, so we just `void` - -- it. - void $ tryAsync @_ @RequestCancelled $ flip finally (tryPutMVar finishedLock ()) $ do - -- wait until we've been told to start. - -- we don't want to start if the request was cancelled - -- already - takeMVar goLock - - -- run and report the answer. - restore (tryAny (run act)) >>= \case - Left ex -> atomically $ writeTVar statusRef (RequestFailed ex) - Right r -> atomically $ writeTVar statusRef (RequestDone r) - ) - -- if Pact itself is killed, kill the request thread too. - (\tid -> throwTo tid RequestCancelled >> takeMVar finishedLock) - (\_tid -> do - -- check first if the request has been cancelled before - -- starting work on it - beforeStarting <- atomically $ do - readTVar statusRef >>= \case - RequestInProgress -> internalError "request in progress before starting" - RequestDone _ -> internalError "request finished before starting" - RequestFailed e -> return (Left e) - RequestNotStarted -> do - writeTVar statusRef RequestInProgress - return (Right ()) - case beforeStarting of - -- the request has already been cancelled, don't - -- start work on it. - Left ex -> return (Left ex) - Right () -> do - -- let the request thread start working - putMVar goLock () - -- wait until the request thread has finished - atomically $ readTVar statusRef >>= \case - RequestInProgress -> retry - RequestDone _ -> return (Right ()) - RequestFailed e -> return (Left e) - RequestNotStarted -> internalError "request not started after starting" - ) - case maybeException of - Left (fromException -> Just AsyncCancelled) -> do - logDebugPact "Pact action was cancelled" - Left (fromException -> Just ThreadKilled) -> do - logWarnPact "Pact action thread was killed" - Left (exn :: SomeException) -> do - logErrorPact $ mconcat - [ "Received exception running pact service (" - , which - , "): " - , sshow exn - ] - Right () -> return () - execNewBlock :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) => MemPoolAccess @@ -991,127 +812,207 @@ execSyncToBlock targetHeader = pactLabel "execSyncToBlock" $ do execValidateBlock :: (CanReadablePayloadCas tbl, Logger logger) => MemPoolAccess - -> BlockHeader - -> CheckablePayload - -> PactServiceM logger tbl (PayloadWithOutputs, Pact4.Gas) -execValidateBlock memPoolAccess headerToValidate payloadToValidate = pactLabel "execValidateBlock" $ do + -> Maybe Hints + -> ForkInfo + -> PactServiceM logger tbl SyncState +execValidateBlock memPoolAccess hints forkInfo = pactLabel "execValidateBlock" $ do bhdb <- view psBlockHeaderDb payloadDb <- view psPdb v <- view chainwebVersion cid <- view chainId RewindLimit reorgLimit <- view psReorgLimit - -- The parent block header must be available in the block header database. - parentOfHeaderToValidate <- getTarget - - -- Add block-hash to the logs if presented - let logBlockHash = - localLabelPact ("block-hash", blockHashToText (view blockParent headerToValidate)) - - logBlockHash $ do - currHeader <- Checkpointer.findLatestValidBlockHeader' - -- find the common ancestor of the new block and our current block - commonAncestor <- liftIO $ case (currHeader, parentOfHeaderToValidate) of - (Just currHeader', Just ph) -> - Just <$> forkEntry bhdb currHeader' (_parentHeader ph) - _ -> - return Nothing - -- check that we don't exceed the rewind limit. for the purpose - -- of this check, the genesis block and the genesis parent - -- have the same height. - let !currHeight = maybe (genesisHeight v cid) (view blockHeight) currHeader - let !ancestorHeight = maybe (genesisHeight v cid) (view blockHeight) commonAncestor - let !rewindLimitSatisfied = ancestorHeight + fromIntegral reorgLimit >= currHeight - unless rewindLimitSatisfied $ - throwM $ RewindLimitExceeded - (RewindLimit reorgLimit) - currHeader - commonAncestor - -- get all blocks on the fork we're going to play, up to the parent - -- of the block we're validating - let withForkBlockStream kont = case parentOfHeaderToValidate of - Nothing -> - -- we're validating a genesis block, so there are no fork blocks to speak of. - kont (pure ()) - Just (ParentHeader parentHeaderOfHeaderToValidate) -> - let forkStartHeight = maybe (genesisHeight v cid) (succ . view blockHeight) commonAncestor - in getBranchIncreasing bhdb parentHeaderOfHeaderToValidate (fromIntegral forkStartHeight) kont - - ((), results) <- - withPactState $ \runPact -> - withForkBlockStream $ \forkBlockHeaders -> do - - -- given a header for a block in the fork, fetch its payload - -- and run its transactions, validating its hashes - let runForkBlockHeaders = forkBlockHeaders & Stream.map (\forkBh -> do - payload <- liftIO $ lookupPayloadWithHeight payloadDb (Just $ view blockHeight forkBh) (view blockPayloadHash forkBh) >>= \case - Nothing -> internalError - $ "execValidateBlock: lookup of payload failed" - <> ". BlockPayloadHash: " <> encodeToText (view blockPayloadHash forkBh) - <> ". Block: " <> encodeToText (ObjectEncoded forkBh) - Just x -> return $ payloadWithOutputsToPayloadData x - SomeBlockM $ Pair - (void $ Pact4.execBlock forkBh (CheckablePayload payload)) - (void $ Pact5.execExistingBlock forkBh (CheckablePayload payload)) - return ([], forkBh) - ) - - -- run the new block, the one we're validating, and - -- validate its hashes - let runThisBlock = Stream.yield $ SomeBlockM $ Pair - (do - !output <- Pact4.execBlock headerToValidate payloadToValidate - return ([output], headerToValidate) - ) - (do - !(gas, pwo) <- Pact5.execExistingBlock headerToValidate payloadToValidate - return ([(fromIntegral (Pact5._gas gas), pwo)], headerToValidate) - ) - - -- here we rewind to the common ancestor block, run the - -- transactions in all of its child blocks until the parent - -- of the block we're validating, then run the block we're - -- validating. - runPact $ Checkpointer.restoreAndSave - (ParentHeader <$> commonAncestor) - (runForkBlockHeaders >> runThisBlock) - let logRewind = - -- we consider a fork of height more than 3 to be notable. - if ancestorHeight + 3 < currHeight - then logWarnPact - else logDebugPact - logRewind $ - "execValidateBlock: rewound " <> sshow (currHeight - ancestorHeight) <> " blocks" - (totalGasUsed, result) <- case results of - [r] -> return r - _ -> internalError "execValidateBlock: wrong number of block results returned from _cpRestoreAndSave." - - -- update mempool - -- - -- Using the parent isn't optimal, since it doesn't delete the txs of - -- `currHeader` from the set of pending tx. The reason for this is that the - -- implementation 'mpaProcessFork' uses the chain database and at this point - -- 'currHeader' is generally not yet available in the database. It would be - -- possible to extract the txs from the result and remove them from the set - -- of pending txs. However, that would add extra complexity and at little - -- gain. - -- - case parentOfHeaderToValidate of - Nothing -> return () - Just (ParentHeader p) -> liftIO $ do - mpaProcessFork memPoolAccess p - mpaSetLastHeader memPoolAccess p - - return (result, totalGasUsed) + currHeader <- Checkpointer.findLatestValidBlockHeader' >>= \case + -- TODO: deal with genesis + Nothing -> error "no latest header... what do we do?" + Just h -> return h + + let findForkPoint (tip:chain) = do + known <- Checkpointer.lookupBlock (_evaluationCtxRankedParentHash tip) + if known + then return (Just (tip, chain)) + else findForkPoint chain + findForkPoint [] = return Nothing + + atTarget <- Checkpointer.lookupBlock (_forkInfoBaseRankedBlockHash forkInfo) + if atTarget + then return $ forkInfo._forkInfoTargetState._consensusStateLatest + else do + let wholeChainTopToBottom = + reverse forkInfo._forkInfoTrace + findForkPoint wholeChainTopToBottom >>= \case + Nothing -> + return $ syncStateOfBlockHeader currHeader + Just (forkPoint, _) | _evaluationCtxRankedParentHash forkPoint == _rankedBlockHash currHeader -> + -- we're already done, find the payload with outputs and return + return forkInfo._forkInfoTargetState._consensusStateLatest + Just (forkPoint, reverse -> forkChainBottomToTop) -> do -- it + pdb <- view psPdb + unknowns' <- liftIO $ dropWhile (isJust . snd) . zip forkChainBottomToTop + <$> tableLookupBatch pdb (_evaluationCtxRankedPayloadHash <$> forkChainBottomToTop) + + -- assert db invariant + unless (all (isNothing . snd) unknowns') $ + error "Chainweb.PayloadProviders.Pact.syncToBlock: detected corrupted payload database" + + let unknowns = fst <$> unknowns' + + -- logDebug_ $ "unknown blocks in context: " <> sshow (length unknowns) + + -- fetch all unkown payloads + -- + -- FIXME do the right thing here. Ideally, fetch all + -- unknowns in batches without redundant local lookups. Then + -- validate all payloads together before sending them to the + -- EVM and inserting them into the DB. + -- + + plds <- forM unknowns $ \ctx -> do + pld <- getPayloadForContext hints ctx + return (ctx, pld) + + withPactState $ \runPact -> do + let runForkBlockHeaders = plds + & traverse Stream.yield + & Stream.map (\(ctx, payload) -> do + return $ SomeBlockM $ Pair + (void $ Pact4.execBlock forkBh (CheckablePayload payload)) + (void $ Pact5.execExistingBlock forkBh (CheckablePayload payload)) + ) + undefined + + undefined + + -- -- The parent block header must be available in the block header database. + -- parentOfHeaderToValidate <- getTarget + + + -- -- Add block-hash to the logs if presented + -- let logBlockHash = + -- localLabelPact ("block-hash", blockHashToText (view blockParent headerToValidate)) + + -- logBlockHash $ do + -- -- find the common ancestor of the new block and our current block + -- commonAncestor <- liftIO $ case (currHeader, parentOfHeaderToValidate) of + -- (Just currHeader', Just ph) -> + -- Just <$> forkEntry bhdb currHeader' (_parentHeader ph) + -- _ -> + -- return Nothing + -- -- check that we don't exceed the rewind limit. for the purpose + -- -- of this check, the genesis block and the genesis parent + -- -- have the same height. + -- let !currHeight = maybe (genesisHeight v cid) (view blockHeight) currHeader + -- let !ancestorHeight = maybe (genesisHeight v cid) (view blockHeight) commonAncestor + -- let !rewindLimitSatisfied = ancestorHeight + fromIntegral reorgLimit >= currHeight + -- unless rewindLimitSatisfied $ + -- throwM $ RewindLimitExceeded + -- (RewindLimit reorgLimit) + -- currHeader + -- commonAncestor + -- -- get all blocks on the fork we're going to play, up to the parent + -- -- of the block we're validating + -- let withForkBlockStream kont = case parentOfHeaderToValidate of + -- Nothing -> + -- -- we're validating a genesis block, so there are no fork blocks to speak of. + -- kont (pure ()) + -- Just (ParentHeader parentHeaderOfHeaderToValidate) -> + -- let forkStartHeight = maybe (genesisHeight v cid) (succ . view blockHeight) commonAncestor + -- in getBranchIncreasing bhdb parentHeaderOfHeaderToValidate (fromIntegral forkStartHeight) kont + + -- ((), results) <- + -- withPactState $ \runPact -> + -- withForkBlockStream $ \forkBlockHeaders -> do + + -- -- given a header for a block in the fork, fetch its payload + -- -- and run its transactions, validating its hashes + -- let runForkBlockHeaders = forkBlockHeaders & Stream.map (\forkBh -> do + -- payload <- liftIO $ lookupPayloadWithHeight payloadDb (Just $ view blockHeight forkBh) (view blockPayloadHash forkBh) >>= \case + -- Nothing -> internalError + -- $ "execValidateBlock: lookup of payload failed" + -- <> ". BlockPayloadHash: " <> encodeToText (view blockPayloadHash forkBh) + -- <> ". Block: " <> encodeToText (ObjectEncoded forkBh) + -- Just x -> return $ payloadWithOutputsToPayloadData x + -- SomeBlockM $ Pair + -- (void $ Pact4.execBlock forkBh (CheckablePayload payload)) + -- (void $ Pact5.execExistingBlock forkBh (CheckablePayload payload)) + -- return ([], forkBh) + -- ) + + -- -- run the new block, the one we're validating, and + -- -- validate its hashes + -- let runThisBlock = Stream.yield $ SomeBlockM $ Pair + -- (do + -- !output <- Pact4.execBlock headerToValidate payloadToValidate + -- return ([output], headerToValidate) + -- ) + -- (do + -- !(gas, pwo) <- Pact5.execExistingBlock headerToValidate payloadToValidate + -- return ([(fromIntegral (Pact5._gas gas), pwo)], headerToValidate) + -- ) + + -- -- here we rewind to the common ancestor block, run the + -- -- transactions in all of its child blocks until the parent + -- -- of the block we're validating, then run the block we're + -- -- validating. + -- runPact $ Checkpointer.restoreAndSave + -- (ParentHeader <$> commonAncestor) + -- (runForkBlockHeaders >> runThisBlock) + -- let logRewind = + -- -- we consider a fork of height more than 3 to be notable. + -- if ancestorHeight + 3 < currHeight + -- then logWarnPact + -- else logDebugPact + -- logRewind $ + -- "execValidateBlock: rewound " <> sshow (currHeight - ancestorHeight) <> " blocks" + -- (totalGasUsed, result) <- case results of + -- [r] -> return r + -- _ -> internalError "execValidateBlock: wrong number of block results returned from _cpRestoreAndSave." + + -- -- update mempool + -- -- + -- -- Using the parent isn't optimal, since it doesn't delete the txs of + -- -- `currHeader` from the set of pending tx. The reason for this is that the + -- -- implementation 'mpaProcessFork' uses the chain database and at this point + -- -- 'currHeader' is generally not yet available in the database. It would be + -- -- possible to extract the txs from the result and remove them from the set + -- -- of pending txs. However, that would add extra complexity and at little + -- -- gain. + -- -- + -- case parentOfHeaderToValidate of + -- Nothing -> return () + -- Just (ParentHeader p) -> liftIO $ do + -- mpaProcessFork memPoolAccess p + -- mpaSetLastHeader memPoolAccess p + + -- return (result, totalGasUsed) + + +getPayloadForContext + :: Logger logger + => Maybe Hints + -> EvaluationCtx + -> PactServiceM logger tbl PayloadData +getPayloadForContext h ctx = do + pdb <- view psPdb + candPdb <- view psCandidatePdb + mapM_ (insertPayloadData candPdb) (_evaluationCtxPayloadData ctx) + + pld <- liftIO $ getPayload + pdb + candPdb + (Priority $ negate $ int $ _evaluationCtxParentHeight ctx) + (_hintsOrigin <$> h) + (_evaluationCtxRankedPayloadHash ctx) + liftIO $ tableInsert candPdb rh pld + return pld + where + rh = _evaluationCtxRankedPayloadHash ctx + + insertPayloadData candPdb (EncodedPayloadData epld) = case decodePayloadData epld of + Right pld -> liftIO $ tableInsert candPdb rh pld + Left e -> do + logWarnPact $ "failed to decode encoded payload from evaluation ctx: " <> sshow e - where - getTarget - | isGenesisBlockHeader headerToValidate = return Nothing - | otherwise = Just . ParentHeader - <$> lookupBlockHeader (view blockParent headerToValidate) "execValidateBlock" - -- It is up to the user of pact service to guaranteed that this - -- succeeds. If this fails it usually means that the block - -- header database is corrupted. execBlockTxHistory :: Logger logger diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 1fe11e3690..a7322f6e48 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -38,6 +38,7 @@ module Chainweb.Pact.PactService.Checkpointer , SomeBlockM(..) , getEarliestBlock , getLatestBlock + , lookupBlock , lookupHistorical , getBlockHistory , Internal.withCheckpointerResources @@ -391,6 +392,11 @@ getLatestBlock = do cp <- view psCheckpointer liftIO $ Internal.getLatestBlock cp.cpSql +lookupBlock :: RankedBlockHash -> PactServiceM logger tbl Bool +lookupBlock b = do + cp <- view psCheckpointer + liftIO $ Internal.lookupBlock cp.cpSql b + lookupHistorical :: BlockHeader -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info diff --git a/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs b/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs index 06cfd9983f..069e8f3fb8 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs @@ -96,6 +96,7 @@ import qualified Pact.Core.Builtin as Pact5 import qualified Pact.Core.Evaluate as Pact5 import qualified Pact.Core.Names as Pact5 import qualified Chainweb.Pact.Backend.Utils as PactDb +import Chainweb.PayloadProvider withCheckpointerResources :: (Logger logger) @@ -150,14 +151,14 @@ readFrom :: forall logger pv a . (Logger logger) => Checkpointer logger - -> Maybe ParentHeader + -> Maybe (Parent RankedBlockHash) -> PactVersionT pv -> (PactDbFor logger pv -> BlockHandle pv -> IO a) -> IO (Historical a) readFrom res maybeParent pactVersion doRead = do let currentHeight = case maybeParent of Nothing -> genesisHeight res.cpCwVersion res.cpChainId - Just parent -> succ . view blockHeight . _parentHeader $ parent + Just parent -> succ $ _rankedBlockHashHeight $ unwrapParent parent modifyMVar res.cpModuleCacheVar $ \sharedModuleCache -> do bracket @@ -166,6 +167,8 @@ readFrom res maybeParent pactVersion doRead = do -- NB it's important to do this *after* you start the savepoint (and thus -- the db transaction) to make sure that the latestHeader check is up to date. latestHeader <- getLatestBlock res.cpSql + -- is the parent the latest header, i.e., can we get away without rewinding? + let parentIsLatestHeader = latestHeader == maybeParent h <- case pactVersion of Pact4T | pact5 res.cpCwVersion res.cpChainId currentHeight -> internalError $ @@ -176,12 +179,6 @@ readFrom res maybeParent pactVersion doRead = do (Pact4.initBlockState defaultModuleCacheLimit startTxId) { Pact4._bsModuleCache = sharedModuleCache } let - -- is the parent the latest header, i.e., can we get away without rewinding? - parentIsLatestHeader = case (latestHeader, maybeParent) of - (Nothing, Nothing) -> True - (Just (_, latestHash), Just (ParentHeader ph)) -> - view blockHash ph == latestHash - _ -> False mkBlockDbEnv db = Pact4.CurrentBlockDbEnv { Pact4._cpPactDbEnv = PactDbEnv db newDbEnv , Pact4._cpRegisterProcessedTx = \hash -> @@ -200,12 +197,6 @@ readFrom res maybeParent pactVersion doRead = do | pact5 res.cpCwVersion res.cpChainId currentHeight -> PactDb.getEndTxId "doReadFrom" res.cpSql maybeParent >>= traverse \startTxId -> do let - -- is the parent the latest header, i.e., can we get away without rewinding? - parentIsLatestHeader = case (latestHeader, maybeParent) of - (Nothing, Nothing) -> True - (Just (_, latestHash), Just (ParentHeader ph)) -> - view blockHash ph == latestHash - _ -> False blockHandlerEnv = Pact5.BlockHandlerEnv { Pact5._blockHandlerDb = res.cpSql , Pact5._blockHandlerLogger = res.cpLogger @@ -229,7 +220,7 @@ readFrom res maybeParent pactVersion doRead = do -- the special case where one doesn't want to extend the chain, just rewind it. -rewindTo :: Logger logger => Checkpointer logger -> Maybe ParentHeader -> IO () +rewindTo :: Logger logger => Checkpointer logger -> Maybe (Parent RankedBlockHash) -> IO () rewindTo cp ancestor = void $ restoreAndSave cp ancestor (pure () :: Stream (Of (RunnableBlock logger ())) IO ()) @@ -265,7 +256,7 @@ restoreAndSave :: forall logger r q. (Logger logger, Monoid q, HasCallStack) => Checkpointer logger - -> Maybe ParentHeader + -> Maybe (Parent RankedBlockHash) -> Stream (Of (RunnableBlock logger q)) IO r -> IO (r, q) restoreAndSave res rewindParent blocks = do @@ -283,13 +274,13 @@ restoreAndSave res rewindParent blocks = do extend :: TxId -> DbCache PersistModuleData - -> IO (Of (q, Maybe ParentHeader, TxId, DbCache PersistModuleData) r) + -> IO (Of (q, Maybe RankedBlockHash, TxId, DbCache PersistModuleData) r) extend startTxId startModuleCache = Streaming.foldM (\(m, maybeParent, txid, moduleCache) block -> do let !bh = case maybeParent of Nothing -> genesisHeight res.cpCwVersion res.cpChainId - Just parent -> (succ . view blockHeight . _parentHeader) parent + Just parent -> _evaluationCtxCurrentHeight parent case block of Pact4RunnableBlock runBlock | pact5 res.cpCwVersion res.cpChainId bh -> @@ -332,18 +323,18 @@ restoreAndSave res rewindParent blocks = do -- of the previous block case maybeParent of Nothing - | genesisHeight res.cpCwVersion res.cpChainId /= view blockHeight newBh -> internalError + | genesisHeight res.cpCwVersion res.cpChainId /= _evaluationCtxCurrentHeight newBh -> internalError "doRestoreAndSave: block with no parent, genesis block, should have genesis height but doesn't," - Just (ParentHeader ph) - | succ (view blockHeight ph) /= view blockHeight newBh -> internalError $ + Just ph + | _evaluationCtxCurrentHeight ph /= _evaluationCtxParentHeight newBh -> internalError $ "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " - <> sshow (view blockHeight ph) <> ", child height " <> sshow (view blockHeight newBh) + <> sshow (_evaluationCtxParentHeight ph) <> ", child height " <> sshow (_evaluationCtxParentHeight newBh) _ -> return () -- persist any changes to the database Pact4.commitBlockStateToDatabase res.cpSql - (view blockHash newBh) (view blockHeight newBh) + (_evaluationCtxParentHash newBh) (_evaluationCtxParentHeight newBh) (BlockHandle (Pact4._bsTxId nextState) (Pact4._bsPendingBlock nextState)) - return (m'', Just (ParentHeader newBh), nextTxId, nextModuleCache) + return (m'', Just newBh, nextTxId, nextModuleCache) Pact5RunnableBlock runBlock | pact5 res.cpCwVersion res.cpChainId bh -> do let @@ -364,18 +355,18 @@ restoreAndSave res rewindParent blocks = do let !m'' = m <> m' case maybeParent of Nothing - | genesisHeight res.cpCwVersion res.cpChainId /= view blockHeight nextBlockHeader -> internalError + | genesisHeight res.cpCwVersion res.cpChainId /= _evaluationCtxCurrentHeight nextBlockHeader -> internalError "doRestoreAndSave: block with no parent, genesis block, should have genesis height but doesn't," - Just (ParentHeader ph) - | succ (view blockHeight ph) /= view blockHeight nextBlockHeader -> internalError $ + Just ph + | _evaluationCtxCurrentHeight ph /= _evaluationCtxParentHeight nextBlockHeader -> internalError $ "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " - <> sshow (view blockHeight ph) <> ", child height " <> sshow (view blockHeight nextBlockHeader) + <> sshow (_evaluationCtxParentHeight ph) <> ", child height " <> sshow (_evaluationCtxParentHeight nextBlockHeader) _ -> return () Pact5.commitBlockStateToDatabase res.cpSql - (view blockHash nextBlockHeader) (view blockHeight nextBlockHeader) + (_evaluationCtxParentHash nextBlockHeader) (_evaluationCtxParentHeight nextBlockHeader) blockHandle - return (m'', Just (ParentHeader nextBlockHeader), _blockHandleTxId blockHandle, moduleCache) + return (m'', Just nextBlockHeader, _blockHandleTxId blockHandle, moduleCache) | otherwise -> internalError $ "Pact 5 block executed on block height before Pact 5 fork, height: " <> sshow bh @@ -404,23 +395,23 @@ getEarliestBlock db = do -- is the height of the block of the block hash. -- -- TODO: Under which circumstances does this return 'Nothing'? -getLatestBlock :: HasCallStack => SQLiteEnv -> IO (Maybe (BlockHeight, BlockHash)) +getLatestBlock :: HasCallStack => SQLiteEnv -> IO (Maybe (Parent RankedBlockHash)) getLatestBlock db = do r <- qry_ db qtext [RInt, RBlob] >>= mapM go case r of [] -> return Nothing - (!o:_) -> return (Just o) + (!o:_) -> return (Just $ Parent o) where qtext = "SELECT blockheight, hash FROM BlockHistory ORDER BY blockheight DESC LIMIT 1" go [SInt hgt, SBlob blob] = let hash = either error id $ runGetEitherS decodeBlockHash blob - in return (fromIntegral hgt, hash) + in return $ RankedBlockHash (fromIntegral hgt) hash go _ = fail "Chainweb.Pact.Backend.RelationalCheckpointer.getLatest: impossible. This is a bug in chainweb-node." -- | Ask: is the checkpointer aware of the given block? -lookupBlock :: SQLiteEnv -> (BlockHeight, BlockHash) -> IO Bool -lookupBlock db (bheight, bhash) = do +lookupBlock :: SQLiteEnv -> RankedBlockHash -> IO Bool +lookupBlock db (RankedBlockHash bheight bhash) = do r <- qry db qtext [SInt $ fromIntegral bheight, SBlob (runPutS (encodeBlockHash bhash))] [RInt] liftIO (expectSingle "row" r) >>= \case @@ -433,7 +424,7 @@ getBlockParent :: ChainwebVersion -> ChainId -> SQLiteEnv -> (BlockHeight, Block getBlockParent v cid db (bh, hash) | bh == genesisHeight v cid = return Nothing | otherwise = do - blockFound <- lookupBlock db (bh, hash) + blockFound <- lookupBlock db (RankedBlockHash bh hash) if not blockFound then return Nothing else do @@ -456,7 +447,7 @@ getBlockHistory getBlockHistory db blockHeader d = do historicalEndTxId <- fmap fromIntegral - <$> PactDb.getEndTxId "getBlockHistory" db (Just $ ParentHeader blockHeader) + <$> PactDb.getEndTxId "getBlockHistory" db (Just $ view rankedBlockHash blockHeader) forM historicalEndTxId $ \endTxId -> do startTxId <- if bHeight == genesisHeight v cid diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 0a4139f128..f7e7781268 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -42,6 +42,7 @@ module Chainweb.Pact.Types , psMempoolAccess , psCheckpointer , psPdb + , psCandidatePdb , psBlockHeaderDb , psReorgLimit , psPreInsertCheckTimeout @@ -270,6 +271,9 @@ import Chainweb.BlockCreationTime import qualified Data.Aeson as Aeson import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Encoding as TL +import Chainweb.Storage.Table.Map (MapTable) +import Chainweb.BlockPayloadHash +import Chainweb.PayloadProvider.P2P -- | Gather tx logs for a block, along with last tx for each @@ -288,8 +292,8 @@ instance Show BlockTxHistory where -- and knows its parent header (Nothing if it's a genesis block). -- Reports back its own header and some extra value. data RunnableBlock logger a - = Pact4RunnableBlock (PactDbFor logger Pact4 -> Maybe ParentHeader -> IO (a, BlockHeader)) - | Pact5RunnableBlock (PactDbFor logger Pact5 -> Maybe ParentHeader -> BlockHandle Pact5 -> IO ((a, BlockHeader), BlockHandle Pact5)) + = Pact4RunnableBlock (PactDbFor logger Pact4 -> Maybe (Parent RankedBlockHash) -> IO (a, RankedBlockHash)) + | Pact5RunnableBlock (PactDbFor logger Pact5 -> Maybe (Parent RankedBlockHash) -> BlockHandle Pact5 -> IO ((a, RankedBlockHash), BlockHandle Pact5)) -- -------------------------------------------------------------------------- -- -- Coinbase output utils @@ -487,11 +491,11 @@ instance Semigroup MemPoolAccess where instance Monoid MemPoolAccess where mempty = MemPoolAccess mempty mempty mempty mempty - data PactServiceEnv logger tbl = PactServiceEnv { _psMempoolAccess :: !(Maybe MemPoolAccess) , _psCheckpointer :: !(Checkpointer logger) - , _psPdb :: !(PayloadDb tbl) + , _psPdb :: !(PayloadStore (PayloadDb tbl) PayloadData) + , _psCandidatePdb :: !(MapTable RankedBlockPayloadHash PayloadData) , _psBlockHeaderDb :: !BlockHeaderDb , _psPreInsertCheckTimeout :: !Micros -- ^ Maximum allowed execution time for the transactions validation. @@ -646,7 +650,7 @@ makeLenses ''PactServiceState data PactBlockEnv logger pv tbl = PactBlockEnv { _psServiceEnv :: !(PactServiceEnv logger tbl) - , _psParentHeader :: !ParentHeader + , _psParentHeader :: !(Parent BlockHeader) , _psIsGenesis :: !Bool , _psBlockDbEnv :: !(PactDbFor logger pv) } @@ -1039,7 +1043,7 @@ data NewBlockReq { _newBlockFill :: !NewBlockFill -- ^ whether to fill this block with transactions; if false, the block -- will be empty. - , _newBlockParent :: !ParentHeader + , _newBlockParent :: !(Parent BlockHeader) -- ^ the parent to use for the new block } deriving stock Show @@ -1157,7 +1161,7 @@ type family CommandResultFor (pv :: PactVersion) where data BlockInProgress pv = BlockInProgress { _blockInProgressHandle :: !(BlockHandle pv) , _blockInProgressModuleCache :: !(ModuleCacheFor pv) - , _blockInProgressParentHeader :: !(Maybe ParentHeader) + , _blockInProgressParentHeader :: !(Maybe (Parent BlockHeader)) , _blockInProgressChainwebVersion :: !ChainwebVersion , _blockInProgressChainId :: !ChainId , _blockInProgressRemainingGasLimit :: !Pact4.GasLimit @@ -1197,7 +1201,7 @@ blockInProgressParent bip = maybe (genesisParentBlockHash v cid, genesisHeight v cid, v ^?! versionGenesis . genesisTime . atChain cid) (\bh -> (view blockHash bh, view blockHeight bh, view blockCreationTime bh)) - (_parentHeader <$> _blockInProgressParentHeader bip) + (unwrapParent <$> _blockInProgressParentHeader bip) where v = _blockInProgressChainwebVersion bip cid = _blockInProgressChainId bip @@ -1211,7 +1215,7 @@ instance Show (BlockInProgress pv) where "Pact5 block," , T.unpack (blockHashToTextShort $ fromMaybe (genesisParentBlockHash (_blockInProgressChainwebVersion bip) (_blockInProgressChainId bip)) - (view blockHash . _parentHeader <$> _blockInProgressParentHeader bip)) + (view blockHash . unwrapParent <$> _blockInProgressParentHeader bip)) , show (_blockInProgressMiner bip ^. minerId) , "# transactions " <> show (V.length (_transactionPairs $ _blockInProgressTransactions bip)) <> "," , "# gas remaining " <> show (_blockInProgressRemainingGasLimit bip) diff --git a/src/Chainweb/Payload/PayloadStore.hs b/src/Chainweb/Payload/PayloadStore.hs index 2e1c59a348..c1b11798d9 100644 --- a/src/Chainweb/Payload/PayloadStore.hs +++ b/src/Chainweb/Payload/PayloadStore.hs @@ -14,6 +14,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE InstanceSigs #-} -- | -- Module: Chainweb.Payload.PayloadStore @@ -96,6 +97,9 @@ import Chainweb.Version import Chainweb.Storage.Table import Chainweb.BlockHeight import Chainweb.PayloadProvider.Pact.Genesis +import Chainweb.BlockPayloadHash (RankedBlockPayloadHash) +import Chainweb.Ranked +import Data.Maybe (isJust) -- -------------------------------------------------------------------------- -- -- Exceptions @@ -199,6 +203,16 @@ data PayloadDb_ a tbl = PayloadDb , _payloadCache :: !(PayloadCache_ a tbl) } +instance CanReadablePayloadCas_ a tbl => ReadableTable (PayloadDb_ a tbl) (Ranked (BlockPayloadHash_ a)) (PayloadData_ a) where + tableLookup :: PayloadDb_ a tbl -> Ranked (BlockPayloadHash_ a) -> IO (Maybe (PayloadData_ a)) + tableLookup pdb (Ranked hei hsh) = + lookupPayloadDataWithHeight pdb (Just hei) hsh + tableLookupBatch' :: PayloadDb_ a tbl -> Traversal s r (Ranked (BlockPayloadHash_ a)) (Maybe (PayloadData_ a)) -> s -> IO r + tableLookupBatch' pdb t s = traverseOf t (tableLookup pdb) s + -- TODO: optimize to stop decoding when we just do a membership check + tableMember :: PayloadDb_ a tbl -> Ranked (BlockPayloadHash_ a) -> IO Bool + tableMember pdb rhsh = isJust <$> tableLookup pdb rhsh + type HeightedCas c t v = c (t (BlockHeight, CasKeyType v) v) (BlockHeight, CasKeyType v) v type CanReadablePayloadCas tbl = CanReadablePayloadCas_ ChainwebMerkleHashAlgorithm tbl type CanReadablePayloadCas_ a tbl = diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 11ffd1df0e..95dd45fb60 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -24,7 +24,8 @@ module Chainweb.PayloadProvider -- * SyncState , SyncState(..) , syncStateOfBlockHeader -, syncStateRankedBlockPayloadHash +, _syncStateRankedBlockPayloadHash +, _syncStateRankedBlockHash -- * ConsensusState , ConsensusState(..) @@ -40,7 +41,9 @@ module Chainweb.PayloadProvider -- * Evaluation Context , EvaluationCtx(..) +, _evaluationCtxCurrentHeight , _evaluationCtxRankedPayloadHash +, _evaluationCtxRankedParentHash -- * Fork Info , ForkInfo(..) @@ -50,6 +53,7 @@ module Chainweb.PayloadProvider , assertForkInfoInvariants , _forkInfoBaseHeight , _forkInfoBaseBlockHash +, _forkInfoBaseRankedBlockHash , _forkInfoBaseRankedPayloadHash -- * New Payload @@ -160,10 +164,14 @@ syncStateOfBlockHeader hdr = SyncState , _syncStateBlockPayloadHash = view blockPayloadHash hdr } -syncStateRankedBlockPayloadHash :: SyncState -> RankedBlockPayloadHash -syncStateRankedBlockPayloadHash s = RankedBlockPayloadHash +_syncStateRankedBlockPayloadHash :: SyncState -> RankedBlockPayloadHash +_syncStateRankedBlockPayloadHash s = RankedBlockPayloadHash (_syncStateHeight s) (_syncStateBlockPayloadHash s) +_syncStateRankedBlockHash :: SyncState -> RankedBlockHash +_syncStateRankedBlockHash s = RankedBlockHash + (_syncStateHeight s) (_syncStateBlockHash s) + syncStateProperties :: forall e kv . KeyValue e kv => SyncState -> [kv] syncStateProperties a = [ "height" .= _syncStateHeight a @@ -224,15 +232,15 @@ genesisConsensusState v c = ConsensusState latestRankedBlockPayloadHash :: ConsensusState -> RankedBlockPayloadHash latestRankedBlockPayloadHash = - syncStateRankedBlockPayloadHash . _consensusStateLatest + _syncStateRankedBlockPayloadHash . _consensusStateLatest safeRankedBlockPayloadHash :: ConsensusState -> RankedBlockPayloadHash safeRankedBlockPayloadHash = - syncStateRankedBlockPayloadHash . _consensusStateSafe + _syncStateRankedBlockPayloadHash . _consensusStateSafe finalRankedBlockPayloadHash :: ConsensusState -> RankedBlockPayloadHash finalRankedBlockPayloadHash = - syncStateRankedBlockPayloadHash . _consensusStateFinal + _syncStateRankedBlockPayloadHash . _consensusStateFinal consensusStateProperties :: forall e kv . KeyValue e kv => ConsensusState -> [kv] consensusStateProperties a = @@ -275,9 +283,9 @@ data EvaluationCtx = EvaluationCtx { _evaluationCtxParentCreationTime :: !BlockCreationTime -- ^ Creation time of the parent block. If transactions in the block -- have a notion of "current" time, they should use this value. - , _evaluationCtxParentHash :: !BlockHash + , _evaluationCtxParentHash :: !(Parent BlockHash) -- ^ Block hash of the parent block. - , _evaluationCtxParentHeight :: !BlockHeight + , _evaluationCtxParentHeight :: !(Parent BlockHeight) -- ^ Block height of the parent block. , _evaluationCtxMinerReward :: !MinerReward -- ^ The miner reward that is assigned to the miner of the block. Miner @@ -313,13 +321,23 @@ data EvaluationCtx = EvaluationCtx } deriving (Show, Eq, Ord) +_evaluationCtxCurrentHeight :: EvaluationCtx -> BlockHeight +_evaluationCtxCurrentHeight = succ . unwrapParent . _evaluationCtxParentHeight + _evaluationCtxRankedPayloadHash :: EvaluationCtx -> RankedBlockPayloadHash _evaluationCtxRankedPayloadHash ctx = RankedBlockPayloadHash - (_evaluationCtxParentHeight ctx + 1) + (_evaluationCtxCurrentHeight ctx) (_evaluationCtxPayloadHash ctx) +_evaluationCtxRankedParentHash + :: EvaluationCtx + -> Parent RankedBlockHash +_evaluationCtxRankedParentHash ctx = Parent $ RankedBlockHash + (unwrapParent $ _evaluationCtxParentHeight ctx) + (unwrapParent $ _evaluationCtxParentHash ctx) + evaluationCtxProperties :: forall e kv . KeyValue e kv => EvaluationCtx -> [kv] evaluationCtxProperties a = [ "parentCreationTime" .= _evaluationCtxParentCreationTime a @@ -364,21 +382,21 @@ data NewBlockCtx = NewBlockCtx -- | Get the evaluation context for given parent header and block payload hash -- blockHeaderToEvaluationCtx - :: ParentHeader + :: Parent BlockHeader -> BlockPayloadHash -> Maybe EncodedPayloadData -> EvaluationCtx -blockHeaderToEvaluationCtx (ParentHeader ph) pld pldData = EvaluationCtx +blockHeaderToEvaluationCtx (Parent ph) pld pldData = EvaluationCtx { _evaluationCtxParentCreationTime = view blockCreationTime ph - , _evaluationCtxParentHash = view blockHash ph + , _evaluationCtxParentHash = Parent $ view blockHash ph , _evaluationCtxParentHeight = parentHeight , _evaluationCtxMinerReward = blockMinerReward v height , _evaluationCtxPayloadHash = pld , _evaluationCtxPayloadData = pldData } where - parentHeight = view blockHeight ph - height = parentHeight + 1 + parentHeight = Parent $ view blockHeight ph + height = unwrapParent parentHeight + 1 v = _chainwebVersion ph newBlockCtxProperties :: forall e kv . KeyValue e kv => NewBlockCtx -> [kv] @@ -481,12 +499,17 @@ data ForkInfo = ForkInfo _forkInfoBaseHeight :: ForkInfo -> BlockHeight _forkInfoBaseHeight fi = case _forkInfoTrace fi of [] -> _latestHeight (_forkInfoTargetState fi) - (h:_) -> _evaluationCtxParentHeight h + (h:_) -> unwrapParent $ _evaluationCtxParentHeight h _forkInfoBaseBlockHash :: ForkInfo -> BlockHash _forkInfoBaseBlockHash fi = case _forkInfoTrace fi of [] -> _latestBlockHash (_forkInfoTargetState fi) - (h:_) -> _evaluationCtxParentHash h + (h:_) -> unwrapParent $ _evaluationCtxParentHash h + +_forkInfoBaseRankedBlockHash :: ForkInfo -> RankedBlockHash +_forkInfoBaseRankedBlockHash fi = case _forkInfoTrace fi of + [] -> _latestRankedBlockHash (_forkInfoTargetState fi) + (h:_) -> unwrapParent $ _evaluationCtxRankedParentHash h _forkInfoBaseRankedPayloadHash :: ForkInfo -> RankedBlockPayloadHash _forkInfoBaseRankedPayloadHash fi = RankedBlockPayloadHash @@ -919,6 +942,9 @@ withPayloadProvider (PayloadProviders ps) c f = case HM.lookup cid ps of _latestBlockHash :: ConsensusState -> BlockHash _latestBlockHash = _syncStateBlockHash . _consensusStateLatest +_latestRankedBlockHash :: ConsensusState -> RankedBlockHash +_latestRankedBlockHash = _syncStateRankedBlockHash . _consensusStateLatest + _latestPayloadHash :: ConsensusState -> BlockPayloadHash _latestPayloadHash = _syncStateBlockPayloadHash . _consensusStateLatest @@ -961,4 +987,3 @@ genesisState v c = ConsensusState , _syncStateBlockPayloadHash = view blockPayloadHash hdr } hdr = genesisBlockHeader (_chainwebVersion v) c - diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 3a010a9e04..8ff9b733fd 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1126,9 +1126,9 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do let unknowns = fst <$> unknowns' - lf Debug $ "unkown blocks in context: " <> sshow (length unknowns) + lf Debug $ "unknown blocks in context: " <> sshow (length unknowns) - -- fetch all unkown payloads + -- fetch all unknown payloads -- -- FIXME do the right thing here. Ideally, fetch all -- unknowns in batches without redundant local lookups. Then @@ -1142,7 +1142,7 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do validatePayload p pld ctx return (_evaluationCtxRankedPayloadHash ctx, pld) - lf Debug $ "fetched payloads for unkowns: " <> sshow (length plds) + lf Debug $ "fetched payloads for unknowns: " <> sshow (length plds) updateEvm p trgState pctx plds @@ -1522,4 +1522,3 @@ getFinalHash -> ConsensusState -> IO EVM.BlockHash getFinalHash p = fmap (view EVM.hdrHash) . getFinalHdr p - diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs new file mode 100644 index 0000000000..80ec2a672d --- /dev/null +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -0,0 +1,60 @@ +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE InstanceSigs #-} +module Chainweb.PayloadProvider.Pact + ( PactPayloadProvider(..) + ) where +import Chainweb.Version +import Control.Concurrent.STM +import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.P2P +import qualified Chainweb.Payload.PayloadStore as PDB +import Chainweb.Storage.Table.RocksDB +import Chainweb.Storage.Table.Map +import Chainweb.BlockPayloadHash +import Chainweb.Pact.Backend.Types +import Data.LogMessage +import Chainweb.Time +import Chainweb.Pact.Types +import Data.Text (Text) +import Chainweb.Counter +import Chainweb.Miner.Pact +import Chainweb.Logger + +data Payload + +data PactPayloadProvider logger = PactPayloadProvider + { _pactChainwebVersion :: !ChainwebVersion + , _pactChainId :: !ChainId + , _pactPayloadVar :: !(TMVar NewPayload) + , _pactPayloadStore :: !(PDB.PayloadDb RocksDbTable) + , _pactCandidatePayloads :: !(MapTable RankedBlockPayloadHash Payload) + , _pactCheckpointer :: !(Checkpointer logger) + , _pactLogger :: !LogFunction + , _pactPreInsertCheckTimeout :: !Micros + -- ^ Maximum allowed execution time for the transactions validation. + , _pactReorgLimit :: !RewindLimit + -- ^ The limit of checkpointer's rewind in the `execValidationBlock` command. + , _pactOnFatalError :: !(forall a. PactException -> Text -> IO a) + , _pactGasLogger :: !(Maybe logger) + , _pactTxFailuresCounter :: !(Maybe (Counter "txFailures")) + , _pactTxTimeLimit :: !(Maybe Micros) + , _pactMiner :: !(Maybe Miner) + } + +instance HasChainId (PactPayloadProvider logger) where + _chainId = _pactChainId +instance HasChainwebVersion (PactPayloadProvider logger) where + _chainwebVersion = _pactChainwebVersion + +instance Logger logger => PayloadProvider (PactPayloadProvider logger) where + prefetchPayloads :: Logger logger => PactPayloadProvider logger -> Maybe Hints -> ForkInfo -> IO () + prefetchPayloads pp hints forkInfo = undefined + syncToBlock :: Logger logger => PactPayloadProvider logger -> Maybe Hints -> ForkInfo -> IO ConsensusState + syncToBlock pp hints forkInfo = undefined + latestPayloadSTM :: Logger logger => PactPayloadProvider logger -> STM NewPayload + latestPayloadSTM = readTMVar . _pactPayloadVar + eventProof :: Logger logger => PactPayloadProvider logger -> XEventId -> IO SpvProof + eventProof = error "not figured out yet" From dc884f3adf621885d42ddfaa85453b80515dd16b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 16 Mar 2025 13:38:08 -0700 Subject: [PATCH 094/378] Import Ethereum.Misc qualified in VM/EngineAPI --- src/Chainweb/PayloadProvider/EVM/EngineAPI.hs | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs index 56c69cd1ab..6a3fb01f46 100644 --- a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs +++ b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs @@ -133,7 +133,7 @@ import Data.Hashable (Hashable) import Data.Text qualified as T import Data.Text.Encoding qualified as T import Data.Time.Clock.POSIX (getPOSIXTime, POSIXTime) -import Ethereum.Misc +import Ethereum.Misc qualified as E import Ethereum.RLP (RLP (..), putRlpByteString, getRlpL, putRlpL, label) import Ethereum.Trie import Ethereum.Utils hiding (int) @@ -156,14 +156,14 @@ import Data.Word -- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#forkchoicestatev1 -- data ForkchoiceStateV1 = ForkchoiceStateV1 - { _forkchoiceHeadBlockHash :: !BlockHash + { _forkchoiceHeadBlockHash :: !E.BlockHash -- ^ headBlockHash: DATA, 32 Bytes - block hash of the head of the -- canonical chain - , _forkchoiceSafeBlockHash :: !BlockHash + , _forkchoiceSafeBlockHash :: !E.BlockHash -- ^ safeBlockHash: DATA, 32 Bytes - the "safe" block hash of the -- canonical chain under certain synchrony and honesty assumptions. This -- value MUST be either equal to or an ancestor of headBlockHash - , _forkchoiceFinalizedBlockHash :: !BlockHash + , _forkchoiceFinalizedBlockHash :: !E.BlockHash -- ^ finalizedBlockHash: DATA, 32 Bytes - block hash of the most recent -- finalized block } @@ -237,7 +237,7 @@ instance HasTextRepresentation PayloadStatusStatus where data PayloadStatusV1 = PayloadStatusV1 { _payloadStatusV1Status :: !PayloadStatusStatus -- ^ Status of the payload - , _payloadStatusV1LatestValidHash :: !(Maybe BlockHash) + , _payloadStatusV1LatestValidHash :: !(Maybe E.BlockHash) -- ^ DATA|null, 32 Bytes - the hash of the most recent valid block in -- the branch defined by payload and its ancestors , _payloadStatusV1ValidationError :: !(Maybe T.Text) @@ -290,7 +290,7 @@ data WithdrawalV1 = WithdrawalV1 -- ^ index: QUANTITY, 64 Bits , _withdrawalValidatorIndex :: !Word64 -- ^ validatorIndex: QUANTITY, 64 Bits - , _withdrawalAddress :: !Address + , _withdrawalAddress :: !E.Address -- ^ address: DATA, 20 Bytes , _withdrawalAmount :: !Word64 -- ^ amount: QUANTITY, 64 Bits @@ -339,7 +339,7 @@ instance RLP WithdrawalV1 where withdrawlsRoot :: [WithdrawalV1] -> WithdrawalsRoot withdrawlsRoot l = unsafePerformIO $ do store <- mkHashMapStore - Trie (Keccak256Hash !t) <- trie (_trieStoreAdd store) + Trie (E.Keccak256Hash !t) <- trie (_trieStoreAdd store) $ bimap putRlpByteString putRlpByteString <$> zip [0::Natural ..] l return (WithdrawalsRoot t) @@ -348,26 +348,26 @@ withdrawlsRoot l = unsafePerformIO $ do -- | EIP-4844 KZG Commitment -- -newtype KzgCommitment = KzgCommitment { _kzgCommitment :: BytesN 48 } +newtype KzgCommitment = KzgCommitment { _kzgCommitment :: E.BytesN 48 } deriving (Show, Eq, Ord) - deriving newtype (RLP, Bytes, Storable, Hashable) - deriving (ToJSON, FromJSON) via (HexBytes (BytesN 48)) + deriving newtype (RLP, E.Bytes, Storable, Hashable) + deriving (ToJSON, FromJSON) via (HexBytes (E.BytesN 48)) -- | EIP-4844 KZG Proof -- -newtype KzgProof = KzgProof { _kzgProof :: BytesN 48 } +newtype KzgProof = KzgProof { _kzgProof :: E.BytesN 48 } deriving (Show, Eq, Ord) - deriving newtype (RLP, Bytes, Storable, Hashable) - deriving (ToJSON, FromJSON) via (HexBytes (BytesN 48)) + deriving newtype (RLP, E.Bytes, Storable, Hashable) + deriving (ToJSON, FromJSON) via (HexBytes (E.BytesN 48)) -- | EIP-4844 Blob -- -- size: FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT = 4096 * 32 = 131072 -- -newtype Blob = Blob { _blob :: BytesN 131072 } +newtype Blob = Blob { _blob :: E.BytesN 131072 } deriving (Show, Eq, Ord) - deriving newtype (RLP, Bytes, Storable, Hashable) - deriving (ToJSON, FromJSON) via (HexBytes (BytesN 131072)) + deriving newtype (RLP, E.Bytes, Storable, Hashable) + deriving (ToJSON, FromJSON) via (HexBytes (E.BytesN 131072)) -- | Blobs Bundle V1 -- @@ -426,7 +426,7 @@ instance FromJSON BlobsBundleV1 where newtype TransactionBytes = TransactionBytes { _transactionBytes :: BS.ShortByteString } deriving (Show, Eq, Ord, Generic) - deriving newtype (Hashable, Bytes) + deriving newtype (Hashable, E.Bytes) instance ToJSON TransactionBytes where toEncoding (TransactionBytes a) = toEncoding (HexBytes a) @@ -440,12 +440,12 @@ instance FromJSON TransactionBytes where return b {-# INLINE parseJSON #-} -transactionsRoot :: [TransactionBytes] -> TransactionsRoot +transactionsRoot :: [TransactionBytes] -> E.TransactionsRoot transactionsRoot l = unsafePerformIO $ do store <- mkHashMapStore Trie !t <- trie (_trieStoreAdd store) - $ bimap putRlpByteString bytes <$> zip [0::Natural ..] l - return (TransactionsRoot t) + $ bimap putRlpByteString E.bytes <$> zip [0::Natural ..] l + return (E.TransactionsRoot t) -- | Execution Payload V1 -- @@ -472,31 +472,31 @@ transactionsRoot l = unsafePerformIO $ do -- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#executionpayloadv1 -- data ExecutionPayloadV1 = ExecutionPayloadV1 - { _executionPayloadV1ParentHash :: !ParentHash + { _executionPayloadV1ParentHash :: !E.ParentHash -- ^ parentHash: DATA, 32 Bytes - , _executionPayloadV1FeeRecipient :: !Beneficiary + , _executionPayloadV1FeeRecipient :: !E.Beneficiary -- ^ feeRecipient: DATA, 20 Bytes - , _executionPayloadV1StateRoot :: !StateRoot + , _executionPayloadV1StateRoot :: !E.StateRoot -- ^ stateRoot: DATA, 32 Bytes - , _executionPayloadV1ReceiptsRoot :: !ReceiptsRoot + , _executionPayloadV1ReceiptsRoot :: !E.ReceiptsRoot -- ^ receiptsRoot: DATA, 32 Bytes - , _executionPayloadV1LogsBloom :: !Bloom + , _executionPayloadV1LogsBloom :: !E.Bloom -- ^ logsBloom: DATA, 256 Bytes , _executionPayloadV1PrevRandao :: !Randao -- ^ prevRandao: DATA, 32 Bytes - , _executionPayloadV1BlockNumber :: !BlockNumber + , _executionPayloadV1BlockNumber :: !E.BlockNumber -- ^ blockNumber: QUANTITY, 64 Bits - , _executionPayloadV1GasLimit :: !GasLimit + , _executionPayloadV1GasLimit :: !E.GasLimit -- ^ gasLimit: QUANTITY, 64 Bits - , _executionPayloadV1GasUsed :: !GasUsed + , _executionPayloadV1GasUsed :: !E.GasUsed -- ^ gasUsed: QUANTITY, 64 Bits - , _executionPayloadV1Timestamp :: !Timestamp + , _executionPayloadV1Timestamp :: !E.Timestamp -- ^ timestamp: QUANTITY, 64 Bits - , _executionPayloadV1ExtraData :: !ExtraData + , _executionPayloadV1ExtraData :: !E.ExtraData -- ^ extraData: DATA, 0 to 32 Bytes , _executionPayloadV1BaseFeePerGas :: !BaseFeePerGas -- ^ baseFeePerGas: QUANTITY, 256 Bits - , _executionPayloadV1BlockHash :: !BlockHash + , _executionPayloadV1BlockHash :: !E.BlockHash -- ^ blockHash: DATA, 32 Bytes , _executionPayloadV1Transactions :: ![TransactionBytes] -- ^ transactions: Array of DATA - Array of transaction objects, each @@ -649,13 +649,13 @@ instance FromJSON ExecutionPayloadV3 where -- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadattributesv1 -- data PayloadAttributesV1 = PayloadAttributesV1 - { _payloadAttributesV1Timestamp :: !Timestamp + { _payloadAttributesV1Timestamp :: !E.Timestamp -- ^ timestamp: QUANTITY, 64 Bits - value for the timestamp field of the -- new payload , _payloadAttributesV1PrevRandao :: !Randao -- ^ prevRandao: DATA, 32 Bytes - value for the prevRandao field of the -- new payload. - , _payloadAttributesV1SuggestedFeeRecipient :: !Address + , _payloadAttributesV1SuggestedFeeRecipient :: !E.Address -- ^ suggestedFeeRecipient: DATA, 20 Bytes - suggested value for the -- feeRecipient field of the new payload } @@ -773,7 +773,7 @@ instance FromJSON PayloadAttributesV3 where -- -- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#request-2 -- -newtype PayloadId = PayloadId { _payloadId :: BytesN 8 } +newtype PayloadId = PayloadId { _payloadId :: E.BytesN 8 } deriving (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via JsonTextRepresentation "PayloadId" PayloadId @@ -1139,7 +1139,7 @@ instance FromJSON GetPayloadV3Response where -- -- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md#jwt-specifications -- -newtype JwtSecret = JwtSecret { _jwtSecret :: BytesN 32 } +newtype JwtSecret = JwtSecret { _jwtSecret :: E.BytesN 32 } deriving (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via (JsonTextRepresentation "JwtSecret" JwtSecret) @@ -1157,14 +1157,14 @@ getJwtToken :: JwtSecret -> IO T.Text getJwtToken secret = jwtToken secret <$> getPOSIXTime jwtToken :: JwtSecret -> POSIXTime -> T.Text -jwtToken (JwtSecret secret) timestamp = +jwtToken (JwtSecret secret) t = T.intercalate "." [header, claim, signature] where header = b64 "{\"alg\":\"HS256\",\"typ\":\"JWT\"}" - claim = b64 $ "{\"iat\":" <> sshow (round @_ @Natural timestamp) <> "}" + claim = b64 $ "{\"iat\":" <> sshow (round @_ @Natural t) <> "}" signature = b64 $ BA.convert - $ hmac @_ @_ @SHA256 (bytes secret) + $ hmac @_ @_ @SHA256 (E.bytes secret) $ T.encodeUtf8 $ T.intercalate "." [header, claim] b64 = encodeB64UrlNoPaddingText From fd1fddba4999f52f13794bf55f0ea09f65c1e9f6 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 16 Mar 2025 13:38:55 -0700 Subject: [PATCH 095/378] Import Ethereum.Misc qualified in EVM.Utils --- src/Chainweb/PayloadProvider/EVM/Utils.hs | 84 +++++++++++------------ 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs index e6ebbdaa64..944b0a9ee8 100644 --- a/src/Chainweb/PayloadProvider/EVM/Utils.hs +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -54,7 +54,7 @@ import Data.Hashable (Hashable) import Data.Text qualified as T import Data.Text.Encoding qualified as T import Data.Text.Read qualified as T -import Ethereum.Misc +import Ethereum.Misc qualified as E import Ethereum.RLP (RLP, get, getRlp) import Ethereum.Receipt import Ethereum.Transaction (Wei (..)) @@ -75,11 +75,11 @@ fromHexQuanity (HexQuantity a) = a fromHexBytes :: HexBytes a -> a fromHexBytes (HexBytes a) = a -nullHash :: Keccak256Hash -nullHash = Keccak256Hash $ replicateN 32 +nullHash :: E.Keccak256Hash +nullHash = E.Keccak256Hash $ E.replicateN 32 -nullBlockHash :: BlockHash -nullBlockHash = BlockHash nullHash +nullBlockHash :: E.BlockHash +nullBlockHash = E.BlockHash nullHash deriving instance Functor HexQuantity deriving instance Functor HexBytes @@ -96,11 +96,11 @@ instance HasTextRepresentation (HexQuantity Natural) where {-# INLINE toText #-} {-# INLINE fromText #-} -instance HasTextRepresentation BlockNumber where - toText (BlockNumber a) = toText (HexQuantity a) +instance HasTextRepresentation E.BlockNumber where + toText (E.BlockNumber a) = toText (HexQuantity a) fromText t = do HexQuantity n <- fromText t - return $ BlockNumber n + return $ E.BlockNumber n {-# INLINE toText #-} {-# INLINE fromText #-} @@ -118,31 +118,31 @@ instance HasTextRepresentation (HexBytes BS.ShortByteString) where {-# INLINE toText #-} {-# INLINE fromText #-} -instance KnownNat n => HasTextRepresentation (HexBytes (BytesN n)) where - toText = toText . fmap bytes +instance KnownNat n => HasTextRepresentation (HexBytes (E.BytesN n)) where + toText = toText . fmap E.bytes fromText t = do HexBytes bs <- fromText t - case bytesN bs of + case E.bytesN bs of Right x -> return (HexBytes x) Left e -> throwM $ TextFormatException $ sshow e {-# INLINE toText #-} {-# INLINE fromText #-} -instance HasTextRepresentation Keccak256Hash where - toText = toText . HexBytes . bytes - fromText = fmap (Keccak256Hash . fromHexBytes) . fromText +instance HasTextRepresentation E.Keccak256Hash where + toText = toText . HexBytes . E.bytes + fromText = fmap (E.Keccak256Hash . fromHexBytes) . fromText {-# INLINE toText #-} {-# INLINE fromText #-} -instance HasTextRepresentation BlockHash where - toText = toText . HexBytes . bytes - fromText = fmap BlockHash . fromText +instance HasTextRepresentation E.BlockHash where + toText = toText . HexBytes . E.bytes + fromText = fmap E.BlockHash . fromText {-# INLINE toText #-} {-# INLINE fromText #-} -instance HasTextRepresentation Address where - toText = toText . HexBytes . bytes - fromText = fmap (Address . fromHexBytes) . fromText +instance HasTextRepresentation E.Address where + toText = toText . HexBytes . E.bytes + fromText = fmap (E.Address . fromHexBytes) . fromText {-# INLINE toText #-} {-# INLINE fromText #-} @@ -154,14 +154,14 @@ dropN . KnownNat m => KnownNat n => n <= m - => BytesN m - -> BytesN n -dropN b = unsafeBytesN @n (BS.drop (int d) (_getBytesN b)) + => E.BytesN m + -> E.BytesN n +dropN b = E.unsafeBytesN @n (BS.drop (int d) (E._getBytesN b)) where d = natVal_ @m - natVal_ @n -- TODO: move to ethereum package -deriving newtype instance Bytes LogData +deriving newtype instance E.Bytes LogData -- -------------------------------------------------------------------------- -- -- RLP Encoding Tools @@ -186,8 +186,8 @@ instance ToJSON (HexBytes Chainweb.BlockHash) where instance FromJSON (HexBytes Chainweb.BlockHash) where parseJSON v = HexBytes <$> do - HexBytes b <- parseJSON @(HexBytes (BytesN 32)) v - case runGetS Chainweb.decodeBlockHash (bytes b) of + HexBytes b <- parseJSON @(HexBytes (E.BytesN 32)) v + case runGetS Chainweb.decodeBlockHash (E.bytes b) of Right x -> return x Left e -> fail (sshow e) {-# INLINE parseJSON #-} @@ -202,24 +202,24 @@ newtype ChainId = ChainId { _chainId :: Natural } -- -------------------------------------------------------------------------- -- -- Address 32 -newtype Address32 = Address32 (BytesN 32) +newtype Address32 = Address32 (E.BytesN 32) deriving (Show, Eq, Ord) - deriving newtype (RLP, Bytes, Storable) - deriving (FromJSON, ToJSON) via (HexBytes (BytesN 32)) + deriving newtype (RLP, E.Bytes, Storable) + deriving (FromJSON, ToJSON) via (HexBytes (E.BytesN 32)) instance HasTextRepresentation Address32 where - toText = toText . HexBytes . bytes + toText = toText . HexBytes . E.bytes fromText = fmap (Address32 . fromHexBytes) . fromText {-# INLINE toText #-} {-# INLINE fromText #-} toAddress32 - :: Address + :: E.Address -> Address32 -toAddress32 (Address b) = Address32 (appendN zeroN b) +toAddress32 (E.Address b) = Address32 (E.appendN zeroN b) -zeroN :: KnownNat n => BytesN n -zeroN = replicateN 0 +zeroN :: KnownNat n => E.BytesN n +zeroN = E.replicateN 0 -- -------------------------------------------------------------------------- -- -- Randao @@ -229,16 +229,16 @@ zeroN = replicateN 0 -- -- 32 bytes [cf. yellow paper 4.4.3 (44)] -- -newtype Randao = Randao (BytesN 32) +newtype Randao = Randao (E.BytesN 32) deriving (Show, Eq, Ord) - deriving newtype (RLP, Bytes, Storable, Hashable) - deriving ToJSON via (HexBytes (BytesN 32)) - deriving FromJSON via (HexBytes (BytesN 32)) + deriving newtype (RLP, E.Bytes, Storable, Hashable) + deriving ToJSON via (HexBytes (E.BytesN 32)) + deriving FromJSON via (HexBytes (E.BytesN 32)) newtype BlockValue = BlockValue { _blockValue :: Wei } deriving (Show, Eq) deriving newtype (RLP) - deriving (ToJSON, FromJSON) via (HexQuantity Word256) + deriving (ToJSON, FromJSON) via (HexQuantity E.Word256) _blockValueStu :: BlockValue -> Stu _blockValueStu (BlockValue (Wei v)) = Stu (int v) @@ -256,7 +256,7 @@ data DefaultBlockParameter | DefaultBlockPending | DefaultBlockSafe | DefaultBlockFinalized - | DefaultBlockNumber !BlockNumber + | DefaultBlockNumber !E.BlockNumber deriving (Show, Eq, Generic) deriving (ToJSON, FromJSON) via (JsonTextRepresentation "DefaultBlockParameter" DefaultBlockParameter) @@ -266,7 +266,7 @@ instance HasTextRepresentation DefaultBlockParameter where toText DefaultBlockPending = "pending" toText DefaultBlockSafe = "safe" toText DefaultBlockFinalized = "finalized" - toText (DefaultBlockNumber (BlockNumber n)) = toText (HexQuantity n) + toText (DefaultBlockNumber (E.BlockNumber n)) = toText (HexQuantity n) {-# INLINE toText #-} fromText t = case t of @@ -275,6 +275,6 @@ instance HasTextRepresentation DefaultBlockParameter where "pending" -> return DefaultBlockPending "safe" -> return DefaultBlockSafe "finalized" -> return DefaultBlockFinalized - x -> DefaultBlockNumber . BlockNumber . fromHexQuanity <$> fromText x + x -> DefaultBlockNumber . E.BlockNumber . fromHexQuanity <$> fromText x {-# INLINE fromText #-} From b3b6820484856ec578a1644c8f86e394459acc2d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 16 Mar 2025 17:59:39 -0700 Subject: [PATCH 096/378] Remove database GC --- chainweb.cabal | 2 - node/src/ChainwebNode.hs | 4 +- src/Chainweb/Chainweb.hs | 26 +- src/Chainweb/Chainweb/Configuration.hs | 66 +-- src/Chainweb/Chainweb/PruneChainDatabase.hs | 580 -------------------- test/lib/Chainweb/Test/Orphans/Internal.hs | 8 - 6 files changed, 7 insertions(+), 679 deletions(-) delete mode 100644 src/Chainweb/Chainweb/PruneChainDatabase.hs diff --git a/chainweb.cabal b/chainweb.cabal index 991d913a63..1946629cef 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -176,7 +176,6 @@ library , Chainweb.Chainweb.MempoolSyncClient , Chainweb.Chainweb.MinerResources , Chainweb.Chainweb.PeerResources - , Chainweb.Chainweb.PruneChainDatabase , Chainweb.Core.Brief , Chainweb.Counter , Chainweb.Crypto.MerkleLog @@ -410,7 +409,6 @@ library , crypton-x509 >=1.7 , crypton-x509-system >=1.6 , crypton-x509-validation >=1.6 - , cuckoo >= 0.3 , data-dword >= 0.3 , deepseq >= 1.4 , digraph >= 0.2.3 diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index bb3a5ad100..aed0ebf7b4 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -340,9 +340,7 @@ node conf logger = do withRocksDb' <- if _configOnlySyncPact cwConf || _configReadOnlyReplay cwConf then - if _cutPruneChainDatabase (_configCuts cwConf) == GcNone - then withReadOnlyRocksDb <$ logFunctionText logger Info "Opening RocksDB in read-only mode" - else withRocksDb <$ logFunctionText logger Info "Opening RocksDB in read-write mode, if this wasn't intended, ensure that cuts.pruneChainDatabase is set to none" + withReadOnlyRocksDb <$ logFunctionText logger Info "Opening RocksDB in read-only mode" else return withRocksDb withRocksDb' rocksDbDir modernDefaultOptions $ \rocksDb -> do diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 5ebef79a20..a946e4dd61 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -45,13 +45,8 @@ -- module Chainweb.Chainweb ( --- * GC Configuration - ChainDatabaseGcConfig(..) -, chainDatabaseGcToText -, chainDatabaseGcFromText - -- * Chainweb Resources -, Chainweb(..) + Chainweb(..) , chainwebChains , chainwebCutResources , chainwebHostAddress @@ -91,7 +86,6 @@ module Chainweb.Chainweb -- * Cut Config , CutConfig(..) -, cutPruneChainDatabase , cutFetchTimeout , cutInitialBlockHeightLimit , cutFastForwardBlockHeightLimit @@ -145,7 +139,6 @@ import Chainweb.Chainweb.Configuration import Chainweb.Chainweb.CutResources import Chainweb.Chainweb.MinerResources import Chainweb.Chainweb.PeerResources -import Chainweb.Chainweb.PruneChainDatabase import Chainweb.Counter import Chainweb.Cut import Chainweb.CutDB @@ -286,24 +279,7 @@ withChainwebInternal -> (StartedChainweb logger -> IO ()) -> IO () withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir resetDb inner = do - - -- Garbage Collection - -- performed before PayloadDb and BlockHeaderDb used by other components - logFunctionJson logger Info PruningDatabases - when (_cutPruneChainDatabase (_configCuts conf) /= GcNone) $ - logg Info "start pruning databases" - case _cutPruneChainDatabase (_configCuts conf) of - GcNone -> return () - GcHeaders -> - pruneAllChains (pruningLogger "headers") rocksDb v [] - GcHeadersChecked -> - pruneAllChains (pruningLogger "headers-checked") rocksDb v [CheckPayloads, CheckFull] - GcFull -> - fullGc (pruningLogger "full") rocksDb v - when (_cutPruneChainDatabase (_configCuts conf) /= GcNone) $ - logg Info "finished pruning databases" logFunctionJson logger Info InitializingChainResources - txFailuresCounter <- newCounter @"txFailures" let monitorTxFailuresCounter = runForever (logFunctionText logger) "monitor txFailuresCounter" $ do diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 01b7ef31bb..c48b30f5ac 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -43,12 +43,7 @@ module Chainweb.Chainweb.Configuration , defaultThrottlingConfig -- * Cut Configuration -, ChainDatabaseGcConfig(..) -, chainDatabaseGcToText -, chainDatabaseGcFromText - , CutConfig(..) -, cutPruneChainDatabase , cutFetchTimeout , cutInitialBlockHeightLimit , cutFastForwardBlockHeightLimit @@ -115,7 +110,6 @@ import Chainweb.Version.Registry import Configuration.Utils hiding (Error, Lens', disabled) import Control.Lens hiding ((.=), (<.>)) import Control.Monad -import Control.Monad.Catch (MonadThrow, throwM) import Control.Monad.Except import Control.Monad.Writer import Data.Aeson.Key qualified as K @@ -358,48 +352,8 @@ instance FromJSON (ThrottlingConfig -> ThrottlingConfig) where -- -------------------------------------------------------------------------- -- -- Cut Configuration -data ChainDatabaseGcConfig - = GcNone - | GcHeaders - | GcHeadersChecked - | GcFull - deriving (Show, Eq, Ord, Enum, Bounded, Generic) - -chainDatabaseGcToText :: ChainDatabaseGcConfig -> T.Text -chainDatabaseGcToText GcNone = "none" -chainDatabaseGcToText GcHeaders = "headers" -chainDatabaseGcToText GcHeadersChecked = "headers-checked" -chainDatabaseGcToText GcFull = "full" - -chainDatabaseGcFromText :: MonadThrow m => T.Text -> m ChainDatabaseGcConfig -chainDatabaseGcFromText t = case T.toCaseFold t of - "none" -> return GcNone - "headers" -> return GcHeaders - "headers-checked" -> return GcHeadersChecked - "full" -> return GcFull - x -> throwM $ TextFormatException $ "unknown value for database pruning configuration: " <> sshow x - -instance HasTextRepresentation ChainDatabaseGcConfig where - toText = chainDatabaseGcToText - fromText = chainDatabaseGcFromText - {-# INLINE toText #-} - {-# INLINE fromText #-} - -instance ToJSON ChainDatabaseGcConfig where - toJSON = toJSON . chainDatabaseGcToText - {-# INLINE toJSON #-} - -instance FromJSON ChainDatabaseGcConfig where - parseJSON v = parseJsonFromText "ChainDatabaseGcConfig" v <|> legacy v - where - legacy = withBool "ChainDatabaseGcConfig" $ \case - True -> return GcHeaders - False -> return GcNone - {-# INLINE parseJSON #-} - data CutConfig = CutConfig - { _cutPruneChainDatabase :: !ChainDatabaseGcConfig - , _cutFetchTimeout :: !Int + { _cutFetchTimeout :: !Int , _cutInitialBlockHeightLimit :: !(Maybe BlockHeight) , _cutFastForwardBlockHeightLimit :: !(Maybe BlockHeight) } deriving (Eq, Show) @@ -408,37 +362,27 @@ makeLenses ''CutConfig instance ToJSON CutConfig where toJSON o = object - [ "pruneChainDatabase" .= _cutPruneChainDatabase o - , "fetchTimeout" .= _cutFetchTimeout o + [ "fetchTimeout" .= _cutFetchTimeout o , "initialBlockHeightLimit" .= _cutInitialBlockHeightLimit o , "fastForwardBlockHeightLimit" .= _cutFastForwardBlockHeightLimit o ] instance FromJSON (CutConfig -> CutConfig) where parseJSON = withObject "CutConfig" $ \o -> id - <$< cutPruneChainDatabase ..: "pruneChainDatabase" % o - <*< cutFetchTimeout ..: "fetchTimeout" % o + <$< cutFetchTimeout ..: "fetchTimeout" % o <*< cutInitialBlockHeightLimit ..: "initialBlockHeightLimit" % o <*< cutFastForwardBlockHeightLimit ..: "fastForwardBlockHeightLimit" % o defaultCutConfig :: CutConfig defaultCutConfig = CutConfig - { _cutPruneChainDatabase = GcNone - , _cutFetchTimeout = 3_000_000 + { _cutFetchTimeout = 3_000_000 , _cutInitialBlockHeightLimit = Nothing , _cutFastForwardBlockHeightLimit = Nothing } pCutConfig :: MParser CutConfig pCutConfig = id - <$< cutPruneChainDatabase .:: textOption - % long "prune-chain-database" - <> help - ( "How to prune the chain database on startup." - <> " This can take several hours." - ) - <> metavar "none|headers|headers-checked|full" - <*< cutFetchTimeout .:: option auto + <$< cutFetchTimeout .:: option auto % long "cut-fetch-timeout" <> help "The timeout for processing new cuts in microseconds" <*< cutInitialBlockHeightLimit .:: fmap (Just . BlockHeight) . option auto diff --git a/src/Chainweb/Chainweb/PruneChainDatabase.hs b/src/Chainweb/Chainweb/PruneChainDatabase.hs deleted file mode 100644 index 08963bef0e..0000000000 --- a/src/Chainweb/Chainweb/PruneChainDatabase.hs +++ /dev/null @@ -1,580 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} - --- | --- Module: Chainweb.Chainweb.PruneChainDatabase --- Copyright: Copyright © 2020 Kadena LLC. --- License: MIT --- Maintainer: Lars Kuhtz --- Stability: experimental --- --- There are four modes for pruning the database: --- --- * `none`: pruning is skipped --- * `headers`: only block headers of stale forks below are certain depth are --- pruned. The payload db is left unchanged. --- * `headers-checked`: like `headers` but block headers are also validated and --- the existence and consistency of the respective paylaods is checked. --- * `full`: like `headers` but additionally garbage collection is performed --- on payload database. --- --- The `headers-checked` mode does a verification of the complete Merkle tree of --- the chain. --- --- There is a block header database for each chain, but only a single shared --- payload database. Payloads or parts thereof can be shared between blocks --- on the same chain and on different chains. Therefore garbage collection is --- implemented as mark and sweep, using a probabilistic set representation --- for marking. --- --- This is an offline implementation. There must be no concurrent writes during --- database pruning. Although, if needed it could (relatively easily) be --- extended for online garbage collection. --- --- /Implementation:/ --- --- The algorithm is probabilistic. Some small percentage of unreachable data --- remains in the database. The datastructure for marking used entries is uses a --- random seed, such that repeated pruning will delete an increasing amount of --- items. --- --- For marking Cuckoo filters are used. Cuckoo filters have the disadvantage --- that they don't support concurrent insertion. Also merging cuckoo filters is --- slow. We use seperate filters for each chain and query them individually. We --- may explore other techniques to represent set membership in the future. --- --- The algorithm works top down on the hiearchical structure of the data. It --- guarantees that the database is consistent even if an exception occurs during --- a sweep phase. In that case some extra garbage would remain in the database, --- but there will be no dangling references. --- --- /NOTE: READ BEFORE MAKING A CHANGE TO THE ALGORITHM:/ --- --- For the algorithm to maintain database "deep" consistency (no dangling --- references) it is madatory that GC proceedes in stages. For instance, it is --- tempting to merge all marking phases into a single traversal of the --- BlockPaylaod store. However, due to the probabilistic nature of marking, some --- BlockPayloads are marked that are not reachable from a block header and will --- thus remain in the database. Hence, marking of the lower-level BlockOutputs --- and BlockTransactions must be based on what is /actually/ kept in the store --- and not just on what is reachable from a block header. --- --- /TODO:/ --- --- * implement incremental pruning (which can't be used with mark and sweep, --- though) --- --- * implement online garbage collection --- --- * Consider changing the database format to store the transactions with the --- block payloads and eliminate sharing. Sharing is benefitial only when most --- blocks are empty and the there's only a small number of pools. However, in --- that case storage requirements are moderate anyways. When most block --- contain transactions the benefits of sharing become marginal, but the --- saving during GC (in particular incremental GC) become larger. --- -module Chainweb.Chainweb.PruneChainDatabase -( PruningChecks(..) -, pruneAllChains -, fullGc -, DatabaseCheckException(..) -) where - -import Chainweb.BlockHeader - -import Control.Concurrent.Async -import Control.Lens (view) -import Control.Monad -import Control.Monad.Catch - -import Data.Aeson hiding (Error) -import qualified Data.ByteArray as BA -import qualified Data.ByteString as B -import Data.Cuckoo -import Data.Foldable - -import GHC.Generics - -import Numeric.Natural - -import qualified Streaming.Prelude as S - -import System.LogLevel -import System.Random - --- internal modules - -import Chainweb.BlockHash -import Chainweb.BlockHeader.Validation -import Chainweb.BlockHeaderDB -import Chainweb.BlockHeaderDB.PruneForks -import Chainweb.BlockHeight -import Chainweb.ChainValue -import Chainweb.Graph -import Chainweb.Logger -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB -import Chainweb.Time -import Chainweb.TreeDB -import Chainweb.Utils -import Chainweb.Version -import Chainweb.WebBlockHeaderDB - -import Chainweb.Storage.Table -import Chainweb.Storage.Table.RocksDB - -import Data.LogMessage - --- -------------------------------------------------------------------------- -- --- Fork Pruning - --- | Different consistency checks that can be piggybacked onto database pruning. --- --- The following runtimes were measured on a mac book pro at a block height of --- about 820,000: --- --- * `[]`: 10s --- * `[CheckInductive]`: 2min --- * `[CheckIntrinsic, CheckPayloadsExist]`: 4min --- * `[CheckFull, CheckPayloads]`: 8min --- * `[CheckFull, CheckPayloadsExist]`: 8min --- -data PruningChecks - = CheckIntrinsic - -- ^ Performs intrinsic validation on all block headers. - | CheckInductive - -- ^ Performs all intrinsic and inductive block header validations. - | CheckFull - -- ^ Performs full block header validation. This includes intrinsic, - -- inductive, and braiding validation. - | CheckPayloads - -- ^ checks that all payload components exist in the payload and can be - -- decoded and the Merkle Trees are consitent. - | CheckPayloadsExist - -- ^ only checks the existence of the payload hash in the - -- BlockPayloadStore, which is faster than fully checking payloads. - deriving (Show, Eq, Ord, Enum, Bounded) - --- | Prune all chains. This doesn't clean up Payloads. --- --- Note: this assumes that the payload db is already initialized, i.e. that --- genesis headers have been injected. --- -pruneAllChains - :: Logger logger - => logger - -> RocksDb - -> ChainwebVersion - -> [PruningChecks] - -> IO () -pruneAllChains logger rdb v checks = do - now <- getCurrentTimeIntegral - wdb <- initWebBlockHeaderDb rdb v - forConcurrently_ (toList $ chainIds v) (pruneChain now wdb) - where - diam = diameter $ chainGraphAt v (maxBound @BlockHeight) - pruneChain now wdb cid = do - let chainLogger = addLabel ("chain", toText cid) logger - chainLogg = logFunctionText chainLogger - cdb <- getWebBlockHeaderDb wdb cid - chainLogg Info "start pruning block header database" - x <- pruneForksLogg chainLogger cdb (1 + diam * 3) (callback now wdb cdb) - chainLogg Info $ "finished pruning block header database. Deleted " <> sshow x <> " block headers." - - pdb = newPayloadDb rdb - - callback now wdb cdb d h = mapM_ (\c -> run c d h) checks - where - run CheckIntrinsic = checkIntrinsic now - run CheckInductive = checkInductive now cdb - run CheckPayloadsExist = checkPayloadsExist pdb - run CheckPayloads = checkPayloads pdb - run CheckFull = checkFull now wdb - --- -------------------------------------------------------------------------- -- --- Fork Pruning callbacks - -data DatabaseCheckException - = MissingPayloadException BlockHeader - | InconsistentPaylaod BlockHeader PayloadWithOutputs - deriving (Show, Generic) - -instance Exception DatabaseCheckException - --- | Verify existance and consistency of payloads for all block headers that are --- not deleted. --- --- Adds about 4 minutes of overhead at block height 800,000 on a mac bock pro. --- -checkPayloads :: PayloadDb RocksDbTable -> Bool -> BlockHeader -> IO () -checkPayloads _ True _ = return () -checkPayloads pdb False h = lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) >>= \case - Just p - | verifyPayloadWithOutputs p -> return () - | otherwise -> throwM $ InconsistentPaylaod h p - Nothing -> throwM $ MissingPayloadException h -{-# INLINE checkPayloads #-} - --- | Just check the existence of the Payload in the Database but don't check --- that it can actually be decoded and is consistent. --- --- This is faster than 'checkPayload' because only the top-level 'PayloadData' --- structure is queried -- and immediately discarded. --- -checkPayloadsExist :: PayloadDb RocksDbTable -> Bool -> BlockHeader -> IO () -checkPayloadsExist _ True _ = return () -checkPayloadsExist pdb False h = do - lookupPayloadDataWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) >>= \case - Just _ -> return () - Nothing -> throwM $ MissingPayloadException h -{-# INLINE checkPayloadsExist #-} - --- | Intrinsically validate all block headers that are not deleted. --- --- Adds less than 1 minute of overhead at block height 800,000 on a mac bock pro. --- -checkIntrinsic :: Time Micros -> Bool -> BlockHeader -> IO () -checkIntrinsic _ True _ = return () -checkIntrinsic now False h = validateIntrinsicM now h -{-# INLINE checkIntrinsic #-} - --- | Intrinsically validate all block headers that are not deleted. --- -checkInductive :: Time Micros -> BlockHeaderDb -> Bool -> BlockHeader -> IO () -checkInductive _ _ True _ = return () -checkInductive now cdb False h = do - validateInductiveChainM (tableLookup cdb) h - validateIntrinsicM now h -{-# INLINE checkInductive #-} - --- | Perform complete block header validation for all block that are not deleted. --- --- Adds about 5 minutes of overhead at block height 800,000 on a mac book pro --- -checkFull :: Time Micros -> WebBlockHeaderDb -> Bool -> BlockHeader -> IO () -checkFull _ _ True = const $ return () -checkFull now wdb False = void . validateBlockHeaderM now ctx - where - ctx :: ChainValue BlockHash -> IO (Maybe BlockHeader) - ctx cv = fmap _chainValueValue <$> tableLookup wdb cv -{-# INLINE checkFull #-} - --- -------------------------------------------------------------------------- -- --- Full GC - --- | Prune all chains and clean up payloads. --- --- Note: this assumes that the payload db is already initialized, i.e. that --- genesis headers have been injected. --- -fullGc - :: Logger logger - => logger - -> RocksDb - -> ChainwebVersion - -> IO () -fullGc logger rdb v = do - logg Info $ "Starting chain database garbage collection" - - -- 1. Concurrently prune chain dbs (TODO use a pool to limit concurrency) - markedPayloads <- forConcurrently (toList $ chainIds v) pruneChain - logg Info $ "Finished pruning block headers" - - -- TODO: parallelize he the sweep payload phase to use all available cores. - - -- Sweep Payloads and mark transactions - (markedTrans, markedOutputs) <- sweepPayloads logg db markedPayloads - - -- Sweep transactions - -- (these are reasonably fast, prossibly because they only iterate over the keys) - concurrently_ - (sweepTransactions logg db markedTrans) - (sweepOutputs logg db markedOutputs) - - logg Info $ "Finished chain database garbage collection" - - where - logg = logFunctionText logger - diam = diameter $ chainGraphAt v (maxBound @BlockHeight) - depth = diam * 3 - - db = newPayloadDb rdb - - -- Prune a single chain and return the sets of marked payloads - -- - pruneChain cid = withBlockHeaderDb rdb v cid $ \cdb -> do - let chainLogger = addLabel ("chain", toText cid) logger - chainLogg = logFunctionText chainLogger - - m <- maxRank cdb - markedPayloads <- mkFilter (round $ (1024 + int @_ @Double m) * 1.1) - - chainLogg Info $ "Allocated " - <> sshow (sizeInAllocatedBytes markedPayloads `div` (1024 * 1024)) - <> "MB for marking database entries" - - -- Mark all entries down to the requested depth, so they don't get GCed - -- - -- Blocks at (maxRank - depth) are used as pivots and pruning and marking starts at - -- (depth - 1) - -- - let keptBound = MinRank $ int $ m - min m depth - void $ entries cdb Nothing Nothing (Just keptBound) Nothing - $ S.mapM_ (markPayload markedPayloads) - - chainLogg Info "start pruning block header database" - x <- pruneForksLogg chainLogger cdb depth $ \isDeleted hdr -> case isDeleted of - True -> chainLogg Debug - $ "pruned header " <> toText (view blockHash hdr) - <> " at height " <> sshow (view blockHeight hdr) - False -> markPayload markedPayloads hdr - - chainLogg Info $ "finished pruning block header database. Deleted " <> sshow x <> " block headers." - return markedPayloads - --- -------------------------------------------------------------------------- -- --- Payload Mark and Sweep - --- | Mark Payloads of non-deleted block headers. --- -markPayload :: Filter BlockPayloadHash -> BlockHeader -> IO () -markPayload f = tryInsert f "payload hash" . view blockPayloadHash -{-# INLINE markPayload #-} - --- | Mark Payload Transactions --- -markTransactions :: Filter BlockTransactionsHash -> BlockPayload -> IO () -markTransactions f - = tryInsert f "transactions hash" . _blockPayloadTransactionsHash -{-# INLINE markTransactions #-} - --- | Mark Payload Outputs --- -markOutputs :: Filter BlockOutputsHash -> BlockPayload -> IO () -markOutputs f - = tryInsert f "outputs hash" . _blockPayloadOutputsHash -{-# INLINE markOutputs #-} - --- | Sweep payload and mark all transactions that are kept --- -sweepPayloads - :: LogFunctionText - -> PayloadDb RocksDbTable - -> [Filter BlockPayloadHash] - -> IO (Filter BlockTransactionsHash, Filter BlockOutputsHash) -sweepPayloads logg db markedPayloads = do - logg Info "Sweeping BlockPayloads" - - -- create filter with sufficient capacity - m <- sum <$> mapM itemCount markedPayloads - markedTrans <- mkFilter (round $ int @_ @Double m * 1.1) - - logg Info $ "Allocated " - <> sshow (sizeInAllocatedBytes markedTrans `div` (1024 * 1024)) - <> "MB for marking transaction hashes entries" - - markedOutputs <- mkFilter (round $ int @_ @Double m * 1.1) - logg Info $ "Allocated " - <> sshow (sizeInAllocatedBytes markedOutputs `div` (1024 * 1024)) - <> "MB for marking outputs hashes entries" - - -- traverse all payloads, both old and new tables - c0 <- withTableIterator newPayloadsTable - $ S.sum_ @_ @Int . S.mapM (go markedTrans markedOutputs) . iterToValueStream - logg Info $ "Swept entries for " <> sshow c0 <> " NEW block payload hashes" - - c1 <- withTableIterator oldPayloadsTable - $ S.sum_ @_ @Int . S.mapM (go markedTrans markedOutputs) . iterToValueStream - logg Info $ "Swept entries for " <> sshow c1 <> " OLD block payload hashes" - - return (markedTrans, markedOutputs) - where - go mt mo x = checkMark markedPayloads (_blockPayloadPayloadHash x) >>= \case - True -> 0 <$ markTransactions mt x <* markOutputs mo x - False -> 1 <$ deleteBlockPayload logg db x - - -- Extract RocksDB Tables from Payload Db - newPayloadsTable :: RocksDbTable (BlockHeight, BlockPayloadHash) BlockPayload - newPayloadsTable = _newTransactionDbBlockPayloadsTbl $ _transactionDb db - - oldPayloadsTable :: RocksDbTable BlockPayloadHash BlockPayload - oldPayloadsTable = unCasify (_oldTransactionDbBlockPayloadsTbl $ _transactionDb db) - --- | Sweep Transations --- -sweepTransactions - :: LogFunctionText - -> PayloadDb RocksDbTable - -> Filter BlockTransactionsHash - -> IO () -sweepTransactions logg db marked = do - logg Info "Sweeping BlockTransactions" - c1 <- withTableIterator (_newTransactionDbBlockTransactionsTbl $ _transactionDb db) $ - S.sum_ @_ @Int . S.mapM (\(bh, hsh) -> go (Just bh, hsh)) . iterToKeyStream - logg Info $ "Swept " <> sshow c1 <> " block transactions hashes (new table)" - c2 <- withTableIterator (_oldTransactionDbBlockTransactionsTbl $ _transactionDb db) $ - S.sum_ @_ @Int . S.mapM (\hsh -> go (Nothing, hsh)) . iterToKeyStream - logg Info $ "Swept " <> sshow c2 <> " block transactions hashes (old table)" - where - go (h, x) = member marked (GcHash x) >>= \case - True -> return 0 - False -> 1 <$ deleteBlockTransactions logg db h x - --- | Sweep Outputs --- -sweepOutputs - :: LogFunctionText - -> PayloadDb RocksDbTable - -> Filter BlockOutputsHash - -> IO () -sweepOutputs logg db marked = do - logg Info "Sweeping BlockOutputss" - c1 <- withTableIterator (_newBlockOutputsTbl $ _payloadCacheBlockOutputs $ _payloadCache db) $ - S.sum_ @_ @Int . S.mapM (\(bh, hsh) -> go (Just bh, hsh)) . iterToKeyStream - logg Info $ "Swept " <> sshow c1 <> " block output hashes (new table)" - c2 <- withTableIterator (_oldBlockOutputsTbl $ _payloadCacheBlockOutputs $ _payloadCache db) $ - S.sum_ @_ @Int . S.mapM (curry go Nothing) . iterToKeyStream - logg Info $ "Swept " <> sshow c2 <> " block output hashes (new table)" - where - go (h, x) = member marked (GcHash x) >>= \case - True -> return 0 - False -> 1 <$ deleteBlockOutputs logg db h x - --- -------------------------------------------------------------------------- -- --- Utils for Mark and sweep GC for Payloads --- - --- | Wraps a MerkleLogHash for usage with a Cuckoo filter. --- -newtype GcHash a = GcHash a - deriving newtype (Show, ToJSON) - -instance BA.ByteArrayAccess a => CuckooFilterHash (GcHash a) where - cuckooHash (Salt s) (GcHash a) = - saltedFnv1aByteString s (B.take 8 $ BA.convert a) - cuckooFingerprint (Salt s) (GcHash a) = - saltedSipHashByteString s (B.take 8 $ BA.convert a) - {-# INLINE cuckooHash #-} - {-# INLINE cuckooFingerprint #-} - -type Filter a = CuckooFilterIO 4 10 (GcHash a) - -mkFilter :: Natural -> IO (Filter a) -mkFilter n = do - s <- randomIO - newCuckooFilter (Salt s) $ max 128 n - --- | inserting a somewhat larger number (I think, it's actually 7) of --- equal elements causes the filter to fail. --- -tryInsert :: BA.ByteArrayAccess a => Filter a -> [Char] -> a -> IO () -tryInsert cf k a = unlessM (member cf $ GcHash a) $ - unlessM (insert cf $ GcHash a) $ error - $ "failed to insert item " <> k <> " in cuckoo filter" - <> ": while very rare this can happen. Usually it is resolve by retrying." -{-# INLINE tryInsert #-} - --- TODO: consider using bloom fiters instead that can be merged. Alternatively, --- implement concurrent insertion for cuckoo filters, where the hashing is done --- concurrently and a lock is used only for the actual modification of the --- underlying buffer. Or do fine grained locking on the filter. --- -checkMark :: BA.ByteArrayAccess a => [Filter a] -> a -> IO Bool -checkMark fs a = go fs - where - go [] = return False - go (h : t) = member h (GcHash a) >>= \case - True -> return True - False -> go t - {-# INLINE go #-} - --- -------------------------------------------------------------------------- -- --- Delete Payload --- --- Payload Components are deleted in a way such that it is guaranteed that there --- are no dangling references. --- --- /Tables:/ --- --- BlockPayloadStore - BlockPayload: --- *BlockPayloadHash, BlockTransactionsHash, BlockOutputsHash --- --- BlockTransactionStore - BlockTransactions: --- *BlockTransactionsHash, Vector Transactions, MinerData --- --- BlockOutputsStore - BlockOutputs: --- *BlockOutputsHash, Vector TransactionOutput, CoinbaseOutput --- --- TransactionTreeStore - TransactionTree: --- *BlockTransactionsHash, MerkleTree --- --- OutputTreeStore - OutputTree --- *BlockOutputsHash, MerkleTree --- - --- | delete BlockPayload --- -deleteBlockPayload - :: CanPayloadCas tbl - => LogFunctionText - -> PayloadDb tbl - -> BlockPayload - -> IO () -deleteBlockPayload logg db p = do - logg Debug $ "Delete PayloadHash for " <> encodeToText (_blockPayloadPayloadHash p) - deletePayload db p - --- | Delete BlockOutputs and OutputTree --- -deleteBlockOutputs - :: CanPayloadCas tbl - => LogFunctionText - -> PayloadDb tbl - -> Maybe BlockHeight - -> BlockOutputsHash - -> IO () -deleteBlockOutputs logg db mh p = do - logg Debug $ "Delete BlockOutputs for " <> encodeToText p - case mh of - Just h -> do - flip tableDelete (h, p) $ - _newOutputTreeStoreTbl $ _payloadCacheOutputTrees $ _payloadCache db - flip tableDelete (h, p) $ - _newBlockOutputsTbl $ _payloadCacheBlockOutputs $ _payloadCache db - Nothing -> do - flip tableDelete p $ - _oldOutputTreeStoreTbl $ _payloadCacheOutputTrees $ _payloadCache db - flip tableDelete p $ - _oldBlockOutputsTbl $ _payloadCacheBlockOutputs $ _payloadCache db - --- | Delete BlockTransactions and TransactionsTree --- -deleteBlockTransactions - :: CanPayloadCas tbl - => LogFunctionText - -> PayloadDb tbl - -> Maybe BlockHeight - -> BlockTransactionsHash - -> IO () -deleteBlockTransactions logg db mh p = do - logg Debug $ "Delete BlockTransactions for " <> encodeToText p - case mh of - Just h -> do - flip tableDelete (h, p) $ - _newTransactionDbBlockTransactionsTbl $ _transactionDb db - flip tableDelete (h, p) $ - _newTransactionTreeStoreTbl $ _payloadCacheTransactionTrees $ _payloadCache db - Nothing -> do - flip tableDelete p $ - _oldTransactionDbBlockTransactionsTbl $ _transactionDb db - flip tableDelete p $ - _oldTransactionTreeStoreTbl $ _payloadCacheTransactionTrees $ _payloadCache db diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index e223ee6f03..6e25f9c67f 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -788,14 +788,6 @@ instance Arbitrary ChainId where instance Arbitrary Fork where arbitrary = elements [minBound..maxBound] -instance Arbitrary ChainDatabaseGcConfig where - arbitrary = elements - [ GcNone - , GcHeaders - , GcHeadersChecked - , GcFull - ] - instance Arbitrary a => Arbitrary (EnableConfig a) where arbitrary = EnableConfig <$> arbitrary <*> arbitrary From 4ad94f642a6d7fb0bd23982c3b16ae49428da7e1 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 16 Mar 2025 23:20:32 -0700 Subject: [PATCH 097/378] WIP on SPV --- cabal.project | 20 +- chainweb.cabal | 2 + src/Chainweb/BlockHash.hs | 21 +- src/Chainweb/BlockHeader/Internal.hs | 4 +- src/Chainweb/BlockPayloadHash.hs | 18 +- src/Chainweb/Chainweb/CutResources.hs | 2 - src/Chainweb/Crypto/MerkleLog.hs | 163 +++--- src/Chainweb/MerkleLogHash.hs | 44 +- src/Chainweb/MerkleUniverse.hs | 6 +- src/Chainweb/Miner/RestAPI/Server.hs | 6 +- src/Chainweb/Pact4/SPV.hs | 5 +- src/Chainweb/Pact5/SPV.hs | 8 +- src/Chainweb/Payload.hs | 95 ++-- src/Chainweb/PayloadProvider.hs | 53 ++ src/Chainweb/PayloadProvider/EVM.hs | 12 +- src/Chainweb/PayloadProvider/EVM/Header.hs | 12 +- src/Chainweb/PayloadProvider/EVM/HeaderDB.hs | 1 - src/Chainweb/PayloadProvider/EVM/SPV.hs | 49 +- .../PayloadProvider/Minimal/Payload.hs | 11 +- src/Chainweb/PayloadProvider/SPV.hs | 504 ++++++++++++++++++ src/Chainweb/SPV.hs | 82 ++- src/Chainweb/SPV/CreateProof.hs | 271 ++++++---- src/Chainweb/SPV/EventProof.hs | 27 +- src/Chainweb/SPV/OutputProof.hs | 16 +- src/Chainweb/SPV/PayloadProof.hs | 49 +- src/Chainweb/SPV/RestAPI.hs | 7 +- src/Chainweb/SPV/RestAPI/Client.hs | 11 +- src/Chainweb/SPV/RestAPI/Server.hs | 60 ++- src/Chainweb/SPV/VerifyProof.hs | 62 ++- src/Chainweb/Utils.hs | 169 +++++- src/P2P/Node.hs | 6 +- 31 files changed, 1312 insertions(+), 484 deletions(-) create mode 100644 src/Chainweb/PayloadProvider/SPV.hs diff --git a/cabal.project b/cabal.project index bd5d0b91b6..3090560120 100644 --- a/cabal.project +++ b/cabal.project @@ -95,14 +95,14 @@ package yet-another-logger source-repository-package type: git location: https://github.com/kadena-io/pact.git - tag: d4f03045df6ba5178a76e534d619b6233ad1c659 - --sha256: 1q4b3i606davn6iyk6az2q7cw5f7llxjhkbqyzw4bsxhrkah7fch + tag: d736a9cc154716274db2d45e94a8cc8ded596b8a + --sha256: 1q4b3i606davn6iyk6az2q7cw5f7llxjhkbqyzw4bsxhrkah7f00 source-repository-package type: git location: https://github.com/kadena-io/pact-5.git - tag: 06f5d75996eba48e4ee165a993326606baaea98c - --sha256: 0wmipbxws45d1axplqx6q4naq0sm3vzh3r354706wiar6a1f556q + tag: 4892ecea6c9f1f84af8078287d23e96a65601a36 + --sha256: 0wmipbxws45d1axplqx6q4naq0sm3vzh3r354706wiar6a1f5500 source-repository-package type: git @@ -128,6 +128,18 @@ source-repository-package tag: e0437bf82e9b4d3fec5ad42ef6e860f4bd04e1b4 --sha256: 1az7jcggcj275djnfsvhdg3n7hjrj6vp8rj137fxrg4hazh0hyzv +source-repository-package + type: git + location: https://github.com/kadena-io/merkle-log.git + tag: 46ad8b617c2cac290bfb09066c1e5f0b6503584b + --sha256: 1az7jcggcj275djnfsvhdg3n7hjrj6vp8rj137fxrg4hazh0hyz0 + +source-repository-package + type: git + location: https://github.com/larskuhtz/hs-hashes.git + tag: 141ffcf37ae57472c148d47f334672681e9d7508 + --sha256: 1az7jcggcj275djnfsvhdg3n7hjrj6vp8rj137fxrg4hazh0hyz0 + -- Required for backward compatibility: -- ixset-typed add difference operator (https://github.com/well-typed/ixset-typed/pull/24) diff --git a/chainweb.cabal b/chainweb.cabal index 1946629cef..c049880ace 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -243,6 +243,7 @@ library , Chainweb.PayloadProvider.P2P.RestAPI , Chainweb.PayloadProvider.P2P.RestAPI.Client , Chainweb.PayloadProvider.P2P.RestAPI.Server + -- , Chainweb.PayloadProvider.SPV , Chainweb.PowHash , Chainweb.Ranked , Chainweb.RestAPI @@ -423,6 +424,7 @@ library , ghc-compact >= 0.1 , growable-vector >= 0.1 , hashable >= 1.4 + , hashes >=0.2.2.0 , heaps >= 0.3 , hourglass >=0.2 , http2 >= 5.2.1 diff --git a/src/Chainweb/BlockHash.hs b/src/Chainweb/BlockHash.hs index 7c1f6f340b..5c4d886c35 100644 --- a/src/Chainweb/BlockHash.hs +++ b/src/Chainweb/BlockHash.hs @@ -19,6 +19,7 @@ {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE DerivingVia #-} -- | -- Module: Chainweb.BlockHash @@ -114,22 +115,16 @@ type BlockHash = BlockHash_ ChainwebMerkleHashAlgorithm newtype BlockHash_ a = BlockHash (MerkleLogHash a) deriving stock (Eq, Ord, Generic) deriving anyclass (NFData) + deriving (IsMerkleLogEntry a ChainwebHashTag) via MerkleRootLogEntry a 'BlockHashTag -instance Show (BlockHash_ a) where +instance MerkleHashAlgorithm a => Show (BlockHash_ a) where show = T.unpack . encodeToText instance Hashable (BlockHash_ a) where hashWithSalt s (BlockHash bytes) = hashWithSalt s bytes {-# INLINE hashWithSalt #-} -instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag (BlockHash_ a) where - type Tag (BlockHash_ a) = 'BlockHashTag - toMerkleNode = encodeMerkleTreeNode - fromMerkleNode = decodeMerkleTreeNode - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} - -encodeBlockHash :: BlockHash_ a -> Put +encodeBlockHash :: MerkleHashAlgorithm a => BlockHash_ a -> Put encodeBlockHash (BlockHash bytes) = encodeMerkleLogHash bytes {-# INLINE encodeBlockHash #-} @@ -137,7 +132,7 @@ decodeBlockHash :: MerkleHashAlgorithm a => Get (BlockHash_ a) decodeBlockHash = BlockHash <$!> decodeMerkleLogHash {-# INLINE decodeBlockHash #-} -instance ToJSON (BlockHash_ a) where +instance MerkleHashAlgorithm a => ToJSON (BlockHash_ a) where toJSON = toJSON . encodeB64UrlNoPaddingText . runPutS . encodeBlockHash toEncoding = b64UrlNoPaddingTextEncoding . runPutS . encodeBlockHash {-# INLINE toJSON #-} @@ -148,7 +143,7 @@ instance MerkleHashAlgorithm a => FromJSON (BlockHash_ a) where . (runGetS decodeBlockHash <=< decodeB64UrlNoPaddingText) {-# INLINE parseJSON #-} -instance ToJSONKey (BlockHash_ a) where +instance MerkleHashAlgorithm a => ToJSONKey (BlockHash_ a) where toJSONKey = toJSONKeyText $ encodeB64UrlNoPaddingText . runPutS . encodeBlockHash {-# INLINE toJSONKey #-} @@ -162,11 +157,11 @@ nullBlockHash :: MerkleHashAlgorithm a => BlockHash_ a nullBlockHash = BlockHash nullHashBytes {-# INLINE nullBlockHash #-} -blockHashToText :: BlockHash_ a -> T.Text +blockHashToText :: MerkleHashAlgorithm a => BlockHash_ a -> T.Text blockHashToText = encodeB64UrlNoPaddingText . runPutS . encodeBlockHash {-# INLINE blockHashToText #-} -blockHashToTextShort :: BlockHash_ a -> T.Text +blockHashToTextShort :: MerkleHashAlgorithm a => BlockHash_ a -> T.Text blockHashToTextShort = T.take 6 . blockHashToText blockHashFromText diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index f1583b6bf5..cf20049d9a 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -177,7 +177,7 @@ import Data.Hashable import Data.IORef import Data.Kind import Data.Memory.Endian qualified as BA -import Data.MerkleLog hiding (Actual, Expected, MerkleHash) +import Data.MerkleLog.Root import Data.Text qualified as T import Data.Word import GHC.Generics (Generic) @@ -658,7 +658,7 @@ isGenesisBlockHeader b = -- genesisParentBlockHash :: HasChainId p => ChainwebVersion -> p -> BlockHash genesisParentBlockHash v p = BlockHash $ MerkleLogHash - $ merkleRoot $ merkleTree @ChainwebMerkleHashAlgorithm + $ merkleRoot @ChainwebMerkleHashAlgorithm [ InputNode "CHAINWEB_GENESIS" , encodeMerkleInputNode encodeChainwebVersionCode (_versionCode v) , encodeMerkleInputNode encodeChainId (_chainId p) diff --git a/src/Chainweb/BlockPayloadHash.hs b/src/Chainweb/BlockPayloadHash.hs index 02d3a4dee0..ecaec15520 100644 --- a/src/Chainweb/BlockPayloadHash.hs +++ b/src/Chainweb/BlockPayloadHash.hs @@ -8,6 +8,8 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE DerivingVia #-} -- | -- Module: Chainweb.BlockPayloadHash @@ -36,7 +38,6 @@ import Control.DeepSeq import Control.Monad import Data.Aeson -import Data.ByteArray qualified as BA import Data.Hashable import GHC.Generics (Generic) @@ -84,11 +85,11 @@ type BlockPayloadHash = BlockPayloadHash_ ChainwebMerkleHashAlgorithm newtype BlockPayloadHash_ a = BlockPayloadHash (MerkleLogHash a) deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) - deriving newtype (BA.ByteArrayAccess) - deriving newtype (Hashable, ToJSON, FromJSON) + deriving newtype (Bytes, Hashable, ToJSON, FromJSON) deriving newtype (ToJSONKey, FromJSONKey) + deriving (IsMerkleLogEntry a ChainwebHashTag) via MerkleRootLogEntry a 'BlockPayloadHashTag -encodeBlockPayloadHash :: BlockPayloadHash_ a -> Put +encodeBlockPayloadHash :: MerkleHashAlgorithm a => BlockPayloadHash_ a -> Put encodeBlockPayloadHash (BlockPayloadHash w) = encodeMerkleLogHash w decodeBlockPayloadHash @@ -96,14 +97,7 @@ decodeBlockPayloadHash => Get (BlockPayloadHash_ a) decodeBlockPayloadHash = BlockPayloadHash <$!> decodeMerkleLogHash -instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag (BlockPayloadHash_ a) where - type Tag (BlockPayloadHash_ a) = 'BlockPayloadHashTag - toMerkleNode = encodeMerkleTreeNode - fromMerkleNode = decodeMerkleTreeNode - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} - -instance HasTextRepresentation BlockPayloadHash where +instance MerkleHashAlgorithm a => HasTextRepresentation (BlockPayloadHash_ a) where toText (BlockPayloadHash h) = toText h fromText = fmap BlockPayloadHash . fromText {-# INLINE toText #-} diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 27c0241c8a..bdd03408ab 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -26,8 +26,6 @@ module Chainweb.Chainweb.CutResources , cutNetworks ) where -import Control.Monad.Catch - import Prelude hiding (log) import qualified Network.HTTP.Client as HTTP diff --git a/src/Chainweb/Crypto/MerkleLog.hs b/src/Chainweb/Crypto/MerkleLog.hs index ea160f0b80..9dddb80c31 100644 --- a/src/Chainweb/Crypto/MerkleLog.hs +++ b/src/Chainweb/Crypto/MerkleLog.hs @@ -6,6 +6,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE RankNTypes #-} @@ -117,6 +118,7 @@ module Chainweb.Crypto.MerkleLog -- * Merkle Log , MerkleLog(..) +, _merkleLogTree , HasMerkleLog(..) , MkLogType , merkleLog @@ -160,27 +162,26 @@ module Chainweb.Crypto.MerkleLog import Control.Monad.Catch -import Crypto.Hash.Algorithms - -import qualified Data.ByteArray as BA -import qualified Data.ByteString as B +import Data.Array.Byte +import Data.ByteString qualified as B +import Data.ByteString.Builder qualified as BB +import Data.ByteString.Short qualified as BS import Data.Coerce import Data.Foldable import Data.Kind -import Data.Memory.Endian -import qualified Data.Memory.Endian as BA import Data.MerkleLog hiding (Expected, Actual) +import Data.MerkleLog.V1 qualified as V1 import Data.Proxy -import qualified Data.Text as T -import qualified Data.Vector as V +import Data.Text qualified as T +import Data.Vector qualified as V import Data.Word +import Data.Hash.SHA2 +import Data.Hash.Keccak import Foreign.Storable import GHC.TypeNats -import System.IO.Unsafe - -- internal modules import Data.Singletons @@ -211,23 +212,16 @@ expectedTreeNodeException = MerkleLogWrongNodeTypeException -- -------------------------------------------------------------------------- -- -- Internal Utils -fromWordBE :: forall w b . BA.ByteArray b => ByteSwap w => w -> b -fromWordBE w = BA.allocAndFreeze (sizeOf (undefined :: w)) $ \ptr -> poke ptr (BA.toBE w) - -unsafeToWordBE :: BA.ByteSwap w => BA.ByteArrayAccess b => b -> w -unsafeToWordBE bytes = BA.fromBE . unsafeDupablePerformIO $ BA.withByteArray bytes peek - toWordBE - :: forall w b m + :: forall w m . MonadThrow m - => BA.ByteSwap w - => BA.ByteArrayAccess b - => b + => ByteSwap w + => Storable w + => B.ByteString -> m w -toWordBE bytes - | BA.length bytes < sizeOf (undefined :: w) = throwM - $ MerkleLogDecodeException "failed to parse Word from bytes: not enough bytes" - | otherwise = return $! unsafeToWordBE bytes +toWordBE bs = case peekByteString bs of + Left e -> throwM $ MerkleLogDecodeException e + Right x -> return x -- -------------------------------------------------------------------------- -- -- $inputs @@ -288,16 +282,14 @@ tagVal = fromIntegral $ natVal (Proxy @(MerkleTagVal u t)) -- -------------------------------------------------------------------------- -- -- Hash Algorithms -type MerkleHashAlgorithm = HashAlgorithm - class MerkleHashAlgorithmName a where merkleHashAlgorithmName :: T.Text -instance MerkleHashAlgorithmName SHA512t_256 where +instance MerkleHashAlgorithmName Sha2_512_256 where merkleHashAlgorithmName = "SHA512t_256" {-# INLINE merkleHashAlgorithmName #-} -instance MerkleHashAlgorithmName Keccak_256 where +instance MerkleHashAlgorithmName Keccak256 where merkleHashAlgorithmName = "Keccak_256" {-# INLINE merkleHashAlgorithmName #-} @@ -322,17 +314,17 @@ class (MerkleHashAlgorithm a, InUniverse u (Tag b)) => IsMerkleLogEntry a u b | toMerkleNode :: b - -> MerkleNodeType a B.ByteString + -> MerkleNodeType a fromMerkleNode - :: MerkleNodeType a B.ByteString + :: MerkleNodeType a -> Either SomeException b fromMerkleNodeM :: forall a u b m . MonadThrow m => IsMerkleLogEntry a u b - => MerkleNodeType a B.ByteString + => MerkleNodeType a -> m b fromMerkleNodeM = either throwM return . fromMerkleNode @a {-# INLINE fromMerkleNodeM #-} @@ -420,15 +412,18 @@ data MerkleLog a u (h :: [Type]) (b :: Type) = MerkleLog -- Note: For creating proofs this isn't needed. Should we make this -- lazy, too? For large entries it may be somewhat costly to pull it -- from the store. - - , _merkleLogTree :: {- Lazy -} MerkleTree a - -- ^ The merkle tree for the entries of the log. It is required to - -- compute the root and for creating a merkle proofs. - -- - -- For some types, computing the merkle tree can be expensive. Therefore - -- it is instantiated lazily and only forced if actually required. } +_merkleLogTree + :: forall a u h b + . MerkleHashAlgorithm a + => MerkleLog a u h b + -> V1.MerkleTree a +_merkleLogTree = V1.merkleTree + . toList + . mapLogEntries (toMerkleNodeTagged @a) + . _merkleLogEntries + -- | Class of types which can be represented as a Merkle tree log. -- -- An instance of 'HasMerkleLog' can be encoded as @@ -459,10 +454,9 @@ class (MerkleUniverse u, MerkleHashAlgorithm a) => HasMerkleLog a u b | b -> u w type MkLogType a u b = MerkleLog a u (MerkleLogHeader b) (MerkleLogBody b) -- | Create a 'MerkleLog' from a 'MerkleRoot' and a sequence of 'MerkleLogEntry's. --- The '_merkleLogTree' fields is instantiated lazily. -- merkleLog - :: forall a u h b + :: forall (a :: Type) u h b . MerkleHashAlgorithm a => MerkleRoot a -> MerkleLogEntries a u h b @@ -470,22 +464,19 @@ merkleLog merkleLog root entries = MerkleLog { _merkleLogRoot = root , _merkleLogEntries = entries - , _merkleLogTree = {- Lazy -} merkleTree - $ toList - $ mapLogEntries (toMerkleNodeTagged @a) entries } --- | /Internal:/ Create a representation Merkle nodes that are tagged with the --- respedtive type from the Merkle universe. +-- | /Internal:/ Create a representation of Merkle nodes that are tagged with the +-- respective type from the Merkle universe. -- toMerkleNodeTagged :: forall a u b . IsMerkleLogEntry a u b => b - -> MerkleNodeType a B.ByteString + -> MerkleNodeType a toMerkleNodeTagged b = case toMerkleNode @a @u @b b of - InputNode bytes -> InputNode @a @B.ByteString - $ fromWordBE @Word16 tag <> bytes + InputNode bs -> InputNode @a + $ buildByteString $ BB.word16BE tag <> BB.byteString bs TreeNode r -> TreeNode @a r where tag :: Word16 @@ -498,18 +489,20 @@ fromMerkleNodeTagged :: forall a u b m . MonadThrow m => IsMerkleLogEntry a u b - => MerkleNodeType a B.ByteString + => MerkleNodeType a -> m b -fromMerkleNodeTagged (InputNode bytes) = do - w16 <- toWordBE @Word16 bytes +fromMerkleNodeTagged (InputNode bs) = do + w16 <- toWordBE @Word16 bs if w16 /= tag then throwM $ MerkleLogWrongTagException (Expected (sshow tag)) (Actual (sshow w16)) - else fromMerkleNodeM @a $ InputNode (B.drop 2 bytes) + else fromMerkleNodeM @a $ InputNode (B.drop 2 bs) where tag = tagVal @u @(Tag b) fromMerkleNodeTagged r = fromMerkleNodeM @a r + + -- | 'IsMerkleLog' values often include a hash of the value itself, which -- represents cyclic dependency of a value on itself. This function allows to -- create such an value from its representation as a sequence of merkle log @@ -522,12 +515,12 @@ newMerkleLog => MerkleLogEntries a u h b -> MerkleLog a u h b newMerkleLog entries = MerkleLog - { _merkleLogRoot = merkleRoot tree + { _merkleLogRoot = merkleRoot + $ toList + $ mapLogEntries (toMerkleNodeTagged @a) + $ entries , _merkleLogEntries = entries - , _merkleLogTree = tree } - where - tree = merkleTree $ toList $ mapLogEntries (toMerkleNodeTagged @a) entries -- | /Internal:/ Get (first) header entry of given type from a Merkle log. -- @@ -543,9 +536,9 @@ class Index c (Hdr b) ~ i => HasHeader_ a u c b i | i b -> c where instance HasHeader_ a u c (MerkleLog a u (c ': t) s) 'Z where type Hdr (MerkleLog a u (c ': t) s) = (c ': t) - header (MerkleLog _ (c :+: _) _) = c + header (MerkleLog _ (c :+: _)) = c headerPos = 0 - headerDict (MerkleLog _ (c :+: _) _) = Dict c + headerDict (MerkleLog _ (c :+: _)) = Dict c -- TODO: -- @@ -558,9 +551,9 @@ instance => HasHeader_ a u c (MerkleLog a u (x ': t) s) ('S i) where type Hdr (MerkleLog a u (x ': t) s) = (x ': t) - header (MerkleLog x (_ :+: t) y) = header @a @u (MerkleLog @a x t y) + header (MerkleLog x (_ :+: t)) = header @a @u (MerkleLog @a x t) headerPos = succ $ headerPos @a @u @c @(MerkleLog a u t s) - headerDict (MerkleLog x (_ :+: t) y) = headerDict @a @u (MerkleLog @a x t y) + headerDict (MerkleLog x (_ :+: t)) = headerDict @a @u (MerkleLog @a x t) type HasHeader a u c b = HasHeader_ a u c b (Index c (Hdr b)) @@ -593,7 +586,7 @@ computeMerkleLogRoot . HasMerkleLog a u b => b -> MerkleRoot a -computeMerkleLogRoot = merkleRoot . _merkleLogTree . toLog @a +computeMerkleLogRoot = _merkleLogRoot . toLog @a {-# INLINE computeMerkleLogRoot #-} -- -------------------------------------------------------------------------- -- @@ -618,8 +611,8 @@ headerProof => HasHeader a u c (MkLogType a u b) => HasMerkleLog a u b => b - -> m (MerkleProof a) -headerProof = uncurry3 merkleProof . headerTree @c @a + -> m (V1.MerkleProof a) +headerProof = uncurry3 (V1.merkleTreeProof @a) . headerTree @c @a {-# INLINE headerProof #-} -- | Create the parameters for creating nested inclusion proofs with the @@ -633,7 +626,7 @@ headerTree . HasHeader a u c (MkLogType a u b) => HasMerkleLog a u b => b - -> (MerkleNodeType a B.ByteString, Int, MerkleTree a) + -> (MerkleNodeType a, Int, V1.MerkleTree a) headerTree b = (node, p, _merkleLogTree @a mlog) where mlog = toLog @a b @@ -652,7 +645,7 @@ headerTree_ . HasHeader a u c (MkLogType a u b) => HasMerkleLog a u b => b - -> (Int, MerkleTree a) + -> (Int, V1.MerkleTree a) headerTree_ b = (p, _merkleLogTree @a mlog) where mlog = toLog @a b @@ -672,8 +665,8 @@ bodyProof => b -> Int -- ^ the index in the body of the log - -> m (MerkleProof a) -bodyProof b = uncurry3 merkleProof . bodyTree @a b + -> m (V1.MerkleProof a) +bodyProof b = uncurry3 V1.merkleTreeProof . bodyTree @a b {-# INLINE bodyProof #-} -- | Create the parameters for creating nested inclusion proofs with the @@ -688,7 +681,7 @@ bodyTree => b -> Int -- ^ the index in the body of the log - -> (MerkleNodeType a B.ByteString, Int, MerkleTree a) + -> (MerkleNodeType a, Int, V1.MerkleTree a) bodyTree b i = (node, i_, _merkleLogTree @a mlog) where mlog = toLog @a b @@ -707,7 +700,7 @@ bodyTree_ => b -> Int -- ^ the index in the body of the log - -> (Int, MerkleTree a) + -> (Int, V1.MerkleTree a) bodyTree_ b i = (i_, _merkleLogTree @a mlog) where mlog = toLog @a b @@ -719,11 +712,11 @@ proofSubject :: forall a u b m . MonadThrow m => IsMerkleLogEntry a u b - => MerkleProof a + => V1.MerkleProof a -> m b proofSubject p = fromMerkleNodeTagged @a subj where - MerkleProofSubject subj = _merkleProofSubject p + V1.MerkleProofSubject subj = V1._merkleProofSubject p {-# INLINE proofSubject #-} -- -------------------------------------------------------------------------- -- @@ -732,43 +725,43 @@ proofSubject p = fromMerkleNodeTagged @a subj encodeMerkleInputNode :: (b -> Put) -> b - -> MerkleNodeType a B.ByteString + -> MerkleNodeType a encodeMerkleInputNode encode = InputNode . runPutS . encode decodeMerkleInputNode :: MonadThrow m => Get b - -> MerkleNodeType a B.ByteString + -> MerkleNodeType a -> m b -decodeMerkleInputNode decode (InputNode bytes) = runGetS decode bytes +decodeMerkleInputNode decode (InputNode bs) = runGetS decode bs decodeMerkleInputNode _ (TreeNode _) = throwM expectedInputNodeException -encodeMerkleTreeNode :: Coercible a (MerkleRoot alg) => a -> MerkleNodeType alg x +encodeMerkleTreeNode :: Coercible a (MerkleRoot alg) => a -> MerkleNodeType alg encodeMerkleTreeNode = TreeNode . coerce decodeMerkleTreeNode :: MonadThrow m => Coercible (MerkleRoot alg) a - => MerkleNodeType alg x + => MerkleNodeType alg -> m a -decodeMerkleTreeNode (TreeNode bytes) = return $! coerce bytes +decodeMerkleTreeNode (TreeNode bs) = return $! coerce bs decodeMerkleTreeNode (InputNode _) = throwM expectedTreeNodeException -- -------------------------------------------------------------------------- -- -- Support for Deriving Via --- | Support for deriving IsMerkleLogEntry for types that are an instance of --- 'BA.ByteArray' via the @DerivingVia@ extension. +-- | Support for deriving IsMerkleLogEntry for types that are Coercible with +-- 'ByteArray' via the @DerivingVia@ extension. -- newtype ByteArrayMerkleLogEntry u (t :: u) b = ByteArrayMerkleLogEntry b instance - (MerkleHashAlgorithm a, InUniverse u t, BA.ByteArray b) + (MerkleHashAlgorithm a, InUniverse u t, Coercible ByteArray b) => IsMerkleLogEntry a u (ByteArrayMerkleLogEntry u (t :: u) b) where type Tag (ByteArrayMerkleLogEntry u t b) = t - toMerkleNode (ByteArrayMerkleLogEntry b) = InputNode $ BA.convert b - fromMerkleNode (InputNode x) = return $! ByteArrayMerkleLogEntry $! BA.convert x + toMerkleNode (ByteArrayMerkleLogEntry b) = InputNode (BS.fromShort $ coerce b) + fromMerkleNode (InputNode x) = return $! ByteArrayMerkleLogEntry (fromByteString x) fromMerkleNode (TreeNode _) = throwM expectedInputNodeException {-# INLINE toMerkleNode #-} {-# INLINE fromMerkleNode #-} @@ -816,7 +809,7 @@ instance (MerkleHashAlgorithm a, InUniverse u t) => IsMerkleLogEntry a u (Word16BeMerkleLogEntry (t :: u)) where type Tag (Word16BeMerkleLogEntry t) = t - toMerkleNode = InputNode . fromWordBE . _getWord16BeLogEntry + toMerkleNode = InputNode . buildByteString . BB.word16BE . _getWord16BeLogEntry fromMerkleNode (InputNode x) = Word16BeMerkleLogEntry <$> toWordBE x fromMerkleNode (TreeNode _) = throwM expectedInputNodeException {-# INLINE toMerkleNode #-} @@ -832,7 +825,7 @@ instance (MerkleHashAlgorithm a, InUniverse u t) => IsMerkleLogEntry a u (Word32BeMerkleLogEntry (t :: u)) where type Tag (Word32BeMerkleLogEntry t) = t - toMerkleNode = InputNode . fromWordBE . _getWord32BeLogEntry + toMerkleNode = InputNode . buildByteString . BB.word32BE . _getWord32BeLogEntry fromMerkleNode (InputNode x) = Word32BeMerkleLogEntry <$> toWordBE x fromMerkleNode (TreeNode _) = throwM expectedInputNodeException {-# INLINE toMerkleNode #-} @@ -848,7 +841,7 @@ instance (MerkleHashAlgorithm a, InUniverse u t) => IsMerkleLogEntry a u (Word64BeMerkleLogEntry (t :: u)) where type Tag (Word64BeMerkleLogEntry t) = t - toMerkleNode = InputNode . fromWordBE . _getWord64BeLogEntry + toMerkleNode = InputNode . buildByteString . BB.word64BE . _getWord64BeLogEntry fromMerkleNode (InputNode x) = Word64BeMerkleLogEntry <$> toWordBE x fromMerkleNode (TreeNode _) = throwM expectedInputNodeException {-# INLINE toMerkleNode #-} diff --git a/src/Chainweb/MerkleLogHash.hs b/src/Chainweb/MerkleLogHash.hs index 689cbe5034..5a0af28903 100644 --- a/src/Chainweb/MerkleLogHash.hs +++ b/src/Chainweb/MerkleLogHash.hs @@ -22,7 +22,8 @@ module Chainweb.MerkleLogHash ( -- * MerkleLogHash - MerkleLogHash(..) -- FIXME import this only internally + MerkleLogHash(..) +, merkleLogHashBytes , MerkleLogHashBytesCount , merkleLogHashBytesCount , merkleLogHash @@ -39,26 +40,23 @@ import Control.Monad.Catch (MonadThrow, displayException, throwM) import Data.Aeson (FromJSON(..), FromJSONKey(..), ToJSON(..), ToJSONKey(..)) import Data.Aeson.Types (FromJSONKeyFunction(..), toJSONKeyText) import Data.Bits -import qualified Data.ByteArray as BA import qualified Data.ByteString as B import Data.Hashable (Hashable(..)) import Data.MerkleLog hiding (Expected, Actual) import Data.Proxy import qualified Data.Text as T -import Foreign.Storable - import GHC.Generics import GHC.Stack (HasCallStack) import GHC.TypeNats -import System.IO.Unsafe - -- internal imports -import Chainweb.Crypto.MerkleLog import Chainweb.Utils import Chainweb.Utils.Serialization +import Data.Either +import Data.Array.Byte +import Data.Hash.Class.Mutable -- -------------------------------------------------------------------------- -- -- MerkleLogHash @@ -75,22 +73,29 @@ merkleLogHashBytesCount = natVal $ Proxy @MerkleLogHashBytesCount newtype MerkleLogHash a = MerkleLogHash (MerkleRoot a) deriving stock (Show, Eq, Ord, Generic) - deriving newtype (BA.ByteArrayAccess) deriving anyclass (NFData) +instance Bytes (MerkleLogHash a) where + byteArray (MerkleLogHash r) = merkleRootBytes r + {-# INLINE byteArray #-} + -- | Smart constructor -- merkleLogHash :: MonadThrow m - => MerkleHashAlgorithm a + => IncrementalHash a => B.ByteString -> m (MerkleLogHash a) merkleLogHash = fmap MerkleLogHash . decodeMerkleRoot {-# INLINE merkleLogHash #-} +merkleLogHashBytes :: MerkleLogHash a -> ByteArray +merkleLogHashBytes (MerkleLogHash r) = merkleRootBytes r +{-# INLINE merkleLogHashBytes #-} + unsafeMerkleLogHash :: HasCallStack - => MerkleHashAlgorithm a + => IncrementalHash a => B.ByteString -> MerkleLogHash a unsafeMerkleLogHash = MerkleLogHash @@ -98,28 +103,27 @@ unsafeMerkleLogHash = MerkleLogHash . decodeMerkleRoot {-# INLINE unsafeMerkleLogHash #-} -encodeMerkleLogHash :: MerkleLogHash a -> Put +encodeMerkleLogHash :: MerkleHashAlgorithm a => MerkleLogHash a -> Put encodeMerkleLogHash (MerkleLogHash bytes) = putByteString $ encodeMerkleRoot bytes {-# INLINE encodeMerkleLogHash #-} decodeMerkleLogHash - :: MerkleHashAlgorithm a + :: IncrementalHash a => Get (MerkleLogHash a) decodeMerkleLogHash = unsafeMerkleLogHash <$> getByteString (int merkleLogHashBytesCount) {-# INLINE decodeMerkleLogHash #-} instance Hashable (MerkleLogHash a) where - hashWithSalt s = xor s - . unsafeDupablePerformIO . flip BA.withByteArray (peek @Int) + hashWithSalt s = xor s . int . fromRight 0 . peekByteArray64 -- BlockHashes are already cryptographically strong hashes -- that include the chain id. {-# INLINE hashWithSalt #-} -nullHashBytes :: MerkleHashAlgorithm a => MerkleLogHash a +nullHashBytes :: IncrementalHash a => MerkleLogHash a nullHashBytes = unsafeMerkleLogHash $ B.replicate (int merkleLogHashBytesCount) 0x00 {-# NOINLINE nullHashBytes #-} -oneHashBytes :: MerkleHashAlgorithm a => MerkleLogHash a +oneHashBytes :: IncrementalHash a => MerkleLogHash a oneHashBytes = unsafeMerkleLogHash $ B.replicate (int merkleLogHashBytesCount) 0xff {-# NOINLINE oneHashBytes #-} @@ -128,7 +132,7 @@ merkleLogHashToText = encodeB64UrlNoPaddingText . runPutS . encodeMerkleLogHash {-# INLINE merkleLogHashToText #-} merkleLogHashFromText - :: MerkleHashAlgorithm a + :: IncrementalHash a => MonadThrow m => T.Text -> m (MerkleLogHash a) @@ -143,13 +147,13 @@ instance MerkleHashAlgorithm a => HasTextRepresentation (MerkleLogHash a) where {-# INLINE fromText #-} instance MerkleHashAlgorithm a => ToJSON (MerkleLogHash a) where - toJSON = toJSON . toText - toEncoding = toEncoding . toText + toJSON = toJSON . merkleLogHashToText + toEncoding = toEncoding . merkleLogHashToText {-# INLINE toJSON #-} {-# INLINE toEncoding #-} instance MerkleHashAlgorithm a => ToJSONKey (MerkleLogHash a) where - toJSONKey = toJSONKeyText toText + toJSONKey = toJSONKeyText merkleLogHashToText {-# INLINE toJSONKey #-} instance MerkleHashAlgorithm a => FromJSON (MerkleLogHash a) where diff --git a/src/Chainweb/MerkleUniverse.hs b/src/Chainweb/MerkleUniverse.hs index 6d3c0bbb5c..d68155736f 100644 --- a/src/Chainweb/MerkleUniverse.hs +++ b/src/Chainweb/MerkleUniverse.hs @@ -36,7 +36,7 @@ module Chainweb.MerkleUniverse import Control.DeepSeq import Control.Monad.Catch -import Crypto.Hash.Algorithms +import Data.Hash.SHA2 import Data.Aeson import qualified Data.Text as T @@ -53,7 +53,7 @@ import Chainweb.Utils -- -------------------------------------------------------------------------- -- -- Chainweb Merkle Hash Algorithm -type ChainwebMerkleHashAlgorithm = SHA512t_256 +type ChainwebMerkleHashAlgorithm = Sha2_512_256 -- -------------------------------------------------------------------------- -- -- Chainweb Merkle Universe @@ -177,7 +177,7 @@ instance MerkleUniverse ChainwebHashTag where type MerkleTagVal ChainwebHashTag 'EthExcessBlobGasTag = 0x0052 type MerkleTagVal ChainwebHashTag 'EthParentBeaconBlockRootTag = 0x0053 -instance HashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Void where +instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Void where type Tag Void = 'VoidTag toMerkleNode = \case fromMerkleNode _ = throwM diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index 05674afec5..42f2ff3508 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -45,7 +45,7 @@ import Control.Monad.Except (throwError) import Control.Monad.IO.Class (liftIO) import Control.Monad.STM -import Data.Binary.Builder (fromByteString) +import Data.Binary.Builder qualified as BB (fromByteString) import Data.Proxy (Proxy(..)) import Network.Wai.EventSource (ServerEvent(..), eventSourceAppIO) @@ -239,11 +239,11 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> do Just WorkOutdated -> do logFunctionText logger Debug $ "sent work outdated event to miner on chain " <> toText cid - return $ ServerEvent (Just $ fromByteString "New Cut") Nothing [] + return $ ServerEvent (Just $ BB.fromByteString "New Cut") Nothing [] Just WorkRefreshed -> do logFunctionText logger Debug $ "sent work outdated event to miner on chain " <> toText cid - return $ ServerEvent (Just $ fromByteString "Refreshed Block") Nothing [] + return $ ServerEvent (Just $ BB.fromByteString "Refreshed Block") Nothing [] where logger = addLabel ("chain", toText cid) (_coordLogger mr) diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs index c0a2ddad3b..eda4129871 100644 --- a/src/Chainweb/Pact4/SPV.hs +++ b/src/Chainweb/Pact4/SPV.hs @@ -47,8 +47,6 @@ import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Text.Read (readMaybe) -import Crypto.Hash.Algorithms - import qualified Ethereum.Header as EthHeader import Ethereum.Misc import Ethereum.Receipt @@ -84,6 +82,7 @@ import qualified Pact.Types.Info as Pact4 import qualified Pact.Types.PactValue as Pact4 import qualified Pact.Types.Runtime as Pact4 import qualified Pact.Types.SPV as Pact4 +import Chainweb.MerkleUniverse catchAndDisplaySPVError :: BlockHeader -> ExceptT Text IO a -> ExceptT Text IO a catchAndDisplaySPVError bh = @@ -296,7 +295,7 @@ verifyCont bdb bh (Pact4.ContProof cp) = runExceptT $ do -- | Extract a 'TransactionOutputProof' from a generic pact object -- -extractProof :: Bool -> Pact4.Object Pact4.Name -> Either Text (TransactionOutputProof SHA512t_256) +extractProof :: Bool -> Pact4.Object Pact4.Name -> Either Text (TransactionOutputProof ChainwebMerkleHashAlgorithm) extractProof False o = Pact4.toPactValue (Pact4.TObject o Pact4.noInfo) >>= k where k = aeson (Left . pack) Right diff --git a/src/Chainweb/Pact5/SPV.hs b/src/Chainweb/Pact5/SPV.hs index 84917207cf..856d306d25 100644 --- a/src/Chainweb/Pact5/SPV.hs +++ b/src/Chainweb/Pact5/SPV.hs @@ -21,7 +21,6 @@ import Control.Monad (when) import Control.Monad.Catch (catch, throwM) import Control.Monad.Except (ExceptT, runExceptT, throwError) import Control.Monad.IO.Class (liftIO) -import Crypto.Hash.Algorithms (SHA512t_256) import Data.Aeson qualified as Aeson import Data.Text (Text) import Data.Text.Encoding qualified as Text @@ -31,6 +30,7 @@ import Pact.Core.Hash (Hash(..)) import Pact.Core.PactValue (ObjectData(..), PactValue(..)) import Pact.Core.SPV (ContProof(..), SPVSupport(..)) import Pact.Core.StableEncoding (encodeStable) +import Chainweb.MerkleUniverse pactSPV :: BlockHeaderDb -> BlockHeader -> SPVSupport pactSPV bdb bh = SPVSupport @@ -51,7 +51,7 @@ verifyCont bdb bh (ContProof base64Proof) = runExceptT $ do Left _ -> throwError "verifyCont: Invalid base64-encoded transaction output proof" Right bs -> return bs - outputProof <- case Aeson.decodeStrict' @(TransactionOutputProof SHA512t_256) proofBytes of + outputProof <- case Aeson.decodeStrict' @(TransactionOutputProof ChainwebMerkleHashAlgorithm) proofBytes of Nothing -> throwError "verifyCont: Cannot decode transaction output proof" Just u -> return u @@ -118,9 +118,9 @@ verifySPV bdb bh proofType proof = runExceptT $ do _ -> do throwError $ "Unsupported SPV type: " <> proofType -pactObjectOutputProof :: ObjectData PactValue -> Either Text (TransactionOutputProof SHA512t_256) +pactObjectOutputProof :: ObjectData PactValue -> Either Text (TransactionOutputProof ChainwebMerkleHashAlgorithm) pactObjectOutputProof (ObjectData o) = do - case Aeson.decodeStrict' @(TransactionOutputProof SHA512t_256) $ encodeStable o of + case Aeson.decodeStrict' @(TransactionOutputProof ChainwebMerkleHashAlgorithm) $ encodeStable o of Nothing -> Left "pactObjectOutputProof: Failed to decode proof object" Just outputProof -> Right outputProof diff --git a/src/Chainweb/Payload.hs b/src/Chainweb/Payload.hs index 0bb33152f8..8126cd8546 100644 --- a/src/Chainweb/Payload.hs +++ b/src/Chainweb/Payload.hs @@ -14,6 +14,9 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE UndecidableInstances #-} -- | -- Module: Chainweb.Payload @@ -148,14 +151,15 @@ import Control.Monad.Catch import Data.Aeson import Data.Aeson.Encoding (encodingToLazyByteString, pair) -import qualified Data.Aeson.Types as A -import qualified Data.ByteArray as BA -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as BL +import Data.Aeson.Types qualified as A +import Data.ByteArray qualified as BA +import Data.ByteString qualified as B +import Data.ByteString.Lazy qualified as BL import Data.Hashable import Data.MerkleLog -import qualified Data.Text as T -import qualified Data.Vector as V +import Data.MerkleLog.V1 qualified as V1 +import Data.Text qualified as T +import Data.Vector qualified as V import Data.Void import GHC.Generics (Generic) @@ -172,7 +176,6 @@ import Chainweb.Storage.Table import Chainweb.Utils import Chainweb.Utils.Serialization -import Crypto.Hash.Algorithms -- -------------------------------------------------------------------------- -- -- Block Transactions Hash @@ -182,10 +185,10 @@ type BlockTransactionsHash = BlockTransactionsHash_ ChainwebMerkleHashAlgorithm newtype BlockTransactionsHash_ a = BlockTransactionsHash (MerkleLogHash a) deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) - deriving newtype (BA.ByteArrayAccess) deriving newtype (Hashable, ToJSON, FromJSON) + deriving (IsMerkleLogEntry a ChainwebHashTag) via MerkleRootLogEntry a 'BlockTransactionsHashTag -encodeBlockTransactionsHash :: BlockTransactionsHash_ a -> Put +encodeBlockTransactionsHash :: MerkleHashAlgorithm a => BlockTransactionsHash_ a -> Put encodeBlockTransactionsHash (BlockTransactionsHash w) = encodeMerkleLogHash w decodeBlockTransactionsHash @@ -193,13 +196,6 @@ decodeBlockTransactionsHash => Get (BlockTransactionsHash_ a) decodeBlockTransactionsHash = BlockTransactionsHash <$!> decodeMerkleLogHash -instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag (BlockTransactionsHash_ a) where - type Tag (BlockTransactionsHash_ a) = 'BlockTransactionsHashTag - toMerkleNode = encodeMerkleTreeNode - fromMerkleNode = decodeMerkleTreeNode - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} - instance HasTextRepresentation BlockTransactionsHash where toText (BlockTransactionsHash h) = toText h fromText = fmap BlockTransactionsHash . fromText @@ -214,10 +210,9 @@ type BlockOutputsHash = BlockOutputsHash_ ChainwebMerkleHashAlgorithm newtype BlockOutputsHash_ a = BlockOutputsHash (MerkleLogHash a) deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) - deriving newtype (BA.ByteArrayAccess) deriving newtype (Hashable, ToJSON, FromJSON) -encodeBlockOutputsHash :: BlockOutputsHash_ a -> Put +encodeBlockOutputsHash :: MerkleHashAlgorithm a => BlockOutputsHash_ a -> Put encodeBlockOutputsHash (BlockOutputsHash w) = encodeMerkleLogHash w decodeBlockOutputsHash @@ -499,7 +494,7 @@ newtype PayloadWithOutputsList = PayloadWithOutputsList { _payloadWithOutputsLis -- -------------------------------------------------------------------------- -- -encodeBlockPayloads :: BlockPayload_ a -> B.ByteString +encodeBlockPayloads :: MerkleHashAlgorithm a => BlockPayload_ a -> B.ByteString encodeBlockPayloads BlockPayload{..} = runPutS $ do encodeBlockPayloadHash _blockPayloadPayloadHash encodeBlockTransactionsHash _blockPayloadTransactionsHash @@ -511,7 +506,7 @@ decodeBlockPayloads = runGetS $ BlockPayload <*> decodeBlockTransactionsHash <*> decodeBlockOutputsHash -encodeBlockTransactions :: BlockTransactions_ a -> B.ByteString +encodeBlockTransactions :: MerkleHashAlgorithm a => BlockTransactions_ a -> B.ByteString encodeBlockTransactions txs = runPutS $ do encodeBlockTransactionsHash (_blockTransactionsHash txs) putWord64be (fromIntegral $ V.length (_blockTransactions txs)) @@ -521,7 +516,7 @@ encodeBlockTransactions txs = runPutS $ do putWord64be (fromIntegral $ B.length $ _minerData $ _blockMinerData txs) putByteString (_minerData $ _blockMinerData txs) -decodeBlockTransactions :: (MonadThrow m, HashAlgorithm a) => B.ByteString -> m (BlockTransactions_ a) +decodeBlockTransactions :: (MonadThrow m, MerkleHashAlgorithm a) => B.ByteString -> m (BlockTransactions_ a) decodeBlockTransactions = runGetS $ do hsh <- decodeBlockTransactionsHash txsCount <- fromIntegral <$> getWord64be @@ -537,7 +532,7 @@ decodeBlockTransactions = runGetS $ do , _blockMinerData = minerData } -encodeBlockOutputs :: BlockOutputs_ a -> B.ByteString +encodeBlockOutputs :: MerkleHashAlgorithm a => BlockOutputs_ a -> B.ByteString encodeBlockOutputs bo = runPutS $ do encodeBlockOutputsHash (_blockOutputsHash bo) putWord64be (fromIntegral $ V.length (_blockOutputs bo)) @@ -547,7 +542,7 @@ encodeBlockOutputs bo = runPutS $ do putWord64be (fromIntegral $ B.length $ _coinbaseOutput $ _blockCoinbaseOutput bo) putByteString $ _coinbaseOutput $ _blockCoinbaseOutput bo -decodeBlockOutputs :: (MonadThrow m, HashAlgorithm a) => B.ByteString -> m (BlockOutputs_ a) +decodeBlockOutputs :: (MonadThrow m, MerkleHashAlgorithm a) => B.ByteString -> m (BlockOutputs_ a) decodeBlockOutputs = runGetS $ do hsh <- decodeBlockOutputsHash txsCount <- fromIntegral <$> getWord64be @@ -563,37 +558,37 @@ decodeBlockOutputs = runGetS $ do , _blockCoinbaseOutput = CoinbaseOutput coinbaseData } -encodeTransactionTree :: TransactionTree_ a -> B.ByteString +encodeTransactionTree :: MerkleHashAlgorithm a => TransactionTree_ a -> B.ByteString encodeTransactionTree tt = runPutS $ do encodeBlockTransactionsHash (_transactionTreeHash tt) - let bs = encodeMerkleTree (_transactionTree tt) + let bs = V1.encodeMerkleTree (_transactionTree tt) putWord64be (fromIntegral $ B.length bs) putByteString bs -decodeTransactionTree :: (MonadThrow m, HashAlgorithm a) => B.ByteString -> m (TransactionTree_ a) +decodeTransactionTree :: (MonadThrow m, MerkleHashAlgorithm a) => B.ByteString -> m (TransactionTree_ a) decodeTransactionTree = runGetS $ do hsh <- decodeBlockTransactionsHash sz <- fromIntegral <$> getWord64be bs <- getByteString sz - mt <- decodeMerkleTree bs + mt <- V1.decodeMerkleTree bs return TransactionTree { _transactionTreeHash = hsh , _transactionTree = mt } -encodeOutputTree :: OutputTree_ a -> B.ByteString +encodeOutputTree :: MerkleHashAlgorithm a => OutputTree_ a -> B.ByteString encodeOutputTree ot = runPutS $ do encodeBlockOutputsHash (_outputTreeHash ot) - let bs = encodeMerkleTree (_outputTree ot) + let bs = V1.encodeMerkleTree (_outputTree ot) putWord64be (fromIntegral $ B.length bs) putByteString bs -decodeOutputTree :: (MonadThrow m, HashAlgorithm a) => B.ByteString -> m (OutputTree_ a) +decodeOutputTree :: (MonadThrow m, MerkleHashAlgorithm a) => B.ByteString -> m (OutputTree_ a) decodeOutputTree = runGetS $ do hsh <- decodeBlockOutputsHash sz <- fromIntegral <$> getWord64be bs <- getByteString sz - mt <- decodeMerkleTree bs + mt <- V1.decodeMerkleTree bs return OutputTree { _outputTreeHash = hsh , _outputTree = mt @@ -609,7 +604,7 @@ decodeMinerData = do sz <- fromIntegral <$> getWord64be MinerData <$> getByteString sz -putPayloadData :: PayloadData_ a -> Put +putPayloadData :: MerkleHashAlgorithm a => PayloadData_ a -> Put putPayloadData pd = do -- first encode _payloadDataTransactions: Vector Transaction putWord64be (fromIntegral $ V.length (_payloadDataTransactions pd)) @@ -621,7 +616,7 @@ putPayloadData pd = do encodeBlockTransactionsHash (_payloadDataTransactionsHash pd) encodeBlockOutputsHash (_payloadDataOutputsHash pd) -getPayloadData :: HashAlgorithm a => Get (PayloadData_ a) +getPayloadData :: MerkleHashAlgorithm a => Get (PayloadData_ a) getPayloadData = do txsCount <- fromIntegral <$> getWord64be txs <- replicateM txsCount $ do @@ -640,10 +635,10 @@ getPayloadData = do , _payloadDataOutputsHash = outHash } -decodePayloadData :: (MonadThrow m, HashAlgorithm a) => B.ByteString -> m (PayloadData_ a) +decodePayloadData :: (MonadThrow m, MerkleHashAlgorithm a) => B.ByteString -> m (PayloadData_ a) decodePayloadData = runGetS getPayloadData -encodePayloadData :: PayloadData_ a -> B.ByteString +encodePayloadData :: MerkleHashAlgorithm a => PayloadData_ a -> B.ByteString encodePayloadData = runPutS . putPayloadData encodeCoinbaseOutput :: CoinbaseOutput -> Put @@ -656,7 +651,7 @@ decodeCoinbaseOutput = do sz <- fromIntegral <$> getWord64be CoinbaseOutput <$> getByteString sz -putPayloadWithOutputs :: PayloadWithOutputs_ a -> Put +putPayloadWithOutputs :: MerkleHashAlgorithm a => PayloadWithOutputs_ a -> Put putPayloadWithOutputs pwo = do putWord64be (fromIntegral $ V.length (_payloadWithOutputsTransactions pwo)) forM_ (_payloadWithOutputsTransactions pwo) $ \(tx, txo) -> do @@ -670,10 +665,10 @@ putPayloadWithOutputs pwo = do encodeBlockTransactionsHash (_payloadWithOutputsTransactionsHash pwo) encodeBlockOutputsHash (_payloadWithOutputsOutputsHash pwo) -encodePayloadWithOutputs :: PayloadWithOutputs_ a -> B.ByteString +encodePayloadWithOutputs :: MerkleHashAlgorithm a => PayloadWithOutputs_ a -> B.ByteString encodePayloadWithOutputs = runPutS . putPayloadWithOutputs -getPayloadWithOutputs :: HashAlgorithm a => Get (PayloadWithOutputs_ a) +getPayloadWithOutputs :: MerkleHashAlgorithm a => Get (PayloadWithOutputs_ a) getPayloadWithOutputs = do txsCount <- fromIntegral <$> getWord64be txs <- replicateM txsCount $ do @@ -696,7 +691,7 @@ getPayloadWithOutputs = do , _payloadWithOutputsOutputsHash = outHash } -decodePayloadWithOutputs :: (MonadThrow m, HashAlgorithm a) => B.ByteString -> m (PayloadWithOutputs_ a) +decodePayloadWithOutputs :: (MonadThrow m, MerkleHashAlgorithm a) => B.ByteString -> m (PayloadWithOutputs_ a) decodePayloadWithOutputs = runGetS getPayloadWithOutputs encodePayloadDataList :: PayloadDataList -> B.ByteString @@ -934,7 +929,7 @@ data TransactionTree_ a = TransactionTree -- ^ Root of '_transactionTree'. Primary key of 'TransactionTreeStore. -- Foreign key into 'BlockTransactionsStore'. - , _transactionTree :: !(MerkleTree a) + , _transactionTree :: !(V1.MerkleTree a) } deriving (Show, Eq) @@ -964,12 +959,12 @@ instance MerkleHashAlgorithm a => FromJSON (TransactionTree_ a) where <$!> o .: "hash" <*> (o .: "tree" >>= merkleTreeFromJson) -merkleTreeToJson :: MerkleTree a -> Value -merkleTreeToJson = toJSON . encodeB64UrlNoPaddingText . encodeMerkleTree +merkleTreeToJson :: V1.MerkleTree a -> Value +merkleTreeToJson = toJSON . encodeB64UrlNoPaddingText . V1.encodeMerkleTree -merkleTreeFromJson :: MerkleHashAlgorithm a => Value -> A.Parser (MerkleTree a) +merkleTreeFromJson :: MerkleHashAlgorithm a => Value -> A.Parser (V1.MerkleTree a) merkleTreeFromJson = withText "MerkleTree" $ \t -> either (fail . sshow) return - $ decodeB64UrlNoPaddingText t >>= decodeMerkleTree + $ decodeB64UrlNoPaddingText t >>= V1.decodeMerkleTree -- | Verify the consistency of the MerkleTree of a 'TransactionTree' value. -- @@ -978,7 +973,7 @@ merkleTreeFromJson = withText "MerkleTree" $ \t -> either (fail . sshow) return -- verifyTransactionTree :: MerkleHashAlgorithm a => TransactionTree_ a -> Bool verifyTransactionTree p = _transactionTreeHash p - == BlockTransactionsHash (MerkleLogHash $ merkleRoot $ _transactionTree p) + == BlockTransactionsHash (MerkleLogHash $ V1.merkleTreeRoot $ _transactionTree p) -- -------------------------------------------------------------------------- -- -- Output Merkle Tree @@ -992,7 +987,7 @@ data OutputTree_ a = OutputTree -- ^ Root of '_outputTree'. Primary key of 'OutputTreeStore. Foreign key -- into 'BlockOutputsStore'. - , _outputTree :: !(MerkleTree a) + , _outputTree :: !(V1.MerkleTree a) } deriving (Show, Eq) @@ -1029,7 +1024,7 @@ instance MerkleHashAlgorithm a => FromJSON (OutputTree_ a) where -- verifyOutputTree :: MerkleHashAlgorithm a => OutputTree_ a -> Bool verifyOutputTree p = _outputTreeHash p - == BlockOutputsHash (MerkleLogHash $ merkleRoot $ _outputTree p) + == BlockOutputsHash (MerkleLogHash $ V1.merkleTreeRoot $ _outputTree p) -- -------------------------------------------------------------------------- -- -- Data Creation @@ -1076,8 +1071,7 @@ transactionLog -> TransactionTree_ a -> BlockTransactionsLog a transactionLog txs tree - | _blockTransactionsHash txs == _transactionTreeHash tree - = (toLog txs) { _merkleLogTree = _transactionTree tree } + | _blockTransactionsHash txs == _transactionTreeHash tree = toLog txs | otherwise = error "Transaction tree and block transactions don't match" -- | This forces the 'MerkleTree' which can be an expensive operation. @@ -1118,8 +1112,7 @@ blockOutputLog -> OutputTree_ a -> BlockOutputsLog a blockOutputLog outs tree - | _blockOutputsHash outs == _outputTreeHash tree - = (toLog outs) { _merkleLogTree = _outputTree tree } + | _blockOutputsHash outs == _outputTreeHash tree = toLog outs | otherwise = error "Output tree and block outputs don't match" -- | Create a BlockPayload from 'BlockTransactions' and 'BlockOutputs' diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 11ffd1df0e..2169772422 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -72,6 +72,8 @@ module Chainweb.PayloadProvider , withPayloadProvider -- * SPV +, PayloadSpvException(..) +, renderPayloadSpvException , TransactionIndex(..) , EventIndex(..) , XEventId(..) @@ -838,6 +840,50 @@ payloadStream p = do -- -------------------------------------------------------------------------- -- -- SPV +-- | SPV Exceptions +-- +data PayloadSpvException + = InvalidBlockHeight XEventId + -- ^ The chain has not yet produced a block a the requested height. + | InvalidTransactionIndex XEventId + -- ^ A transaction with that index can not be found in the block + | InvalidEventIndex XEventId + -- ^ An event with that index can not be found in the transaction + | UnsupportedEventType XEventId + -- ^ The event type is not supported by the protocol + | InvalidEvent XEventId T.Text + -- ^ The event is invalid for some reason + | ProofPending XEventId BlockHeight + -- ^ The proof is not yet available on the target chain, which currently + -- is at the given block height. + deriving (Show, Eq) + +instance Exception PayloadSpvException where + displayException = T.unpack . renderPayloadSpvException + +renderPayloadSpvException :: PayloadSpvException -> T.Text +renderPayloadSpvException (InvalidBlockHeight e) = + "The chain has not yet produced a block a the requested height. " + <> encodeToText e +renderPayloadSpvException (InvalidTransactionIndex e) = + "A transaction with that index can not be found in the block. " + <> encodeToText e +renderPayloadSpvException (InvalidEventIndex e) = + "An event with that index can not be found in the transaction. " + <> encodeToText e +renderPayloadSpvException (UnsupportedEventType e) = + "The event type is not supported by the protocol. " + <> encodeToText e +renderPayloadSpvException (InvalidEvent e msg) = + "The event is invalid. " + <> encodeToText e + <> ". " <> msg +renderPayloadSpvException (ProofPending e curHeight) = + "The proof is not yet available on the target chain. " + <> encodeToText e + <> ". Current target chain height: " + <> sshow curHeight + newtype TransactionIndex = TransactionIndex Natural deriving (Show, Eq, Ord, Generic) deriving newtype (FromJSON, ToJSON, Num, Enum, Real, Integral) @@ -866,6 +912,13 @@ data XEventId = XEventId } deriving (Show, Eq, Generic) +instance ToJSON XEventId where + toJSON o = object + [ "height" .= _xEventBlockHeight o + , "transactionIndex" .= _xEventTransactionIndex o + , "eventIndex" .= _xEventEventIndex o + ] + -- | Preliminary Type for SPV Event Proofs. -- newtype SpvProof = SpvProof Value diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 3a010a9e04..683890e9ee 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -92,7 +92,6 @@ import Data.Maybe import Data.PQueue import Data.Singletons import Data.Text qualified as T -import Data.Tuple import Ethereum.Misc import Ethereum.Misc qualified as EVM import Ethereum.Misc qualified as Ethereum @@ -431,13 +430,6 @@ data EvmHeaderNotFoundException deriving (Eq, Show, Generic) instance Exception EvmHeaderNotFoundException -data LogEntryNotFoundException - = InvalidTransactionIndex XEventId - | InvalidEventIndex XEventId - | AmbiguousLogEntries XEventId [RpcLogEntry] - deriving (Eq, Show, Generic) -instance Exception LogEntryNotFoundException - data InvalidEvmState = EvmGenesisHeaderNotFound deriving (Eq, Show, Generic) @@ -1288,7 +1280,7 @@ getLogEntry -> XEventId -> IO LogEntry getLogEntry p e = do - -- it would be nice if we could just use the long entry index, but that is + -- it would be nice if we could just use the log entry index, but that is -- over the whole block and not just the tx logs <- getLogEntries p (int $ _xEventBlockHeight e) case filter ftx logs of @@ -1308,7 +1300,7 @@ getSpvProof getSpvProof p e = do le <- getLogEntry p e lf Info $ "got logEntry: " <> encodeToText le - ld <- parseXLogData (_chainwebVersion p) (_xEventBlockHeight e) le + ld <- parseXLogData (_chainwebVersion p) e le lf Info $ "got logData: " <> sshow ld return $ SpvProof $ object [ "origin" .= object diff --git a/src/Chainweb/PayloadProvider/EVM/Header.hs b/src/Chainweb/PayloadProvider/EVM/Header.hs index e409fb0d34..eeb9526218 100644 --- a/src/Chainweb/PayloadProvider/EVM/Header.hs +++ b/src/Chainweb/PayloadProvider/EVM/Header.hs @@ -111,7 +111,8 @@ import Data.ByteString.Short qualified as BS import Data.ByteString.Short qualified as SBS import Data.Function import Data.Hashable (Hashable(..)) -import Data.MerkleLog +import Data.MerkleLog.Common +import Data.MerkleLog.V1 qualified as V1 import Data.Ratio ((%)) import Data.Text qualified as T import Data.Void @@ -390,15 +391,18 @@ headerProof => a ~ ChainwebMerkleHashAlgorithm => HasHeader a ChainwebHashTag c (MkLogType a ChainwebHashTag Header) => Header - -> m (MerkleProof a) + -> m (V1.MerkleProof a) headerProof = MerkleLog.headerProof @c {-# INLINE headerProof #-} -- | Runs a header proof. Returns the BlockPayloadHash of the EVM execution -- header for which inclusion is proven. -- -runHeaderProof :: MerkleProof ChainwebMerkleHashAlgorithm -> BlockPayloadHash -runHeaderProof = BlockPayloadHash . MerkleLogHash . runMerkleProof +runHeaderProof + :: MonadThrow m + => V1.MerkleProof ChainwebMerkleHashAlgorithm + -> m BlockPayloadHash +runHeaderProof p = BlockPayloadHash . MerkleLogHash <$> V1.runMerkleProof p {-# INLINE runHeaderProof #-} -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs b/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs index 85dee72525..42d50c013d 100644 --- a/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs +++ b/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs @@ -68,7 +68,6 @@ import Data.Aeson import Data.ByteString qualified as B import Data.Function import Data.Hashable -import Data.Text qualified as T import Data.Text.Encoding qualified as T import Ethereum.RLP import GHC.Generics diff --git a/src/Chainweb/PayloadProvider/EVM/SPV.hs b/src/Chainweb/PayloadProvider/EVM/SPV.hs index 6eab9f8d5b..b81d70bf93 100644 --- a/src/Chainweb/PayloadProvider/EVM/SPV.hs +++ b/src/Chainweb/PayloadProvider/EVM/SPV.hs @@ -27,7 +27,6 @@ module Chainweb.PayloadProvider.EVM.SPV , parseXLogData ) where -import Chainweb.BlockHeight import Chainweb.ChainId import Chainweb.Utils import Chainweb.Utils.Serialization @@ -37,12 +36,14 @@ import Control.Monad.Catch import Data.ByteString qualified as B import Data.Text qualified as T import Data.Word -import Ethereum.Misc +import Ethereum.Misc qualified as E import Ethereum.Receipt import GHC.Generics (Generic) import Chainweb.PayloadProvider.EVM.Utils hiding (ChainId) import Data.Aeson import Ethereum.Utils (HexBytes(..)) +import Control.Monad +import Chainweb.PayloadProvider -- -------------------------------------------------------------------------- -- Exceptions @@ -56,16 +57,16 @@ instance Exception XChainException -- -------------------------------------------------------------------------- -- -- Event Signature -newtype EventId = EventId (BytesN 32) - deriving (Show, Eq, Generic, Bytes) +newtype EventId = EventId (E.BytesN 32) + deriving (Show, Eq, Generic, E.Bytes) xChainInitializedSignature :: B.ByteString xChainInitializedSignature = "CrosschainInitialized(uint32,address,uint64,bytes)" xChainInitializedId :: EventId xChainInitializedId = EventId - $ _getKeccak256Hash - $ keccak256 + $ E._getKeccak256Hash + $ E.keccak256 $ xChainInitializedSignature -- -------------------------------------------------------------------------- -- @@ -96,40 +97,58 @@ newtype XChainData = XChainData B.ByteString -- | X-Chain LogData -- +-- This data structure is specifically for KIP-34 crosschain transfer proofs. +-- data XLogData = XLogData - { _xLogDataOperationName :: !XChainOperationName - , _xLogDataSenderAddress :: !Address32 + { _xLogDataSenderAddress :: !Address32 , _xLogDataTargetChain :: !ChainId , _xLogDataTargetContract :: !Address32 + , _xLogDataOperationName :: !XChainOperationName , _xLogDataMessage :: !XChainData } deriving (Show, Eq, Generic) +-- | The signature of the event that is stored in the first topic. +-- +-- @ +-- Keccak256('CrossChainInitialized(uint32,address,uint64,bytes)') +-- @ +-- +xLogDataSignature :: E.BytesN 32 +xLogDataSignature = b + where + HexBytes b = unsafeFromText + "0x9d2528c24edd576da7816ca2bdaa28765177c54b32fb18e2ca18567fbc2a9550" + parseXLogData :: MonadThrow m => ChainwebVersion - -> BlockHeight + -> XEventId -> LogEntry -> m XLogData -parseXLogData v h e = do +parseXLogData v eid e = do (LogTopic t0, LogTopic t1, LogTopic t2, LogTopic t3) <- getTopics -- FIXME FIXME FIXME - -- Check the event signatgure! + -- Check the event signture + + unless (t0 == xLogDataSignature) $ + throwM $ UnsupportedEventType eid - targetChain <- mkChainId v h =<< runGetS decodeWordBe (bytes $ dropN @32 @4 t1) - operationName <- XChainOperationName <$> runGetS decodeWordBe (bytes $ dropN @32 @8 t3) + targetChain <- mkChainId v h =<< runGetS decodeWordBe (E.bytes $ dropN @32 @4 t1) + operationName <- XChainOperationName <$> runGetS decodeWordBe (E.bytes $ dropN @32 @8 t3) return XLogData { _xLogDataOperationName = operationName , _xLogDataSenderAddress = toAddress32 $ _logEntryAddress e , _xLogDataTargetChain = targetChain , _xLogDataTargetContract = Address32 t2 - , _xLogDataMessage = XChainData $ bytes $ _logEntryData e + , _xLogDataMessage = XChainData $ E.bytes $ _logEntryData e } where + h = _xEventBlockHeight eid getTopics = case _logEntryTopics e of (t0 : t1 : t2 : t3: _ ) -> return (t0, t1, t2, t3) - l -> throwM $ InvalidLogData $ + l -> throwM $ InvalidEvent eid $ "expected at least four topics but got " <> sshow (length l) diff --git a/src/Chainweb/PayloadProvider/Minimal/Payload.hs b/src/Chainweb/PayloadProvider/Minimal/Payload.hs index 8a89ee9c64..cba7a94b61 100644 --- a/src/Chainweb/PayloadProvider/Minimal/Payload.hs +++ b/src/Chainweb/PayloadProvider/Minimal/Payload.hs @@ -63,7 +63,7 @@ import Data.Aeson.Types (Pair) import Data.ByteString.Short qualified as BS import Data.Function import Data.Hashable -import Data.MerkleLog (MerkleProof, runMerkleProof) +import Data.MerkleLog.V1 qualified as V1 (MerkleProof, runMerkleProof) import Data.Void import Data.Word import GHC.Generics (Generic) @@ -234,15 +234,18 @@ proof => a ~ ChainwebMerkleHashAlgorithm -- => HasHeader a ChainwebHashTag c (MkLogType a ChainwebHashTag Payload) => Payload - -> m (MerkleProof a) + -> m (V1.MerkleProof a) proof = headerProof @Payload {-# INLINE proof #-} -- | Runs a proof. Returns the BlockPayloadHash of the payload for which -- inclusion is proven. -- -runProof :: MerkleProof ChainwebMerkleHashAlgorithm -> BlockPayloadHash -runProof = BlockPayloadHash . MerkleLogHash . runMerkleProof +runProof + :: MonadThrow m + => V1.MerkleProof ChainwebMerkleHashAlgorithm + -> m BlockPayloadHash +runProof p = BlockPayloadHash . MerkleLogHash <$> V1.runMerkleProof p {-# INLINE runProof #-} -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/PayloadProvider/SPV.hs b/src/Chainweb/PayloadProvider/SPV.hs new file mode 100644 index 0000000000..40d96da3b4 --- /dev/null +++ b/src/Chainweb/PayloadProvider/SPV.hs @@ -0,0 +1,504 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TypeAbstractions #-} + +-- | +-- Module: Chainweb.PayloadProvider.SPV +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.SPV +( Argument(..) +, runProof +) where + +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.ChainId +import Chainweb.ChainId +import Chainweb.Crypto.MerkleLog +import Chainweb.MerkleLogHash +import Chainweb.MerkleUniverse +import Chainweb.MinerReward +import Chainweb.Payload +import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.EVM.EthRpcAPI qualified as RPC +import Chainweb.PayloadProvider.EVM.JsonRPC +import Chainweb.PayloadProvider.EVM.JsonRPC qualified as RPC +import Chainweb.SPV +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Chainweb.Version +import Control.Exception +import Control.Monad +import Control.Monad.Catch qualified as C +import Data.ByteString qualified as B +import Data.ByteString.Short qualified as BS +import Data.Kind +import Data.MerkleLog qualified as ML +import Data.Text qualified as T +import Data.Word +import Ethereum.Misc +import Ethereum.Misc qualified as E +import Ethereum.RLP qualified as E +import Ethereum.Receipt qualified as E +import Ethereum.Receipt.ReceiptProof qualified as E +import Ethereum.Trie qualified as E +import GHC.Generics +import GHC.TypeNats +import Numeric.Natural + +-- -------------------------------------------------------------------------- -- +-- Alternative Approach + +-- Protocol flow: +-- +-- 1. Event emission [on-chain]: the user creates the event. The event may be the +-- native event format of the payload provider, a custom language extension, +-- or an agreement to write a particular value to the contract store. +-- +-- 2. Proof creation [off-chain]: the user creates a proof (usually through +-- calling an API) that contains the relevant event data along with evidence +-- for its inclusion on the source chain and its provenance. +-- +-- 3. Send redeem transaction [off-chain]: the user sends a transaction that +-- includes the proof to the target chain. +-- +-- 4. Proof verification [on-chain]: the execution environment on the receiving +-- chain verifies the evidence in the proof and extract the relevant message +-- and provenance data. It performance additional checks on that data and, if +-- successful, notifies the receiver contract. +-- +-- 4. Redeem [on-chain]: the receiving contract obtains the notification data. +-- It performs some checks on the data. If the checks succeed it performs the +-- redeem operations. +-- +-- +-- TOPIC FORMAT: +-- +-- 0: event type signature hash (keccak256()) +-- 1: targetChain [uint32, bigendian] +-- 2: target address [address, last 20 bytes] +-- 3: ignored (optional for indexing) +-- +-- LogData: +-- * receiver address B.ByteString (eth address 20 bytes) +-- * amount [uint256] + +-- -------------------------------------------------------------------------- -- + +-- -------------------------------------------------------------------------- -- +-- TODO: +-- +-- The types in the Merkle Universe must be available to all payload providers +-- (the alternative would be to define a unique event format across all payload +-- providers and require that all payload providers support direct proofs of +-- those events in their respective payload Merkle tree. +-- +-- For instance the EVM provider could collect events from the EVM and transform +-- those to the common format and add those to a new event tree. +-- +-- We should create a package that collects and reexports all types from the +-- Chainweb Merkle universe. + +-- -------------------------------------------------------------------------- -- +-- TagUsage + +data TagType + = LeafTag + | InnerTag + +type family GetTagType (t :: ChainwebHashTag) :: TagType where + + -- GetTagType VoidTag = 'LeafTag + -- GetTagType MerkleRootTag = 'InnerTag + + -- BlockHeader + GetTagType ChainIdTag = 'LeafTag + GetTagType BlockHeightTag = 'LeafTag + GetTagType BlockWeightTag = 'LeafTag + GetTagType BlockPayloadHashTag = 'LeafTag + GetTagType BlockNonceTag = 'LeafTag + GetTagType BlockCreationTimeTag = 'LeafTag + GetTagType ChainwebVersionTag = 'LeafTag + GetTagType PowHashTag = 'LeafTag + GetTagType BlockHashTag = 'InnerTag + GetTagType HashTargetTag = 'LeafTag + GetTagType EpochStartTimeTag = 'LeafTag + GetTagType FeatureFlagsTag = 'LeafTag + + -- Pact Payloads + GetTagType TransactionTag = 'LeafTag + GetTagType TransactionOutputTag = 'LeafTag + GetTagType BlockTransactionsHashTag = 'InnerTag + GetTagType BlockOutputsHashTag = 'InnerTag + GetTagType MinerDataTag = 'LeafTag + GetTagType CoinbaseOutputTag = 'LeafTag + + -- Pact Event (not Currently Chainweb Merkle Tree) + GetTagType OutputEventsTag = 'InnerTag + GetTagType BlockEventsHashTag = 'InnerTag + GetTagType RequestKeyTag = 'LeafTag + GetTagType PactEventTag = 'LeafTag + + -- Minimal Payload Provider + GetTagType MinimalPayloadTag = 'LeafTag + + -- EVM Payload Provider + GetTagType EthParentHashTag = 'LeafTag + GetTagType EthOmmersHashTag = 'LeafTag + GetTagType EthBeneficiaryTag = 'LeafTag + GetTagType EthStateRootTag = 'LeafTag + GetTagType EthTransactionsRootTag = 'LeafTag + GetTagType EthReceiptsRootTag = 'LeafTag + GetTagType EthBloomTag = 'LeafTag + GetTagType EthDifficultyTag = 'LeafTag + GetTagType EthBlockNumberTag = 'LeafTag + GetTagType EthGasLimitTag = 'LeafTag + GetTagType EthGasUsedTag = 'LeafTag + GetTagType EthTimestampTag = 'LeafTag + GetTagType EthExtraDataTag = 'LeafTag + GetTagType EthRandaoTag = 'LeafTag + GetTagType EthNonceTag = 'LeafTag + GetTagType EthBaseFeePerGasTag = 'LeafTag + GetTagType EthWithdrawalsRootTag = 'LeafTag + GetTagType EthBlobGasUsedTag = 'LeafTag + GetTagType EthExcessBlobGasTag = 'LeafTag + GetTagType EthParentBeaconBlockRootTag = 'LeafTag + +-- -------------------------------------------------------------------------- +-- Claims + +data Conjunct a b where + Conjunct :: (InclusionClaim a, InclusionClaim b) => a -> b -> Conjunct a b + +class InclusionClaim a +instance {-# OVERLAPPABLE #-} + (IsMerkleLogEntry ChainwebMerkleHashAlgorithm ChainwebHashTag a) + => InclusionClaim a +instance {-# OVERLAPPING #-} InclusionClaim (Conjunct a b) + +-- class ClaimType a +-- instance {-# OVERLAPPABLE #-} (ChainwebMerkleEntry a, GetTagType (Tag a) ~ 'LeafTag) => ClaimType a +-- instance {-# OVERLAPPING #-} (ClaimType a, ClaimType b) => ClaimType (a, b) +-- +-- class RootType a +-- instance {-# OVERLAPPABLE #-} (ChainwebMerkleEntry a, GetTagType (Tag a) ~ 'InnerTag) => RootType a +-- instance {-# OVERLAPPING #-} (RootType a, RootType b) => RootType (a, b) + +-- -------------------------------------------------------------------------- -- +-- Exceptions + +newtype VerificationException = VerificationException T.Text + deriving (Show, Eq) + +instance Exception VerificationException + +-- -------------------------------------------------------------------------- -- +-- NEW SPV + +-- proofProperties +-- :: forall e kv +-- . KeyValue e kv +-- => ChainId +-- -> MerkleProof Sha2_512_256 +-- -> [kv] +-- proofProperties cid p = +-- [ "chain" .= cid +-- , "event" .= JsonProofSubject (_getMerkleProofSubject $ _merkleProofSubject p) +-- , "algorithm" .= ("Sha2_512_256" :: T.Text) +-- ] +-- where +-- obj = encodeB64UrlNoPaddingText . encodeMerkleProofObject +-- +-- data XChainMessage = XChainMessage +-- { _xChainMsgTargetChain :: !ChainId +-- , _xChainMsgReceiverAccount :: !B.ByteString +-- , _xChainMsgAmount :: !Stu +-- } +-- +-- data XChainEvent = XChainEvent +-- { _xChainSourceChain :: !ChainId +-- -- ^ not in the ETH log +-- , _xChainSourceContract :: !B.ByteString +-- -- ^ Added by the system +-- , _xChainSourceBlockHeight :: !Word64 +-- -- Not in EHT Log +-- , _xChainSourceTxIdx :: !Word64 +-- -- ^ this is not in the event it is provided by the user when they select +-- -- the event +-- , _xChainSourceEventIdx :: !Word64 +-- -- ^ this is not authenticated by the proof. It is provided by the user and +-- -- instructs the precompile to limit the proof to just this one event. +-- , _xChainMessage :: !XChainMessage +-- -- ^ The actual user payload. Cf. below for an example for simple ERC-20 +-- -- cross chain transfers. +-- } + + +-- data PayloadProof a = PayloadProof +-- { _payloadProofRootType :: !MerkleRootType +-- -- ^ The type of the Merle root. +-- , _payloadProofBlob :: !(MerkleProof a) +-- -- ^ The Merkle proof blob which coaintains both, the proof object and +-- -- the proof subject. +-- } deriving (Show, Eq, Generic, NFData) +-- +-- payloadProofProperties +-- :: forall a e kv +-- . MerkleHashAlgorithmName a +-- => KeyValue e kv +-- => PayloadProof a +-- -> [kv] +-- payloadProofProperties p = +-- [ "rootType" .= _payloadProofRootType p +-- , "object" .= (obj . _merkleProofObject) blob +-- , "subject" .= JsonProofSubject (_getMerkleProofSubject $ _merkleProofSubject blob) +-- , "algorithm" .= merkleHashAlgorithmName @a +-- ] +-- where +-- blob = _payloadProofBlob p +-- obj = encodeB64UrlNoPaddingText . encodeMerkleProofObject +-- {-# INLINE payloadProofProperties #-} +-- -------------------------------------------------------------------------- -- + +-- TODO: replace concrete types by Tags. +-- +-- The types themself are not available to all payload providers, but the tags +-- are. + +-- TODO do we need extensible arguments, e.g. as a data family or just class? + +-- Data type of Argument +-- +-- An argument provides evidence for the statement: +-- +-- \(\text{root} \in \text{Chainweb} \rightarrow \text{claim} \in \text{Chainweb}\) +-- +-- where `claim` is a value that is stored on chain and has a type that is a +-- member of the Chainweb Merkle universe. +-- +-- This statement is established by applying the argument to the claim, which +-- may succeed or fail. +-- +data Argument (claim :: Type) (root :: Type) where + Trivial :: Argument a a + BlockPayloadHashArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument BlockPayloadHash BlockHash + ParentHeaderArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument ParentHash BlockHash + PactOutputArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument TransactionOutput BlockPayloadHash + EthReceiptArgument + :: TrieProof + -> Argument E.Receipt ReceiptsRoot + EthHeaderArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument ReceiptsRoot BlockPayloadHash + + -- Composed Arguments + ComposeArgument + :: InclusionClaim a + => Argument claim a + -> Argument a root + -> Argument claim root + ComposeArgument2 + :: (InclusionClaim a0, InclusionClaim a1) + => Argument claim0 a0 + -> Argument claim1 a1 + -> Argument (Conjunct a0 a1) root + -> Argument (Conjunct claim0 claim1) root + +-- What would be the benefit of generic Arguments? Probably not much. When +-- serialized, neither the root nor the claim is included. Therefore the +-- overhead would be just the respective constructor tag. + +-- -- Generic Arguments +-- MerkleArgument +-- :: (InclusionClaim claim) +-- => MerkleProof Sha2_512_256 +-- -> Argument claim root +-- TrieArgument +-- :: (InclusionClaim claim) +-- => TrieProof +-- -> Argument claim root + +compose + :: InclusionClaim claim + => InclusionClaim a + => Argument claim a + -> Argument a root + -> Maybe (Argument claim root) +compose Trivial a = Just a +compose a Trivial = Just a +compose (BlockPayloadHashArgument m0) (ParentHeaderArgument m1) + = MerkleArgument (ML.composeProof m0 m1) + + +-- | Compute the proof root and provide evidence that the proof claim is +-- included in the root of the respective Chainweb Merkle tree. +-- +-- The claim is either a leave and a member of the Chainweb Merkle universe or +-- it is an inner node of the Chainweb Merkle tree. In case of the latter no +-- futher evidence about its type is included. +-- +-- IMPORTANT NOTE: +-- +-- If the result is an inner tree node (e.g. a 'BlockHash' or a +-- 'BlockPayloadHash') type is not checked and purely informational. There is no +-- evidence for it being correct. (we could add that evidence in the future, but +-- right now it is not provided.) +-- +-- It is an important security invariant of Chainweb that the preimage of each +-- root is completely guarded by types values in the Chainweb Merkle universe. +-- +runProof + :: InclusionClaim claim + => InclusionClaim root + => Argument claim root + -> claim + -> Either VerificationException root +runProof (TransactionOutputProof evidence) claim = + runTransactionOutputProof evidence claim +runProof (EthReceiptArgument evidence) claim = + ReceiptsRoot <$> validateTrieProof (Just $ E.putRlpByteString claim) evidence +runProof (ComposeArgument a0 a1) claim = runProof a0 claim >>= runProof a1 +runProof (ComposeArgument2 a0 a1 a3) (Conjunct c0 c1) = do + r0 <- runProof a0 c0 + r1 <- runProof a1 c1 + runProof a3 (Conjunct r0 r1) + + +-- -- | Compute the proof root and provide evidence that is included as a leave in +-- -- the root of the respective Chainweb Merkle tree. The evidence includes that +-- -- the claim is the correct type in the Chainweb Merkle universe. +-- -- +-- -- When running proofs we restrict claims to members of the Merkle universe. It +-- -- is an important security invariant of Chainweb that the preimage of each root +-- -- is completely guarded by types values in the Chainweb Merkle universe. +-- -- +-- runLeafProof +-- :: ClaimType claim +-- => RootType root +-- => Argument claim root +-- -> claim +-- -> Either VerificationException root +-- runLeafProof = runProof + +-- -------------------------------------------------------------------------- -- +-- | Pact Output Proof + +instance E.RLP (ML.MerkleProof a) where + putRlp p = E.putRlp p + getRlp = E.label "MerkleProof" $ MerkleProof <$> E.getRlp + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +createMerkleProof + :: [(B.ByteString, B.ByteString)] + -- ^ Key-value pairs that are stroed in the Trie + -> B.ByteString + -- ^ the key of the proofs + -> ML.MerkleProof a +createMerkleProof ps key = TrieProof + { _trieProofKey = E._proofKey p + , _trieProofNodes = E._proofNodes p + , _trieProofRoot = E._proofRoot p + } + where + p = E.createProof ps key + +-- -------------------------------------------------------------------------- -- +-- | Ethereum Trie Proofs +-- +-- Note that trie proofs are "forward": given the root, the key, and the +-- evidence, they produce the claim. +-- +-- Whereas Merkle proofs are "backward": given the claim, and some evidence they +-- produce the claim (and possibly the key, i.e. the position in the tree). +-- +-- Our implementation of verification turns the argument for trie proofs around +-- to support backward reasoning. +-- +data TrieProof = TrieProof + { _trieProofKey :: !B.ByteString + , _trieProofNodes :: ![B.ByteString] + , _trieProofRoot :: !Keccak256Hash + } + deriving (Show, Eq) + +instance E.RLP TrieProof where + putRlp p = E.putRlp + ( _trieProofKey p + , _trieProofNodes p + , _trieProofRoot p + ) + getRlp = E.label "TrieProof" $ E.getRlpL $ TrieProof + <$> E.label "proofKey" E.getRlp + <*> E.label "proofNodes" E.getRlp + <*> E.label "proofRoot" E.getRlp + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +-- | Crate a proof for the value is stored in the trie for the given key. +-- +-- If no value is stored for the given key the returned proof includes a +-- '_proofValue' of 'Nothing' and witnesses that the key doesn't exist in the +-- trie. +-- +createTrieProof + :: [(B.ByteString, B.ByteString)] + -- ^ Key-value pairs that are stroed in the Trie + -> B.ByteString + -- ^ the key of the proofs + -> TrieProof +createTrieProof ps key = TrieProof + { _trieProofKey = E._proofKey p + , _trieProofNodes = E._proofNodes p + , _trieProofRoot = E._proofRoot p + } + where + p = E.createProof ps key + +validateTrieProof + :: Maybe B.ByteString + -> TrieProof + -> Either VerificationException Keccak256Hash +validateTrieProof claim p = case E.validateProof q of + Left _ -> + Left (VerificationException "Malformed Proof") + Right False -> + Left (VerificationException "Invalid Proof") + Right True -> + Right $ _trieProofRoot p + where + q = E.Proof + { E._proofKey = _trieProofKey p + , E._proofNodes = _trieProofNodes p + , E._proofRoot = _trieProofRoot p + , E._proofValue = claim + } + diff --git a/src/Chainweb/SPV.hs b/src/Chainweb/SPV.hs index 12f555a6a6..0756fa192e 100644 --- a/src/Chainweb/SPV.hs +++ b/src/Chainweb/SPV.hs @@ -34,12 +34,10 @@ import Control.Lens (Getter, to) import Control.Monad import Control.Monad.Catch -import Crypto.Hash.Algorithms - import Data.Aeson import qualified Data.Aeson.Types as Aeson -import qualified Data.ByteString as B -import Data.MerkleLog hiding (Expected, Actual) +import Data.MerkleLog.Common hiding (Expected, Actual) +import qualified Data.MerkleLog.V1 as V1 import qualified Data.Text as T import GHC.Generics (Generic) @@ -54,6 +52,7 @@ import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainId import Chainweb.Utils +import Chainweb.MerkleUniverse -- -------------------------------------------------------------------------- -- -- Exceptions @@ -94,23 +93,26 @@ proofProperties :: forall e kv . KeyValue e kv => ChainId - -> MerkleProof SHA512t_256 + -> V1.MerkleProof ChainwebMerkleHashAlgorithm -> [kv] proofProperties cid p = [ "chain" .= cid - , "object" .= obj (_merkleProofObject p) - , "subject" .= JsonProofSubject (_getMerkleProofSubject $ _merkleProofSubject p) + , "object" .= obj (V1._merkleProofObject p) + , "subject" .= JsonProofSubject (V1._getMerkleProofSubject $ V1._merkleProofSubject p) , "algorithm" .= ("SHA512t_256" :: T.Text) ] where - obj = encodeB64UrlNoPaddingText . encodeMerkleProofObject + obj = encodeB64UrlNoPaddingText . V1.encodeMerkleProofObject -- | Internal helper type of holding the ToJSON dictionary for the -- legacy proof subject encoding. -- -newtype JsonProofSubject = JsonProofSubject (MerkleNodeType SHA512t_256 B.ByteString) +newtype JsonProofSubject = JsonProofSubject (MerkleNodeType ChainwebMerkleHashAlgorithm) -jsonProofSubjectProperties :: KeyValue e kv => JsonProofSubject -> [kv] +jsonProofSubjectProperties + :: KeyValue e kv + => JsonProofSubject + -> [kv] jsonProofSubjectProperties (JsonProofSubject (TreeNode h)) = [ "tree" .= encodeB64UrlNoPaddingText (encodeMerkleRoot h) ] @@ -126,7 +128,7 @@ instance ToJSON JsonProofSubject where parseProof :: String - -> (ChainId -> MerkleProof SHA512t_256 -> a) + -> (ChainId -> V1.MerkleProof ChainwebMerkleHashAlgorithm -> a) -> Value -> Aeson.Parser a parseProof name mkProof = withObject name $ \o -> mkProof @@ -134,11 +136,11 @@ parseProof name mkProof = withObject name $ \o -> mkProof <*> parse o <* (assertJSON ("SHA512t_256" :: T.Text) =<< o .: "algorithm") where - parse o = MerkleProof + parse o = V1.MerkleProof <$> (parseSubject =<< o .: "subject") <*> (parseObject =<< o .: "object") - parseSubject = withObject "ProofSubject" $ \o -> MerkleProofSubject + parseSubject = withObject "ProofSubject" $ \o -> V1.MerkleProofSubject <$> ((o .: "tree" >>= parseTreeNode) <|> (o .: "input" >>= parseInputNode)) parseTreeNode = withText "TreeNode" @@ -148,7 +150,7 @@ parseProof name mkProof = withObject name $ \o -> mkProof $ fmap InputNode . parseBinary pure parseObject = withText "ProofObject" - $ parseBinary decodeMerkleProofObject + $ parseBinary V1.decodeMerkleProofObject assertJSON e a = unless (e == a) $ fail $ "expected " <> sshow e <> ", got " <> sshow a @@ -166,18 +168,18 @@ data TransactionProof a = TransactionProof !ChainId -- ^ the target chain of the proof, i.e the chain which contains -- the root of the proof. - !(MerkleProof a) + !(V1.MerkleProof a) -- ^ the Merkle proof blob, which contains both the proof object and -- the subject. deriving (Show, Eq) -instance ToJSON (TransactionProof SHA512t_256) where +instance ToJSON (TransactionProof ChainwebMerkleHashAlgorithm) where toJSON (TransactionProof cid p) = object $ proofProperties cid p toEncoding (TransactionProof cid p) = pairs . mconcat $ proofProperties cid p {-# INLINE toJSON #-} {-# INLINE toEncoding #-} -instance FromJSON (TransactionProof SHA512t_256) where +instance FromJSON (TransactionProof ChainwebMerkleHashAlgorithm) where parseJSON = parseProof "TransactionProof" TransactionProof {-# INLINE parseJSON #-} @@ -196,18 +198,18 @@ data TransactionOutputProof a = TransactionOutputProof !ChainId -- ^ the target chain of the proof, i.e the chain which contains -- the root of the proof. - !(MerkleProof a) + !(V1.MerkleProof a) -- ^ the Merkle proof blob, which contains both the proof object and -- the subject. deriving (Show, Eq) -instance ToJSON (TransactionOutputProof SHA512t_256) where +instance ToJSON (TransactionOutputProof ChainwebMerkleHashAlgorithm) where toJSON (TransactionOutputProof cid p) = object $ proofProperties cid p toEncoding (TransactionOutputProof cid p) = pairs . mconcat $ proofProperties cid p {-# INLINE toJSON #-} {-# INLINE toEncoding #-} -instance FromJSON (TransactionOutputProof SHA512t_256) where +instance FromJSON (TransactionOutputProof ChainwebMerkleHashAlgorithm) where parseJSON = parseProof "TransactionOutputProof" TransactionOutputProof {-# INLINE parseJSON #-} @@ -225,18 +227,18 @@ data EventProof a = EventProof !ChainId -- ^ the target chain of the proof, i.e the chain which contains -- the root of the proof. - !(MerkleProof a) + !(V1.MerkleProof a) -- ^ the Merkle proof blob, which contains both the proof object and -- the subject. deriving (Show, Eq) -instance ToJSON (EventProof SHA512t_256) where +instance ToJSON (EventProof ChainwebMerkleHashAlgorithm) where toJSON (EventProof cid p) = object $ proofProperties cid p toEncoding (EventProof cid p) = pairs . mconcat $ proofProperties cid p {-# INLINE toJSON #-} {-# INLINE toEncoding #-} -instance FromJSON (EventProof SHA512t_256) where +instance FromJSON (EventProof ChainwebMerkleHashAlgorithm) where parseJSON = parseProof "EventProof" EventProof {-# INLINE parseJSON #-} @@ -245,6 +247,40 @@ instance FromJSON (EventProof SHA512t_256) where eventProofChainId :: Getter (EventProof a) ChainId eventProofChainId = to (\(EventProof cid _) -> cid) +-- -------------------------------------------------------------------------- -- +-- XEvent Proofs + +-- -- | Witness that a crosschain transaction event occurred at the source chain +-- -- +-- data XEventProof a b = XEventProof +-- !ChainId +-- -- ^ the target chain of the proof, i.e the chain which contains +-- -- the root of the proof. +-- !(MerkleProof a) +-- -- ^ the Merkle proof blob, which contains both the proof object and +-- -- the subject. +-- !b +-- -- ^ Payload provider specific proof. The root of this proof is the +-- -- input to the Merkle proof +-- -- +-- -- FIXME: implement proper proof composition +-- deriving (Show, Eq) +-- +-- instance ToJSON b => ToJSON (XEventProof SHA512t_256 b) where +-- toJSON (XEventProof cid p) = object $ proofProperties cid p +-- toEncoding (XEventProof cid p) = pairs . mconcat $ proofProperties cid p +-- {-# INLINE toJSON #-} +-- {-# INLINE toEncoding #-} +-- +-- instance FromJSON b => FromJSON (XEventProof SHA512t_256 b) where +-- parseJSON = parseProof "XEventProof" XEventProof +-- {-# INLINE parseJSON #-} +-- +-- -- | Getter into the chain id of a 'XEventProof' +-- -- +-- xEventProofChainId :: Getter (XEventProof a b) ChainId +-- xEventProofChainId = to (\(XEventProof cid _ _) -> cid) + -- -------------------------------------------------------------------------- -- -- Fake Event Proof diff --git a/src/Chainweb/SPV/CreateProof.hs b/src/Chainweb/SPV/CreateProof.hs index 182723d67c..013003f964 100644 --- a/src/Chainweb/SPV/CreateProof.hs +++ b/src/Chainweb/SPV/CreateProof.hs @@ -17,24 +17,19 @@ -- module Chainweb.SPV.CreateProof ( createTransactionProof -, createTransactionProof_ -, createTransactionProofMax +, createSmallTransactionProof , createTransactionOutputProof -, createTransactionOutputProof_ -, createTransactionOutputProof' +, createSmallTransactionOutputProof ) where import Control.Applicative import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Catch -import Control.Monad.Trans.Maybe -import Crypto.Hash.Algorithms - -import qualified Data.ByteString as B import qualified Data.List.NonEmpty as N -import Data.MerkleLog +import Data.MerkleLog.Common +import qualified Data.MerkleLog.V1 as V1 import GHC.Stack @@ -50,8 +45,6 @@ import Chainweb.Crypto.MerkleLog import Chainweb.CutDB import Chainweb.Graph import Chainweb.MerkleUniverse -import Chainweb.Payload -import Chainweb.Payload.PayloadStore import Chainweb.SPV import Chainweb.TreeDB import Chainweb.Utils @@ -59,8 +52,6 @@ import Chainweb.Version import Chainweb.WebBlockHeaderDB import Chainweb.PayloadProvider -import Chainweb.Storage.Table - -- -------------------------------------------------------------------------- -- -- FIXME -- @@ -70,7 +61,6 @@ import Chainweb.Storage.Table -- CutDb will not include access to payloads. The logic for creating payload -- proofs will be implemented in the payload provider. -- --- -- We have two options: -- -- 1. Make proof creation a consensus API. @@ -128,9 +118,9 @@ createTransactionProof -- ^ The block height of the transaction -> Int -- ^ The index of the transaction in the block - -> IO (TransactionProof SHA512t_256) + -> IO (TransactionProof ChainwebMerkleHashAlgorithm) createTransactionProof cutDb tcid scid = - createTransactionProof_ + createSmallTransactionProof (view cutDbWebBlockHeaderDb cutDb) (view cutDbPayloadProviders cutDb ^?! ixg scid) tcid @@ -138,7 +128,10 @@ createTransactionProof cutDb tcid scid = -- | Version without CutDb dependency -- -createTransactionProof_ +-- NOTE: The target header is the minimum block header in the target chain. +-- Note, that this header may not yet be confirmed and may thus be volatile. +-- +createSmallTransactionProof :: HasCallStack => WebBlockHeaderDb -> SomePayloadProvider @@ -151,43 +144,21 @@ createTransactionProof_ -- ^ The block height of the transaction -> Int -- ^ The index of the transaction in the block - -> IO (TransactionProof SHA512t_256) -createTransactionProof_ headerDb provider tcid scid bh i = do + -> IO (TransactionProof ChainwebMerkleHashAlgorithm) +createSmallTransactionProof headerDb provider tcid scid bh i = do trgHeader <- minimumTrgHeader headerDb tcid scid bh TransactionProof tcid - <$> createPayloadProof_ transactionProofPrefix headerDb provider tcid scid bh i trgHeader - - --- | Creates a witness that a transaction is included in a chain of a chainweb. --- --- NOTE: The target header is the current maximum block header in the target --- chain. Note, that this header may not yet be confirmed and may thus be --- volatile. --- -createTransactionProofMax - :: HasCallStack - => CutDb - -- ^ Block Header Database - -> ChainId - -- ^ target chain. The proof asserts that the subject is included in - -- this chain - -> ChainId - -- ^ source chain. This the chain of the subject - -> BlockHeight - -- ^ The block height of the transaction - -> Int - -- ^ The index of the transaction in the block - -> IO (TransactionProof SHA512t_256) -createTransactionProofMax cutDb tcid scid bh i = TransactionProof tcid - <$> createPayloadProof transactionProofPrefix cutDb tcid scid bh i + <$> createPayloadProof_ getProofPrefix headerDb tcid scid bh i trgHeader + where + getProofPrefix = transactionProofPrefix provider transactionProofPrefix - :: Int + :: SomePayloadProvider + -> Int -> BlockHeight - -> SomePayloadProvider -> BlockPayloadHash -> IO PayloadProofPrefix -transactionProofPrefix i bh provider ph = +transactionProofPrefix provider i bh ph = error "Chainweb.SPV.CreateProof.transactionProofPrefix: FIXME: not yet implemented" -- -- 1. TX proof -- let @@ -240,22 +211,27 @@ createTransactionOutputProof -- ^ The block height of the transaction -> Int -- ^ The index of the transaction in the block - -> IO (TransactionOutputProof SHA512t_256) + -> IO (TransactionOutputProof ChainwebMerkleHashAlgorithm) createTransactionOutputProof cutDb tcid scid = - createTransactionOutputProof_ - (view cutDbWebBlockHeaderDb cutDb) - (view cutDbPayloadProviders cutDb ^?! ixg scid) - tcid - scid - + error "Chainweb.SPV.CreateProof.createTransactionOutputProof: FIXME: not yet implemented" +-- createSmallTransactionOutputProof +-- (view cutDbWebBlockHeaderDb cutDb) +-- (view cutDbPayloadProviders cutDb ^?! ixg scid) +-- tcid +-- scid -- | Version without CutDb dependency -- -createTransactionOutputProof_ +-- The target header is the current minimum block header in the target chain. +-- Note, that this header may not yet be confirmed and may thus be volatile. +-- +createSmallTransactionOutputProof :: HasCallStack + => PayloadProvider p => WebBlockHeaderDb - -> SomePayloadProvider -- ^ Block Header Database + -> p + -- ^ paylaod provider -> ChainId -- ^ target chain. The proof asserts that the subject is included in -- this chain @@ -265,45 +241,26 @@ createTransactionOutputProof_ -- ^ The block height of the transaction -> Int -- ^ The index of the transaction in the block - -> IO (TransactionOutputProof SHA512t_256) -createTransactionOutputProof_ headerDb provider tcid scid bh i = do + -> IO (TransactionOutputProof ChainwebMerkleHashAlgorithm) +createSmallTransactionOutputProof headerDb provider tcid scid bh i = do trgHeader <- minimumTrgHeader headerDb tcid scid bh TransactionOutputProof tcid - <$> createPayloadProof_ outputProofPrefix headerDb provider tcid scid bh i trgHeader - - --- | Creates a witness that a transaction is included in a chain of a chainweb. --- --- The target header is the current maximum block header in the target chain. --- Note, that this header may not yet be confirmed and may thus be volatile. --- -createTransactionOutputProof' - :: HasCallStack - => CutDb - -- ^ Block Header Database - -> ChainId - -- ^ target chain. The proof asserts that the subject is included in - -- this chain - -> ChainId - -- ^ source chain. This the chain of the subject - -> BlockHeight - -- ^ The block height of the transaction - -> Int - -- ^ The index of the transaction in the block - -> IO (TransactionOutputProof SHA512t_256) -createTransactionOutputProof' cutDb tcid scid bh i - = TransactionOutputProof tcid - <$> createPayloadProof outputProofPrefix cutDb tcid scid bh i + <$> createPayloadProof_ getProofPrefix headerDb tcid scid bh i trgHeader + where + getProofPrefix = outputProofPrefix provider outputProofPrefix - :: Int + :: PayloadProvider p + => p + -> Int -- ^ transaction index -> BlockHeight - -> SomePayloadProvider -> BlockPayloadHash -> IO PayloadProofPrefix -outputProofPrefix i bh provider ph = do +outputProofPrefix provider i bh ph = do error "Chainweb.SPV.CreateProof.outputProofPrefix: FIXME: not yet implemented" +-- where +-- -- -- 1. TX proof -- let -- lookupOld = tableLookup @@ -333,6 +290,108 @@ outputProofPrefix i bh provider ph = do -- , _blockPayloadPayloadHash = view payloadDataPayloadHash pd -- } +-- -------------------------------------------------------------------------- -- +-- Create CrossChain Event Proof + +-- -- | Creates a witness that crosschain transaction event occurred on the source +-- -- chain. +-- -- +-- -- The proof is of minimal size. It only witnesses inclusion in the chain for +-- -- the header on the target chain that this closest to the source header. +-- -- +-- -- The size of the result is linear in the distance of the source and target +-- -- chain in the chain graph plus the logarithm of the size of the source block. +-- -- +-- createXEventProof +-- :: HasCallStack +-- => CutDb +-- -- ^ Block Header Database +-- -> ChainId +-- -- ^ target chain. The proof asserts that the subject is included in +-- -- this chain +-- -> ChainId +-- -- ^ source chain. This the chain of the subject +-- -> IO (XEventProof ChainwebMerkleHashAlgorithm) +-- createXEventProof cutDb tcid scid = +-- createSmallXEventProof +-- (view cutDbWebBlockHeaderDb cutDb) +-- (view cutDbPayloadProviders cutDb ^?! ixg scid) +-- tcid +-- scid +-- +-- -- | Version without CutDb dependency +-- -- +-- -- The target header is the current minimum block header in the target chain. +-- -- Note, that this header may not yet be confirmed and may thus be volatile. +-- -- +-- createSmallXEventProof +-- :: HasCallStack +-- => PayloadProvider p +-- => WebBlockHeaderDb +-- -- ^ Block Header Database +-- -> p +-- -- ^ paylaod provider +-- -> ChainId +-- -- ^ target chain. The proof asserts that the subject is included in +-- -- this chain +-- -> ChainId +-- -- ^ source chain. This the chain of the subject +-- -> XEventId +-- -- ^ The event locator +-- -> IO (TransactionOutputProof ChainwebMerkleHashAlgorithm) +-- createSmallXEventProof headerDb provider tcid scid bh i = do +-- trgHeader <- minimumTrgHeader headerDb tcid scid bh +-- XEventProof tcid +-- <$> createPayloadProof_ getProofPrefix headerDb tcid scid bh i trgHeader +-- where +-- getProofPrefix = xEventProofPrefix provider +-- +-- xEventProofPrefix +-- :: PayloadProvider p +-- => p +-- -> XEventId +-- -- ^ The event locator +-- -> BlockPayloadHash +-- -> IO PayloadProofPrefix +-- xEventProofPrefix provider i bh ph = do +-- error "Chainweb.SPV.CreateProof.xEventProofPrefix: FIXME: not yet implemented" +-- +-- where +-- xevent = XEventId +-- { _xEventBlockHeight = bh +-- , _xEventTransactionIndex = int i +-- , _xEventEventIndex = int e +-- } +-- +-- -- -- 1. TX proof +-- -- let +-- -- lookupOld = tableLookup +-- -- (_oldBlockOutputsTbl blockOutputs) +-- -- (_blockPayloadOutputsHash payload) +-- -- lookupNew = tableLookup +-- -- (_newBlockOutputsTbl blockOutputs) +-- -- (bh, _blockPayloadOutputsHash payload) +-- -- Just outs <- runMaybeT $ MaybeT lookupNew <|> MaybeT lookupOld +-- -- -- TODO: use the transaction tree cache +-- -- let (!subj, pos, t) = bodyTree @_ @ChainwebHashTag outs i +-- -- -- FIXME use log +-- -- let tree = (pos, t) +-- -- -- we blindly trust the ix +-- -- +-- -- -- 2. Payload proof +-- -- let !proof = tree N.:| [headerTree_ @BlockOutputsHash payload] +-- -- return (subj, proof) +-- -- where +-- -- blockOutputs = _payloadCacheBlockOutputs $ _payloadCache db +-- -- +-- -- payload = do +-- -- Just pd <- lookupPayloadDataWithHeight payloadDb (Just $ view blockHeight txHeader) (view blockPayloadHash txHeader) +-- -- return $ BlockPayload +-- -- { _blockPayloadTransactionsHash = view payloadDataTransactionsHash pd +-- -- , _blockPayloadOutputsHash = view payloadDataOutputsHash pd +-- -- , _blockPayloadPayloadHash = view payloadDataPayloadHash pd +-- -- } +-- -- -------------------------------------------------------------------------- -- -- Internal Proof Creation @@ -340,41 +399,17 @@ outputProofPrefix i bh provider ph = do -- of merkle trees for the final proof. -- type PayloadProofPrefix = - (MerkleNodeType SHA512t_256 B.ByteString, N.NonEmpty (Int, MerkleTree SHA512t_256)) - --- | Creates a witness that a transaction is included in a chain of a chainweb. --- -createPayloadProof - :: HasCallStack - => (Int -> BlockHeight -> SomePayloadProvider -> BlockPayloadHash -> IO PayloadProofPrefix) - -> CutDb - -- ^ Block Header Database - -> ChainId - -- ^ target chain. The proof asserts that the subject is included in - -- this chain - -> ChainId - -- ^ source chain. This the chain of the subject - -> BlockHeight - -- ^ The block height of the transaction - -> Int - -- ^ The index of the transaction in the block - -> IO (MerkleProof SHA512t_256) -createPayloadProof getPrefix cutDb tcid scid txHeight txIx = do - trgHeadHeader <- maxEntry trgChain - createPayloadProof_ getPrefix headerDb provider tcid scid txHeight txIx trgHeadHeader - where - headerDb = view cutDbWebBlockHeaderDb cutDb - provider = view cutDbPayloadProviders cutDb ^?! ixg scid - trgChain = headerDb ^?! ixg tcid + ( MerkleNodeType ChainwebMerkleHashAlgorithm + , N.NonEmpty (Int, V1.MerkleTree ChainwebMerkleHashAlgorithm) + ) -- | Creates a witness that a transaction is included in a chain of a chainweb -- at the given target header. -- createPayloadProof_ :: HasCallStack - => (Int -> BlockHeight -> SomePayloadProvider -> BlockPayloadHash -> IO PayloadProofPrefix) + => (Int -> BlockHeight -> BlockPayloadHash -> IO PayloadProofPrefix) -> WebBlockHeaderDb - -> SomePayloadProvider -> ChainId -- ^ target chain. The proof asserts that the subject is included in -- this chain @@ -386,8 +421,8 @@ createPayloadProof_ -- ^ The index of the transaction in the block -> BlockHeader -- ^ the target header of the proof - -> IO (MerkleProof SHA512t_256) -createPayloadProof_ getPrefix headerDb provider tcid scid txHeight txIx trgHeader = do + -> IO (V1.MerkleProof ChainwebMerkleHashAlgorithm) +createPayloadProof_ getPrefix headerDb tcid scid txHeight txIx trgHeader = do -- -- 1. TransactionTree -- 2. BlockPayload @@ -443,7 +478,7 @@ createPayloadProof_ getPrefix headerDb provider tcid scid txHeight txIx trgHeade -- ----------------------------- -- -- 1. Payload Proofs (TXs and Payload) - (subj, prefix) <- getPrefix txIx txHeight provider payloadHash + (subj, prefix) <- getPrefix txIx txHeight payloadHash -- ----------------------------- -- @@ -467,7 +502,7 @@ createPayloadProof_ getPrefix headerDb provider tcid scid txHeight txIx trgHeade -- Put proofs together -- - merkleProof_ subj $ append prefix + V1.merkleTreeProof_ subj $ append prefix $ blockHeaderTree : chainTrees <> crossTrees diff --git a/src/Chainweb/SPV/EventProof.hs b/src/Chainweb/SPV/EventProof.hs index a8320b7d66..47079f3e0f 100644 --- a/src/Chainweb/SPV/EventProof.hs +++ b/src/Chainweb/SPV/EventProof.hs @@ -12,6 +12,8 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE UndecidableInstances #-} -- | -- Module: Chainweb.SPV.EventProof @@ -104,17 +106,15 @@ import Control.Lens (view) import Control.Monad import Control.Monad.Catch -import Crypto.Hash.Algorithms - import Data.Aeson -import qualified Data.ByteArray as BA import qualified Data.ByteString as B import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Short as BS import Data.Decimal import Data.Foldable +import Data.Hash.Keccak (Keccak256) import Data.Hashable -import Data.MerkleLog hiding (Actual, Expected) +import qualified Data.MerkleLog.V1 as V1 import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Vector as V @@ -409,14 +409,14 @@ type BlockEventsHash = BlockEventsHash_ ChainwebMerkleHashAlgorithm newtype BlockEventsHash_ a = BlockEventsHash (MerkleLogHash a) deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) - deriving newtype (BA.ByteArrayAccess) deriving newtype (Hashable, ToJSON, FromJSON) + deriving (IsMerkleLogEntry a ChainwebHashTag) via MerkleRootLogEntry a 'BlockEventsHashTag instance MerkleHashAlgorithm a => HasTextRepresentation (BlockEventsHash_ a) where toText (BlockEventsHash h) = toText h fromText t = BlockEventsHash <$> fromText t -encodeBlockEventsHash :: BlockEventsHash_ a -> Put +encodeBlockEventsHash :: MerkleHashAlgorithm a => BlockEventsHash_ a -> Put encodeBlockEventsHash (BlockEventsHash w) = encodeMerkleLogHash w decodeBlockEventsHash @@ -424,13 +424,6 @@ decodeBlockEventsHash => Get (BlockEventsHash_ a) decodeBlockEventsHash = BlockEventsHash <$!> decodeMerkleLogHash -instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag (BlockEventsHash_ a) where - type Tag (BlockEventsHash_ a) = 'BlockEventsHashTag - toMerkleNode = encodeMerkleTreeNode - fromMerkleNode = decodeMerkleTreeNode - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} - -- -------------------------------------------------------------------------- -- -- Output Events @@ -530,7 +523,7 @@ eventsMerkleProof => PayloadWithOutputs_ h -> RequestKey -- ^ RequestKey of the transaction - -> m (MerkleProof a) + -> m (V1.MerkleProof a) eventsMerkleProof p reqKey = do events <- getBlockEvents @_ @a p @@ -541,7 +534,7 @@ eventsMerkleProof p reqKey = do -- Create proof from outputs tree and payload tree let (!subj, !pos, !t) = bodyTree events i - merkleProof subj pos t + V1.merkleTreeProof subj pos t createEventsProof_ :: forall a @@ -568,7 +561,7 @@ createEventsProofKeccak256 :: PayloadWithOutputs -> RequestKey -- ^ RequestKey of the transaction - -> IO (PayloadProof Keccak_256) + -> IO (PayloadProof Keccak256) createEventsProofKeccak256 = createEventsProof_ -- -------------------------------------------------------------------------- -- @@ -630,7 +623,7 @@ createEventsProofDbKeccak256 -- ^ the target header of the proof -> RequestKey -- ^ RequestKey of the transaction - -> IO (PayloadProof Keccak_256) + -> IO (PayloadProof Keccak256) createEventsProofDbKeccak256 = createEventsProofDb_ -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/SPV/OutputProof.hs b/src/Chainweb/SPV/OutputProof.hs index adb6794501..cc04450809 100644 --- a/src/Chainweb/SPV/OutputProof.hs +++ b/src/Chainweb/SPV/OutputProof.hs @@ -4,6 +4,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.SPV.OutputProof @@ -36,11 +37,10 @@ import Control.Lens (view) import Control.Monad import Control.Monad.Catch -import Crypto.Hash.Algorithms - import qualified Data.List.NonEmpty as N -import Data.MerkleLog hiding (Expected, Actual) +import Data.MerkleLog.V1 qualified as V1 import qualified Data.Vector as V +import Data.Hash.Keccak (Keccak256) import GHC.Stack @@ -116,11 +116,11 @@ outputMerkleProofByIdx => PayloadWithOutputs_ h -> Int -- ^ The index of the transaction in the block - -> m (MerkleProof a) + -> m (V1.MerkleProof a) outputMerkleProofByIdx p txIdx = do -- Create proof from outputs tree and payload tree let (!subj, pos, t) = bodyTree outs txIdx - merkleProof_ subj + V1.merkleTreeProof_ subj $ (pos, t) N.:| [headerTree_ @(BlockOutputsHash_ a) payload] where @@ -150,7 +150,7 @@ outputMerkleProof => PayloadWithOutputs_ h -> RequestKey -- ^ The index of the transaction in the block - -> m (MerkleProof a) + -> m (V1.MerkleProof a) outputMerkleProof p = findTxIdx p >=> outputMerkleProofByIdx p -- | Creates a witness that a transaction is included in a chain of a chainweb @@ -189,7 +189,7 @@ createOutputProofKeccak256 :: PayloadWithOutputs -> RequestKey -- ^ The index of the transaction in the block - -> IO (PayloadProof Keccak_256) + -> IO (PayloadProof Keccak256) createOutputProofKeccak256 = createOutputProof_ -- | Creates a witness that a transaction is included in a chain of a chainweb @@ -257,7 +257,7 @@ createOutputProofDbKeccak256 -- ^ the target header of the proof -> RequestKey -- ^ RequestKey of the transaction - -> IO (PayloadProof Keccak_256) + -> IO (PayloadProof Keccak256) createOutputProofDbKeccak256 = createOutputProofDb_ -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/SPV/PayloadProof.hs b/src/Chainweb/SPV/PayloadProof.hs index 23853a7700..09f4d427eb 100644 --- a/src/Chainweb/SPV/PayloadProof.hs +++ b/src/Chainweb/SPV/PayloadProof.hs @@ -16,6 +16,7 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.SPV.PayloadProof @@ -42,12 +43,12 @@ import Control.DeepSeq import Control.Monad import Control.Monad.Catch -import Crypto.Hash.Algorithms +import Data.Hash.Keccak import Data.Aeson -import qualified Data.ByteString as B -import Data.MerkleLog -import qualified Data.Text as T +import Data.MerkleLog (MerkleNodeType(..)) +import Data.MerkleLog.V1 qualified as V1 +import Data.Text qualified as T import GHC.Generics @@ -72,17 +73,21 @@ instance Exception RequestKeyNotFoundException -- | Internal helper type of holding the ToJSON dictionary for the -- proof subject encoding. -- -newtype JsonProofSubject a = JsonProofSubject (MerkleNodeType a B.ByteString) +newtype JsonProofSubject a = JsonProofSubject (MerkleNodeType a) -jsonProofSubjectProperties :: KeyValue e kv => JsonProofSubject a -> [kv] +jsonProofSubjectProperties + :: MerkleHashAlgorithm a + => KeyValue e kv + => JsonProofSubject a + -> [kv] jsonProofSubjectProperties (JsonProofSubject (TreeNode h)) = - [ "tree" .= encodeB64UrlNoPaddingText (encodeMerkleRoot h) + [ "tree" .= encodeB64UrlNoPaddingText (V1.encodeMerkleRoot h) ] jsonProofSubjectProperties (JsonProofSubject (InputNode bytes)) = [ "input" .= encodeB64UrlNoPaddingText bytes ] -instance ToJSON (JsonProofSubject a) where +instance MerkleHashAlgorithm a => ToJSON (JsonProofSubject a) where toJSON = object . jsonProofSubjectProperties toEncoding = pairs . mconcat . jsonProofSubjectProperties {-# INLINE toJSON #-} @@ -103,7 +108,7 @@ instance ToJSON (JsonProofSubject a) where data PayloadProof a = PayloadProof { _payloadProofRootType :: !MerkleRootType -- ^ The type of the Merle root. - , _payloadProofBlob :: !(MerkleProof a) + , _payloadProofBlob :: !(V1.MerkleProof a) -- ^ The Merkle proof blob which coaintains both, the proof object and -- the proof subject. } deriving (Show, Eq, Generic, NFData) @@ -111,21 +116,22 @@ data PayloadProof a = PayloadProof payloadProofProperties :: forall a e kv . MerkleHashAlgorithmName a + => MerkleHashAlgorithm a => KeyValue e kv => PayloadProof a -> [kv] payloadProofProperties p = [ "rootType" .= _payloadProofRootType p - , "object" .= (obj . _merkleProofObject) blob - , "subject" .= JsonProofSubject (_getMerkleProofSubject $ _merkleProofSubject blob) + , "object" .= (obj . V1._merkleProofObject) blob + , "subject" .= JsonProofSubject (V1._getMerkleProofSubject $ V1._merkleProofSubject blob) , "algorithm" .= merkleHashAlgorithmName @a ] where blob = _payloadProofBlob p - obj = encodeB64UrlNoPaddingText . encodeMerkleProofObject + obj = encodeB64UrlNoPaddingText . V1.encodeMerkleProofObject {-# INLINE payloadProofProperties #-} -instance MerkleHashAlgorithmName a => ToJSON (PayloadProof a) where +instance (MerkleHashAlgorithm a, MerkleHashAlgorithmName a) => ToJSON (PayloadProof a) where toJSON = object . payloadProofProperties toEncoding = pairs . mconcat . payloadProofProperties {-# INLINE toJSON #-} @@ -137,21 +143,21 @@ instance (MerkleHashAlgorithm a, MerkleHashAlgorithmName a) => FromJSON (Payload <*> o .: "rootType" <*> parse o where - parse o = MerkleProof + parse o = V1.MerkleProof <$> (parseSubject =<< o .: "subject") <*> (parseObject =<< o .: "object") - parseSubject = withObject "ProofSubject" $ \o -> MerkleProofSubject + parseSubject = withObject "ProofSubject" $ \o -> V1.MerkleProofSubject <$> ((o .: "tree" >>= parseTreeNode) <|> (o .: "input" >>= parseInputNode)) parseTreeNode = withText "TreeNode" - $ fmap TreeNode . parseBinary decodeMerkleRoot + $ fmap TreeNode . parseBinary V1.decodeMerkleRoot parseInputNode = withText "InputNode" $ fmap InputNode . parseBinary pure parseObject = withText "ProofObject" - $ parseBinary decodeMerkleProofObject + $ parseBinary V1.decodeMerkleProofObject assertJSON e a = unless (e == a) $ fail $ "expected " <> sshow e <> ", got " <> sshow a @@ -179,8 +185,8 @@ instance FromJSON SomePayloadProof where pick a | a == merkleHashAlgorithmName @ChainwebMerkleHashAlgorithm = SomePayloadProof @ChainwebMerkleHashAlgorithm <$> parseJSON v - | a == merkleHashAlgorithmName @Keccak_256 = - SomePayloadProof @Keccak_256 <$> parseJSON v + | a == merkleHashAlgorithmName @Keccak256 = + SomePayloadProof @Keccak256 <$> parseJSON v | otherwise = fail $ "unsupported Merkle hash algorithm: " <> T.unpack a {-# INLINE parseJSON #-} @@ -205,8 +211,9 @@ runPayloadProof => IsMerkleLogEntry a ChainwebHashTag b => PayloadProof a -> m (MerkleRootType, MerkleLogHash a, b) -runPayloadProof p = (_payloadProofRootType p, root,) <$> proofSubject blob +runPayloadProof p = do + root <- MerkleLogHash <$> V1.runMerkleProof blob + (_payloadProofRootType p, root,) <$> proofSubject blob where - root = MerkleLogHash $ runMerkleProof blob blob = _payloadProofBlob p diff --git a/src/Chainweb/SPV/RestAPI.hs b/src/Chainweb/SPV/RestAPI.hs index aaa19df614..044760b158 100644 --- a/src/Chainweb/SPV/RestAPI.hs +++ b/src/Chainweb/SPV/RestAPI.hs @@ -36,8 +36,6 @@ module Chainweb.SPV.RestAPI , someSpvApis ) where -import Crypto.Hash.Algorithms - import Data.Proxy import Numeric.Natural @@ -52,6 +50,7 @@ import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils import Chainweb.SPV import Chainweb.Version +import Chainweb.MerkleUniverse -- -------------------------------------------------------------------------- -- -- GET Transaction Proof @@ -61,7 +60,7 @@ type SpvGetTransactionProofApi_ :> "chain" :> Capture "spvChain" ChainId :> "height" :> Capture "spvHeight" BlockHeight :> "transaction" :> Capture "spvTransactionIndex" Natural - :> Get '[JSON] (TransactionProof SHA512t_256) + :> Get '[JSON] (TransactionProof ChainwebMerkleHashAlgorithm) type SpvGetTransactionProofApi (v :: ChainwebVersionT) (c :: ChainIdT) = 'ChainwebEndpoint v :> ChainEndpoint c :> SpvGetTransactionProofApi_ @@ -79,7 +78,7 @@ type SpvGetTransactionOutputProofApi_ :> "chain" :> Capture "spvChain" ChainId :> "height" :> Capture "spvHeight" BlockHeight :> "output" :> Capture "spvTransactionOutputIndex" Natural - :> Get '[JSON] (TransactionOutputProof SHA512t_256) + :> Get '[JSON] (TransactionOutputProof ChainwebMerkleHashAlgorithm) type SpvGetTransactionOutputProofApi (v :: ChainwebVersionT) (c :: ChainIdT) = 'ChainwebEndpoint v :> ChainEndpoint c :> SpvGetTransactionOutputProofApi_ diff --git a/src/Chainweb/SPV/RestAPI/Client.hs b/src/Chainweb/SPV/RestAPI/Client.hs index 53a2a132e9..4f06b0d745 100644 --- a/src/Chainweb/SPV/RestAPI/Client.hs +++ b/src/Chainweb/SPV/RestAPI/Client.hs @@ -21,8 +21,6 @@ module Chainweb.SPV.RestAPI.Client import Control.Monad.Identity -import Crypto.Hash.Algorithms - import Data.Proxy import Numeric.Natural @@ -37,6 +35,7 @@ import Chainweb.RestAPI.Orphans () import Chainweb.SPV import Chainweb.SPV.RestAPI import Chainweb.Version +import Chainweb.MerkleUniverse -- -------------------------------------------------------------------------- -- -- SPV Transaction Proof Client @@ -54,7 +53,7 @@ spvGetTransactionProofClient_ -> Natural -- ^ the index of the proof subject, the transaction for which inclusion -- is proven. - -> ClientM (TransactionProof SHA512t_256) + -> ClientM (TransactionProof ChainwebMerkleHashAlgorithm) spvGetTransactionProofClient_ = client (spvGetTransactionProofApi @v @c) spvGetTransactionProofClient @@ -71,7 +70,7 @@ spvGetTransactionProofClient -> Natural -- ^ the index of the proof subject, the transaction for which inclusion -- is proven. - -> ClientM (TransactionProof SHA512t_256) + -> ClientM (TransactionProof ChainwebMerkleHashAlgorithm) spvGetTransactionProofClient v tcid scid h i = runIdentity $ do SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal tcid @@ -94,7 +93,7 @@ spvGetTransactionOutputProofClient_ -> Natural -- ^ the index of the proof subject, the transaction output for which -- inclusion is proven. - -> ClientM (TransactionOutputProof SHA512t_256) + -> ClientM (TransactionOutputProof ChainwebMerkleHashAlgorithm) spvGetTransactionOutputProofClient_ = client (spvGetTransactionOutputProofApi @v @c) spvGetTransactionOutputProofClient @@ -112,7 +111,7 @@ spvGetTransactionOutputProofClient -> Natural -- ^ the index of the proof subject, the transaction output for which -- inclusion is proven. - -> ClientM (TransactionOutputProof SHA512t_256) + -> ClientM (TransactionOutputProof ChainwebMerkleHashAlgorithm) spvGetTransactionOutputProofClient v tcid scid h i = runIdentity $ do SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal tcid diff --git a/src/Chainweb/SPV/RestAPI/Server.hs b/src/Chainweb/SPV/RestAPI/Server.hs index 0b97c4c4c4..76a36b929c 100644 --- a/src/Chainweb/SPV/RestAPI/Server.hs +++ b/src/Chainweb/SPV/RestAPI/Server.hs @@ -4,6 +4,8 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} -- | -- Module: Chainweb.SPV.RestAPI.Server @@ -25,31 +27,25 @@ module Chainweb.SPV.RestAPI.Server ) where import Control.Monad.IO.Class - -import Crypto.Hash.Algorithms - +import Control.Exception (try, Exception(..)) import Data.Foldable -import qualified Data.Text.IO as T - -import Numeric.Natural - -import Servant - --- internal modules - import Chainweb.BlockHeight import Chainweb.ChainId import Chainweb.CutDB +import Chainweb.PayloadProvider import Chainweb.RestAPI.Utils import Chainweb.SPV import Chainweb.SPV.CreateProof import Chainweb.SPV.RestAPI import Chainweb.Utils import Chainweb.Version - -import Data.Singletons -import Chainweb.PayloadProvider import Control.Lens (view) +import Data.Singletons +import Data.Text.IO qualified as T +import Numeric.Natural +import Servant +import qualified Data.ByteString.Lazy.Char8 as BL8 +import Chainweb.MerkleUniverse -- -------------------------------------------------------------------------- -- -- SPV Transaction Proof Handler @@ -68,7 +64,7 @@ spvGetTransactionProofHandler -> Natural -- ^ the index of the proof subject, the transaction for which inclusion -- is proven. - -> Handler (TransactionProof SHA512t_256) + -> Handler (TransactionProof ChainwebMerkleHashAlgorithm) spvGetTransactionProofHandler db tcid scid bh i = liftIO $ createTransactionProof db tcid scid bh (int i) -- FIXME: add proper error handling @@ -91,7 +87,7 @@ spvGetTransactionOutputProofHandler -> Natural -- ^ the index of the proof subject, the transaction output for which -- inclusion is proven. - -> Handler (TransactionOutputProof SHA512t_256) + -> Handler (TransactionOutputProof ChainwebMerkleHashAlgorithm) spvGetTransactionOutputProofHandler db tcid scid bh i = liftIO $ createTransactionOutputProof db tcid scid bh (int i) -- FIXME: add proper error handling @@ -99,6 +95,13 @@ spvGetTransactionOutputProofHandler db tcid scid bh i = -- -------------------------------------------------------------------------- -- -- SPV Event Output Proof Handler +-- | TODO: we are probably missusing http status codes here. All +-- PayloadSPvExceptions represent Application level protocol failures and not +-- HTTP failures. +-- +-- On the other hand, adding another level of failure reporting might be +-- overkill. +-- spvGetEventProofHandler :: CutDb -> ChainId @@ -117,11 +120,28 @@ spvGetEventProofHandler -> Natural -- ^ The event index in the transaction -> Handler FakeEventProof -spvGetEventProofHandler db tcid scid bh i e = liftIO $ do - withPayloadProvider providers scid $ \p -> do - (SpvProof v) <- liftIO $ eventProof p xevent - return $ FakeEventProof tcid v +spvGetEventProofHandler db tcid scid bh i e = do + liftIO (try getProof) >>= \case + Left err -> do + let msg = BL8.pack (displayException err) + throwError $ case err of + InvalidBlockHeight {} -> err404 { errBody = msg } + InvalidTransactionIndex {} -> err404 { errBody = msg } + InvalidEventIndex {} -> err404 { errBody = msg } + UnsupportedEventType {} -> err422 { errBody = msg } + InvalidEvent {} -> err422 { errBody = msg } + -- or err400? + ProofPending {} -> err404 { errBody = msg } + Right v -> + return $ FakeEventProof tcid v where + getProof = withPayloadProvider providers scid $ \p -> do + -- trgHeader <- minimumTrgHeader headerDb tcid scid bh + -- TODO: check through block height whether a proof can possibly + -- already by available before we do any expensive computations. + (SpvProof v) <- eventProof p xevent + return v + providers = view cutDbPayloadProviders db xevent = XEventId { _xEventBlockHeight = bh diff --git a/src/Chainweb/SPV/VerifyProof.hs b/src/Chainweb/SPV/VerifyProof.hs index 9d0074f5ec..f91a0ad4e6 100644 --- a/src/Chainweb/SPV/VerifyProof.hs +++ b/src/Chainweb/SPV/VerifyProof.hs @@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.SPV.VerifyProof @@ -27,9 +28,7 @@ module Chainweb.SPV.VerifyProof import Control.Monad.Catch -import Crypto.Hash.Algorithms - -import Data.MerkleLog +import Data.MerkleLog.V1 qualified as V1 import Prelude hiding (lookup) @@ -44,6 +43,7 @@ import Chainweb.Payload import Chainweb.SPV import Chainweb.TreeDB import Chainweb.Utils +import Chainweb.MerkleUniverse -- -------------------------------------------------------------------------- -- -- Transaction Proofs @@ -51,9 +51,12 @@ import Chainweb.Utils -- | Runs a transaction Proof. Returns the block hash on the target chain for -- which inclusion is proven. -- -runTransactionProof :: TransactionProof SHA512t_256 -> BlockHash +runTransactionProof + :: MonadThrow m + => TransactionProof ChainwebMerkleHashAlgorithm + -> m BlockHash runTransactionProof (TransactionProof _ p) - = BlockHash $ MerkleLogHash $ runMerkleProof p + = BlockHash . MerkleLogHash <$> V1.runMerkleProof p -- | Verifies the proof against the current state of consensus. The result -- confirms that the subject of the proof occurs in the history of the winning @@ -61,14 +64,15 @@ runTransactionProof (TransactionProof _ p) -- verifyTransactionProof :: CutDb - -> TransactionProof SHA512t_256 + -> TransactionProof ChainwebMerkleHashAlgorithm -> IO Transaction verifyTransactionProof cutDb proof@(TransactionProof cid p) = do + -- FIXME FIXME FIXME: If this is recorded on chain we have to double check + -- if this introduces a new failure condition! + h <- runTransactionProof proof unlessM (member cutDb cid h) $ throwM $ SpvExceptionVerificationFailed "target header is not in the chain" proofSubject p - where - h = runTransactionProof proof -- | Verifies the proof for the given block hash. The result confirms that the -- subject of the proof occurs in the history of the target chain before the @@ -79,15 +83,16 @@ verifyTransactionProof cutDb proof@(TransactionProof cid p) = do -- verifyTransactionProofAt :: CutDb - -> TransactionProof SHA512t_256 + -> TransactionProof ChainwebMerkleHashAlgorithm -> BlockHash -> IO Transaction verifyTransactionProofAt cutDb proof@(TransactionProof cid p) ctx = do + -- FIXME FIXME FIXME: If this is recorded on chain we have to double check + -- if this introduces a new failure condition! + h <- runTransactionProof proof unlessM (memberOfM cutDb cid h ctx) $ throwM $ SpvExceptionVerificationFailed "target header is not in the chain" proofSubject p - where - h = runTransactionProof proof -- | Verifies the proof for the given block hash. The result confirms that the -- subject of the proof occurs in the history of the target chain before the @@ -98,15 +103,16 @@ verifyTransactionProofAt cutDb proof@(TransactionProof cid p) ctx = do -- verifyTransactionProofAt_ :: BlockHeaderDb - -> TransactionProof SHA512t_256 + -> TransactionProof ChainwebMerkleHashAlgorithm -> BlockHash -> IO Transaction verifyTransactionProofAt_ bdb proof@(TransactionProof _cid p) ctx = do + -- FIXME FIXME FIXME: If this is recorded on chain we have to double check + -- if this introduces a new failure condition! + h <- runTransactionProof proof unlessM (ancestorOf bdb h ctx) $ throwM $ SpvExceptionVerificationFailed "target header is not in the chain" proofSubject p - where - h = runTransactionProof proof -- -------------------------------------------------------------------------- -- -- Output Proofs @@ -114,9 +120,12 @@ verifyTransactionProofAt_ bdb proof@(TransactionProof _cid p) ctx = do -- | Runs a transaction Proof. Returns the block hash on the target chain for -- which inclusion is proven. -- -runTransactionOutputProof :: TransactionOutputProof SHA512t_256 -> BlockHash +runTransactionOutputProof + :: MonadThrow m + => TransactionOutputProof ChainwebMerkleHashAlgorithm + -> m BlockHash runTransactionOutputProof (TransactionOutputProof _ p) - = BlockHash $ MerkleLogHash $ runMerkleProof p + = BlockHash . MerkleLogHash <$> V1.runMerkleProof p -- | Verifies the proof against the current state of consensus. The result -- confirms that the subject of the proof occurs in the history of the winning @@ -124,14 +133,15 @@ runTransactionOutputProof (TransactionOutputProof _ p) -- verifyTransactionOutputProof :: CutDb - -> TransactionOutputProof SHA512t_256 + -> TransactionOutputProof ChainwebMerkleHashAlgorithm -> IO TransactionOutput verifyTransactionOutputProof cutDb proof@(TransactionOutputProof cid p) = do + -- FIXME FIXME FIXME: If this is recorded on chain we have to double check + -- if this introduces a new failure condition! + h <- runTransactionOutputProof proof unlessM (member cutDb cid h) $ throwM $ SpvExceptionVerificationFailed "target header is not in the chain" proofSubject p - where - h = runTransactionOutputProof proof -- | Verifies the proof for the given block hash. The result confirms that the -- subject of the proof occurs in the history of the target chain before the @@ -142,15 +152,16 @@ verifyTransactionOutputProof cutDb proof@(TransactionOutputProof cid p) = do -- verifyTransactionOutputProofAt :: CutDb - -> TransactionOutputProof SHA512t_256 + -> TransactionOutputProof ChainwebMerkleHashAlgorithm -> BlockHash -> IO TransactionOutput verifyTransactionOutputProofAt cutDb proof@(TransactionOutputProof cid p) ctx = do + -- FIXME FIXME FIXME: If this is recorded on chain we have to double check + -- if this introduces a new failure condition! + h <- runTransactionOutputProof proof unlessM (memberOfM cutDb cid h ctx) $ throwM $ SpvExceptionVerificationFailed "target header is not in the chain" proofSubject p - where - h = runTransactionOutputProof proof -- | Verifies the proof for the given block hash. The result confirms that the -- subject of the proof occurs in the history of the target chain before the @@ -161,12 +172,13 @@ verifyTransactionOutputProofAt cutDb proof@(TransactionOutputProof cid p) ctx = -- verifyTransactionOutputProofAt_ :: BlockHeaderDb - -> TransactionOutputProof SHA512t_256 + -> TransactionOutputProof ChainwebMerkleHashAlgorithm -> BlockHash -> IO TransactionOutput verifyTransactionOutputProofAt_ bdb proof@(TransactionOutputProof _cid p) ctx = do + -- FIXME FIXME FIXME: If this is recorded on chain we have to double check + -- if this introduces a new failure condition! + h <- runTransactionOutputProof proof unlessM (ancestorOf bdb h ctx) $ throwM $ SpvExceptionVerificationFailed "target header is not in the chain" proofSubject p - where - h = runTransactionOutputProof proof diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index e4c6c6d55e..335c0cb346 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -25,6 +25,7 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-deprecations #-} +{-# LANGUAGE ConstraintKinds #-} -- | -- Module: Chainweb.Utils @@ -238,6 +239,19 @@ module Chainweb.Utils , unsafeHead , unsafeTail , withEarlyReturn + +-- * Bits and Bytes +, Bytes(..) +, fromByteString +, toByteString +, buildByteString +, ByteSwap(..) +, be +, le +, peekByteString +, peekByteArray16 +, peekByteArray32 +, peekByteArray64 ) where import Configuration.Utils hiding (Error, Lens) @@ -258,18 +272,23 @@ import Control.Monad.Reader as Reader import Data.Aeson.Text (encodeToLazyText) import Data.Aeson.Types qualified as Aeson +import Data.Array.Byte import Data.Attoparsec.Text qualified as A import Data.Bifunctor import Data.Bool (bool) import Data.ByteString (ByteString) import Data.ByteString qualified as B +import Data.ByteString.Unsafe qualified as B import Data.ByteString.Base64 qualified as B64 import Data.ByteString.Base64.URL qualified as B64U import Data.ByteString.Builder qualified as BB import Data.ByteString.Char8 qualified as B8 import Data.ByteString.Lazy qualified as BL +import Data.ByteString.Short qualified as BS +import Data.Coerce import Data.Csv qualified as CSV import Data.Decimal +import Data.DoubleWord import Data.Functor.Of import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS @@ -284,12 +303,16 @@ import Data.Vector qualified as V import Data.Vector.Mutable qualified as MV import Data.Word -import GHC.Exts (proxy#) +import Foreign (Storable (sizeOf), peek, castPtr) + +import GHC.ByteOrder +import GHC.Exts (proxy#, byteSwap#, indexWord64Array#, indexWord16Array#, indexWord32Array#) import GHC.Generics import GHC.Stack (HasCallStack, callStack, prettyCallStack) import GHC.TypeLits (KnownSymbol, Symbol, symbolVal') import GHC.TypeLits qualified as Int (KnownNat, natVal') import GHC.TypeNats qualified as Nat (KnownNat, natVal') +import GHC.Word (Word(W#), Word64 (W64#), Word16 (..), Word32 (..)) import Network.Connection qualified as HTTP import Network.HTTP.Client qualified as HTTP @@ -297,6 +320,7 @@ import Network.HTTP.Client.TLS qualified as HTTP import Network.HTTP.Types qualified as HTTP import Network.Socket hiding (Debug) import Network.TLS qualified as HTTP +import Network.URI import Numeric.Natural @@ -307,7 +331,7 @@ import Servant.Client qualified import Streaming qualified as S (concats, effect, inspect) import Streaming.Prelude qualified as S -import System.IO.Unsafe (unsafeInterleaveIO, unsafePerformIO) +import System.IO.Unsafe (unsafeInterleaveIO, unsafePerformIO, unsafeDupablePerformIO) import System.LogLevel import System.Random.MWC qualified as Prob import System.Random.MWC.Probability qualified as Prob @@ -315,7 +339,6 @@ import System.Timeout qualified as Timeout import Text.Printf (printf) import Text.Read (readEither) -import Network.URI -- -------------------------------------------------------------------------- -- -- SI unit prefixes @@ -1511,6 +1534,146 @@ hostArch = "x86_64" hostArch = "unknown" #endif +-- -------------------------------------------------------------------------- -- +-- Bits and Bytes + +-- | Class of types that can be coerced to ByteArray +-- +-- Morally this should have Coercible ByteArray as superclass. However, +-- Coercible dictionaries are not propagated by GHC in the same way like oher +-- constraints. In particular, it is available only if the constructor for the +-- respective type is brought into scope. +-- +class Bytes a where + byteArray :: a -> ByteArray + + default byteArray :: Coercible a ByteArray => a -> ByteArray + byteArray = coerce + {-# INLINE byteArray #-} + +instance Bytes ByteArray where +instance Bytes BS.ShortByteString where + +bytes :: Coercible ByteArray b => Bytes a => a -> b +bytes = coerce . byteArray +{-# INLINE bytes #-} + +class ByteSwap a where swap :: a -> a +instance ByteSwap Word where swap (W# w#) = W# (byteSwap# w#) +instance ByteSwap Word16 where swap = byteSwap16 +instance ByteSwap Word32 where swap = byteSwap32 +instance ByteSwap Word64 where swap = byteSwap64 +instance ByteSwap Word128 where swap (Word128 a b) = Word128 (swap b) (swap a) +instance ByteSwap Word256 where swap (Word256 a b) = Word256 (swap b) (swap a) + +toByteString :: Bytes a => a -> B.ByteString +toByteString = BS.fromShort . bytes +{-# INLINE toByteString #-} + +fromByteString :: Coercible ByteArray a => B.ByteString -> a +fromByteString = coerce . BS.toShort +{-# INLINE fromByteString #-} + +be :: ByteSwap a => a -> a +be + | targetByteOrder == BigEndian = id + | otherwise = swap +{-# INLINE be #-} + +le :: ByteSwap a => a -> a +le + | targetByteOrder == LittleEndian = id + | otherwise = swap +{-# INLINE le #-} + +buildByteString :: BB.Builder -> B.ByteString +buildByteString = BL.toStrict . BB.toLazyByteString +{-# INLINE buildByteString #-} + +peekByteString + :: forall w + . Storable w + => B.ByteString + -> Either T.Text w +peekByteString bs + | l < s = Left $ "toWordBe: size of input bytestring to small" + <> ". Expected: " <> sshow s + <> ". Actual: " <> sshow l + | otherwise = Right $ unsafeDupablePerformIO $ + B.unsafeUseAsCStringLen bs $ peek . castPtr . fst + where + l = B.length bs + s = sizeOf @w undefined + +peekByteArray16 + :: Bytes b + => b + -> Either T.Text Word16 +peekByteArray16 b + | l < 2 = Left $ "toWordBe: size of input bytestring to small" + <> ". Expected: 2" + <> ". Actual: " <> sshow l + | otherwise = Right $ W16# (indexWord16Array# s# 0#) + where + s = bytes b + !(BS.SBS s#) = coerce s + l = BS.length s + +peekByteArray32 + :: Bytes b + => b + -> Either T.Text Word32 +peekByteArray32 b + | l < 4 = Left $ "toWordBe: size of input bytestring to small" + <> ". Expected: 4" + <> ". Actual: " <> sshow l + | otherwise = Right $ W32# (indexWord32Array# s# 0#) + where + s = bytes b + !(BS.SBS s#) = coerce s + l = BS.length s + +peekByteArray64 + :: Bytes b + => b + -> Either T.Text Word64 +peekByteArray64 b + | l < 8 = Left $ "toWordBe: size of input bytestring to small" + <> ". Expected: 8" + <> ". Actual: " <> sshow l + | otherwise = Right $ W64# (indexWord64Array# s# 0#) + where + s = bytes b + !(BS.SBS s#) = coerce s + l = BS.length s + +-- -- | Convert a 'Word64' to a 'ByteArray'. +-- -- +-- word64ToByteArray :: Word64 -> ByteArray +-- word64ToByteArray !(W64# h) = ByteArray $ runRW# $ \s0 -> do +-- case newByteArray# 8# s0 of +-- (# s1, mba #) -> case writeWord64Array# mba 0# h s1 of +-- s2 -> case unsafeFreezeByteArray# mba s2 of +-- (# _, ba #) -> ba +-- +-- -- | Convert a 'Word32' to a 'ByteArray'. +-- -- +-- word32ToByteArray :: Word32 -> ByteArray +-- word32ToByteArray !(W32# h) = ByteArray $ runRW# $ \s0 -> do +-- case newByteArray# 4# s0 of +-- (# s1, mba #) -> case writeWord32Array# mba 0# h s1 of +-- s2 -> case unsafeFreezeByteArray# mba s2 of +-- (# _, ba #) -> ba +-- +-- -- | Convert a 'Word' to a 'ByteArray'. +-- -- +-- wordToByteArray :: Word -> ByteArray +-- wordToByteArray !(W# h) = ByteArray $ runRW# $ \s0 -> do +-- case newByteArray# (wordS s0 of +-- (# s1, mba #) -> case writeWord32Array# mba 0# h s1 of +-- s2 -> case unsafeFreezeByteArray# mba s2 of +-- (# _, ba #) -> ba + -- -------------------------------------------------------------------------- -- -- Debugging Tools diff --git a/src/P2P/Node.hs b/src/P2P/Node.hs index 48830c90f5..8371c5e40b 100644 --- a/src/P2P/Node.hs +++ b/src/P2P/Node.hs @@ -100,7 +100,7 @@ import qualified Data.Map.Strict as M import Data.Maybe import qualified Data.Set as S import qualified Data.Text as T -import Data.Tuple +import qualified Data.Tuple as Tuple import GHC.Generics import GHC.Stack @@ -302,7 +302,7 @@ loggFun :: P2pNode -> LogFunction loggFun = _p2pNodeLogFunction nodeRandom :: R.Random a => P2pNode -> (R.StdGen -> (a, R.StdGen)) -> IO a -nodeRandom node r = atomicModifyIORef' (_p2pNodeRng node) $ swap . r +nodeRandom node r = atomicModifyIORef' (_p2pNodeRng node) $ Tuple.swap . r {-# INLINE nodeRandom #-} nodeRandomR :: R.Random a => P2pNode -> (a, a) -> IO a @@ -533,7 +533,7 @@ findNextPeer node = do -- random circular shift of a set let shift :: Int -> [a] -> [a] shift i = uncurry (++) - . swap + . Tuple.swap . splitAt i let shiftR :: [a] -> IO [a] From 43a5b714bbdb413dff1d7e8b442a1bee52065c62 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 17 Mar 2025 20:06:44 -0700 Subject: [PATCH 098/378] Remove unused SPV proof verification code. --- src/Chainweb/Pact4/SPV.hs | 4 +- src/Chainweb/Pact5/SPV.hs | 10 +- src/Chainweb/SPV.hs | 101 +-------------- src/Chainweb/SPV/CreateProof.hs | 199 +---------------------------- src/Chainweb/SPV/RestAPI.hs | 24 +--- src/Chainweb/SPV/RestAPI/Client.hs | 42 +----- src/Chainweb/SPV/RestAPI/Server.hs | 25 +--- src/Chainweb/SPV/VerifyProof.hs | 147 ++------------------- 8 files changed, 22 insertions(+), 530 deletions(-) diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs index eda4129871..123b7843d1 100644 --- a/src/Chainweb/Pact4/SPV.hs +++ b/src/Chainweb/Pact4/SPV.hs @@ -158,7 +158,7 @@ verifySPV bdb bh typ proof = runExceptT $ go typ proof -- 3. Extract tx outputs as a pact object and return the -- object. - TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) + TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt bdb u (view blockHash bh) q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of Nothing -> forkedThrower bh "unable to decode spv transaction output" @@ -281,7 +281,7 @@ verifyCont bdb bh (Pact4.ContProof cp) = runExceptT $ do -- 3. Extract continuation 'PactExec' from decoded result -- and return the cont exec object - TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) + TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt bdb u (view blockHash bh) q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of Nothing -> forkedThrower bh "unable to decode spv transaction output" diff --git a/src/Chainweb/Pact5/SPV.hs b/src/Chainweb/Pact5/SPV.hs index 856d306d25..47231fa972 100644 --- a/src/Chainweb/Pact5/SPV.hs +++ b/src/Chainweb/Pact5/SPV.hs @@ -10,9 +10,10 @@ module Chainweb.Pact5.SPV (pactSPV) where import Chainweb.BlockHeader (BlockHeader, blockHash, blockHeight) import Chainweb.BlockHeaderDB (BlockHeaderDb) +import Chainweb.MerkleUniverse import Chainweb.Payload (TransactionOutput(..)) import Chainweb.SPV (SpvException(..), TransactionOutputProof(..), outputProofChainId) -import Chainweb.SPV.VerifyProof (verifyTransactionOutputProofAt_) +import Chainweb.SPV.VerifyProof (verifyTransactionOutputProofAt) import Chainweb.Utils (decodeB64UrlNoPaddingText) import Chainweb.Version qualified as CW import Chainweb.Version.Guards qualified as CW @@ -30,7 +31,6 @@ import Pact.Core.Hash (Hash(..)) import Pact.Core.PactValue (ObjectData(..), PactValue(..)) import Pact.Core.SPV (ContProof(..), SPVSupport(..)) import Pact.Core.StableEncoding (encodeStable) -import Chainweb.MerkleUniverse pactSPV :: BlockHeaderDb -> BlockHeader -> SPVSupport pactSPV bdb bh = SPVSupport @@ -64,7 +64,8 @@ verifyCont bdb bh (ContProof base64Proof) = runExceptT $ do -- 1. Verify SPV TransactionOutput proof via Chainweb SPV API -- 2. Decode tx outputs to 'CommandResult' 'Hash' _ -- 3. Extract continuation 'DefPactExec' from decoded result and return the cont exec object - TransactionOutput proof <- catchAndDisplaySPVError bh $ liftIO $ verifyTransactionOutputProofAt_ bdb outputProof (view blockHash bh) + TransactionOutput proof <- catchAndDisplaySPVError bh $ liftIO $ + verifyTransactionOutputProofAt bdb outputProof (view blockHash bh) -- TODO: Do we care about the error type here? commandResult <- case Aeson.decodeStrict' @(CommandResult Hash Aeson.Value) proof of @@ -100,7 +101,8 @@ verifySPV bdb bh proofType proof = runExceptT $ do -- 2. Decode tx outputs to 'CommandResult' 'Hash' _ -- 3. Extract tx outputs as a pact object and return the object - TransactionOutput rawCommandResult <- catchAndDisplaySPVError bh $ liftIO $ verifyTransactionOutputProofAt_ bdb outputProof (view blockHash bh) + TransactionOutput rawCommandResult <- catchAndDisplaySPVError bh $ liftIO $ + verifyTransactionOutputProofAt bdb outputProof (view blockHash bh) commandResult <- case Aeson.decodeStrict' @(CommandResult Hash Aeson.Value) rawCommandResult of Nothing -> throwError "verifySPV: Unable to decode SPV transaction output" diff --git a/src/Chainweb/SPV.hs b/src/Chainweb/SPV.hs index 0756fa192e..aef9a1c0b3 100644 --- a/src/Chainweb/SPV.hs +++ b/src/Chainweb/SPV.hs @@ -19,12 +19,8 @@ -- module Chainweb.SPV ( SpvException(..) -, TransactionProof(..) -, proofChainId , TransactionOutputProof(..) , outputProofChainId -, EventProof(..) -, eventProofChainId , FakeEventProof(..) ) where @@ -44,15 +40,13 @@ import GHC.Generics (Generic) import Numeric.Natural -import Prelude hiding (lookup) - -- internal modules import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainId -import Chainweb.Utils import Chainweb.MerkleUniverse +import Chainweb.Utils -- -------------------------------------------------------------------------- -- -- Exceptions @@ -158,36 +152,6 @@ parseProof name mkProof = withObject name $ \o -> mkProof parseBinary p t = either (fail . show) return $ p =<< decodeB64UrlNoPaddingText t --- -------------------------------------------------------------------------- -- --- Transaction Proofs - --- | Witness that a transaction is included in the head of a chain in a --- chainweb. --- -data TransactionProof a = TransactionProof - !ChainId - -- ^ the target chain of the proof, i.e the chain which contains - -- the root of the proof. - !(V1.MerkleProof a) - -- ^ the Merkle proof blob, which contains both the proof object and - -- the subject. - deriving (Show, Eq) - -instance ToJSON (TransactionProof ChainwebMerkleHashAlgorithm) where - toJSON (TransactionProof cid p) = object $ proofProperties cid p - toEncoding (TransactionProof cid p) = pairs . mconcat $ proofProperties cid p - {-# INLINE toJSON #-} - {-# INLINE toEncoding #-} - -instance FromJSON (TransactionProof ChainwebMerkleHashAlgorithm) where - parseJSON = parseProof "TransactionProof" TransactionProof - {-# INLINE parseJSON #-} - --- | Getter into the chain id of a 'TransactionProof' --- -proofChainId :: Getter (TransactionProof a) ChainId -proofChainId = to (\(TransactionProof cid _) -> cid) - -- -------------------------------------------------------------------------- -- -- Output Proofs @@ -218,69 +182,6 @@ instance FromJSON (TransactionOutputProof ChainwebMerkleHashAlgorithm) where outputProofChainId :: Getter (TransactionOutputProof a) ChainId outputProofChainId = to (\(TransactionOutputProof cid _) -> cid) --- -------------------------------------------------------------------------- -- --- Event Proofs - --- | Witness that a event is included in the head of a chain in a chainweb. --- -data EventProof a = EventProof - !ChainId - -- ^ the target chain of the proof, i.e the chain which contains - -- the root of the proof. - !(V1.MerkleProof a) - -- ^ the Merkle proof blob, which contains both the proof object and - -- the subject. - deriving (Show, Eq) - -instance ToJSON (EventProof ChainwebMerkleHashAlgorithm) where - toJSON (EventProof cid p) = object $ proofProperties cid p - toEncoding (EventProof cid p) = pairs . mconcat $ proofProperties cid p - {-# INLINE toJSON #-} - {-# INLINE toEncoding #-} - -instance FromJSON (EventProof ChainwebMerkleHashAlgorithm) where - parseJSON = parseProof "EventProof" EventProof - {-# INLINE parseJSON #-} - --- | Getter into the chain id of a 'EventProof' --- -eventProofChainId :: Getter (EventProof a) ChainId -eventProofChainId = to (\(EventProof cid _) -> cid) - --- -------------------------------------------------------------------------- -- --- XEvent Proofs - --- -- | Witness that a crosschain transaction event occurred at the source chain --- -- --- data XEventProof a b = XEventProof --- !ChainId --- -- ^ the target chain of the proof, i.e the chain which contains --- -- the root of the proof. --- !(MerkleProof a) --- -- ^ the Merkle proof blob, which contains both the proof object and --- -- the subject. --- !b --- -- ^ Payload provider specific proof. The root of this proof is the --- -- input to the Merkle proof --- -- --- -- FIXME: implement proper proof composition --- deriving (Show, Eq) --- --- instance ToJSON b => ToJSON (XEventProof SHA512t_256 b) where --- toJSON (XEventProof cid p) = object $ proofProperties cid p --- toEncoding (XEventProof cid p) = pairs . mconcat $ proofProperties cid p --- {-# INLINE toJSON #-} --- {-# INLINE toEncoding #-} --- --- instance FromJSON b => FromJSON (XEventProof SHA512t_256 b) where --- parseJSON = parseProof "XEventProof" XEventProof --- {-# INLINE parseJSON #-} --- --- -- | Getter into the chain id of a 'XEventProof' --- -- --- xEventProofChainId :: Getter (XEventProof a b) ChainId --- xEventProofChainId = to (\(XEventProof cid _ _) -> cid) - -- -------------------------------------------------------------------------- -- -- Fake Event Proof diff --git a/src/Chainweb/SPV/CreateProof.hs b/src/Chainweb/SPV/CreateProof.hs index 013003f964..22aa8978f5 100644 --- a/src/Chainweb/SPV/CreateProof.hs +++ b/src/Chainweb/SPV/CreateProof.hs @@ -16,9 +16,7 @@ -- Construction of Merkle proofs in the Chainweb Merkle tree. -- module Chainweb.SPV.CreateProof -( createTransactionProof -, createSmallTransactionProof -, createTransactionOutputProof +( createTransactionOutputProof , createSmallTransactionOutputProof ) where @@ -94,99 +92,6 @@ import Chainweb.PayloadProvider -- The first solution is in alignment with the overall architecture of the -- conensus protocol. --- -------------------------------------------------------------------------- -- --- Create Transaction Proof - --- | Creates a witness that a transaction is included in a chain of a chainweb. --- --- The proof is of minimal size. It only witnesses inclusion in the chain for --- the header on the target chain that this closest to the source header. --- --- The size of the result is linear in the distance of the source and target --- chain in the chain graph plus the logarithm of the size of the source block. --- -createTransactionProof - :: HasCallStack - => CutDb - -- ^ Block Header Database - -> ChainId - -- ^ target chain. The proof asserts that the subject is included in - -- this chain - -> ChainId - -- ^ source chain. This the chain of the subject - -> BlockHeight - -- ^ The block height of the transaction - -> Int - -- ^ The index of the transaction in the block - -> IO (TransactionProof ChainwebMerkleHashAlgorithm) -createTransactionProof cutDb tcid scid = - createSmallTransactionProof - (view cutDbWebBlockHeaderDb cutDb) - (view cutDbPayloadProviders cutDb ^?! ixg scid) - tcid - scid - --- | Version without CutDb dependency --- --- NOTE: The target header is the minimum block header in the target chain. --- Note, that this header may not yet be confirmed and may thus be volatile. --- -createSmallTransactionProof - :: HasCallStack - => WebBlockHeaderDb - -> SomePayloadProvider - -> ChainId - -- ^ target chain. The proof asserts that the subject is included in - -- this chain - -> ChainId - -- ^ source chain. This the chain of the subject - -> BlockHeight - -- ^ The block height of the transaction - -> Int - -- ^ The index of the transaction in the block - -> IO (TransactionProof ChainwebMerkleHashAlgorithm) -createSmallTransactionProof headerDb provider tcid scid bh i = do - trgHeader <- minimumTrgHeader headerDb tcid scid bh - TransactionProof tcid - <$> createPayloadProof_ getProofPrefix headerDb tcid scid bh i trgHeader - where - getProofPrefix = transactionProofPrefix provider - -transactionProofPrefix - :: SomePayloadProvider - -> Int - -> BlockHeight - -> BlockPayloadHash - -> IO PayloadProofPrefix -transactionProofPrefix provider i bh ph = - error "Chainweb.SPV.CreateProof.transactionProofPrefix: FIXME: not yet implemented" --- -- 1. TX proof --- let --- lookupOld = tableLookup --- (_oldTransactionDbBlockTransactionsTbl $ _transactionDb provider) --- (_blockPayloadTransactionsHash payload) --- lookupNew = tableLookup --- (_newTransactionDbBlockTransactionsTbl $ _transactionDb provider) --- (bh, _blockPayloadTransactionsHash payload) --- Just txs <- runMaybeT $ MaybeT lookupNew <|> MaybeT lookupOld --- -- TODO: use the transaction tree cache --- let (!subj, pos, t) = bodyTree @_ @ChainwebHashTag txs i --- -- FIXME use log --- let !tree = (pos, t) --- -- we blindly trust the ix --- --- -- 2. Payload proof --- let !proof = tree N.:| [headerTree_ @BlockTransactionsHash payload] --- return (subj, proof) --- --- payload = do --- Just pd <- lookupPayloadDataWithHeight payloadDb (Just $ view blockHeight txHeader) (view blockPayloadHash txHeader) --- return $ BlockPayload --- { _blockPayloadTransactionsHash = view payloadDataTransactionsHash pd --- , _blockPayloadOutputsHash = view payloadDataOutputsHash pd --- , _blockPayloadPayloadHash = view payloadDataPayloadHash pd --- } - -- -------------------------------------------------------------------------- -- -- Creates Output Proof @@ -290,108 +195,6 @@ outputProofPrefix provider i bh ph = do -- , _blockPayloadPayloadHash = view payloadDataPayloadHash pd -- } --- -------------------------------------------------------------------------- -- --- Create CrossChain Event Proof - --- -- | Creates a witness that crosschain transaction event occurred on the source --- -- chain. --- -- --- -- The proof is of minimal size. It only witnesses inclusion in the chain for --- -- the header on the target chain that this closest to the source header. --- -- --- -- The size of the result is linear in the distance of the source and target --- -- chain in the chain graph plus the logarithm of the size of the source block. --- -- --- createXEventProof --- :: HasCallStack --- => CutDb --- -- ^ Block Header Database --- -> ChainId --- -- ^ target chain. The proof asserts that the subject is included in --- -- this chain --- -> ChainId --- -- ^ source chain. This the chain of the subject --- -> IO (XEventProof ChainwebMerkleHashAlgorithm) --- createXEventProof cutDb tcid scid = --- createSmallXEventProof --- (view cutDbWebBlockHeaderDb cutDb) --- (view cutDbPayloadProviders cutDb ^?! ixg scid) --- tcid --- scid --- --- -- | Version without CutDb dependency --- -- --- -- The target header is the current minimum block header in the target chain. --- -- Note, that this header may not yet be confirmed and may thus be volatile. --- -- --- createSmallXEventProof --- :: HasCallStack --- => PayloadProvider p --- => WebBlockHeaderDb --- -- ^ Block Header Database --- -> p --- -- ^ paylaod provider --- -> ChainId --- -- ^ target chain. The proof asserts that the subject is included in --- -- this chain --- -> ChainId --- -- ^ source chain. This the chain of the subject --- -> XEventId --- -- ^ The event locator --- -> IO (TransactionOutputProof ChainwebMerkleHashAlgorithm) --- createSmallXEventProof headerDb provider tcid scid bh i = do --- trgHeader <- minimumTrgHeader headerDb tcid scid bh --- XEventProof tcid --- <$> createPayloadProof_ getProofPrefix headerDb tcid scid bh i trgHeader --- where --- getProofPrefix = xEventProofPrefix provider --- --- xEventProofPrefix --- :: PayloadProvider p --- => p --- -> XEventId --- -- ^ The event locator --- -> BlockPayloadHash --- -> IO PayloadProofPrefix --- xEventProofPrefix provider i bh ph = do --- error "Chainweb.SPV.CreateProof.xEventProofPrefix: FIXME: not yet implemented" --- --- where --- xevent = XEventId --- { _xEventBlockHeight = bh --- , _xEventTransactionIndex = int i --- , _xEventEventIndex = int e --- } --- --- -- -- 1. TX proof --- -- let --- -- lookupOld = tableLookup --- -- (_oldBlockOutputsTbl blockOutputs) --- -- (_blockPayloadOutputsHash payload) --- -- lookupNew = tableLookup --- -- (_newBlockOutputsTbl blockOutputs) --- -- (bh, _blockPayloadOutputsHash payload) --- -- Just outs <- runMaybeT $ MaybeT lookupNew <|> MaybeT lookupOld --- -- -- TODO: use the transaction tree cache --- -- let (!subj, pos, t) = bodyTree @_ @ChainwebHashTag outs i --- -- -- FIXME use log --- -- let tree = (pos, t) --- -- -- we blindly trust the ix --- -- --- -- -- 2. Payload proof --- -- let !proof = tree N.:| [headerTree_ @BlockOutputsHash payload] --- -- return (subj, proof) --- -- where --- -- blockOutputs = _payloadCacheBlockOutputs $ _payloadCache db --- -- --- -- payload = do --- -- Just pd <- lookupPayloadDataWithHeight payloadDb (Just $ view blockHeight txHeader) (view blockPayloadHash txHeader) --- -- return $ BlockPayload --- -- { _blockPayloadTransactionsHash = view payloadDataTransactionsHash pd --- -- , _blockPayloadOutputsHash = view payloadDataOutputsHash pd --- -- , _blockPayloadPayloadHash = view payloadDataPayloadHash pd --- -- } --- -- -------------------------------------------------------------------------- -- -- Internal Proof Creation diff --git a/src/Chainweb/SPV/RestAPI.hs b/src/Chainweb/SPV/RestAPI.hs index 044760b158..69c16f9f10 100644 --- a/src/Chainweb/SPV/RestAPI.hs +++ b/src/Chainweb/SPV/RestAPI.hs @@ -15,12 +15,8 @@ -- module Chainweb.SPV.RestAPI ( --- * Transaction Proof API - SpvGetTransactionProofApi -, spvGetTransactionProofApi - -- * Transaction Output Proof API -, SpvGetTransactionOutputProofApi + SpvGetTransactionOutputProofApi , spvGetTransactionOutputProofApi -- * Event Proof API @@ -52,24 +48,6 @@ import Chainweb.SPV import Chainweb.Version import Chainweb.MerkleUniverse --- -------------------------------------------------------------------------- -- --- GET Transaction Proof - -type SpvGetTransactionProofApi_ - = "spv" - :> "chain" :> Capture "spvChain" ChainId - :> "height" :> Capture "spvHeight" BlockHeight - :> "transaction" :> Capture "spvTransactionIndex" Natural - :> Get '[JSON] (TransactionProof ChainwebMerkleHashAlgorithm) - -type SpvGetTransactionProofApi (v :: ChainwebVersionT) (c :: ChainIdT) - = 'ChainwebEndpoint v :> ChainEndpoint c :> SpvGetTransactionProofApi_ - -spvGetTransactionProofApi - :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . Proxy (SpvGetTransactionProofApi v c) -spvGetTransactionProofApi = Proxy - -- -------------------------------------------------------------------------- -- -- GET Transaction Output Proof diff --git a/src/Chainweb/SPV/RestAPI/Client.hs b/src/Chainweb/SPV/RestAPI/Client.hs index 4f06b0d745..b36cc8040b 100644 --- a/src/Chainweb/SPV/RestAPI/Client.hs +++ b/src/Chainweb/SPV/RestAPI/Client.hs @@ -15,8 +15,7 @@ -- Client implementation of the SPV REST API -- module Chainweb.SPV.RestAPI.Client -( spvGetTransactionProofClient -, spvGetTransactionOutputProofClient +( spvGetTransactionOutputProofClient ) where import Control.Monad.Identity @@ -37,45 +36,6 @@ import Chainweb.SPV.RestAPI import Chainweb.Version import Chainweb.MerkleUniverse --- -------------------------------------------------------------------------- -- --- SPV Transaction Proof Client - -spvGetTransactionProofClient_ - :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . KnownChainwebVersionSymbol v - => KnownChainIdSymbol c - => ChainId - -- ^ the source chain of the proof. This is the chain where the proof - -- subject, the transaction for which inclusion is proven, is located. - -> BlockHeight - -- ^ the block height of the proof subject, the transaction for which - -- inclusion is proven. - -> Natural - -- ^ the index of the proof subject, the transaction for which inclusion - -- is proven. - -> ClientM (TransactionProof ChainwebMerkleHashAlgorithm) -spvGetTransactionProofClient_ = client (spvGetTransactionProofApi @v @c) - -spvGetTransactionProofClient - :: ChainwebVersion - -> ChainId - -- ^ the target chain of the proof. This is the chain for which - -- inclusion is proved. - -> ChainId - -- ^ the source chain of the proof. This is the chain where the proof - -- subject, the transaction for which inclusion is proven, is located. - -> BlockHeight - -- ^ the block height of the proof subject, the transaction for which - -- inclusion is proven. - -> Natural - -- ^ the index of the proof subject, the transaction for which inclusion - -- is proven. - -> ClientM (TransactionProof ChainwebMerkleHashAlgorithm) -spvGetTransactionProofClient v tcid scid h i = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v - SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal tcid - return $ spvGetTransactionProofClient_ @v @c scid h i - -- -------------------------------------------------------------------------- -- -- SPV Transaction Output Proof Client diff --git a/src/Chainweb/SPV/RestAPI/Server.hs b/src/Chainweb/SPV/RestAPI/Server.hs index 76a36b929c..8f9ebca71d 100644 --- a/src/Chainweb/SPV/RestAPI/Server.hs +++ b/src/Chainweb/SPV/RestAPI/Server.hs @@ -17,8 +17,7 @@ -- Server implementation of the SPV REST API -- module Chainweb.SPV.RestAPI.Server -( spvGetTransactionProofHandler -, spvGetTransactionOutputProofHandler +( spvGetTransactionOutputProofHandler , spvServer , spvApp , spvApiLayout @@ -47,28 +46,6 @@ import Servant import qualified Data.ByteString.Lazy.Char8 as BL8 import Chainweb.MerkleUniverse --- -------------------------------------------------------------------------- -- --- SPV Transaction Proof Handler - -spvGetTransactionProofHandler - :: CutDb - -> ChainId - -- ^ the target chain of the proof. This is the chain for which - -- inclusion is proved. - -> ChainId - -- ^ the source chain of the proof. This is the chain where the proof - -- subject, the transaction for which inclusion is proven, is located. - -> BlockHeight - -- ^ the block height of the proof subject, the transaction for which - -- inclusion is proven. - -> Natural - -- ^ the index of the proof subject, the transaction for which inclusion - -- is proven. - -> Handler (TransactionProof ChainwebMerkleHashAlgorithm) -spvGetTransactionProofHandler db tcid scid bh i = - liftIO $ createTransactionProof db tcid scid bh (int i) - -- FIXME: add proper error handling - -- -------------------------------------------------------------------------- -- -- SPV Transaction Output Proof Handler diff --git a/src/Chainweb/SPV/VerifyProof.hs b/src/Chainweb/SPV/VerifyProof.hs index f91a0ad4e6..76b716dbc3 100644 --- a/src/Chainweb/SPV/VerifyProof.hs +++ b/src/Chainweb/SPV/VerifyProof.hs @@ -1,6 +1,5 @@ -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedStrings #-} -- | -- Module: Chainweb.SPV.VerifyProof @@ -12,173 +11,45 @@ -- Verification of Merkle proofs in the Chainweb Merkle tree. -- module Chainweb.SPV.VerifyProof -( --- * Transaction Proofs - runTransactionProof -, verifyTransactionProof -, verifyTransactionProofAt -, verifyTransactionProofAt_ - --- * Transaction Output Proofs -, runTransactionOutputProof -, verifyTransactionOutputProof -, verifyTransactionOutputProofAt -, verifyTransactionOutputProofAt_ +( verifyTransactionOutputProofAt ) where import Control.Monad.Catch import Data.MerkleLog.V1 qualified as V1 -import Prelude hiding (lookup) - -- internal modules import Chainweb.BlockHash import Chainweb.BlockHeaderDB import Chainweb.Crypto.MerkleLog -import Chainweb.CutDB import Chainweb.MerkleLogHash +import Chainweb.MerkleUniverse import Chainweb.Payload import Chainweb.SPV import Chainweb.TreeDB import Chainweb.Utils -import Chainweb.MerkleUniverse -- -------------------------------------------------------------------------- -- --- Transaction Proofs - --- | Runs a transaction Proof. Returns the block hash on the target chain for --- which inclusion is proven. --- -runTransactionProof - :: MonadThrow m - => TransactionProof ChainwebMerkleHashAlgorithm - -> m BlockHash -runTransactionProof (TransactionProof _ p) - = BlockHash . MerkleLogHash <$> V1.runMerkleProof p - --- | Verifies the proof against the current state of consensus. The result --- confirms that the subject of the proof occurs in the history of the winning --- fork of the target chain. --- -verifyTransactionProof - :: CutDb - -> TransactionProof ChainwebMerkleHashAlgorithm - -> IO Transaction -verifyTransactionProof cutDb proof@(TransactionProof cid p) = do - -- FIXME FIXME FIXME: If this is recorded on chain we have to double check - -- if this introduces a new failure condition! - h <- runTransactionProof proof - unlessM (member cutDb cid h) $ throwM - $ SpvExceptionVerificationFailed "target header is not in the chain" - proofSubject p -- | Verifies the proof for the given block hash. The result confirms that the -- subject of the proof occurs in the history of the target chain before the -- given block hash. -- --- Throws 'TreeDbKeyNotFound' if the given block hash doesn't exist on target --- chain. +-- Throws 'TreeDbKeyNotFound' if the given block hash isn't found in the given +-- BlockHeaderDb. -- -verifyTransactionProofAt - :: CutDb - -> TransactionProof ChainwebMerkleHashAlgorithm - -> BlockHash - -> IO Transaction -verifyTransactionProofAt cutDb proof@(TransactionProof cid p) ctx = do - -- FIXME FIXME FIXME: If this is recorded on chain we have to double check - -- if this introduces a new failure condition! - h <- runTransactionProof proof - unlessM (memberOfM cutDb cid h ctx) $ throwM - $ SpvExceptionVerificationFailed "target header is not in the chain" - proofSubject p - --- | Verifies the proof for the given block hash. The result confirms that the --- subject of the proof occurs in the history of the target chain before the --- given block hash. +-- Note that the target chain argument in the proof is ignored. -- --- Throws 'TreeDbKeyNotFound' if the given block hash doesn't exist on target --- then chain or when the given BlockHeaderDb is not for the target chain. --- -verifyTransactionProofAt_ - :: BlockHeaderDb - -> TransactionProof ChainwebMerkleHashAlgorithm - -> BlockHash - -> IO Transaction -verifyTransactionProofAt_ bdb proof@(TransactionProof _cid p) ctx = do - -- FIXME FIXME FIXME: If this is recorded on chain we have to double check - -- if this introduces a new failure condition! - h <- runTransactionProof proof - unlessM (ancestorOf bdb h ctx) $ throwM - $ SpvExceptionVerificationFailed "target header is not in the chain" - proofSubject p - --- -------------------------------------------------------------------------- -- --- Output Proofs - --- | Runs a transaction Proof. Returns the block hash on the target chain for --- which inclusion is proven. --- -runTransactionOutputProof - :: MonadThrow m - => TransactionOutputProof ChainwebMerkleHashAlgorithm - -> m BlockHash -runTransactionOutputProof (TransactionOutputProof _ p) - = BlockHash . MerkleLogHash <$> V1.runMerkleProof p - --- | Verifies the proof against the current state of consensus. The result --- confirms that the subject of the proof occurs in the history of the winning --- fork of the target chain. --- -verifyTransactionOutputProof - :: CutDb - -> TransactionOutputProof ChainwebMerkleHashAlgorithm - -> IO TransactionOutput -verifyTransactionOutputProof cutDb proof@(TransactionOutputProof cid p) = do - -- FIXME FIXME FIXME: If this is recorded on chain we have to double check - -- if this introduces a new failure condition! - h <- runTransactionOutputProof proof - unlessM (member cutDb cid h) $ throwM - $ SpvExceptionVerificationFailed "target header is not in the chain" - proofSubject p - --- | Verifies the proof for the given block hash. The result confirms that the --- subject of the proof occurs in the history of the target chain before the --- given block hash. --- --- Throws 'TreeDbKeyNotFound' if the given block hash doesn't exist on target --- chain. +-- NOTE: Used by Pact-4 and Pact-5 on-chain SPV support! -- verifyTransactionOutputProofAt - :: CutDb - -> TransactionOutputProof ChainwebMerkleHashAlgorithm - -> BlockHash - -> IO TransactionOutput -verifyTransactionOutputProofAt cutDb proof@(TransactionOutputProof cid p) ctx = do - -- FIXME FIXME FIXME: If this is recorded on chain we have to double check - -- if this introduces a new failure condition! - h <- runTransactionOutputProof proof - unlessM (memberOfM cutDb cid h ctx) $ throwM - $ SpvExceptionVerificationFailed "target header is not in the chain" - proofSubject p - --- | Verifies the proof for the given block hash. The result confirms that the --- subject of the proof occurs in the history of the target chain before the --- given block hash. --- --- Throws 'TreeDbKeyNotFound' if the given block hash doesn't exist on target --- the chain or when the given BlockHeaderDb is not for the target chain. --- -verifyTransactionOutputProofAt_ :: BlockHeaderDb -> TransactionOutputProof ChainwebMerkleHashAlgorithm -> BlockHash -> IO TransactionOutput -verifyTransactionOutputProofAt_ bdb proof@(TransactionOutputProof _cid p) ctx = do - -- FIXME FIXME FIXME: If this is recorded on chain we have to double check - -- if this introduces a new failure condition! - h <- runTransactionOutputProof proof +verifyTransactionOutputProofAt bdb (TransactionOutputProof _ p) ctx = do + h <- BlockHash . MerkleLogHash <$> V1.runMerkleProof p unlessM (ancestorOf bdb h ctx) $ throwM $ SpvExceptionVerificationFailed "target header is not in the chain" proofSubject p From 1eb542cb3d93680e8dc3c39e9d6b60055d187204 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 10 Mar 2025 17:12:54 -0400 Subject: [PATCH 099/378] Delete pact 4 --- chainweb.cabal | 89 +- node/src/ChainwebNode.hs | 4 - src/Chainweb/Chainweb.hs | 4 +- src/Chainweb/Chainweb/Configuration.hs | 16 +- src/Chainweb/Core/Brief.hs | 3 + src/Chainweb/Mempool/Consensus.hs | 31 +- src/Chainweb/Mempool/InMem.hs | 36 +- .../Mempool/InMem/ValidatingConfig.hs | 6 +- src/Chainweb/Mempool/Mempool.hs | 77 +- src/Chainweb/Miner/Coordinator.hs | 46 +- src/Chainweb/Miner/Miners.hs | 3 +- src/Chainweb/Miner/PayloadCache.hs | 24 +- .../{Pact5 => Pact}/Backend/ChainwebPactDb.hs | 43 +- src/Chainweb/Pact/Backend/Compaction.hs | 2 +- .../Backend/PactState/GrandHash/Import.hs | 2 +- src/Chainweb/Pact/Backend/Types.hs | 86 +- src/Chainweb/Pact/Backend/Utils.hs | 55 +- src/Chainweb/{Pact5 => Pact}/NoCoinbase.hs | 4 +- src/Chainweb/Pact/PactService.hs | 100 +- src/Chainweb/Pact/PactService/Checkpointer.hs | 28 +- .../Pact/PactService/Checkpointer/Internal.hs | 252 +-- .../Pact/PactService/{Pact5 => }/ExecBlock.hs | 23 +- .../Pact/PactService/Pact4/ExecBlock.hs | 886 -------- src/Chainweb/Pact/RestAPI/Server.hs | 12 +- src/Chainweb/{Pact5 => Pact}/SPV.hs | 2 +- src/Chainweb/Pact/Service/BlockValidation.hs | 156 -- src/Chainweb/Pact/Service/PactInProcApi.hs | 2 +- src/Chainweb/Pact/Service/PactQueue.hs | 240 --- src/Chainweb/{Pact5 => Pact}/Templates.hs | 6 +- src/Chainweb/Pact/Transaction.hs | 109 + .../{Pact5 => Pact}/TransactionExec.hs | 52 +- .../Pact/Transactions/CoinV3Transactions.hs | 19 - .../Pact/Transactions/CoinV4Transactions.hs | 21 - .../Pact/Transactions/CoinV5Transactions.hs | 19 - .../Pact/Transactions/CoinV6Transactions.hs | 19 - .../Transactions/FungibleV2Transactions.hs | 19 - .../Pact/Transactions/Mainnet0Transactions.hs | 23 - .../Pact/Transactions/Mainnet1Transactions.hs | 23 - .../Pact/Transactions/Mainnet2Transactions.hs | 23 - .../Pact/Transactions/Mainnet3Transactions.hs | 23 - .../Pact/Transactions/Mainnet4Transactions.hs | 23 - .../Pact/Transactions/Mainnet5Transactions.hs | 23 - .../Pact/Transactions/Mainnet6Transactions.hs | 23 - .../Pact/Transactions/Mainnet7Transactions.hs | 23 - .../Pact/Transactions/Mainnet8Transactions.hs | 23 - .../Pact/Transactions/Mainnet9Transactions.hs | 23 - .../Transactions/MainnetKADTransactions.hs | 19 - .../Pact/Transactions/OtherTransactions.hs | 21 - .../RecapDevelopmentTransactions.hs | 23 - src/Chainweb/Pact/Types.hs | 1639 ++++----------- src/Chainweb/{Pact5 => Pact}/Validations.hs | 31 +- src/Chainweb/Pact4/Backend/ChainwebPactDb.hs | 891 -------- src/Chainweb/Pact4/ModuleCache.hs | 74 - src/Chainweb/Pact4/NoCoinbase.hs | 31 - src/Chainweb/Pact4/SPV.hs | 489 ----- src/Chainweb/Pact4/Templates.hs | 203 -- src/Chainweb/Pact4/Transaction.hs | 190 -- src/Chainweb/Pact4/TransactionExec.hs | 1553 -------------- src/Chainweb/Pact4/Types.hs | 326 --- src/Chainweb/Pact4/Validations.hs | 285 --- src/Chainweb/Pact5/Transaction.hs | 249 --- src/Chainweb/Pact5/Types.hs | 197 -- src/Chainweb/PayloadProvider.hs | 90 +- src/Chainweb/PayloadProvider/EVM.hs | 8 +- src/Chainweb/PayloadProvider/Minimal.hs | 16 +- src/Chainweb/PayloadProvider/Pact.hs | 1 - src/Chainweb/Sync/WebBlockHeaderStore.hs | 2 +- src/Chainweb/Version.hs | 89 +- src/Chainweb/Version/Guards.hs | 14 - src/Chainweb/Version/Mainnet.hs | 37 +- src/Chainweb/Version/RecapDevelopment.hs | 85 +- src/Chainweb/Version/Registry.hs | 6 +- src/Chainweb/Version/Testnet04.hs | 37 +- src/Chainweb/WebPactExecutionService.hs | 10 +- test/lib/Chainweb/Test/Pact4/Utils.hs | 2 +- test/lib/Chainweb/Test/Utils/BlockHeader.hs | 30 +- test/lib/Chainweb/Test/Utils/TestHeader.hs | 8 +- .../Chainweb/Test/BlockHeader/Validation.hs | 2 +- test/unit/Chainweb/Test/Pact4/Checkpointer.hs | 872 -------- test/unit/Chainweb/Test/Pact4/DbCacheTest.hs | 86 - test/unit/Chainweb/Test/Pact4/GrandHash.hs | 190 -- .../Test/Pact4/ModuleCacheOnRestart.hs | 306 --- test/unit/Chainweb/Test/Pact4/NoCoinbase.hs | 37 - test/unit/Chainweb/Test/Pact4/PactExec.hs | 659 ------ .../Chainweb/Test/Pact4/PactMultiChainTest.hs | 1833 ----------------- test/unit/Chainweb/Test/Pact4/PactReplay.hs | 363 ---- .../Test/Pact4/PactSingleChainTest.hs | 1471 ------------- .../Chainweb/Test/Pact4/RemotePactTest.hs | 1319 ------------ test/unit/Chainweb/Test/Pact4/RewardsTest.hs | 40 - test/unit/Chainweb/Test/Pact4/SPV.hs | 549 ----- test/unit/Chainweb/Test/Pact4/SQLite.hs | 307 --- test/unit/Chainweb/Test/Pact4/TTL.hs | 302 --- .../Chainweb/Test/Pact4/TransactionTests.hs | 410 ---- .../Chainweb/Test/Pact4/VerifierPluginTest.hs | 16 - 94 files changed, 1001 insertions(+), 17143 deletions(-) rename src/Chainweb/{Pact5 => Pact}/Backend/ChainwebPactDb.hs (95%) rename src/Chainweb/{Pact5 => Pact}/NoCoinbase.hs (90%) rename src/Chainweb/Pact/PactService/{Pact5 => }/ExecBlock.hs (98%) delete mode 100644 src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs rename src/Chainweb/{Pact5 => Pact}/SPV.hs (99%) delete mode 100644 src/Chainweb/Pact/Service/BlockValidation.hs delete mode 100644 src/Chainweb/Pact/Service/PactQueue.hs rename src/Chainweb/{Pact5 => Pact}/Templates.hs (97%) create mode 100644 src/Chainweb/Pact/Transaction.hs rename src/Chainweb/{Pact5 => Pact}/TransactionExec.hs (95%) delete mode 100644 src/Chainweb/Pact/Transactions/CoinV3Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/CoinV4Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/CoinV5Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/CoinV6Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/FungibleV2Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/OtherTransactions.hs delete mode 100644 src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs rename src/Chainweb/{Pact5 => Pact}/Validations.hs (90%) delete mode 100644 src/Chainweb/Pact4/Backend/ChainwebPactDb.hs delete mode 100644 src/Chainweb/Pact4/ModuleCache.hs delete mode 100644 src/Chainweb/Pact4/NoCoinbase.hs delete mode 100644 src/Chainweb/Pact4/SPV.hs delete mode 100644 src/Chainweb/Pact4/Templates.hs delete mode 100644 src/Chainweb/Pact4/Transaction.hs delete mode 100644 src/Chainweb/Pact4/TransactionExec.hs delete mode 100644 src/Chainweb/Pact4/Types.hs delete mode 100644 src/Chainweb/Pact4/Validations.hs delete mode 100644 src/Chainweb/Pact5/Transaction.hs delete mode 100644 src/Chainweb/Pact5/Types.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/Checkpointer.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/DbCacheTest.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/GrandHash.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/ModuleCacheOnRestart.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/NoCoinbase.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/PactExec.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/PactReplay.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/PactSingleChainTest.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/RemotePactTest.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/RewardsTest.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/SPV.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/SQLite.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/TTL.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/TransactionTests.hs delete mode 100644 test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs diff --git a/chainweb.cabal b/chainweb.cabal index 51085aa84d..c127217864 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -266,8 +266,7 @@ library , Chainweb.SPV.RestAPI.Client , Chainweb.Sync.WebBlockHeaderStore , Chainweb.Time - , Chainweb.Pact4.Transaction - , Chainweb.Pact5.Transaction + , Chainweb.Pact.Transaction , Chainweb.TreeDB , Chainweb.Utils , Chainweb.Utils.Paging @@ -317,8 +316,7 @@ library , P2P.TaskQueue -- pact - , Chainweb.Pact4.Backend.ChainwebPactDb - , Chainweb.Pact5.Backend.ChainwebPactDb + , Chainweb.Pact.Backend.ChainwebPactDb , Chainweb.Pact.Backend.DbCache , Chainweb.Pact.Backend.Compaction , Chainweb.Pact.Backend.PactState @@ -338,48 +336,19 @@ library , Chainweb.Pact.PactService , Chainweb.Pact.PactService.Checkpointer , Chainweb.Pact.PactService.Checkpointer.Internal - , Chainweb.Pact.PactService.Pact4.ExecBlock - , Chainweb.Pact.PactService.Pact5.ExecBlock + , Chainweb.Pact.PactService.ExecBlock , Chainweb.Pact.RestAPI , Chainweb.Pact.RestAPI.Client , Chainweb.Pact.RestAPI.EthSpv , Chainweb.Pact.RestAPI.SPV , Chainweb.Pact.RestAPI.Server - , Chainweb.Pact4.SPV - , Chainweb.Pact5.SPV - , Chainweb.Pact.Service.BlockValidation + , Chainweb.Pact.SPV , Chainweb.Pact.Service.PactInProcApi - , Chainweb.Pact.Service.PactQueue - , Chainweb.Pact4.ModuleCache - , Chainweb.Pact4.NoCoinbase - , Chainweb.Pact4.Templates - , Chainweb.Pact4.TransactionExec - , Chainweb.Pact4.Types - , Chainweb.Pact4.Validations - , Chainweb.Pact5.NoCoinbase - , Chainweb.Pact5.Templates - , Chainweb.Pact5.TransactionExec - , Chainweb.Pact5.Types - , Chainweb.Pact5.Validations - , Chainweb.Pact.Transactions.FungibleV2Transactions - , Chainweb.Pact.Transactions.CoinV3Transactions - , Chainweb.Pact.Transactions.CoinV4Transactions - , Chainweb.Pact.Transactions.CoinV5Transactions - , Chainweb.Pact.Transactions.CoinV6Transactions - , Chainweb.Pact.Transactions.Mainnet0Transactions - , Chainweb.Pact.Transactions.Mainnet1Transactions - , Chainweb.Pact.Transactions.Mainnet2Transactions - , Chainweb.Pact.Transactions.Mainnet3Transactions - , Chainweb.Pact.Transactions.Mainnet4Transactions - , Chainweb.Pact.Transactions.Mainnet5Transactions - , Chainweb.Pact.Transactions.Mainnet6Transactions - , Chainweb.Pact.Transactions.Mainnet7Transactions - , Chainweb.Pact.Transactions.Mainnet8Transactions - , Chainweb.Pact.Transactions.Mainnet9Transactions - , Chainweb.Pact.Transactions.MainnetKADTransactions - , Chainweb.Pact.Transactions.OtherTransactions - , Chainweb.Pact.Transactions.RecapDevelopmentTransactions + , Chainweb.Pact.NoCoinbase + , Chainweb.Pact.Templates + , Chainweb.Pact.TransactionExec , Chainweb.Pact.Types + , Chainweb.Pact.Validations , Chainweb.Pact.Utils -- utils @@ -519,14 +488,8 @@ library chainweb-test-utils Chainweb.Test.HostAddress Chainweb.Test.MultiNode Chainweb.Test.P2P.Peer.BootstrapConfig - Chainweb.Test.Pact4.Utils - Chainweb.Test.Pact4.VerifierPluginTest.Transaction - Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Message.After225 - Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Message.Before225 - Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils - Chainweb.Test.Pact4.VerifierPluginTest.Unit - Chainweb.Test.Pact5.CmdBuilder - Chainweb.Test.Pact5.Utils + Chainweb.Test.Pact.CmdBuilder + Chainweb.Test.Pact.Utils Chainweb.Test.RestAPI.Client_ Chainweb.Test.RestAPI.Utils Chainweb.Test.TestVersions @@ -657,30 +620,14 @@ test-suite chainweb-tests Chainweb.Test.MinerReward Chainweb.Test.Mining Chainweb.Test.Misc - Chainweb.Test.Pact4.Checkpointer - Chainweb.Test.Pact4.DbCacheTest - Chainweb.Test.Pact4.GrandHash - Chainweb.Test.Pact4.ModuleCacheOnRestart - Chainweb.Test.Pact4.NoCoinbase - Chainweb.Test.Pact4.PactExec - Chainweb.Test.Pact4.PactMultiChainTest - Chainweb.Test.Pact4.PactReplay - Chainweb.Test.Pact4.PactSingleChainTest - Chainweb.Test.Pact4.RemotePactTest - Chainweb.Test.Pact4.RewardsTest - Chainweb.Test.Pact4.SPV - Chainweb.Test.Pact4.SQLite - Chainweb.Test.Pact4.TTL - Chainweb.Test.Pact4.TransactionTests - Chainweb.Test.Pact4.VerifierPluginTest - Chainweb.Test.Pact5.CheckpointerTest - Chainweb.Test.Pact5.CutFixture - Chainweb.Test.Pact5.HyperlanePluginTests - Chainweb.Test.Pact5.PactServiceTest - Chainweb.Test.Pact5.RemotePactTest - Chainweb.Test.Pact5.SPVTest - Chainweb.Test.Pact5.TransactionExecTest - Chainweb.Test.Pact5.TransactionTests + Chainweb.Test.Pact.CheckpointerTest + Chainweb.Test.Pact.CutFixture + Chainweb.Test.Pact.HyperlanePluginTests + Chainweb.Test.Pact.PactServiceTest + Chainweb.Test.Pact.RemotePactTest + Chainweb.Test.Pact.SPVTest + Chainweb.Test.Pact.TransactionExecTest + Chainweb.Test.Pact.TransactionTests Chainweb.Test.RestAPI Chainweb.Test.Roundtrips Chainweb.Test.SPV diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index bb3a5ad100..a48ad38bb2 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -94,7 +94,6 @@ import Chainweb.Mempool.Consensus (ReintroducedTxsLog) import Chainweb.Mempool.InMemTypes (MempoolStats(..)) -- import Chainweb.Miner.Coordinator (MiningStats) import Chainweb.Pact.Backend.DbCache (DbCacheStats) -import Chainweb.Pact.Service.PactQueue (PactQueueStats) import Chainweb.Pact.RestAPI.Server (PactCmdLog(..)) import Chainweb.Pact.Types import Chainweb.Payload @@ -425,8 +424,6 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do $ mkTelemetryLogger @DbCacheStats mgr teleLogConfig dbStatsBackend <- managed $ mkTelemetryLogger @DbStats mgr teleLogConfig - pactQueueStatsBackend <- managed - $ mkTelemetryLogger @PactQueueStats mgr teleLogConfig p2pNodeStatsBackend <- managed $ mkTelemetryLogger @P2pNodeStats mgr teleLogConfig topLevelStatusBackend <- managed @@ -454,7 +451,6 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do , logHandler blockUpdateBackend , logHandler dbCacheBackend , logHandler dbStatsBackend - , logHandler pactQueueStatsBackend , logHandler p2pNodeStatsBackend , logHandler topLevelStatusBackend ] diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 5ebef79a20..7d4ec74265 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -159,7 +159,7 @@ import Chainweb.OpenAPIValidation qualified as OpenAPIValidation import Chainweb.Pact.Backend.Types(IntraBlockPersistence(..)) import Chainweb.Pact.RestAPI.Server (PactServerData(..)) import Chainweb.Pact.Types (PactServiceConfig(..)) -import Chainweb.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact.Transaction qualified as Pact4 import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.RocksDB import Chainweb.PayloadProvider @@ -390,7 +390,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir isJust (_cutDbParamsInitialCutFile cutDbParams) , _pactNewBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) , _pactLogGas = _configLogGas conf - , _pactModuleCacheLimit = _configModuleCacheLimit conf , _pactEnableLocalTimeout = _configEnableLocalTimeout conf , _pactFullHistoryRequired = _configFullHistoricPactState conf , _pactPersistIntraBlockWrites = @@ -969,4 +968,3 @@ runChainweb cw nowServing = do -- return $ map (runMempoolSyncClient mgr conf (_chainwebPeer cw)) chainVals logg Warn "Overwriting mempool p2p sync client configuration. It is currently not supported" return [] - diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 01b7ef31bb..16a3827a89 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -100,7 +100,7 @@ import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Config import Chainweb.Pact.Backend.DbCache (DbCacheLimitBytes) import Chainweb.Pact.Types (RewindLimit(..)) -import Chainweb.Pact.Types (defaultReorgLimit, defaultModuleCacheLimit, defaultPreInsertCheckTimeout) +import Chainweb.Pact.Types (defaultReorgLimit, defaultPreInsertCheckTimeout) import Chainweb.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) import Chainweb.PayloadProvider.EVM (EvmProviderConfig, defaultEvmProviderConfig, pEvmProviderConfig) import Chainweb.PayloadProvider.Minimal (MinimalProviderConfig, defaultMinimalProviderConfig, pMinimalProviderConfig) @@ -135,6 +135,7 @@ import P2P.Node.Configuration import Pact.JSON.Encode qualified as J import Prelude hiding (log) import System.Directory +import qualified Pact.Core.Gas as Pact -- -------------------------------------------------------------------------- -- -- Payload Provider Configuration @@ -598,8 +599,6 @@ data ChainwebConfiguration = ChainwebConfiguration , _configSyncPactChains :: !(Maybe [ChainId]) -- ^ the only chains to be synchronized on startup to the latest cut. -- if unset, all chains will be synchronized. - , _configModuleCacheLimit :: !DbCacheLimitBytes - -- ^ module cache size limit in bytes , _configEnableLocalTimeout :: !Bool , _configPayloadProviders :: PayloadProviderConfig } deriving (Show, Eq, Generic) @@ -648,9 +647,9 @@ defaultChainwebConfiguration v = ChainwebConfiguration , _configP2p = defaultP2pConfiguration , _configThrottling = defaultThrottlingConfig , _configMempoolP2p = defaultEnableConfig defaultMempoolP2pConfig - , _configBlockGasLimit = 150_000 + , _configBlockGasLimit = Pact.GasLimit (Pact.Gas 150_000) , _configLogGas = False - , _configMinGasPrice = 1e-8 + , _configMinGasPrice = Pact.GasPrice 1e-8 , _configPactQueueSize = 2000 , _configReorgLimit = defaultReorgLimit , _configPreInsertCheckTimeout = defaultPreInsertCheckTimeout @@ -661,7 +660,6 @@ defaultChainwebConfiguration v = ChainwebConfiguration , _configReadOnlyReplay = False , _configSyncPactChains = Nothing , _configBackup = defaultBackupConfig - , _configModuleCacheLimit = defaultModuleCacheLimit , _configEnableLocalTimeout = False , _configPayloadProviders = minimalPayloadProviderConfig defaultMinimalProviderConfig -- Similar to bootstrap-peers, there is no default configuration that @@ -703,7 +701,6 @@ instance ToJSON ChainwebConfiguration where , "readOnlyReplay" .= _configReadOnlyReplay o , "syncPactChains" .= _configSyncPactChains o , "backup" .= _configBackup o - , "moduleCacheLimit" .= _configModuleCacheLimit o , "enableLocalTimeout" .= _configEnableLocalTimeout o , "payloadProviders" .= _configPayloadProviders o ] @@ -735,7 +732,6 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where <*< configReadOnlyReplay ..: "readOnlyReplay" % o <*< configSyncPactChains ..: "syncPactChains" % o <*< configBackup %.: "backup" % o - <*< configModuleCacheLimit ..: "moduleCacheLimit" % o <*< configEnableLocalTimeout ..: "enableLocalTimeout" % o <*< configPayloadProviders %.: "payloadProviders" % o @@ -791,10 +787,6 @@ pChainwebConfiguration = id <> help "The only Pact databases to synchronize. If empty or unset, all chains will be synchronized." <> metavar "JSON list of chain ids" <*< configBackup %:: pBackupConfig - <*< configModuleCacheLimit .:: option auto - % long "module-cache-limit" - <> help "Maximum size of the per-chain checkpointer module cache in bytes" - <> metavar "INT" <*< configEnableLocalTimeout .:: option auto % long "enable-local-timeout" <> help "Enable timeout support on /local endpoints" diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index fc3c4964bd..daf446b0f8 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -63,6 +63,9 @@ instance Brief a => Brief (Maybe a) where instance Brief a => Brief [a] where brief l = "[" <> (T.intercalate "," $ brief <$> l) <> "]" +instance Brief a => Brief (Parent a) where + brief = brief . unwrapParent + -- -------------------------------------------------------------------------- -- -- Core Chainweb Types diff --git a/src/Chainweb/Mempool/Consensus.hs b/src/Chainweb/Mempool/Consensus.hs index 12d2c22336..4de8ec0104 100644 --- a/src/Chainweb/Mempool/Consensus.hs +++ b/src/Chainweb/Mempool/Consensus.hs @@ -47,20 +47,19 @@ import Chainweb.Mempool.Mempool import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time -import qualified Chainweb.Pact4.Transaction as Pact4 +import qualified Chainweb.Pact.Transaction as Pact import Chainweb.TreeDB import Chainweb.Utils import Data.LogMessage (JsonLog(..), LogFunction) -import qualified Pact.Types.ChainMeta as Pact4 -import Data.Text (Text) +import Data.Coerce (coerce) ------------------------------------------------------------------------------ data MempoolConsensus = MempoolConsensus - { mpcMempool :: !(MempoolBackend Pact4.UnparsedTransaction) + { mpcMempool :: !(MempoolBackend Pact.Transaction) , mpcLastNewBlockParent :: !(IORef (Maybe BlockHeader)) , mpcProcessFork - :: LogFunction -> BlockHeader -> IO (Vector Pact4.UnparsedTransaction, Vector Pact4.UnparsedTransaction) + :: LogFunction -> BlockHeader -> IO (Vector Pact.Transaction, Vector Pact.Transaction) } data ReintroducedTxsLog = ReintroducedTxsLog @@ -81,7 +80,7 @@ instance Exception MempoolException ------------------------------------------------------------------------------ mkMempoolConsensus :: CanReadablePayloadCas tbl - => MempoolBackend Pact4.UnparsedTransaction + => MempoolBackend Pact.Transaction -> BlockHeaderDb -> Maybe (PayloadDb tbl) -> IO MempoolConsensus @@ -103,23 +102,23 @@ processFork -> IORef (Maybe BlockHeader) -> LogFunction -> BlockHeader - -> IO (Vector Pact4.UnparsedTransaction, Vector Pact4.UnparsedTransaction) + -> IO (Vector Pact.Transaction, Vector Pact.Transaction) processFork blockHeaderDb payloadStore lastHeaderRef logFun newHeader = do now <- getCurrentTimeIntegral lastHeader <- readIORef lastHeaderRef (a, b) <- processFork' logFun blockHeaderDb newHeader lastHeader (payloadLookup payloadStore) (processForkCheckTTL now) - return (V.map Pact4.unHashable a, V.map Pact4.unHashable b) + return (coerce a, coerce b) ------------------------------------------------------------------------------ processForkCheckTTL :: Time Micros - -> Pact4.HashableTrans (Pact4.PayloadWithText Pact4.PublicMeta Text) -> Bool -processForkCheckTTL now (Pact4.HashableTrans t) = + -> Pact.HashableTransaction -> Bool +processForkCheckTTL now (Pact.HashableTransaction t) = either (const False) (const True) $ - txTTLCheck pact4TransactionConfig now t + txTTLCheck pactTransactionConfig now t ------------------------------------------------------------------------------ @@ -168,7 +167,7 @@ payloadLookup :: CanReadablePayloadCas tbl => Maybe (PayloadDb tbl) -> BlockHeader - -> IO (HashSet (Pact4.HashableTrans (Pact4.PayloadWithText Pact4.PublicMeta Text))) + -> IO (HashSet Pact.HashableTransaction) payloadLookup payloadStore bh = case payloadStore of Nothing -> return mempty @@ -180,14 +179,14 @@ payloadLookup payloadStore bh = ------------------------------------------------------------------------------ chainwebTxsFromPd :: PayloadData - -> IO (HashSet (Pact4.HashableTrans (Pact4.PayloadWithText Pact4.PublicMeta Text))) + -> IO (HashSet Pact.HashableTransaction) chainwebTxsFromPd pd = do let transSeq = view payloadDataTransactions pd let bytes = _transactionBytes <$> transSeq let eithers = toCWTransaction <$> bytes -- Note: if any transactions fail to convert, the final validation hash will fail to match -- the one computed during newBlock - let theRights = rights $ toList eithers - return $! HS.fromList $ Pact4.HashableTrans <$!> theRights + let theRights = rights $ toList eithers + return $! HS.fromList $ coerce theRights where - toCWTransaction = codecDecode Pact4.rawCommandCodec + toCWTransaction = codecDecode Pact.payloadCodec diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 2c86640c5b..5ef1d7e839 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -58,8 +58,6 @@ import Data.Vector (Vector) import qualified Data.Vector as V import qualified Data.Vector.Algorithms.Tim as TimSort -import Pact.Parse - import Prelude hiding (init, lookup, pred) import System.LogLevel @@ -73,17 +71,17 @@ import Chainweb.Logger import Chainweb.Mempool.CurrentTxs import Chainweb.Mempool.InMemTypes import Chainweb.Mempool.Mempool -import Chainweb.Pact4.Validations (defaultMaxTTL, defaultMaxCoinDecimalPlaces) +import Chainweb.Pact.Validations (defaultMaxTTLSeconds, defaultMaxCoinDecimalPlaces) import Chainweb.Time import Chainweb.Utils import Chainweb.Version (ChainwebVersion) -import qualified Pact.Types.ChainMeta as P - import Numeric.AffineSpace import Data.ByteString (ByteString) import Data.Either (partitionEithers) import Control.Lens +import Chainweb.BlockHeader +import Pact.Core.Gas ------------------------------------------------------------------------------ compareOnGasPrice :: TransactionConfig t -> t -> t -> Ordering @@ -274,8 +272,7 @@ addToBadListInMem lock txs = withMVarMasked lock $ \mdata -> do let !pnd' = foldl' (flip HashMap.delete) pnd txs -- we don't have the expiry time here, so just use maxTTL now <- getCurrentTimeIntegral - let P.TTLSeconds (ParsedInteger mt) = defaultMaxTTL - let !endTime = add (secondsToTimeSpan $ fromIntegral mt) now + let !endTime = add (secondsToTimeSpan $ fromIntegral defaultMaxTTLSeconds) now let !bad' = foldl' (\h tx -> HashMap.insert tx endTime h) bad txs writeIORef (_inmemPending mdata) pnd' writeIORef (_inmemBadMap mdata) bad' @@ -444,7 +441,7 @@ validateOne cfg badmap curTxIdx now t h = gasPriceRoundingCheck = ebool_ (InsertErrorOther msg) (f (txGasPrice txcfg t)) where - f (GasPrice (ParsedDecimal d)) = decimalPlaces d <= defaultMaxCoinDecimalPlaces + f (GasPrice d) = decimalPlaces d <= defaultMaxCoinDecimalPlaces msg = T.unwords [ "This transaction's gas price:" , sshow (txGasPrice txcfg t) @@ -571,7 +568,7 @@ getBlockInMem -> BlockFill -> MempoolPreBlockCheck t to -> BlockHeight - -> BlockHash + -> Parent BlockHash -> IO (Vector to) getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight phash = do logFunctionText logg Debug $ "getBlockInMem: " <> sshow (gasLimit,bheight,phash) @@ -692,27 +689,27 @@ getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight p -> [(TransactionHash, (SB.ShortByteString, t))] -> Int -> [(TransactionHash, (SB.ShortByteString, t))] - getBatch !pendingTxs !sz !soFar !inARow + getBatch !pendingTxs !(GasLimit (Gas sz)) !soFar !inARow -- we'll keep looking for transactions until we hit maxInARow that are -- too large | V.null pendingTxs = soFar - | inARow >= maxInARow || sz <= 0 = soFar + | inARow >= maxInARow || sz == 0 = soFar | otherwise = do let (T2 (h, pe) !pendingTxs') = unconsV pendingTxs let !txbytes = _inmemPeBytes pe let !tx = decodeTx txbytes - let !txSz = getSize tx + let !(GasLimit (Gas txSz)) = getSize tx if txSz <= sz - then getBatch pendingTxs' (sz - txSz) ((h,(txbytes, tx)):soFar) 0 - else getBatch pendingTxs' sz soFar (inARow + 1) + then getBatch pendingTxs' (GasLimit (Gas (sz - txSz))) ((h,(txbytes, tx)):soFar) 0 + else getBatch pendingTxs' (GasLimit (Gas sz)) soFar (inARow + 1) go :: PendingMap -> BadMap -> GasLimit -> [[(TransactionHash, (SB.ShortByteString, t, to))]] -> IO (T3 PendingMap BadMap (Vector (TransactionHash, (SB.ShortByteString, t, to)))) - go !psq !badmap !remainingGas !soFar = do - nb <- nextBatch psq remainingGas + go !psq !badmap !(GasLimit remainingGas) !soFar = do + nb <- nextBatch psq (GasLimit remainingGas) if null nb then do logFunctionText logg Debug "getBlockInMem: Batch empty" @@ -720,8 +717,11 @@ getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight p else do logFunctionText logg Debug "validating batch..." T3 good psq' badmap' <- validateBatch psq badmap $! V.fromList nb - let !newGas = foldl' (\s (_, (_, t, _)) -> s + getSize t) 0 good - go psq' badmap' (remainingGas - newGas) (good : soFar) + let !newGas = foldl' + (\s (_, (_, t, _)) -> s - view (_GasLimit . to _gas) (getSize t)) + (_gas remainingGas) + good + go psq' badmap' (GasLimit $ Gas newGas) (good : soFar) ------------------------------------------------------------------------------ diff --git a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs index 179634c82f..2436dd7d4c 100644 --- a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs +++ b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs @@ -17,8 +17,8 @@ module Chainweb.Mempool.InMem.ValidatingConfig import Chainweb.ChainId import Chainweb.Mempool.InMemTypes import Chainweb.Mempool.Mempool -import Chainweb.Pact4.Transaction qualified as Pact4 -import Chainweb.Pact4.Validations +import Chainweb.Pact.Transaction qualified as Pact4 +import Chainweb.Pact.Validations import Chainweb.Utils import Chainweb.Version import Chainweb.WebPactExecutionService @@ -95,5 +95,3 @@ validatingMempoolConfig cid v gl gp mv = InMemConfig Nothing -> Right (T2 h t) f (That (T2 h _)) = Left (T2 h $ InsertErrorOther "preInsertBatch: align mismatch 0") f (This _) = Left (T2 (TransactionHash "") (InsertErrorOther "preInsertBatch: align mismatch 1")) - - diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Mempool/Mempool.hs index 7ea32837eb..1d7826043f 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Mempool/Mempool.hs @@ -12,6 +12,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE ImportQualifiedPost #-} ------------------------------------------------------------------------------ -- | Mempool @@ -74,7 +75,7 @@ module Chainweb.Mempool.Mempool , bfTxHashes , bfCount - , pact4TransactionConfig + , pactTransactionConfig , mockCodec , mockEncode , mockBlockGasLimit @@ -86,8 +87,7 @@ module Chainweb.Mempool.Mempool , syncMempools' , GasLimit(..) , GasPrice(..) - , pact4RequestKeyToTransactionHash - , pact5RequestKeyToTransactionHash + , pactRequestKeyToTransactionHash ) where ------------------------------------------------------------------------------ @@ -121,7 +121,7 @@ import Data.Vector (Vector) import qualified Data.Vector as V import Data.Word (Word64) -import GHC.Generics +import GHC.Generics hiding (to) import Prelude hiding (log) @@ -130,23 +130,21 @@ import System.LogLevel -- internal modules import qualified Pact.JSON.Encode as J -import Pact.Parse (ParsedDecimal(..), ParsedInteger(..)) -import Pact.Types.ChainMeta (TTLSeconds(..), TxCreationTime(..)) -import Pact.Types.Command -import Pact.Types.Gas (GasLimit(..), GasPrice(..)) -import qualified Pact.Types.Hash as Pact4 import Chainweb.BlockHash import Chainweb.BlockHeight import Chainweb.Time (Micros(..), Time(..), TimeSpan(..)) -import qualified Chainweb.Time as Time -import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Time qualified as Time +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Utils import Chainweb.Utils.Serialization import Data.LogMessage (LogFunctionText) -import qualified Pact.Types.Command as Pact4 -import qualified Pact.Core.Command.Types as Pact5 -import qualified Pact.Core.Hash as Pact5 +import Pact.Core.Command.Types qualified as Pact +import Pact.Core.Hash qualified as Pact +import Pact.Core.Gas +import Pact.Core.ChainData +import qualified Pact.Core.ChainData as Pact +import Chainweb.BlockHeader ------------------------------------------------------------------------------ data LookupResult t = Missing @@ -187,7 +185,7 @@ instance Traversable LookupResult where Pending x -> Pending <$> f x ------------------------------------------------------------------------------ -type MempoolPreBlockCheck ti to = BlockHeight -> BlockHash -> Vector ti -> IO (Vector (Either InsertError to)) +type MempoolPreBlockCheck ti to = BlockHeight -> Parent BlockHash -> Vector ti -> IO (Vector (Either InsertError to)) ------------------------------------------------------------------------------ -- | Mempool operates over a transaction type @t@. Mempool needs several @@ -315,7 +313,7 @@ data MempoolBackend t = MempoolBackend { -- for mining. -- , mempoolGetBlock - :: forall to. BlockFill -> MempoolPreBlockCheck t to -> BlockHeight -> BlockHash -> IO (Vector to) + :: forall to. BlockFill -> MempoolPreBlockCheck t to -> BlockHeight -> Parent BlockHash -> IO (Vector to) -- | Discard any expired transactions. , mempoolPrune :: IO () @@ -359,8 +357,8 @@ noopMempool = do noopCodec = Codec (const "") (const $ Left "unimplemented") noopHasher = const $ chainwebTestHasher "noopMempool" noopHashMeta = chainwebTestHashMeta - noopGasPrice = const 0 - noopSize = const 1 + noopGasPrice = const (GasPrice 0) + noopSize = const (GasLimit $ Gas 1) noopMeta = const $ TransactionMetadata Time.minTime Time.maxTime txcfg = TransactionConfig noopCodec noopHasher noopHashMeta noopGasPrice noopSize noopMeta @@ -380,24 +378,22 @@ noopMempool = do ------------------------------------------------------------------------------ -pact4TransactionConfig - :: TransactionConfig Pact4.UnparsedTransaction -pact4TransactionConfig = TransactionConfig - { txCodec = Pact4.rawCommandCodec +pactTransactionConfig + :: TransactionConfig Pact.Transaction +pactTransactionConfig = TransactionConfig + { txCodec = Pact.payloadCodec , txHasher = commandHash , txHashMeta = chainwebTestHashMeta - , txGasPrice = getGasPrice - , txGasLimit = getGasLimit + , txGasPrice = view (Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmGasPrice) + , txGasLimit = view (Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmGasLimit) , txMetadata = txmeta } where - getGasPrice = view Pact4.cmdGasPrice . fmap Pact4.payloadObj - getGasLimit = view Pact4.cmdGasLimit . fmap Pact4.payloadObj - getTimeToLive = view Pact4.cmdTimeToLive . fmap Pact4.payloadObj - getCreationTime = view Pact4.cmdCreationTime . fmap Pact4.payloadObj - commandHash c = let (Pact4.Hash !h) = Pact4.toUntypedHash $ _cmdHash c + getTimeToLive = view (Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmTTL) + getCreationTime = view (Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmCreationTime) + commandHash c = let Pact.Hash h = Pact._cmdHash c in TransactionHash h txmeta t = TransactionMetadata @@ -619,11 +615,8 @@ instance HasTextRepresentation TransactionHash where {-# INLINE toText #-} {-# INLINE fromText #-} -pact4RequestKeyToTransactionHash :: Pact4.RequestKey -> TransactionHash -pact4RequestKeyToTransactionHash = TransactionHash . Pact4.unHash . Pact4.unRequestKey - -pact5RequestKeyToTransactionHash :: Pact5.RequestKey -> TransactionHash -pact5RequestKeyToTransactionHash = TransactionHash . Pact5.unHash . Pact5.unRequestKey +pactRequestKeyToTransactionHash :: Pact.RequestKey -> TransactionHash +pactRequestKeyToTransactionHash = TransactionHash . Pact.unHash . Pact.unRequestKey ------------------------------------------------------------------------------ -- @@ -718,8 +711,8 @@ data MockTx = MockTx { instance J.Encode MockTx where build o = J.object [ "mockNonce" J..= J.Aeson (mockNonce o) - , "mockGasPrice" J..= mockGasPrice o - , "mockGasLimit" J..= mockGasLimit o + , "mockGasPrice" J..= J.number (realToFrac @Decimal @_ (view _GasPrice (mockGasPrice o))) + , "mockGasLimit" J..= J.number (fromIntegral $ view (_GasLimit . to _gas) (mockGasLimit o)) , "mockMeta" J..= mockMeta o ] {-# INLINE build #-} @@ -732,13 +725,13 @@ instance ToJSON MockTx where instance FromJSON MockTx where parseJSON = withObject "MockTx" $ \o -> MockTx <$> o .: "mockNonce" - <*> o .: "mockGasPrice" - <*> o .: "mockGasLimit" + <*> fmap (GasPrice . realToFrac @Double @Decimal) (o .: "mockGasPrice") + <*> fmap (GasLimit . Gas . fromIntegral @Int @SatWord) (o .: "mockGasLimit") <*> o .: "mockMeta" {-# INLINE parseJSON #-} mockBlockGasLimit :: GasLimit -mockBlockGasLimit = 100_000_000 +mockBlockGasLimit = GasLimit $ Gas 100_000_000 -- | A codec for transactions when sending them over the wire. mockCodec :: Codec MockTx @@ -746,7 +739,7 @@ mockCodec = Codec mockEncode mockDecode mockEncode :: MockTx -> ByteString -mockEncode (MockTx nonce (GasPrice (ParsedDecimal price)) limit meta) = +mockEncode (MockTx nonce (GasPrice price) (GasLimit (Gas limit)) meta) = B64.encode $ runPutS $ do putWord64le $ fromIntegral nonce @@ -793,8 +786,8 @@ mockDecode s = do s' <- B64.decode s runGetEitherS (MockTx <$> getI64 <*> getPrice <*> getGL <*> getMeta) s' where - getPrice = GasPrice . ParsedDecimal <$> getDecimal - getGL = GasLimit . ParsedInteger . fromIntegral <$> getWord64le + getPrice = GasPrice <$> getDecimal + getGL = GasLimit . Gas . fromIntegral <$> getWord64le getI64 = fromIntegral <$> getWord64le getMeta = TransactionMetadata <$> Time.decodeTime <*> Time.decodeTime diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 467897a6ea..92ba9b7b53 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -104,7 +104,6 @@ import Streaming.Prelude qualified as S import System.LogLevel (LogLevel(..)) import System.Random (randomRIO) -import Chainweb.Ranked import Control.Concurrent.Async import qualified Data.Vector as V import Data.Hashable @@ -162,7 +161,7 @@ awaitPayloadsNext awaitPayloadsNext caches c prevs = msum (awaitChain <$> HM.toList xs) where xs = HM.intersectionWith (,) caches rhs - rhs = _rankedBlockHash <$> _cutHeaders c + rhs = Parent . _rankedBlockHash <$> _cutHeaders c awaitChain (ChainId cid, (ca, rh)) = do x <- awaitLatestSTM ca rh @@ -237,52 +236,52 @@ awaitPayloadsNext caches c prevs = msum (awaitChain <$> HM.toList xs) -- @ -- data WorkState - = WorkNotReady !RankedBlockHash + = WorkNotReady !(Parent RankedBlockHash) -- ^ Chain is blocked and no payload has yet been produced - | WorkStale !RankedBlockHash !WorkParents + | WorkStale !(Parent RankedBlockHash) !WorkParents -- ^ The chain is unblocked but no payload has produced yet. -- -- Invariant: The work parents must match the ranked block hash. - | WorkBlocked !RankedBlockHash !NewPayload + | WorkBlocked !(Parent RankedBlockHash) !NewPayload -- ^ A payload is ready but the chain is still blocked -- -- Invariant: The payload must match the ranked block hash. - | WorkReady !RankedBlockHash !NewPayload !WorkParents !WorkHeader + | WorkReady !(Parent RankedBlockHash) !NewPayload !WorkParents !WorkHeader -- ^ The chain is ready for mining -- -- Invariant: The payload, parents, work header must match the ranked block -- hash. - | WorkSolved !RankedBlockHash !NewPayload !WorkParents + | WorkSolved !(Parent RankedBlockHash) !NewPayload !WorkParents -- ^ A block with this parent has already been solved and submitted to the -- cut pipeline - we don't want to mine it again. So we wait until the -- current cut is updated with a new parent header for this chain. deriving stock (Show, Eq, Generic) -_workRankedHash :: WorkState -> RankedBlockHash +_workRankedHash :: WorkState -> Parent RankedBlockHash _workRankedHash (WorkNotReady rh) = rh _workRankedHash (WorkStale rh _) = rh _workRankedHash (WorkBlocked rh _) = rh _workRankedHash (WorkReady rh _ _ _) = rh _workRankedHash (WorkSolved rh _ _) = rh -_workStateHash :: WorkState -> BlockHash -_workStateHash = _rankedBlockHashHash . _workRankedHash +_workStateHash :: WorkState -> Parent BlockHash +_workStateHash = fmap _rankedBlockHashHash . _workRankedHash -_workStateHeight :: WorkState -> BlockHeight -_workStateHeight = _rankedBlockHashHeight . _workRankedHash +_workStateHeight :: WorkState -> Parent BlockHeight +_workStateHeight = fmap _rankedBlockHashHeight . _workRankedHash workReady :: BlockCreationTime - -> RankedBlockHash + -> Parent RankedBlockHash -> NewPayload -> WorkParents -> WorkState -workReady t rh pld ps' = WorkReady rh pld ps' +workReady t rbh pld ps' = WorkReady rbh pld ps' $ newWork t ps' $ _newPayloadBlockPayloadHash pld @@ -303,7 +302,7 @@ instance Brief WorkState where -- | Called on headers event -- onHeader - :: RankedBlockHash + :: Parent RankedBlockHash -> WorkState -> Maybe WorkState onHeader rh cur @@ -325,7 +324,7 @@ onParents onParents t (Just ps) cur | psRankedHash /= _workRankedHash cur = onHeader psRankedHash cur >>= onParents t (Just ps) where - psRankedHash = _rankedParentHash $ _workParent ps + psRankedHash = _rankedBlockHash <$> _workParent ps onParents _ (Just ps') (WorkNotReady rh) = Just $ WorkStale rh ps' onParents t (Just ps') (WorkBlocked rh pld) = Just $ workReady t rh pld ps' onParents _ (Just ps') (WorkStale rh ps) @@ -371,16 +370,17 @@ onSolved :: SolvedWork -> WorkState -> Maybe WorkState -onSolved (SolvedWork hdr) (WorkSolved rh _ ps) +onSolved (SolvedWork hdr) (WorkSolved (Parent rh) _ ps) -- If we solved this header in this cut before we do not change the work -- state, even if the payload differs. - | _rankedBlockHash hdr == rh + -- TODO: this might be wrong. `hdr` is the newly made work header, but `rh` looks to be the parent header of that. + | view blockParent hdr == _rankedBlockHashHash rh && view blockAdjacentHashes hdr /= _workParentsAdjacentHashes ps = Nothing -onSolved (SolvedWork hdr) (WorkReady rh pld ps _) +onSolved (SolvedWork hdr) (WorkReady (Parent rh) pld ps _) -- If work is currently ready for this header in this cut, we mark it solved. - | _rankedBlockHash hdr == rh + | view blockParent hdr == _rankedBlockHashHash rh && view blockAdjacentHashes hdr /= _workParentsAdjacentHashes ps = - Just $ WorkSolved rh pld ps + Just $ WorkSolved (Parent rh) pld ps -- otherwise do not change the state. onSolved _ _ = Nothing @@ -417,6 +417,7 @@ newMiningState c = do states <- forM cids $ \cid -> do var <- newTVarIO $ WorkNotReady + $ Parent $ _rankedBlockHash $ fromMaybe (genesisBlockHeader v cid) (HM.lookup cid (_cutMap c)) return (cid, var) @@ -903,7 +904,7 @@ solve mr solved@(SolvedWork hdr) = cdb = _coordCutDb mr caches = _coordPayloadCache mr cache = caches HM.! cid - cacheKey = RankedBlockHash (view blockHeight hdr - 1) (view blockParent hdr) + cacheKey = Parent $ RankedBlockHash (view blockHeight hdr - 1) (view blockParent hdr) lf :: LogFunction lf = logFunction $ _coordLogger mr @@ -933,4 +934,3 @@ logMinedBlock lf bh np = do publish :: CutDb -> CutHashes -> IO () publish cdb ch = addCutHashes cdb ch - diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index c631ad2bd2..330268a710 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -65,7 +65,7 @@ import Chainweb.Miner.Config (MinerCount(..)) import Chainweb.Miner.Coordinator import Chainweb.Miner.Core import Chainweb.RestAPI.Orphans () -import qualified Chainweb.Pact4.Transaction as Pact4 +import qualified Chainweb.Pact.Transaction as Pact4 import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version @@ -169,4 +169,3 @@ localPOW lf coord cdb = runForever lf "Chainweb.Miner.Miners.localPOW" $ do c <- _cutStm cdb let h' = view blockHeight $ c ^?! ixg cid guard (h <= h') - diff --git a/src/Chainweb/Miner/PayloadCache.hs b/src/Chainweb/Miner/PayloadCache.hs index 9ca0e4069f..9a8ba29fa6 100644 --- a/src/Chainweb/Miner/PayloadCache.hs +++ b/src/Chainweb/Miner/PayloadCache.hs @@ -69,6 +69,7 @@ import Data.List qualified as L import Data.Map.Strict qualified as M import Numeric.Natural import Chainweb.BlockHeight +import Chainweb.BlockHeader -- | A new payload cache for a chain. -- @@ -88,7 +89,7 @@ import Chainweb.BlockHeight -- data PayloadCache = PayloadCache { _payloadCacheDepth :: !Natural - , _payloadCacheMap :: !(TVar (M.Map (RankedBlockHash, Int) NewPayload)) + , _payloadCacheMap :: !(TVar (M.Map (Parent RankedBlockHash, Int) NewPayload)) } -- NOTE that we provide separate @STM@ and @IO@ versions for functions. The @@ -125,7 +126,7 @@ payloadHashesIO pc = fmap _newPayloadBlockPayloadHash . M.elems -- getLatestSTM :: PayloadCache - -> RankedBlockHash + -> Parent RankedBlockHash -> STM (Maybe NewPayload) getLatestSTM pc rh = do lookupValueGE (rh, minBound) <$> readTVar (_payloadCacheMap pc) >>= \case @@ -137,7 +138,7 @@ getLatestSTM pc rh = do -- getLatestIO :: PayloadCache - -> RankedBlockHash + -> Parent RankedBlockHash -> IO (Maybe NewPayload) getLatestIO pc rh = lookupValueGE (rh, minBound) <$> readTVarIO (_payloadCacheMap pc) >>= \case @@ -149,7 +150,7 @@ getLatestIO pc rh = -- awaitLatestSTM :: PayloadCache - -> RankedBlockHash + -> Parent RankedBlockHash -> STM NewPayload awaitLatestSTM pc rh = getLatestSTM pc rh >>= maybe retry return @@ -157,7 +158,7 @@ awaitLatestSTM pc rh = getLatestSTM pc rh >>= maybe retry return -- awaitLatestIO :: PayloadCache - -> RankedBlockHash + -> Parent RankedBlockHash -> IO NewPayload awaitLatestIO pc = atomically . awaitLatestSTM pc @@ -165,7 +166,7 @@ awaitLatestIO pc = atomically . awaitLatestSTM pc -- lookupSTM :: PayloadCache - -> RankedBlockHash + -> Parent RankedBlockHash -> BlockPayloadHash -> STM (Maybe NewPayload) lookupSTM pc rh pld = do @@ -189,7 +190,7 @@ lookupSTM pc rh pld = do -- lookupIO :: PayloadCache - -> RankedBlockHash + -> Parent RankedBlockHash -> BlockPayloadHash -> IO (Maybe NewPayload) lookupIO pc rh pld = do @@ -221,7 +222,7 @@ insertSTM pc pld = where h = _newPayloadParentHeight pld p = _newPayloadParentHash pld - key = (RankedBlockHash h p, _newPayloadNumber pld) + key = (Parent (RankedBlockHash h p), _newPayloadNumber pld) -- | Insert a new payload into the cache. The cache is pruned before the new -- item is inserted. @@ -260,11 +261,10 @@ lookupValueGE k m = snd <$> M.lookupGE k m prune :: Natural -> BlockHeight - -> M.Map (RankedBlockHash, Int) v - -> M.Map (RankedBlockHash, Int) v + -> M.Map (Parent RankedBlockHash, Int) v + -> M.Map (Parent RankedBlockHash, Int) v prune d h m | h < fromIntegral d = m | otherwise = snd $ M.split pivot m where - pivot = (RankedBlockHash (h - fromIntegral d) nullBlockHash, minBound) - + pivot = (Parent $ RankedBlockHash (h - fromIntegral d) nullBlockHash, minBound) diff --git a/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs similarity index 95% rename from src/Chainweb/Pact5/Backend/ChainwebPactDb.hs rename to src/Chainweb/Pact/Backend/ChainwebPactDb.hs index e56d5ce220..a880fa3af5 100644 --- a/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -49,19 +49,19 @@ -- -- Transactions must be nested in this way. -- --- SQLite transaction ensures that the Pact5Db transaction +-- SQLite transaction ensures that the ChainwebPactDb transaction -- sees a consistent view of the database, especially if its -- writes are committed later. -- --- Pact5Db tx ensures that the Pact tx's writes +-- ChainwebPactDb tx ensures that the Pact tx's writes -- are recorded. -- -- Pact tx ensures that failed transactions' writes are not recorded. -module Chainweb.Pact5.Backend.ChainwebPactDb +module Chainweb.Pact.Backend.ChainwebPactDb ( chainwebPactBlockDb - , Pact5Db(..) + , ChainwebPactDb(..) , BlockHandlerEnv(..) , blockHandlerDb , blockHandlerLogger @@ -100,7 +100,6 @@ import Data.ByteString.Short qualified as SB import Data.DList (DList) import Data.DList qualified as DL import Data.Foldable -import Data.HashMap.Strict (HashMap) import Data.HashMap.Strict qualified as HM import Data.HashMap.Strict qualified as HashMap import Data.HashSet qualified as HashSet @@ -112,7 +111,6 @@ import Data.Singletons (Dict(..)) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T -import Data.Vector (Vector) import Database.SQLite3.Direct qualified as SQ3 import GHC.Stack import Pact.Core.Builtin qualified as Pact @@ -218,8 +216,8 @@ data BlockHandlerEnv logger = BlockHandlerEnv -- | The state used by database operations. -- Includes both the state re: the whole block, and the state re: a transaction in progress. data BlockState = BlockState - { _bsBlockHandle :: !(BlockHandle Pact5) - , _bsPendingTxWrites :: !(SQLitePendingData InMemDb.Store) + { _bsBlockHandle :: !BlockHandle + , _bsPendingTxWrites :: !SQLitePendingData , _bsPendingTxLog :: !(Maybe (DList (Pact.TxLog ByteString))) } @@ -232,27 +230,6 @@ getPendingTxLogOrError msg = do Nothing -> liftGas $ Pact.throwDbOpErrorGasM (Pact.NotInTx msg) Just t -> return t --- | The Pact 5 database as it's provided by the checkpointer. -data Pact5Db = Pact5Db - { doPact5DbTransaction - :: forall a - . BlockHandle Pact5 - -> Maybe RequestKey - -> (Pact.PactDb Pact.CoreBuiltin Pact.Info -> IO a) - -> IO (a, BlockHandle Pact5) - -- ^ Give this function a BlockHandle representing the state of a pending - -- block and it will pass you a PactDb which contains the Pact state as of - -- that point in the block. After you're done, it passes you back a - -- BlockHandle representing the state of the block extended with any writes - -- you made to the PactDb. - -- Note also that this function handles registering - -- transactions as completed, if you pass it a RequestKey. - , lookupPactTransactions :: Vector RequestKey -> IO (HashMap RequestKey (T2 BlockHeight BlockHash)) - -- ^ Used to implement transaction polling. - } - -type instance PactDbFor logger Pact5 = Pact5Db - -- this monad allows access to the database environment "in" a particular -- transaction, and allows charging gas for database operations. newtype BlockHandler logger a = BlockHandler @@ -309,9 +286,9 @@ runOnBlockGassed env stateVar act = do return (newState, fmap fst r) liftEither r -chainwebPactBlockDb :: (Logger logger) => BlockHandlerEnv logger -> Pact5Db -chainwebPactBlockDb env = Pact5Db - { doPact5DbTransaction = \blockHandle maybeRequestKey kont -> do +chainwebPactBlockDb :: (Logger logger) => BlockHandlerEnv logger -> ChainwebPactDb +chainwebPactBlockDb env = ChainwebPactDb + { doChainwebPactDbTransaction = \blockHandle maybeRequestKey kont -> do stateVar <- newMVar $ BlockState blockHandle (_blockHandlePending blockHandle) Nothing let pactDb = Pact.PactDb { Pact._pdbPurity = Pact.PImpure @@ -699,7 +676,7 @@ toTxLog version cid bh d key value = do toPactTxLog :: Pact.TxLog Pact.RowData -> Pact4.TxLog Pact.RowData toPactTxLog (Pact.TxLog d k v) = Pact4.TxLog d k v -commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockHeight -> BlockHandle Pact5 -> IO () +commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockHeight -> BlockHandle -> IO () commitBlockStateToDatabase db hsh bh blockHandle = throwOnDbError $ do let newTables = _pendingTableCreation $ _blockHandlePending blockHandle mapM_ (\tn -> createUserTable (toUtf8 tn)) newTables diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index 5e9bab3e6e..fc80e528e7 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -72,7 +72,7 @@ import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.Cut.CutHashes (cutIdToText) import Chainweb.CutDB (cutHashesTable) import Chainweb.Logger (Logger, l2l, setComponent, logFunctionText) -import Chainweb.Pact4.Backend.ChainwebPactDb () +import Chainweb.Pact.Backend.ChainwebPactDb () import Chainweb.Pact.Backend.PactState import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs index dd6d81e23e..aa8fd1baee 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs @@ -186,7 +186,7 @@ pactDropPostVerified logger v srcDir tgtDir snapshotBlockHeight snapshotChainHas let logger' = addChainIdLabel cid logger logFunctionText logger' Info $ "Dropping anything post verified state (BlockHeight " <> sshow snapshotBlockHeight <> ")" - Checkpointer.Internal.withCheckpointerResources logger defaultModuleCacheLimit sqliteEnv DoNotPersistIntraBlockWrites v cid $ \cp -> do + Checkpointer.Internal.withCheckpointerResources logger sqliteEnv DoNotPersistIntraBlockWrites v cid $ \cp -> do Checkpointer.Internal.rewindTo cp (Just $ ParentHeader $ blockHeader $ snapshotChainHashes ^?! ix cid) data PactImportConfig = PactImportConfig diff --git a/src/Chainweb/Pact/Backend/Types.hs b/src/Chainweb/Pact/Backend/Types.hs index 82f31354d3..5bec23e14d 100644 --- a/src/Chainweb/Pact/Backend/Types.hs +++ b/src/Chainweb/Pact/Backend/Types.hs @@ -27,9 +27,8 @@ module Chainweb.Pact.Backend.Types , BlockHandle(..) , blockHandleTxId , blockHandlePending - , emptyPact4BlockHandle - , emptyPact5BlockHandle , SQLitePendingData(..) + , emptyBlockHandle , emptySQLitePendingData , pendingWrites , pendingSuccessfulTxs @@ -39,22 +38,18 @@ module Chainweb.Pact.Backend.Types , Historical(..) , _Historical , _NoHistory - , PactDbFor - , PendingWrites + , ChainwebPactDb(..) + , RunnableBlock(..) ) where import Control.Lens -import Chainweb.Pact.Backend.DbCache import Chainweb.Version import Database.SQLite3.Direct (Database) -import Control.Concurrent.MVar import Data.ByteString (ByteString) import Data.Text (Text) import Data.DList (DList) import Data.Map (Map) import Data.HashSet (HashSet) -import Data.HashMap.Strict (HashMap) -import Data.List.NonEmpty (NonEmpty) import Control.DeepSeq (NFData) import GHC.Generics @@ -62,12 +57,47 @@ import qualified Chainweb.Pact.Backend.InMemDb as InMemDb import qualified Pact.Types.Persistence as Pact4 import qualified Pact.Types.Names as Pact4 +import Chainweb.BlockHeader +import Chainweb.BlockHash +import Pact.Core.Command.Types +import qualified Pact.Core.Persistence as Pact +import qualified Pact.Core.Builtin as Pact +import qualified Pact.Core.Evaluate as Pact +import Data.Vector (Vector) +import Data.HashMap.Strict (HashMap) +import Chainweb.BlockHeight +import Chainweb.Utils -- | Whether we write rows to the database that were already overwritten -- in the same block. data IntraBlockPersistence = PersistIntraBlockWrites | DoNotPersistIntraBlockWrites deriving (Eq, Ord, Show) +-- | The Pact database as it's provided by the checkpointer. +data ChainwebPactDb = ChainwebPactDb + { doChainwebPactDbTransaction + :: forall a + . BlockHandle + -> Maybe RequestKey + -> (Pact.PactDb Pact.CoreBuiltin Pact.Info -> IO a) + -> IO (a, BlockHandle) + -- ^ Give this function a BlockHandle representing the state of a pending + -- block and it will pass you a PactDb which contains the Pact state as of + -- that point in the block. After you're done, it passes you back a + -- BlockHandle representing the state of the block extended with any writes + -- you made to the PactDb. + -- Note also that this function handles registering + -- transactions as completed, if you pass it a RequestKey. + , lookupPactTransactions :: Vector RequestKey -> IO (HashMap RequestKey (T2 BlockHeight BlockHash)) + -- ^ Used to implement transaction polling. + } + +newtype RunnableBlock logger a = RunnableBlock + ( ChainwebPactDb + -> Maybe (Parent RankedBlockHash) + -> BlockHandle -> IO ((a, RankedBlockHash), BlockHandle) + ) + data Checkpointer logger = Checkpointer { cpLogger :: logger @@ -75,7 +105,6 @@ data Checkpointer logger , cpChainId :: ChainId , cpSql :: SQLiteEnv , cpIntraBlockPersistence :: IntraBlockPersistence - , cpModuleCacheVar :: MVar (DbCache Pact4.PersistModuleData) } type SQLiteEnv = Database @@ -111,10 +140,6 @@ type SQLitePendingTableCreations = HashSet Text -- | Pact transaction hashes resolved during this block. type SQLitePendingSuccessfulTxs = HashSet ByteString --- | Pending writes to the pact db during a block, to be recorded in 'BlockState'. --- Structured as a map from table name to a map from rowkey to inserted row delta. -type SQLitePendingWrites = HashMap Text (HashMap ByteString (NonEmpty SQLiteRowDelta)) - -- Note [TxLogs in SQLitePendingData] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- We should really not store TxLogs in SQLitePendingData, @@ -127,39 +152,26 @@ type SQLitePendingWrites = HashMap Text (HashMap ByteString (NonEmpty SQLiteRowD -- these; one for the block as a whole, and one for any pending pact -- transaction. Upon pact transaction commit, the two 'SQLitePendingData' -- values are merged together. -data SQLitePendingData w = SQLitePendingData +data SQLitePendingData = SQLitePendingData { _pendingTableCreation :: !SQLitePendingTableCreations - , _pendingWrites :: !w + , _pendingWrites :: !InMemDb.Store -- See Note [TxLogs in SQLitePendingData] , _pendingTxLogMap :: !TxLogMap , _pendingSuccessfulTxs :: !SQLitePendingSuccessfulTxs } deriving (Eq, Show) -makeLenses ''SQLitePendingData - -type family PendingWrites (pv :: PactVersion) = w | w -> pv where - PendingWrites Pact4 = SQLitePendingWrites - PendingWrites Pact5 = InMemDb.Store +emptySQLitePendingData :: SQLitePendingData +emptySQLitePendingData = SQLitePendingData mempty InMemDb.empty mempty mempty -emptySQLitePendingData :: w -> SQLitePendingData w -emptySQLitePendingData w = SQLitePendingData mempty w mempty mempty - -data BlockHandle (pv :: PactVersion) = BlockHandle +data BlockHandle = BlockHandle { _blockHandleTxId :: !Pact4.TxId - , _blockHandlePending :: !(SQLitePendingData (PendingWrites pv)) + , _blockHandlePending :: !SQLitePendingData } -deriving instance Eq (BlockHandle Pact4) -deriving instance Eq (BlockHandle Pact5) -deriving instance Show (BlockHandle Pact4) -deriving instance Show (BlockHandle Pact5) -makeLenses ''BlockHandle - -emptyPact4BlockHandle :: Pact4.TxId -> BlockHandle Pact4 -emptyPact4BlockHandle txid = BlockHandle txid (emptySQLitePendingData mempty) + deriving (Eq, Show) -emptyPact5BlockHandle :: Pact4.TxId -> BlockHandle Pact5 -emptyPact5BlockHandle txid = BlockHandle txid (emptySQLitePendingData InMemDb.empty) +emptyBlockHandle :: Pact4.TxId -> BlockHandle +emptyBlockHandle txid = BlockHandle txid emptySQLitePendingData -- | The result of a historical lookup which might fail to even find the -- header the history is being queried for. @@ -170,5 +182,5 @@ data Historical a deriving anyclass NFData makePrisms ''Historical - -type family PactDbFor logger (pv :: PactVersion) +makeLenses ''BlockHandle +makeLenses ''SQLitePendingData diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index d65509b171..2fdd624149 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -48,8 +48,6 @@ module Chainweb.Pact.Backend.Utils , fromUtf8 , asStringUtf8 , convSavepointName - , expectSingleRowCol - , expectSingle -- * SQLite runners , withSqliteDb , startSqliteDb @@ -64,9 +62,9 @@ module Chainweb.Pact.Backend.Utils , chainwebPragmas ) where -import Control.Exception (SomeAsyncException, evaluate) +import Control.Exception (evaluate) +import Control.Exception.Safe import Control.Monad -import Control.Monad.Catch import Control.Monad.State.Strict import Data.Bits @@ -94,7 +92,6 @@ import Pact.Types.Util (AsString(..)) import Chainweb.Logger import Chainweb.Pact.Backend.SQLite.DirectV2 -import Chainweb.Pact.Types import Chainweb.Version import Chainweb.Utils import Chainweb.BlockHash @@ -145,9 +142,8 @@ withSavepoint db name action = mask $ \resetMask -> do r <- resetMask action `onException` abortSavepoint db name liftIO $ commitSavepoint db name liftIO $ evaluate r - throwErr s = internalError $ "withSavepoint (" <> toText name <> "): " <> s - handlers = [ Handler $ \(e :: PactException) -> throwErr (sshow e) - , Handler $ \(e :: SomeAsyncException) -> throwM e + throwErr s = error $ "withSavepoint (" <> show name <> "): " <> s + handlers = [ Handler $ \(e :: SomeAsyncException) -> throwM e , Handler $ \(e :: SomeException) -> throwErr ("non-pact exception: " <> sshow e) ] @@ -203,22 +199,6 @@ instance HasTextRepresentation SavepointName where <> ". Valid names are " <> T.intercalate ", " (toText @SavepointName <$> [minBound .. maxBound]) {-# INLINE fromText #-} -expectSingleRowCol :: Show a => String -> [[a]] -> IO a -expectSingleRowCol _ [[s]] = return s -expectSingleRowCol s v = - internalError $ - "expectSingleRowCol: " - <> asString s <> - " expected single row and column result, got: " - <> asString (show v) - -expectSingle :: Show a => String -> [a] -> IO a -expectSingle _ [s] = return s -expectSingle desc v = - internalError $ - "Expected single-" <> asString (show desc) <> " result, got: " <> - asString (show v) - chainwebPragmas :: [Pact4.Pragma] chainwebPragmas = [ "synchronous = NORMAL" @@ -294,9 +274,9 @@ withSQLiteConnection file ps = openSQLiteConnection :: String -> [Pact4.Pragma] -> IO SQLiteEnv openSQLiteConnection file ps = open2 file >>= \case Left (err, msg) -> - internalError $ + error $ "withSQLiteConnection: Can't open db with " - <> asString (show err) <> ": " <> asString (show msg) + <> show err <> ": " <> show msg Right r -> do Pact4.runPragmas r ps return r @@ -339,7 +319,7 @@ sqlite_open_fullmutex = 0x00010000 tbl :: HasCallStack => Utf8 -> Utf8 tbl t@(Utf8 b) - | B8.elem ']' b = error $ "Chainweb.Pact4.Backend.ChainwebPactDb: Code invariant violation. Illegal SQL table name " <> sshow b <> ". Please report this as a bug." + | B8.elem ']' b = error $ "Chainweb.Pact.Backend.ChainwebPactDb: Code invariant violation. Illegal SQL table name " <> sshow b <> ". Please report this as a bug." | otherwise = "[" <> t <> "]" doLookupSuccessful :: Database -> BlockHeight -> V.Vector SB.ShortByteString -> IO (HashMap.HashMap SB.ShortByteString (T2 BlockHeight BlockHash)) @@ -381,20 +361,20 @@ doLookupSuccessful db curHeight hashes = do getEndTxId :: Text -> SQLiteEnv -> Maybe (Parent RankedBlockHash) -> IO (Historical Pact4.TxId) getEndTxId msg sql pc = case pc of Nothing -> return (Historical 0) - Just ph -> getEndTxId' msg sql (_rankedBlockHashHeight <$> ph) (_rankedBlockHashHash <$> ph) + Just ph -> getEndTxId' msg sql ph -getEndTxId' :: Text -> SQLiteEnv -> Parent BlockHeight -> Parent BlockHash -> IO (Historical Pact4.TxId) -getEndTxId' msg sql (Parent bh) (Parent bhsh) = do +getEndTxId' :: Text -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact4.TxId) +getEndTxId' msg sql (Parent rbh) = do r <- Pact4.qry sql "SELECT endingtxid FROM BlockHistory WHERE blockheight = ? and hash = ?;" - [ Pact4.SInt $ fromIntegral bh - , Pact4.SBlob $ runPutS (encodeBlockHash bhsh) + [ Pact4.SInt $ fromIntegral $ _rankedBlockHashHeight rbh + , Pact4.SBlob $ runPutS (encodeBlockHash $ _rankedBlockHashHash rbh) ] [Pact4.RInt] case r of [[Pact4.SInt tid]] -> return $ Historical (Pact4.TxId (fromIntegral tid)) [] -> return NoHistory - _ -> internalError $ msg <> ".getEndTxId: expected single-row int result, got " <> sshow r + _ -> error $ T.unpack msg <> ".getEndTxId: expected single-row int result, got " <> sshow r -- | Delete any state from the database newer than the input parent header. @@ -410,8 +390,7 @@ rewindDbTo db mh@(Just ph) = do !historicalEndingTxId <- getEndTxId "rewindDbToBlock" db mh endingTxId <- case historicalEndingTxId of NoHistory -> - throwM - $ BlockHeaderLookupFailure + error $ "rewindDbTo.getEndTxId: not in db: " <> sshow ph Historical endingTxId -> @@ -433,7 +412,7 @@ rewindDbToGenesis db = do tblNames <- Pact4.qry_ db "SELECT tablename FROM VersionedTableCreation;" [Pact4.RText] forM_ tblNames $ \t -> case t of [Pact4.SText tn] -> Pact4.exec_ db ("DROP TABLE [" <> tn <> "];") - _ -> internalError "Something went wrong when resetting tables." + _ -> error "Something went wrong when resetting tables." Pact4.exec_ db "DELETE FROM VersionedTableCreation;" Pact4.exec_ db "DELETE FROM VersionedTableMutation;" Pact4.exec_ db "DELETE FROM TransactionIndex;" @@ -460,7 +439,7 @@ rewindDbToBlock db (Parent bh) endingTxId = do [Pact4.SText tblname@(Utf8 tn)] -> do Pact4.exec_ db $ "DROP TABLE IF EXISTS " <> tbl tblname return tn - _ -> internalError rewindmsg + _ -> error rewindmsg Pact4.exec' db "DELETE FROM VersionedTableCreation WHERE createBlockheight > ?" [Pact4.SInt (fromIntegral bh)] @@ -479,7 +458,7 @@ rewindDbToBlock db (Parent bh) endingTxId = do vacuumTablesAtRewind droppedtbls = do let processMutatedTables ms = fmap HashSet.fromList . forM ms $ \case [Pact4.SText (Utf8 tn)] -> return tn - _ -> internalError "rewindBlock: vacuumTablesAtRewind: Couldn't resolve the name \ + _ -> error "rewindBlock: vacuumTablesAtRewind: Couldn't resolve the name \ \of the table to possibly vacuum." mutatedTables <- Pact4.qry db "SELECT DISTINCT tablename FROM VersionedTableMutation WHERE blockheight > ?;" diff --git a/src/Chainweb/Pact5/NoCoinbase.hs b/src/Chainweb/Pact/NoCoinbase.hs similarity index 90% rename from src/Chainweb/Pact5/NoCoinbase.hs rename to src/Chainweb/Pact/NoCoinbase.hs index 62e47813f1..185c1ce380 100644 --- a/src/Chainweb/Pact5/NoCoinbase.hs +++ b/src/Chainweb/Pact/NoCoinbase.hs @@ -2,7 +2,7 @@ {-# LANGUAGE ScopedTypeVariables #-} -- | --- Module: Chainweb.Pact5.NoCoinbase +-- Module: Chainweb.Pact.NoCoinbase -- Copyright: Copyright © 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -10,7 +10,7 @@ -- -- A noop coin base for genesis transactions and testing purposes. -- -module Chainweb.Pact5.NoCoinbase +module Chainweb.Pact.NoCoinbase ( noCoinbase ) where diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 83a6786228..537b634dc5 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -74,21 +74,16 @@ import Prelude hiding (lookup) import qualified Streaming as Stream import qualified Streaming.Prelude as Stream -import qualified Pact.Gas as Pact4 import Pact.Interpreter(PactDbEnv(..)) import qualified Pact.JSON.Encode as J -import qualified Pact.Types.Command as Pact4 -import qualified Pact.Types.Hash as Pact4 -import qualified Pact.Types.Runtime as Pact4 hiding (catchesPactError) -import qualified Pact.Types.Pretty as Pact4 -import qualified Pact.Core.Builtin as Pact5 -import qualified Pact.Core.Persistence as Pact5 -import qualified Pact.Core.Gas as Pact5 -import qualified Pact.Core.Info as Pact5 +import qualified Pact.Core.Builtin as Pact +import qualified Pact.Core.Persistence as Pact +import qualified Pact.Core.Gas as Pact +import qualified Pact.Core.Info as Pact -import qualified Chainweb.Pact4.TransactionExec as Pact4 -import qualified Chainweb.Pact4.Validations as Pact4 +import qualified Chainweb.Pact.TransactionExec as Pact +import qualified Chainweb.Pact.Validations as Pact import Chainweb.BlockHash import Chainweb.BlockHeader @@ -99,15 +94,14 @@ import Chainweb.Logger import Chainweb.Mempool.Mempool as Mempool import Chainweb.Miner.Pact -import Chainweb.Pact.PactService.Pact4.ExecBlock -import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact4 +import Chainweb.Pact.PactService.ExecBlock +import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact import Chainweb.Pact.Types -import Chainweb.Pact4.SPV qualified as Pact4 -import Chainweb.Pact5.SPV qualified as Pact5 +import Chainweb.Pact.SPV qualified as Pact import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time -import qualified Chainweb.Pact4.Transaction as Pact4 +import qualified Chainweb.Pact.Transaction as Pact import Chainweb.TreeDB import Chainweb.Utils hiding (check) import Chainweb.Version @@ -117,31 +111,28 @@ import Chainweb.Counter import Data.Time.Clock import Text.Printf import Data.Time.Format.ISO8601 -import qualified Chainweb.Pact.PactService.Pact4.ExecBlock as Pact4 -import qualified Chainweb.Pact4.Types as Pact4 -import qualified Chainweb.Pact5.Backend.ChainwebPactDb as Pact5 -import qualified Pact.Core.Command.Types as Pact5 -import qualified Pact.Core.Hash as Pact5 -import qualified Data.ByteString.Short as SB +import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact +import Pact.Core.Command.Types qualified as Pact +import Pact.Core.Hash qualified as Pact +import Data.ByteString.Short qualified as SB import Data.Coerce (coerce) import Data.Void -import qualified Chainweb.Pact5.Types as Pact5 -import qualified Chainweb.Pact.PactService.Pact5.ExecBlock as Pact5 -import qualified Pact.Core.Evaluate as Pact5 -import qualified Pact.Core.Names as Pact5 +import Chainweb.Pact.Types qualified as Pact +import Chainweb.Pact.PactService.ExecBlock qualified as Pact +import qualified Pact.Core.Evaluate as Pact +import qualified Pact.Core.Names as Pact import Data.Functor.Product -import qualified Chainweb.Pact5.TransactionExec as Pact5 -import qualified Chainweb.Pact5.Transaction as Pact5 +import qualified Chainweb.Pact.TransactionExec as Pact +import qualified Chainweb.Pact.Transaction as Pact import Control.Monad.Except -import qualified Chainweb.Pact5.NoCoinbase as Pact5 -import qualified Pact.Parse as Pact4 +import qualified Chainweb.Pact.NoCoinbase as Pact import qualified Control.Parallel.Strategies as Strategies -import qualified Chainweb.Pact5.Validations as Pact5 -import qualified Pact.Core.Errors as Pact5 +import qualified Chainweb.Pact.Validations as Pact +import qualified Pact.Core.Errors as Pact import Chainweb.Pact.Backend.Types import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer import Chainweb.Pact.PactService.Checkpointer (SomeBlockM(..)) -import qualified Pact.Core.StableEncoding as Pact5 +import qualified Pact.Core.StableEncoding as Pact import Control.Monad.Cont (evalContT) import qualified Data.List.NonEmpty as NonEmpty import Chainweb.PayloadProvider.Pact.Genesis (genesisPayload) @@ -1090,53 +1081,14 @@ execPreInsertCheckReq txs = pactLabel "execPreInsertCheckReq" $ do pure result where - attemptBuyGasPact4 - :: forall logger tbl. (Logger logger) - => Miner - -> Pact4.Transaction - -> Pact4.PactBlockM logger tbl (Either InsertError ()) - attemptBuyGasPact4 miner tx = Pact4.localLabelBlock ("transaction", "attemptBuyGas") $ do - mcache <- Pact4.getInitCache - l <- view (psServiceEnv . psLogger) - do - let cmd = Pact4.payloadObj <$> tx - gasPrice = view Pact4.cmdGasPrice cmd - gasLimit = fromIntegral $ view Pact4.cmdGasLimit cmd - txst = Pact4.TransactionState - { _txCache = mcache - , _txLogs = mempty - , _txGasUsed = 0 - , _txGasId = Nothing - , _txGasModel = Pact4._geGasModel Pact4.freeGasEnv - , _txWarnings = mempty - } - let !nid = Pact4.networkIdOf cmd - let !rk = Pact4.cmdToRequestKey cmd - pd <- Pact4.getTxContext miner (Pact4.publicMetaOf cmd) - bhdb <- view (psServiceEnv . psBlockHeaderDb) - dbEnv <- Pact4._cpPactDbEnv <$> view psBlockDbEnv - spv <- Pact4.pactSPV bhdb . _parentHeader <$> view psParentHeader - let ec = Pact4.mkExecutionConfig $ - [ Pact4.FlagDisableModuleInstall - , Pact4.FlagDisableHistoryInTransactionalMode ] ++ - Pact4.disableReturnRTC (Pact4.ctxVersion pd) (Pact4.ctxChainId pd) (Pact4.ctxCurrentBlockHeight pd) - let buyGasEnv = Pact4.TransactionEnv Pact4.Transactional dbEnv l Nothing (Pact4.ctxToPublicData pd) spv nid gasPrice rk gasLimit ec Nothing Nothing - - cr <- liftIO - $! Pact4.catchesPactError l Pact4.CensorsUnexpectedError - $! Pact4.execTransactionM buyGasEnv txst - $! Pact4.buyGas pd cmd miner - - return $ bimap (InsertErrorBuyGas . sshow) (\_ -> ()) cr - - attemptBuyGasPact5 + attemptBuyGas :: (Logger logger) => logger -> ParentHeader -> Miner -> Pact5.Transaction -> ExceptT InsertError (Pact5.PactBlockM logger tbl) () - attemptBuyGasPact5 logger ph miner tx = do + attemptBuyGas logger ph miner tx = do let logger' = addLabel ("transaction", "attemptBuyGas") logger result <- lift $ Pact5.pactTransaction Nothing $ \pactDb -> do let txCtx = Pact5.TxContext ph miner diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index a7322f6e48..741c222234 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -35,7 +35,7 @@ module Chainweb.Pact.PactService.Checkpointer , findLatestValidBlockHeader , exitOnRewindLimitExceeded , rewindToIncremental - , SomeBlockM(..) + , PactBlockM(..) , getEarliestBlock , getLatestBlock , lookupBlock @@ -74,16 +74,15 @@ import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger -import qualified Chainweb.Pact.PactService.Pact4.ExecBlock as Pact4 -import qualified Chainweb.Pact.PactService.Pact5.ExecBlock as Pact5 +import qualified Chainweb.Pact.PactService.ExecBlock as Pact import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.TreeDB (getBranchIncreasing, forkEntry, lookup, seekAncestor) import Chainweb.Utils hiding (check) import Chainweb.Version -import qualified Chainweb.Pact4.Types as Pact4 -import qualified Chainweb.Pact5.Types as Pact5 +import qualified Chainweb.Pact.Types as Pact4 +import qualified Chainweb.Pact.Types as Pact5 import Chainweb.Version.Guards (pact5) import qualified Chainweb.Pact.PactService.Checkpointer.Internal as Internal import Chainweb.BlockHash @@ -113,11 +112,6 @@ exitOnRewindLimitExceeded = handle $ \case \ docs/RecoveringFromDeepForks.md" ] -newtype SomeBlockM logger tbl a = SomeBlockM (Product (Pact4.PactBlockM logger tbl) (Pact5.PactBlockM logger tbl) a) - deriving newtype (Functor, Applicative, Monad) -instance MonadIO (SomeBlockM logger tbl) where - liftIO a = SomeBlockM $ Pair (liftIO a) (liftIO a) - -- read-only rewind to the latest block. -- note: because there is a race between getting the latest header -- and doing the rewind, there's a chance that the latest header @@ -126,7 +120,7 @@ instance MonadIO (SomeBlockM logger tbl) where -- note: this function will never rewind before genesis. readFromLatest :: Logger logger - => SomeBlockM logger tbl a + => PactBlockM logger tbl a -> PactServiceM logger tbl a readFromLatest doRead = readFromNthParent 0 doRead @@ -136,7 +130,7 @@ readFromNthParent :: forall logger tbl a . Logger logger => Word - -> SomeBlockM logger tbl a + -> PactBlockM logger tbl a -> PactServiceM logger tbl a readFromNthParent n doRead = go 0 where @@ -173,7 +167,7 @@ readFromNthParent n doRead = go 0 readFrom :: Logger logger => Maybe ParentHeader - -> SomeBlockM logger tbl a + -> PactBlockM logger tbl a -> PactServiceM logger tbl (Historical a) readFrom ph doRead = do cp <- view psCheckpointer @@ -193,7 +187,7 @@ readFrom ph doRead = do evalPactServiceM s e $ do fst <$> Pact5.runPactBlockM pactParent (isNothing ph) dbenv blockHandle act case doRead of - SomeBlockM (Pair forPact4 forPact5) + PactBlockM (Pair forPact4 forPact5) | pact5 v cid currentHeight -> execPact5 forPact5 | otherwise -> execPact4 forPact4 @@ -213,7 +207,7 @@ getPactParent ph = do restoreAndSave :: (CanReadablePayloadCas tbl, Logger logger, Monoid q) => Maybe ParentHeader - -> Stream (Of (SomeBlockM logger tbl (q, BlockHeader))) IO r + -> Stream (Of (PactBlockM logger tbl (q, BlockHeader))) IO r -> PactServiceM logger tbl (r, q) restoreAndSave ph blocks = do cp <- view psCheckpointer @@ -227,7 +221,7 @@ restoreAndSave ph blocks = do Internal.restoreAndSave cp ph $ blocks & S.zip (S.iterate succ firstBlockHeight) & S.map (\case - (height, SomeBlockM (Pair pact4Block pact5Block)) + (height, PactBlockM (Pair pact4Block pact5Block)) | pact5 v cid height -> Pact5RunnableBlock $ \dbEnv mph blockHandle -> runPact $ do pactParent <- getPactParent mph @@ -355,7 +349,7 @@ rewindToIncremental rewindLimit (ParentHeader parent) = do <> ". Block: "<> encodeToText (ObjectEncoded blockHeader) Just x -> return $ payloadWithOutputsToPayloadData x liftIO $ writeIORef heightRef (view blockHeight blockHeader) - SomeBlockM $ Pair + PactBlockM $ Pair (void $ Pact4.execBlock blockHeader (CheckablePayload payload)) (void $ Pact5.execExistingBlock blockHeader (CheckablePayload payload)) return (Last (Just blockHeader), blockHeader) diff --git a/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs b/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs index 069e8f3fb8..7bd0bd917b 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs @@ -31,9 +31,7 @@ module Chainweb.Pact.PactService.Checkpointer.Internal , lookupBlock , getEarliestBlock , getLatestBlock - , getBlockHistory , getBlockParent - , lookupHistorical ) where import Control.Concurrent (threadDelay) @@ -81,8 +79,8 @@ import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger -import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact4 -import qualified Chainweb.Pact5.Backend.ChainwebPactDb as Pact5 +import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact4 +import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact5 import Chainweb.Pact.Backend.DbCache import Chainweb.Pact.Backend.Utils @@ -96,49 +94,35 @@ import qualified Pact.Core.Builtin as Pact5 import qualified Pact.Core.Evaluate as Pact5 import qualified Pact.Core.Names as Pact5 import qualified Chainweb.Pact.Backend.Utils as PactDb -import Chainweb.PayloadProvider withCheckpointerResources :: (Logger logger) => logger - -> DbCacheLimitBytes -> SQLiteEnv -> IntraBlockPersistence -> ChainwebVersion -> ChainId -> (Checkpointer logger -> IO a) -> IO a -withCheckpointerResources logger dbCacheLimit sqlenv p v cid inner = do - cp <- initCheckpointerResources dbCacheLimit sqlenv p logger v cid - withAsync (logModuleCacheStats (cpModuleCacheVar cp)) $ \_ -> inner cp - where - logFun = logFunctionText logger - logModuleCacheStats e = runForever logFun "ModuleCacheStats" $ do - stats <- modifyMVar e $ \db -> do - let (s, !mc') = updateCacheStats db - return (mc', s) - logFunctionJson logger Info stats - threadDelay 60_000_000 {- 1 minute -} +withCheckpointerResources logger sqlenv p v cid inner = do + inner =<< initCheckpointerResources sqlenv p logger v cid initCheckpointerResources :: (Logger logger) - => DbCacheLimitBytes - -> SQLiteEnv + => SQLiteEnv -> IntraBlockPersistence -> logger -> ChainwebVersion -> ChainId -> IO (Checkpointer logger) -initCheckpointerResources dbCacheLimit sql p loggr v cid = do +initCheckpointerResources sql p loggr v cid = do Pact5.initSchema sql - moduleCacheVar <- newMVar (emptyDbCache dbCacheLimit) return Checkpointer { cpLogger = loggr , cpCwVersion = v , cpChainId = cid , cpSql = sql , cpIntraBlockPersistence = p - , cpModuleCacheVar = moduleCacheVar } -- | Rewind to a particular block *in-memory*, producing a read-write snapshot @@ -148,14 +132,13 @@ initCheckpointerResources dbCacheLimit sql p loggr v cid = do -- prerequisite: ParentHeader is an ancestor of the "latest block"; -- if that isn't the case, NoHistory is returned. readFrom - :: forall logger pv a + :: forall logger a . (Logger logger) => Checkpointer logger -> Maybe (Parent RankedBlockHash) - -> PactVersionT pv - -> (PactDbFor logger pv -> BlockHandle pv -> IO a) + -> (ChainwebPactDb -> BlockHandle -> IO a) -> IO (Historical a) -readFrom res maybeParent pactVersion doRead = do +readFrom res maybeParent doRead = do let currentHeight = case maybeParent of Nothing -> genesisHeight res.cpCwVersion res.cpChainId Just parent -> succ $ _rankedBlockHashHeight $ unwrapParent parent @@ -168,32 +151,8 @@ readFrom res maybeParent pactVersion doRead = do -- the db transaction) to make sure that the latestHeader check is up to date. latestHeader <- getLatestBlock res.cpSql -- is the parent the latest header, i.e., can we get away without rewinding? - let parentIsLatestHeader = latestHeader == maybeParent - h <- case pactVersion of - Pact4T - | pact5 res.cpCwVersion res.cpChainId currentHeight -> internalError $ - "Pact 4 readFrom executed on block height after Pact 5 fork, height: " <> sshow currentHeight - | otherwise -> PactDb.getEndTxId "doReadFrom" res.cpSql maybeParent >>= traverse \startTxId -> do - newDbEnv <- newMVar $ Pact4.BlockEnv - (Pact4.mkBlockHandlerEnv res.cpCwVersion res.cpChainId currentHeight res.cpSql DoNotPersistIntraBlockWrites res.cpLogger) - (Pact4.initBlockState defaultModuleCacheLimit startTxId) - { Pact4._bsModuleCache = sharedModuleCache } - let - mkBlockDbEnv db = Pact4.CurrentBlockDbEnv - { Pact4._cpPactDbEnv = PactDbEnv db newDbEnv - , Pact4._cpRegisterProcessedTx = \hash -> - Pact4.runBlockEnv newDbEnv (Pact4.indexPactTransaction $ BS.fromShort $ coerce hash) - , Pact4._cpLookupProcessedTx = \hs -> - HashMap.mapKeys coerce <$> doLookupSuccessful res.cpSql currentHeight (coerce hs) - } - pactDb - | parentIsLatestHeader = Pact4.chainwebPactDb - | otherwise = Pact4.rewoundPactDb currentHeight startTxId - r <- doRead (mkBlockDbEnv pactDb) (emptyPact4BlockHandle startTxId) - finalCache <- Pact4._bsModuleCache . Pact4._benvBlockState <$> readMVar newDbEnv - return (r, finalCache) - - Pact5T + let parentIsLatestHeader = fmap Parent latestHeader == maybeParent + h <- if | pact5 res.cpCwVersion res.cpChainId currentHeight -> PactDb.getEndTxId "doReadFrom" res.cpSql maybeParent >>= traverse \startTxId -> do let @@ -208,10 +167,10 @@ readFrom res maybeParent pactVersion doRead = do , Pact5._blockHandlerAtTip = parentIsLatestHeader } let pactDb = Pact5.chainwebPactBlockDb blockHandlerEnv - r <- doRead pactDb (emptyPact5BlockHandle startTxId) + r <- doRead pactDb (emptyBlockHandle startTxId) return (r, sharedModuleCache) | otherwise -> - internalError $ + error $ "Pact 5 readFrom executed on block height before Pact 5 fork, height: " <> sshow currentHeight case h of NoHistory -> return (sharedModuleCache, NoHistory) @@ -274,68 +233,15 @@ restoreAndSave res rewindParent blocks = do extend :: TxId -> DbCache PersistModuleData - -> IO (Of (q, Maybe RankedBlockHash, TxId, DbCache PersistModuleData) r) + -> IO (Of (q, Maybe (Parent RankedBlockHash), TxId, DbCache PersistModuleData) r) extend startTxId startModuleCache = Streaming.foldM (\(m, maybeParent, txid, moduleCache) block -> do let !bh = case maybeParent of Nothing -> genesisHeight res.cpCwVersion res.cpChainId - Just parent -> _evaluationCtxCurrentHeight parent + Just (Parent parent) -> succ $ _rankedBlockHashHeight parent case block of - Pact4RunnableBlock runBlock - | pact5 res.cpCwVersion res.cpChainId bh -> - internalError $ - "Pact 4 block executed on block height after Pact 5 fork, height: " <> sshow bh - | otherwise -> do - -- prepare a fresh block state - let handlerEnv = Pact4.mkBlockHandlerEnv res.cpCwVersion res.cpChainId bh res.cpSql res.cpIntraBlockPersistence res.cpLogger - let state = (Pact4.initBlockState defaultModuleCacheLimit txid) - { Pact4._bsModuleCache = moduleCache } - dbMVar <- newMVar Pact4.BlockEnv - { Pact4._blockHandlerEnv = handlerEnv - , Pact4._benvBlockState = state - } - - let - mkBlockDbEnv db = Pact4.CurrentBlockDbEnv - { Pact4._cpPactDbEnv = db - , Pact4._cpRegisterProcessedTx = \hash -> - Pact4.runBlockEnv dbMVar (Pact4.indexPactTransaction $ BS.fromShort $ coerce hash) - , Pact4._cpLookupProcessedTx = \hs -> - fmap (HashMap.mapKeys coerce) $ - doLookupSuccessful res.cpSql bh $ - coerce hs - } - - -- execute the block - let pact4Db = PactDbEnv Pact4.chainwebPactDb dbMVar - (m', newBh) <- runBlock (mkBlockDbEnv pact4Db) maybeParent - - -- grab any resulting state that we're interested in keeping - nextState <- Pact4._benvBlockState <$> takeMVar dbMVar - let !nextTxId = Pact4._bsTxId nextState - let !nextModuleCache = Pact4._bsModuleCache nextState - when (isJust (Pact4._bsPendingTx nextState)) $ - internalError "tx still in progress at the end of block" - -- compute the accumulator early - let !m'' = m <> m' - -- check that the new parent header has the right height for a child - -- of the previous block - case maybeParent of - Nothing - | genesisHeight res.cpCwVersion res.cpChainId /= _evaluationCtxCurrentHeight newBh -> internalError - "doRestoreAndSave: block with no parent, genesis block, should have genesis height but doesn't," - Just ph - | _evaluationCtxCurrentHeight ph /= _evaluationCtxParentHeight newBh -> internalError $ - "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " - <> sshow (_evaluationCtxParentHeight ph) <> ", child height " <> sshow (_evaluationCtxParentHeight newBh) - _ -> return () - -- persist any changes to the database - Pact4.commitBlockStateToDatabase res.cpSql - (_evaluationCtxParentHash newBh) (_evaluationCtxParentHeight newBh) - (BlockHandle (Pact4._bsTxId nextState) (Pact4._bsPendingBlock nextState)) - return (m'', Just newBh, nextTxId, nextModuleCache) - Pact5RunnableBlock runBlock + RunnableBlock runBlock | pact5 res.cpCwVersion res.cpChainId bh -> do let blockEnv = Pact5.BlockHandlerEnv @@ -350,25 +256,25 @@ restoreAndSave res rewindParent blocks = do } pactDb = Pact5.chainwebPactBlockDb blockEnv -- run the block - ((m', nextBlockHeader), blockHandle) <- runBlock pactDb maybeParent (emptyPact5BlockHandle txid) + ((m', nextBlockHeader), blockHandle) <- runBlock pactDb maybeParent (emptyBlockHandle txid) -- compute the accumulator early let !m'' = m <> m' case maybeParent of Nothing - | genesisHeight res.cpCwVersion res.cpChainId /= _evaluationCtxCurrentHeight nextBlockHeader -> internalError + | genesisHeight res.cpCwVersion res.cpChainId /= succ (_rankedBlockHashHeight nextBlockHeader) -> error "doRestoreAndSave: block with no parent, genesis block, should have genesis height but doesn't," - Just ph - | _evaluationCtxCurrentHeight ph /= _evaluationCtxParentHeight nextBlockHeader -> internalError $ + Just (Parent ph) + | bh /= _rankedBlockHashHeight nextBlockHeader -> error $ "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " - <> sshow (_evaluationCtxParentHeight ph) <> ", child height " <> sshow (_evaluationCtxParentHeight nextBlockHeader) + <> sshow (_rankedBlockHashHeight ph) <> ", child height " <> sshow (_rankedBlockHashHeight nextBlockHeader) _ -> return () Pact5.commitBlockStateToDatabase res.cpSql - (_evaluationCtxParentHash nextBlockHeader) (_evaluationCtxParentHeight nextBlockHeader) + (_rankedBlockHashHash nextBlockHeader) (_rankedBlockHashHeight nextBlockHeader) blockHandle - return (m'', Just nextBlockHeader, _blockHandleTxId blockHandle, moduleCache) + return (m'', Just (Parent nextBlockHeader), _blockHandleTxId blockHandle, moduleCache) - | otherwise -> internalError $ + | otherwise -> error $ "Pact 5 block executed on block height before Pact 5 fork, height: " <> sshow bh ) (return (mempty, rewindParent, startTxId, startModuleCache)) @@ -395,12 +301,12 @@ getEarliestBlock db = do -- is the height of the block of the block hash. -- -- TODO: Under which circumstances does this return 'Nothing'? -getLatestBlock :: HasCallStack => SQLiteEnv -> IO (Maybe (Parent RankedBlockHash)) +getLatestBlock :: HasCallStack => SQLiteEnv -> IO (Maybe RankedBlockHash) getLatestBlock db = do r <- qry_ db qtext [RInt, RBlob] >>= mapM go case r of [] -> return Nothing - (!o:_) -> return (Just $ Parent o) + (!o:_) -> return (Just o) where qtext = "SELECT blockheight, hash FROM BlockHistory ORDER BY blockheight DESC LIMIT 1" @@ -414,9 +320,10 @@ lookupBlock :: SQLiteEnv -> RankedBlockHash -> IO Bool lookupBlock db (RankedBlockHash bheight bhash) = do r <- qry db qtext [SInt $ fromIntegral bheight, SBlob (runPutS (encodeBlockHash bhash))] [RInt] - liftIO (expectSingle "row" r) >>= \case - [SInt n] -> return $! n == 1 - _ -> internalError "lookupBlock: output type mismatch" + case r of + [[SInt n]] -> return $! n == 1 + [_] -> error "lookupBlock: output type mismatch" + _ -> error "Expected single-row result" where qtext = "SELECT COUNT(*) FROM BlockHistory WHERE blockheight = ? AND hash = ?;" @@ -431,103 +338,8 @@ getBlockParent v cid db (bh, hash) r <- qry db qtext [SInt (fromIntegral (pred bh))] [RBlob] case r of [[SBlob blob]] -> - either (internalError . T.pack) (return . return) $! runGetEitherS decodeBlockHash blob - [] -> internalError "getBlockParent: block was found but its parent couldn't be found" + either error (return . return) $! runGetEitherS decodeBlockHash blob + [] -> error "getBlockParent: block was found but its parent couldn't be found" _ -> error "getBlockParent: output type mismatch" where qtext = "SELECT hash FROM BlockHistory WHERE blockheight = ?" - - --- TODO: do this in ChainwebPactDb instead? -getBlockHistory - :: SQLiteEnv - -> BlockHeader - -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info - -> IO (Historical BlockTxHistory) -getBlockHistory db blockHeader d = do - historicalEndTxId <- - fmap fromIntegral - <$> PactDb.getEndTxId "getBlockHistory" db (Just $ view rankedBlockHash blockHeader) - forM historicalEndTxId $ \endTxId -> do - startTxId <- - if bHeight == genesisHeight v cid - then return 0 - else PactDb.getEndTxId' "getBlockHistory" db (pred bHeight) (view blockParent blockHeader) >>= \case - NoHistory -> - internalError $ "getBlockHistory: missing parent for: " <> sshow blockHeader - Historical startTxId -> - return $ fromIntegral startTxId - - let tname = Pact5.domainTableName d - history <- queryHistory tname startTxId endTxId - let (!hkeys,tmap) = foldl' procTxHist (S.empty,mempty) history - !prev <- M.fromList . catMaybes <$> mapM (queryPrev tname startTxId) (S.toList hkeys) - return $ BlockTxHistory tmap prev - where - v = _chainwebVersion blockHeader - cid = view blockChainId blockHeader - bHeight = view blockHeight blockHeader - - procTxHist - :: (S.Set Utf8, M.Map TxId [Pact5.TxLog Pact5.RowData]) - -> (Utf8,TxId,Pact5.TxLog Pact5.RowData) - -> (S.Set Utf8,M.Map TxId [Pact5.TxLog Pact5.RowData]) - procTxHist (ks,r) (uk,t,l) = (S.insert uk ks, M.insertWith (++) t [l] r) - - -- Start index is inclusive, while ending index is not. - -- `endingtxid` in a block is the beginning txid of the following block. - queryHistory :: Utf8 -> Int64 -> Int64 -> IO [(Utf8,TxId,Pact5.TxLog Pact5.RowData)] - queryHistory tableName s e = do - let sql = "SELECT txid, rowkey, rowdata FROM [" <> tableName <> - "] WHERE txid >= ? AND txid < ?" - r <- qry db sql - [SInt s,SInt e] - [RInt,RText,RBlob] - forM r $ \case - [SInt txid, SText key, SBlob value] -> - (key,fromIntegral txid,) - <$> Pact5.toTxLog (_chainwebVersion blockHeader) (_chainId blockHeader) (view blockHeight blockHeader) (Pact5.renderDomain d) key value - err -> internalError $ - "queryHistory: Expected single row with three columns as the \ - \result, got: " <> T.pack (show err) - - -- Get last tx data, if any, for key before start index. - queryPrev :: Utf8 -> Int64 -> Utf8 -> IO (Maybe (RowKey,Pact5.TxLog Pact5.RowData)) - queryPrev tableName s k@(Utf8 sk) = do - let sql = "SELECT rowdata FROM [" <> tableName <> - "] WHERE rowkey = ? AND txid < ? " <> - "ORDER BY txid DESC LIMIT 1" - r <- qry db sql - [SText k,SInt s] - [RBlob] - case r of - [] -> return Nothing - [[SBlob value]] -> - Just . (RowKey $ T.decodeUtf8 sk,) - <$> Pact5.toTxLog (_chainwebVersion blockHeader) (_chainId blockHeader) (view blockHeight blockHeader) (Pact5.renderDomain d) k value - _ -> internalError $ "queryPrev: expected 0 or 1 rows, got: " <> T.pack (show r) - --- TODO: do this in ChainwebPactDb instead? -lookupHistorical - :: SQLiteEnv - -> BlockHeader - -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info - -> Pact5.RowKey - -> IO (Historical (Maybe (Pact5.TxLog Pact5.RowData))) -lookupHistorical db blockHeader d k = do - historicalEndTxId <- - PactDb.getEndTxId "lookupHistorical" db (Just $ ParentHeader blockHeader) - forM historicalEndTxId (queryHistoryLookup . fromIntegral) - where - queryHistoryLookup :: Int64 -> IO (Maybe (Pact5.TxLog Pact5.RowData)) - queryHistoryLookup e = do - let sql = "SELECT rowKey, rowdata FROM [" <> Pact5.domainTableName d <> - "] WHERE txid < ? AND rowkey = ? ORDER BY txid DESC LIMIT 1;" - r <- qry db sql - [SInt e, SText (toUtf8 $ Pact5.convRowKey k)] - [RText, RBlob] - case r of - [[SText key, SBlob value]] -> - Just <$> Pact5.toTxLog (_chainwebVersion blockHeader) (_chainId blockHeader) (view blockHeight blockHeader) (Pact5.renderDomain d) key value - [] -> pure Nothing - _ -> internalError $ "lookupHistorical: expected single-row result, got " <> sshow r diff --git a/src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs similarity index 98% rename from src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs rename to src/Chainweb/Pact/PactService/ExecBlock.hs index 5154c08d7f..afb3873b9c 100644 --- a/src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -14,13 +14,12 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} -module Chainweb.Pact.PactService.Pact5.ExecBlock +module Chainweb.Pact.PactService.ExecBlock ( runCoinbase , continueBlock , execExistingBlock , validateRawChainwebTx , validateParsedChainwebTx - ) where import Chainweb.BlockHeader @@ -29,12 +28,12 @@ import Chainweb.Logger import Chainweb.Mempool.Mempool(BlockFill (..), pact5RequestKeyToTransactionHash, InsertError (..)) import Chainweb.MinerReward import Chainweb.Miner.Pact -import Chainweb.Pact5.Backend.ChainwebPactDb (Pact5Db(doPact5DbTransaction)) -import Chainweb.Pact5.SPV qualified as Pact5 +import Chainweb.Pact.Backend.ChainwebPactDb (Pact5Db(doPact5DbTransaction)) +import Chainweb.Pact.SPV qualified as Pact5 +import Chainweb.Pact.Types +import Chainweb.Pact.Transaction +import Chainweb.Pact.TransactionExec import Chainweb.Pact.Types -import Chainweb.Pact5.Transaction -import Chainweb.Pact5.TransactionExec -import Chainweb.Pact5.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time @@ -74,17 +73,17 @@ import qualified Pact.Types.Gas as Pact4 import qualified Pact.Core.Gas as P import qualified Data.Text.Encoding as T import qualified Data.HashMap.Strict as HashMap -import qualified Chainweb.Pact5.Backend.ChainwebPactDb as Pact5 -import qualified Chainweb.Pact4.Transaction as Pact4 -import qualified Chainweb.Pact5.Transaction as Pact5 -import qualified Chainweb.Pact5.Validations as Pact5 +import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact5 +import qualified Chainweb.Pact.Transaction as Pact4 +import qualified Chainweb.Pact.Transaction as Pact5 +import qualified Chainweb.Pact.Validations as Pact5 import Pact.Core.Pretty qualified as Pact5 import qualified Data.ByteString.Short as SB import qualified Pact.Core.Hash as Pact5 import System.LogLevel import qualified Data.Aeson as Aeson import qualified Data.List.NonEmpty as NEL -import Chainweb.Pact5.NoCoinbase +import Chainweb.Pact.NoCoinbase import qualified Pact.Core.Errors as Pact5 import qualified Pact.Core.Evaluate as Pact5 import Chainweb.Pact.Backend.Types diff --git a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs deleted file mode 100644 index b7e92d4487..0000000000 --- a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs +++ /dev/null @@ -1,886 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE MultiWayIf #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE ViewPatterns #-} - --- | --- Module: Chainweb.Pact.PactService.Pact4.ExecBlock --- Copyright: Copyright © 2020 Kadena LLC. --- License: See LICENSE file --- Maintainers: Lars Kuhtz, Emily Pillmore, Stuart Popejoy --- Stability: experimental --- --- Functionality for playing block transactions. --- -module Chainweb.Pact.PactService.Pact4.ExecBlock - ( execBlock - , execTransactions - , continueBlock - , toPayloadWithOutputs - , validateParsedChainwebTx - , validateRawChainwebTx - , validateHashes - , throwCommandInvalidError - , initModuleCacheForBlock - , runCoinbase - , CommandInvalidError(..) - , checkParse - ) where - -import Chronos qualified -import Control.Concurrent.MVar -import Control.DeepSeq -import Control.Exception (evaluate) -import Control.Lens -import Control.Monad -import Control.Monad.Catch -import Control.Monad.Reader -import Control.Monad.State.Strict - -import System.LogLevel (LogLevel(..)) -import qualified Data.ByteString.Short as SB -import Data.List qualified as List -import Data.Either -import Data.Foldable (toList) -import qualified Data.HashMap.Strict as HashMap -import qualified Data.Map as Map -import Data.Maybe -import Data.Text (Text) -import qualified Data.Text as T -import Data.Vector (Vector) -import qualified Data.Vector as V - -import System.IO -import System.Timeout - -import Prelude hiding (lookup) - -import Pact.Compile (compileExps) -import Pact.Interpreter(PactDbEnv(..)) -import qualified Pact.JSON.Encode as J -import qualified Pact.Parse as Pact4 hiding (parsePact) -import qualified Pact.Types.Command as Pact4 -import Pact.Types.ExpParser (mkTextInfo, ParseEnv(..)) -import qualified Pact.Types.Hash as Pact4 -import Pact.Types.RPC -import qualified Pact.Types.Runtime as Pact4 -import qualified Pact.Types.SPV as Pact4 - -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Logger -import Chainweb.Mempool.Mempool as Mempool -import Chainweb.MinerReward -import Chainweb.Miner.Pact - -import Chainweb.Pact.Types -import Chainweb.Pact4.SPV qualified as Pact4 -import Chainweb.Pact4.NoCoinbase -import qualified Chainweb.Pact4.Transaction as Pact4 -import qualified Chainweb.Pact4.TransactionExec as Pact4 -import qualified Chainweb.Pact4.Validations as Pact4 -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Time -import Chainweb.Utils hiding (check) -import Chainweb.Version -import Chainweb.Version.Guards -import Chainweb.Pact4.Backend.ChainwebPactDb -import Data.Coerce -import Data.Word -import GrowableVector.Lifted (Vec) -import Control.Monad.Primitive -import qualified GrowableVector.Lifted as Vec -import qualified Data.Set as S -import Chainweb.Pact4.Types -import Chainweb.Pact4.ModuleCache -import Control.Monad.Except -import qualified Data.List.NonEmpty as NE -import Chainweb.Pact.Backend.Types (BlockHandle(..)) - - --- | Execute a block -- only called in validate either for replay or for validating current block. --- -execBlock - :: (CanReadablePayloadCas tbl, Logger logger) - => BlockHeader - -- ^ this is the current header. We may consider changing this to the parent - -- header to avoid confusion with new block and prevent using data from this - -- header when we should use the respective values from the parent header - -- instead. - -> CheckablePayload - -> PactBlockM logger tbl (Pact4.Gas, PayloadWithOutputs) -execBlock currHeader payload = do - let plData = checkablePayloadToPayloadData payload - dbEnv <- view psBlockDbEnv - miner <- decodeStrictOrThrow' (_minerData $ view payloadDataMiner plData) - - trans <- liftIO $ pact4TransactionsFromPayload - (pact4ParserVersion v (_chainId currHeader) (view blockHeight currHeader)) - plData - logger <- view (psServiceEnv . psLogger) - - -- The reference time for tx timings validation. - -- - -- The legacy behavior is to use the creation time of the /current/ header. - -- The new default behavior is to use the creation time of the /parent/ header. - -- - txValidationTime <- if isGenesisBlockHeader currHeader - then return (ParentCreationTime $ view blockCreationTime currHeader) - else ParentCreationTime . view blockCreationTime . _parentHeader <$> view psParentHeader - - -- prop_tx_ttl_validate - errorsIfPresent <- liftIO $ - forM (V.toList trans) $ \tx -> - fmap (Pact4._cmdHash tx,) $ - runExceptT $ - validateParsedChainwebTx logger v cid dbEnv txValidationTime - (view blockHeight currHeader) tx - - case NE.nonEmpty [ (hsh, sshow err) | (hsh, Left err) <- errorsIfPresent ] of - Nothing -> return () - Just errs -> throwM $ Pact4TransactionValidationException errs - - logInitCache - - !results <- go miner trans >>= throwCommandInvalidError - - let !totalGasUsed = sumOf (folded . to Pact4._crGas) results - - pwo <- either throwM return $ - validateHashes currHeader payload miner results - return (totalGasUsed, pwo) - where - blockGasLimit = - fromIntegral <$> maxBlockGasLimit v (view blockHeight currHeader) - - logInitCache = liftPactServiceM $ do - mc <- fmap (fmap instr . _getModuleCache) <$> use psInitCache - logDebugPact $ "execBlock: initCache: " <> sshow mc - - instr (md,_) = preview (Pact4._MDModule . Pact4.mHash) $ Pact4._mdModule md - - v = _chainwebVersion currHeader - cid = _chainId currHeader - - isGenesisBlock = isGenesisBlockHeader currHeader - - go m txs = if isGenesisBlock - then - -- GENESIS VALIDATE COINBASE: Reject bad coinbase, use date rule for precompilation - execTransactions m txs - (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled False) blockGasLimit Nothing - else - -- VALIDATE COINBASE: back-compat allow failures, use date rule for precompilation - execTransactions m txs - (EnforceCoinbaseFailure False) (CoinbaseUsePrecompiled False) blockGasLimit Nothing - -throwCommandInvalidError - :: Transactions Pact4 (Either CommandInvalidError a) - -> PactBlockM logger tbl (Transactions Pact4 a) -throwCommandInvalidError = (transactionPairs . traverse . _2) throwGasFailure - where - throwGasFailure = \case - Left (CommandInvalidGasPurchaseFailure e) -> throwM (Pact4BuyGasFailure e) - - -- this should be impossible because we don't - -- set tx time limits in validateBlock - Left (CommandInvalidTxTimeout t) -> throwM t - - Right r -> pure r - --- | The validation logic for Pact Transactions that have not had their --- code parsed yet. This is used by the mempool to estimate tx validity --- before inclusion into blocks, but it's also used by ExecBlock to check --- if all of the txs in a block are valid. --- --- Skips validation for genesis transactions, since gas accounts, etc. don't --- exist yet. --- -validateRawChainwebTx - :: forall logger - . (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> PactDbFor logger Pact4 - -> ParentCreationTime - -- ^ reference time for tx validation. - -> BlockHeight - -- ^ Current block height - -> Pact4.UnparsedTransaction - -> ExceptT InsertError IO Pact4.Transaction -validateRawChainwebTx logger v cid dbEnv txValidationTime bh tx = do - parsed <- checkParse logger v cid bh tx - validateParsedChainwebTx logger v cid dbEnv txValidationTime bh parsed - return parsed - --- | The principal validation logic for groups of Pact Transactions. --- This is used by the mempool to estimate tx validity --- before inclusion into blocks, but it's also used by ExecBlock to check --- if all of the txs in a block are valid. --- --- Skips validation for genesis transactions, since gas accounts, etc. don't --- exist yet. --- -validateParsedChainwebTx - :: Logger logger - => logger - -> ChainwebVersion - -> ChainId - -> PactDbFor logger Pact4 - -> ParentCreationTime - -- ^ reference time for tx validation. - -> BlockHeight - -- ^ Current block height - -> Pact4.Transaction - -> ExceptT InsertError IO () -validateParsedChainwebTx logger v cid dbEnv txValidationTime bh tx - | bh == genesisHeight v cid = pure () - | otherwise = do - checkUnique logger dbEnv tx - checkTxHash logger v cid bh tx - checkChain cid tx - checkTxSigs logger v cid bh tx - checkTimes logger v cid bh txValidationTime tx - _ <- checkCompile logger v cid bh tx - return () - -checkChain - :: ChainId -> Pact4.Transaction -> ExceptT InsertError IO () -checkChain cid tx = - unless (Pact4.assertChainId cid txCid) $ - throwError $ InsertErrorWrongChain (chainIdToText cid) (Pact4._chainId txCid) - where - txCid = view (Pact4.cmdPayload . to Pact4.payloadObj . Pact4.pMeta . Pact4.pmChainId) tx - -checkUnique - :: (Logger logger) - => logger - -> PactDbFor logger Pact4 - -> Pact4.Command (Pact4.PayloadWithText meta code) - -> ExceptT InsertError IO () -checkUnique logger dbEnv t = do - liftIO $ logFunctionText logger Debug $ "Pact4.checkUnique: " <> sshow (Pact4._cmdHash t) - found <- liftIO $ - HashMap.lookup (coerce $ Pact4.toUntypedHash $ Pact4._cmdHash t) <$> - _cpLookupProcessedTx dbEnv - (V.singleton $ coerce $ Pact4.toUntypedHash $ Pact4._cmdHash t) - case found of - Nothing -> pure () - Just _ -> throwError InsertErrorDuplicate - -checkTimes - :: (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> BlockHeight - -> ParentCreationTime - -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta code) - -> ExceptT InsertError IO () -checkTimes logger v cid bh txValidationTime t = do - liftIO $ logFunctionText logger Debug $ "Pact4.checkTimes: " <> sshow (Pact4._cmdHash t) - if | skipTxTimingValidation v cid bh -> - return () - | not (Pact4.assertTxNotInFuture txValidationTime (Pact4.payloadObj <$> t)) -> - throwError InsertErrorTimeInFuture - | not (Pact4.assertTxTimeRelativeToParent txValidationTime (Pact4.payloadObj <$> t)) -> - throwError InsertErrorTTLExpired - | otherwise -> - return () - -checkTxHash - :: (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> BlockHeight - -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta code) - -> ExceptT InsertError IO () -checkTxHash logger v cid bh t = do - liftIO $ logFunctionText logger Debug $ "Pact4.checkTxHash: " <> sshow (Pact4._cmdHash t) - case Pact4.verifyHash (Pact4._cmdHash t) (SB.fromShort $ Pact4.payloadBytes $ Pact4._cmdPayload t) of - Left _ - | doCheckTxHash v cid bh -> throwError InsertErrorInvalidHash - | otherwise -> pure () - Right _ -> pure () - -checkTxSigs - :: (MonadIO f, MonadError InsertError f, Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> BlockHeight - -> Pact4.Command (Pact4.PayloadWithText m c) - -> f () -checkTxSigs logger v cid bh t = do - liftIO $ logFunctionText logger Debug $ "Pact4.checkTxSigs: " <> sshow (Pact4._cmdHash t) - case Pact4.assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs of - Right _ -> do - pure () - Left err -> do - throwError $ InsertErrorInvalidSigs (displayAssertValidateSigsError err) - where - hsh = Pact4._cmdHash t - sigs = Pact4._cmdSigs t - signers = Pact4._pSigners $ Pact4.payloadObj $ Pact4._cmdPayload t - validSchemes = validPPKSchemes v cid bh - webAuthnPrefixLegal = isWebAuthnPrefixLegal v cid bh - -checkCompile - :: (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> BlockHeight - -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Pact4.ParsedCode) - -> ExceptT InsertError IO Pact4.Transaction -checkCompile logger v cid bh tx = do - liftIO $ logFunctionText logger Debug $ "Pact4.checkCompile: " <> sshow (Pact4._cmdHash tx) - case payload of - Exec (ExecMsg parsedCode _) -> - case compileCode parsedCode of - Left perr -> throwError $ InsertErrorCompilationFailed (sshow perr) - Right _ -> return tx - _ -> return tx - where - payload = Pact4._pPayload $ Pact4.payloadObj $ Pact4._cmdPayload tx - compileCode p = - let e = ParseEnv (chainweb216Pact v cid bh) - in compileExps e (mkTextInfo (Pact4._pcCode p)) (Pact4._pcExps p) - -checkParse - :: (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> BlockHeight - -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text) - -> ExceptT InsertError IO Pact4.Transaction -checkParse logger v cid bh tx = do - liftIO $ logFunctionText logger Debug $ "Pact4.checkParse: " <> sshow (Pact4._cmdHash tx) - forMOf (traversed . traversed) tx - (either (throwError . InsertErrorPactParseError . T.pack) return . Pact4.parsePact (pact4ParserVersion v cid bh)) - -execTransactions - :: (Logger logger) - => Miner - -> Vector Pact4.Transaction - -> EnforceCoinbaseFailure - -> CoinbaseUsePrecompiled - -> Maybe Pact4.Gas - -> Maybe Micros - -> PactBlockM logger tbl (Transactions Pact4 (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))) -execTransactions miner ctxs enfCBFail usePrecomp gasLimit timeLimit = do - mc <- initModuleCacheForBlock - -- for legacy reasons (ask Emily) we don't use the module cache resulting - -- from coinbase to run the pact cmds - coinOut <- runCoinbase miner enfCBFail usePrecomp mc - T2 txOuts _mcOut <- applyPactCmds ctxs miner mc gasLimit timeLimit - return $! Transactions (V.zip ctxs txOuts) coinOut - -initModuleCacheForBlock :: (Logger logger) => PactBlockM logger tbl ModuleCache -initModuleCacheForBlock = do - PactServiceState{..} <- get - isGenesis <- view psIsGenesis - pbh <- views psParentHeader (view blockHeight . _parentHeader) - case Map.lookupLE pbh _psInitCache of - Nothing -> if isGenesis - then return mempty - else do - mc <- Pact4.readInitModules - updateInitCacheM mc - return mc - Just (_,mc) -> pure mc - -runCoinbase - :: (Logger logger) - => Miner - -> EnforceCoinbaseFailure - -> CoinbaseUsePrecompiled - -> ModuleCache - -> PactBlockM logger tbl (Pact4.CommandResult [Pact4.TxLogJson]) -runCoinbase miner enfCBFail usePrecomp mc = do - isGenesis <- view psIsGenesis - if isGenesis - then return noCoinbase - else do - logger <- view (psServiceEnv . psLogger) - v <- view chainwebVersion - txCtx <- getTxContext miner Pact4.noPublicMeta - - let !bh = ctxCurrentBlockHeight txCtx - - let reward = minerReward v bh - dbEnv <- view psBlockDbEnv - let pactDb = _cpPactDbEnv dbEnv - - T2 cr upgradedCacheM <- - liftIO $ Pact4.applyCoinbase v logger pactDb reward txCtx enfCBFail usePrecomp mc - mapM_ upgradeInitCache upgradedCacheM - liftPactServiceM $ debugResult "runPact4Coinbase" (Pact4.crLogs %~ fmap J.Array $ cr) - return $! cr - - where - upgradeInitCache newCache = do - liftPactServiceM $ logInfoPact "Updating init cache for upgrade" - updateInitCacheM newCache - - -data CommandInvalidError - = CommandInvalidGasPurchaseFailure !Pact4GasPurchaseFailure - | CommandInvalidTxTimeout !TxTimeout - --- | Apply multiple Pact commands, incrementing the transaction Id for each. --- The output vector is in the same order as the input (i.e. you can zip it --- with the inputs.) -applyPactCmds - :: forall logger tbl. (Logger logger) - => Vector Pact4.Transaction - -> Miner - -> ModuleCache - -> Maybe Pact4.Gas - -> Maybe Micros - -> PactBlockM logger tbl (T2 (Vector (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))) ModuleCache) -applyPactCmds cmds miner startModuleCache blockGas txTimeLimit = do - let txsGas txs = fromIntegral $ sumOf (traversed . _Right . to Pact4._crGas) txs - (txOuts, T2 mcOut _) <- tracePactBlockM' "applyPactCmds" (\_ -> ()) (txsGas . fst) $ - flip runStateT (T2 startModuleCache blockGas) $ - go [] (zip [0..] $ V.toList cmds) - return $! T2 (V.fromList . List.reverse $ txOuts) mcOut - where - go - :: [Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])] - -> [(Word, Pact4.Transaction)] - -> StateT - (T2 ModuleCache (Maybe Pact4.Gas)) - (PactBlockM logger tbl) - [Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])] - go !acc = \case - [] -> do - pure acc - (txIdxInBlock, tx) : rest -> do - r <- applyPactCmd (TxBlockIdx txIdxInBlock) miner txTimeLimit tx - case r of - Left e@(CommandInvalidTxTimeout _) -> do - pure (Left e : acc) - Left e@(CommandInvalidGasPurchaseFailure _) -> do - go (Left e : acc) rest - Right a -> do - go (Right a : acc) rest - -applyPactCmd - :: (Logger logger) - => TxIdxInBlock - -> Miner - -> Maybe Micros - -> Pact4.Transaction - -> StateT - (T2 ModuleCache (Maybe Pact4.Gas)) - (PactBlockM logger tbl) - (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])) -applyPactCmd txIdxInBlock miner txTimeLimit cmd = StateT $ \(T2 mcache maybeBlockGasRemaining) -> do - dbEnv <- view psBlockDbEnv - let pactDb = _cpPactDbEnv dbEnv - prevBlockState <- liftIO $ fmap _benvBlockState $ - readMVar $ pdPactDbVar pactDb - logger <- view (psServiceEnv . psLogger) - gasLogger <- view (psServiceEnv . psGasLogger) - txFailuresCounter <- view (psServiceEnv . psTxFailuresCounter) - isGenesis <- view psIsGenesis - v <- view chainwebVersion - let - -- for errors so fatal that the tx doesn't make it in the block - onFatalError e - | Just (Pact4BuyGasFailure f) <- fromException e = pure (Left (CommandInvalidGasPurchaseFailure f), T2 mcache maybeBlockGasRemaining) - | Just t@(TxTimeout {}) <- fromException e = do - -- timeouts can occur at any point during the transaction, even after - -- gas has been bought (or even while gas is being redeemed, after the - -- transaction proper is done). therefore we need to revert the block - -- state ourselves if it happens. - liftIO $ Pact4.modifyMVar' - (pdPactDbVar pactDb) - (benvBlockState .~ prevBlockState) - pure (Left (CommandInvalidTxTimeout t), T2 mcache maybeBlockGasRemaining) - | otherwise = throwM e - requestedTxGasLimit = view Pact4.cmdGasLimit (Pact4.payloadObj <$> cmd) - -- notice that we add 1 to the remaining block gas here, to distinguish the - -- cases "tx used exactly as much gas remained in the block" (which is fine) - -- and "tx attempted to use more gas than remains in the block" (which is - -- illegal). for example: tx has a tx gas limit of 10000. the block has 5000 - -- remaining gas. therefore the tx is applied with a tx gas limit of 5001. - -- if it uses 5001, that's illegal; if it uses 5000 or less, that's legal. - newTxGasLimit = case maybeBlockGasRemaining of - Nothing -> requestedTxGasLimit - Just blockGasRemaining -> min (fromIntegral blockGasRemaining) requestedTxGasLimit - gasLimitedCmd = - set Pact4.cmdGasLimit newTxGasLimit (Pact4.payloadObj <$> cmd) - initialGas = Pact4.initialGasOf (Pact4._cmdPayload cmd) - let !hsh = Pact4._cmdHash cmd - - handle onFatalError $ do - T2 result mcache' <- do - txCtx <- getTxContext miner (Pact4.publicMetaOf gasLimitedCmd) - let gasModel = getGasModel txCtx - if isGenesis - then liftIO $! Pact4.applyGenesisCmd logger pactDb Pact4.noSPVSupport txCtx gasLimitedCmd - else do - bhdb <- view (psServiceEnv . psBlockHeaderDb) - parent <- view psParentHeader - let spv = Pact4.pactSPV bhdb (_parentHeader parent) - let - !timeoutError = TxTimeout (pact4RequestKeyToTransactionHash $ Pact4.cmdToRequestKey cmd) - txTimeout io = case txTimeLimit of - Nothing -> do - logFunctionText logger Debug $ "txTimeLimit was not set - defaulting to a function of the block gas limit" - io - Just limit -> do - logFunctionText logger Debug $ "txTimeLimit was " <> sshow limit - maybe (throwM timeoutError) return =<< newTimeout (fromIntegral limit) io - txGas (T3 r _ _) = fromIntegral $ Pact4._crGas r - T3 r c _warns <- do - tracePactBlockM' "applyCmd" (\_ -> J.toJsonViaEncode hsh) txGas $ do - liftIO $ txTimeout $ - Pact4.applyCmd v logger gasLogger txFailuresCounter pactDb miner gasModel txCtx txIdxInBlock spv gasLimitedCmd initialGas mcache ApplySend - pure $ T2 r c - - if isGenesis - then updateInitCacheM mcache' - else liftPactServiceM $ debugResult "applyPactCmd" (Pact4.crLogs %~ fmap J.Array $ result) - - -- mark the tx as processed at the checkpointer. - liftIO $ _cpRegisterProcessedTx dbEnv (coerce $ Pact4.toUntypedHash hsh) - case maybeBlockGasRemaining of - Just blockGasRemaining - | Left _ <- Pact4._pactResult (Pact4._crResult result) - , blockGasRemaining < fromIntegral requestedTxGasLimit - -> throwM $ BlockGasLimitExceeded (fromIntegral requestedTxGasLimit - blockGasRemaining) - -- ^ this tx attempted to consume more gas than remains in the - -- block, so the block is invalid. we know this because failing - -- transactions consume their entire gas limit. - _ -> return () - let maybeBlockGasRemaining' = (\g -> g - Pact4._crGas result) <$> maybeBlockGasRemaining - pure (Right result, T2 mcache' maybeBlockGasRemaining') - -pact4TransactionsFromPayload - :: Pact4.PactParserVersion - -> PayloadData - -> IO (Vector Pact4.Transaction) -pact4TransactionsFromPayload ppv plData = do - vtrans <- fmap V.fromList $ - mapM toCWTransaction $ - toList (view payloadDataTransactions plData) - let (theLefts, theRights) = partitionEithers $ V.toList vtrans - unless (null theLefts) $ do - let ls = map T.pack theLefts - throwM $ TransactionDecodeFailure $ "Failed to decode pact transactions: " - <> T.intercalate ". " ls - return $! V.fromList theRights - where - toCWTransaction bs = evaluate (force (codecDecode (Pact4.payloadCodec ppv) $ - _transactionBytes bs)) - -debugResult :: J.Encode a => Logger logger => Text -> a -> PactServiceM logger tbl () -debugResult msg result = - logDebugPact $ trunc $ msg <> " result: " <> J.encodeText result - where - trunc t | T.length t < limit = t - | otherwise = T.take limit t <> " [truncated]" - limit = 5000 - - --- | Calculate miner reward. --- --- See: 'rewards/miner_rewards.csv' --- -minerReward - :: ChainwebVersion - -> BlockHeight - -> Pact4.ParsedDecimal -minerReward v = Pact4.ParsedDecimal - . _kda - . minerRewardKda - . blockMinerReward v -{-# INLINE minerReward #-} - -data CRLogPair = CRLogPair Pact4.Hash [Pact4.TxLogJson] - -instance J.Encode CRLogPair where - build (CRLogPair h logs) = J.object - [ "hash" J..= h - , "rawLogs" J..= J.Array logs - ] - {-# INLINE build #-} - -validateHashes - :: BlockHeader - -- ^ Current Header - -> CheckablePayload - -> Miner - -> Transactions Pact4 (Pact4.CommandResult [Pact4.TxLogJson]) - -> Either PactException PayloadWithOutputs -validateHashes bHeader payload miner transactions = - if newHash == prevHash - then Right actualPwo - else Left $ BlockValidationFailure $ BlockValidationFailureMsg $ - J.encodeText $ J.object - [ "header" J..= J.encodeWithAeson (ObjectEncoded bHeader) - , "actual" J..= J.encodeWithAeson actualPwo - , "expected" J..?= case payload of - CheckablePayload _ -> Nothing - CheckablePayloadWithOutputs pwo -> Just $ J.encodeWithAeson pwo - ] - where - - actualPwo = toPayloadWithOutputs Pact4T miner transactions - - newHash = _payloadWithOutputsPayloadHash actualPwo - prevHash = view blockPayloadHash bHeader - -type GrowableVec = Vec (PrimState IO) - --- | Continue adding transactions to an existing block. -continueBlock - :: forall logger tbl - . (Logger logger, CanReadablePayloadCas tbl) - => MemPoolAccess - -> BlockInProgress Pact4 - -> PactBlockM logger tbl (BlockInProgress Pact4) -continueBlock mpAccess blockInProgress = do - v <- view chainwebVersion - cid <- view chainId - ParentHeader parent <- view psParentHeader - let pHeight = view blockHeight parent - let pHash = view blockHash parent - liftIO $ do - mpaProcessFork mpAccess parent - mpaSetLastHeader mpAccess parent - liftPactServiceM $ - logInfoPact $ "(parent height = " <> sshow pHeight <> ")" - <> " (parent hash = " <> sshow pHash <> ")" - - blockDbEnv <- view psBlockDbEnv - let pactDb = _cpPactDbEnv blockDbEnv - -- restore the block state from the block being continued - liftIO $ - modifyMVar_ (pdPactDbVar pactDb) $ \blockEnv -> - return - $! blockEnv - & benvBlockState . bsPendingBlock .~ _blockHandlePending (_blockInProgressHandle blockInProgress) - & benvBlockState . bsTxId .~ _blockHandleTxId (_blockInProgressHandle blockInProgress) - - blockGasLimit <- view (psServiceEnv . psBlockGasLimit) - mTxTimeLimit <- view (psServiceEnv . psTxTimeLimit) - - let txTimeHeadroomFactor :: Double - txTimeHeadroomFactor = 5 - let txTimeLimit :: Micros - -- 2.5 microseconds per unit gas - txTimeLimit = fromMaybe - (round $ (2.5 * txTimeHeadroomFactor) * fromIntegral blockGasLimit) - mTxTimeLimit - - let Pact4ModuleCache initCache = _blockInProgressModuleCache blockInProgress - let cb = _transactionCoinbase (_blockInProgressTransactions blockInProgress) - let startTxs = _transactionPairs (_blockInProgressTransactions blockInProgress) - - successes <- liftIO $ Vec.fromFoldable startTxs - failures <- liftIO $ Vec.new @_ @_ @TransactionHash - - let initState = BlockFill - (_blockInProgressRemainingGasLimit blockInProgress) - (S.fromList $ pact4RequestKeyToTransactionHash . Pact4._crReqKey . snd <$> V.toList startTxs) - 0 - - -- Heuristic: limit fetches to count of 1000-gas txs in block. - let fetchLimit = fromIntegral $ blockGasLimit `div` 1000 - T2 - finalModuleCache - BlockFill { _bfTxHashes = requestKeys, _bfGasLimit = finalGasLimit } - <- refill fetchLimit txTimeLimit successes failures initCache initState - - liftPactServiceM $ logInfoPact $ "(request keys = " <> sshow requestKeys <> ")" - - liftIO $ do - txHashes <- Vec.toLiftedVector failures - mpaBadlistTx mpAccess txHashes - - txs <- liftIO $ Vec.toLiftedVector successes - -- edmund: we need to be careful about timeouts. - -- If a tx times out, it must not be in the block state, otherwise - -- the "block in progress" will contain pieces of state from that tx. - -- - -- this cannot happen now because applyPactCmd doesn't let it. - finalBlockState <- fmap _benvBlockState - $ liftIO - $ readMVar - $ pdPactDbVar - $ pactDb - let !blockInProgress' = BlockInProgress - { _blockInProgressModuleCache = Pact4ModuleCache finalModuleCache - , _blockInProgressHandle = BlockHandle - { _blockHandleTxId = _bsTxId finalBlockState - , _blockHandlePending = _bsPendingBlock finalBlockState - } - , _blockInProgressParentHeader = newBlockParent - , _blockInProgressRemainingGasLimit = finalGasLimit - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = cb - , _transactionPairs = txs - } - , _blockInProgressMiner = _blockInProgressMiner blockInProgress - , _blockInProgressPactVersion = Pact4T - , _blockInProgressChainwebVersion = v - , _blockInProgressChainId = cid - } - return blockInProgress' - where - newBlockParent = _blockInProgressParentHeader blockInProgress - - - getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector Pact4.Transaction) - getBlockTxs bfState = do - dbEnv <- view psBlockDbEnv - psEnv <- ask - let v = _chainwebVersion psEnv - cid = _chainId psEnv - logger <- view (psServiceEnv . psLogger) - -- parent time needs to know if we're *actually* at genesis - let parentTime = - maybe - (v ^?! versionGenesis . genesisTime . atChain cid) - (view blockCreationTime . _parentHeader) - newBlockParent - ParentHeader parent <- view psParentHeader - let pHeight = view blockHeight parent - let pHash = view blockHash parent - let validate bhi _bha txs = forM txs $ \tx -> runExceptT $ do - validateRawChainwebTx logger v cid dbEnv (ParentCreationTime parentTime) bhi tx - - liftIO $! - mpaGetBlock mpAccess bfState validate (pHeight + 1) pHash parentTime - - refill - :: Word64 - -> Micros - -> GrowableVec (Pact4.Transaction, Pact4.CommandResult [Pact4.TxLogJson]) - -> GrowableVec TransactionHash - -> ModuleCache - -> BlockFill - -> PactBlockM logger tbl (T2 ModuleCache BlockFill) - refill fetchLimit txTimeLimit successes failures = go - where - go :: ModuleCache -> BlockFill -> PactBlockM logger tbl (T2 ModuleCache BlockFill) - go mc unchanged@bfState = do - - case unchanged of - BlockFill g _ c -> do - (goodLength, badLength) <- liftIO $ (,) <$> Vec.length successes <*> Vec.length failures - liftPactServiceM $ logDebugPact $ "Block fill: count=" <> sshow c - <> ", gaslimit=" <> sshow g <> ", good=" - <> sshow goodLength <> ", bad=" <> sshow badLength - - -- LOOP INVARIANT: limit absolute recursion count - if _bfCount bfState > fetchLimit then liftPactServiceM $ do - logInfoPact $ "Refill fetch limit exceeded (" <> sshow fetchLimit <> ")" - pure (T2 mc unchanged) - else do - when (_bfGasLimit bfState < 0) $ - throwM $ MempoolFillFailure $ "Internal error, negative gas limit: " <> sshow bfState - - if _bfGasLimit bfState == 0 then pure (T2 mc unchanged) else do - - newTrans <- getBlockTxs bfState - if V.null newTrans then pure (T2 mc unchanged) else do - - T2 pairs mc' <- do - T2 txOuts mcOut <- applyPactCmds newTrans (_blockInProgressMiner blockInProgress) mc Nothing (Just txTimeLimit) - return $! T2 (V.force (V.zip newTrans txOuts)) mcOut - - oldSuccessesLength <- liftIO $ Vec.length successes - - (newState, timedOut) <- splitResults successes failures unchanged (V.toList pairs) - - -- LOOP INVARIANT: gas must not increase - when (_bfGasLimit newState > _bfGasLimit bfState) $ - throwM $ MempoolFillFailure $ "Gas must not increase: " <> sshow (bfState,newState) - - newSuccessesLength <- liftIO $ Vec.length successes - let addedSuccessCount = newSuccessesLength - oldSuccessesLength - - if timedOut - then - -- a transaction timed out, so give up early and make the block - pure (T2 mc' (incCount newState)) - else if _bfGasLimit newState >= _bfGasLimit bfState && addedSuccessCount > 0 - then - -- INVARIANT: gas must decrease if any transactions succeeded - throwM $ MempoolFillFailure - $ "Invariant failure, gas did not decrease: " - <> sshow (bfState,newState,V.length newTrans,addedSuccessCount) - else - go mc' (incCount newState) - - incCount :: BlockFill -> BlockFill - incCount b = over bfCount succ b - - -- | Split the results of applying each command into successes and failures, - -- and return the final 'BlockFill'. - -- - -- If we encounter a 'TxTimeout', we short-circuit, and only return - -- what we've put into the block before the timeout. We also report - -- that we timed out, so that `refill` can stop early. - -- - -- The failed txs are later badlisted. - splitResults :: () - => GrowableVec (Pact4.Transaction, Pact4.CommandResult [Pact4.TxLogJson]) - -> GrowableVec TransactionHash -- ^ failed txs - -> BlockFill - -> [(Pact4.Transaction, Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))] - -> PactBlockM logger tbl (BlockFill, Bool) - splitResults successes failures = go - where - go acc@(BlockFill g rks i) = \case - [] -> pure (acc, False) - (t, r) : rest -> case r of - Right cr -> do - !rks' <- enforceUnique rks (pact4RequestKeyToTransactionHash $ Pact4._crReqKey cr) - -- Decrement actual gas used from block limit - let !g' = g - fromIntegral (Pact4._crGas cr) - liftIO $ Vec.push successes (t, cr) - go (BlockFill g' rks' i) rest - Left (CommandInvalidGasPurchaseFailure (Pact4GasPurchaseFailure h _)) -> do - !rks' <- enforceUnique rks h - -- Gas buy failure adds failed request key to fail list only - liftIO $ Vec.push failures h - go (BlockFill g rks' i) rest - Left (CommandInvalidTxTimeout (TxTimeout h)) -> do - liftIO $ Vec.push failures h - liftPactServiceM $ logErrorPact $ "timed out on " <> sshow h - return (acc, True) - - enforceUnique rks rk - | S.member rk rks = - throwM $ MempoolFillFailure $ "Duplicate transaction: " <> sshow rk - | otherwise = return $ S.insert rk rks - --- | This timeout variant returns Nothing if the timeout elapsed, regardless of whether or not it was actually able to interrupt its argument. --- This is more robust in the face of scheduler behavior than the standard 'System.Timeout.timeout', with small timeouts. -newTimeout :: Int -> IO a -> IO (Maybe a) -newTimeout n f = do - (timeSpan, a) <- Chronos.stopwatch (timeout n f) - if Chronos.getTimespan timeSpan > fromIntegral (n * 1000) - then return Nothing - else return a diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index 93a0b0f8fa..800b7ceb58 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -103,7 +103,7 @@ import Chainweb.Pact.RestAPI import Chainweb.Pact.RestAPI.EthSpv import Chainweb.Pact.RestAPI.SPV import Chainweb.Pact.Types -import Chainweb.Pact4.SPV qualified as Pact4 +import Chainweb.Pact.SPV qualified as Pact4 import Pact.Types.ChainMeta qualified as Pact4 import Chainweb.Payload import Chainweb.Payload.PayloadStore @@ -114,11 +114,11 @@ import Chainweb.SPV.CreateProof import Chainweb.SPV.EventProof import Chainweb.SPV.OutputProof import Chainweb.SPV.PayloadProof -import qualified Chainweb.Pact4.Transaction as Pact4 hiding (parsePact) +import qualified Chainweb.Pact.Transaction as Pact4 hiding (parsePact) import qualified Chainweb.TreeDB as TreeDB import Chainweb.Utils import Chainweb.Version -import qualified Chainweb.Pact4.Validations as Pact4 +import qualified Chainweb.Pact.Validations as Pact4 import Chainweb.Version.Guards (isWebAuthnPrefixLegal, validPPKSchemes) import Chainweb.WebPactExecutionService @@ -131,9 +131,9 @@ import qualified Pact.Types.Hash as Pact4 import qualified Pact.Core.Command.Types as Pact5 import qualified Pact.Core.Pretty as Pact5 -import qualified Chainweb.Pact5.Transaction as Pact5 -import qualified Chainweb.Pact5.Types as Pact5 -import qualified Chainweb.Pact5.Validations as Pact5 +import qualified Chainweb.Pact.Transaction as Pact5 +import qualified Chainweb.Pact.Types as Pact5 +import qualified Chainweb.Pact.Validations as Pact5 import Data.Coerce import qualified Pact.Core.Command.Server as Pact5 import qualified Pact.Core.Errors as Pact5 diff --git a/src/Chainweb/Pact5/SPV.hs b/src/Chainweb/Pact/SPV.hs similarity index 99% rename from src/Chainweb/Pact5/SPV.hs rename to src/Chainweb/Pact/SPV.hs index 84917207cf..a0c167033e 100644 --- a/src/Chainweb/Pact5/SPV.hs +++ b/src/Chainweb/Pact/SPV.hs @@ -6,7 +6,7 @@ , TypeApplications #-} -module Chainweb.Pact5.SPV (pactSPV) where +module Chainweb.Pact.SPV (pactSPV) where import Chainweb.BlockHeader (BlockHeader, blockHash, blockHeight) import Chainweb.BlockHeaderDB (BlockHeaderDb) diff --git a/src/Chainweb/Pact/Service/BlockValidation.hs b/src/Chainweb/Pact/Service/BlockValidation.hs deleted file mode 100644 index 8f548c48b8..0000000000 --- a/src/Chainweb/Pact/Service/BlockValidation.hs +++ /dev/null @@ -1,156 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} - --- | --- Module : Chainweb.Pact.Service.BlockValidation --- Copyright : Copyright © 2018 Kadena LLC. --- License : (see the file LICENSE) --- Maintainer : Emily Pillmore --- Stability : experimental --- --- The block validation logic for Pact Service --- --- This exists due to moving things around resolving --- chainweb dependencies. This should find a new home. --- -module Chainweb.Pact.Service.BlockValidation -( validateBlock -, newBlock -, continueBlock -, local -, lookupPactTxs -, pactPreInsertCheck -, pactBlockTxHistory -, pactHistoricalLookup -, pactSyncToBlock -, pactReadOnlyReplay -) where - - -import Data.Vector (Vector) -import Data.HashMap.Strict (HashMap) - -import qualified Pact.Core.Persistence as Pact5 - -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Mempool.Mempool (InsertError) -import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Types -import Chainweb.Payload -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils -import Chainweb.Version -import Data.ByteString.Short (ShortByteString) -import qualified Pact.Core.Names as Pact5 -import qualified Pact.Core.Builtin as Pact5 -import qualified Pact.Core.Evaluate as Pact5 -import qualified Pact.Types.ChainMeta as Pact4 -import Data.Text (Text) -import qualified Pact.Types.Command as Pact4 - -newBlock :: NewBlockFill -> ParentHeader -> PactQueue -> IO (Historical (ForSomePactVersion BlockInProgress)) -newBlock fill parent reqQ = do - let - !msg = NewBlockMsg NewBlockReq - { _newBlockFill = fill - , _newBlockParent = parent - } - submitRequestAndWait reqQ msg - -continueBlock :: BlockInProgress pv -> PactQueue -> IO (Historical (BlockInProgress pv)) -continueBlock bip reqQ = do - let !msg = ContinueBlockMsg (ContinueBlockReq bip) - submitRequestAndWait reqQ msg - -validateBlock - :: BlockHeader - -> CheckablePayload - -> PactQueue - -> IO PayloadWithOutputs -validateBlock bHeader payload reqQ = do - let !msg = ValidateBlockMsg ValidateBlockReq - { _valBlockHeader = bHeader - , _valCheckablePayload = payload - } - submitRequestAndWait reqQ msg - -local - :: Maybe LocalPreflightSimulation - -> Maybe LocalSignatureVerification - -> Maybe RewindDepth - -> Pact4.UnparsedTransaction - -> PactQueue - -> IO LocalResult -local preflight sigVerify rd ct reqQ = do - let !msg = LocalMsg LocalReq - { _localRequest = ct - , _localPreflight = preflight - , _localSigVerification = sigVerify - , _localRewindDepth = rd - } - submitRequestAndWait reqQ msg - -lookupPactTxs - :: Maybe ConfirmationDepth - -> Vector ShortByteString - -> PactQueue - -> IO (HashMap ShortByteString (T2 BlockHeight BlockHash)) -lookupPactTxs confDepth txs reqQ = do - let !req = LookupPactTxsReq confDepth txs - let !msg = LookupPactTxsMsg req - submitRequestAndWait reqQ msg - -pactReadOnlyReplay - :: BlockHeader - -> Maybe BlockHeader - -> PactQueue - -> IO () -pactReadOnlyReplay l u reqQ = do - let !msg = ReadOnlyReplayMsg ReadOnlyReplayReq - { _readOnlyReplayLowerBound = l - , _readOnlyReplayUpperBound = u - } - submitRequestAndWait reqQ msg - -pactPreInsertCheck - :: Vector (Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text)) - -> PactQueue - -> IO (Vector (Maybe InsertError)) -pactPreInsertCheck txs reqQ = do - let !req = PreInsertCheckReq txs - let !msg = PreInsertCheckMsg req - submitRequestAndWait reqQ msg - -pactBlockTxHistory - :: BlockHeader - -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info - -> PactQueue - -> IO (Historical BlockTxHistory) -pactBlockTxHistory bh d reqQ = do - let !req = BlockTxHistoryReq bh d - let !msg = BlockTxHistoryMsg req - submitRequestAndWait reqQ msg - -pactHistoricalLookup - :: BlockHeader - -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info - -> Pact5.RowKey - -> PactQueue - -> IO (Historical (Maybe (Pact5.TxLog Pact5.RowData))) -pactHistoricalLookup bh d k reqQ = do - let !req = HistoricalLookupReq bh d k - let !msg = HistoricalLookupMsg req - submitRequestAndWait reqQ msg - -pactSyncToBlock - :: BlockHeader - -> PactQueue - -> IO () -pactSyncToBlock bh reqQ = do - let !msg = SyncToBlockMsg SyncToBlockReq - { _syncToBlockHeader = bh - } - submitRequestAndWait reqQ msg diff --git a/src/Chainweb/Pact/Service/PactInProcApi.hs b/src/Chainweb/Pact/Service/PactInProcApi.hs index 490b3e5fa9..9678b994a6 100644 --- a/src/Chainweb/Pact/Service/PactInProcApi.hs +++ b/src/Chainweb/Pact/Service/PactInProcApi.hs @@ -46,7 +46,7 @@ import Chainweb.Pact.Types import qualified Chainweb.Pact.PactService as PS import Chainweb.Pact.Service.PactQueue import Chainweb.Payload.PayloadStore -import qualified Chainweb.Pact4.Transaction as Pact4 +import qualified Chainweb.Pact.Transaction as Pact4 import Chainweb.Utils import Chainweb.Version diff --git a/src/Chainweb/Pact/Service/PactQueue.hs b/src/Chainweb/Pact/Service/PactQueue.hs deleted file mode 100644 index a489973d6e..0000000000 --- a/src/Chainweb/Pact/Service/PactQueue.hs +++ /dev/null @@ -1,240 +0,0 @@ -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE ViewPatterns #-} - --- | --- Module: Chainweb.Pact.Service.PactQueue --- Copyright: Copyright © 2022 Kadena LLC. --- License: See LICENSE file --- Maintainer: Mark Nichols --- Stability: experimental --- --- Pact execution service queue for Chainweb --- -module Chainweb.Pact.Service.PactQueue -( addRequest -, cancelSubmittedRequest -, waitForSubmittedRequest -, submitRequestAnd -, submitRequestAndWait -, getNextRequest -, getPactQueueStats -, newPactQueue -, resetPactQueueStats -, PactQueue -, PactQueueStats(..) -) where - -import Control.Applicative -import Control.Concurrent.Async -import Control.Concurrent.STM -import Control.DeepSeq (NFData) -import Control.Exception.Safe -import Control.Monad - -import Data.Aeson -import Data.IORef - -import GHC.Generics - -import Numeric.Natural - --- internal modules - -import Chainweb.Pact.Types -import Chainweb.Time -import Chainweb.Utils - --- -------------------------------------------------------------------------- -- --- Pact Queue - --- | A Pact Queue with different priorities for block validation, new block, and --- other requests. --- -data PactQueue = PactQueue - { _pactQueueValidateBlock :: !(TBQueue (T2 SubmittedRequestMsg (Time Micros))) - , _pactQueueNewBlock :: !(TBQueue (T2 SubmittedRequestMsg (Time Micros))) - , _pactQueueOtherMsg :: !(TBQueue (T2 SubmittedRequestMsg (Time Micros))) - , _pactQueuePactQueueValidateBlockMsgCounters :: !(IORef PactQueueCounters) - , _pactQueuePactQueueNewBlockMsgCounters :: !(IORef PactQueueCounters) - , _pactQueuePactQueueOtherMsgCounters :: !(IORef PactQueueCounters) - } - -initPactQueueCounters :: PactQueueCounters -initPactQueueCounters = PactQueueCounters - { _pactQueueCountersCount = 0 - , _pactQueueCountersSum = 0 - , _pactQueueCountersMin = maxBound - , _pactQueueCountersMax = 0 - } - -newPactQueue :: Natural -> IO PactQueue -newPactQueue sz = PactQueue - <$> newTBQueueIO sz - <*> newTBQueueIO sz - <*> newTBQueueIO sz - <*> newIORef initPactQueueCounters - <*> newIORef initPactQueueCounters - <*> newIORef initPactQueueCounters - --- | Add a request to the Pact execution queue --- -addRequest :: PactQueue -> RequestMsg r -> IO (TVar (RequestStatus r)) -addRequest q msg = do - statusRef <- newTVarIO RequestNotStarted - let submittedReq = SubmittedRequestMsg msg statusRef - entranceTime <- getCurrentTimeIntegral - atomically $ writeTBQueue priority (T2 submittedReq entranceTime) - return statusRef - where - priority = case msg of - ValidateBlockMsg {} -> _pactQueueValidateBlock q - NewBlockMsg {} -> _pactQueueNewBlock q - _ -> _pactQueueOtherMsg q - --- | Cancel a request that's already been submitted to the Pact queue. --- -cancelSubmittedRequest :: TVar (RequestStatus r) -> IO () -cancelSubmittedRequest statusRef = atomically $ do - status <- readTVar statusRef - case status of - RequestFailed _ -> return () - RequestInProgress -> writeTVar statusRef (RequestFailed (toException AsyncCancelled)) - RequestDone _ -> return () - RequestNotStarted -> writeTVar statusRef (RequestFailed (toException AsyncCancelled)) - --- | Block waiting for the result of a request that's already been submitted --- to the Pact queue. --- -waitForSubmittedRequest :: TVar (RequestStatus r) -> IO r -waitForSubmittedRequest statusRef = atomically $ do - status <- readTVar statusRef - case status of - RequestFailed e -> throwIO e - RequestInProgress -> retry - RequestDone r -> return r - RequestNotStarted -> retry - --- | Submit a request and give a handle on its status to the continuation. --- When the continuation terminates, *cancel the request*. --- -submitRequestAnd :: PactQueue -> RequestMsg r -> (TVar (RequestStatus r) -> IO a) -> IO a -submitRequestAnd q msg k = uninterruptibleMask $ \restore -> do - status <- addRequest q msg - restore (k status) `onException` - uninterruptibleMask_ (cancelSubmittedRequest status) - --- | Submit a request and wait for it to finish; if interrupted by an --- asynchronous exception, *cancel the request*. --- -submitRequestAndWait :: PactQueue -> RequestMsg r -> IO r -submitRequestAndWait q msg = submitRequestAnd q msg waitForSubmittedRequest - --- | Get the next available request from the Pact execution queue --- -getNextRequest :: PactQueue -> IO SubmittedRequestMsg -getNextRequest q = do - T2 req entranceTime <- atomically - $ tryReadTBQueueOrRetry (_pactQueueValidateBlock q) - <|> tryReadTBQueueOrRetry (_pactQueueNewBlock q) - <|> tryReadTBQueueOrRetry (_pactQueueOtherMsg q) - requestTime <- diff <$> getCurrentTimeIntegral <*> pure entranceTime - updatePactQueueCounters (counters req q) requestTime - return req - where - tryReadTBQueueOrRetry = tryReadTBQueue >=> \case - Nothing -> retry - Just msg -> return msg - - counters (SubmittedRequestMsg ValidateBlockMsg{} _) = _pactQueuePactQueueValidateBlockMsgCounters - counters (SubmittedRequestMsg NewBlockMsg{} _) = _pactQueuePactQueueNewBlockMsgCounters - counters _ = _pactQueuePactQueueOtherMsgCounters - --- -------------------------------------------------------------------------- -- --- Pact Queue Telemetry - --- | Counters for one Pact queue priority --- -data PactQueueCounters = PactQueueCounters - { _pactQueueCountersCount :: {-# UNPACK #-} !Int - , _pactQueueCountersSum :: {-# UNPACK #-} !Micros - , _pactQueueCountersMin :: {-# UNPACK #-} !Micros - , _pactQueueCountersMax :: {-# UNPACK #-} !Micros - } - deriving (Show, Generic) - deriving anyclass NFData - -instance ToJSON PactQueueCounters where - toJSON = object . pactQueueCountersProperties - toEncoding = pairs . mconcat . pactQueueCountersProperties - {-# INLINE toJSON #-} - {-# INLINE toEncoding #-} - -pactQueueCountersProperties :: KeyValue e kv => PactQueueCounters -> [kv] -pactQueueCountersProperties pqc = - [ "count" .= _pactQueueCountersCount pqc - , "sum" .= _pactQueueCountersSum pqc - , "min" .= _pactQueueCountersMin pqc - , "max" .= _pactQueueCountersMax pqc - , "avg" .= avg - ] - where - avg :: Maybe Double - avg = if _pactQueueCountersCount pqc == 0 - then Nothing - else Just $ fromIntegral (_pactQueueCountersSum pqc) / fromIntegral (_pactQueueCountersCount pqc) -{-# INLINE pactQueueCountersProperties #-} - -updatePactQueueCounters :: IORef PactQueueCounters -> TimeSpan Micros -> IO () -updatePactQueueCounters countersRef (timeSpanToMicros -> timespan) = do - atomicModifyIORef' countersRef $ \ctrs -> - ( PactQueueCounters - { _pactQueueCountersCount = _pactQueueCountersCount ctrs + 1 - , _pactQueueCountersSum = _pactQueueCountersSum ctrs + timespan - , _pactQueueCountersMin = _pactQueueCountersMin ctrs `min` timespan - , _pactQueueCountersMax = _pactQueueCountersMax ctrs `max` timespan - } - , () - ) - --- | Statistics for all Pact queue priorities --- -data PactQueueStats = PactQueueStats - { _validateblock :: !PactQueueCounters - , _newblock :: !PactQueueCounters - , _othermsg :: !PactQueueCounters - } - deriving (Generic, NFData) - -instance ToJSON PactQueueStats where - toJSON = object . pactQueueStatsProperties - toEncoding = pairs . mconcat . pactQueueStatsProperties - {-# INLINE toJSON #-} - {-# INLINE toEncoding #-} - -pactQueueStatsProperties :: KeyValue e kv => PactQueueStats -> [kv] -pactQueueStatsProperties o = - [ "validate" .= _validateblock o - , "newblock" .= _newblock o - , "other" .= _othermsg o - ] -{-# INLINE pactQueueStatsProperties #-} - -resetPactQueueStats :: PactQueue -> IO () -resetPactQueueStats q = do - reset (_pactQueuePactQueueValidateBlockMsgCounters q) - reset (_pactQueuePactQueueNewBlockMsgCounters q) - reset (_pactQueuePactQueueOtherMsgCounters q) - where - reset ref = atomicWriteIORef ref initPactQueueCounters - -getPactQueueStats :: PactQueue -> IO PactQueueStats -getPactQueueStats q = PactQueueStats - <$> readIORef (_pactQueuePactQueueValidateBlockMsgCounters q) - <*> readIORef (_pactQueuePactQueueNewBlockMsgCounters q) - <*> readIORef (_pactQueuePactQueueOtherMsgCounters q) diff --git a/src/Chainweb/Pact5/Templates.hs b/src/Chainweb/Pact/Templates.hs similarity index 97% rename from src/Chainweb/Pact5/Templates.hs rename to src/Chainweb/Pact/Templates.hs index b2540e418f..8e588671ba 100644 --- a/src/Chainweb/Pact5/Templates.hs +++ b/src/Chainweb/Pact/Templates.hs @@ -6,7 +6,7 @@ {-# LANGUAGE ScopedTypeVariables #-} -- | --- Module : Chainweb.Pact5.Templates +-- Module : Chainweb.Pact.Templates -- Copyright : Copyright © 2010 Kadena LLC. -- License : (see the file LICENSE) -- Maintainer : Stuart Popejoy @@ -14,7 +14,7 @@ -- -- Prebuilt Term templates for automated operations (coinbase, gas buy) -- -module Chainweb.Pact5.Templates +module Chainweb.Pact.Templates ( mkFundTxTerm , mkBuyGasTerm , mkRedeemGasTerm @@ -39,7 +39,7 @@ import Chainweb.Utils (decodeOrThrow) import Pact.Core.StableEncoding (StableEncoding(_stableEncoding)) import Control.Exception.Safe (impureThrow) import qualified Pact.Types.KeySet as Pact4 -import Chainweb.Pact5.Types +import Chainweb.Pact.Types fundTxTemplate :: Text -> Text -> Expr () fundTxTemplate sender mid = diff --git a/src/Chainweb/Pact/Transaction.hs b/src/Chainweb/Pact/Transaction.hs new file mode 100644 index 0000000000..fd78d7618a --- /dev/null +++ b/src/Chainweb/Pact/Transaction.hs @@ -0,0 +1,109 @@ +{-# language DeriveAnyClass #-} +{-# language DeriveFunctor #-} +{-# language DeriveGeneric #-} +{-# language DerivingStrategies #-} +{-# language FlexibleContexts #-} +{-# language ImportQualifiedPost #-} +{-# language LambdaCase #-} +{-# language OverloadedStrings #-} +{-# language PackageImports #-} +{-# language ScopedTypeVariables #-} +{-# language TypeApplications #-} + +module Chainweb.Pact.Transaction + ( Transaction + , PayloadWithText + , payloadBytes + , payloadObj + , payloadCodec + , parseCommand + , HashableTransaction(..) + ) where + +import "aeson" Data.Aeson qualified as Aeson +import "base" Data.Function +import "base" GHC.Generics (Generic) +import "bytestring" Data.ByteString.Char8 (ByteString) +import "bytestring" Data.ByteString.Short qualified as SB +import "deepseq" Control.DeepSeq +import "lens" Control.Lens +import "pact-json" Pact.JSON.Encode qualified as J +import "pact-json" Pact.JSON.Encode (Encode(..)) +import "pact-tng" Pact.Core.ChainData +import "pact-tng" Pact.Core.Command.Types +import "pact-tng" Pact.Core.StableEncoding +import "pact-tng" Pact.Core.Errors +import "pact-tng" Pact.Core.Info +import "pact-tng" Pact.Core.Pretty qualified as Pact5 +import "text" Data.Text (Text) +import "text" Data.Text.Encoding (decodeUtf8, encodeUtf8) +import Chainweb.Utils +import Data.Hashable + +type Transaction = Command (PayloadWithText PublicMeta ParsedCode) + +data PayloadWithText meta code = UnsafePayloadWithText + { _payloadBytes :: !SB.ShortByteString + , _payloadObj :: !(Payload meta code) + } + deriving stock (Show, Generic) + deriving stock (Functor) + deriving anyclass (NFData) + +instance Eq (PayloadWithText meta code) where + (==) = (==) `on` _payloadBytes + +instance (J.Encode meta, J.Encode code) => J.Encode (PayloadWithText meta code) where + build p = J.object + [ "payloadBytes" J..= J.encodeText (decodeUtf8 $ SB.fromShort $ _payloadBytes p) + , "payloadObject" J..= _payloadObj p + ] + +payloadBytes :: Getter (PayloadWithText meta code) SB.ShortByteString +payloadBytes = to _payloadBytes +{-# inline conlike payloadBytes #-} + +payloadObj :: Getter (PayloadWithText meta code) (Payload meta code) +payloadObj = to _payloadObj +{-# inline conlike payloadObj #-} + +-- | A codec for Pact5's (Command PayloadWithText) transactions. +-- +payloadCodec + :: Codec (Command (PayloadWithText PublicMeta ParsedCode)) +payloadCodec = Codec enc dec + where + enc c = J.encodeStrict $ fmap (decodeUtf8 . encodePayload) c + dec bs = case Aeson.decodeStrict' bs of + -- Note: this can only ever emit a `ParseError`, which by default are quite small. + -- Still, `pretty` instances are scary, but this cannot make it into block outputs so this should + -- be okay + Just (cmd :: Command Text) -> over _Left Pact5.renderCompactString $ parseCommand cmd + Nothing -> Left "decode PayloadWithText failed" + +parseCommand :: Command Text -> Either (PactError SpanInfo) (Command (PayloadWithText PublicMeta ParsedCode)) +parseCommand cmd = do + let cmd' = fmap encodeUtf8 cmd + let code = SB.toShort (_cmdPayload cmd') + parsedCmd <- over (_Right . cmdPayload . pMeta) _stableEncoding $ unsafeParseCommand cmd' + return + (parsedCmd & cmdPayload %~ \obj -> UnsafePayloadWithText { _payloadBytes = code, _payloadObj = obj }) + +encodePayload :: PayloadWithText meta code -> ByteString +encodePayload = SB.fromShort . _payloadBytes + +newtype HashableTransaction = HashableTransaction Transaction + deriving Eq + +instance Hashable HashableTransaction where + hashWithSalt salt (HashableTransaction tx) = hashWithSalt salt (_cmdHash tx) + +-- decodePayload +-- :: ByteString +-- -> Either String PayloadWithText +-- decodePayload bs = case Aeson.decodeStrict' bs of +-- Just (payload :: Payload (StableEncoding PublicMeta) Text) -> do +-- p <- traverse parseCode $ +-- over pMeta _stableEncoding payload +-- return $! PayloadWithText (SB.toShort bs) p +-- Nothing -> Left "decoding Payload failed" diff --git a/src/Chainweb/Pact5/TransactionExec.hs b/src/Chainweb/Pact/TransactionExec.hs similarity index 95% rename from src/Chainweb/Pact5/TransactionExec.hs rename to src/Chainweb/Pact/TransactionExec.hs index 2d3baf47d1..628d97527f 100644 --- a/src/Chainweb/Pact5/TransactionExec.hs +++ b/src/Chainweb/Pact/TransactionExec.hs @@ -26,7 +26,7 @@ -- -- Pact command execution and coin-contract transaction logic for Chainweb -- -module Chainweb.Pact5.TransactionExec +module Chainweb.Pact.TransactionExec ( -- * Public API TransactionM(..) , TransactionEnv(..) @@ -104,11 +104,10 @@ import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Miner.Pact import Chainweb.Pact.Types -import Chainweb.Pact5.Templates -import Chainweb.Pact5.Types +import Chainweb.Pact.Templates import Chainweb.Time -import Chainweb.Pact5.Transaction +import Chainweb.Pact.Transaction import Chainweb.VerifierPlugin hiding (chargeGas) import Chainweb.Utils import Chainweb.Version as V @@ -131,6 +130,7 @@ import qualified Pact.Types.Capability as Pact4 import qualified Pact.Types.Names as Pact4 import qualified Pact.Types.Runtime as Pact4 import qualified Pact.Core.Errors as Pact5 +import qualified Pact.Core.Evaluate as Pact5 -- Note [Throw out verifier proofs eagerly] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -329,7 +329,7 @@ applyCmd -- ^ initial gas cost -> Command (Payload PublicMeta ParsedCode) -- ^ command with payload to execute - -> IO (Either Pact5GasPurchaseFailure (CommandResult [TxLog ByteString] (Pact5.PactError Info))) + -> IO (Either TxInvalidError (CommandResult [TxLog ByteString] (Pact5.PactError Info))) applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do logDebug_ logger $ "applyCmd: " <> sshow (_cmdHash cmd) let flags = Set.fromList @@ -340,7 +340,6 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do ] `Set.union` guardDisablePact51Flags txCtx let gasLogsEnabled = maybe GasLogsDisabled (const GasLogsEnabled) maybeGasLogger gasEnv <- mkTableGasEnv (MilliGasLimit $ gasToMilliGas $ gasLimit ^. _GasLimit) gasLogsEnabled - let !requestKey = cmdToRequestKey cmd -- this process is "paid for", i.e. it's powered by a supply of gas that was -- purchased by a user already. any errors here will result in the entire gas -- supply being paid to the miner and the transaction failing, but still going @@ -361,11 +360,11 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do eBuyGasResult <- do if GasLimit initialGas > gasLimit then do - pure $ Left (PurchaseGasTxTooBigForGasLimit requestKey) + pure $ Left PurchaseGasTxTooBigForGasLimit else do buyGas logger gasEnv db txCtx cmd >>= \case Left buyGasError -> do - pure $ Left (BuyGasError requestKey buyGasError) + pure $ Left (BuyGasError buyGasError) Right buyGasResult -> do pure $ Right buyGasResult @@ -389,7 +388,7 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do cmd case eRedeemGasResult of Left redeemGasError -> do - pure (Left (RedeemGasError requestKey redeemGasError)) + pure (Left (RedeemGasError redeemGasError)) Right redeemGasResult -> do return $ Right $ CommandResult { _crReqKey = RequestKey $ _cmdHash cmd @@ -414,7 +413,7 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do case eRedeemGasResult of Left redeemGasError -> do - pure (Left (RedeemGasError requestKey redeemGasError)) + pure (Left (RedeemGasError redeemGasError)) Right redeemGasResult -> do -- ensure we include the events and logs from buyGas and redeemGas in the result return $ Right $ CommandResult @@ -437,18 +436,17 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do -- hash and blocktime data. -- ctxToPublicData :: PublicMeta -> TxContext -> PublicData -ctxToPublicData pm (TxContext ph _) = PublicData +ctxToPublicData pm ctx = PublicData { _pdPublicMeta = pm , _pdBlockHeight = bh , _pdBlockTime = bt , _pdPrevBlockHash = toText h } where - bheader = _parentHeader ph - BlockHeight !bh = succ $ view blockHeight bheader - BlockCreationTime (Time (TimeSpan (Micros !bt))) = - view blockCreationTime bheader - BlockHash h = view blockHash bheader + BlockHeight !bh = succ $ unwrapParent $ _tcParentHeight ctx + Parent (BlockCreationTime (Time (TimeSpan (Micros !bt)))) = + _tcParentCreationTime ctx + Parent (BlockHash h) = _tcParentHash ctx -- | 'applyCoinbase' performs upgrade transactions and constructs and executes -- a transaction which pays miners their block reward. @@ -462,7 +460,7 @@ applyCoinbase -- ^ Miner reward -> TxContext -- ^ tx metadata and parent header - -> IO (Either Pact5CoinbaseError (CommandResult [TxLog ByteString] Void)) + -> IO (Either (Pact5.PactError Pact5.Info) (CommandResult [TxLog ByteString] Void)) applyCoinbase logger db reward txCtx = do -- for some reason this is the base64-encoded hash, rather than the binary hash let coinbaseHash = Hash $ SB.toShort $ T.encodeUtf8 $ blockHashToText parentBlockHash @@ -488,7 +486,7 @@ applyCoinbase logger db reward txCtx = do (noSpanInfo <$ coinbaseTerm) case eCoinbaseTxResult of Left err -> do - pure $ Left $ CoinbasePactError err + pure $ Left err Right coinbaseTxResult -> do return $! Right $! CommandResult { _crReqKey = RequestKey coinbaseHash @@ -504,7 +502,7 @@ applyCoinbase logger db reward txCtx = do } where - parentBlockHash = view blockHash $ _parentHeader $ _tcParentHeader txCtx + parentBlockHash = unwrapParent $ _tcParentHash txCtx Miner mid mks = _tcMiner txCtx -- | Apply (forking) upgrade transactions and module cache updates @@ -523,9 +521,7 @@ applyUpgrades -> TxContext -> IO () applyUpgrades logger db txCtx - | Just Pact4Upgrade{} <- - v ^? versionUpgrades . atChain cid . ix currentHeight = error "Expected Pact 4 upgrade, got Pact 5" - | Just Pact5Upgrade{_pact5UpgradeTransactions = upgradeTxs} <- + | Just PactUpgrade{_pactUpgradeTransactions = upgradeTxs} <- v ^? versionUpgrades . atChain cid . ix currentHeight = applyUpgrade upgradeTxs | otherwise = return () where @@ -687,7 +683,7 @@ runUpgrade _logger db txContext cmd = case payload ^. pPayload of & csSlots .~ [CapSlot (CapToken (QualifiedName "REMEDIATE" (ModuleName "coin" Nothing)) []) []] & csModuleAdmin .~ S.singleton (ModuleName "coin" Nothing)) (fmap (noSpanInfo <$) $ _pcExps $ _pmCode pm) >>= \case - Left err -> internalError $ "Pact5.runGenesis: internal error " <> sshow err + Left err -> error $ "Pact5.runGenesis: internal error " <> sshow err -- TODO: we should probably put these events somewhere! Right _r -> return () Continuation _ -> error "runGenesisCore Continuation not supported" @@ -733,7 +729,7 @@ buyGas -> PactDb CoreBuiltin Info -> TxContext -> Command (Payload PublicMeta ParsedCode) - -> IO (Either Pact5BuyGasError EvalResult) + -> IO (Either BuyGasError EvalResult) buyGas logger origGasEnv db txCtx cmd = do let gasEnv = origGasEnv & geGasModel . gmGasLimit .~ Just (MilliGasLimit (MilliGas 1_500_000)) logFunctionText logger L.Debug $ @@ -804,10 +800,10 @@ buyGas logger origGasEnv db txCtx cmd = do | otherwise -> -- should never occur pre-chainweb 2.24: -- would mean coin.fund-tx is not a pact - internalError "buyGas: Internal error - empty continuation before 2.24 fork" + error "buyGas: Internal error - empty continuation before 2.24 fork" Just _pe | isChainweb224Pact -> - internalError "buyGas: Internal error - continuation found after 2.24 fork" + error "buyGas: Internal error - continuation found after 2.24 fork" | otherwise -> return er' Left err -> do @@ -842,7 +838,7 @@ redeemGas :: (Logger logger) -> Gas -> Maybe DefPactId -> Command (Payload PublicMeta ParsedCode) - -> IO (Either Pact5RedeemGasError EvalResult) + -> IO (Either RedeemGasError EvalResult) redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd | isChainweb224Pact, Nothing <- maybeFundTxPactId = do logFunctionText logger L.Debug $ @@ -898,7 +894,7 @@ redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd return $ Right evalResult | otherwise = - internalError "redeemGas: Internal error - defpact ID does not match chainweb224Pact flag" + error "redeemGas: Internal error - defpact ID does not match chainweb224Pact flag" where Hash chash = _cmdHash cmd diff --git a/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs deleted file mode 100644 index 41ddd3695f..0000000000 --- a/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs +++ /dev/null @@ -1,19 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.CoinV3Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiRkd0RlNjcW1neklEQzlENkUwSUtQSFN0ZDhPdW9JdVhRanp4TFdyWTBZayIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgOyB2MyBjYXBhYmlsaXRpZXNcXG4gIChkZWZjYXAgUkVMRUFTRV9BTExPQ0FUSU9OXFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGZvciBhbGxvY2F0aW9uIHJlbGVhc2UsIGNhbiBiZSB1c2VkIGZvciBzaWcgc2NvcGluZy5cXFwiXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJndWFyZFxcXCIgOj0gb2xkLWd1YXJkIH1cXG5cXG4gICAgICAgIChlbmZvcmNlLWd1YXJkIG9sZC1ndWFyZClcXG5cXG4gICAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgIHsgXFxcImd1YXJkXFxcIiA6IG5ldy1ndWFyZCB9XFxuICAgICAgICAgICkpKVxcbiAgICApXFxuXFxuXFxuICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgKClcXG4gICAgTUlOSU1VTV9QUkVDSVNJT04pXFxuXFxuICAoZGVmdW4gdHJhbnNmZXI6c3RyaW5nIChzZW5kZXI6c3RyaW5nIHJlY2VpdmVyOnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBzZW5kZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgcmVjZWl2ZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpIF1cXG5cXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcilcXG4gICAgICBcXFwic2VuZGVyIGNhbm5vdCBiZSB0aGUgcmVjZWl2ZXIgb2YgYSB0cmFuc2ZlclxcXCIpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcInRyYW5zZmVyIGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFRSQU5TRkVSIHNlbmRlciByZWNlaXZlciBhbW91bnQpXFxuICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIHJlY2VpdmVyXFxuICAgICAgICB7IFxcXCJndWFyZFxcXCIgOj0gZyB9XFxuXFxuICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIGcgYW1vdW50KSlcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgYW1vdW50OmRlY2ltYWwgKVxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpIF1cXG5cXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcilcXG4gICAgICBcXFwic2VuZGVyIGNhbm5vdCBiZSB0aGUgcmVjZWl2ZXIgb2YgYSB0cmFuc2ZlclxcXCIpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcInRyYW5zZmVyIGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFRSQU5TRkVSIHNlbmRlciByZWNlaXZlciBhbW91bnQpXFxuICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgKGNyZWRpdCByZWNlaXZlciByZWNlaXZlci1ndWFyZCBhbW91bnQpKVxcbiAgICApXFxuXFxuICAoZGVmdW4gY29pbmJhc2U6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhY2NvdW50LWd1YXJkOmd1YXJkIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJJbnRlcm5hbCBmdW5jdGlvbiBmb3IgdGhlIGluaXRpYWwgY3JlYXRpb24gb2YgY29pbnMuICBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgXFxcXGNhbm5vdCBiZSB1c2VkIG91dHNpZGUgb2YgdGhlIGNvaW4gY29udHJhY3QuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENPSU5CQVNFKSlcXG4gICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIGFjY291bnQgYW1vdW50KSkgO3YzXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIGFjY291bnQgYW1vdW50KSkgO3YzXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuXFxuICAgICAgKGVuZm9yY2UgKDw9IGFtb3VudCBiYWxhbmNlKSBcXFwiSW5zdWZmaWNpZW50IGZ1bmRzXFxcIilcXG5cXG4gICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAoLSBiYWxhbmNlIGFtb3VudCkgfVxcbiAgICAgICAgKSlcXG4gICAgKVxcblxcbiAgKGRlZnBhY3QgZnVuZC10eCAoc2VuZGVyOnN0cmluZyBtaW5lcjpzdHJpbmcgbWluZXItZ3VhcmQ6Z3VhcmQgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiJ2Z1bmQtdHgnIGlzIGEgc3BlY2lhbCBwYWN0IHRvIGZ1bmQgYSB0cmFuc2FjdGlvbiBpbiB0d28gc3RlcHMsICAgICBcXFxcXFxuICAgIFxcXFx3aXRoIHRoZSBhY3R1YWwgdHJhbnNhY3Rpb24gdHJhbnNwaXJpbmcgaW4gdGhlIG1pZGRsZTogICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFxcXFxcXG4gICAgXFxcXCAgMSkgQSBidXlpbmcgcGhhc2UsIGRlYml0aW5nIHRoZSBzZW5kZXIgZm9yIHRvdGFsIGdhcyBhbmQgZmVlLCB5aWVsZGluZyBcXFxcXFxuICAgIFxcXFwgICAgIFRYX01BWF9DSEFSR0UuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAyKSBBIHNldHRsZW1lbnQgcGhhc2UsIHJlc3VtaW5nIFRYX01BWF9DSEFSR0UsIGFuZCBhbGxvY2F0aW5nIHRvIHRoZSAgIFxcXFxcXG4gICAgXFxcXCAgICAgY29pbmJhc2UgYWNjb3VudCBmb3IgdXNlZCBnYXMgYW5kIGZlZSwgYW5kIHNlbmRlciBhY2NvdW50IGZvciBiYWwtICBcXFxcXFxuICAgIFxcXFwgICAgIGFuY2UgKHVudXNlZCBnYXMsIGlmIGFueSkuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICAgIDsocHJvcGVydHkgY29uc2VydmVzLW1hc3MpIG5vdCBzdXBwb3J0ZWQgeWV0XFxuICAgICAgICAgICBdXFxuXFxuICAgIChzdGVwIChidXktZ2FzIHNlbmRlciB0b3RhbCkpXFxuICAgIChzdGVwIChyZWRlZW0tZ2FzIG1pbmVyIG1pbmVyLWd1YXJkIHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZWJpdDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJEZWJpdCBBTU9VTlQgZnJvbSBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJkZWJpdCBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChERUJJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICAoZGVmdW4gY3JlZGl0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkNyZWRpdCBBTU9VTlQgdG8gQUNDT1VOVCBiYWxhbmNlXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcImNyZWRpdCBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDUkVESVQgYWNjb3VudCkpXFxuICAgICh3aXRoLWRlZmF1bHQtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IC0xLjAsIFxcXCJndWFyZFxcXCIgOiBndWFyZCB9XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSwgXFxcImd1YXJkXFxcIiA6PSByZXRnIH1cXG4gICAgICA7IHdlIGRvbid0IHdhbnQgdG8gb3ZlcndyaXRlIGFuIGV4aXN0aW5nIGd1YXJkIHdpdGggdGhlIHVzZXItc3VwcGxpZWQgb25lXFxuICAgICAgKGVuZm9yY2UgKD0gcmV0ZyBndWFyZClcXG4gICAgICAgIFxcXCJhY2NvdW50IGd1YXJkcyBkbyBub3QgbWF0Y2hcXFwiKVxcblxcbiAgICAgIChsZXQgKChpcy1uZXdcXG4gICAgICAgICAgICAgKGlmICg9IGJhbGFuY2UgLTEuMClcXG4gICAgICAgICAgICAgICAgIChlbmZvcmNlLXJlc2VydmVkIGFjY291bnQgZ3VhcmQpXFxuICAgICAgICAgICAgICAgZmFsc2UpKSlcXG5cXG4gICAgICAgICh3cml0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAoaWYgaXMtbmV3IGFtb3VudCAoKyBiYWxhbmNlIGFtb3VudCkpXFxuICAgICAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogcmV0Z1xcbiAgICAgICAgICB9KSlcXG4gICAgICApKVxcblxcbiAgKGRlZnVuIGNoZWNrLXJlc2VydmVkOnN0cmluZyAoYWNjb3VudDpzdHJpbmcpXFxuICAgIFxcXCIgQ2hlY2tzIEFDQ09VTlQgZm9yIHJlc2VydmVkIG5hbWUgYW5kIHJldHVybnMgdHlwZSBpZiBcXFxcXFxuICAgIFxcXFwgZm91bmQgb3IgZW1wdHkgc3RyaW5nLiBSZXNlcnZlZCBuYW1lcyBzdGFydCB3aXRoIGEgXFxcXFxcbiAgICBcXFxcIHNpbmdsZSBjaGFyIGFuZCBjb2xvbiwgZS5nLiAnYzpmb28nLCB3aGljaCB3b3VsZCByZXR1cm4gJ2MnIGFzIHR5cGUuXFxcIlxcbiAgICAobGV0ICgocGZ4ICh0YWtlIDIgYWNjb3VudCkpKVxcbiAgICAgIChpZiAoPSBcXFwiOlxcXCIgKHRha2UgLTEgcGZ4KSkgKHRha2UgMSBwZngpIFxcXCJcXFwiKSkpXFxuXFxuICAoZGVmdW4gZW5mb3JjZS1yZXNlcnZlZDpib29sIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQGRvYyBcXFwiRW5mb3JjZSByZXNlcnZlZCBhY2NvdW50IG5hbWUgcHJvdG9jb2xzLlxcXCJcXG4gICAgKGxldCAoKHIgKGNoZWNrLXJlc2VydmVkIGFjY291bnQpKSlcXG4gICAgICAoaWYgKD0gXFxcIlxcXCIgcikgdHJ1ZVxcbiAgICAgICAgKGlmICg9IFxcXCJrXFxcIiByKVxcbiAgICAgICAgICAoZW5mb3JjZVxcbiAgICAgICAgICAgICg9IChmb3JtYXQgXFxcInt9XFxcIiBbZ3VhcmRdKVxcbiAgICAgICAgICAgICAgIChmb3JtYXQgXFxcIktleVNldCB7a2V5czogW3t9XSxwcmVkOiBrZXlzLWFsbH1cXFwiXFxuICAgICAgICAgICAgICAgICAgICAgICBbKGRyb3AgMiBhY2NvdW50KV0pKVxcbiAgICAgICAgICAgIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgKGVuZm9yY2UgZmFsc2VcXG4gICAgICAgICAgICAoZm9ybWF0IFxcXCJVbnJlY29nbml6ZWQgcmVzZXJ2ZWQgcHJvdG9jb2w6IHt9XFxcIiBbcl0pKSkpKSlcXG5cXG5cXG4gIChkZWZzY2hlbWEgY3Jvc3NjaGFpbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiU2NoZW1hIGZvciB5aWVsZGVkIHZhbHVlIGluIGNyb3NzLWNoYWluIHRyYW5zZmVyc1xcXCJcXG4gICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgIGFtb3VudDpkZWNpbWFsKVxcblxcbiAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICB0YXJnZXQtY2hhaW46c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWwgKVxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBzZW5kZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgcmVjZWl2ZXIpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAoc3RlcFxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKERFQklUIHNlbmRlcilcXG5cXG4gICAgICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKCE9IFxcXCJcXFwiIHRhcmdldC1jaGFpbikgXFxcImVtcHR5IHRhcmdldC1jaGFpblxcXCIpXFxuICAgICAgICAoZW5mb3JjZSAoIT0gKGF0ICdjaGFpbi1pZCAoY2hhaW4tZGF0YSkpIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgXFxcImNhbm5vdCBydW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzIHRvIHRoZSBzYW1lIGNoYWluXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgICAgIFxcXCJ0cmFuc2ZlciBxdWFudGl0eSBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAgICAgOzsgc3RlcCAxIC0gZGViaXQgZGVsZXRlLWFjY291bnQgb24gY3VycmVudCBjaGFpblxcbiAgICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIFxcXCJcXFwiIGFtb3VudCkpXFxuXFxuICAgICAgICAobGV0XFxuICAgICAgICAgICgoY3Jvc3NjaGFpbi1kZXRhaWxzOm9iamVjdHtjcm9zc2NoYWluLXNjaGVtYX1cXG4gICAgICAgICAgICB7IFxcXCJyZWNlaXZlclxcXCIgOiByZWNlaXZlclxcbiAgICAgICAgICAgICwgXFxcInJlY2VpdmVyLWd1YXJkXFxcIiA6IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAgICAgLCBcXFwiYW1vdW50XFxcIiA6IGFtb3VudFxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICB9XFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50KSlcXG4gICAgICAgIDs7IHN0ZXAgMiAtIGNyZWRpdCBjcmVhdGUgYWNjb3VudCBvbiB0YXJnZXQgY2hhaW5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCByZWNlaXZlcilcXG4gICAgICAgICAgKGNyZWRpdCByZWNlaXZlciByZWNlaXZlci1ndWFyZCBhbW91bnQpKVxcbiAgICAgICAgKSlcXG4gICAgKVxcblxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIGFsbG9jYXRpb25zXFxuXFxuICAoZGVmc2NoZW1hIGFsbG9jYXRpb24tc2NoZW1hXFxuICAgIEBkb2MgXFxcIkdlbmVzaXMgYWxsb2NhdGlvbiByZWdpc3RyeVxcXCJcXG4gICAgO0Btb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZGF0ZTp0aW1lXFxuICAgIGd1YXJkOmd1YXJkXFxuICAgIHJlZGVlbWVkOmJvb2wpXFxuXFxuICAoZGVmdGFibGUgYWxsb2NhdGlvbi10YWJsZTp7YWxsb2NhdGlvbi1zY2hlbWF9KVxcblxcbiAgKGRlZnVuIGNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnRcXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGRhdGU6dGltZVxcbiAgICAgIGtleXNldC1yZWY6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICBAZG9jIFxcXCJBZGQgYW4gZW50cnkgdG8gdGhlIGNvaW4gYWxsb2NhdGlvbiB0YWJsZS4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGFsc28gY3JlYXRlcyBhIGNvcnJlc3BvbmRpbmcgZW1wdHkgY29pbiBjb250cmFjdCBhY2NvdW50IFxcXFxcXG4gICAgICAgICBcXFxcb2YgdGhlIHNhbWUgbmFtZSBhbmQgZ3VhcmQuIFJlcXVpcmVzIEdFTkVTSVMgY2FwYWJpbGl0eS4gXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoR0VORVNJUykpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlICg-PSBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJhbGxvY2F0aW9uIGFtb3VudCBtdXN0IGJlIG5vbi1uZWdhdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAobGV0XFxuICAgICAgKChndWFyZDpndWFyZCAoa2V5c2V0LXJlZi1ndWFyZCBrZXlzZXQtcmVmKSkpXFxuXFxuICAgICAgKGNyZWF0ZS1hY2NvdW50IGFjY291bnQgZ3VhcmQpXFxuXFxuICAgICAgKGluc2VydCBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogYW1vdW50XFxuICAgICAgICAsIFxcXCJkYXRlXFxcIiA6IGRhdGVcXG4gICAgICAgICwgXFxcImd1YXJkXFxcIiA6IGd1YXJkXFxuICAgICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOiBmYWxzZVxcbiAgICAgICAgfSkpKVxcblxcbiAgKGRlZnVuIHJlbGVhc2UtYWxsb2NhdGlvblxcbiAgICAoIGFjY291bnQ6c3RyaW5nIClcXG5cXG4gICAgQGRvYyBcXFwiUmVsZWFzZSBmdW5kcyBhc3NvY2lhdGVkIHdpdGggYWxsb2NhdGlvbiBBQ0NPVU5UIGludG8gbWFpbiBsZWRnZXIuICAgXFxcXFxcbiAgICAgICAgIFxcXFxBQ0NPVU5UIG11c3QgYWxyZWFkeSBleGlzdCBpbiBtYWluIGxlZGdlci4gQWxsb2NhdGlvbiBpcyBkZWFjdGl2YXRlZCBcXFxcXFxuICAgICAgICAgXFxcXGFmdGVyIHJlbGVhc2UuXFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKHdpdGgtcmVhZCBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlXFxuICAgICAgLCBcXFwiZGF0ZVxcXCIgOj0gcmVsZWFzZS10aW1lXFxuICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDo9IHJlZGVlbWVkXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGd1YXJkXFxuICAgICAgfVxcblxcbiAgICAgIChsZXQgKChjdXJyLXRpbWU6dGltZSAoYXQgJ2Jsb2NrLXRpbWUgKGNoYWluLWRhdGEpKSkpXFxuXFxuICAgICAgICAoZW5mb3JjZSAobm90IHJlZGVlbWVkKVxcbiAgICAgICAgICBcXFwiYWxsb2NhdGlvbiBmdW5kcyBoYXZlIGFscmVhZHkgYmVlbiByZWRlZW1lZFxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZVxcbiAgICAgICAgICAoPj0gY3Vyci10aW1lIHJlbGVhc2UtdGltZSlcXG4gICAgICAgICAgKGZvcm1hdCBcXFwiZnVuZHMgbG9ja2VkIHVudGlsIHt9LiBjdXJyZW50IHRpbWU6IHt9XFxcIiBbcmVsZWFzZS10aW1lIGN1cnItdGltZV0pKVxcblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoUkVMRUFTRV9BTExPQ0FUSU9OIGFjY291bnQgYmFsYW5jZSlcXG5cXG4gICAgICAgIChlbmZvcmNlLWd1YXJkIGd1YXJkKVxcblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBcXFwiXFxcIiBhY2NvdW50IGJhbGFuY2UpKVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKSlcXG4gICAgKSkpXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12M1wifSJ9" - ] diff --git a/src/Chainweb/Pact/Transactions/CoinV4Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV4Transactions.hs deleted file mode 100644 index c12a24b8f4..0000000000 --- a/src/Chainweb/Pact/Transactions/CoinV4Transactions.hs +++ /dev/null @@ -1,21 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.CoinV4Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0" - , - "eyJoYXNoIjoiby1RNlV2RU4tSmNXSFozUnpvM0lET3R5czRkUTFKX2pwN25vUXdoWEMwVSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBTY2hlbWFzIGFuZCBUYWJsZXNcXG5cXG4gIChkZWZzY2hlbWEgY29pbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiVGhlIGNvaW4gY29udHJhY3QgdG9rZW4gc2NoZW1hXFxcIlxcbiAgICBAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcbiAgKGRlZnRhYmxlIGNvaW4tdGFibGU6e2NvaW4tc2NoZW1hfSlcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ2FwYWJpbGl0aWVzXFxuXFxuICAoZGVmY2FwIEdPVkVSTkFOQ0UgKClcXG4gICAgKGVuZm9yY2UgZmFsc2UgXFxcIkVuZm9yY2Ugbm9uLXVwZ3JhZGVhYmlsaXR5XFxcIikpXFxuXFxuICAoZGVmY2FwIEdBUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IGdhcyBidXkgYW5kIHJlZGVlbVxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgQ09JTkJBU0UgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBtaW5lciByZXdhcmRcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIEdFTkVTSVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgY29uc3RyYWluaW5nIGdlbmVzaXMgdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBSRU1FRElBVEUgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgREVCSVQgKHNlbmRlcjpzdHJpbmcpXFxuICAgIFxcXCJDYXBhYmlsaXR5IGZvciBtYW5hZ2luZyBkZWJpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZS1ndWFyZCAoYXQgJ2d1YXJkIChyZWFkIGNvaW4tdGFibGUgc2VuZGVyKSkpXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCBzZW5kZXJcXFwiKSlcXG5cXG4gIChkZWZjYXAgQ1JFRElUIChyZWNlaXZlcjpzdHJpbmcpXFxuICAgIFxcXCJDYXBhYmlsaXR5IGZvciBtYW5hZ2luZyBjcmVkaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSBcXFwidmFsaWQgcmVjZWl2ZXJcXFwiKSlcXG5cXG4gIChkZWZjYXAgUk9UQVRFIChhY2NvdW50OnN0cmluZylcXG4gICAgQGRvYyBcXFwiQXV0b25vbW91c2x5IG1hbmFnZWQgY2FwYWJpbGl0eSBmb3IgZ3VhcmQgcm90YXRpb25cXFwiXFxuICAgIEBtYW5hZ2VkXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFRSQU5TRkVSOmJvb2xcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgKVxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpIFxcXCJzYW1lIHNlbmRlciBhbmQgcmVjZWl2ZXJcXFwiKVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIlBvc2l0aXZlIGFtb3VudFxcXCIpXFxuICAgIChjb21wb3NlLWNhcGFiaWxpdHkgKERFQklUIHNlbmRlcikpXFxuICAgIChjb21wb3NlLWNhcGFiaWxpdHkgKENSRURJVCByZWNlaXZlcikpXFxuICApXFxuXFxuICAoZGVmdW4gVFJBTlNGRVItbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChsZXQgKChuZXdiYWwgKC0gbWFuYWdlZCByZXF1ZXN0ZWQpKSlcXG4gICAgICAoZW5mb3JjZSAoPj0gbmV3YmFsIDAuMClcXG4gICAgICAgIChmb3JtYXQgXFxcIlRSQU5TRkVSIGV4Y2VlZGVkIGZvciBiYWxhbmNlIHt9XFxcIiBbbWFuYWdlZF0pKVxcbiAgICAgIG5ld2JhbClcXG4gIClcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVJfWENIQUlOOmJvb2xcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgICB0YXJnZXQtY2hhaW46c3RyaW5nXFxuICAgIClcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJDcm9zcy1jaGFpbiB0cmFuc2ZlcnMgcmVxdWlyZSBhIHBvc2l0aXZlIGFtb3VudFxcXCIpXFxuICAgIChjb21wb3NlLWNhcGFiaWxpdHkgKERFQklUIHNlbmRlcikpXFxuICApXFxuXFxuICAoZGVmdW4gVFJBTlNGRVJfWENIQUlOLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAoZW5mb3JjZSAoPj0gbWFuYWdlZCByZXF1ZXN0ZWQpXFxuICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVJfWENIQUlOIGV4Y2VlZGVkIGZvciBiYWxhbmNlIHt9XFxcIiBbbWFuYWdlZF0pKVxcbiAgICAwLjBcXG4gIClcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVJfWENIQUlOX1JFQ0Q6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHNvdXJjZS1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZXZlbnQgdHJ1ZVxcbiAgKVxcblxcbiAgOyB2MyBjYXBhYmlsaXRpZXNcXG4gIChkZWZjYXAgUkVMRUFTRV9BTExPQ0FUSU9OXFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGZvciBhbGxvY2F0aW9uIHJlbGVhc2UsIGNhbiBiZSB1c2VkIGZvciBzaWcgc2NvcGluZy5cXFwiXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJndWFyZFxcXCIgOj0gb2xkLWd1YXJkIH1cXG5cXG4gICAgICAgIChlbmZvcmNlLWd1YXJkIG9sZC1ndWFyZClcXG5cXG4gICAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgIHsgXFxcImd1YXJkXFxcIiA6IG5ldy1ndWFyZCB9XFxuICAgICAgICAgICkpKVxcbiAgICApXFxuXFxuXFxuICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgKClcXG4gICAgTUlOSU1VTV9QUkVDSVNJT04pXFxuXFxuICAoZGVmdW4gdHJhbnNmZXI6c3RyaW5nIChzZW5kZXI6c3RyaW5nIHJlY2VpdmVyOnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBzZW5kZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgcmVjZWl2ZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpIF1cXG5cXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcilcXG4gICAgICBcXFwic2VuZGVyIGNhbm5vdCBiZSB0aGUgcmVjZWl2ZXIgb2YgYSB0cmFuc2ZlclxcXCIpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcInRyYW5zZmVyIGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFRSQU5TRkVSIHNlbmRlciByZWNlaXZlciBhbW91bnQpXFxuICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIHJlY2VpdmVyXFxuICAgICAgICB7IFxcXCJndWFyZFxcXCIgOj0gZyB9XFxuXFxuICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIGcgYW1vdW50KSlcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgYW1vdW50OmRlY2ltYWwgKVxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpIF1cXG5cXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcilcXG4gICAgICBcXFwic2VuZGVyIGNhbm5vdCBiZSB0aGUgcmVjZWl2ZXIgb2YgYSB0cmFuc2ZlclxcXCIpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcInRyYW5zZmVyIGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFRSQU5TRkVSIHNlbmRlciByZWNlaXZlciBhbW91bnQpXFxuICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgKGNyZWRpdCByZWNlaXZlciByZWNlaXZlci1ndWFyZCBhbW91bnQpKVxcbiAgICApXFxuXFxuICAoZGVmdW4gY29pbmJhc2U6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhY2NvdW50LWd1YXJkOmd1YXJkIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJJbnRlcm5hbCBmdW5jdGlvbiBmb3IgdGhlIGluaXRpYWwgY3JlYXRpb24gb2YgY29pbnMuICBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgXFxcXGNhbm5vdCBiZSB1c2VkIG91dHNpZGUgb2YgdGhlIGNvaW4gY29udHJhY3QuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENPSU5CQVNFKSlcXG4gICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIGFjY291bnQgYW1vdW50KSkgO3YzXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIGFjY291bnQgYW1vdW50KSkgO3YzXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuXFxuICAgICAgKGVuZm9yY2UgKDw9IGFtb3VudCBiYWxhbmNlKSBcXFwiSW5zdWZmaWNpZW50IGZ1bmRzXFxcIilcXG5cXG4gICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAoLSBiYWxhbmNlIGFtb3VudCkgfVxcbiAgICAgICAgKSlcXG4gICAgKVxcblxcbiAgKGRlZnBhY3QgZnVuZC10eCAoc2VuZGVyOnN0cmluZyBtaW5lcjpzdHJpbmcgbWluZXItZ3VhcmQ6Z3VhcmQgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiJ2Z1bmQtdHgnIGlzIGEgc3BlY2lhbCBwYWN0IHRvIGZ1bmQgYSB0cmFuc2FjdGlvbiBpbiB0d28gc3RlcHMsICAgICBcXFxcXFxuICAgIFxcXFx3aXRoIHRoZSBhY3R1YWwgdHJhbnNhY3Rpb24gdHJhbnNwaXJpbmcgaW4gdGhlIG1pZGRsZTogICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFxcXFxcXG4gICAgXFxcXCAgMSkgQSBidXlpbmcgcGhhc2UsIGRlYml0aW5nIHRoZSBzZW5kZXIgZm9yIHRvdGFsIGdhcyBhbmQgZmVlLCB5aWVsZGluZyBcXFxcXFxuICAgIFxcXFwgICAgIFRYX01BWF9DSEFSR0UuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAyKSBBIHNldHRsZW1lbnQgcGhhc2UsIHJlc3VtaW5nIFRYX01BWF9DSEFSR0UsIGFuZCBhbGxvY2F0aW5nIHRvIHRoZSAgIFxcXFxcXG4gICAgXFxcXCAgICAgY29pbmJhc2UgYWNjb3VudCBmb3IgdXNlZCBnYXMgYW5kIGZlZSwgYW5kIHNlbmRlciBhY2NvdW50IGZvciBiYWwtICBcXFxcXFxuICAgIFxcXFwgICAgIGFuY2UgKHVudXNlZCBnYXMsIGlmIGFueSkuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICAgIDsocHJvcGVydHkgY29uc2VydmVzLW1hc3MpIG5vdCBzdXBwb3J0ZWQgeWV0XFxuICAgICAgICAgICBdXFxuXFxuICAgIChzdGVwIChidXktZ2FzIHNlbmRlciB0b3RhbCkpXFxuICAgIChzdGVwIChyZWRlZW0tZ2FzIG1pbmVyIG1pbmVyLWd1YXJkIHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZWJpdDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJEZWJpdCBBTU9VTlQgZnJvbSBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJkZWJpdCBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChERUJJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICAoZGVmdW4gY3JlZGl0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkNyZWRpdCBBTU9VTlQgdG8gQUNDT1VOVCBiYWxhbmNlXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcImNyZWRpdCBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDUkVESVQgYWNjb3VudCkpXFxuICAgICh3aXRoLWRlZmF1bHQtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IC0xLjAsIFxcXCJndWFyZFxcXCIgOiBndWFyZCB9XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSwgXFxcImd1YXJkXFxcIiA6PSByZXRnIH1cXG4gICAgICA7IHdlIGRvbid0IHdhbnQgdG8gb3ZlcndyaXRlIGFuIGV4aXN0aW5nIGd1YXJkIHdpdGggdGhlIHVzZXItc3VwcGxpZWQgb25lXFxuICAgICAgKGVuZm9yY2UgKD0gcmV0ZyBndWFyZClcXG4gICAgICAgIFxcXCJhY2NvdW50IGd1YXJkcyBkbyBub3QgbWF0Y2hcXFwiKVxcblxcbiAgICAgIChsZXQgKChpcy1uZXdcXG4gICAgICAgICAgICAgKGlmICg9IGJhbGFuY2UgLTEuMClcXG4gICAgICAgICAgICAgICAgIChlbmZvcmNlLXJlc2VydmVkIGFjY291bnQgZ3VhcmQpXFxuICAgICAgICAgICAgICAgZmFsc2UpKSlcXG5cXG4gICAgICAgICh3cml0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAoaWYgaXMtbmV3IGFtb3VudCAoKyBiYWxhbmNlIGFtb3VudCkpXFxuICAgICAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogcmV0Z1xcbiAgICAgICAgICB9KSlcXG4gICAgICApKVxcblxcbiAgKGRlZnVuIGNoZWNrLXJlc2VydmVkOnN0cmluZyAoYWNjb3VudDpzdHJpbmcpXFxuICAgIFxcXCIgQ2hlY2tzIEFDQ09VTlQgZm9yIHJlc2VydmVkIG5hbWUgYW5kIHJldHVybnMgdHlwZSBpZiBcXFxcXFxuICAgIFxcXFwgZm91bmQgb3IgZW1wdHkgc3RyaW5nLiBSZXNlcnZlZCBuYW1lcyBzdGFydCB3aXRoIGEgXFxcXFxcbiAgICBcXFxcIHNpbmdsZSBjaGFyIGFuZCBjb2xvbiwgZS5nLiAnYzpmb28nLCB3aGljaCB3b3VsZCByZXR1cm4gJ2MnIGFzIHR5cGUuXFxcIlxcbiAgICAobGV0ICgocGZ4ICh0YWtlIDIgYWNjb3VudCkpKVxcbiAgICAgIChpZiAoPSBcXFwiOlxcXCIgKHRha2UgLTEgcGZ4KSkgKHRha2UgMSBwZngpIFxcXCJcXFwiKSkpXFxuXFxuICAoZGVmdW4gZW5mb3JjZS1yZXNlcnZlZDpib29sIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQGRvYyBcXFwiRW5mb3JjZSByZXNlcnZlZCBhY2NvdW50IG5hbWUgcHJvdG9jb2xzLlxcXCJcXG4gICAgKGlmICh2YWxpZGF0ZS1wcmluY2lwYWwgZ3VhcmQgYWNjb3VudClcXG4gICAgICB0cnVlXFxuICAgICAgKGxldCAoKHIgKGNoZWNrLXJlc2VydmVkIGFjY291bnQpKSlcXG4gICAgICAgIChpZiAoPSByIFxcXCJcXFwiKVxcbiAgICAgICAgICB0cnVlXFxuICAgICAgICAgIChpZiAoPSByIFxcXCJrXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZSBcXFwiU2luZ2xlLWtleSBhY2NvdW50IHByb3RvY29sIHZpb2xhdGlvblxcXCIpXFxuICAgICAgICAgICAgKGVuZm9yY2UgZmFsc2VcXG4gICAgICAgICAgICAgIChmb3JtYXQgXFxcIlJlc2VydmVkIHByb3RvY29sIGd1YXJkIHZpb2xhdGlvbjoge31cXFwiIFtyXSkpXFxuICAgICAgICAgICAgKSkpKSlcXG5cXG5cXG4gIChkZWZzY2hlbWEgY3Jvc3NjaGFpbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiU2NoZW1hIGZvciB5aWVsZGVkIHZhbHVlIGluIGNyb3NzLWNoYWluIHRyYW5zZmVyc1xcXCJcXG4gICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIHNvdXJjZS1jaGFpbjpzdHJpbmcpXFxuXFxuICAoZGVmcGFjdCB0cmFuc2Zlci1jcm9zc2NoYWluOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eVxcbiAgICAgICAgKFRSQU5TRkVSX1hDSEFJTiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50IHRhcmdldC1jaGFpbilcXG5cXG4gICAgICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKCE9IFxcXCJcXFwiIHRhcmdldC1jaGFpbikgXFxcImVtcHR5IHRhcmdldC1jaGFpblxcXCIpXFxuICAgICAgICAoZW5mb3JjZSAoIT0gKGF0ICdjaGFpbi1pZCAoY2hhaW4tZGF0YSkpIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgXFxcImNhbm5vdCBydW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzIHRvIHRoZSBzYW1lIGNoYWluXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgICAgIFxcXCJ0cmFuc2ZlciBxdWFudGl0eSBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAgICAgOzsgc3RlcCAxIC0gZGViaXQgZGVsZXRlLWFjY291bnQgb24gY3VycmVudCBjaGFpblxcbiAgICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIFxcXCJcXFwiIGFtb3VudCkpXFxuXFxuICAgICAgICAobGV0XFxuICAgICAgICAgICgoY3Jvc3NjaGFpbi1kZXRhaWxzOm9iamVjdHtjcm9zc2NoYWluLXNjaGVtYX1cXG4gICAgICAgICAgICB7IFxcXCJyZWNlaXZlclxcXCIgOiByZWNlaXZlclxcbiAgICAgICAgICAgICwgXFxcInJlY2VpdmVyLWd1YXJkXFxcIiA6IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAgICAgLCBcXFwiYW1vdW50XFxcIiA6IGFtb3VudFxcbiAgICAgICAgICAgICwgXFxcInNvdXJjZS1jaGFpblxcXCIgOiAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSlcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImNvaW4tY29udHJhY3QtdjRcIn0ifQ" - ] diff --git a/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs deleted file mode 100644 index 19bd9563d6..0000000000 --- a/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs +++ /dev/null @@ -1,19 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.CoinV5Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiOERDei1xb2pVcWUyRTJ6R1V1clhuanBJUHlxSFVlcFlmdFdockhUZVd3SSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICAoZGVmY2FwIFRSQU5TRkVSX1hDSEFJTjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICApXFxuXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUl9YQ0hBSU4tbWdyXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiQ3Jvc3MtY2hhaW4gdHJhbnNmZXJzIHJlcXVpcmUgYSBwb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGVuZm9yY2UgKD49IG1hbmFnZWQgcmVxdWVzdGVkKVxcbiAgICAgIChmb3JtYXQgXFxcIlRSQU5TRkVSX1hDSEFJTiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgMC4wXFxuICApXFxuXFxuICAoZGVmY2FwIFRSQU5TRkVSX1hDSEFJTl9SRUNEOmJvb2xcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgICBzb3VyY2UtY2hhaW46c3RyaW5nXFxuICAgIClcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgdjMgY2FwYWJpbGl0aWVzXFxuICAoZGVmY2FwIFJFTEVBU0VfQUxMT0NBVElPTlxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgKVxcbiAgICBAZG9jIFxcXCJFdmVudCBmb3IgYWxsb2NhdGlvbiByZWxlYXNlLCBjYW4gYmUgdXNlZCBmb3Igc2lnIHNjb3BpbmcuXFxcIlxcbiAgICBAZXZlbnQgdHJ1ZVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb25zdGFudHNcXG5cXG4gIChkZWZjb25zdCBDT0lOX0NIQVJTRVQgQ0hBUlNFVF9MQVRJTjFcXG4gICAgXFxcIlRoZSBkZWZhdWx0IGNvaW4gY29udHJhY3QgY2hhcmFjdGVyIHNldFxcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9QUkVDSVNJT04gMTJcXG4gICAgXFxcIk1pbmltdW0gYWxsb3dlZCBwcmVjaXNpb24gZm9yIGNvaW4gdHJhbnNhY3Rpb25zXFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX0FDQ09VTlRfTEVOR1RIIDNcXG4gICAgXFxcIk1pbmltdW0gYWNjb3VudCBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUFYSU1VTV9BQ0NPVU5UX0xFTkdUSCAyNTZcXG4gICAgXFxcIk1heGltdW0gYWNjb3VudCBuYW1lIGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBWQUxJRF9DSEFJTl9JRFMgKG1hcCAoaW50LXRvLXN0ciAxMCkgKGVudW1lcmF0ZSAwIDE5KSlcXG4gICAgXFxcIkxpc3Qgb2YgYWxsIHZhbGlkIENoYWlud2ViIGNoYWluIGlkc1xcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFV0aWxpdGllc1xcblxcbiAgKGRlZnVuIGVuZm9yY2UtdW5pdDpib29sIChhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiRW5mb3JjZSBtaW5pbXVtIHByZWNpc2lvbiBhbGxvd2VkIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoPSAoZmxvb3IgYW1vdW50IE1JTklNVU1fUFJFQ0lTSU9OKVxcbiAgICAgICAgIGFtb3VudClcXG4gICAgICAoZm9ybWF0IFxcXCJBbW91bnQgdmlvbGF0ZXMgbWluaW11bSBwcmVjaXNpb246IHt9XFxcIiBbYW1vdW50XSkpXFxuICAgIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZS1hY2NvdW50IChhY2NvdW50OnN0cmluZylcXG4gICAgQGRvYyBcXFwiRW5mb3JjZSB0aGF0IGFuIGFjY291bnQgbmFtZSBjb25mb3JtcyB0byB0aGUgY29pbiBjb250cmFjdCBcXFxcXFxuICAgICAgICAgXFxcXG1pbmltdW0gYW5kIG1heGltdW0gbGVuZ3RoIHJlcXVpcmVtZW50cywgYXMgd2VsbCBhcyB0aGUgICAgXFxcXFxcbiAgICAgICAgIFxcXFxsYXRpbi0xIGNoYXJhY3RlciBzZXQuXFxcIlxcblxcbiAgICAoZW5mb3JjZVxcbiAgICAgIChpcy1jaGFyc2V0IENPSU5fQ0hBUlNFVCBhY2NvdW50KVxcbiAgICAgIChmb3JtYXRcXG4gICAgICAgIFxcXCJBY2NvdW50IGRvZXMgbm90IGNvbmZvcm0gdG8gdGhlIGNvaW4gY29udHJhY3QgY2hhcnNldDoge31cXFwiXFxuICAgICAgICBbYWNjb3VudF0pKVxcblxcbiAgICAobGV0ICgoYWNjb3VudC1sZW5ndGggKGxlbmd0aCBhY2NvdW50KSkpXFxuXFxuICAgICAgKGVuZm9yY2VcXG4gICAgICAgICg-PSBhY2NvdW50LWxlbmd0aCBNSU5JTVVNX0FDQ09VTlRfTEVOR1RIKVxcbiAgICAgICAgKGZvcm1hdFxcbiAgICAgICAgICBcXFwiQWNjb3VudCBuYW1lIGRvZXMgbm90IGNvbmZvcm0gdG8gdGhlIG1pbiBsZW5ndGggcmVxdWlyZW1lbnQ6IHt9XFxcIlxcbiAgICAgICAgICBbYWNjb3VudF0pKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPD0gYWNjb3VudC1sZW5ndGggTUFYSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtYXggbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG4gICAgICApXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gQ29udHJhY3RcXG5cXG4gIChkZWZ1biBnYXMtb25seSAoKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMtb25seSB1c2VyIGd1YXJkcy5cXFwiXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpKVxcblxcbiAgKGRlZnVuIGdhcy1ndWFyZCAoZ3VhcmQ6Z3VhcmQpXFxuICAgIFxcXCJQcmVkaWNhdGUgZm9yIGdhcyArIHNpbmdsZSBrZXkgdXNlciBndWFyZHNcXFwiXFxuICAgIChlbmZvcmNlLW9uZVxcbiAgICAgIFxcXCJFbmZvcmNlIGVpdGhlciB0aGUgcHJlc2VuY2Ugb2YgYSBHQVMgY2FwIG9yIGtleXNldFxcXCJcXG4gICAgICBbIChnYXMtb25seSlcXG4gICAgICAgIChlbmZvcmNlLWd1YXJkIGd1YXJkKVxcbiAgICAgIF0pKVxcblxcbiAgKGRlZnVuIGJ1eS1nYXM6c3RyaW5nIChzZW5kZXI6c3RyaW5nIHRvdGFsOmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIlRoaXMgZnVuY3Rpb24gZGVzY3JpYmVzIHRoZSBtYWluICdnYXMgYnV5JyBvcGVyYXRpb24uIEF0IHRoaXMgcG9pbnQgXFxcXFxcbiAgICBcXFxcTUlORVIgaGFzIGJlZW4gY2hvc2VuIGZyb20gdGhlIHBvb2wsIGFuZCB3aWxsIGJlIHZhbGlkYXRlZC4gVGhlIFNFTkRFUiAgIFxcXFxcXG4gICAgXFxcXG9mIHRoaXMgdHJhbnNhY3Rpb24gaGFzIHNwZWNpZmllZCBhIGdhcyBsaW1pdCBMSU1JVCAobWF4aW11bSBnYXMpIGZvciAgICBcXFxcXFxuICAgIFxcXFx0aGUgdHJhbnNhY3Rpb24sIGFuZCB0aGUgcHJpY2UgaXMgdGhlIHNwb3QgcHJpY2Ugb2YgZ2FzIGF0IHRoYXQgdGltZS4gICAgXFxcXFxcbiAgICBcXFxcVGhlIGdhcyBidXkgd2lsbCBiZSBleGVjdXRlZCBwcmlvciB0byBleGVjdXRpbmcgU0VOREVSJ3MgY29kZS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcblxcbiAgICAoZW5mb3JjZS11bml0IHRvdGFsKVxcbiAgICAoZW5mb3JjZSAoPiB0b3RhbCAwLjApIFxcXCJnYXMgc3VwcGx5IG11c3QgYmUgYSBwb3NpdGl2ZSBxdWFudGl0eVxcXCIpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKERFQklUIHNlbmRlcilcXG4gICAgICAoZGViaXQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlZGVlbS1nYXM6c3RyaW5nIChtaW5lcjpzdHJpbmcgbWluZXItZ3VhcmQ6Z3VhcmQgc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAncmVkZWVtIGdhcycgb3BlcmF0aW9uLiBBdCB0aGlzICAgIFxcXFxcXG4gICAgXFxcXHBvaW50LCB0aGUgU0VOREVSJ3MgdHJhbnNhY3Rpb24gaGFzIGJlZW4gZXhlY3V0ZWQsIGFuZCB0aGUgZ2FzIHRoYXQgICAgICBcXFxcXFxuICAgIFxcXFx3YXMgY2hhcmdlZCBoYXMgYmVlbiBjYWxjdWxhdGVkLiBNSU5FUiB3aWxsIGJlIGNyZWRpdGVkIHRoZSBnYXMgY29zdCwgICAgXFxcXFxcbiAgICBcXFxcYW5kIFNFTkRFUiB3aWxsIHJlY2VpdmUgdGhlIHJlbWFpbmRlciB1cCB0byB0aGUgbGltaXRcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBtaW5lcilcXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoR0FTKSlcXG4gICAgKGxldCpcXG4gICAgICAoKGZlZSAocmVhZC1kZWNpbWFsIFxcXCJmZWVcXFwiKSlcXG4gICAgICAgKHJlZnVuZCAoLSB0b3RhbCBmZWUpKSlcXG5cXG4gICAgICAoZW5mb3JjZS11bml0IGZlZSlcXG4gICAgICAoZW5mb3JjZSAoPj0gZmVlIDAuMClcXG4gICAgICAgIFxcXCJmZWUgbXVzdCBiZSBhIG5vbi1uZWdhdGl2ZSBxdWFudGl0eVxcXCIpXFxuXFxuICAgICAgKGVuZm9yY2UgKD49IHJlZnVuZCAwLjApXFxuICAgICAgICBcXFwicmVmdW5kIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgbWluZXIgZmVlKSkgO3YzXFxuXFxuICAgICAgICA7IGRpcmVjdGx5IHVwZGF0ZSBpbnN0ZWFkIG9mIGNyZWRpdFxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBzZW5kZXIpXFxuICAgICAgICAoaWYgKD4gcmVmdW5kIDAuMClcXG4gICAgICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgICAgICAgICh1cGRhdGUgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICAgIHsgXFxcImJhbGFuY2VcXFwiOiAoKyBiYWxhbmNlIHJlZnVuZCkgfSkpXFxuXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuXFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIG1pbmVyKVxcbiAgICAgICAgKGlmICg-IGZlZSAwLjApXFxuICAgICAgICAgIChjcmVkaXQgbWluZXIgbWluZXItZ3VhcmQgZmVlKVxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcbiAgICAgIClcXG5cXG4gICAgKVxcblxcbiAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgZ3VhcmQ6Z3VhcmQpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS1yZXNlcnZlZCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAoaW5zZXJ0IGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiBndWFyZFxcbiAgICAgIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsIChhY2NvdW50OnN0cmluZylcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICBiYWxhbmNlXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gZGV0YWlsczpvYmplY3R7ZnVuZ2libGUtdjIuYWNjb3VudC1kZXRhaWxzfVxcbiAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZyB9XFxuICAgICAgeyBcXFwiYWNjb3VudFxcXCIgOiBhY2NvdW50XFxuICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiBiYWxcXG4gICAgICAsIFxcXCJndWFyZFxcXCI6IGcgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJvdGF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIG5ldy1ndWFyZDpndWFyZClcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoUk9UQVRFIGFjY291bnQpXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcblxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcblxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImNvaW4tY29udHJhY3QtdjVcIn0ifQ" - ] diff --git a/src/Chainweb/Pact/Transactions/CoinV6Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV6Transactions.hs deleted file mode 100644 index 2eea8935e4..0000000000 --- a/src/Chainweb/Pact/Transactions/CoinV6Transactions.hs +++ /dev/null @@ -1,19 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.CoinV6Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiOGJXY0xlSzFSYUZYVGVLbnhzQ2tuWW5QcnAza29vX0cxTk05eHl0VmFjVSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImNvaW4tY29udHJhY3QtdjZcIn0ifQ" - ] diff --git a/src/Chainweb/Pact/Transactions/FungibleV2Transactions.hs b/src/Chainweb/Pact/Transactions/FungibleV2Transactions.hs deleted file mode 100644 index da6824b057..0000000000 --- a/src/Chainweb/Pact/Transactions/FungibleV2Transactions.hs +++ /dev/null @@ -1,19 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.FungibleV2Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs deleted file mode 100644 index e4c81f1994..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet0Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiMEZ0bjlmYXp3amlpbkZTT3pxQVdXX2Vaa0VFVWo0b0lHbjN6UktENm1HcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiNDA2OWU3MmY2MmRlYjlkZGU4ZWUwNjg2ZWUyOTA4NTgwMTMxMTI4YjBhMzUwZTIzODkwZTliNDZlNjFmMTY2ZFxcXCIgNDYwOTAuNDYwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiAxNjEzMTYuNjEwMDApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJtYWlubmV0LXJlbWVkaWF0aW9ucy0wXCJ9In0" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs deleted file mode 100644 index c89811abb8..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet1Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiQTlUdFRpTHpqS2RiaHA1cE5IbnB2SlVKSThQOVc3azVsemY5aUo0TE1OOCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiYTMwZmZkM2JhN2ZhMDhkNGFhZmNmZDlmMTU1OTRkMjBlNzMyYjhmZGRhNDNmMDI0NzBkNzZiN2ViZmE0NjdlNlxcXCIgNjkxMzUuNjkwMDApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJtYWlubmV0LXJlbWVkaWF0aW9ucy0xXCJ9In0" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs deleted file mode 100644 index 8504c9af74..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet2Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiYVBib0FkV2w3SnJ4d2lNUGNRNGRlSFJ0ZzZtaHJWczdfQ0JRXzZNUE1sTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiYTMwZmZkM2JhN2ZhMDhkNGFhZmNmZDlmMTU1OTRkMjBlNzMyYjhmZGRhNDNmMDI0NzBkNzZiN2ViZmE0NjdlNlxcXCIgOTIxODAuOTIwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCI0MDY5ZTcyZjYyZGViOWRkZThlZTA2ODZlZTI5MDg1ODAxMzExMjhiMGEzNTBlMjM4OTBlOWI0NmU2MWYxNjZkXFxcIiA0NjA5MC40NjAwMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcIm1haW5uZXQtcmVtZWRpYXRpb25zLTJcIn0ifQ" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs deleted file mode 100644 index eb3a4dec14..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet3Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoicXN0NzdIOFNvUVZkanhOSnBDRy1qWGsyZ0FXNzFJUEFqNVVIeDNGdWllcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiN2UzYzMwMmRmNDFlYzUxNzViNGU5YmY0M2E3ZDk2N2ZkMDg4ZmQyZGU3NzRlYzMxZTFlNmQwYjRhNzViZTVkM1xcXCIgMjMwNDUuMjMwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiA5MjE4MC45MjAwMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcIm1haW5uZXQtcmVtZWRpYXRpb25zLTNcIn0ifQ" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs deleted file mode 100644 index 8259c60c75..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet4Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiZzNTMi1ieTM3ZDRac3Y0aTF6VVdtLW5jR1hJM2g4OHBzem9lYXk4eDVvTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiYTMwZmZkM2JhN2ZhMDhkNGFhZmNmZDlmMTU1OTRkMjBlNzMyYjhmZGRhNDNmMDI0NzBkNzZiN2ViZmE0NjdlNlxcXCIgMTE1MjI2LjE1MDAwKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwibWFpbm5ldC1yZW1lZGlhdGlvbnMtNFwifSJ9" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs deleted file mode 100644 index 633f7fe335..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet5Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiNy13bXVJT2ZrYkp6bzdYS3M0OUFUMjlCRF9BWENGOEcxYVJTc0t0S25GMCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiN2UzYzMwMmRmNDFlYzUxNzViNGU5YmY0M2E3ZDk2N2ZkMDg4ZmQyZGU3NzRlYzMxZTFlNmQwYjRhNzViZTVkM1xcXCIgNjkxMzUuNjkwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiA0NjA5MC40NjAwMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcIm1haW5uZXQtcmVtZWRpYXRpb25zLTVcIn0ifQ" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs deleted file mode 100644 index 2475deda9a..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet6Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiQVloSFRveFllYktqcEZYa0t0LXl6c2c2Mkl2RlR2emFTNGZYa3JxSmlUYyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiNDA2OWU3MmY2MmRlYjlkZGU4ZWUwNjg2ZWUyOTA4NTgwMTMxMTI4YjBhMzUwZTIzODkwZTliNDZlNjFmMTY2ZFxcXCIgMjMwNDUuMjMwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiAxMTUyMjYuMTUwMDApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJtYWlubmV0LXJlbWVkaWF0aW9ucy02XCJ9In0" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs deleted file mode 100644 index b12a2eda30..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet7Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiSld1VXZmNlNWTzYyaFlKVEx6UTYxWTNxUS1BNEdwU1VXc0l4RXFmSjN0SSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiYTMwZmZkM2JhN2ZhMDhkNGFhZmNmZDlmMTU1OTRkMjBlNzMyYjhmZGRhNDNmMDI0NzBkNzZiN2ViZmE0NjdlNlxcXCIgMjMwNDUyLjMwMDAwKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwibWFpbm5ldC1yZW1lZGlhdGlvbnMtN1wifSJ9" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs deleted file mode 100644 index 87e87b4208..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet8Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoibXkwVHZzYk1QNTRTTEVta3lqdU9YWDdLX2FJZHU4OWZ4cktyeDlIbE5JMCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiNDA2OWU3MmY2MmRlYjlkZGU4ZWUwNjg2ZWUyOTA4NTgwMTMxMTI4YjBhMzUwZTIzODkwZTliNDZlNjFmMTY2ZFxcXCIgMjMwNDUuMjMwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiA5MjE4MC45MjAwMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcIm1haW5uZXQtcmVtZWRpYXRpb25zLThcIn0ifQ" - ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs deleted file mode 100644 index bd9910d976..0000000000 --- a/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.Mainnet9Transactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiZTE4Z2QwRF9sREZHT2FKUUFLeGVRX0ozU0xYMHExdVZzUlFTSlVBWGJMRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiN2UzYzMwMmRmNDFlYzUxNzViNGU5YmY0M2E3ZDk2N2ZkMDg4ZmQyZGU3NzRlYzMxZTFlNmQwYjRhNzViZTVkM1xcXCIgMjMwNDUuMjMwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiAyOTk1ODcuOTkwMDApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJtYWlubmV0LXJlbWVkaWF0aW9ucy05XCJ9In0" - ] diff --git a/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs b/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs deleted file mode 100644 index 9f161a9c0c..0000000000 --- a/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs +++ /dev/null @@ -1,19 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.MainnetKADTransactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoieThkd0Rkc0RZdmM5alg2WHZxVG1CSndEX2xlRlJUTWlJTXJMcjhKODlVOCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihpZlxcbiAgKDw9IDEwMC4wIChjb2luLmdldC1iYWxhbmNlIFxcXCJlN2Y3NjM0ZTkyNTU0MWYzNjhiODI3YWQ1YzcyNDIxOTA1MTAwZjYyMDUyODVhNzhjMTlkN2I0YTM4NzExODA1XFxcIikpXFxuXFxuICAoY29pbi5yZW1lZGlhdGUgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIDEwMC4wKVxcbiAgXFxcIldhcm5pbmc6IGluc3VmZmljaWVudCBmdW5kcyBmb3IgcmVtZWRpYXRpb24sIHNvbGRpZXJpbmcgb25cXFwiKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwibWFpbm5ldC1yZW1lZGlhdGlvbnMta2FkLW9wc1wifSJ9" - ] diff --git a/src/Chainweb/Pact/Transactions/OtherTransactions.hs b/src/Chainweb/Pact/Transactions/OtherTransactions.hs deleted file mode 100644 index 02c9ecfec0..0000000000 --- a/src/Chainweb/Pact/Transactions/OtherTransactions.hs +++ /dev/null @@ -1,21 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.OtherTransactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - ] diff --git a/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs b/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs deleted file mode 100644 index c87145ed38..0000000000 --- a/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - --- This module is auto-generated. DO NOT EDIT IT MANUALLY. - -module Chainweb.Pact.Transactions.RecapDevelopmentTransactions ( transactions ) where - -import Data.Bifunctor (first) -import System.IO.Unsafe - -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils - -transactions :: [Pact4.Transaction] -transactions = - let decodeTx t = - fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t - in unsafePerformIO $ mapM decodeTx [ - "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" - , - "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" - , - "eyJoYXNoIjoiS1BleXNfYndLeHpGV1M4cEdrWVg0QmZSWkh6MXhiVm43MkVTSk9PRGs0WSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwic2VuZGVyMDdcXFwiIDEzMzcuNylcXG4oY29pbi5yZW1lZGlhdGUgXFxcInNlbmRlcjA5XFxcIiAxMzM3LjkpXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtb3RoZXItcmVtZWRpYXRpb25zXCJ9In0" - ] diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index f7e7781268..2f634ef1ac 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -1,823 +1,199 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DeriveTraversable #-} -{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE PartialTypeSignatures #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE StrictData #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TypeApplications #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE ViewPatterns #-} --- | --- Module: Chainweb.Pact.Types --- Copyright: Copyright © 2018 Kadena LLC. --- License: See LICENSE file --- Maintainer: Mark Nichols --- Stability: experimental --- --- Pact Types module for Chainweb --- +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} + module Chainweb.Pact.Types - -- ( Pact4GasSupply(..) - -- , Pact5GasSupply(..) - -- , cleanModuleCache - - -- * Pact Service Env - ( PactServiceEnv(..) - , psMempoolAccess - , psCheckpointer - , psPdb - , psCandidatePdb - , psBlockHeaderDb - , psReorgLimit - , psPreInsertCheckTimeout - , psOnFatalError - , psVersion - , psLogger - , psGasLogger - , psAllowReadsInLocal - , psBlockGasLimit - , psEnableLocalTimeout - , psTxFailuresCounter - , psTxTimeLimit - , psMiner - -- - -- * Pact Service State - , PactServiceState(..) - , psInitCache - , PactException(..) - , finalizeBlock - , RunnableBlock(..) - , BlockTxHistory(..) - , emptySQLitePendingData - , BlockInProgress(..) - , blockInProgressParent - , blockInProgressHandle - , blockInProgressModuleCache - , blockInProgressParentHeader - , blockInProgressRemainingGasLimit - , blockInProgressMiner - , blockInProgressTransactions - , blockInProgressPactVersion - , blockInProgressChainwebVersion - , blockInProgressChainId - , NewBlockFill(..) - , Historical(..) - , throwIfNoHistory - , NewBlockReq(..) - , ContinueBlockReq(..) - , SubmittedRequestMsg(..) - , ValidateBlockReq(..) - , RewindDepth(..) - , LocalResult(..) - , _MetadataValidationFailure - , _LocalResultWithWarns - , _LocalResultLegacy - , _LocalTimeout - , pattern Pact4LocalResultLegacy - , _Pact4LocalResultLegacy - , pattern Pact5LocalResultLegacy - , _Pact5LocalResultLegacy - , pattern Pact4LocalResultWithWarns - , _Pact4LocalResultWithWarns - , pattern Pact5LocalResultWithWarns - , _Pact5LocalResultWithWarns - , LocalReq(..) - , ReadOnlyReplayReq(..) - , ConfirmationDepth(..) - , LocalPreflightSimulation(..) - , SyncToBlockReq(..) - , RequestMsg(..) - , RewindLimit(..) - , LookupPactTxsReq(..) - , BlockTxHistoryReq(..) - , PreInsertCheckReq(..) - , SpvRequest(..) - , HistoricalLookupReq(..) - , TransactionOutputProofB64(..) - , RequestStatus(..) - , internalError - , LocalSignatureVerification(..) - , Pact4GasPurchaseFailure(..) - , CoinbaseFailure(..) - , Pact5CoinbaseError(..) - , Pact5BuyGasError(..) - , _BuyGasPactError - , Pact5RedeemGasError(..) - , _RedeemGasPactError - , Pact5GasPurchaseFailure(..) - , _BuyGasError - , _RedeemGasError - , _PurchaseGasTxTooBigForGasLimit - , prettyPact5GasPurchaseFailure - , Transactions(..) - , transactionPairs - , transactionCoinbase - , MemPoolAccess(..) - , ModuleCacheFor(..) - , BlockValidationFailureMsg(..) - , toPayloadWithOutputs - , hashPact4TxLogs - , hashPact5TxLogs - , PactServiceConfig(..) - , RequestCancelled(..) - , convertPact5Error - - - -- * Module cache - , ModuleInitCache - - -- * Pact Service Monad - , PactServiceM(..) - , runPactServiceM - , evalPactServiceM - , execPactServiceM - , PactDbFor - - , PactBlockEnv(..) - , psBlockDbEnv - , psParentHeader - , psIsGenesis - , psServiceEnv - - -- * Logging with Pact logger - - -- , tracePactBlockM - -- , tracePactBlockM' - , pactLoggers - , logg_ - , logInfo_ - , logWarn_ - , logError_ - , logDebug_ - , logPact - , logInfoPact - , logWarnPact - , logErrorPact - , logDebugPact - , logJsonTrace_ - , logJsonTracePact - , localLabelPact - - -- * types - , TxTimeout(..) - , ApplyCmdExecutionContext(..) - , Pact4TxFailureLog(..) - , Pact5TxFailureLog(..) - , AssertCommandError(..) - , displayAssertCommandError - , AssertValidateSigsError(..) - , displayAssertValidateSigsError - - -- * miscellaneous - , defaultOnFatalError - , defaultReorgLimit - , testPactServiceConfig - , testBlockGasLimit - , defaultModuleCacheLimit - , defaultPreInsertCheckTimeout - , withPactState - ) where + ( PactServiceEnv(..) + , psBlockGasLimit + , PactServiceM(..) + , PactBlockEnv(..) + , PactBlockState(..) + , PactBlockM(..) + + , MemPoolAccess(..) + + , TxContext(..) + , guardCtx + , ctxCurrentBlockHeight + + , GasSupply(..) + , RewindLimit(..) + , defaultReorgLimit + , defaultPreInsertCheckTimeout + + , pbBlockHandle + , runPactBlockM + , tracePactBlockM + , tracePactBlockM' + , liftPactServiceM + , pactTransaction + , localLabelBlock + -- * default values + , noInfo + , noPublicMeta + , noSpanInfo + , emptyCapState + + , AssertValidateSigsError(..) + , displayAssertValidateSigsError + , AssertCommandError(..) + , displayAssertCommandError + + , LocalSignatureVerification(..) + , LocalPreflightSimulation(..) + , RewindDepth(..) + , ConfirmationDepth(..) + , LocalResult(..) + + , SpvRequest(..) + , TransactionOutputProofB64(..) + + , TxInvalidError(..) + , BuyGasError(..) + , RedeemGasError(..) + + , logg_ + , logDebug_ + , logInfo_ + , logWarn_ + , logError_ + ) + where +import Control.Applicative ((<|>)) import Control.DeepSeq -import Control.Exception (asyncExceptionFromException, asyncExceptionToException) import Control.Exception.Safe import Control.Lens -import Control.Monad +import Control.Monad.IO.Class import Control.Monad.Reader import Control.Monad.State.Strict - -import Data.Aeson hiding (Error,(.=)) -import Data.IORef +import Data.Aeson hiding (Error, (.=)) +import Data.Decimal +import qualified Data.List.NonEmpty as NE import Data.LogMessage -import qualified Data.Map.Strict as M import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Encoding as T - +import Data.Vector (Vector) +import Data.Word import GHC.Generics (Generic) - +import Numeric.Natural +import Pact.Core.Builtin qualified as Pact +import Pact.Core.Capabilities qualified as Pact +import Pact.Core.ChainData qualified as Pact +import Pact.Core.Command.Types (RequestKey) +import Pact.Core.Errors qualified as Pact +import Pact.Core.Evaluate qualified as Pact +import Pact.Core.Gas.Types qualified as Pact +import Pact.Core.Info qualified as Pact +import Pact.Core.Literal qualified as Pact +import Pact.Core.Persistence +import Pact.Core.StableEncoding qualified as Pact +import Pact.JSON.Encode qualified as J import System.LogLevel +import Utils.Logging.Trace --- internal pact modules - -import qualified Pact.Core.Builtin as Pact5 -import qualified Pact.Core.Errors as Pact5 -import qualified Pact.Core.Evaluate as Pact5 -import qualified Pact.Core.Pretty as Pact5 - --- internal chainweb modules - +import Chainweb.BlockCreationTime import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight -import Chainweb.BlockHeaderDB -import Chainweb.ChainId +import Chainweb.BlockPayloadHash +import qualified Chainweb.ChainId as Chainweb import Chainweb.Counter -import Chainweb.Mempool.Mempool (TransactionHash, BlockFill, MempoolPreBlockCheck, InsertError) -import Chainweb.Miner.Pact import Chainweb.Logger +import Chainweb.Mempool.Mempool +import Chainweb.Miner.Pact (Miner) +import Chainweb.MinerReward +import Chainweb.Pact.Backend.ChainwebPactDb import Chainweb.Pact.Backend.DbCache import Chainweb.Pact.Backend.Types - +import qualified Chainweb.Pact.Transaction as Pact +import Chainweb.Payload import Chainweb.Payload.PayloadStore +import Chainweb.PayloadProvider.P2P +import Chainweb.Storage.Table.Map import Chainweb.Time import Chainweb.Utils import Chainweb.Version -import Data.Word -import qualified Chainweb.Pact4.Transaction as Pact4 -import qualified Chainweb.Pact4.ModuleCache as Pact4 -import Data.Vector (Vector) -import qualified Chainweb.Pact5.Transaction as Pact5 -import Data.ByteString (ByteString) -import qualified Pact.Types.Persistence as Pact4 -import Data.Map (Map) -import qualified Pact.Core.Persistence as Pact5 -import Data.HashMap.Strict (HashMap) -import Data.List.NonEmpty (NonEmpty) -import qualified Pact.Core.Names as Pact5 -import GHC.Stack -import Streaming -import qualified Pact.Core.Command.Types as Pact5 -import qualified Pact.Types.Runtime as Pact4 -import qualified Pact.JSON.Encode as J -import Numeric.Natural -import qualified Pact.Types.Command as Pact4 -import qualified Pact.Types.Logger as Pact4 -import qualified Data.List.NonEmpty as NE -import Control.Concurrent.STM -import Chainweb.Payload -import Data.ByteString.Short (ShortByteString) -import qualified Data.ByteString.Short as SB -import qualified Data.Vector as V -import qualified Pact.Core.Hash as Pact5 -import Data.Maybe -import Chainweb.BlockCreationTime -import qualified Data.Aeson as Aeson -import qualified Data.Text.Lazy as TL -import qualified Data.Text.Lazy.Encoding as TL -import Chainweb.Storage.Table.Map (MapTable) -import Chainweb.BlockPayloadHash -import Chainweb.PayloadProvider.P2P +import qualified Pact.Core.Command.Types as Pact +data AssertValidateSigsError + = SignersAndSignaturesLengthMismatch + { _signersLength :: !Int + , _signaturesLength :: !Int + } + | InvalidSignerScheme + { _position :: !Int + } + | InvalidSignerWebAuthnPrefix + { _position :: !Int + } + | InvalidUserSig + { _position :: !Int + , _errMsg :: Text + } --- | Gather tx logs for a block, along with last tx for each --- key in history, if any --- Not intended for public API use; ToJSONs are for logging output. -data BlockTxHistory = BlockTxHistory - { _blockTxHistory :: !(Map Pact4.TxId [Pact5.TxLog Pact5.RowData]) - , _blockPrevHistory :: !(Map Pact4.RowKey (Pact5.TxLog Pact5.RowData)) - } - deriving (Eq,Generic) -instance Show BlockTxHistory where - show = show . fmap (show) . _blockTxHistory --- instance NFData BlockTxHistory -- TODO: add NFData for RowData - --- | A callback which writes a block's data to the input database snapshot, --- and knows its parent header (Nothing if it's a genesis block). --- Reports back its own header and some extra value. -data RunnableBlock logger a - = Pact4RunnableBlock (PactDbFor logger Pact4 -> Maybe (Parent RankedBlockHash) -> IO (a, RankedBlockHash)) - | Pact5RunnableBlock (PactDbFor logger Pact5 -> Maybe (Parent RankedBlockHash) -> BlockHandle Pact5 -> IO ((a, RankedBlockHash), BlockHandle Pact5)) +displayAssertValidateSigsError :: AssertValidateSigsError -> Text +displayAssertValidateSigsError = \case + SignersAndSignaturesLengthMismatch signersLength sigsLength -> + "The number of signers and signatures do not match. Number of signers: " <> sshow signersLength <> ". Number of signatures: " <> sshow sigsLength <> "." + InvalidSignerScheme pos -> + "The signer at position " <> sshow pos <> " has an invalid signature scheme." + InvalidSignerWebAuthnPrefix pos -> + "The signer at position " <> sshow pos <> " has an invalid WebAuthn prefix." + InvalidUserSig pos errMsg -> + "The signature at position " <> sshow pos <> " is invalid: " <> errMsg <> "." --- -------------------------------------------------------------------------- -- --- Coinbase output utils - --- -------------------------------------------------------------------- -- --- Local vs. Send execution context flag - -data ApplyCmdExecutionContext = ApplyLocal | ApplySend - -newtype BlockValidationFailureMsg = BlockValidationFailureMsg Text - deriving (Eq, Ord, Generic) - deriving newtype (Show, J.Encode) - -data CoinbaseFailure - = Pact4CoinbaseFailure !Text - | Pact5CoinbaseFailure !Pact5CoinbaseError - deriving stock (Eq, Show) - -instance J.Encode CoinbaseFailure where - build = \case - Pact4CoinbaseFailure e -> J.build e - Pact5CoinbaseFailure e -> J.build e - -data Pact5CoinbaseError - = CoinbasePactError !(Pact5.PactError Pact5.Info) - deriving stock (Eq, Show) - -instance J.Encode Pact5CoinbaseError where - build = \case - CoinbasePactError e -> J.object - [ "tag" J..= J.text "CoinbasePactError" - , "contents" J..= J.text (sshow e) - ] - -data Pact5RedeemGasError - = RedeemGasPactError !(Pact5.PactError Pact5.Info) - -- ^ Expected pact error - deriving stock (Eq, Show) -makePrisms ''Pact5RedeemGasError - -data Pact5BuyGasError - = BuyGasPactError !(Pact5.PactError Pact5.Info) - | BuyGasMultipleGasPayerCaps - deriving stock (Eq, Show) -makePrisms ''Pact5BuyGasError - -data Pact5GasPurchaseFailure - = BuyGasError !Pact5.RequestKey !Pact5BuyGasError - | RedeemGasError !Pact5.RequestKey !Pact5RedeemGasError - | PurchaseGasTxTooBigForGasLimit !Pact5.RequestKey - deriving stock (Eq, Show) -makePrisms ''Pact5GasPurchaseFailure - -prettyPact5GasPurchaseFailure :: Pact5GasPurchaseFailure -> Text -prettyPact5GasPurchaseFailure = \case - BuyGasError rk e -> sshow rk <> " Failed to buy gas: " <> case e of - BuyGasPactError err -> Pact5.renderText err - BuyGasMultipleGasPayerCaps -> "Multiple gas payer capabilities" - RedeemGasError rk e -> sshow rk <> " Failed to redeem gas: " <> case e of - RedeemGasPactError err -> Pact5.renderText err - PurchaseGasTxTooBigForGasLimit rk -> sshow rk <> " Failed to purchas gas: tx too big for gas limit" - -data Pact4GasPurchaseFailure = Pact4GasPurchaseFailure !TransactionHash !Pact4.PactError - deriving (Eq, Show) - -instance J.Encode Pact4GasPurchaseFailure where - build (Pact4GasPurchaseFailure h e) = J.build (J.Array (h, e)) - --- | Exceptions thrown by PactService components that --- are _not_ recorded in blockchain record. --- -data PactException - = BlockValidationFailure !BlockValidationFailureMsg - -- TODO: use this CallStack in the Show instance somehow, or the displayException impl. - | PactInternalError !CallStack !Text - | PactTransactionExecError !Pact4.PactHash !Text - | CoinbaseFailure !CoinbaseFailure - | NoBlockValidatedYet - | Pact4TransactionValidationException !(NonEmpty (Pact4.PactHash, Text)) - | Pact5TransactionValidationException !(NonEmpty (Pact5.Hash, Text)) - | Pact5GenesisCommandFailed !Pact5.Hash !Text - | Pact5GenesisCommandsInvalid ![InsertError] - | PactDuplicateTableError !Text - | TransactionDecodeFailure !Text - | RewindLimitExceeded - { _rewindExceededLimit :: !RewindLimit - -- ^ Rewind limit - , _rewindExceededLast :: !(Maybe BlockHeader) - -- ^ current header - , _rewindExceededTarget :: !(Maybe BlockHeader) - -- ^ target header - } - | BlockHeaderLookupFailure !Text - | Pact4BuyGasFailure !Pact4GasPurchaseFailure - | Pact5BuyGasFailure !Pact5GasPurchaseFailure - | MempoolFillFailure !Text - | BlockGasLimitExceeded !Pact4.Gas - | FullHistoryRequired - { _earliestBlockHeight :: !BlockHeight - , _genesisHeight :: !BlockHeight - } - deriving stock (Generic) - deriving anyclass (Exception) - -instance Show PactException where - show = T.unpack . J.encodeText - -instance J.Encode PactException where - build (BlockValidationFailure msg) = tagged "BlockValidationFailure" msg - build (PactInternalError _stack msg) = tagged "PactInternalError" msg - build (PactTransactionExecError h msg) = tagged "PactTransactionExecError" (J.Array (h, msg)) - build (CoinbaseFailure msg) = tagged "CoinbaseFailure" msg - build NoBlockValidatedYet = tagged "NoBlockValidatedYet" J.null - build (Pact4TransactionValidationException l) = tagged "TransactionValidationException" (J.Array $ J.Array <$> l) - build (Pact5TransactionValidationException l) = tagged "TransactionValidationException" (J.Array $ J.Array <$> l) - build (PactDuplicateTableError msg) = tagged "PactDuplicateTableError" msg - build (TransactionDecodeFailure msg) = tagged "TransactionDecodeFailure" msg - build o@(RewindLimitExceeded{}) = tagged "RewindLimitExceeded" $ J.object - [ "_rewindExceededLimit" J..= J.Aeson (_rewindLimit $ _rewindExceededLimit o) - , "_rewindExceededLast" J..= J.encodeWithAeson (ObjectEncoded <$> _rewindExceededLast o) - , "_rewindExceededTarget" J..= J.encodeWithAeson (ObjectEncoded <$> _rewindExceededTarget o) - ] - build (BlockHeaderLookupFailure msg) = tagged "BlockHeaderLookupFailure" msg - build (Pact4BuyGasFailure failure) = tagged "BuyGasFailure" failure - build (Pact5BuyGasFailure failure) = tagged "BuyGasFailure" (sshow @_ @Text failure) - build (MempoolFillFailure msg) = tagged "MempoolFillFailure" msg - build (Pact5GenesisCommandFailed hash text) = tagged "BlockGasLimitExceeded" (J.Array $ [sshow @_ @Text hash, text]) - build (Pact5GenesisCommandsInvalid errs) = tagged "BlockGasLimitExceeded" (J.Array $ sshow @_ @Text <$> errs) - build (BlockGasLimitExceeded gas) = tagged "BlockGasLimitExceeded" gas - build o@(FullHistoryRequired{}) = tagged "FullHistoryRequired" $ J.object - [ "_fullHistoryRequiredEarliestBlockHeight" J..= J.Aeson @Int (fromIntegral $ _earliestBlockHeight o) - , "_fullHistoryRequiredGenesisHeight" J..= J.Aeson @Int (fromIntegral $ _genesisHeight o) - ] +data AssertCommandError + = InvalidPayloadHash + | AssertValidateSigsError AssertValidateSigsError + +displayAssertCommandError :: AssertCommandError -> Text +displayAssertCommandError = \case + InvalidPayloadHash -> "The hash of the payload was invalid." + AssertValidateSigsError err -> displayAssertValidateSigsError err -tagged :: J.Encode v => Text -> v -> J.Builder -tagged t v = J.object - [ "tag" J..= t - , "contents" J..= v +data SpvRequest = SpvRequest + { _spvRequestKey :: !Pact.RequestKey + , _spvTargetChainId :: !Pact.ChainId + } deriving (Eq, Show, Generic) + +instance J.Encode SpvRequest where + build r = J.object + [ "requestKey" J..= _spvRequestKey r + , "targetChainId" J..= Pact._chainId (_spvTargetChainId r) ] + {-# INLINE build #-} -instance Eq PactException where - BlockValidationFailure m == BlockValidationFailure m' = m == m' - PactInternalError _ m == PactInternalError _ m' = m == m' - PactTransactionExecError h m == PactTransactionExecError h' m' = - h == h' && m == m' - CoinbaseFailure e == CoinbaseFailure e' = e == e' - NoBlockValidatedYet == NoBlockValidatedYet = True - Pact4TransactionValidationException txs == Pact4TransactionValidationException txs' = - txs == txs' - Pact5TransactionValidationException txs == Pact5TransactionValidationException txs' = - txs == txs' - Pact5GenesisCommandFailed txHash err == Pact5GenesisCommandFailed txHash' err' = - txHash == txHash' && err == err' - Pact5GenesisCommandsInvalid errs == Pact5GenesisCommandsInvalid errs' = - errs == errs' - PactDuplicateTableError m == PactDuplicateTableError m' = - m == m' - TransactionDecodeFailure m == TransactionDecodeFailure m' = - m == m' - RewindLimitExceeded l lt t == RewindLimitExceeded l' lt' t' = - l == l' && lt == lt' && t == t' - BlockHeaderLookupFailure m == BlockHeaderLookupFailure m' = - m == m' - Pact4BuyGasFailure f == Pact4BuyGasFailure f' = f == f' - MempoolFillFailure m == MempoolFillFailure m' = m == m' - BlockGasLimitExceeded g == BlockGasLimitExceeded g' = g == g' - FullHistoryRequired e g == FullHistoryRequired e' g' = - e == e' && g == g' - _ == _ = False +instance FromJSON SpvRequest where + parseJSON = withObject "SpvRequest" $ \o -> SpvRequest + <$> o .: "requestKey" + <*> fmap Pact.ChainId (o .: "targetChainId") + {-# INLINE parseJSON #-} + +newtype TransactionOutputProofB64 = TransactionOutputProofB64 Text + deriving stock (Eq, Show, Generic) + deriving newtype (ToJSON, FromJSON) -- | Value that represents a limitation for rewinding. newtype RewindLimit = RewindLimit { _rewindLimit :: Word64 } deriving (Eq, Ord) deriving newtype (Show, FromJSON, ToJSON, Enum, Bounded) --- TODO: get rid of this shim, it's probably not necessary -data MemPoolAccess = MemPoolAccess - { mpaGetBlock - :: !(forall to. BlockFill - -> MempoolPreBlockCheck Pact4.UnparsedTransaction to - -> BlockHeight - -> BlockHash - -> BlockCreationTime - -> IO (Vector to) - ) - , mpaSetLastHeader :: !(BlockHeader -> IO ()) - , mpaProcessFork :: !(BlockHeader -> IO ()) - , mpaBadlistTx :: !(Vector TransactionHash -> IO ()) - } - -instance Semigroup MemPoolAccess where - MemPoolAccess f g h i <> MemPoolAccess t u v w = - MemPoolAccess (f <> t) (g <> u) (h <> v) (i <> w) - -instance Monoid MemPoolAccess where - mempty = MemPoolAccess mempty mempty mempty mempty - -data PactServiceEnv logger tbl = PactServiceEnv - { _psMempoolAccess :: !(Maybe MemPoolAccess) - , _psCheckpointer :: !(Checkpointer logger) - , _psPdb :: !(PayloadStore (PayloadDb tbl) PayloadData) - , _psCandidatePdb :: !(MapTable RankedBlockPayloadHash PayloadData) - , _psBlockHeaderDb :: !BlockHeaderDb - , _psPreInsertCheckTimeout :: !Micros - -- ^ Maximum allowed execution time for the transactions validation. - , _psReorgLimit :: !RewindLimit - -- ^ The limit of checkpointer's rewind in the `execValidationBlock` command. - , _psOnFatalError :: !(forall a. PactException -> Text -> IO a) - , _psVersion :: !ChainwebVersion - , _psAllowReadsInLocal :: !Bool - , _psLogger :: !logger - , _psGasLogger :: !(Maybe logger) - - , _psBlockGasLimit :: !Pact4.GasLimit - - , _psEnableLocalTimeout :: !Bool - , _psTxFailuresCounter :: !(Maybe (Counter "txFailures")) - , _psTxTimeLimit :: !(Maybe Micros) - , _psMiner :: !(Maybe Miner) - } -makeLenses ''PactServiceEnv - -instance HasChainwebVersion (PactServiceEnv logger c) where - _chainwebVersion = _chainwebVersion . _psBlockHeaderDb - {-# INLINE _chainwebVersion #-} - -instance HasChainId (PactServiceEnv logger c) where - _chainId = _chainId . _psBlockHeaderDb - {-# INLINE _chainId #-} - defaultReorgLimit :: RewindLimit defaultReorgLimit = RewindLimit 480 defaultPreInsertCheckTimeout :: Micros defaultPreInsertCheckTimeout = 1000000 -- 1 second --- | Default limit for the per chain size of the decoded module cache. --- --- default limit: 60 MiB per chain --- -defaultModuleCacheLimit :: DbCacheLimitBytes -defaultModuleCacheLimit = DbCacheLimitBytes (60 * mebi) - --- | Externally-injected PactService properties. --- -data PactServiceConfig = PactServiceConfig - { _pactReorgLimit :: !RewindLimit - -- ^ Maximum allowed reorg depth, implemented as a rewind limit in validate. New block - -- hardcodes this to 8 currently. - , _pactPreInsertCheckTimeout :: !Micros - -- ^ Maximum allowed execution time for the transactions validation. - , _pactAllowReadsInLocal :: !Bool - -- ^ Allow direct database reads in local mode - , _pactQueueSize :: !Natural - -- ^ max size of pact internal queue. - , _pactResetDb :: !Bool - -- ^ blow away pact dbs - , _pactUnlimitedInitialRewind :: !Bool - -- ^ disable initial rewind limit - , _pactNewBlockGasLimit :: !Pact4.GasLimit - -- ^ the gas limit for new block creation, not for validation - , _pactLogGas :: !Bool - -- ^ whether to write transaction gas logs at INFO - , _pactModuleCacheLimit :: !DbCacheLimitBytes - -- ^ limit of the database module cache in bytes of corresponding row data - , _pactFullHistoryRequired :: !Bool - -- ^ Whether or not the node requires that the full Pact history be - -- available. Compaction can remove history. - , _pactEnableLocalTimeout :: !Bool - -- ^ Whether to enable the local timeout to prevent long-running transactions - , _pactPersistIntraBlockWrites :: !IntraBlockPersistence - -- ^ Whether or not the node requires that all writes made in a block - -- are persisted. Useful if you want to use PactService BlockTxHistory. - , _pactTxTimeLimit :: !(Maybe Micros) - -- ^ *Only affects Pact5* - -- Maximum allowed execution time for a single transaction. - -- If 'Nothing', it's a function of the BlockGasLimit. - -- - -- FIXME: this seems dangerous. It could fork the chain! - , _pactMiner :: !(Maybe Miner) - } deriving (Eq,Show) - - --- | NOTE this is only used for tests/benchmarks. DO NOT USE IN PROD -testPactServiceConfig :: PactServiceConfig -testPactServiceConfig = PactServiceConfig - { _pactReorgLimit = defaultReorgLimit - , _pactPreInsertCheckTimeout = defaultPreInsertCheckTimeout - , _pactQueueSize = 1000 - , _pactResetDb = True - , _pactAllowReadsInLocal = False - , _pactUnlimitedInitialRewind = False - , _pactNewBlockGasLimit = testBlockGasLimit - , _pactLogGas = False - , _pactModuleCacheLimit = defaultModuleCacheLimit - , _pactFullHistoryRequired = False - , _pactEnableLocalTimeout = False - , _pactPersistIntraBlockWrites = DoNotPersistIntraBlockWrites - , _pactTxTimeLimit = Nothing - , _pactMiner = Nothing - } - --- | This default value is only relevant for testing. In a chainweb-node the @GasLimit@ --- is initialized from the @_configBlockGasLimit@ value of @ChainwebConfiguration@. --- -testBlockGasLimit :: Pact4.GasLimit -testBlockGasLimit = 100000 - -newtype ReorgLimitExceeded = ReorgLimitExceeded Text - -instance Show ReorgLimitExceeded where - show (ReorgLimitExceeded t) = "reorg limit exceeded: \n" <> T.unpack t - -instance Exception ReorgLimitExceeded where - fromException = asyncExceptionFromException - toException = asyncExceptionToException - -newtype TxTimeout = TxTimeout TransactionHash - deriving Show -instance Exception TxTimeout - -data Pact4TxFailureLog = Pact4TxFailureLog !Pact4.RequestKey !Pact4.PactError !Text - deriving stock (Generic) - deriving anyclass (NFData, Typeable) -instance LogMessage Pact4TxFailureLog where - logText (Pact4TxFailureLog rk err msg) = - msg <> ": " <> sshow rk <> ": " <> sshow err -instance Show Pact4TxFailureLog where - show m = T.unpack (logText m) - -data Pact5TxFailureLog = Pact5TxFailureLog !Pact5.RequestKey !Text - deriving stock (Generic) - deriving anyclass (NFData, Typeable) -instance LogMessage Pact5TxFailureLog where - logText (Pact5TxFailureLog rk msg) = - "Failed tx " <> sshow rk <> ": " <> msg -instance Show Pact5TxFailureLog where - show m = T.unpack (logText m) - -defaultOnFatalError :: forall a. (LogLevel -> Text -> IO ()) -> PactException -> Text -> IO a -defaultOnFatalError lf pex t = do - lf Error errMsg - throw $ ReorgLimitExceeded errMsg - where - errMsg = T.pack (show pex) <> "\n" <> t - -type ModuleInitCache = M.Map BlockHeight Pact4.ModuleCache - -data PactServiceState = PactServiceState - { _psInitCache :: !ModuleInitCache - } - -makeLenses ''PactServiceState - -data PactBlockEnv logger pv tbl = PactBlockEnv - { _psServiceEnv :: !(PactServiceEnv logger tbl) - , _psParentHeader :: !(Parent BlockHeader) - , _psIsGenesis :: !Bool - , _psBlockDbEnv :: !(PactDbFor logger pv) - } - -makeLenses ''PactBlockEnv - -instance HasChainwebVersion (PactBlockEnv logger db tbl) where - chainwebVersion = psServiceEnv . chainwebVersion -instance HasChainId (PactBlockEnv logger db tbl) where - chainId = psServiceEnv . chainId - --- | The top level monad of PactService, notably allowing access to a --- checkpointer and module init cache and some configuration parameters. -newtype PactServiceM logger tbl a = PactServiceM - { _unPactServiceM :: - ReaderT (PactServiceEnv logger tbl) (StateT PactServiceState IO) a - } deriving newtype - ( Functor, Applicative, Monad - , MonadReader (PactServiceEnv logger tbl) - , MonadState PactServiceState - , MonadThrow, MonadCatch, MonadMask - , MonadIO - ) - --- | Support lifting bracket style continuations in 'IO' into 'PactServiceM' by --- providing a function that allows unwrapping pact actions in IO while --- threading through the pact service state. --- --- /NOTE:/ This must not be used to access the pact service state from another --- thread. --- -withPactState - :: forall logger tbl b - . (Logger logger) - => ((forall a . PactServiceM logger tbl a -> IO a) -> IO b) - -> PactServiceM logger tbl b -withPactState inner = bracket captureState releaseState $ \ref -> do - e <- ask - liftIO $ inner $ \act -> mask $ \umask -> do - s <- readIORef ref - T2 r s' <- umask $ runPactServiceM s e act - writeIORef ref s' - return r - where - captureState = liftIO . newIORef =<< get - releaseState = liftIO . readIORef >=> put - --- | Run a 'PactServiceM' computation given some initial --- reader and state values, returning final value and --- final program state --- -runPactServiceM - :: PactServiceState - -> PactServiceEnv logger tbl - -> PactServiceM logger tbl a - -> IO (T2 a PactServiceState) -runPactServiceM st env act - = view (from _T2) - <$> runStateT (runReaderT (_unPactServiceM act) env) st - --- | Run a 'PactServiceM' computation given some initial --- reader and state values, discarding final state --- -evalPactServiceM - :: PactServiceState - -> PactServiceEnv logger tbl - -> PactServiceM logger tbl a - -> IO a -evalPactServiceM st env act - = evalStateT (runReaderT (_unPactServiceM act) env) st - --- | Run a 'PactServiceM' computation given some initial --- reader and state values, discarding final state --- -execPactServiceM - :: PactServiceState - -> PactServiceEnv logger tbl - -> PactServiceM logger tbl a - -> IO PactServiceState -execPactServiceM st env act - = execStateT (runReaderT (_unPactServiceM act) env) st - --- -------------------------------------------------------------------------- -- --- Pact Logger - -pactLogLevel :: String -> LogLevel -pactLogLevel "INFO" = Info -pactLogLevel "ERROR" = Error -pactLogLevel "DEBUG" = Debug -pactLogLevel "WARN" = Warn -pactLogLevel _ = Info - --- | Create Pact Loggers that use the the chainweb logging system as backend. --- -pactLoggers :: Logger logger => logger -> Pact4.Loggers -pactLoggers logger = Pact4.Loggers $ Pact4.mkLogger (error "ignored") fun (Pact4.LogRules mempty) - where - fun :: Pact4.LoggerLogFun - fun _ (Pact4.LogName n) cat msg = do - let namedLogger = addLabel ("logger", T.pack n) logger - logFunctionText namedLogger (pactLogLevel cat) $ T.pack msg - --- | Write log message --- -logg_ :: (MonadIO m, Logger logger) => logger -> LogLevel -> Text -> m () -logg_ logger level msg = liftIO $ logFunction logger level msg - --- | Write log message using the logger in Checkpointer environment - -logInfo_ :: (MonadIO m, Logger logger) => logger -> Text -> m () -logInfo_ l = logg_ l Info - -logWarn_ :: (MonadIO m, Logger logger) => logger -> Text -> m () -logWarn_ l = logg_ l Warn - -logError_ :: (MonadIO m, Logger logger) => logger -> Text -> m () -logError_ l = logg_ l Error - -logDebug_ :: (MonadIO m, Logger logger) => logger -> Text -> m () -logDebug_ l = logg_ l Debug - -logJsonTrace_ :: (MonadIO m, ToJSON a, Typeable a, NFData a, Logger logger) => logger -> LogLevel -> JsonLog a -> m () -logJsonTrace_ logger level msg = liftIO $ logFunction logger level msg - --- | Write log message using the logger in Checkpointer environment --- -logPact :: (Logger logger) => LogLevel -> Text -> PactServiceM logger tbl () -logPact level msg = view psLogger >>= \l -> logg_ l level msg - -logInfoPact :: (Logger logger) => Text -> PactServiceM logger tbl () -logInfoPact msg = view psLogger >>= \l -> logInfo_ l msg - -logWarnPact :: (Logger logger) => Text -> PactServiceM logger tbl () -logWarnPact msg = view psLogger >>= \l -> logWarn_ l msg - -logErrorPact :: (Logger logger) => Text -> PactServiceM logger tbl () -logErrorPact msg = view psLogger >>= \l -> logError_ l msg - -logDebugPact :: (Logger logger) => Text -> PactServiceM logger tbl () -logDebugPact msg = view psLogger >>= \l -> logDebug_ l msg - -logJsonTracePact :: (ToJSON a, Typeable a, NFData a, Logger logger) => LogLevel -> JsonLog a -> PactServiceM logger tbl () -logJsonTracePact level msg = view psLogger >>= \l -> logJsonTrace_ l level msg - -localLabelPact :: (Logger logger) => (Text, Text) -> PactServiceM logger tbl x -> PactServiceM logger tbl x -localLabelPact lbl x = do - locally psLogger (addLabel lbl) x - - -data PactServiceException = PactServiceIllegalRewind - { _attemptedRewindTo :: !(Maybe (BlockHeight, BlockHash)) - , _latestBlock :: !(Maybe (BlockHeight, BlockHash)) - } deriving (Generic) - -instance Show PactServiceException where - show (PactServiceIllegalRewind att l) - = concat - [ "illegal rewind attempt to block " - , show att - , ", latest was " - , show l - ] - -instance Exception PactServiceException - -makePrisms ''Historical - -- | Value that represents how far to go backwards while rewinding. newtype RewindDepth = RewindDepth { _rewindDepth :: Word64 } deriving (Eq, Ord) @@ -841,6 +217,7 @@ data LocalPreflightSimulation | LegacySimulation deriving stock (Eq, Show, Generic) + -- | The type of local results (used in /local endpoint) -- Internally contains a JsonText instead of a CommandResult for compatibility across Pact versions, -- but the constructors are hidden. @@ -853,58 +230,6 @@ data LocalResult | LocalTimeout deriving stock (Generic, Show) -pattern ConvertLocalResultLegacy :: (FromJSON a, J.Encode a) => a -> LocalResult -pattern ConvertLocalResultLegacy cr <- LocalResultLegacy (Aeson.decode . TL.encodeUtf8 . TL.fromStrict . J.getJsonText -> Just cr) where - ConvertLocalResultLegacy cr = LocalResultLegacy (J.encodeJsonText cr) - -pattern Pact4LocalResultLegacy - :: Pact4.CommandResult Pact4.Hash - -> LocalResult -pattern Pact4LocalResultLegacy cr <- ConvertLocalResultLegacy cr where - Pact4LocalResultLegacy cr = ConvertLocalResultLegacy cr -_Pact4LocalResultLegacy :: Prism' LocalResult (Pact4.CommandResult Pact4.Hash) -_Pact4LocalResultLegacy = prism' Pact4LocalResultLegacy $ \case - Pact4LocalResultLegacy cr -> Just cr - _ -> Nothing - -pattern Pact5LocalResultLegacy - :: Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError - -> LocalResult -pattern Pact5LocalResultLegacy cr <- ConvertLocalResultLegacy cr where - Pact5LocalResultLegacy cr = ConvertLocalResultLegacy cr -_Pact5LocalResultLegacy :: Prism' LocalResult (Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError) -_Pact5LocalResultLegacy = prism' Pact5LocalResultLegacy $ \case - Pact5LocalResultLegacy cr -> Just cr - _ -> Nothing - -pattern ConvertLocalResultWithWarns :: (FromJSON a, J.Encode a) => a -> [Text] -> LocalResult -pattern ConvertLocalResultWithWarns cr warns <- LocalResultWithWarns (Aeson.decode . TL.encodeUtf8 . TL.fromStrict . J.getJsonText -> Just cr) warns where - ConvertLocalResultWithWarns cr warns = LocalResultWithWarns (J.encodeJsonText cr) warns - -pattern Pact4LocalResultWithWarns - :: Pact4.CommandResult Pact4.Hash - -> [Text] - -> LocalResult -pattern Pact4LocalResultWithWarns cr warns <- ConvertLocalResultWithWarns cr warns where - Pact4LocalResultWithWarns cr warns = ConvertLocalResultWithWarns cr warns -_Pact4LocalResultWithWarns :: Prism' LocalResult (Pact4.CommandResult Pact4.Hash, [Text]) -_Pact4LocalResultWithWarns = prism' (uncurry Pact4LocalResultWithWarns) $ \case - Pact4LocalResultWithWarns cr warns -> Just (cr, warns) - _ -> Nothing - -pattern Pact5LocalResultWithWarns - :: Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError - -> [Text] - -> LocalResult -pattern Pact5LocalResultWithWarns cr warns <- ConvertLocalResultWithWarns cr warns where - Pact5LocalResultWithWarns cr warns = ConvertLocalResultWithWarns cr warns -_Pact5LocalResultWithWarns :: Prism' LocalResult (Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError, [Text]) -_Pact5LocalResultWithWarns = prism' (uncurry Pact5LocalResultWithWarns) $ \case - Pact5LocalResultWithWarns cr warns -> Just (cr, warns) - _ -> Nothing - -makePrisms ''LocalResult - instance J.Encode LocalResult where build (MetadataValidationFailure e) = J.object [ "preflightValidationFailures" J..= J.Array (J.text <$> e) @@ -940,426 +265,290 @@ instance FromJSON LocalResult where legacyFallbackParser _ = LocalResultLegacy $ J.encodeJsonText v --- | Used in tests for matching on JSON serialized pact exceptions --- -newtype PactExceptionTag = PactExceptionTag Text - deriving (Show, Eq) - -instance FromJSON PactExceptionTag where - parseJSON = withObject "PactExceptionTag" $ \o -> PactExceptionTag - <$> o .: "tag" - --- --- | Gather tx logs for a block, along with last tx for each --- --- key in history, if any --- --- Not intended for public API use; ToJSONs are for logging output. --- -data BlockTxHistory = BlockTxHistory --- - { _blockTxHistory :: !(Map TxId [TxLog RowData]) --- - , _blockPrevHistory :: !(Map RowKey (TxLog RowData)) --- - } --- - deriving (Eq,Generic) --- -instance Show BlockTxHistory where --- - show = show . fmap (J.encodeText . J.Array) . _blockTxHistory --- -instance NFData BlockTxHistory --- | Gather tx logs for a block, along with last tx for each --- key in history, if any --- Not intended for public API use; ToJSONs are for logging output. --- data BlockTxHistory = BlockTxHistory --- { _blockTxHistory :: !(Map TxId [Pact5.TxLog Pact5.RowData]) --- , _blockPrevHistory :: !(Map RowKey (Pact5.TxLog Pact5.RowData)) --- } --- deriving (Eq,Generic) --- instance Show BlockTxHistory where --- show = show . fmap (show) . _blockTxHistory --- TODO: fix show above --- instance NFData BlockTxHistory -- TODO: add NFData for RowData - -internalError :: (HasCallStack, MonadThrow m) => Text -> m a -internalError = throwM . PactInternalError callStack - -throwIfNoHistory :: (HasCallStack, MonadThrow m) => Historical a -> m a -throwIfNoHistory NoHistory = internalError "missing history" -throwIfNoHistory (Historical a) = return a - -data RequestCancelled = RequestCancelled - deriving (Eq, Show) -instance Exception RequestCancelled where - toException = asyncExceptionToException - fromException = asyncExceptionFromException - --- graph-easy < [ RequestNotStarted ] - cancelled -> [ RequestFailed ] --- > [ RequestNotStarted ] - started -> [ RequestInProgress ] --- > [ RequestInProgress ] - cancelled/failed -> [ RequestFailed ] --- > [ RequestInProgress ] - completed -> [ RequestDone ] --- > EOF +-- | Externally-injected PactService properties. -- --- +-------------------+ started +-------------------+ completed +-------------+ --- | RequestNotStarted | ------------------> | RequestInProgress | -----------> | RequestDone | --- +-------------------+ +-------------------+ +-------------+ --- | | --- | cancelled | --- v | --- +-------------------+ cancelled/failed | --- | RequestFailed | <---------------------+ --- +-------------------+ -data RequestStatus r - = RequestDone !r - | RequestInProgress - | RequestNotStarted - | RequestFailed !SomeException -data SubmittedRequestMsg - = forall r. SubmittedRequestMsg (RequestMsg r) (TVar (RequestStatus r)) -instance Show SubmittedRequestMsg where - show (SubmittedRequestMsg msg _) = show msg - -data RequestMsg r where - ContinueBlockMsg :: !(ContinueBlockReq pv) -> RequestMsg (Historical (BlockInProgress pv)) - NewBlockMsg :: !NewBlockReq -> RequestMsg (Historical (ForSomePactVersion BlockInProgress)) - ValidateBlockMsg :: !ValidateBlockReq -> RequestMsg PayloadWithOutputs - LocalMsg :: !LocalReq -> RequestMsg LocalResult - LookupPactTxsMsg :: !LookupPactTxsReq -> RequestMsg (HashMap ShortByteString (T2 BlockHeight BlockHash)) - PreInsertCheckMsg :: !PreInsertCheckReq -> RequestMsg (Vector (Maybe InsertError)) - BlockTxHistoryMsg :: !BlockTxHistoryReq -> RequestMsg (Historical BlockTxHistory) - HistoricalLookupMsg :: !HistoricalLookupReq -> RequestMsg (Historical (Maybe (Pact5.TxLog Pact5.RowData))) - SyncToBlockMsg :: !SyncToBlockReq -> RequestMsg () - ReadOnlyReplayMsg :: !ReadOnlyReplayReq -> RequestMsg () - CloseMsg :: RequestMsg () - -instance Show (RequestMsg r) where - show (NewBlockMsg req) = show req - show (ContinueBlockMsg req) = show req - show (ValidateBlockMsg req) = show req - show (LocalMsg req) = show req - show (LookupPactTxsMsg req) = show req - show (PreInsertCheckMsg req) = show req - show (BlockTxHistoryMsg req) = show req - show (HistoricalLookupMsg req) = show req - show (SyncToBlockMsg req) = show req - show (ReadOnlyReplayMsg req) = show req - show CloseMsg = "CloseReq" - -data NewBlockReq - = NewBlockReq - { _newBlockFill :: !NewBlockFill - -- ^ whether to fill this block with transactions; if false, the block - -- will be empty. - , _newBlockParent :: !(Parent BlockHeader) - -- ^ the parent to use for the new block - } deriving stock Show - -data NewBlockFill = NewBlockFill | NewBlockEmpty - deriving stock Show - -data ContinueBlockReq pv - = ContinueBlockReq (BlockInProgress pv) -instance Show (ContinueBlockReq pv) where - showsPrec p (ContinueBlockReq bip) = - showParen (p > 10) $ - showString "ContinueBlockReq " . showsPrec 11 p . showString " " . - (case _blockInProgressPactVersion bip of {Pact4T -> showsPrec 11 bip; Pact5T -> showsPrec 11 bip}) - -data ValidateBlockReq = ValidateBlockReq - { _valBlockHeader :: !BlockHeader - , _valCheckablePayload :: !CheckablePayload - } deriving stock Show - -data LocalReq = LocalReq - { _localRequest :: !Pact4.UnparsedTransaction - , _localPreflight :: !(Maybe LocalPreflightSimulation) - , _localSigVerification :: !(Maybe LocalSignatureVerification) - , _localRewindDepth :: !(Maybe RewindDepth) - } -instance Show LocalReq where show LocalReq{..} = show _localRequest +data PactServiceConfig = PactServiceConfig + { _pactReorgLimit :: !RewindLimit + -- ^ Maximum allowed reorg depth, implemented as a rewind limit in validate. New block + -- hardcodes this to 8 currently. + , _pactPreInsertCheckTimeout :: !Micros + -- ^ Maximum allowed execution time for the transactions validation. + , _pactAllowReadsInLocal :: !Bool + -- ^ Allow direct database reads in local mode + , _pactQueueSize :: !Natural + -- ^ max size of pact internal queue. + , _pactUnlimitedInitialRewind :: !Bool + -- ^ disable initial rewind limit + , _pactNewBlockGasLimit :: !Pact.GasLimit + -- ^ the gas limit for new block creation, not for validation + , _pactLogGas :: !Bool + -- ^ whether to write transaction gas logs at INFO + , _pactModuleCacheLimit :: !DbCacheLimitBytes + -- ^ limit of the database module cache in bytes of corresponding row data + , _pactFullHistoryRequired :: !Bool + -- ^ Whether or not the node requires that the full Pact history be + -- available. Compaction can remove history. + , _pactEnableLocalTimeout :: !Bool + -- ^ Whether to enable the local timeout to prevent long-running transactions + , _pactPersistIntraBlockWrites :: !IntraBlockPersistence + -- ^ Whether or not the node requires that all writes made in a block + -- are persisted. Useful if you want to use PactService BlockTxHistory. + , _pactTxTimeLimit :: !(Maybe Micros) + -- ^ *Only affects Pact5* + -- Maximum allowed execution time for a single transaction. + -- If 'Nothing', it's a function of the BlockGasLimit. + -- + , _pactMiner :: !(Maybe Miner) + } deriving (Eq,Show) -data LookupPactTxsReq = LookupPactTxsReq - { _lookupConfirmationDepth :: !(Maybe ConfirmationDepth) - , _lookupKeys :: !(Vector ShortByteString) - } -instance Show LookupPactTxsReq where - show (LookupPactTxsReq m _) = - "LookupPactTxsReq@" ++ show m +-- TODO: get rid of this shim, it's probably not necessary +data MemPoolAccess = MemPoolAccess + { mpaGetBlock + :: !(forall to. BlockFill + -> MempoolPreBlockCheck Pact.Transaction to + -> BlockHeight + -> BlockHash + -> BlockCreationTime + -> IO (Vector to) + ) + , mpaSetLastHeader :: !(BlockHeader -> IO ()) + , mpaProcessFork :: !(BlockHeader -> IO ()) + , mpaBadlistTx :: !(Vector TransactionHash -> IO ()) + } -data PreInsertCheckReq = PreInsertCheckReq - { _preInsCheckTxs :: !(Vector (Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text))) - } -instance Show PreInsertCheckReq where - show (PreInsertCheckReq v) = - "PreInsertCheckReq@" ++ show v +instance Semigroup MemPoolAccess where + MemPoolAccess f g h i <> MemPoolAccess t u v w = + MemPoolAccess (f <> t) (g <> u) (h <> v) (i <> w) -data BlockTxHistoryReq = BlockTxHistoryReq - { _blockTxHistoryHeader :: !BlockHeader - , _blockTxHistoryDomain :: !(Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info) - } -instance Show BlockTxHistoryReq where - show (BlockTxHistoryReq h d) = - "BlockTxHistoryReq@" ++ show h ++ ", " ++ show d - -data HistoricalLookupReq = HistoricalLookupReq - { _historicalLookupHeader :: !BlockHeader - , _historicalLookupDomain :: !(Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info) - , _historicalLookupRowKey :: !Pact5.RowKey - } -instance Show HistoricalLookupReq where - show (HistoricalLookupReq h d k) = - "HistoricalLookupReq@" ++ show h ++ ", " ++ show d ++ ", " ++ show k +instance Monoid MemPoolAccess where + mempty = MemPoolAccess mempty mempty mempty mempty -data ReadOnlyReplayReq = ReadOnlyReplayReq - { _readOnlyReplayLowerBound :: !BlockHeader - , _readOnlyReplayUpperBound :: !(Maybe BlockHeader) - } -instance Show ReadOnlyReplayReq where - show (ReadOnlyReplayReq l u) = - "ReadOnlyReplayReq@" ++ show l ++ ", " ++ show u +data PactServiceEnv logger tbl = PactServiceEnv + { _psMempoolAccess :: !(Maybe MemPoolAccess) + , _psCheckpointer :: !(Checkpointer logger) + , _psPdb :: !(PayloadStore (PayloadDb tbl) PayloadData) + , _psCandidatePdb :: !(MapTable RankedBlockPayloadHash PayloadData) + , _psPreInsertCheckTimeout :: !Micros + -- ^ Maximum allowed execution time for the transactions validation. + , _psReorgLimit :: !RewindLimit + -- ^ The limit of checkpointer's rewind in the `execValidationBlock` command. + , _psVersion :: !ChainwebVersion + , _psChainId :: !ChainId + , _psAllowReadsInLocal :: !Bool + , _psLogger :: !logger + , _psGasLogger :: !(Maybe logger) + + , _psBlockGasLimit :: !Pact.GasLimit -data SyncToBlockReq = SyncToBlockReq - { _syncToBlockHeader :: !BlockHeader + , _psEnableLocalTimeout :: !Bool + , _psTxFailuresCounter :: !(Maybe (Counter "txFailures")) + , _psTxTimeLimit :: !(Maybe Micros) + , _psMiner :: !(Maybe Miner) } -instance Show SyncToBlockReq where show SyncToBlockReq{..} = show _syncToBlockHeader +makeLenses ''PactServiceEnv -data SpvRequest = SpvRequest - { _spvRequestKey :: !Pact4.RequestKey - , _spvTargetChainId :: !Pact4.ChainId - } deriving (Eq, Show, Generic) -instance J.Encode SpvRequest where - build r = J.object - [ "requestKey" J..= _spvRequestKey r - , "targetChainId" J..= _spvTargetChainId r - ] - {-# INLINE build #-} +instance HasChainwebVersion (PactServiceEnv logger c) where + chainwebVersion = psVersion + {-# INLINE chainwebVersion #-} +instance HasChainId (PactServiceEnv logger c) where + chainId = psChainId + {-# INLINE chainId #-} -instance FromJSON SpvRequest where - parseJSON = withObject "SpvRequest" $ \o -> SpvRequest - <$> o .: "requestKey" - <*> o .: "targetChainId" - {-# INLINE parseJSON #-} +-- | The top level monad of PactService, notably allowing access to a +-- checkpointer and module init cache and some configuration parameters. +newtype PactServiceM logger tbl a = PactServiceM + { runPactServiceM :: + ReaderT (PactServiceEnv logger tbl) IO a + } deriving newtype + ( Functor, Applicative, Monad + , MonadReader (PactServiceEnv logger tbl) + , MonadThrow, MonadCatch, MonadMask + , MonadIO + ) -newtype TransactionOutputProofB64 = TransactionOutputProofB64 Text - deriving stock (Eq, Show, Generic) - deriving newtype (ToJSON, FromJSON) +withPactState + :: forall logger tbl b + . Logger logger + => ((forall a. PactServiceM logger tbl a -> IO a) -> IO b) + -> PactServiceM logger tbl b +withPactState inner = do + e <- ask + liftIO $ inner $ \act -> + runReaderT (runPactServiceM act) e + +data PactBlockEnv logger tbl = PactBlockEnv + { _psServiceEnv :: !(PactServiceEnv logger tbl) + , _psParentHeader :: !(Parent BlockHeader) + , _psIsGenesis :: !Bool + , _psBlockDbEnv :: !ChainwebPactDb + } +makeLenses ''PactBlockEnv -data family ModuleCacheFor (pv :: PactVersion) -newtype instance ModuleCacheFor Pact4 - = Pact4ModuleCache Pact4.ModuleCache - deriving newtype (Eq, Show, Monoid, Semigroup) -data instance ModuleCacheFor Pact5 - = Pact5NoModuleCache - deriving (Eq, Show) -instance Monoid (ModuleCacheFor Pact5) where - mempty = Pact5NoModuleCache -instance Semigroup (ModuleCacheFor Pact5) where - _ <> _ = Pact5NoModuleCache - -type family CommandResultFor (pv :: PactVersion) where - CommandResultFor Pact4 = Pact4.CommandResult [Pact4.TxLogJson] - CommandResultFor Pact5 = Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info) - --- State from a block in progress, which is used to extend blocks after --- running their payloads. -data BlockInProgress pv = BlockInProgress - { _blockInProgressHandle :: !(BlockHandle pv) - , _blockInProgressModuleCache :: !(ModuleCacheFor pv) - , _blockInProgressParentHeader :: !(Maybe (Parent BlockHeader)) - , _blockInProgressChainwebVersion :: !ChainwebVersion - , _blockInProgressChainId :: !ChainId - , _blockInProgressRemainingGasLimit :: !Pact4.GasLimit - , _blockInProgressMiner :: !Miner - , _blockInProgressTransactions :: !(Transactions pv (CommandResultFor pv)) - , _blockInProgressPactVersion :: !(PactVersionT pv) +instance HasChainwebVersion (PactBlockEnv logger tbl) where + chainwebVersion = psServiceEnv . chainwebVersion +instance HasChainId (PactBlockEnv logger tbl) where + chainId = psServiceEnv . chainId + +data PactBlockState = PactBlockState + { _pbBlockHandle :: !BlockHandle } -instance Eq (BlockInProgress pv) where - bip == bip' = - case (_blockInProgressPactVersion bip, _blockInProgressPactVersion bip') of - (Pact4T, Pact4T) -> - _blockInProgressHandle bip == _blockInProgressHandle bip' && - _blockInProgressModuleCache bip == _blockInProgressModuleCache bip' && - _blockInProgressParentHeader bip == _blockInProgressParentHeader bip' && - _blockInProgressRemainingGasLimit bip == _blockInProgressRemainingGasLimit bip' && - _blockInProgressMiner bip == _blockInProgressMiner bip' && - _blockInProgressTransactions bip == _blockInProgressTransactions bip' - (Pact5T, Pact5T) -> - _blockInProgressHandle bip == _blockInProgressHandle bip' && - _blockInProgressModuleCache bip == _blockInProgressModuleCache bip' && - _blockInProgressParentHeader bip == _blockInProgressParentHeader bip' && - _blockInProgressRemainingGasLimit bip == _blockInProgressRemainingGasLimit bip' && - _blockInProgressMiner bip == _blockInProgressMiner bip' && - _blockInProgressTransactions bip == _blockInProgressTransactions bip' - -instance HasChainwebVersion (BlockInProgress pv) where - _chainwebVersion = _blockInProgressChainwebVersion - {-# INLINE _chainwebVersion #-} - -instance HasChainId (BlockInProgress pv) where - _chainId = _blockInProgressChainId - {-# INLINE _chainId #-} - -blockInProgressParent :: BlockInProgress pv -> (BlockHash, BlockHeight, BlockCreationTime) -blockInProgressParent bip = - maybe - (genesisParentBlockHash v cid, genesisHeight v cid, v ^?! versionGenesis . genesisTime . atChain cid) - (\bh -> (view blockHash bh, view blockHeight bh, view blockCreationTime bh)) - (unwrapParent <$> _blockInProgressParentHeader bip) - where - v = _blockInProgressChainwebVersion bip - cid = _blockInProgressChainId bip - -instance Show (BlockInProgress pv) where - show bip = unwords - [ case _blockInProgressPactVersion bip of - Pact4T -> - "Pact4 block," - Pact5T -> - "Pact5 block," - , T.unpack (blockHashToTextShort $ fromMaybe - (genesisParentBlockHash (_blockInProgressChainwebVersion bip) (_blockInProgressChainId bip)) - (view blockHash . unwrapParent <$> _blockInProgressParentHeader bip)) - , show (_blockInProgressMiner bip ^. minerId) - , "# transactions " <> show (V.length (_transactionPairs $ _blockInProgressTransactions bip)) <> "," - , "# gas remaining " <> show (_blockInProgressRemainingGasLimit bip) - ] +makeLenses ''PactBlockState -finalizeBlock :: BlockInProgress pv -> PayloadWithOutputs -finalizeBlock bip = - toPayloadWithOutputs - (_blockInProgressPactVersion bip) - (_blockInProgressMiner bip) - (_blockInProgressTransactions bip) - -pact4CommandToBytes :: Pact4.Command Text -> Transaction -pact4CommandToBytes cwTrans = - let plBytes = J.encodeStrict cwTrans - in Transaction { _transactionBytes = plBytes } - -pact4CommandResultToBytes :: Pact4.CommandResult Pact4.Hash -> TransactionOutput -pact4CommandResultToBytes cr = - let outBytes = J.encodeStrict cr - in TransactionOutput { _transactionOutputBytes = outBytes } - -hashPact4TxLogs :: Pact4.CommandResult [Pact4.TxLogJson] -> Pact4.CommandResult Pact4.Hash -hashPact4TxLogs = over (Pact4.crLogs . _Just) $ Pact4.pactHash . Pact4.encodeTxLogJsonArray - -pact5CommandToBytes :: Pact5.Command Text -> Transaction -pact5CommandToBytes tx = Transaction - { _transactionBytes = - J.encodeStrict tx - } +-- | A sub-monad of PactServiceM, for actions taking place at a particular block. +newtype PactBlockM logger tbl a = PactBlockM + { _unPactBlockM :: + ReaderT (PactBlockEnv logger tbl) (StateT PactBlockState IO) a + } deriving newtype + ( Functor, Applicative, Monad + , MonadReader (PactBlockEnv logger tbl) + , MonadState PactBlockState + , MonadThrow, MonadCatch, MonadMask + , MonadIO + ) + +-- | Lifts PactServiceM to PactBlockM by forgetting about the current block. +-- It is unsafe to use `runPactBlockM` inside the argument to this function. +liftPactServiceM :: PactServiceM logger tbl a -> PactBlockM logger tbl a +liftPactServiceM (PactServiceM a) = + PactBlockM $ ReaderT $ \e -> + liftIO $ runReaderT a (_psServiceEnv e) + +-- | Run 'PactBlockM' by providing the block context, in the form of +-- a database snapshot at that block and information about the parent header. +-- It is unsafe to use this function in an argument to `liftPactServiceM`. +runPactBlockM + :: Parent BlockHeader -> Bool -> ChainwebPactDb -> BlockHandle + -> PactBlockM logger tbl a -> PactServiceM logger tbl (a, BlockHandle) +runPactBlockM pctx isGenesis dbEnv startBlockHandle (PactBlockM act) = PactServiceM $ ReaderT $ \e -> do + let blockEnv = PactBlockEnv + { _psServiceEnv = e + , _psParentHeader = pctx + , _psIsGenesis = isGenesis + , _psBlockDbEnv = dbEnv + } + (a, s') <- runStateT + (runReaderT act blockEnv) + (PactBlockState startBlockHandle) + return ((a, _pbBlockHandle s')) + +tracePactBlockM :: (Logger logger, ToJSON param) => Text -> param -> Int -> PactBlockM logger tbl a -> PactBlockM logger tbl a +tracePactBlockM label param weight a = tracePactBlockM' label (const param) (const weight) a + +tracePactBlockM' :: (Logger logger, ToJSON param) => Text -> (a -> param) -> (a -> Int) -> PactBlockM logger tbl a -> PactBlockM logger tbl a +tracePactBlockM' label calcParam calcWeight a = do + e <- ask + s <- get + (r, s') <- liftIO $ trace' (logJsonTrace_ (_psLogger $ _psServiceEnv e)) label (calcParam . fst) (calcWeight . fst) + $ runStateT (runReaderT (_unPactBlockM a) e) s + put s' + return r + +logJsonTrace_ :: (MonadIO m, ToJSON a, Typeable a, NFData a, Logger logger) => logger -> LogLevel -> JsonLog a -> m () +logJsonTrace_ logger level msg = liftIO $ logFunction logger level msg + +-- | Pair parent header with transaction metadata. +-- In cases where there is no transaction/Command, 'PublicMeta' +-- default value is used. +data TxContext = TxContext + { _tcParentCreationTime :: !(Parent BlockCreationTime) + , _tcParentHash :: !(Parent BlockHash) + , _tcParentHeight :: !(Parent BlockHeight) + , _tcChainId :: !ChainId + , _tcChainwebVersion :: !ChainwebVersion + , _tcMinerReward :: !MinerReward + , _tcMiner :: !Miner + } deriving Show + +instance HasChainId TxContext where + _chainId = _tcChainId +instance HasChainwebVersion TxContext where + _chainwebVersion = _tcChainwebVersion + +-- | Get "current" block height, which means parent height + 1. +-- This reflects Pact environment focus on current block height, +-- which influenced legacy switch checks as well. +ctxCurrentBlockHeight :: TxContext -> BlockHeight +ctxCurrentBlockHeight = succ . unwrapParent . _tcParentHeight + +guardCtx :: (ChainwebVersion -> Chainweb.ChainId -> BlockHeight -> a) -> TxContext -> a +guardCtx g txCtx = g (_chainwebVersion txCtx) (_chainId txCtx) (ctxCurrentBlockHeight txCtx) + +pactTransaction :: Maybe RequestKey -> (PactDb Pact.CoreBuiltin Pact.Info -> IO a) -> PactBlockM logger tbl a +pactTransaction rk k = do + e <- view psBlockDbEnv + h <- use pbBlockHandle + (r, h') <- liftIO $ doChainwebPactDbTransaction e h rk k + pbBlockHandle .= h' + return r + +-- | Indicates a computed gas charge (gas amount * gas price) +newtype GasSupply = GasSupply { _pact5GasSupply :: Decimal } + deriving (Eq,Ord) + deriving newtype (Num,Real,Fractional) + +instance J.Encode GasSupply where + build = J.build . Pact.StableEncoding . Pact.LDecimal . _pact5GasSupply +instance Show GasSupply where show (GasSupply g) = show g + +localLabelBlock :: (Logger logger) => (Text, Text) -> PactBlockM logger tbl x -> PactBlockM logger tbl x +localLabelBlock lbl x = do + locally (psServiceEnv . psLogger) (addLabel lbl) x + +-- -------------------------------------------------------------------------- -- +-- Default Values +-- +-- TODO: move to Pact5 --- | This function converts CommandResults into bytes in a stable way that can --- be stored on-chain. -pact5CommandResultToBytes :: Pact5.CommandResult Pact5.Hash (Pact5.PactError Pact5.Info) -> ByteString -pact5CommandResultToBytes cr = - J.encodeStrict (fmap Pact5.pactErrorToOnChainError cr) - -convertPact5Error :: Pact5.PactError Pact5.Info -> Pact5.PactOnChainError -convertPact5Error err = - Pact5.pactErrorToOnChainError err - -hashPact5TxLogs :: Pact5.CommandResult [Pact5.TxLog ByteString] err -> Pact5.CommandResult Pact5.Hash err -hashPact5TxLogs cr = cr & over (Pact5.crLogs . _Just) - (\ls -> Pact5.hashTxLogs ls) - -toPayloadWithOutputs :: PactVersionT pv -> Miner -> Transactions pv (CommandResultFor pv) -> PayloadWithOutputs -toPayloadWithOutputs Pact4T mi ts = - let oldSeq = _transactionPairs ts - trans = cmdBSToTx . fst <$> oldSeq - transOuts = pact4CommandResultToBytes . hashPact4TxLogs . snd <$> oldSeq - - miner = toMinerData mi - cb = CoinbaseOutput $ J.encodeStrict $ hashPact4TxLogs $ _transactionCoinbase ts - blockTrans = snd $ newBlockTransactions miner trans - cmdBSToTx = pact4CommandToBytes - . fmap (T.decodeUtf8 . SB.fromShort . Pact4.payloadBytes) - blockOuts = snd $ newBlockOutputs cb transOuts - - blockPL = blockPayload blockTrans blockOuts - plData = payloadData blockTrans blockPL - in payloadWithOutputs plData cb transOuts -toPayloadWithOutputs Pact5T mi ts = - let - oldSeq :: Vector (TransactionFor Pact5, CommandResultFor Pact5) - oldSeq = _transactionPairs ts - trans :: Vector Transaction - trans = cmdBSToTx . fst <$> oldSeq - transOuts :: Vector TransactionOutput - transOuts = TransactionOutput . pact5CommandResultToBytes . hashPact5TxLogs . snd <$> oldSeq - - miner :: MinerData - miner = toMinerData mi - cb :: CoinbaseOutput - cb = CoinbaseOutput $ pact5CommandResultToBytes $ hashPact5TxLogs $ _transactionCoinbase ts - blockTrans :: BlockTransactions - blockTrans = snd $ newBlockTransactions miner trans - cmdBSToTx :: Pact5.Transaction -> Transaction - cmdBSToTx = pact5CommandToBytes - . fmap (T.decodeUtf8 . SB.fromShort . view Pact5.payloadBytes) - blockOuts :: BlockOutputs - blockOuts = snd $ newBlockOutputs cb transOuts - - blockPL :: BlockPayload - blockPL = blockPayload blockTrans blockOuts - plData :: PayloadData - plData = payloadData blockTrans blockPL - in payloadWithOutputs plData cb transOuts - -type family TransactionFor (pv :: PactVersion) where - TransactionFor Pact4 = Pact4.Transaction - TransactionFor Pact5 = Pact5.Transaction - -data Transactions (pv :: PactVersion) r = Transactions - { _transactionPairs :: !(Vector (TransactionFor pv, r)) - , _transactionCoinbase :: !(CommandResultFor pv) +noInfo :: Pact.Info +noInfo = Pact.LineInfo 0 + +emptyCapState :: Ord name => Ord v => Pact.CapState name v +emptyCapState = Pact.CapState mempty mempty mempty mempty mempty + +noSpanInfo :: Pact.SpanInfo +noSpanInfo = Pact.SpanInfo 0 0 0 0 + +noPublicMeta :: Pact.PublicMeta +noPublicMeta = Pact.PublicMeta + { Pact._pmChainId = Pact.ChainId "" + , Pact._pmSender = "" + , Pact._pmGasLimit = Pact.GasLimit (Pact.Gas 0) + , Pact._pmGasPrice = Pact.GasPrice 0 + , Pact._pmTTL = Pact.TTLSeconds 0 + , Pact._pmCreationTime = Pact.TxCreationTime 0 } - deriving stock (Functor, Foldable, Traversable, Generic) -deriving stock instance Eq r => Eq (Transactions Pact4 r) -deriving stock instance Eq r => Eq (Transactions Pact5 r) -deriving stock instance Show r => Show (Transactions Pact4 r) -deriving stock instance Show r => Show (Transactions Pact5 r) -deriving anyclass instance NFData r => NFData (Transactions Pact4 r) --- why doesn't this compile? --- deriving anyclass instance NFData r => NFData (Transactions Pact5 r) -instance NFData r => NFData (Transactions Pact5 r) where - rnf txs = - rnf (_transactionPairs txs) - `seq` rnf (_transactionCoinbase) - -makeLenses 'Transactions -makeLenses 'BlockInProgress -data AssertValidateSigsError - = SignersAndSignaturesLengthMismatch - { _signersLength :: !Int - , _signaturesLength :: !Int - } - | InvalidSignerScheme - { _position :: !Int - } - | InvalidSignerWebAuthnPrefix - { _position :: !Int - } - | InvalidUserSig - { _position :: !Int - , _errMsg :: Text - } +data BuyGasError + = BuyGasPactError !(Pact.PactError Pact.Info) + | BuyGasMultipleGasPayerCaps -displayAssertValidateSigsError :: AssertValidateSigsError -> Text -displayAssertValidateSigsError = \case - SignersAndSignaturesLengthMismatch signersLength sigsLength -> - "The number of signers and signatures do not match. Number of signers: " <> sshow signersLength <> ". Number of signatures: " <> sshow sigsLength <> "." - InvalidSignerScheme pos -> - "The signer at position " <> sshow pos <> " has an invalid signature scheme." - InvalidSignerWebAuthnPrefix pos -> - "The signer at position " <> sshow pos <> " has an invalid WebAuthn prefix." - InvalidUserSig pos errMsg -> - "The signature at position " <> sshow pos <> " is invalid: " <> errMsg <> "." +data RedeemGasError + = RedeemGasPactError !(Pact.PactError Pact.Info) -data AssertCommandError - = InvalidPayloadHash - | AssertValidateSigsError AssertValidateSigsError +data TxInvalidError + = BuyGasError !BuyGasError + | RedeemGasError !RedeemGasError + | PurchaseGasTxTooBigForGasLimit -displayAssertCommandError :: AssertCommandError -> Text -displayAssertCommandError = \case - InvalidPayloadHash -> "The hash of the payload was invalid." - AssertValidateSigsError err -> displayAssertValidateSigsError err +-- | Write log message +-- +logg_ :: (MonadIO m, Logger logger) => logger -> LogLevel -> Text -> m () +logg_ logger level msg = liftIO $ logFunction logger level msg + +-- | Write log message using the logger in Checkpointer environment + +logInfo_ :: (MonadIO m, Logger logger) => logger -> Text -> m () +logInfo_ l = logg_ l Info + +logWarn_ :: (MonadIO m, Logger logger) => logger -> Text -> m () +logWarn_ l = logg_ l Warn + +logError_ :: (MonadIO m, Logger logger) => logger -> Text -> m () +logError_ l = logg_ l Error + +logDebug_ :: (MonadIO m, Logger logger) => logger -> Text -> m () +logDebug_ l = logg_ l Debug diff --git a/src/Chainweb/Pact5/Validations.hs b/src/Chainweb/Pact/Validations.hs similarity index 90% rename from src/Chainweb/Pact5/Validations.hs rename to src/Chainweb/Pact/Validations.hs index bdafd74796..8e606f5fe4 100644 --- a/src/Chainweb/Pact5/Validations.hs +++ b/src/Chainweb/Pact/Validations.hs @@ -2,7 +2,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} -- | --- Module: Chainweb.Pact5.Validations +-- Module: Chainweb.Pact.Validations -- Copyright: Copyright © 2018,2019,2020,2021,2022 Kadena LLC. -- License: See LICENSE file -- Maintainers: Lars Kuhtz, Emily Pillmore, Stuart Popejoy, Greg Hale @@ -13,7 +13,7 @@ -- - The codepath for adding transactions to the mempool -- - The codepath for letting users test their transaction via /local -- -module Chainweb.Pact5.Validations +module Chainweb.Pact.Validations ( -- * Local metadata _validation assertPreflightMetadata -- * Validation checks @@ -56,11 +56,8 @@ import qualified Pact.Core.Command.Types as P import qualified Pact.Core.ChainData as P import qualified Pact.Core.Gas.Types as P import qualified Pact.Core.Hash as P -import qualified Chainweb.Pact5.Transaction as P -import qualified Pact.Types.Gas as Pact4 -import qualified Pact.Parse as Pact4 -import Chainweb.Pact5.Types -import qualified Chainweb.Pact5.Transaction as Pact5 +import qualified Chainweb.Pact.Transaction as P +import qualified Chainweb.Pact.Transaction as Pact5 import Chainweb.Utils (ebool_) @@ -72,9 +69,9 @@ assertPreflightMetadata -> Maybe LocalSignatureVerification -> PactServiceM logger tbl (Either (NonEmpty Text) ()) assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do - v <- view psVersion + v <- view chainwebVersion cid <- view chainId - Pact4.GasLimit (Pact4.ParsedInteger bgl) <- view psBlockGasLimit + bgl <- view psBlockGasLimit let P.PublicMeta pcid _ gl gp _ _ = P._pMeta pay nid = P._pNetworkId pay @@ -84,7 +81,7 @@ assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do [ eUnless "Chain id mismatch" $ assertChainId cid pcid -- TODO: use failing conversion , eUnless "Transaction Gas limit exceeds block gas limit" - $ assertBlockGasLimit (P.GasLimit $ P.Gas (fromIntegral @Integer @P.SatWord bgl)) gl + $ assertBlockGasLimit bgl gl , eUnless "Gas price decimal precision too high" $ assertGasPrice gp , eUnless "Network id mismatch" $ assertNetworkId v nid , eUnless "Signature list size too big" $ assertSigSize sigs @@ -100,11 +97,7 @@ assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do | Just NoVerify <- sigVerify = True | otherwise = isRight $ assertValidateSigs hsh signers sigs - pct = ParentCreationTime - . view blockCreationTime - . _parentHeader - . _tcParentHeader - $ txCtx + pct = _tcParentCreationTime txCtx eUnless t assertion | assertion = Nothing @@ -180,10 +173,10 @@ assertValidateSigs hsh signers sigs = do -- skipped when replaying old blocks. -- assertTxTimeRelativeToParent - :: ParentCreationTime + :: Parent BlockCreationTime -> P.Command (P.Payload P.PublicMeta c) -> Bool -assertTxTimeRelativeToParent (ParentCreationTime (BlockCreationTime txValidationTime)) tx = +assertTxTimeRelativeToParent (Parent (BlockCreationTime txValidationTime)) tx = ttl > 0 && txValidationTime >= timeFromSeconds 0 && txOriginationTime >= 0 @@ -197,10 +190,10 @@ assertTxTimeRelativeToParent (ParentCreationTime (BlockCreationTime txValidation -- | Check that the tx's creation time is not too far in the future relative -- to the block creation time assertTxNotInFuture - :: ParentCreationTime + :: Parent BlockCreationTime -> P.Command (P.Payload P.PublicMeta c) -> Bool -assertTxNotInFuture (ParentCreationTime (BlockCreationTime txValidationTime)) tx = +assertTxNotInFuture (Parent (BlockCreationTime txValidationTime)) tx = timeFromSeconds txOriginationTime <= lenientTxValidationTime where timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral diff --git a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs deleted file mode 100644 index 80226154d6..0000000000 --- a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs +++ /dev/null @@ -1,891 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ImportQualifiedPost #-} --- TODO pact5: fix the orphan PactDbFor instance -{-# OPTIONS_GHC -Wno-orphans #-} -{-# LANGUAGE ViewPatterns #-} - --- | --- Module: Chainweb.Pact4.Backend.ChainwebPactDb --- Copyright: Copyright © 2018 - 2020 Kadena LLC. --- License: MIT --- Maintainer: Emmanuel Denloye-Ito --- Stability: experimental --- - -module Chainweb.Pact4.Backend.ChainwebPactDb -( chainwebPactDb -, rewoundPactDb -, indexPactTransaction -, vacuumDb -, toTxLog -, CurrentBlockDbEnv(..) -, cpPactDbEnv -, cpRegisterProcessedTx -, cpLookupProcessedTx -, callDb -, BlockEnv(..) -, blockHandlerEnv -, benvBlockState -, runBlockEnv -, BlockState(..) -, bsPendingBlock -, bsTxId -, initBlockState -, BlockHandler(..) -, BlockHandlerEnv(..) -, blockHandlerDb -, blockHandlerLogger -, blockHandlerBlockHeight -, blockHandlerModuleNameFix -, blockHandlerSortedKeys -, blockHandlerLowerCaseTables -, blockHandlerPersistIntraBlockWrites -, mkBlockHandlerEnv - -, domainTableName -, convKeySetName -, convModuleName -, convNamespaceName -, convRowKey -, convPactId - -, commitBlockStateToDatabase -) where - -import Control.Applicative -import Control.Lens -import Control.Monad -import Control.Monad.Reader -import Control.Monad.State.Strict -import Control.Monad.Trans.Maybe - -import Data.Aeson hiding ((.=)) -import qualified Data.ByteString as BS -import qualified Data.ByteString.Char8 as B8 -import qualified Data.DList as DL -import Data.List(sort) -import Data.List.NonEmpty (NonEmpty(..)) -import qualified Data.List.NonEmpty as NE -import qualified Data.HashMap.Strict as HashMap -import qualified Data.HashSet as HashSet -import qualified Data.Map.Strict as M -import Data.Maybe -import qualified Data.Set as Set -import Data.String -import qualified Data.Text as T -import qualified Data.Text.Encoding as T - -import Database.SQLite3.Direct as SQ3 - -import Prelude hiding (concat, log) - --- pact - -import Pact.Persist -import Pact.PersistPactDb hiding (db) -import Pact.Types.Persistence -import Pact.Types.RowData -import Pact.Types.SQLite -import Pact.Types.Term (ModuleName(..), ObjectMap(..), TableName(..), KeySetName(..), NamespaceName(..), PactId(..)) -import Pact.Types.Util (AsString(..)) - -import qualified Pact.JSON.Encode as J -import qualified Pact.JSON.Legacy.HashMap as LHM - --- chainweb - -import Chainweb.BlockHash -import Chainweb.BlockHeight -import Chainweb.Logger -import Chainweb.Pact.Backend.DbCache -import Chainweb.Pact.Backend.Utils -import Chainweb.Pact.Types -import Chainweb.Utils -import Chainweb.Version -import Pact.Interpreter (PactDbEnv) -import Data.HashMap.Strict (HashMap) -import Data.Vector (Vector) -import Control.Concurrent -import Chainweb.Version.Guards -import Control.Exception.Safe -import Pact.Types.Command (RequestKey) -import Chainweb.Pact.Backend.Types -import Chainweb.Utils.Serialization (runPutS) -import Data.Foldable - -execMulti :: Traversable t => SQ3.Database -> SQ3.Utf8 -> t [SType] -> IO () -execMulti db q rows = bracket (prepStmt db q) destroy $ \stmt -> do - forM_ rows $ \row -> do - SQ3.reset stmt >>= checkError - SQ3.clearBindings stmt - bindParams stmt row - SQ3.step stmt >>= checkError - where - checkError (Left e) = void $ fail $ "error during batch insert: " ++ show e - checkError (Right _) = return () - - destroy x = void (SQ3.finalize x >>= checkError) - -domainTableName :: Domain k v -> Text -domainTableName = asString - -convKeySetName :: KeySetName -> SQ3.Utf8 -convKeySetName = toUtf8 . asString - -convModuleName - :: Bool - -- ^ whether to apply module name fix - -> ModuleName - -> SQ3.Utf8 -convModuleName False (ModuleName name _) = toUtf8 name -convModuleName True mn = asStringUtf8 mn - -convNamespaceName :: NamespaceName -> SQ3.Utf8 -convNamespaceName (NamespaceName name) = toUtf8 name - -convRowKey :: RowKey -> SQ3.Utf8 -convRowKey (RowKey name) = toUtf8 name - -convPactId :: PactId -> SQ3.Utf8 -convPactId = toUtf8 . sshow - -callDb - :: (MonadCatch m, MonadReader (BlockHandlerEnv logger) m, MonadIO m) - => T.Text - -> (SQ3.Database -> IO b) - -> m b -callDb callerName action = do - c <- asks _blockHandlerDb - res <- tryAny $ liftIO $ action c - case res of - Left err -> internalError $ "callDb (" <> callerName <> "): " <> sshow err - Right r -> return r - -data BlockHandlerEnv logger = BlockHandlerEnv - { _blockHandlerDb :: !SQLiteEnv - , _blockHandlerLogger :: !logger - , _blockHandlerBlockHeight :: !BlockHeight - , _blockHandlerModuleNameFix :: !Bool - , _blockHandlerSortedKeys :: !Bool - , _blockHandlerLowerCaseTables :: !Bool - , _blockHandlerPersistIntraBlockWrites :: !IntraBlockPersistence - } - -mkBlockHandlerEnv - :: ChainwebVersion -> ChainId -> BlockHeight - -> SQLiteEnv -> IntraBlockPersistence -> logger -> BlockHandlerEnv logger -mkBlockHandlerEnv v cid bh sql p logger = BlockHandlerEnv - { _blockHandlerDb = sql - , _blockHandlerLogger = logger - , _blockHandlerBlockHeight = bh - , _blockHandlerModuleNameFix = enableModuleNameFix v cid bh - , _blockHandlerSortedKeys = pact42 v cid bh - , _blockHandlerLowerCaseTables = chainweb217Pact v cid bh - , _blockHandlerPersistIntraBlockWrites = p - } - -makeLenses ''BlockHandlerEnv - -data BlockEnv logger = - BlockEnv - { _blockHandlerEnv :: !(BlockHandlerEnv logger) - , _benvBlockState :: !BlockState -- ^ The current block state. - } - -runBlockEnv :: MVar (BlockEnv logger) -> BlockHandler logger a -> IO a -runBlockEnv e m = modifyMVar e $ - \(BlockEnv dbenv bs) -> do - (!a,!s) <- runStateT (runReaderT (runBlockHandler m) dbenv) bs - return (BlockEnv dbenv s, a) - --- this monad allows access to the database environment "at" a particular block. --- unfortunately, this is tied to a useless MVar via runBlockEnv, which will --- be deleted with pact 5. -newtype BlockHandler logger a = BlockHandler - { runBlockHandler :: ReaderT (BlockHandlerEnv logger) (StateT BlockState IO) a - } deriving newtype - ( Functor - , Applicative - , Monad - , MonadState BlockState - , MonadThrow - , MonadCatch - , MonadMask - , MonadIO - , MonadReader (BlockHandlerEnv logger) - ) - --- | Monad state for 'BlockHandler. --- This notably contains all of the information that's being mutated during --- blocks, notably _bsPendingBlock, the pending writes in the block, and --- _bsPendingTx, the pending writes in the transaction which will be discarded --- on tx failure. -data BlockState = BlockState - { _bsTxId :: !TxId - , _bsPendingBlock :: !(SQLitePendingData (PendingWrites Pact4)) - , _bsPendingTx :: !(Maybe (SQLitePendingData (PendingWrites Pact4))) - , _bsMode :: !(Maybe ExecutionMode) - , _bsModuleCache :: !(DbCache PersistModuleData) - } -initBlockState - :: DbCacheLimitBytes - -- ^ Module Cache Limit (in bytes of corresponding rowdata) - -> TxId - -- ^ next tx id (end txid of previous block) - -> BlockState -initBlockState cl txid = BlockState - { _bsTxId = txid - , _bsMode = Nothing - , _bsPendingBlock = emptySQLitePendingData mempty - , _bsPendingTx = Nothing - , _bsModuleCache = emptyDbCache cl - } - -makeLenses ''BlockEnv -makeLenses ''BlockState - - --- this is effectively a read-write snapshot of the Pact state at a block. -data CurrentBlockDbEnv logger = CurrentBlockDbEnv - { _cpPactDbEnv :: !(PactDbEnv (BlockEnv logger)) - , _cpRegisterProcessedTx :: !(RequestKey -> IO ()) - , _cpLookupProcessedTx :: - !(Vector RequestKey -> IO (HashMap RequestKey (T2 BlockHeight BlockHash))) - } -makeLenses ''CurrentBlockDbEnv - -type instance PactDbFor logger Pact4 = CurrentBlockDbEnv logger - --- | Pact DB which reads from the tip of the checkpointer -chainwebPactDb :: (Logger logger) => PactDb (BlockEnv logger) -chainwebPactDb = PactDb - { _readRow = \d k e -> runBlockEnv e $ doReadRow Nothing d k - , _writeRow = \wt d k v e -> runBlockEnv e $ doWriteRow Nothing wt d k v - , _keys = \d e -> runBlockEnv e $ doKeys Nothing d - , _txids = \t txid e -> runBlockEnv e $ doTxIds t txid - , _createUserTable = \tn mn e -> runBlockEnv e $ doCreateUserTable Nothing tn mn - , _getUserTableInfo = \_ -> error "WILL BE DEPRECATED!" - , _beginTx = \m e -> runBlockEnv e $ doBegin m - , _commitTx = \e -> runBlockEnv e doCommit - , _rollbackTx = \e -> runBlockEnv e doRollback - , _getTxLog = \d tid e -> runBlockEnv e $ doGetTxLog d tid - } - --- | Pact DB which reads from some past block height, instead of the tip of the checkpointer -rewoundPactDb :: (Logger logger) => BlockHeight -> TxId -> PactDb (BlockEnv logger) -rewoundPactDb bh endTxId = chainwebPactDb - { _readRow = \d k e -> runBlockEnv e $ doReadRow (Just (bh, endTxId)) d k - , _writeRow = \wt d k v e -> runBlockEnv e $ doWriteRow (Just (bh, endTxId)) wt d k v - , _keys = \d e -> runBlockEnv e $ doKeys (Just (bh, endTxId)) d - , _createUserTable = \tn mn e -> runBlockEnv e $ doCreateUserTable (Just bh) tn mn - } - --- returns pending writes in the reverse order they were made -getPendingData :: BlockHandler logger [SQLitePendingData (PendingWrites Pact4)] -getPendingData = do - pb <- use bsPendingBlock - ptx <- maybeToList <$> use bsPendingTx - -- lookup in pending transactions first - return $ ptx ++ [pb] - -forModuleNameFix :: (Bool -> BlockHandler logger a) -> BlockHandler logger a -forModuleNameFix f = view blockHandlerModuleNameFix >>= f - --- TODO: speed this up, cache it? -tableExistsInDbAtHeight :: Text -> BlockHeight -> BlockHandler logger Bool -tableExistsInDbAtHeight tableName bh = do - let knownTbls = - ["SYS:Pacts", "SYS:Modules", "SYS:KeySets", "SYS:Namespaces", "SYS:ModuleSources"] - if tableName `elem` knownTbls - then return True - else callDb "tableExists" $ \db -> do - let tableExistsStmt = - -- table names are case-sensitive - "SELECT tablename FROM VersionedTableCreation WHERE createBlockheight < ? AND lower(tablename) = lower(?)" - qry db tableExistsStmt [SInt $ max 0 (fromIntegral bh), SText (toUtf8 tableName)] [RText] >>= \case - [] -> return False - _ -> return True - -doReadRow - :: (IsString k, FromJSON v) - => Maybe (BlockHeight, TxId) - -- ^ the highest block we should be reading writes from - -> Domain k v - -> k - -> BlockHandler logger (Maybe v) -doReadRow mlim d k = forModuleNameFix $ \mnFix -> - case d of - KeySets -> lookupWithKey (convKeySetName k) noCache - -- TODO: This is incomplete (the modules case), due to namespace - -- resolution concerns - Modules -> lookupWithKey (convModuleName mnFix k) checkModuleCache - Namespaces -> lookupWithKey (convNamespaceName k) noCache - (UserTables _) -> lookupWithKey (convRowKey k) noCache - Pacts -> lookupWithKey (convPactId k) noCache - where - tableName = domainTableName d - - lookupWithKey - :: forall logger v . FromJSON v - => Utf8 - -> (Utf8 -> BS.ByteString -> MaybeT (BlockHandler logger ) v) - -> BlockHandler logger (Maybe v) - lookupWithKey key checkCache = do - pds <- getPendingData - let lookPD = foldr1 (<|>) $ map (lookupInPendingData key) pds - let lookDB = lookupInDb key checkCache - runMaybeT (lookPD <|> lookDB) - - lookupInPendingData - :: forall logger v . FromJSON v - => Utf8 - -> SQLitePendingData (PendingWrites Pact4) - -> MaybeT (BlockHandler logger) v - lookupInPendingData (Utf8 rowkey) p = do - -- we get the latest-written value at this rowkey - allKeys <- hoistMaybe $ HashMap.lookup tableName (_pendingWrites p) - ddata <- _deltaData . NE.head <$> hoistMaybe (HashMap.lookup rowkey allKeys) - MaybeT $ return $! decodeStrict' ddata - - lookupInDb - :: forall logger v . FromJSON v - => Utf8 - -> (Utf8 -> BS.ByteString -> MaybeT (BlockHandler logger) v) - -> MaybeT (BlockHandler logger) v - lookupInDb rowkey checkCache = do - -- First, check: did we create this table during this block? If so, - -- there's no point in looking up the key. - checkDbTablePendingCreation tableName - lift $ forM_ mlim $ \(bh, _) -> - failIfTableDoesNotExistInDbAtHeight "doReadRow" tableName bh - -- we inject the endingtx limitation to reduce the scope up to the provided block height - let blockLimitStmt = maybe "" (const " AND txid < ?") mlim - let blockLimitParam = maybe [] (\(TxId txid) -> [SInt $ fromIntegral txid]) (snd <$> mlim) - let queryStmt = - "SELECT rowdata FROM " <> tbl (toUtf8 tableName) <> " WHERE rowkey = ?" <> blockLimitStmt - <> " ORDER BY txid DESC LIMIT 1;" - result <- lift $ callDb "doReadRow" - $ \db -> qry db queryStmt ([SText rowkey] ++ blockLimitParam) [RBlob] - case result of - [] -> mzero - [[SBlob a]] -> checkCache rowkey a - err -> internalError $ - "doReadRow: Expected (at most) a single result, but got: " <> - T.pack (show err) - - checkModuleCache u b = MaybeT $ do - !txid <- use bsTxId -- cache priority - mc <- use bsModuleCache - (r, mc') <- liftIO $ checkDbCache u decodeStrict b txid mc - modify' (bsModuleCache .~ mc') - return r - - noCache - :: FromJSON v - => Utf8 - -> BS.ByteString - -> MaybeT (BlockHandler logger) v - noCache _key rowdata = MaybeT $ return $! decodeStrict' rowdata - - -checkDbTablePendingCreation :: Text -> MaybeT (BlockHandler logger) () -checkDbTablePendingCreation tableName = do - pds <- lift getPendingData - forM_ pds $ \p -> - when (HashSet.member tableName (_pendingTableCreation p)) mzero - -writeSys - :: (AsString k, J.Encode v) - => Domain k v - -> k - -> v - -> BlockHandler logger () -writeSys d k v = gets _bsTxId >>= go - where - go txid = do - forModuleNameFix $ \mnFix -> - recordPendingUpdate (getKeyString mnFix k) tableName txid v - recordTxLog (TableName tableName) d k v - - tableName = domainTableName d - - getKeyString mnFix = case d of - KeySets -> convKeySetName - Modules -> convModuleName mnFix - Namespaces -> convNamespaceName - Pacts -> convPactId - UserTables _ -> error "impossible" - -recordPendingUpdate - :: J.Encode v - => Utf8 - -> Text - -> TxId - -> v - -> BlockHandler logger () -recordPendingUpdate (Utf8 key) tn txid v = modifyPendingData modf - where - !vs = J.encodeStrict v - delta = SQLiteRowDelta tn txid key vs - - modf = over pendingWrites upd - upd = HashMap.unionWith - HashMap.union - (HashMap.singleton tn - (HashMap.singleton key (NE.singleton delta))) - - -checkInsertIsOK - :: Maybe (BlockHeight, TxId) - -- ^ the highest block we should be reading writes from - -> WriteType - -> Domain RowKey RowData - -> RowKey - -> BlockHandler logger (Maybe RowData) -checkInsertIsOK mlim wt d k = do - olds <- doReadRow mlim d k - case (olds, wt) of - (Nothing, Insert) -> return Nothing - (Just _, Insert) -> err "Insert: row found for key " - (Nothing, Write) -> return Nothing - (Just old, Write) -> return $ Just old - (Just old, Update) -> return $ Just old - (Nothing, Update) -> err "Update: no row found for key " - where - err msg = internalError $ "checkInsertIsOK: " <> msg <> asString k - -writeUser - :: Maybe (BlockHeight, TxId) - -- ^ the highest block we should be reading writes from - -> WriteType - -> Domain RowKey RowData - -> RowKey - -> RowData - -> BlockHandler logger () -writeUser mlim wt d k rowdata@(RowData _ row) = gets _bsTxId >>= go - where - tn = domainTableName d - ttn = TableName tn - - go txid = do - m <- checkInsertIsOK mlim wt d k - row' <- case m of - Nothing -> ins - (Just old) -> upd old - recordTxLog ttn d k row' - - where - upd (RowData oldV oldrow) = do - let row' = RowData oldV $ ObjectMap (M.union (_objectMap row) (_objectMap oldrow)) - recordPendingUpdate (convRowKey k) tn txid row' - return row' - - ins = do - recordPendingUpdate (convRowKey k) tn txid rowdata - return rowdata - -doWriteRow - :: (AsString k, J.Encode v) - => Maybe (BlockHeight, TxId) - -- ^ the highest block we should be reading writes from - -> WriteType - -> Domain k v - -> k - -> v - -> BlockHandler logger () -doWriteRow mlim wt d k v = case d of - (UserTables _) -> writeUser mlim wt d k v - _ -> writeSys d k v - -doKeys - :: (IsString k) - => Maybe (BlockHeight, TxId) - -- ^ the highest block we should be reading writes from - -> Domain k v - -> BlockHandler logger [k] -doKeys mlim d = do - msort <- views blockHandlerSortedKeys (\c -> if c then sort else id) - dbKeys <- getDbKeys - pb <- use bsPendingBlock - mptx <- use bsPendingTx - - let memKeys = fmap (B8.unpack . _deltaRowKey) - $ collect pb ++ maybe [] collect mptx - - let !allKeys = fmap fromString - $ msort -- becomes available with Pact42Upgrade - $ LHM.sort - $ dbKeys ++ memKeys - return allKeys - - where - blockLimitStmt = maybe "" (const " WHERE txid < ?;") mlim - blockLimitParam = maybe [] (\(TxId txid) -> [SInt (fromIntegral txid)]) (snd <$> mlim) - getDbKeys = do - m <- runMaybeT $ checkDbTablePendingCreation $ tn - case m of - Nothing -> return mempty - Just () -> do - forM_ mlim (failIfTableDoesNotExistInDbAtHeight "doKeys" tn . fst) - ks <- callDb "doKeys" $ \db -> - qry db ("SELECT DISTINCT rowkey FROM " <> tbl (toUtf8 tn) <> blockLimitStmt) blockLimitParam [RText] - forM ks $ \row -> do - case row of - [SText k] -> return $! T.unpack $ fromUtf8 k - _ -> internalError "doKeys: The impossible happened." - - tn = domainTableName d - collect p = - concatMap NE.toList $ HashMap.elems $ fromMaybe mempty $ HashMap.lookup tn (_pendingWrites p) -{-# INLINE doKeys #-} - -failIfTableDoesNotExistInDbAtHeight - :: Text -> Text -> BlockHeight -> BlockHandler logger () -failIfTableDoesNotExistInDbAtHeight caller tn bh = do - exists <- tableExistsInDbAtHeight tn bh - -- we must reproduce errors that were thrown in earlier blocks from tables - -- not existing, if this table does not yet exist. - unless exists $ - internalError $ "callDb (" <> caller <> "): user error (Database error: ErrorError)" - --- tid is non-inclusive lower bound for the search -doTxIds :: TableName -> TxId -> BlockHandler logger [TxId] -doTxIds (TableName tn) _tid@(TxId tid) = do - dbOut <- getFromDb - - -- also collect from pending non-committed data - pb <- use bsPendingBlock - mptx <- use bsPendingTx - - -- uniquify txids before returning - return $ Set.toList - $! Set.fromList - $ dbOut ++ collect pb ++ maybe [] collect mptx - - where - getFromDb = do - m <- runMaybeT $ checkDbTablePendingCreation tn - case m of - Nothing -> return mempty - Just () -> do - rows <- callDb "doTxIds" $ \db -> - qry db stmt - [SInt (fromIntegral tid)] - [RInt] - forM rows $ \case - [SInt tid'] -> return $ TxId (fromIntegral tid') - _ -> internalError "doTxIds: the impossible happened" - - stmt = "SELECT DISTINCT txid FROM " <> tbl (toUtf8 tn) <> " WHERE txid > ?" - - collect p = - let txids = fmap _deltaTxId $ - concatMap NE.toList $ - HashMap.elems $ - fromMaybe mempty $ - HashMap.lookup tn (_pendingWrites p) - in filter (> _tid) txids -{-# INLINE doTxIds #-} - -recordTxLog - :: (AsString k, J.Encode v) - => TableName - -> Domain k v - -> k - -> v - -> BlockHandler logger () -recordTxLog tt d k v = do - -- are we in a tx? - mptx <- use bsPendingTx - modify' $ case mptx of - Nothing -> over (bsPendingBlock . pendingTxLogMap) upd - (Just _) -> over (bsPendingTx . _Just . pendingTxLogMap) upd - - where - !upd = M.insertWith DL.append tt txlogs - !txlogs = DL.singleton $! encodeTxLog $ TxLog (asString d) (asString k) v - -modifyPendingData - :: (SQLitePendingData (PendingWrites Pact4) -> SQLitePendingData (PendingWrites Pact4)) - -> BlockHandler logger () -modifyPendingData f = do - m <- use bsPendingTx - modify' $ case m of - Just d -> set bsPendingTx (Just $! f d) - Nothing -> over bsPendingBlock f - -doCreateUserTable - :: Maybe BlockHeight - -- ^ the highest block we should be seeing tables from - -> TableName - -> ModuleName - -> BlockHandler logger () -doCreateUserTable mbh tn@(TableName ttxt) mn = do - -- first check if tablename already exists in pending queues - m <- runMaybeT $ checkDbTablePendingCreation ttxt - case m of - Nothing -> throwM $ PactDuplicateTableError ttxt - Just () -> do - -- then check if it is in the db - lcTables <- view blockHandlerLowerCaseTables - cond <- inDb lcTables ttxt - when cond $ throwM $ PactDuplicateTableError ttxt - modifyPendingData - $ over pendingTableCreation (HashSet.insert ttxt) - . over pendingTxLogMap (M.insertWith DL.append (TableName txlogKey) txlogs) - where - inDb lcTables t = do - r <- callDb "doCreateUserTable" $ \db -> - qry db (tableLookupStmt lcTables) [SText (toUtf8 t)] [RText] - case r of - [[SText (Utf8 (T.decodeUtf8 -> rname))]] -> - case mbh of - -- if lowercase matching, no need to check equality - -- (wasn't needed before either but leaving alone for replay) - Nothing -> return (lcTables || rname == t) - Just bh -> do - existsInDb <- tableExistsInDbAtHeight t bh - return $ existsInDb && (lcTables || rname == t) - _ -> return False - - tableLookupStmt False = - "SELECT name FROM sqlite_master WHERE type='table' and name=?;" - tableLookupStmt True = - "SELECT name FROM sqlite_master WHERE type='table' and lower(name)=lower(?);" - txlogKey = "SYS:usertables" - stn = asString tn - uti = UserTableInfo mn - txlogs = DL.singleton $ encodeTxLog $ TxLog txlogKey stn uti -{-# INLINE doCreateUserTable #-} - -doRollback :: BlockHandler logger () -doRollback = modify' - $ set bsMode Nothing - . set bsPendingTx Nothing - --- | Commit a Pact transaction -doCommit :: BlockHandler logger [TxLogJson] -doCommit = use bsMode >>= \case - Nothing -> doRollback >> internalError "doCommit: Not in transaction" - Just m -> do - txrs <- if m == Transactional - then do - modify' $ over bsTxId succ - -- merge pending tx into block data - pending <- use bsPendingTx - persistIntraBlockWrites <- view blockHandlerPersistIntraBlockWrites - modify' $ over bsPendingBlock (merge persistIntraBlockWrites pending) - -- this is mostly a lie; the previous `merge` call has already replaced the tx - -- logs from bsPendingBlock with those of the transaction. - -- from what I can tell, it's impossible for `pending` to be `Nothing` here, - -- but we don't throw an error for it. - blockLogs <- use $ bsPendingBlock . pendingTxLogMap - modify' $ set bsPendingTx Nothing - resetTemp - return blockLogs - else doRollback >> return mempty - return $! concatMap (reverse . DL.toList) txrs - where - merge _ Nothing a = a - merge persistIntraBlockWrites (Just txPending) blockPending = SQLitePendingData - { _pendingTableCreation = HashSet.union (_pendingTableCreation txPending) (_pendingTableCreation blockPending) - , _pendingWrites = HashMap.unionWith (HashMap.unionWith mergeAtRowKey) (_pendingWrites txPending) (_pendingWrites blockPending) - , _pendingTxLogMap = _pendingTxLogMap txPending - , _pendingSuccessfulTxs = _pendingSuccessfulTxs blockPending - } - where - mergeAtRowKey txWrites blockWrites = - let lastTxWrite = NE.head txWrites - in case persistIntraBlockWrites of - PersistIntraBlockWrites -> lastTxWrite `NE.cons` blockWrites - DoNotPersistIntraBlockWrites -> lastTxWrite :| [] -{-# INLINE doCommit #-} - --- | Begin a Pact transaction -doBegin :: (Logger logger) => ExecutionMode -> BlockHandler logger (Maybe TxId) -doBegin m = do - logger <- view blockHandlerLogger - use bsMode >>= \case - Just {} -> do - logError_ logger "PactDb.beginTx: In transaction, rolling back" - doRollback - Nothing -> return () - resetTemp - modify' - $ set bsMode (Just m) - . set bsPendingTx (Just $ emptySQLitePendingData mempty) - case m of - Transactional -> Just <$> use bsTxId - Local -> pure Nothing -{-# INLINE doBegin #-} - -resetTemp :: BlockHandler logger () -resetTemp = modify' - $ set bsMode Nothing - -- clear out txlog entries - . set (bsPendingBlock . pendingTxLogMap) mempty - -doGetTxLog :: Domain k RowData -> TxId -> BlockHandler logger [TxLog RowData] -doGetTxLog d txid = do - -- try to look up this tx from pending log -- if we find it there it can't - -- possibly be in the db. - p <- readFromPending - if null p then readFromDb else return p - - where - tableName = domainTableName d - - readFromPending = do - allPendingData <- getPendingData - let deltas = do - -- grab all pending writes in this transaction and elsewhere in - -- this block - pending <- allPendingData - -- all writes to the table - let writesAtTableByKey = - fromMaybe mempty $ HashMap.lookup tableName $ _pendingWrites pending - -- a list of all writes to the table for some particular key - allWritesForSomeKey <- HashMap.elems writesAtTableByKey - -- the single latest write to the table for that key which is - -- from this txid; the most recent writes are inserted at the - -- front of the pending data - latestWriteForSomeKey <- take 1 - [ writeForSomeKey - | writeForSomeKey <- NE.toList allWritesForSomeKey - , _deltaTxId writeForSomeKey == txid - ] - return latestWriteForSomeKey - mapM (\x -> toTxLog (asString d) (Utf8 $ _deltaRowKey x) (_deltaData x)) deltas - - readFromDb = do - rows <- callDb "doGetTxLog" $ \db -> qry db stmt - [SInt (fromIntegral txid)] - [RText, RBlob] - forM rows $ \case - [SText key, SBlob value] -> toTxLog (asString d) key value - err -> internalError $ - "readHistoryResult: Expected single row with two columns as the \ - \result, got: " <> T.pack (show err) - stmt = "SELECT rowkey, rowdata FROM " <> tbl (toUtf8 tableName) <> " WHERE txid = ?" - - -toTxLog :: MonadThrow m => - T.Text -> Utf8 -> BS.ByteString -> m (TxLog RowData) -toTxLog d key value = - case Data.Aeson.decodeStrict' value of - Nothing -> internalError $ "toTxLog: Unexpected value, unable to deserialize log: " <> T.decodeUtf8 value - Just v -> - return $! TxLog d (fromUtf8 key) v - --- | Register a successful transaction in the pending data for the block -indexPactTransaction :: BS.ByteString -> BlockHandler logger () -indexPactTransaction h = modify' $ - over (bsPendingBlock . pendingSuccessfulTxs) $ HashSet.insert h - - --- | Careful doing this! It's expensive and for our use case, probably pointless. --- We should reserve vacuuming for an offline process -vacuumDb :: BlockHandler logger () -vacuumDb = callDb "vacuumDb" (`exec_` "VACUUM;") - -commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockHeight -> BlockHandle Pact4 -> IO () -commitBlockStateToDatabase db hsh bh blockHandle = do - let newTables = _pendingTableCreation $ _blockHandlePending blockHandle - mapM_ (\tn -> createUserTable tn) newTables - let writeV = toChunks $ _pendingWrites (_blockHandlePending blockHandle) - backendWriteUpdateBatch writeV - indexPendingPactTransactions - let nextTxId = _blockHandleTxId blockHandle - blockHistoryInsert nextTxId - where - toChunks writes = - over _2 (concatMap toList . HashMap.elems) . - over _1 toUtf8 <$> HashMap.toList writes - - backendWriteUpdateBatch - :: [(Utf8, [SQLiteRowDelta])] - -> IO () - backendWriteUpdateBatch writesByTable = mapM_ writeTable writesByTable - where - prepRow (SQLiteRowDelta _ txid rowkey rowdata) = - [ SText (Utf8 rowkey) - , SInt (fromIntegral txid) - , SBlob rowdata - ] - - writeTable (tableName, writes) = do - execMulti db q (map prepRow writes) - markTableMutation tableName bh - where - q = "INSERT OR REPLACE INTO " <> tbl tableName <> "(rowkey,txid,rowdata) VALUES(?,?,?)" - - -- Mark the table as being mutated during this block, so that we know - -- to delete from it if we rewind past this block. - markTableMutation tablename blockheight = do - exec' db mutq [SText tablename, SInt (fromIntegral blockheight)] - where - mutq = "INSERT OR IGNORE INTO VersionedTableMutation VALUES (?,?);" - - -- | Record a block as being in the history of the checkpointer. - blockHistoryInsert :: TxId -> IO () - blockHistoryInsert t = - exec' db stmt - [ SInt (fromIntegral bh) - , SBlob (runPutS (encodeBlockHash hsh)) - , SInt (fromIntegral t) - ] - where - stmt = - "INSERT INTO BlockHistory ('blockheight','hash','endingtxid') VALUES (?,?,?);" - - createUserTable :: Text -> IO () - createUserTable (toUtf8 -> tablename) = do - createVersionedTable tablename db - markTableCreation tablename - - -- Mark the table as being created during this block, so that we know - -- to drop it if we rewind past this block. - markTableCreation tablename = - exec' db insertstmt insertargs - where - insertstmt = "INSERT OR IGNORE INTO VersionedTableCreation VALUES (?,?)" - insertargs = [SText tablename, SInt (fromIntegral bh)] - - -- | Commit the index of pending successful transactions to the database - indexPendingPactTransactions :: IO () - indexPendingPactTransactions = do - let txs = _pendingSuccessfulTxs $ _blockHandlePending blockHandle - dbIndexTransactions txs - - where - toRow b = [SBlob b, SInt (fromIntegral bh)] - dbIndexTransactions txs = do - let rows = map toRow $ toList txs - execMulti db "INSERT INTO TransactionIndex (txhash, blockheight) \ - \ VALUES (?, ?)" rows - - -createVersionedTable :: Utf8 -> Database -> IO () -createVersionedTable tablename db = do - exec_ db createtablestmt - exec_ db indexcreationstmt - where - ixName = tablename <> "_ix" - createtablestmt = - "CREATE TABLE IF NOT EXISTS " <> tbl tablename <> " \ - \ (rowkey TEXT\ - \, txid UNSIGNED BIGINT NOT NULL\ - \, rowdata BLOB NOT NULL\ - \, UNIQUE (rowkey, txid));" - indexcreationstmt = - "CREATE INDEX IF NOT EXISTS " <> tbl ixName <> " ON " <> tbl tablename <> "(txid DESC);" diff --git a/src/Chainweb/Pact4/ModuleCache.hs b/src/Chainweb/Pact4/ModuleCache.hs deleted file mode 100644 index ea961e8b80..0000000000 --- a/src/Chainweb/Pact4/ModuleCache.hs +++ /dev/null @@ -1,74 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} - -module Chainweb.Pact4.ModuleCache - ( ModuleCache(..) - , filterModuleCacheByKey - , moduleCacheToHashMap - , moduleCacheFromHashMap - , moduleCacheKeys - , cleanModuleCache - ) where - -import Control.DeepSeq -import Control.Lens - --- internal pact modules - -import Pact.Types.Runtime (ModuleData) -import Pact.Types.Term -import qualified Pact.Utils.StableHashMap as SHM - --- internal chainweb modules - -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Version - -import qualified Pact.JSON.Legacy.HashMap as LHM - --- | Block scoped Module Cache --- -newtype ModuleCache = ModuleCache { _getModuleCache :: LHM.HashMap ModuleName (ModuleData Ref, Bool) } - deriving newtype (Show, Eq, Semigroup, Monoid, NFData) - -filterModuleCacheByKey - :: (ModuleName -> Bool) - -> ModuleCache - -> ModuleCache -filterModuleCacheByKey f (ModuleCache c) = ModuleCache $ - LHM.fromList $ filter (f . fst) $ LHM.toList c -{-# INLINE filterModuleCacheByKey #-} - -moduleCacheToHashMap - :: ModuleCache - -> SHM.StableHashMap ModuleName (ModuleData Ref, Bool) -moduleCacheToHashMap (ModuleCache c) = SHM.fromList $ LHM.toList c -{-# INLINE moduleCacheToHashMap #-} - -moduleCacheFromHashMap - :: SHM.StableHashMap ModuleName (ModuleData Ref, Bool) - -> ModuleCache -moduleCacheFromHashMap = ModuleCache . LHM.fromList . SHM.toList -{-# INLINE moduleCacheFromHashMap #-} - -moduleCacheKeys :: ModuleCache -> [ModuleName] -moduleCacheKeys (ModuleCache a) = fst <$> LHM.toList a -{-# INLINE moduleCacheKeys #-} - --- this can't go in Chainweb.Version.Guards because it causes an import cycle --- it uses genesisHeight which is from BlockHeader which imports Guards -cleanModuleCache :: ChainwebVersion -> ChainId -> BlockHeight -> Bool -cleanModuleCache v cid bh = - case v ^?! versionForks . at Chainweb217Pact . _Just . atChain cid of - ForkAtBlockHeight bh' -> bh == bh' - ForkAtGenesis -> bh == genesisHeight v cid - ForkNever -> False diff --git a/src/Chainweb/Pact4/NoCoinbase.hs b/src/Chainweb/Pact4/NoCoinbase.hs deleted file mode 100644 index 5994f6335a..0000000000 --- a/src/Chainweb/Pact4/NoCoinbase.hs +++ /dev/null @@ -1,31 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} - --- | --- Module: Chainweb.Pact4.NoCoinbase --- Copyright: Copyright © 2020 Kadena LLC. --- License: MIT --- Maintainer: Lars Kuhtz --- Stability: experimental --- --- A noop coin base for genesis transactions and testing purposes. --- -module Chainweb.Pact4.NoCoinbase -( noCoinbase -) where - --- internal modules - -import Pact.Types.Command -import Pact.Types.Exp -import Pact.Types.Hash -import Pact.Types.PactValue - --- | No-op coinbase payload --- -noCoinbase :: CommandResult a -noCoinbase = CommandResult - (RequestKey pactInitialHash) Nothing - (PactResult (Right (PLiteral (LString "NO_COINBASE")))) - 0 Nothing Nothing Nothing [] -{-# NOINLINE noCoinbase #-} diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs deleted file mode 100644 index c0a2ddad3b..0000000000 --- a/src/Chainweb/Pact4/SPV.hs +++ /dev/null @@ -1,489 +0,0 @@ -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE ViewPatterns #-} - --- | --- Module: Chainweb.Pact4.SPV --- Copyright: Copyright © 2018 - 2020 Kadena LLC. --- License: See LICENSE file --- Maintainers: Emily Pillmore --- Stability: experimental --- --- Pact Service SPV support --- -module Chainweb.Pact4.SPV -( -- * spv support - pactSPV -, verifySPV -, verifyCont - -- * spv api utilities -, getTxIdx -) where - - -import GHC.Stack - -import Control.Error -import Control.Lens hiding (index) -import Control.Monad -import Control.Monad.Catch -import Control.Monad.Except -import Control.Monad.Trans.Except - -import Data.Aeson hiding (Object, (.=)) -import Data.Bifunctor -import qualified Data.ByteString as B -import qualified Data.ByteString.Base64.URL as B64U -import qualified Data.Map.Strict as M -import Data.Text (Text, pack) -import qualified Data.Text as Text -import qualified Data.Text.Encoding as Text -import Text.Read (readMaybe) - -import Crypto.Hash.Algorithms - -import qualified Ethereum.Header as EthHeader -import Ethereum.Misc -import Ethereum.Receipt -import Ethereum.Receipt.ReceiptProof -import Ethereum.RLP - -import Numeric.Natural - -import qualified Streaming.Prelude as S - --- internal chainweb modules - -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB -import Chainweb.BlockHeight -import Chainweb.Pact.Types(internalError) -import Chainweb.Pact.Utils (aeson) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.SPV -import Chainweb.SPV.VerifyProof -import Chainweb.TreeDB -import Chainweb.Utils -import qualified Chainweb.Version as CW -import qualified Chainweb.Version.Guards as CW - --- internal pact modules - -import qualified Pact.JSON.Encode as J -import qualified Pact.Types.Command as Pact4 -import qualified Pact.Types.Hash as Pact4 -import qualified Pact.Types.Info as Pact4 -import qualified Pact.Types.PactValue as Pact4 -import qualified Pact.Types.Runtime as Pact4 -import qualified Pact.Types.SPV as Pact4 - -catchAndDisplaySPVError :: BlockHeader -> ExceptT Text IO a -> ExceptT Text IO a -catchAndDisplaySPVError bh = - if CW.chainweb219Pact (CW._chainwebVersion bh) (CW._chainId bh) (view blockHeight bh) - then flip catch $ \case - SpvExceptionVerificationFailed m -> throwError ("spv verification failed: " <> m) - spvErr -> throwM spvErr - else id - -forkedThrower :: BlockHeader -> Text -> ExceptT Text IO a -forkedThrower bh = - if CW.chainweb219Pact (CW._chainwebVersion bh) (CW._chainId bh) (view blockHeight bh) - then throwError - else internalError - --- | Spv support for pact --- -pactSPV - :: BlockHeaderDb - -- ^ handle into the cutdb - -> BlockHeader - -- ^ the context for verifying the proof - -> Pact4.SPVSupport -pactSPV bdb bh = Pact4.SPVSupport (verifySPV bdb bh) (verifyCont bdb bh) - --- | SPV transaction verification support. Calls to 'verify-spv' in Pact --- will thread through this function and verify an SPV receipt, making the --- requisite calls to the SPV api and verifying the output proof. --- -verifySPV - :: BlockHeaderDb - -- ^ handle into the cut db - -> BlockHeader - -- ^ the context for verifying the proof - -> Text - -- ^ TXOUT or TXIN - defines the type of proof - -- used in validation - -> Pact4.Object Pact4.Name - -- ^ the proof object to validate - -> IO (Either Text (Pact4.Object Pact4.Name)) -verifySPV bdb bh typ proof = runExceptT $ go typ proof - where - cid = CW._chainId bdb - enableBridge = CW.enableSPVBridge (CW._chainwebVersion bh) cid (view blockHeight bh) - - mkSPVResult' cr j - | enableBridge = - return $ mkSPVResult cr j - | otherwise = case Pact4.fromPactValue j of - Pact4.TObject o _ -> return o - _ -> throwError "spv-verified tx output has invalid type" - - go s o = case s of - - -- Ethereum Receipt Proof - "ETH" | enableBridge -> except (extractEthProof o) >>= - \parsedProof -> case validateReceiptProof parsedProof of - Left e -> throwError $ "Validation of Eth proof failed: " <> sshow e - Right result -> return $ ethResultToPactValue result - - -- Chainweb tx output proof - "TXOUT" -> do - u <- except $ extractProof enableBridge o - unless (view outputProofChainId u == cid) $ - forkedThrower bh "cannot redeem spv proof on wrong target chain" - - -- SPV proof verification is a 3 step process: - -- - -- 1. verify spv tx output proof via chainweb spv api - -- - -- 2. Decode tx outputs to 'HashCommandResult' - -- - -- 3. Extract tx outputs as a pact object and return the - -- object. - - TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) - - q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of - Nothing -> forkedThrower bh "unable to decode spv transaction output" - Just cr -> return cr - - case Pact4._crResult q of - Pact4.PactResult Left{} -> - throwError "Failed command result in tx output proof" - Pact4.PactResult (Right v) -> - mkSPVResult' q v - - t -> throwError $! "unsupported SPV types: " <> t - --- | A tag for specifying the format of base64 error messages on chain. --- --- `Legacy` errors match those produced by our legacy version of --- base64-bytestring, and are produced by parsing the error messages --- we receive from our current version of base64-bytestring (1.2), --- and formatting them in the older style. Legacy behavior also implies --- that non-canonical encodings be allowed, because that was the behavior --- of the legacy bytestring parser; and improperly padded messages --- will get extra padding, because that is the legacy chainweb behavior. --- --- `Simplified` errors are a static string, which may not describe --- the issue with the base64-encoded string as well, but will be --- more stable as we upgrade base64 decoding libraries in the future. --- In the `Simplified` errors setting, messages rejected for using --- non-canonical encodings will remain as errors, because we want to --- begin enforcing the expected constraints on base64-encoded messages. --- Similarly, Simplified parsing will not add extra padding to improperly --- padded messages. -data GenerateBase64ErrorMessage - = Legacy - | Simplified - deriving (Eq, Show) - - --- | A modified version of `decodeBase64UrlNoPaddingText` that emits --- base64 decoding errors in a configurable way. -decodeB64UrlNoPaddingTextWithFixedErrorMessage :: MonadThrow m => GenerateBase64ErrorMessage -> Text.Text -> m B.ByteString -decodeB64UrlNoPaddingTextWithFixedErrorMessage errorMessageType msg = - fromEitherM - . first (\e -> Base64DecodeException $ base64ErrorMessage e) - . (if errorMessageType == Legacy then patchNonCanonical else id) - . B64U.decode - . Text.encodeUtf8 - . (if errorMessageType == Legacy then pad else id) - $ msg - where - pad t = let s = Text.length t `mod` 4 in t <> Text.replicate ((4 - s) `mod` 4) "=" - base64ErrorMessage m = case errorMessageType of - Legacy -> base64DowngradeErrorMessage (Text.pack m) - Simplified -> "could not base64-decode message" - patchNonCanonical decodeResult = case decodeResult of - Right bs -> Right bs - Left e | "non-canonical" `Text.isInfixOf` Text.pack e -> - (B64U.decodeNonCanonical (Text.encodeUtf8 msg)) - Left e -> Left e -{-# INLINE decodeB64UrlNoPaddingTextWithFixedErrorMessage #-} - - - --- | Converts the error message format of base64-bytestring-1.2 --- into that of base64-bytestring-0.1, for the error messages --- that have made it onto the chain. --- This allows us to upgrade to base64-bytestring-1.2 without --- breaking compatibility. -base64DowngradeErrorMessage :: Text -> Text -base64DowngradeErrorMessage msg = case msg of - "Base64-encoded bytestring has invalid size" -> - "invalid base64 encoding near offset 0" - (Text.stripPrefix "invalid character at offset: " -> Just suffix) -> - Text.pack $ "invalid base64 encoding near offset " ++ show (adjustedOffset suffix) - (Text.stripPrefix "invalid padding at offset: " -> Just suffix) -> - Text.pack $ "invalid padding near offset " ++ show (adjustedOffset suffix) - e -> e - where - adjustedOffset :: Text -> Int - adjustedOffset suffix = case readMaybe (Text.unpack suffix) of - Nothing -> 0 - Just offset -> let - endsWithThreeEquals = Text.drop (Text.length msg - 3) msg == "===" - adjustment = if endsWithThreeEquals then -1 else 0 - in - offset - (offset `rem` 4) + adjustment - --- | SPV defpact transaction verification support. This call validates a pact 'endorsement' --- in Pact, providing a validation that the yield data of a cross-chain pact is valid. --- -verifyCont - :: BlockHeaderDb - -- ^ handle into the cut db - -> BlockHeader - -- ^ the context for verifying the proof - -> Pact4.ContProof - -- ^ bytestring of 'TransactionOutputP roof' object to validate - -> IO (Either Text Pact4.PactExec) -verifyCont bdb bh (Pact4.ContProof cp) = runExceptT $ do - let errorMessageType = - if CW.chainweb221Pact - (CW._chainwebVersion bh) - (CW._chainId bh) - (view blockHeight bh) - then Simplified - else Legacy - t <- decodeB64UrlNoPaddingTextWithFixedErrorMessage errorMessageType $ Text.decodeUtf8 cp - case decodeStrict' t of - Nothing -> forkedThrower bh "unable to decode continuation proof" - Just u - | view outputProofChainId u /= cid -> - forkedThrower bh "cannot redeem continuation proof on wrong target chain" - | otherwise -> do - - -- Cont proof verification is a 3 step process: - -- - -- 1. verify spv tx output proof via chainweb spv api - -- - -- 2. Decode tx outputs to 'Pact4.CommandResult' 'Pact4.Hash' - -- - -- 3. Extract continuation 'PactExec' from decoded result - -- and return the cont exec object - - TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) - - q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of - Nothing -> forkedThrower bh "unable to decode spv transaction output" - Just cr -> return cr - - case Pact4._crContinuation q of - Nothing -> throwError "no pact exec found in command result" - Just pe -> return pe - where - cid = CW._chainId bdb - --- | Extract a 'TransactionOutputProof' from a generic pact object --- -extractProof :: Bool -> Pact4.Object Pact4.Name -> Either Text (TransactionOutputProof SHA512t_256) -extractProof False o = Pact4.toPactValue (Pact4.TObject o Pact4.noInfo) >>= k - where - k = aeson (Left . pack) Right - . fromJSON - . J.toJsonViaEncode -extractProof True (Pact4.Object (Pact4.ObjectMap o) _ _ _) = case M.lookup "proof" o of - Just (Pact4.TLitString proof) -> do - j <- first (const "Base64 decode failed") (decodeB64UrlNoPaddingText proof) - first (const "Decode of TransactionOutputProof failed") (decodeStrictOrThrow j) - _ -> Left "Invalid input, expected 'proof' field with base64url unpadded text" - --- | Extract an Eth 'ReceiptProof' from a generic pact object --- --- The proof object has a sinle property "proof". The value is the --- base64UrlWithoutPadding encoded proof blob. --- --- NOTE: If this fails the failure message is included on the chain. We --- therefore replace failure and exception messages from external libraries with --- stable internal messages. --- --- For details of the returned value see 'Ethereum.Receipt' --- -extractEthProof :: Pact4.Object Pact4.Name -> Either Text ReceiptProof -extractEthProof o = case M.lookup "proof" $ Pact4._objectMap $ Pact4._oObject o of - Nothing -> Left "Decoding of Eth proof object failed: missing 'proof' property" - Just (Pact4.TLitString p) -> do - bytes' <- errMsg "Decoding of Eth proof object failed: invalid base64URLWithoutPadding encoding" - $ decodeB64UrlNoPaddingText p - errMsg "Decoding of Eth proof object failed: invalid binary proof data" - $ get getRlp bytes' - Just _ -> Left "Decoding of Eth proof object failed: invalid 'proof' property" - where - errMsg t = first (const t) - -ethResultToPactValue :: ReceiptProofValidation -> Pact4.Object Pact4.Name -ethResultToPactValue ReceiptProofValidation{..} = mkObject - [ ("depth", tInt _receiptProofValidationDepth) - , ("header", header _receiptProofValidationHeader) - , ("index", tix _receiptProofValidationIndex) - , ("root",jsonStr _receiptProofValidationRoot) - , ("weight",tInt _receiptProofValidationWeight) - , ("receipt",receipt _receiptProofValidationReceipt) - ] - where - receipt Receipt{..} = obj - [ ("cumulative-gas-used", tInt _receiptGasUsed) - , ("status",Pact4.toTerm $ _receiptStatus == TxStatus 1) - , ("logs",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map rlog _receiptLogs)] - rlog LogEntry{..} = obj - [ ("address",jsonStr _logEntryAddress) - , ("topics",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map topic _logEntryTopics) - , ("data",jsonStr _logEntryData)] - topic t = jsonStr t - header ch@EthHeader.ConsensusHeader{..} = obj - [ ("difficulty", jsonStr _hdrDifficulty) - , ("extra-data", jsonStr _hdrExtraData) - , ("gas-limit", tInt _hdrGasLimit) - , ("gas-used", tInt _hdrGasUsed) - , ("hash", jsonStr $ EthHeader.blockHash ch) - , ("miner", jsonStr _hdrBeneficiary) - , ("mix-hash", jsonStr _hdrMixHash) - , ("nonce", jsonStr _hdrNonce) - , ("number", tInt _hdrNumber) - , ("parent-hash", jsonStr _hdrParentHash) - , ("receipts-root", jsonStr _hdrReceiptsRoot) - , ("sha3-uncles", jsonStr _hdrOmmersHash) - , ("state-root", jsonStr _hdrStateRoot) - , ("timestamp", ts _hdrTimestamp) - , ("transactions-root", jsonStr _hdrTransactionsRoot) - ] - jsonStr v = case toJSON v of - String s -> Pact4.tStr s - _ -> Pact4.tStr $ sshow v - ts (Timestamp t) = tInt t - tix (TransactionIndex i) = tInt i -{-# INLINE ethResultToPactValue #-} - --- | Look up pact tx hash at some block height in the --- payload db, and return the tx index for proof creation. --- --- Note: runs in O(n) - this should be revisited if possible --- -getTxIdx - :: HasCallStack - => CanReadablePayloadCas tbl - => BlockHeaderDb - -> PayloadDb tbl - -> BlockHeight - -> Pact4.PactHash - -> IO (Either Text Int) -getTxIdx bdb pdb bh th = do - -- get BlockPayloadHash - m <- maxEntry bdb - ph <- seekAncestor bdb m (int bh) >>= \case - Just x -> return $ Right $! view blockPayloadHash x - Nothing -> return $ Left "unable to find payload associated with transaction hash" - - case ph of - (Left !s) -> return $ Left s - (Right !a) -> do - -- get payload - Just payload <- lookupPayloadWithHeight pdb (Just bh) a - - -- Find transaction index - r <- S.each (_payloadWithOutputsTransactions payload) - & S.map fst - & S.mapM toTxHash - & sindex (== th) - - r & note "unable to find transaction at the given block height" - & fmap int - & return - where - toPactTx :: MonadThrow m => Transaction -> m (Pact4.Command Text) - toPactTx (Transaction b) = decodeStrictOrThrow' b - - toTxHash :: MonadThrow m => Transaction -> m Pact4.PactHash - toTxHash = fmap Pact4._cmdHash . toPactTx - - sfind :: Monad m => (a -> Bool) -> S.Stream (S.Of a) m () -> m (Maybe a) - sfind p = S.head_ . S.dropWhile (not . p) - - sindex :: Monad m => (a -> Bool) -> S.Stream (S.Of a) m () -> m (Maybe Natural) - sindex p s = S.zip (S.each [0..]) s & sfind (p . snd) & fmap (fmap fst) - -mkObject :: [(Pact4.FieldKey, Pact4.Term n)] -> Pact4.Object n -mkObject ps = Pact4.Object (Pact4.ObjectMap (M.fromList ps)) Pact4.TyAny Nothing Pact4.noInfo - -obj :: [(Pact4.FieldKey, Pact4.Term n)] -> Pact4.Term n -obj = Pact4.toTObject Pact4.TyAny Pact4.noInfo - -tInt :: Integral i => i -> Pact4.Term Pact4.Name -tInt = Pact4.toTerm . fromIntegral @_ @Integer - --- | Encode a "successful" CommandResult into a Pact object. -mkSPVResult - :: Pact4.CommandResult Pact4.Hash - -- ^ Full CR - -> Pact4.PactValue - -- ^ Success result - -> Pact4.Object Pact4.Name -mkSPVResult Pact4.CommandResult{..} j = - mkObject - [ ("result", Pact4.fromPactValue j) - , ("req-key", Pact4.tStr $ Pact4.asString $ Pact4.unRequestKey _crReqKey) - , ("txid", Pact4.tStr $ maybe "" Pact4.asString _crTxId) - , ("gas", Pact4.toTerm $ (fromIntegral _crGas :: Integer)) - , ("meta", maybe empty metaField _crMetaData) - , ("logs", Pact4.tStr $ Pact4.asString _crLogs) - , ("continuation", maybe empty contField _crContinuation) - , ("events", Pact4.toTList Pact4.TyAny Pact4.noInfo $ map eventField _crEvents) - ] - where - metaField v = case fromJSON v of - Error _ -> obj [] - Success p -> Pact4.fromPactValue p - - contField (Pact4.PactExec stepCount yield executed step pactId pactCont rollback _nested) = obj - [ ("step", Pact4.toTerm step) - , ("step-count", Pact4.toTerm stepCount) - , ("yield", maybe empty yieldField yield) - , ("pact-id", Pact4.toTerm pactId) - , ("cont",contField1 pactCont) - , ("step-has-rollback",Pact4.toTerm rollback) - , ("executed",Pact4.tStr $ maybe "" sshow executed) - ] - - contField1 Pact4.PactContinuation {..} = obj - [ ("name",Pact4.tStr $ Pact4.asString _pcDef) - , ("args",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map Pact4.fromPactValue _pcArgs) - ] - - yieldField Pact4.Yield {..} = obj - [ ("data",Pact4.fromPactValue (Pact4.PObject _yData)) - , ("provenance", maybe empty provField _yProvenance) - ] - - provField Pact4.Provenance {..} = obj - [ ("target-chain", Pact4.toTerm $ Pact4._chainId _pTargetChainId) - , ("module-hash", Pact4.tStr $ Pact4.asString $ Pact4._mhHash $ _pModuleHash) - ] - - eventField Pact4.PactEvent {..} = obj - [ ("name", Pact4.toTerm _eventName) - , ("params", Pact4.toTList Pact4.TyAny Pact4.noInfo (map Pact4.fromPactValue _eventParams)) - , ("module", Pact4.tStr $ Pact4.asString _eventModule) - , ("module-hash", Pact4.tStr $ Pact4.asString _eventModuleHash) - ] - - empty = obj [] diff --git a/src/Chainweb/Pact4/Templates.hs b/src/Chainweb/Pact4/Templates.hs deleted file mode 100644 index a8f62002bb..0000000000 --- a/src/Chainweb/Pact4/Templates.hs +++ /dev/null @@ -1,203 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} - --- | --- Module : Chainweb.Pact.Templates --- Copyright : Copyright © 2010 Kadena LLC. --- License : (see the file LICENSE) --- Maintainer : Stuart Popejoy --- Stability : experimental --- --- Prebuilt Term templates for automated operations (coinbase, gas buy) --- -module Chainweb.Pact4.Templates -( mkFundTxTerm -, mkBuyGasTerm -, mkRedeemGasTerm -, mkCoinbaseTerm - -, mkCoinbaseCmd -) where - - -import Control.Lens -import Data.Text (Text, pack) - -import Text.Trifecta.Delta (Delta(..)) - --- internal modules - -import qualified Pact.JSON.Encode as J -import Pact.JSON.Legacy.Value -import Pact.Parse -import Pact.Types.Command -import Pact.Types.RPC -import Pact.Types.Runtime - -import Chainweb.Miner.Pact -import Chainweb.Pact.Types -import Chainweb.Pact4.Types (GasSupply) - -inf :: Info -inf = Info $ Just (Code "",Parsed (Columns 0 0) 0) -{-# NOINLINE inf #-} - -app :: Name -> [Term Name] -> Term Name -app f as = TApp (App (TVar f inf) as inf) inf -{-# INLINE app #-} - -qn :: ModuleName -> Text -> Name -qn mn d = QName $ QualifiedName mn d inf -{-# INLINE qn #-} - -bn :: Text -> Name -bn n = Name $ BareName n inf -{-# INLINE bn #-} - -strLit :: Text -> Term Name -strLit s = TLiteral (LString s) inf -{-# INLINE strLit #-} - -strArgSetter :: Int -> ASetter' (Term Name) Text -strArgSetter idx = tApp . appArgs . ix idx . tLiteral . _LString -{-# INLINE strArgSetter #-} - -fundTxTemplate :: (Term Name, ASetter' (Term Name) Text, ASetter' (Term Name) Text) -fundTxTemplate = - ( app (qn "coin" "fund-tx") - [ strLit "sender" - , strLit "mid" - , app (bn "read-keyset") [strLit "miner-keyset"] - , app (bn "read-decimal") [strLit "total"] - ] - , strArgSetter 0 - , strArgSetter 1 - ) -{-# NOINLINE fundTxTemplate #-} - -buyGasTemplate :: (Term Name, ASetter' (Term Name) Text) -buyGasTemplate = - ( app (qn "coin" "buy-gas") - [ strLit "sender" - , app (bn "read-decimal") [strLit "total"] - ] - , strArgSetter 0 - ) - -redeemGasTemplate :: (Term Name, ASetter' (Term Name) Text, ASetter' (Term Name) Text) -redeemGasTemplate = - ( app (qn "coin" "redeem-gas") - [ strLit "mid" - , app (bn "read-keyset") [strLit "miner-keyset"] - , strLit "sender" - , app (bn "read-decimal") [strLit "total"] - ] - , strArgSetter 2 - , strArgSetter 0 - ) - -dummyParsedCode :: ParsedCode -dummyParsedCode = ParsedCode "1" [ELiteral $ LiteralExp (LInteger 1) (Parsed mempty 0)] -{-# NOINLINE dummyParsedCode #-} - - -mkFundTxTerm - :: MinerId -- ^ Id of the miner to fund - -> MinerKeys -- ^ Miner keyset - -> Text -- ^ Address of the sender from the command - -> GasSupply -- ^ The gas limit total * price - -> (Term Name,ExecMsg ParsedCode) -mkFundTxTerm (MinerId mid) (MinerKeys ks) sender total = (populatedTerm, execMsg) - where (term, senderS, minerS) = fundTxTemplate - populatedTerm = set senderS sender $ set minerS mid term - execMsg = ExecMsg dummyParsedCode (toLegacyJsonViaEncode buyGasData) - buyGasData = J.object - [ "miner-keyset" J..= ks - , "total" J..= total - ] -{-# INLINABLE mkFundTxTerm #-} - -mkBuyGasTerm - :: Text -- ^ Address of the sender from the command - -> GasSupply -- ^ The gas limit total * price - -> (Term Name,ExecMsg ParsedCode) -mkBuyGasTerm sender total = (populatedTerm, execMsg) - where (term, senderS) = buyGasTemplate - populatedTerm = set senderS sender term - execMsg = ExecMsg dummyParsedCode (toLegacyJsonViaEncode buyGasData) - buyGasData = J.object - [ "total" J..= total ] -{-# INLINABLE mkBuyGasTerm #-} - -mkRedeemGasTerm - :: MinerId -- ^ Id of the miner to fund - -> MinerKeys -- ^ Miner keyset - -> Text -- ^ Address of the sender from the command - -> GasSupply -- ^ The gas limit total * price - -> GasSupply -- ^ The gas used * price - -> (Term Name,ExecMsg ParsedCode) -mkRedeemGasTerm (MinerId mid) (MinerKeys ks) sender total fee = (populatedTerm, execMsg) - where (term, senderS, minerS) = redeemGasTemplate - populatedTerm = set senderS sender $ set minerS mid term - execMsg = ExecMsg dummyParsedCode (toLegacyJsonViaEncode redeemGasData) - redeemGasData = J.object - [ "total" J..= total - , "fee" J..= J.toJsonViaEncode fee - , "miner-keyset" J..= ks - ] -{-# INLINABLE mkRedeemGasTerm #-} - -coinbaseTemplate :: (Term Name,ASetter' (Term Name) Text) -coinbaseTemplate = - ( app (qn "coin" "coinbase") - [ strLit "mid" - , app (bn "read-keyset") [strLit "miner-keyset"] - , app (bn "read-decimal") [strLit "reward"] - ] - , strArgSetter 0 - ) -{-# NOINLINE coinbaseTemplate #-} - -mkCoinbaseTerm :: MinerId -> MinerKeys -> ParsedDecimal -> (Term Name,ExecMsg ParsedCode) -mkCoinbaseTerm (MinerId mid) (MinerKeys ks) reward = (populatedTerm, execMsg) - where - (term, minerS) = coinbaseTemplate - populatedTerm = set minerS mid term - execMsg = ExecMsg dummyParsedCode (toLegacyJsonViaEncode coinbaseData) - coinbaseData = J.object - [ "miner-keyset" J..= ks - , "reward" J..= reward - ] -{-# INLINABLE mkCoinbaseTerm #-} - --- | "Old method" to build a coinbase 'ExecMsg' for back-compat. --- -mkCoinbaseCmd :: MinerId -> MinerKeys -> ParsedDecimal -> IO (ExecMsg ParsedCode) -mkCoinbaseCmd (MinerId mid) (MinerKeys ks) reward = - buildExecParsedCode $ mconcat - [ "(coin.coinbase" - , " \"" <> mid <> "\"" - , " (read-keyset \"miner-keyset\")" - , " (read-decimal \"reward\"))" - ] - where - - coinbaseData = J.object - [ "miner-keyset" J..= ks - , "reward" J..= reward - ] - - -- | Build the 'ExecMsg' for some pact code fed to the function. - -- - buildExecParsedCode :: Text -> IO (ExecMsg ParsedCode) - buildExecParsedCode code = case parsePact code of - Right !t -> pure $! ExecMsg t (toLegacyJsonViaEncode coinbaseData) - -- if we can't construct coin contract calls, this should - -- fail fast - Left err -> internalError $ "buildExecParsedCode: parse failed: " <> pack err - -{-# INLINABLE mkCoinbaseCmd #-} diff --git a/src/Chainweb/Pact4/Transaction.hs b/src/Chainweb/Pact4/Transaction.hs deleted file mode 100644 index eb74a6f31b..0000000000 --- a/src/Chainweb/Pact4/Transaction.hs +++ /dev/null @@ -1,190 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE DeriveTraversable #-} - -module Chainweb.Pact4.Transaction - ( Transaction - , UnparsedTransaction - , unparseTransaction - , HashableTrans(..) - , PayloadWithText - , PactParserVersion(..) - , IsWebAuthnPrefixLegal(..) - , payloadCodec - , rawCommandCodec - , encodePayload - , decodePayload - , cmdGasLimit - , cmdGasPrice - , cmdTimeToLive - , cmdCreationTime - , mkPayloadWithText - , mkPayloadWithTextOld - , mkPayloadWithTextOldUnparsed - , payloadBytes - , payloadObj - , parsePact - ) where - -import Control.DeepSeq -import Control.Lens - -import qualified Data.Aeson as Aeson -import Data.ByteString.Char8 (ByteString) -import qualified Data.ByteString.Char8 as B -import qualified Data.ByteString.Short as SB -import Data.Hashable -import Data.Text (Text) -import Data.Text.Encoding (decodeUtf8, encodeUtf8) - -import GHC.Generics (Generic) - -import qualified Pact.Parse as P (parsePact, legacyParsePact) -import Pact.Types.ChainMeta -import Pact.Types.Command -import Pact.Types.Gas (GasLimit(..), GasPrice(..)) -import Pact.Types.Hash -import qualified Pact.JSON.Encode as J -import Pact.JSON.Legacy.Value - -import Chainweb.Utils -import Chainweb.Utils.Serialization - --- | A product type representing a `Payload PublicMeta ParsedCode` coupled with --- the text that it was parsed from, to make gossiping easier. --- -data PayloadWithText meta code = PayloadWithText - { _payloadBytes :: !SB.ShortByteString - , _payloadObj :: !(Payload meta code) - } - deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic) - deriving anyclass (NFData) - -payloadBytes :: PayloadWithText meta code -> SB.ShortByteString -payloadBytes = _payloadBytes - -payloadObj :: PayloadWithText meta code -> Payload meta code -payloadObj = _payloadObj - -mkPayloadWithText :: Command (ByteString, Payload meta code) -> Command (PayloadWithText meta code) -mkPayloadWithText = over cmdPayload $ \(bs, p) -> PayloadWithText - { _payloadBytes = SB.toShort bs - , _payloadObj = p - } - -mkPayloadWithTextOld :: Payload PublicMeta ParsedCode -> PayloadWithText PublicMeta ParsedCode -mkPayloadWithTextOld p = PayloadWithText - { _payloadBytes = SB.toShort $ J.encodeStrict $ toLegacyJsonViaEncode $ fmap _pcCode p - , _payloadObj = p - } - -mkPayloadWithTextOldUnparsed :: Payload PublicMeta Text -> PayloadWithText PublicMeta Text -mkPayloadWithTextOldUnparsed p = PayloadWithText - { _payloadBytes = SB.toShort $ J.encodeStrict $ toLegacyJsonViaEncode p - , _payloadObj = p - } - --- | Pact 4 transactions. -type Transaction = Command (PayloadWithText PublicMeta ParsedCode) - --- | Pact 4 commands with code left not parsed are used in the mempool. -type UnparsedTransaction = Command (PayloadWithText PublicMeta Text) - --- | Throw away the parsed representation of the Pact code. -unparseTransaction :: Transaction -> UnparsedTransaction -unparseTransaction cmd = cmd <&> fmap _pcCode - -data PactParserVersion - = PactParserGenesis - | PactParserChainweb213 - deriving (Eq, Ord, Bounded, Show, Enum) - --- | Denotes whether the `WEBAUTHN-` key prefix is valid at this point in the block history. -data IsWebAuthnPrefixLegal - = WebAuthnPrefixIllegal - | WebAuthnPrefixLegal - deriving (Eq, Ord, Bounded, Show, Enum) - --- | Hashable newtype of Transaction -newtype HashableTrans a = HashableTrans { unHashable :: Command a } - deriving (Eq, Functor, Ord) - -instance (Eq code, Eq meta) => Hashable (HashableTrans (PayloadWithText meta code)) where - hashWithSalt s (HashableTrans t) = hashWithSalt s hashCode - where - (TypedHash hc) = _cmdHash t - decHC = runGetEitherS getWord64le - !hashCode = either error id $ decHC (B.take 8 $ SB.fromShort hc) - {-# INLINE hashWithSalt #-} - --- | A codec for the transaction type used in the mempool. --- -rawCommandCodec :: Codec UnparsedTransaction -rawCommandCodec = Codec enc dec - where - enc cmd = J.encodeStrict $ J.text . decodeUtf8 . SB.fromShort . _payloadBytes <$> cmd - dec bs = do - cmd <- Aeson.eitherDecodeStrict' bs - let p = encodeUtf8 $ _cmdPayload cmd - payloadObject <- Aeson.eitherDecodeStrict' p - let payloadWithText = PayloadWithText { _payloadBytes = (SB.toShort p), _payloadObj = payloadObject } - return $ payloadWithText <$ cmd - --- | A codec for Pact4's (Command PayloadWithText) transactions. --- -payloadCodec - :: PactParserVersion - -> Codec Transaction -payloadCodec ppv = Codec enc dec - where - enc c = J.encodeStrict $ fmap (decodeUtf8 . encodePayload) c - dec bs = case Aeson.decodeStrict' bs of - Just cmd -> traverse (decodePayload ppv . encodeUtf8) cmd - Nothing -> Left "decode PayloadWithText failed" - -encodePayload :: PayloadWithText meta code -> ByteString -encodePayload = SB.fromShort . _payloadBytes - -decodePayload - :: Aeson.FromJSON meta - => PactParserVersion - -> ByteString - -> Either String (PayloadWithText meta ParsedCode) -decodePayload ppv bs = case Aeson.decodeStrict' bs of - Just payload -> do - p <- traverse (parsePact ppv) payload - return $! PayloadWithText (SB.toShort bs) p - Nothing -> Left "decoding Payload failed" - -parsePact - :: PactParserVersion - -- ^ If the chain context is @Nothing@, latest parser version is used. - -> Text - -> Either String ParsedCode -parsePact PactParserChainweb213 = P.parsePact -parsePact PactParserGenesis = P.legacyParsePact - --- | Access the gas limit/supply of a public chain command payload -cmdGasLimit :: Lens' (Command (Payload PublicMeta c)) GasLimit -cmdGasLimit = cmdPayload . pMeta . pmGasLimit -{-# INLINE cmdGasLimit #-} - --- | Get the gas price of a public chain command payload -cmdGasPrice :: Lens' (Command (Payload PublicMeta c)) GasPrice -cmdGasPrice = cmdPayload . pMeta . pmGasPrice -{-# INLINE cmdGasPrice #-} - -cmdTimeToLive :: Lens' (Command (Payload PublicMeta c)) TTLSeconds -cmdTimeToLive = cmdPayload . pMeta . pmTTL -{-# INLINE cmdTimeToLive #-} - -cmdCreationTime :: Lens' (Command (Payload PublicMeta c)) TxCreationTime -cmdCreationTime = cmdPayload . pMeta . pmCreationTime -{-# INLINE cmdCreationTime #-} diff --git a/src/Chainweb/Pact4/TransactionExec.hs b/src/Chainweb/Pact4/TransactionExec.hs deleted file mode 100644 index b877b1201b..0000000000 --- a/src/Chainweb/Pact4/TransactionExec.hs +++ /dev/null @@ -1,1553 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE MultiWayIf #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE MonoLocalBinds #-} --- | --- Module : Chainweb.Pact4.TransactionExec --- Copyright : Copyright © 2018 Kadena LLC. --- License : (see the file LICENSE) --- Maintainer : Mark Nichols , Emily Pillmore --- Stability : experimental --- --- Pact command execution and coin-contract transaction logic for Chainweb --- -module Chainweb.Pact4.TransactionExec - -- * Transaction State - ( TransactionState(..) - , txGasModel - , txGasLimit - , txGasUsed - , txGasId - , txLogs - , txCache - , txWarnings - - -- * Transaction Env - , TransactionEnv(..) - , txMode - , txDbEnv - , txLogger - , txGasLogger - , txPublicData - , txSpvSupport - , txNetworkId - , txGasPrice - , txRequestKey - , txExecutionConfig - , txQuirkGasFee - , txTxFailuresCounter - - -- * Transaction Execution Monad - , TransactionM(..) - , runTransactionM - , evalTransactionM - , execTransactionM - -- * Transaction Execution - - , applyCmd - , applyGenesisCmd - , applyLocal - , applyExec - , applyExec' - , applyContinuation - , applyContinuation' - , runPayload - , readInitModules - , enablePactEvents' - , enforceKeysetFormats' - , disableReturnRTC - - -- * Gas Execution - , buyGas - - -- * Coinbase Execution - , applyCoinbase - , EnforceCoinbaseFailure(..) - - -- * Command Helpers - , publicMetaOf - , networkIdOf - , gasSupplyOf - - -- * Utilities - , buildExecParsedCode - , mkMagicCapSlot - , listErrMsg - , initialGasOf - -) where - -import Control.DeepSeq -import Control.Lens -import Control.Monad -import Control.Monad.Catch -import Control.Monad.Reader -import Control.Monad.State.Strict -import Control.Monad.Trans.Maybe -import Control.Parallel.Strategies(using, rseq) - -import Data.Aeson hiding ((.=)) -import qualified Data.Aeson as A -import Data.Bifunctor -import qualified Data.ByteString as B -import qualified Data.ByteString.Short as SB -import Data.Decimal (Decimal, roundTo) -import Data.Foldable (fold, for_, traverse_) -import Data.IORef -import qualified Data.List as List -import qualified Data.Map.Strict as M -import Data.Maybe -import qualified Data.Set as S -import Data.Text (Text) -import qualified Data.Text as T -import qualified System.LogLevel as L - --- internal Pact modules - -import Chainweb.Counter -import Pact.Eval (eval, liftTerm) -import Pact.Gas (freeGasEnv) -import Pact.Interpreter -import qualified Pact.JSON.Encode as J -import Pact.JSON.Legacy.Value -import Pact.Native.Capabilities (evalCap) -import Pact.Native.Internal (appToCap) -import Pact.Parse (ParsedDecimal(..)) -import Pact.Runtime.Capabilities (popCapStack) -import Pact.Runtime.Utils (lookupModule) -import Pact.Types.Capability -import Pact.Types.Command -import Pact.Types.Hash as Pact -import Pact.Types.KeySet -import Pact.Types.PactValue -import Pact.Types.Pretty -import Pact.Types.RPC -import Pact.Types.Runtime hiding (catchesPactError) -import Pact.Types.Server -import Pact.Types.SPV -import Pact.Types.Verifier - -import Pact.Types.Util as PU -import qualified Pact.Utils.StableHashMap as SHM - --- internal Chainweb modules - -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Logger -import qualified Chainweb.ChainId as Chainweb -import Chainweb.Mempool.Mempool (pact4RequestKeyToTransactionHash) -import Chainweb.Miner.Pact -import Chainweb.Pact4.Templates -import Chainweb.Pact.Types -import Chainweb.Pact4.Types -import Chainweb.Pact4.Transaction -import Chainweb.Utils -import Chainweb.VerifierPlugin -import Chainweb.Version as V -import Chainweb.Version.Guards as V -import Chainweb.Version.Utils as V -import Pact.JSON.Encode (toJsonViaEncode) -import Data.Set (Set) -import Chainweb.Pact4.ModuleCache -import Chainweb.Pact4.Backend.ChainwebPactDb - -import Pact.Core.Errors (VerifierError(..)) - --- Note [Throw out verifier proofs eagerly] --- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- We try to discard verifier proofs eagerly so that we don't hang onto them in --- the liveset. This implies that we also try to discard `Command`s for the same --- reason, because they contain the verifier proofs and other data we probably --- don't need. - --- -------------------------------------------------------------------------- -- - --- -------------------------------------------------------------------- -- --- Tx Execution Service Monad - --- | Transaction execution state --- -data TransactionState = TransactionState - { _txCache :: !ModuleCache - , _txLogs :: ![TxLogJson] - , _txGasUsed :: !Gas - , _txGasId :: !(Maybe GasId) - , _txGasModel :: !GasModel - , _txWarnings :: !(Set PactWarning) - } -makeLenses ''TransactionState - --- | Transaction execution env --- -data TransactionEnv logger db = TransactionEnv - { _txMode :: !ExecutionMode - , _txDbEnv :: !(PactDbEnv db) - , _txLogger :: !logger - , _txGasLogger :: !(Maybe logger) - , _txPublicData :: !PublicData - , _txSpvSupport :: !SPVSupport - , _txNetworkId :: !(Maybe NetworkId) - , _txGasPrice :: !GasPrice - , _txRequestKey :: !RequestKey - , _txGasLimit :: !Gas - , _txExecutionConfig :: !ExecutionConfig - , _txQuirkGasFee :: !(Maybe Gas) - , _txTxFailuresCounter :: !(Maybe (Counter "txFailures")) - } -makeLenses ''TransactionEnv - --- | The transaction monad used in transaction execute. The reader --- environment is the a Pact command env, writer is a list of json-ified --- tx logs, and transaction state consists of a module cache, gas env, --- and log values. --- -newtype TransactionM logger db a = TransactionM - { _unTransactionM - :: ReaderT (TransactionEnv logger db) (StateT TransactionState IO) a - } deriving newtype - ( Functor, Applicative, Monad - , MonadReader (TransactionEnv logger db) - , MonadState TransactionState - , MonadThrow, MonadCatch, MonadMask - , MonadIO - ) - --- | Run a 'TransactionM' computation given some initial --- reader and state values, returning the full range of --- results in a strict tuple --- -runTransactionM - :: forall logger db a - . TransactionEnv logger db - -- ^ initial reader env - -> TransactionState - -- ^ initial state - -> TransactionM logger db a - -- ^ computation to execute - -> IO (T2 a TransactionState) -runTransactionM tenv txst act - = view (from _T2) - <$> runStateT (runReaderT (_unTransactionM act) tenv) txst - --- | Run a 'TransactionM' computation given some initial --- reader and state values, discarding the final state. --- -evalTransactionM - :: forall logger db a - . TransactionEnv logger db - -- ^ initial reader env - -> TransactionState - -- ^ initial state - -> TransactionM logger db a - -> IO a -evalTransactionM tenv txst act - = evalStateT (runReaderT (_unTransactionM act) tenv) txst - --- | Run a 'TransactionM' computation given some initial --- reader and state values, returning just the final state. --- -execTransactionM - :: forall logger db a - . TransactionEnv logger db - -- ^ initial reader env - -> TransactionState - -- ^ initial state - -> TransactionM logger db a - -> IO TransactionState -execTransactionM tenv txst act - = execStateT (runReaderT (_unTransactionM act) tenv) txst - - - - --- | "Magic" capability 'COINBASE' used in the coin contract to --- constrain coinbase calls. --- -magic_COINBASE :: CapSlot SigCapability -magic_COINBASE = mkMagicCapSlot "COINBASE" - --- | "Magic" capability 'GAS' used in the coin contract to --- constrain gas buy/redeem calls. --- -magic_GAS :: CapSlot SigCapability -magic_GAS = mkMagicCapSlot "GAS" - --- | "Magic" capability 'GENESIS' used in the coin contract to --- constrain genesis-only allocations --- -magic_GENESIS :: CapSlot SigCapability -magic_GENESIS = mkMagicCapSlot "GENESIS" - -debitCap :: Text -> SigCapability -debitCap s = mkCoinCap "DEBIT" [PLiteral (LString s)] - -onChainErrorPrintingFor :: TxContext -> UnexpectedErrorPrinting -onChainErrorPrintingFor txCtx = - if guardCtx chainweb219Pact txCtx - then CensorsUnexpectedError - else PrintsUnexpectedError - --- | The main entry point to executing transactions. From here, --- 'applyCmd' assembles the command environment for a command and --- orchestrates gas buys/redemption, and executing payloads. --- Note that crMetaData is intentionally left unset in this path; --- it's populated by `/poll`, when using `applyLocal`, or by the preflight --- codepath later. --- -applyCmd - :: (Logger logger) - => ChainwebVersion - -> logger - -- ^ Pact logger - -> Maybe logger - -- ^ Pact gas logger - -> Maybe (Counter "txFailures") - -> PactDbEnv p - -- ^ Pact db environment - -> Miner - -- ^ The miner chosen to mine the block - -> GasModel - -- ^ Gas model (pact Service config) - -> TxContext - -- ^ tx metadata and parent header - -> TxIdxInBlock - -> SPVSupport - -- ^ SPV support (validates cont proofs) - -> Command (Payload PublicMeta ParsedCode) - -- ^ command with payload to execute - -> Gas - -- ^ initial gas used - -> ModuleCache - -- ^ cached module state - -> ApplyCmdExecutionContext - -- ^ is this a local or send execution context? - -> IO (T3 (CommandResult [TxLogJson]) ModuleCache (S.Set PactWarning)) -applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxInBlock spv cmd initialGas mcache0 callCtx = do - T2 cr st <- runTransactionM cenv txst applyBuyGas - - let cache = _txCache st - warns = _txWarnings st - - pure $ T3 cr cache warns - where - stGasModel - | chainweb217Pact' = gasModel - | otherwise = _geGasModel freeGasEnv - txst = TransactionState mcache0 mempty 0 Nothing stGasModel mempty - quirkGasFee = v ^? versionQuirks . quirkGasFees . ixg cid . ix (ctxCurrentBlockHeight txCtx, txIdxInBlock) - - executionConfigNoHistory = ExecutionConfig - $ S.singleton FlagDisableHistoryInTransactionalMode - <> S.fromList - ([ FlagOldReadOnlyBehavior | isPactBackCompatV16 ] - ++ [ FlagPreserveModuleNameBug | not isModuleNameFix ] - ++ [ FlagPreserveNsModuleInstallBug | not isModuleNameFix2 ]) - <> flagsFor v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) - - cenv = TransactionEnv Transactional pdbenv logger gasLogger (ctxToPublicData txCtx) spv nid gasPrice - requestKey (fromIntegral gasLimit) executionConfigNoHistory quirkGasFee txFailuresCounter - - !requestKey = cmdToRequestKey cmd - !gasPrice = view cmdGasPrice cmd - !gasLimit = view cmdGasLimit cmd - !nid = networkIdOf cmd - currHeight = ctxCurrentBlockHeight txCtx - cid = ctxChainId txCtx - isModuleNameFix = enableModuleNameFix v cid currHeight - isModuleNameFix2 = enableModuleNameFix2 v cid currHeight - isPactBackCompatV16 = pactBackCompat_v16 v cid currHeight - chainweb213Pact' = guardCtx chainweb213Pact txCtx - chainweb217Pact' = guardCtx chainweb217Pact txCtx - chainweb219Pact' = guardCtx chainweb219Pact txCtx - chainweb223Pact' = guardCtx chainweb223Pact txCtx - allVerifiers = verifiersAt v cid currHeight - toEmptyPactError (PactError errty _ _ _) = PactError errty noInfo [] mempty - - toOldListErr pe = pe { peDoc = listErrMsg } - isOldListErr = \case - PactError EvalError _ _ doc -> "Unknown primitive" `T.isInfixOf` renderCompactText' doc - _ -> False - - redeemAllGas r = do - txGasUsed .= fromIntegral gasLimit - applyRedeem r - - applyBuyGas = - catchesPactError logger (onChainErrorPrintingFor txCtx) (buyGas txCtx cmd miner) >>= \case - Left e -> view txRequestKey >>= \rk -> - throwM $ Pact4BuyGasFailure $ Pact4GasPurchaseFailure (pact4RequestKeyToTransactionHash rk) e - Right _ -> checkTooBigTx initialGas gasLimit applyVerifiers redeemAllGas - - displayPactError e = do - r <- failTxWith e "tx failure for request key when running cmd" - redeemAllGas r - - stripPactError e = do - let e' = case callCtx of - ApplyLocal -> e - ApplySend -> toEmptyPactError e - r <- failTxWith e' "tx failure for request key when running cmd" - redeemAllGas r - - applyVerifiers = do - if chainweb223Pact' - then do - gasUsed <- use txGasUsed - let initGasRemaining = fromIntegral gasLimit - gasUsed - verifierResult <- - liftIO $ runVerifierPlugins - (ctxVersion txCtx, cid, currHeight) - logger allVerifiers initGasRemaining - (fromMaybe [] (cmd ^. cmdPayload . pVerifiers)) - case verifierResult of - Left err -> do - let errMsg = "Tx verifier error: " <> _verifierError err - cmdResult <- failTxWith - (PactError TxFailure noInfo [] (pretty errMsg)) - errMsg - redeemAllGas cmdResult - Right verifierGasRemaining -> do - txGasUsed += initGasRemaining - verifierGasRemaining - applyPayload - else applyPayload - - applyPayload = do - txGasModel .= gasModel - if chainweb217Pact' then txGasUsed += initialGas - else txGasUsed .= initialGas - - cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! runPayload cmd managedNamespacePolicy - case cr of - Left e - -- 2.19 onwards errors return on chain - | chainweb219Pact' -> displayPactError e - -- 2.17 errors were removed - | chainweb217Pact' -> stripPactError e - | chainweb213Pact' || not (isOldListErr e) -> displayPactError e - | otherwise -> do - r <- failTxWith (toOldListErr e) "tx failure for request key when running cmd" - redeemAllGas r - Right r -> applyRedeem r - - applyRedeem cr = do - txGasModel .= _geGasModel freeGasEnv - - r <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! redeemGas txCtx cmd miner - case r of - Left e -> - -- redeem gas failure is fatal (block-failing) so miner doesn't lose coins - fatal $ "tx failure for request key while redeeming gas: " <> sshow e - Right es -> do - logs <- use txLogs - - -- /local requires enriched results with metadata, while /send strips them. - -- when ?preflight=true is set, make sure that metadata occurs in result. - - let !cr' = case callCtx of - ApplySend -> set crLogs (Just logs) $ over crEvents (es ++) cr - ApplyLocal -> set crMetaData (Just $ J.toJsonViaEncode $ ctxToPublicData' txCtx) - $ set crLogs (Just logs) - $ over crEvents (es ++) cr - - return cr' - -listErrMsg :: Doc -listErrMsg = - "Unknown primitive \"list\" in determining cost of GUnreduced\nCallStack (from HasCallStack):\n error, called at src/Pact/Gas/Table.hs:209:22 in pact-4.2.0-fe223ad86f1795ba381192792f450820557e59c2926c747bf2aa6e398394bee6:Pact.Gas.Table" - -applyGenesisCmd - :: (Logger logger) - => logger - -- ^ Pact logger - -> PactDbEnv p - -- ^ Pact db environment - -> SPVSupport - -- ^ SPV support (validates cont proofs) - -> TxContext - -- ^ tx metadata - -> Command (Payload PublicMeta ParsedCode) - -- ^ command with payload to execute - -> IO (T2 (CommandResult [TxLogJson]) ModuleCache) -applyGenesisCmd logger dbEnv spv txCtx cmd = - second _txCache <$!> runTransactionM tenv txst go - where - nid = networkIdOf cmd - rk = cmdToRequestKey cmd - tenv = TransactionEnv - { _txMode = Transactional - , _txDbEnv = dbEnv - , _txLogger = logger - , _txGasLogger = Nothing - , _txPublicData = noPublicData - , _txSpvSupport = spv - , _txNetworkId = nid - , _txGasPrice = 0.0 - , _txRequestKey = rk - , _txGasLimit = 0 - , _txExecutionConfig = ExecutionConfig - $ flagsFor (ctxVersion txCtx) (ctxChainId txCtx) (view blockHeight $ ctxBlockHeader txCtx) - -- TODO this is very ugly. Genesis blocks need to install keysets - -- outside of namespaces so we need to disable Pact 4.4. It would be - -- preferable to have a flag specifically for the namespaced keyset - -- stuff so that we retain this power in genesis and upgrade txs even - -- after the block height where pact4.4 is on. - <> S.fromList [ FlagDisableInlineMemCheck, FlagDisablePact44 ] - , _txQuirkGasFee = Nothing - , _txTxFailuresCounter = Nothing - } - txst = TransactionState - { _txCache = mempty - , _txLogs = mempty - , _txGasUsed = 0 - , _txGasId = Nothing - , _txGasModel = _geGasModel freeGasEnv - , _txWarnings = mempty - } - - interp = initStateInterpreter - $ initCapabilities [magic_GENESIS, magic_COINBASE] - - go = do - -- TODO: fix with version recordification so that this matches the flags at genesis heights. - cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! runGenesis cmd permissiveNamespacePolicy interp - case cr of - Left e -> fatal $ "Genesis command failed: " <> sshow e - Right r -> r <$ debug "successful genesis tx for request key" - -flagsFor :: ChainwebVersion -> V.ChainId -> BlockHeight -> S.Set ExecutionFlag -flagsFor v cid bh = S.fromList $ concat - [ enablePactEvents' v cid bh - , enablePact40 v cid bh - , enablePact42 v cid bh - , enforceKeysetFormats' v cid bh - , enablePactModuleMemcheck v cid bh - , enablePact43 v cid bh - , enablePact431 v cid bh - , enablePact44 v cid bh - , enablePact45 v cid bh - , enableNewTrans v cid bh - , enablePact46 v cid bh - , enablePact47 v cid bh - , enablePact48 v cid bh - , disableReturnRTC v cid bh - , enablePact49 v cid bh - , enablePact410 v cid bh - , enablePact411 v cid bh - , enablePact412 v cid bh - ] - -applyCoinbase - :: (Logger logger) - => ChainwebVersion - -> logger - -- ^ Pact logger - -> PactDbEnv p - -- ^ Pact db environment - -> ParsedDecimal - -- ^ Miner reward - -> TxContext - -- ^ tx metadata and parent header - -> EnforceCoinbaseFailure - -- ^ enforce coinbase failure or not - -> CoinbaseUsePrecompiled - -- ^ always enable precompilation - -> ModuleCache - -> IO (T2 (CommandResult [TxLogJson]) (Maybe ModuleCache)) -applyCoinbase v logger dbEnv reward@(ParsedDecimal d) txCtx - (EnforceCoinbaseFailure enfCBFailure) (CoinbaseUsePrecompiled enablePC) mc - | fork1_3InEffect || enablePC = do - when chainweb213Pact' $ enforceKeyFormats - (\k -> throwM $ CoinbaseFailure $ Pact4CoinbaseFailure $ "Invalid miner key: " <> sshow k) - (validKeyFormats v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx)) - mk - let (cterm, cexec) = mkCoinbaseTerm mid mks reward - interp = Interpreter $ \_ -> do put initState; fmap pure (eval cterm) - - go interp cexec - | otherwise = do - cexec <- mkCoinbaseCmd mid mks reward - let interp = initStateInterpreter initState - go interp cexec - where - (Miner mid mks@(MinerKeys mk)) = _tcMiner txCtx - chainweb213Pact' = chainweb213Pact v cid bh - fork1_3InEffect = vuln797Fix v cid bh - throwCritical = fork1_3InEffect || enfCBFailure - ec = ExecutionConfig $ S.delete FlagEnforceKeyFormats $ fold - [ S.singleton FlagDisableModuleInstall - , S.singleton FlagDisableHistoryInTransactionalMode - , flagsFor v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) - ] - tenv = TransactionEnv Transactional dbEnv logger Nothing (ctxToPublicData txCtx) noSPVSupport - Nothing 0.0 rk 0 ec Nothing Nothing - txst = TransactionState mc mempty 0 Nothing (_geGasModel freeGasEnv) mempty - initState = setModuleCache mc $ initCapabilities [magic_COINBASE] - rk = RequestKey chash - parent = _tcParentHeader txCtx - - bh = ctxCurrentBlockHeight txCtx - cid = Chainweb._chainId parent - chash = Pact.Hash $ SB.toShort $ encodeToByteString $ view blockHash $ _parentHeader parent - -- NOTE: it holds that @ _pdPrevBlockHash pd == encode _blockHash@ - -- NOTE: chash includes the /quoted/ text of the parent header. - - go interp cexec = evalTransactionM tenv txst $! do - cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $ - applyExec' 0 interp cexec [] [] chash managedNamespacePolicy - - case cr of - Left e - | throwCritical -> throwM $ CoinbaseFailure $ Pact4CoinbaseFailure $ sshow e - | otherwise -> (`T2` Nothing) <$> failTxWith e "coinbase tx failure" - Right er -> do - debug - $! "successful coinbase of " - <> T.take 18 (sshow d) - <> " to " - <> sshow mid - - upgradedModuleCache <- applyUpgrades v cid bh - - logs <- use txLogs - - return $! T2 - CommandResult - { _crReqKey = rk - , _crTxId = _erTxId er - , _crResult = PactResult (Right (last (_erOutput er))) - , _crGas = _erGas er - , _crLogs = Just logs - , _crContinuation = _erExec er - , _crMetaData = Nothing - , _crEvents = _erEvents er - } - upgradedModuleCache - -applyLocal - :: (Logger logger) - => logger - -- ^ Pact logger - -> Maybe logger - -- ^ Pact gas logger - -> PactDbEnv p - -- ^ Pact db environment - -> GasModel - -- ^ Gas model (pact Service config) - -> TxContext - -- ^ tx metadata and parent header - -> SPVSupport - -- ^ SPV support (validates cont proofs) - -> Transaction - -- ^ command with payload to execute - -> ModuleCache - -> ExecutionConfig - -> IO (CommandResult [TxLogJson]) -applyLocal logger gasLogger dbEnv gasModel txCtx spv cmdIn mc execConfig = - evalTransactionM tenv txst go - where - !cmd = payloadObj <$> cmdIn `using` traverse rseq - !rk = cmdToRequestKey cmd - !nid = networkIdOf cmd - !chash = toUntypedHash $ _cmdHash cmd - !signers = _pSigners $ _cmdPayload cmd - !verifiers = fromMaybe [] $ _pVerifiers $ _cmdPayload cmd - !gasPrice = view cmdGasPrice cmd - !gasLimit = view cmdGasLimit cmd - tenv = TransactionEnv Local dbEnv logger gasLogger (ctxToPublicData txCtx) spv nid gasPrice - rk (fromIntegral gasLimit) execConfig Nothing Nothing - txst = TransactionState mc mempty 0 Nothing gasModel mempty - gas0 = initialGasOf (_cmdPayload cmdIn) - currHeight = ctxCurrentBlockHeight txCtx - cid = V._chainId txCtx - v = _chainwebVersion txCtx - allVerifiers = verifiersAt v cid currHeight - -- Note [Throw out verifier proofs eagerly] - !verifiersWithNoProof = - (fmap . fmap) (\_ -> ()) verifiers - `using` (traverse . traverse) rseq - - applyVerifiers m = do - let initGasRemaining = fromIntegral gasLimit - gas0 - verifierResult <- - liftIO $ runVerifierPlugins - (v, cid, currHeight) logger allVerifiers initGasRemaining - (fromMaybe [] $ cmd ^. cmdPayload . pVerifiers) - case verifierResult of - Left err -> do - let errMsg = "Tx verifier error: " <> _verifierError err - failTxWith - (PactError TxFailure noInfo [] (pretty errMsg)) - errMsg - Right verifierGasRemaining -> do - let gas1 = (initGasRemaining - verifierGasRemaining) + gas0 - applyPayload gas1 m - - applyPayload gas1 m = do - interp <- gasInterpreter gas1 - cr <- catchesPactError logger PrintsUnexpectedError $! case m of - Exec em -> - applyExec gas1 interp em signers verifiersWithNoProof chash managedNamespacePolicy - Continuation cm -> - applyContinuation gas1 interp cm signers chash managedNamespacePolicy - - case cr of - Left e -> failTxWith e "applyLocal" - Right r -> return $! r { _crMetaData = Just (J.toJsonViaEncode $ ctxToPublicData' txCtx) } - - go = checkTooBigTx gas0 gasLimit (applyVerifiers $ _pPayload $ _cmdPayload cmd) return - -readInitModules - :: forall logger tbl. (Logger logger) - => PactBlockM logger tbl ModuleCache -readInitModules = do - logger <- view (psServiceEnv . psLogger) - dbEnv <- view (psBlockDbEnv . to _cpPactDbEnv) - txCtx <- getTxContext noMiner noPublicMeta - - -- guarding chainweb 2.17 here to allow for - -- cache purging everything but coin and its - -- dependencies. - let - chainweb217Pact' = guardCtx chainweb217Pact txCtx - chainweb224Pact' = guardCtx chainweb224Pact txCtx - - parent = _tcParentHeader txCtx - v = ctxVersion txCtx - cid = ctxChainId txCtx - h = view blockHeight (_parentHeader parent) + 1 - rk = RequestKey chash - nid = Nothing - chash = pactInitialHash - tenv = TransactionEnv Local dbEnv logger Nothing (ctxToPublicData txCtx) noSPVSupport nid 0.0 - rk 0 emptyExecutionConfig Nothing Nothing - txst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty - interp = defaultInterpreter - die msg = internalError $ "readInitModules: " <> msg - mkCmd = buildExecParsedCode (pact4ParserVersion v cid h) Nothing - run :: Text -> ExecMsg ParsedCode -> TransactionM logger p PactValue - run msg cmd = do - er <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! - applyExec' 0 interp cmd [] [] chash permissiveNamespacePolicy - case er of - Left e -> die $ msg <> ": failed: " <> sshow e - Right r -> case _erOutput r of - [] -> die $ msg <> ": empty result" - (o:_) -> return o - - - go :: TransactionM logger p ModuleCache - go = do - - -- see if fungible-v2 is there - checkCmd <- liftIO $ mkCmd "(contains \"fungible-v2\" (list-modules))" - checkFv2 <- run "check fungible-v2" checkCmd - hasFv2 <- case checkFv2 of - (PLiteral (LBool b)) -> return b - t -> die $ "got non-bool result from module read: " <> T.pack (showPretty t) - - -- see if fungible-xchain-v1 is there - checkCmdx <- liftIO $ mkCmd "(contains \"fungible-xchain-v1\" (list-modules))" - checkFx <- run "check fungible-xchain-v1" checkCmdx - hasFx <- case checkFx of - (PLiteral (LBool b)) -> return b - t -> die $ "got non-bool result from module read: " <> T.pack (showPretty t) - - -- load modules by referencing members - refModsCmd <- liftIO $ mkCmd $ T.intercalate " " $ - [ "coin.MINIMUM_PRECISION" - , "ns.GUARD_SUCCESS" - , "(use gas-payer-v1)" - , "fungible-v1.account-details"] ++ - [ "fungible-v2.account-details" | hasFv2 ] ++ - [ "(let ((m:module{fungible-xchain-v1} coin)) 1)" | hasFx ] - void $ run "load modules" refModsCmd - - -- return loaded cache - use txCache - - -- Only load coin and its dependencies for chainweb >=2.17 - -- Note: no need to check if things are there, because this - -- requires a block height that witnesses the invariant. - -- - -- if this changes, we must change the filter in 'updateInitCache' - goCw217 :: TransactionM logger p ModuleCache - goCw217 = do - coinDepCmd <- liftIO $ mkCmd "coin.MINIMUM_PRECISION" - void $ run "load modules" coinDepCmd - use txCache - - if - | chainweb224Pact' -> pure mempty - | chainweb217Pact' -> liftIO $ evalTransactionM tenv txst goCw217 - | otherwise -> liftIO $ evalTransactionM tenv txst go - --- | Apply (forking) upgrade transactions and module cache updates --- at a particular blockheight. --- --- This is the place where we consistently /introduce/ new transactions --- into the blockchain along with module cache updates. The only other --- places are Pact Service startup and the --- empty-module-cache-after-initial-rewind case caught in 'execTransactions' --- which both hit the database. --- -applyUpgrades - :: forall logger p - . (Logger logger) - => ChainwebVersion - -> Chainweb.ChainId - -> BlockHeight - -> TransactionM logger p (Maybe ModuleCache) -applyUpgrades v cid height - | Just Pact4Upgrade{_pact4UpgradeTransactions = txs, _legacyUpgradeIsPrecocious = isPrecocious} <- - v ^? versionUpgrades . atChain cid . ix height = applyUpgrade txs isPrecocious - | Just Pact5Upgrade{} <- - v ^? versionUpgrades . atChain cid . ix height = error "Expected Pact 4 upgrade, got Pact 5" - | cleanModuleCache v cid height = filterModuleCache - | otherwise = return Nothing - where - installCoinModuleAdmin = set (evalCapabilities . capModuleAdmin) $ S.singleton (ModuleName "coin" Nothing) - - filterModuleCache = do - mc <- use txCache - pure $ Just $ filterModuleCacheByKey (== "coin") mc - - applyUpgrade :: [Transaction] -> Bool -> TransactionM logger p (Maybe ModuleCache) - applyUpgrade upg isPrecocious = do - infoLog "Applying upgrade!" - let payloads = map (fmap payloadObj) upg - - -- - -- In order to prime the module cache with all new modules for subsequent - -- blocks, the caches from each tx are collected and the union of all - -- those caches is returned. The calling code adds this new cache to the - -- init cache in the pact service state (_psInitCache). - -- - - let flags = flagsFor v cid (if isPrecocious then height + 1 else height) - caches <- local - (txExecutionConfig .~ ExecutionConfig flags) - (mapM applyTx payloads) - return $ Just $ mconcat $ reverse caches - - interp = initStateInterpreter - $ installCoinModuleAdmin - $ initCapabilities [mkMagicCapSlot "REMEDIATE"] - - applyTx tx = do - infoLog $ "Running upgrade tx " <> sshow (_cmdHash tx) - - tryAllSynchronous (runGenesis tx permissiveNamespacePolicy interp) >>= \case - Right _ -> use txCache - Left e -> do - logError $ "Upgrade transaction failed! " <> sshow e - throwM e - -failTxWith - :: (Logger logger) - => PactError - -> Text - -> TransactionM logger p (CommandResult [TxLogJson]) -failTxWith err msg = do - logs <- use txLogs - gas <- view txGasLimit -- error means all gas was charged - rk <- view txRequestKey - l <- view txLogger - - liftIO $ logFunction l L.Debug - (Pact4TxFailureLog rk err msg) - liftIO . traverse_ inc - =<< view txTxFailuresCounter - - return $! CommandResult rk Nothing (PactResult (Left err)) - gas (Just logs) Nothing Nothing [] - -runPayload - :: (Logger logger) - => Command (Payload PublicMeta ParsedCode) - -> NamespacePolicy - -> TransactionM logger p (CommandResult [TxLogJson]) -runPayload cmd nsp = do - g0 <- use txGasUsed - interp <- gasInterpreter g0 - - -- Note [Throw out verifier proofs eagerly] - let !verifiersWithNoProof = - (fmap . fmap) (\_ -> ()) verifiers - `using` (traverse . traverse) rseq - - case payload of - Exec pm -> - applyExec g0 interp pm signers verifiersWithNoProof chash nsp - Continuation ym -> - applyContinuation g0 interp ym signers chash nsp - - - where - verifiers = fromMaybe [] $ _pVerifiers $ _cmdPayload cmd - signers = _pSigners $ _cmdPayload cmd - chash = toUntypedHash $ _cmdHash cmd - payload = _pPayload $ _cmdPayload cmd - --- | Run genesis transaction payloads with custom interpreter --- -runGenesis - :: (Logger logger) - => Command (Payload PublicMeta ParsedCode) - -> NamespacePolicy - -> Interpreter p - -> TransactionM logger p (CommandResult [TxLogJson]) -runGenesis cmd nsp interp = case payload of - Exec pm -> - applyExec 0 interp pm signers verifiersWithNoProof chash nsp - Continuation ym -> - applyContinuation 0 interp ym signers chash nsp - where - signers = _pSigners $ _cmdPayload cmd - verifiers = fromMaybe [] $ _pVerifiers $ _cmdPayload cmd - -- Note [Throw out verifier proofs eagerly] - !verifiersWithNoProof = - (fmap . fmap) (\_ -> ()) verifiers - `using` (traverse . traverse) rseq - chash = toUntypedHash $ _cmdHash cmd - payload = _pPayload $ _cmdPayload cmd - --- | Execute an 'ExecMsg' and Return the result with module cache --- -applyExec - :: (Logger logger) - => Gas - -> Interpreter p - -> ExecMsg ParsedCode - -> [Signer] - -> [Verifier ()] - -> Hash - -> NamespacePolicy - -> TransactionM logger p (CommandResult [TxLogJson]) -applyExec initialGas interp em senderSigs verifiers hsh nsp = do - EvalResult{..} <- applyExec' initialGas interp em senderSigs verifiers hsh nsp - for_ _erLogGas $ \gl -> gasLog $ "gas logs: " <> sshow gl - !logs <- use txLogs - !rk <- view txRequestKey - - -- concat tx warnings with eval warnings - modify' $ txWarnings <>~ _erWarnings - - -- applyExec enforces non-empty expression set so `last` ok - -- forcing it here for lazy errors. TODO NFData the Pacts - let !lastResult = force $ last _erOutput - return $ CommandResult rk _erTxId (PactResult (Right lastResult)) - _erGas (Just logs) _erExec Nothing _erEvents - --- | Variation on 'applyExec' that returns 'EvalResult' as opposed to --- wrapping it up in a JSON result. --- -applyExec' - :: (Logger logger) - => Gas - -> Interpreter p - -> ExecMsg ParsedCode - -> [Signer] - -> [Verifier ()] - -> Hash - -> NamespacePolicy - -> TransactionM logger p EvalResult -applyExec' initialGas interp (ExecMsg parsedCode execData) senderSigs verifiersWithNoProof hsh nsp - | null (_pcExps parsedCode) = throwCmdEx "No expressions found" - | otherwise = do - - eenv <- mkEvalEnv nsp (MsgData execData Nothing hsh senderSigs verifiersWithNoProof) - - setEnvGas initialGas eenv - - evalResult <- liftIO $! evalExec interp eenv parsedCode - -- if we specified this transaction's gas fee manually as a "quirk", - -- here we set the result's gas fee to agree with that - quirkGasFee <- view txQuirkGasFee - let quirkedEvalResult = case quirkGasFee of - Nothing -> evalResult - Just fee -> evalResult { _erGas = fee } - - for_ (_erExec quirkedEvalResult) $ \pe -> debug - $ "applyExec: new pact added: " - <> sshow (_pePactId pe, _peStep pe, _peYield pe, _peExecuted pe) - - -- set log + cache updates + used gas - setTxResultState quirkedEvalResult - - return quirkedEvalResult - -enablePactEvents' :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePactEvents' v cid bh = [FlagDisablePactEvents | not (enablePactEvents v cid bh)] - -enforceKeysetFormats' :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enforceKeysetFormats' v cid bh = [FlagEnforceKeyFormats | enforceKeysetFormats v cid bh] - -enablePact40 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact40 v cid bh = [FlagDisablePact40 | not (pact4Coin3 v cid bh)] - -enablePact42 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact42 v cid bh = [FlagDisablePact42 | not (pact42 v cid bh)] - -enablePactModuleMemcheck :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePactModuleMemcheck v cid bh = [FlagDisableInlineMemCheck | not (chainweb213Pact v cid bh)] - -enablePact43 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact43 v cid bh = [FlagDisablePact43 | not (chainweb214Pact v cid bh)] - -enablePact431 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact431 v cid bh = [FlagDisablePact431 | not (chainweb215Pact v cid bh)] - -enablePact44 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact44 v cid bh = [FlagDisablePact44 | not (chainweb216Pact v cid bh)] - -enablePact45 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact45 v cid bh = [FlagDisablePact45 | not (chainweb217Pact v cid bh)] - -enableNewTrans :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enableNewTrans v cid bh = [FlagDisableNewTrans | not (pact44NewTrans v cid bh)] - -enablePact46 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact46 v cid bh = [FlagDisablePact46 | not (chainweb218Pact v cid bh)] - -enablePact47 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact47 v cid bh = [FlagDisablePact47 | not (chainweb219Pact v cid bh)] - -enablePact48 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact48 v cid bh = [FlagDisablePact48 | not (chainweb220Pact v cid bh)] - -enablePact49 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact49 v cid bh = [FlagDisablePact49 | not (chainweb221Pact v cid bh)] - -enablePact410 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact410 v cid bh = [FlagDisablePact410 | not (chainweb222Pact v cid bh)] - -enablePact411 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact411 v cid bh = [FlagDisablePact411 | not (chainweb223Pact v cid bh)] - -enablePact412 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact412 v cid bh = [FlagDisablePact412 | not (chainweb224Pact v cid bh)] - --- | Even though this is not forking, abstracting for future shutoffs -disableReturnRTC :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -disableReturnRTC _v _cid _bh = [FlagDisableRuntimeReturnTypeChecking] - --- | Execute a 'ContMsg' and return the command result and module cache --- -applyContinuation - :: (Logger logger) - => Gas - -> Interpreter p - -> ContMsg - -> [Signer] - -> Hash - -> NamespacePolicy - -> TransactionM logger p (CommandResult [TxLogJson]) -applyContinuation initialGas interp cm senderSigs hsh nsp = do - EvalResult{..} <- applyContinuation' initialGas interp cm senderSigs hsh nsp - for_ _erLogGas $ \gl -> gasLog $ "gas logs: " <> sshow gl - logs <- use txLogs - rk <- view txRequestKey - - -- set tx warnings to eval warnings - txWarnings <>= _erWarnings - - -- last safe here because cont msg is guaranteed one exp - return $! CommandResult rk _erTxId (PactResult (Right (last _erOutput))) - _erGas (Just logs) _erExec Nothing _erEvents - - -setEnvGas :: Gas -> EvalEnv e -> TransactionM logger p () -setEnvGas initialGas = liftIO . views eeGas (`writeIORef` gasToMilliGas initialGas) - --- | Execute a 'ContMsg' and return just eval result, not wrapped in a --- 'CommandResult' wrapper --- -applyContinuation' - :: Gas - -> Interpreter p - -> ContMsg - -> [Signer] - -> Hash - -> NamespacePolicy - -> TransactionM logger p EvalResult -applyContinuation' initialGas interp cm@(ContMsg pid s rb d _) senderSigs hsh nsp = do - - eenv <- mkEvalEnv nsp (MsgData d pactStep hsh senderSigs []) - - setEnvGas initialGas eenv - - evalResult <- liftIO $! evalContinuation interp eenv cm - -- if we specified this transaction's gas fee manually as a "quirk", - -- here we set the result's gas fee to agree with that - quirkGasFee <- view txQuirkGasFee - let quirkedEvalResult = case quirkGasFee of - Nothing -> evalResult - Just fee -> evalResult { _erGas = fee } - - setTxResultState quirkedEvalResult - - return quirkedEvalResult - where - pactStep = Just $ PactStep s rb pid Nothing - --- | Build and execute 'coin.buygas' command from miner info and user command --- info (see 'TransactionExec.applyCmd') --- --- see: 'pact/coin-contract/coin.pact#fund-tx' --- -buyGas :: (Logger logger) => TxContext -> Command (Payload PublicMeta ParsedCode) -> Miner -> TransactionM logger p () -buyGas txCtx cmd (Miner mid mks) = go - where - isChainweb224Pact = guardCtx chainweb224Pact txCtx - sender = view (cmdPayload . pMeta . pmSender) cmd - - initState mc logGas = - set evalLogGas (guard logGas >> Just [("GBuyGas",0)]) $ setModuleCache mc $ initCapabilities [magic_GAS] - - run input = do - (findPayer txCtx cmd) >>= \r -> case r of - Nothing -> input - Just withPayerCap -> withPayerCap input - - (Hash chash) = toUntypedHash (_cmdHash cmd) - bgHash = Hash (chash <> "-buygas") - - go = do - mcache <- use txCache - supply <- gasSupplyOf <$> view txGasLimit <*> view txGasPrice - logGas <- isJust <$> view txGasLogger - - let (buyGasTerm, buyGasCmd) = - -- post-chainweb 2.24, we call buy-gas directly rather than - -- going through fund-tx which is a defpact. - if isChainweb224Pact - then mkBuyGasTerm sender supply - else mkFundTxTerm mid mks sender supply - -- I don't recall why exactly, but we set up an interpreter - -- that ignores its argument and instead executes a term - -- of our choice. we do the same to redeem gas. - interp mc = Interpreter $ \_input -> - put (initState mc logGas) >> run (pure <$> eval buyGasTerm) - - let - gasCapName = QualifiedName (ModuleName "coin" Nothing) "GAS" noInfo - signedForGas signer = - any (\sc -> _scName sc == gasCapName) (_siCapList signer) - addDebit signer - | signedForGas signer = - signer & siCapList %~ (debitCap sender:) - | otherwise = signer - addDebitToSigners = - fmap addDebit - - -- no verifiers are allowed in buy gas - -- quirked gas is not used either - result <- locally txQuirkGasFee (const Nothing) $ - applyExec' 0 (interp mcache) buyGasCmd - (addDebitToSigners $ _pSigners $ _cmdPayload cmd) [] bgHash managedNamespacePolicy - - case _erExec result of - Nothing - | isChainweb224Pact -> - return () - | otherwise -> - -- should never occur pre-chainweb 2.24: - -- would mean coin.fund-tx is not a pact - fatal "buyGas: Internal error - empty continuation before 2.24 fork" - Just pe - | isChainweb224Pact -> - fatal "buyGas: Internal error - continuation found after 2.24 fork" - | otherwise -> - void $! txGasId .= (Just $! GasId (_pePactId pe)) - -findPayer - :: TxContext - -> Command (Payload PublicMeta ParsedCode) - -> Eval e (Maybe (Eval e [Term Name] -> Eval e [Term Name])) -findPayer txCtx cmd = runMaybeT $ do - (!m,!qn,!as) <- MaybeT findPayerCap - pMod <- MaybeT $ lookupModule qn m - capRef <- MaybeT $ return $ lookupIfaceModRef qn pMod - return $ runCap (getInfo qn) capRef as - where - setEnvMsgBody v e = set eeMsgBody v e - - findPayerCap :: Eval e (Maybe (ModuleName,QualifiedName,[PactValue])) - findPayerCap = preview $ eeMsgSigs . folded . folded . to sigPayerCap . _Just - - sigPayerCap (SigCapability q@(QualifiedName m n _) as) - | n == "GAS_PAYER" = Just (m,q,as) - sigPayerCap _ = Nothing - - gasPayerIface = ModuleName "gas-payer-v1" Nothing - - lookupIfaceModRef (QualifiedName _ n _) (ModuleData (MDModule Module{..}) refs _) - | gasPayerIface `elem` _mInterfaces = SHM.lookup n refs - lookupIfaceModRef _ _ = Nothing - - mkApp i r as = App (TVar r i) (map (liftTerm . fromPactValue) as) i - - runCap i capRef as input = do - let msgBody = enrichedMsgBody cmd - enrichMsgBody | guardCtx pactBackCompat_v16 txCtx = id - | otherwise = setEnvMsgBody (toLegacyJson msgBody) - ar <- local enrichMsgBody $ do - (cap, capDef, args) <- appToCap $ mkApp i capRef as - evalCap i CapCallStack False (cap, capDef, args, i) - - case ar of - NewlyAcquired -> do - r <- input - popCapStack (const (return ())) - return r - _ -> evalError' i "Internal error, GAS_PAYER already acquired" - -enrichedMsgBody :: Command (Payload PublicMeta ParsedCode) -> Value -enrichedMsgBody cmd = case (_pPayload $ _cmdPayload cmd) of - Exec (ExecMsg (ParsedCode _ exps) userData) -> - object [ "tx-type" A..= ( "exec" :: Text) - , "exec-code" A..= map renderCompactText exps - , "exec-user-data" A..= pactFriendlyUserData (_getLegacyValue userData) ] - Continuation (ContMsg pid step isRollback userData proof) -> - object [ "tx-type" A..= ("cont" :: Text) - , "cont-pact-id" A..= toJsonViaEncode pid - , "cont-step" A..= toJsonViaEncode (LInteger $ toInteger step) - , "cont-is-rollback" A..= toJsonViaEncode (LBool isRollback) - , "cont-user-data" A..= pactFriendlyUserData (_getLegacyValue userData) - , "cont-has-proof" A..= toJsonViaEncode (isJust proof) - ] - where - pactFriendlyUserData Null = object [] - pactFriendlyUserData v = v - --- | Build and execute 'coin.redeem-gas' command from miner info and previous --- command results (see 'TransactionExec.applyCmd') --- --- see: 'pact/coin-contract/coin.pact#fund-tx' --- -redeemGas :: (Logger logger) => TxContext -> Command (Payload PublicMeta ParsedCode) -> Miner -> TransactionM logger p [PactEvent] -redeemGas txCtx cmd (Miner mid mks) = do - mcache <- use txCache - let sender = view (cmdPayload . pMeta . pmSender) cmd - fee <- gasSupplyOf <$> use txGasUsed <*> view txGasPrice - -- if we're past chainweb 2.24, we don't use defpacts for gas - if guardCtx chainweb224Pact txCtx - then do - total <- gasSupplyOf <$> view txGasLimit <*> view txGasPrice - let (redeemGasTerm, redeemGasCmd) = - mkRedeemGasTerm mid mks sender total fee - -- I don't recall why exactly, but we set up an interpreter - -- that ignores its argument and instead executes a term - -- of our choice. we do the same to buy gas. - interp = Interpreter $ \_input -> do - -- we don't log gas when redeeming, because nobody can pay for it - put (initCapabilities [magic_GAS] & setModuleCache mcache) - fmap List.singleton (eval redeemGasTerm) - (Hash chash) = toUntypedHash (_cmdHash cmd) - rgHash = Hash (chash <> "-redeemgas") - - locally txQuirkGasFee (const Nothing) $ _erEvents <$> - applyExec' 0 interp redeemGasCmd - (_pSigners $ _cmdPayload cmd) - [] - rgHash - managedNamespacePolicy - else do - GasId gid <- use txGasId >>= \case - Nothing -> fatal $! "redeemGas: no gas id in scope for gas refunds" - Just g -> return g - let redeemGasCmd = - ContMsg gid 1 False (toLegacyJson $ object [ "fee" A..= toJsonViaEncode fee ]) Nothing - - fmap _crEvents $ locally txQuirkGasFee (const Nothing) $ - applyContinuation 0 (initState mcache) redeemGasCmd - (_pSigners $ _cmdPayload cmd) (toUntypedHash $ _cmdHash cmd) - managedNamespacePolicy - - where - initState mc = initStateInterpreter - $ setModuleCache mc - $ initCapabilities [magic_GAS] - - --- ---------------------------------------------------------------------------- -- --- Utilities - --- | Initialize a fresh eval state with magic capabilities. --- This is the way we inject the correct guards into the environment --- during Pact code execution --- -initCapabilities :: [CapSlot SigCapability] -> EvalState -initCapabilities cs = set (evalCapabilities . capStack) cs emptyEvalState -{-# INLINABLE initCapabilities #-} - -initStateInterpreter :: EvalState -> Interpreter e -initStateInterpreter s = Interpreter (put s >>) - --- | Check whether the cost of running a tx is more than the allowed --- gas limit and do some action depending on the outcome --- -checkTooBigTx - :: (Logger logger) - => Gas - -> GasLimit - -> TransactionM logger p (CommandResult [TxLogJson]) - -> (CommandResult [TxLogJson] -> TransactionM logger p (CommandResult [TxLogJson])) - -> TransactionM logger p (CommandResult [TxLogJson]) -checkTooBigTx initialGas gasLimit next onFail - | initialGas >= fromIntegral gasLimit = do - - let !pe = PactError GasError noInfo [] - $ "Tx too big (" <> pretty initialGas <> "), limit " - <> pretty gasLimit - - r <- failTxWith pe "Tx too big" - onFail r - | otherwise = next - -gasInterpreter :: Gas -> TransactionM logger db (Interpreter p) -gasInterpreter g = do - mc <- use txCache - logGas <- isJust <$> view txGasLogger - return $ initStateInterpreter - $ set evalLogGas (guard logGas >> Just [("GTxSize",g)]) -- enables gas logging - $ setModuleCache mc emptyEvalState - - --- | Initial gas charged for transaction size --- ignoring the size of a continuation proof, if present --- -initialGasOf :: PayloadWithText PublicMeta ParsedCode -> Gas -initialGasOf payload = gasFee - where - feePerByte :: Rational = 0.01 - - contProofSize = - case _pPayload (payloadObj payload) of - Continuation (ContMsg _ _ _ _ (Just (ContProof p))) -> B.length p - _ -> 0 - txSize = SB.length (payloadBytes payload) - contProofSize - - costPerByte = fromIntegral txSize * feePerByte - sizePenalty = txSizeAccelerationFee costPerByte - gasFee = ceiling (costPerByte + sizePenalty) -{-# INLINE initialGasOf #-} - -txSizeAccelerationFee :: Rational -> Rational -txSizeAccelerationFee costPerByte = total - where - total = (costPerByte / bytePenalty) ^ power - bytePenalty = 512 - power :: Integer = 7 -{-# INLINE txSizeAccelerationFee #-} - --- | Set the module cache of a pact 'EvalState' --- -setModuleCache - :: ModuleCache - -> EvalState - -> EvalState -setModuleCache mcache es = - let allDeps = foldMap (allModuleExports . fst) $ _getModuleCache mcache - in set (evalRefs . rsQualifiedDeps) allDeps $ set (evalRefs . rsLoadedModules) c es - where - c = moduleCacheToHashMap mcache -{-# INLINE setModuleCache #-} - --- | Set tx result state --- -setTxResultState :: EvalResult -> TransactionM logger db () -setTxResultState er = do - txLogs <>= _erLogs er - txCache .= moduleCacheFromHashMap (_erLoadedModules er) - txGasUsed .= _erGas er -{-# INLINE setTxResultState #-} - --- | Make an 'EvalEnv' given a tx env + state --- -mkEvalEnv - :: NamespacePolicy - -> MsgData - -> TransactionM logger db (EvalEnv db) -mkEvalEnv nsp msg = do - tenv <- ask - genv <- GasEnv - <$> view (txGasLimit . to (MilliGasLimit . gasToMilliGas)) - <*> view txGasPrice - <*> use txGasModel - fmap (set eeSigCapBypass txCapBypass) - $ liftIO $ setupEnv tenv genv - where - setupEnv tenv genv = setupEvalEnv (_txDbEnv tenv) Nothing (_txMode tenv) - msg (versionedNativesRefStore (_txExecutionConfig tenv)) genv - nsp (_txSpvSupport tenv) (_txPublicData tenv) (_txExecutionConfig tenv) - txCapBypass = - M.fromList - [ (wizaDebit, (wizaBypass, wizaMH)) - , (skdxDebit, (kdxBypass, skdxMH)) - , (collectGallinasMarket, (collectGallinasBypass, collectGallinasMH)) - , (marmaladeGuardPolicyMint, (marmaladeBypass, marmaladeGuardPolicyMH)) - ] - where - -- wiza code - wizaDebit = QualifiedName "free.wiza" "DEBIT" noInfo - wizaMH = unsafeModuleHashFromB64Text "8b4USA1ZNVoLYRT1LBear4YKt3GB2_bl0AghZU8QxjI" - wizEquipmentOwner = QualifiedName "free.wiz-equipment" "OWNER" noInfo - wizEquipmentAcctGuard = QualifiedName "free.wiz-equipment" "ACCOUNT_GUARD" noInfo - wizArenaAcctGuard = QualifiedName "free.wiz-arena" "ACCOUNT_GUARD" noInfo - wizArenaOwner = QualifiedName "free.wiz-arena" "OWNER" noInfo - wizaTransfer = QualifiedName "free.wiza" "TRANSFER" noInfo - - wizaBypass granted sigCaps = - let debits = filter ((== wizaDebit) . _scName) $ S.toList granted - in all (\c -> any (match c) sigCaps) debits - where - match prov sigCap = fromMaybe False $ do - guard $ _scName sigCap `elem` wizaBypassList - sender <- preview _head (_scArgs prov) - (== sender) <$> preview _head (_scArgs sigCap) - wizaBypassList = - [ wizArenaOwner - , wizEquipmentOwner - , wizaTransfer - , wizEquipmentAcctGuard - , wizArenaAcctGuard] - -- kaddex code - skdxDebit = QualifiedName "kaddex.skdx" "DEBIT" noInfo - skdxMH = unsafeModuleHashFromB64Text "g90VWmbKj87GkMkGs8uW947kh_Wg8JdQowa8rO_vZ1M" - kdxUnstake = QualifiedName "kaddex.staking" "UNSTAKE" noInfo - - kdxBypass granted sigCaps = - let debits = filter ((== skdxDebit) . _scName) $ S.toList granted - in all (\c -> S.member (SigCapability kdxUnstake (_scArgs c)) sigCaps) debits - -- Collect-gallinas code - collectGallinasMH = unsafeModuleHashFromB64Text "x3BLGdidqSjUQy5q3MorGco9mBDpoVTh_Yoagzu0hls" - collectGallinasMarket = QualifiedName "free.collect-gallinas" "MARKET" noInfo - collectGallinasAcctGuard = QualifiedName "free.collect-gallinas" "ACCOUNT_GUARD" noInfo - - collectGallinasBypass granted sigCaps = fromMaybe False $ do - let mkt = filter ((== collectGallinasMarket) . _scName) $ S.toList granted - let matchingGuard provided toMatch = _scName toMatch == collectGallinasAcctGuard && (_scArgs provided == _scArgs toMatch) - pure $ all (\c -> any (matchingGuard c) sigCaps) mkt - -- marmalade code - marmaladeGuardPolicyMH = unsafeModuleHashFromB64Text "LB5sRKx8jN3FP9ZK-rxDK7Bqh0gyznprzS8L4jYlT5o" - marmaladeGuardPolicyMint = QualifiedName "marmalade-v2.guard-policy-v1" "MINT" noInfo - marmaladeLedgerMint = QualifiedName "marmalade-v2.ledger" "MINT-CALL" noInfo - - marmaladeBypass granted sigCaps = fromMaybe False $ do - let mkt = filter ((== marmaladeGuardPolicyMint) . _scName) $ S.toList granted - let matchingGuard provided toMatch = _scName toMatch == marmaladeLedgerMint && (_scArgs provided == _scArgs toMatch) - pure $ all (\c -> any (matchingGuard c) sigCaps) mkt - -unsafeModuleHashFromB64Text :: Text -> ModuleHash -unsafeModuleHashFromB64Text = - either error ModuleHash . PU.fromText' - --- | Managed namespace policy CAF --- -managedNamespacePolicy :: NamespacePolicy -managedNamespacePolicy = SmartNamespacePolicy False - (QualifiedName (ModuleName "ns" Nothing) "validate" noInfo) -{-# NOINLINE managedNamespacePolicy #-} - --- | Builder for "magic" capabilities given a magic cap name --- -mkMagicCapSlot :: Text -> CapSlot SigCapability -mkMagicCapSlot c = CapSlot CapCallStack (mkCoinCap c []) [] -{-# INLINE mkMagicCapSlot #-} - -mkCoinCap :: Text -> [PactValue] -> SigCapability -mkCoinCap c as = SigCapability fqn as - where - mn = ModuleName "coin" Nothing - fqn = QualifiedName mn c noInfo -{-# INLINE mkCoinCap #-} - --- | Build the 'ExecMsg' for some pact code fed to the function. The 'value' --- parameter is for any possible environmental data that needs to go into --- the 'ExecMsg'. --- -buildExecParsedCode - :: PactParserVersion - -> Maybe Value - -> Text - -> IO (ExecMsg ParsedCode) -buildExecParsedCode ppv value code = maybe (go Null) go value - where - go val = case parsePact ppv code of - Right !t -> pure $! ExecMsg t (toLegacyJson val) - -- if we can't construct coin contract calls, this should - -- fail fast - Left err -> internalError $ "buildExecParsedCode: parse failed: " <> T.pack err - --- | Retrieve public metadata from a command --- -publicMetaOf :: Command (Payload PublicMeta code) -> PublicMeta -publicMetaOf = _pMeta . _cmdPayload -{-# INLINE publicMetaOf #-} - --- | Retrieve the optional Network identifier from a command --- -networkIdOf :: Command (Payload PublicMeta code) -> Maybe NetworkId -networkIdOf = _pNetworkId . _cmdPayload -{-# INLINE networkIdOf #-} - --- | Calculate the gas fee (pact-generate gas cost * user-specified gas price), --- rounding to the nearest stu. --- -gasSupplyOf :: Gas -> GasPrice -> GasSupply -gasSupplyOf gas (GasPrice (ParsedDecimal gp)) = GasSupply (ParsedDecimal gs) - where - gs = toCoinUnit ((fromIntegral gas) * gp) -{-# INLINE gasSupplyOf #-} - --- | Round to the nearest Stu --- -toCoinUnit :: Decimal -> Decimal -toCoinUnit = roundTo 12 -{-# INLINE toCoinUnit #-} - -gasLog :: (Logger logger) => Text -> TransactionM logger db () -gasLog m = do - l <- view txGasLogger - rk <- view txRequestKey - for_ l $ \logger -> - logInfo_ logger $ m <> ": " <> sshow rk - --- | Log request keys at DEBUG when successful --- -debug :: (Logger logger) => Text -> TransactionM logger db () -debug s = do - l <- view txLogger - rk <- view txRequestKey - logDebug_ l $ s <> ": " <> sshow rk - --- | Denotes fatal failure points in the tx exec process --- -fatal :: (Logger logger) => Text -> TransactionM logger db a -fatal e = do - l <- view txLogger - rk <- view txRequestKey - - logError_ l - $ "critical transaction failure: " - <> sshow rk <> ": " <> e - - throwM $ PactTransactionExecError (fromUntypedHash $ unRequestKey rk) e - -logError :: (Logger logger) => Text -> TransactionM logger db () -logError msg = view txLogger >>= \l -> logError_ l msg - -infoLog :: (Logger logger) => Text -> TransactionM logger db () -infoLog msg = view txLogger >>= \l -> logInfo_ l msg diff --git a/src/Chainweb/Pact4/Types.hs b/src/Chainweb/Pact4/Types.hs deleted file mode 100644 index 92ad20eb0a..0000000000 --- a/src/Chainweb/Pact4/Types.hs +++ /dev/null @@ -1,326 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} - -module Chainweb.Pact4.Types - ( getInitCache - , updateInitCache - , updateInitCacheM - - , GasSupply(..) - - -- * TxContext - , TxContext(..) - , ctxToPublicData - , ctxToPublicData' - , ctxBlockHeader - , ctxCurrentBlockHeight - , ctxChainId - , ctxVersion - , guardCtx - , getTxContext - , localLabelBlock - - , catchesPactError - , UnexpectedErrorPrinting(..) - , GasId(..) - , EnforceCoinbaseFailure(..) - , CoinbaseUsePrecompiled(..) - , PactBlockM(..) - , liftPactServiceM - , runPactBlockM - , tracePactBlockM - , tracePactBlockM' - - , getGasModel - ) where - -import Control.Exception.Safe -import Control.Lens -import Control.Monad.Reader -import Control.Monad.State.Strict - -import Data.Aeson hiding (Error,(.=)) -import qualified Data.Map.Strict as M -import Data.Text (Text) - --- internal pact modules - -import qualified Pact.JSON.Encode as J -import Pact.Parse (ParsedDecimal) -import Pact.Types.ChainMeta -import Pact.Types.Gas -import Pact.Types.Info -import Pact.Types.Pretty (viaShow) -import Pact.Types.Runtime (PactError(..), PactErrorType(..)) -import Pact.Types.Term - --- internal chainweb modules - -import Chainweb.BlockCreationTime -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Miner.Pact -import Chainweb.Logger -import Chainweb.Time -import Chainweb.Utils -import Chainweb.Version -import Utils.Logging.Trace - -import Pact.Gas.Table -import Chainweb.Pact.Types -import Chainweb.Pact4.ModuleCache -import Chainweb.Version.Guards - - --- | Indicates a computed gas charge (gas amount * gas price) -newtype GasSupply = GasSupply { _gasSupply :: ParsedDecimal } - deriving (Eq,Ord) - deriving newtype (Num,Real,Fractional,FromJSON) -instance Show GasSupply where show (GasSupply g) = show g - -instance J.Encode GasSupply where - build = J.build . _gasSupply - --- | Update init cache at adjusted parent block height (APBH). --- Contents are merged with cache found at or before APBH. --- APBH is 0 for genesis and (parent block height + 1) thereafter. -updateInitCache :: ModuleCache -> ParentHeader -> PactServiceM logger tbl () -updateInitCache mc ph = get >>= \PactServiceState{..} -> do - let bf 0 = 0 - bf h = succ h - let pbh = bf (view blockHeight $ _parentHeader ph) - - v <- view psVersion - cid <- view chainId - - psInitCache .= case M.lookupLE pbh _psInitCache of - Nothing -> M.singleton pbh mc - Just (_,before) - | cleanModuleCache v cid pbh -> - M.insert pbh mc _psInitCache - | otherwise -> M.insert pbh (before <> mc) _psInitCache - --- | Pair parent header with transaction metadata. --- In cases where there is no transaction/Command, 'PublicMeta' --- default value is used. -data TxContext = TxContext - { _tcParentHeader :: !ParentHeader - , _tcPublicMeta :: !PublicMeta - , _tcMiner :: !Miner - } deriving Show - -instance HasChainId TxContext where - _chainId = _chainId . _tcParentHeader -instance HasChainwebVersion TxContext where - _chainwebVersion = _chainwebVersion . _tcParentHeader - --- | Convert context to datatype for Pact environment. --- --- TODO: this should be deprecated, since the `ctxBlockHeader` --- call fetches a grandparent, not the parent. --- -ctxToPublicData :: TxContext -> PublicData -ctxToPublicData ctx = PublicData - { _pdPublicMeta = _tcPublicMeta ctx - , _pdBlockHeight = bh - , _pdBlockTime = bt - , _pdPrevBlockHash = toText hsh - } - where - h = ctxBlockHeader ctx - BlockHeight bh = ctxCurrentBlockHeight ctx - BlockCreationTime (Time (TimeSpan (Micros !bt))) = view blockCreationTime h - BlockHash hsh = view blockParent h - --- | Convert context to datatype for Pact environment using the --- current blockheight, referencing the parent header (not grandparent!) --- hash and blocktime data --- -ctxToPublicData' :: TxContext -> PublicData -ctxToPublicData' ctx = PublicData - { _pdPublicMeta = _tcPublicMeta ctx - , _pdBlockHeight = bh - , _pdBlockTime = bt - , _pdPrevBlockHash = toText h - } - where - bheader = _parentHeader (_tcParentHeader ctx) - BlockHeight !bh = succ $ view blockHeight bheader - BlockCreationTime (Time (TimeSpan (Micros !bt))) = - view blockCreationTime bheader - BlockHash h = view blockHash bheader - --- | Retrieve parent header as 'BlockHeader' -ctxBlockHeader :: TxContext -> BlockHeader -ctxBlockHeader = _parentHeader . _tcParentHeader - --- | Get "current" block height, which means parent height + 1. --- This reflects Pact environment focus on current block height, --- which influenced legacy switch checks as well. -ctxCurrentBlockHeight :: TxContext -> BlockHeight -ctxCurrentBlockHeight = succ . view blockHeight . ctxBlockHeader - -ctxChainId :: TxContext -> ChainId -ctxChainId = view blockChainId . ctxBlockHeader - -ctxVersion :: TxContext -> ChainwebVersion -ctxVersion = view chainwebVersion . ctxBlockHeader - -guardCtx :: (ChainwebVersion -> ChainId -> BlockHeight -> a) -> TxContext -> a -guardCtx g txCtx = g (ctxVersion txCtx) (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) - --- | Assemble tx context from transaction metadata and parent header. -getTxContext :: Miner -> PublicMeta -> PactBlockM logger tbl TxContext -getTxContext miner pm = view psParentHeader >>= \ph -> return (TxContext ph pm miner) - --- | A sub-monad of PactServiceM, for actions taking place at a particular block. -newtype PactBlockM logger tbl a = PactBlockM - { _unPactBlockM :: - ReaderT (PactBlockEnv logger Pact4 tbl) (StateT PactServiceState IO) a - } deriving newtype - ( Functor, Applicative, Monad - , MonadReader (PactBlockEnv logger Pact4 tbl) - , MonadState PactServiceState - , MonadThrow, MonadCatch, MonadMask - , MonadIO - ) - --- | Lifts PactServiceM to PactBlockM by forgetting about the current block. --- It is unsafe to use `runPactBlockM` inside the argument to this function. -liftPactServiceM :: PactServiceM logger tbl a -> PactBlockM logger tbl a -liftPactServiceM (PactServiceM a) = PactBlockM $ ReaderT $ \e -> - StateT $ \s -> do - runStateT (runReaderT a (_psServiceEnv e)) s - --- | Look up an init cache that is stored at or before the height of the current parent header. -getInitCache :: PactBlockM logger tbl ModuleCache -getInitCache = do - ph <- views psParentHeader (view blockHeight . _parentHeader) - get >>= \PactServiceState{..} -> - case M.lookupLE ph _psInitCache of - Just (_,mc) -> return mc - Nothing -> return mempty - --- | A wrapper for 'updateInitCache' that uses the current block. -updateInitCacheM :: ModuleCache -> PactBlockM logger tbl () -updateInitCacheM mc = do - pc <- view psParentHeader - liftPactServiceM $ - updateInitCache mc pc - --- | Run 'PactBlockM' by providing the block context, in the form of --- a database snapshot at that block and information about the parent header. --- It is unsafe to use this function in an argument to `liftPactServiceM`. -runPactBlockM - :: ParentHeader -> Bool -> PactDbFor logger Pact4 - -> PactBlockM logger tbl a -> PactServiceM logger tbl a -runPactBlockM pctx isGenesis dbEnv (PactBlockM act) = PactServiceM $ ReaderT $ \e -> StateT $ \s -> do - let blockEnv = PactBlockEnv - { _psServiceEnv = e - , _psIsGenesis = isGenesis - , _psParentHeader = pctx - , _psBlockDbEnv = dbEnv - } - (a, s') <- runStateT - (runReaderT act blockEnv) - s - return (a, s') - -tracePactBlockM :: (Logger logger, ToJSON param) => Text -> param -> Int -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM label param weight a = tracePactBlockM' label (const param) (const weight) a - -tracePactBlockM' :: (Logger logger, ToJSON param) => Text -> (a -> param) -> (a -> Int) -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM' label calcParam calcWeight a = do - e <- ask - s <- get - (r, s') <- liftIO $ trace' (logJsonTrace_ (_psLogger $ _psServiceEnv e)) label (calcParam . fst) (calcWeight . fst) - $ runStateT (runReaderT (_unPactBlockM a) e) s - put s' - return r - -localLabelBlock :: (Logger logger) => (Text, Text) -> PactBlockM logger tbl x -> PactBlockM logger tbl x -localLabelBlock lbl x = do - locally (psServiceEnv . psLogger) (addLabel lbl) x - -data UnexpectedErrorPrinting = PrintsUnexpectedError | CensorsUnexpectedError - -catchesPactError :: (MonadCatch m, MonadIO m, Logger logger) => logger -> UnexpectedErrorPrinting -> m a -> m (Either PactError a) -catchesPactError logger exnPrinting action = catches (Right <$> action) - [ Handler $ \(e :: PactError) -> return $ Left e - , Handler $ \(e :: SomeException) -> do - !err <- case exnPrinting of - PrintsUnexpectedError -> - return (viaShow e) - CensorsUnexpectedError -> do - liftIO $ logWarn_ logger ("catchesPactError: unknown error: " <> sshow e) - return "unknown error" - return $ Left $ PactError EvalError noInfo [] err - ] - - -newtype GasId = GasId PactId deriving (Eq, Show) - --- | Whether to enforce coinbase failures, failing the block, --- or be backward-compatible and allow. --- Backward-compat fix is to enforce in new block, but ignore in validate. --- -newtype EnforceCoinbaseFailure = EnforceCoinbaseFailure Bool - --- | Always use precompiled templates in coinbase or use date rule. -newtype CoinbaseUsePrecompiled = CoinbaseUsePrecompiled Bool - --- | Modified table gas module with free module loads --- -freeModuleLoadGasModel :: GasModel -freeModuleLoadGasModel = modifiedGasModel - where - defGasModel = tableGasModel defaultGasConfig - fullRunFunction = runGasModel defGasModel - modifiedRunFunction name ga = case ga of - GPostRead ReadModule {} -> MilliGas 0 - _ -> fullRunFunction name ga - modifiedGasModel = defGasModel { runGasModel = modifiedRunFunction } - -chainweb213GasModel :: GasModel -chainweb213GasModel = modifiedGasModel - where - defGasModel = tableGasModel gasConfig - unknownOperationPenalty = 1000000 - multiRowOperation = 40000 - gasConfig = defaultGasConfig { _gasCostConfig_primTable = updTable } - updTable = M.union upd defaultGasTable - upd = M.fromList - [("keys", multiRowOperation) - ,("select", multiRowOperation) - ,("fold-db", multiRowOperation) - ] - fullRunFunction = runGasModel defGasModel - modifiedRunFunction name ga = case ga of - GPostRead ReadModule {} -> 0 - GUnreduced _ts -> case M.lookup name updTable of - Just g -> g - Nothing -> unknownOperationPenalty - _ -> milliGasToGas $ fullRunFunction name ga - modifiedGasModel = defGasModel { runGasModel = \t g -> gasToMilliGas (modifiedRunFunction t g) } - -chainweb224GasModel :: GasModel -chainweb224GasModel = chainweb213GasModel - { runGasModel = \name -> \case - GPostRead ReadInterface {} -> MilliGas 0 - ga -> runGasModel chainweb213GasModel name ga - } - -getGasModel :: TxContext -> GasModel -getGasModel ctx - | guardCtx chainweb213Pact ctx = chainweb213GasModel - | guardCtx chainweb224Pact ctx = chainweb224GasModel - | otherwise = freeModuleLoadGasModel diff --git a/src/Chainweb/Pact4/Validations.hs b/src/Chainweb/Pact4/Validations.hs deleted file mode 100644 index d279ec439e..0000000000 --- a/src/Chainweb/Pact4/Validations.hs +++ /dev/null @@ -1,285 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} - --- | --- Module: Chainweb.Pact4.Validations --- Copyright: Copyright © 2018,2019,2020,2021,2022 Kadena LLC. --- License: See LICENSE file --- Maintainers: Lars Kuhtz, Emily Pillmore, Stuart Popejoy, Greg Hale --- Stability: experimental --- --- Validation checks for transaction requests. --- These functions are meant to be shared between: --- - The codepath for adding transactions to the mempool --- - The codepath for letting users test their transaction via /local --- -module Chainweb.Pact4.Validations -( -- * Local metadata _validation - assertPreflightMetadata - -- * Validation checks -, assertParseChainId -, assertChainId -, assertGasPrice -, assertNetworkId -, assertSigSize -, assertTxSize -, IsWebAuthnPrefixLegal(..) -, assertValidateSigs -, AssertValidateSigsError(..) -, displayAssertValidateSigsError -, assertTxTimeRelativeToParent -, assertTxNotInFuture -, assertCommand -, AssertCommandError(..) -, displayAssertCommandError - -- * Defaults -, defaultMaxCommandUserSigListSize -, defaultMaxCoinDecimalPlaces -, defaultMaxTTL -, defaultLenientTimeSlop -) where - -import Control.Lens - -import Data.Decimal (decimalPlaces) -import Data.Bifunctor (first) -import Data.Maybe (isJust, catMaybes, fromMaybe) -import Data.Either (isRight) -import Data.List.NonEmpty (NonEmpty, nonEmpty) -import Data.Text (Text) -import qualified Data.Text as Text -import qualified Data.ByteString.Short as SBS -import Data.Word (Word8) - --- internal modules - -import Chainweb.BlockHeader -import Chainweb.BlockCreationTime (BlockCreationTime(..)) -import Chainweb.Pact.Types -import Chainweb.Pact.Utils (fromPactChainId) -import Chainweb.Time (Seconds(..), Time(..), secondsToTimeSpan, scaleTimeSpan, second, add) -import Chainweb.Pact4.Transaction -import Chainweb.Version -import Chainweb.Version.Guards (isWebAuthnPrefixLegal, validPPKSchemes) - -import qualified Pact.Types.Gas as P -import qualified Pact.Types.Hash as P -import qualified Pact.Types.ChainId as P -import qualified Pact.Types.Command as P -import qualified Pact.Types.ChainMeta as P -import qualified Pact.Types.KeySet as P -import qualified Pact.Parse as P -import Chainweb.Pact4.Types -import Chainweb.Utils (ebool_) - - --- | Check whether a local Api request has valid metadata --- -assertPreflightMetadata - :: P.Command (P.Payload P.PublicMeta c) - -> TxContext - -> Maybe LocalSignatureVerification - -> PactServiceM logger tbl (Either (NonEmpty Text) ()) -assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do - v <- view psVersion - cid <- view chainId - bgl <- view psBlockGasLimit - - let bh = ctxCurrentBlockHeight txCtx - let validSchemes = validPPKSchemes v cid bh - let webAuthnPrefixLegal = isWebAuthnPrefixLegal v cid bh - - let P.PublicMeta pcid _ gl gp _ _ = P._pMeta pay - nid = P._pNetworkId pay - signers = P._pSigners pay - - let errs = catMaybes - [ eUnless "Unparseable transaction chain id" $ assertParseChainId pcid - , eUnless "Chain id mismatch" $ assertChainId cid pcid - -- TODO - , eUnless "Transaction Gas limit exceeds block gas limit" $ assertBlockGasLimit bgl gl - , eUnless "Gas price decimal precision too high" $ assertGasPrice gp - , eUnless "Network id mismatch" $ assertNetworkId v nid - , eUnless "Signature list size too big" $ assertSigSize sigs - , eUnless "Invalid transaction signatures" $ sigValidate validSchemes webAuthnPrefixLegal signers - , eUnless "Tx time outside of valid range" $ assertTxTimeRelativeToParent pct cmd - ] - - pure $ case nonEmpty errs of - Nothing -> Right () - Just vs -> Left vs - where - sigValidate validSchemes webAuthnPrefixLegal signers - | Just NoVerify <- sigVerify = True - | otherwise = isRight $ assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs - - pct = ParentCreationTime - . view blockCreationTime - . _parentHeader - . _tcParentHeader - $ txCtx - - eUnless t assertion - | assertion = Nothing - | otherwise = Just t - --- | Check whether a particular Pact chain id is parseable --- -assertParseChainId :: P.ChainId -> Bool -assertParseChainId = isJust . fromPactChainId - --- | Check whether the chain id defined in the metadata of a Pact/Chainweb --- command payload matches a given chain id. --- --- The supplied chain id should be derived from the current --- chainweb node structure --- -assertChainId :: ChainId -> P.ChainId -> Bool -assertChainId cid0 cid1 = chainIdToText cid0 == P._chainId cid1 - --- | Check and assert that 'GasPrice' is rounded to at most 12 decimal --- places. --- -assertGasPrice :: P.GasPrice -> Bool -assertGasPrice (P.GasPrice (P.ParsedDecimal gp)) = decimalPlaces gp <= defaultMaxCoinDecimalPlaces - --- | Check and assert that the 'GasLimit' of a transaction is less than or eqaul to --- the block gas limit --- -assertBlockGasLimit :: P.GasLimit -> P.GasLimit -> Bool -assertBlockGasLimit bgl tgl = bgl >= tgl - --- | Check and assert that 'ChainwebVersion' is equal to some pact 'NetworkId'. --- -assertNetworkId :: ChainwebVersion -> Maybe P.NetworkId -> Bool -assertNetworkId _ Nothing = False -assertNetworkId v (Just (P.NetworkId nid)) = ChainwebVersionName nid == _versionName v - --- | Check and assert that the number of signatures in a 'Command' is --- at most 100. --- -assertSigSize :: [P.UserSig] -> Bool -assertSigSize sigs = length sigs <= defaultMaxCommandUserSigListSize - --- | Check and assert that the initial 'Gas' cost of a transaction --- is less than the specified 'GasLimit'. --- -assertTxSize :: P.Gas -> P.GasLimit -> Bool -assertTxSize initialGas gasLimit = initialGas < fromIntegral gasLimit - --- | Check and assert that signers and user signatures are valid for a given --- transaction hash. --- -assertValidateSigs :: () - => [P.PPKScheme] - -> IsWebAuthnPrefixLegal - -> P.PactHash - -> [P.Signer] - -> [P.UserSig] - -> Either AssertValidateSigsError () -assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs = do - let signersLength = length signers - let sigsLength = length sigs - ebool_ - SignersAndSignaturesLengthMismatch - { _signersLength = signersLength - , _signaturesLength = sigsLength - } - (signersLength == sigsLength) - - iforM_ (zip sigs signers) $ \pos (sig, signer) -> do - ebool_ - (InvalidSignerScheme pos) - (fromMaybe P.ED25519 (P._siScheme signer) `elem` validSchemes) - ebool_ - (InvalidSignerWebAuthnPrefix pos) - (webAuthnPrefixLegal == WebAuthnPrefixLegal || not (P.webAuthnPrefix `Text.isPrefixOf` P._siPubKey signer)) - case P.verifyUserSig hsh sig signer of - Left errMsg -> Left (InvalidUserSig pos (Text.pack errMsg)) - Right () -> Right () - --- prop_tx_ttl_newBlock/validateBlock --- --- Timing checks used to be based on the creation time of the validated --- block. That changed on mainnet at block height 449940. Tx creation time --- and TTL don't affect the tx outputs and pact state and can thus be --- skipped when replaying old blocks. --- -assertTxTimeRelativeToParent - :: ParentCreationTime - -> P.Command (P.Payload P.PublicMeta c) - -> Bool -assertTxTimeRelativeToParent (ParentCreationTime (BlockCreationTime txValidationTime)) tx = - ttl > 0 - && txValidationTime >= timeFromSeconds 0 - && txOriginationTime >= 0 - && timeFromSeconds (txOriginationTime + ttl) > txValidationTime - && P.TTLSeconds ttl <= defaultMaxTTL - where - P.TTLSeconds ttl = view cmdTimeToLive tx - timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral - P.TxCreationTime txOriginationTime = view cmdCreationTime tx - --- | Check that the tx's creation time is not too far in the future relative --- to the block creation time -assertTxNotInFuture - :: ParentCreationTime - -> P.Command (P.Payload P.PublicMeta c) - -> Bool -assertTxNotInFuture (ParentCreationTime (BlockCreationTime txValidationTime)) tx = - timeFromSeconds txOriginationTime <= lenientTxValidationTime - where - timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral - P.TxCreationTime txOriginationTime = view cmdCreationTime tx - lenientTxValidationTime = add (scaleTimeSpan defaultLenientTimeSlop second) txValidationTime - - --- | Assert that the command hash matches its payload and --- its signatures are valid, without parsing the payload. -assertCommand :: P.Command (PayloadWithText m c) -> [P.PPKScheme] -> IsWebAuthnPrefixLegal -> Either AssertCommandError () -assertCommand (P.Command pwt sigs hsh) ppkSchemePassList webAuthnPrefixLegal = do - if isRight assertHash - then first AssertValidateSigsError $ assertValidateSigs ppkSchemePassList webAuthnPrefixLegal hsh signers sigs - else Left InvalidPayloadHash - where - cmdBS = SBS.fromShort $ payloadBytes pwt - signers = P._pSigners (payloadObj pwt) - assertHash = P.verifyHash @'P.Blake2b_256 hsh cmdBS - --- -------------------------------------------------------------------- -- --- defaults - --- | The maximum admissible signature list size allowed for --- Pact/Chainweb transactions --- -defaultMaxCommandUserSigListSize :: Int -defaultMaxCommandUserSigListSize = 100 - --- | The maximum admissible number of decimal places allowed --- by the coin contract. --- -defaultMaxCoinDecimalPlaces :: Word8 -defaultMaxCoinDecimalPlaces = 12 - - --- | The maximum time-to-live (expressed in seconds) --- --- This is probably going to be changed. Let us make it 2 days for now. --- -defaultMaxTTL :: P.TTLSeconds -defaultMaxTTL = P.TTLSeconds $ P.ParsedInteger $ 2 * 24 * 60 * 60 - --- | Validation "slop" to allow for a more lenient creation time check after --- @useLegacyCreationTimeForTxValidation@ is no longer true. --- --- Without this, transactions showing up in the interim between --- parent block issuance and new block creation can get rejected; the tradeoff reduces --- the accuracy of the tx creation time vs "blockchain time", but is better than e.g. --- incurring artificial latency to wait for a parent block that is acceptable for a tx. --- 95 seconds represents the 99th percentile of block arrival times. --- -defaultLenientTimeSlop :: Seconds -defaultLenientTimeSlop = 95 diff --git a/src/Chainweb/Pact5/Transaction.hs b/src/Chainweb/Pact5/Transaction.hs deleted file mode 100644 index d9a8aa8714..0000000000 --- a/src/Chainweb/Pact5/Transaction.hs +++ /dev/null @@ -1,249 +0,0 @@ -{-# language DeriveAnyClass #-} -{-# language DeriveFunctor #-} -{-# language DeriveGeneric #-} -{-# language DerivingStrategies #-} -{-# language FlexibleContexts #-} -{-# language ImportQualifiedPost #-} -{-# language LambdaCase #-} -{-# language OverloadedStrings #-} -{-# language PackageImports #-} -{-# language ScopedTypeVariables #-} -{-# language TypeApplications #-} - -module Chainweb.Pact5.Transaction - ( Transaction - , PayloadWithText - , payloadBytes - , payloadObj - , payloadCodec - , parseCommand - , parsePact4Command - ) where - -import "aeson" Data.Aeson qualified as Aeson -import "base" Data.Coerce (coerce) -import "base" Data.Function -import "base" GHC.Generics (Generic) -import "bytestring" Data.ByteString.Char8 (ByteString) -import "bytestring" Data.ByteString.Short qualified as SB -import "deepseq" Control.DeepSeq -import "lens" Control.Lens -import "pact" Pact.Parse qualified as Pact4 -import "pact" Pact.Types.Capability qualified as Pact4 -import "pact" Pact.Types.ChainId qualified as Pact4 -import "pact" Pact.Types.ChainMeta qualified as Pact4 -import "pact" Pact.Types.Command qualified as Pact4 -import "pact" Pact.Types.Crypto qualified as Pact4 -import "pact" Pact.Types.Gas qualified as Pact4 -import "pact" Pact.Types.Hash qualified as Pact4 -import "pact" Pact.Types.PactValue qualified as Pact4 -import "pact" Pact.Types.RPC qualified as Pact4 -import "pact" Pact.Types.SPV qualified as Pact4 -import "pact" Pact.Types.Term.Internal (PactId(..)) -import "pact" Pact.Types.Verifier (VerifierName(..)) -import "pact" Pact.Types.Verifier qualified as Pact4 -import "pact-json" Pact.JSON.Encode qualified as J -import "pact-json" Pact.JSON.Encode (Encode(..)) -import "pact-json" Pact.JSON.Legacy.Value qualified as J -import "pact-tng" Pact.Core.Capabilities -import "pact-tng" Pact.Core.ChainData -import "pact-tng" Pact.Core.Command.Crypto -import "pact-tng" Pact.Core.Command.RPC -import "pact-tng" Pact.Core.Command.Types -import "pact-tng" Pact.Core.Gas.Types -import "pact-tng" Pact.Core.Hash -import "pact-tng" Pact.Core.Names -import "pact-tng" Pact.Core.PactValue -import "pact-tng" Pact.Core.SPV -import "pact-tng" Pact.Core.StableEncoding -import "pact-tng" Pact.Core.Verifiers -import "pact-tng" Pact.Core.Verifiers qualified as Pact5 -import "pact-tng" Pact.Core.Signer -import "pact-tng" Pact.Core.Errors -import "pact-tng" Pact.Core.Info -import "pact-tng" Pact.Core.Pretty qualified as Pact5 -import "text" Data.Text (Text) -import "text" Data.Text.Encoding (decodeUtf8, encodeUtf8) -import Chainweb.Pact.Conversion -import Chainweb.Pact4.Transaction qualified as Pact4 -import Chainweb.Utils - -type Transaction = Command (PayloadWithText PublicMeta ParsedCode) - -data PayloadWithText meta code = UnsafePayloadWithText - { _payloadBytes :: !SB.ShortByteString - , _payloadObj :: !(Payload meta code) - } - deriving stock (Show, Generic) - deriving stock (Functor) - deriving anyclass (NFData) - -instance Eq (PayloadWithText meta code) where - (==) = (==) `on` _payloadBytes - -instance (J.Encode meta, J.Encode code) => J.Encode (PayloadWithText meta code) where - build p = J.object - [ "payloadBytes" J..= J.encodeText (decodeUtf8 $ SB.fromShort $ _payloadBytes p) - , "payloadObject" J..= _payloadObj p - ] - -payloadBytes :: Getter (PayloadWithText meta code) SB.ShortByteString -payloadBytes = to _payloadBytes -{-# inline conlike payloadBytes #-} - -payloadObj :: Getter (PayloadWithText meta code) (Payload meta code) -payloadObj = to _payloadObj -{-# inline conlike payloadObj #-} - --- | A codec for Pact5's (Command PayloadWithText) transactions. --- -payloadCodec - :: Codec (Command (PayloadWithText PublicMeta ParsedCode)) -payloadCodec = Codec enc dec - where - enc c = J.encodeStrict $ fmap (decodeUtf8 . encodePayload) c - dec bs = case Aeson.decodeStrict' bs of - -- Note: this can only ever emit a `ParseError`, which by default are quite small. - -- Still, `pretty` instances are scary, but this cannot make it into block outputs so this should - -- be okay - Just (cmd :: Command Text) -> over _Left Pact5.renderCompactString $ parseCommand cmd - Nothing -> Left "decode PayloadWithText failed" - -parseCommand :: Command Text -> Either (PactError SpanInfo) (Command (PayloadWithText PublicMeta ParsedCode)) -parseCommand cmd = do - let cmd' = fmap encodeUtf8 cmd - let code = SB.toShort (_cmdPayload cmd') - parsedCmd <- over (_Right . cmdPayload . pMeta) _stableEncoding $ unsafeParseCommand cmd' - return (parsedCmd & cmdPayload %~ \obj -> UnsafePayloadWithText { _payloadBytes = code, _payloadObj = obj }) - -encodePayload :: PayloadWithText meta code -> ByteString -encodePayload = SB.fromShort . _payloadBytes - -parsePact4Command :: Pact4.UnparsedTransaction -> Either (PactError SpanInfo) Transaction -parsePact4Command cmd4 = do - let cmd = fromPact4Command cmd4 - payloadWithParsedCode :: Payload PublicMeta ParsedCode <- - (pPayload . _Exec . pmCode) parsePact (cmd ^. cmdPayload . payloadObj) - let payloadWithTextWithParsedCode = - UnsafePayloadWithText (cmd ^. cmdPayload . payloadBytes) payloadWithParsedCode - return $ cmd & cmdPayload .~ payloadWithTextWithParsedCode - -fromPact4Command :: Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text) -> Command (PayloadWithText PublicMeta Text) -fromPact4Command cmd4 = Command - { _cmdPayload = fromPact4PayloadWithText (Pact4._cmdPayload cmd4) - , _cmdSigs = map fromPact4UserSig (Pact4._cmdSigs cmd4) - , _cmdHash = fromPact4Hash (Pact4._cmdHash cmd4) - } - where - fromPact4PayloadWithText :: Pact4.PayloadWithText Pact4.PublicMeta Text -> PayloadWithText PublicMeta Text - fromPact4PayloadWithText payload4 = UnsafePayloadWithText - { _payloadBytes = Pact4.payloadBytes payload4 - , _payloadObj = fromPact4Payload (Pact4.payloadObj payload4) - } - - fromPact4Payload :: Pact4.Payload Pact4.PublicMeta Text -> Payload PublicMeta Text - fromPact4Payload payload4 = Payload - { _pPayload = fromPact4RPC (Pact4._pPayload payload4) - , _pNonce = Pact4._pNonce payload4 - , _pMeta = fromPact4PublicMeta (Pact4._pMeta payload4) - , _pSigners = map fromPact4Signer (Pact4._pSigners payload4) - , _pVerifiers = map fromPact4Verifier <$> Pact4._pVerifiers payload4 - , _pNetworkId = fromPact4NetworkId <$> Pact4._pNetworkId payload4 - } - - fromPact4RPC :: Pact4.PactRPC c -> PactRPC c - fromPact4RPC = \case - Pact4.Exec execMsg -> Exec $ ExecMsg - { _pmCode = Pact4._pmCode execMsg - , _pmData = legacyJsonToPactValue (Pact4._pmData execMsg) - } - Pact4.Continuation contMsg -> Continuation $ ContMsg - { _cmPactId = coerce Pact4._cmPactId contMsg - , _cmStep = Pact4._cmStep contMsg - , _cmRollback = Pact4._cmRollback contMsg - , _cmData = legacyJsonToPactValue (Pact4._cmData contMsg) - , _cmProof = ContProof . Pact4._contProof <$> Pact4._cmProof contMsg - } - - fromPact4PublicMeta :: Pact4.PublicMeta -> PublicMeta - fromPact4PublicMeta pm4 = PublicMeta - { _pmChainId = coerce (Pact4._pmChainId pm4) - , _pmSender = Pact4._pmSender pm4 - , _pmGasLimit = fromPact4GasLimit (Pact4._pmGasLimit pm4) - , _pmGasPrice = fromPact4GasPrice (Pact4._pmGasPrice pm4) - , _pmTTL = fromPact4TTLSeconds (Pact4._pmTTL pm4) - , _pmCreationTime = fromPact4TxCreationTime (Pact4._pmCreationTime pm4) - } - - fromPact4Signer :: Pact4.Signer -> Signer - fromPact4Signer signer4 = Signer - { _siScheme = Pact4._siScheme signer4 <&> \case { Pact4.ED25519 -> ED25519; Pact4.WebAuthn -> WebAuthn; } - , _siPubKey = Pact4._siPubKey signer4 - , _siAddress = Pact4._siAddress signer4 - , _siCapList = map fromPact4SigCapability (Pact4._siCapList signer4) - } - - fromPact4SigCapability :: Pact4.SigCapability -> SigCapability - fromPact4SigCapability cap4 = SigCapability $ CapToken - { _ctName = fromLegacyQualifiedName (Pact4._scName cap4) - , _ctArgs = fromPact4PactValue <$> Pact4._scArgs cap4 - } - - fromPact4Verifier :: Pact4.Verifier Pact4.ParsedVerifierProof -> Verifier Pact5.ParsedVerifierProof - fromPact4Verifier verifier4 = Verifier - { _verifierName = coerce (Pact4._verifierName verifier4) - , _verifierProof = Pact5.ParsedVerifierProof - $ fromPact4PactValue - $ case Pact4._verifierProof verifier4 of { Pact4.ParsedVerifierProof pv -> pv; } - , _verifierCaps = fromPact4SigCapability <$> Pact4._verifierCaps verifier4 - } - - fromPact4NetworkId :: Pact4.NetworkId -> NetworkId - fromPact4NetworkId = NetworkId . Pact4._networkId - - legacyJsonToPactValue :: J.LegacyValue -> PactValue - legacyJsonToPactValue lv = case eitherDecodeStable @PactValue (J.encodeStrict lv) of - Right pv -> pv - Left msg -> error $ "TODO: don't throw an error here, use Either: " <> sshow (J.encodeStrict lv, msg) - - fromPact4PactValue :: Pact4.PactValue -> PactValue - fromPact4PactValue pv4 = case fromLegacyPactValue pv4 of - Right pv -> pv - Left err -> error $ "TODO: don't throw an error here: " ++ show err - - fromPact4UserSig :: Pact4.UserSig -> UserSig - fromPact4UserSig = \case - Pact4.ED25519Sig txt -> ED25519Sig txt - Pact4.WebAuthnSig webAuthnSig4 -> WebAuthnSig $ WebAuthnSignature - { clientDataJSON = Pact4.clientDataJSON webAuthnSig4 - , authenticatorData = Pact4.authenticatorData webAuthnSig4 - , signature = Pact4.signature webAuthnSig4 - } - - fromPact4Hash :: Pact4.PactHash -> Hash - fromPact4Hash (Pact4.TypedHash sbs) = Hash sbs - - fromPact4ParsedInteger :: Pact4.ParsedInteger -> SatWord - fromPact4ParsedInteger (Pact4.ParsedInteger i) = fromIntegral i - - fromPact4GasLimit :: Pact4.GasLimit -> GasLimit - fromPact4GasLimit (Pact4.GasLimit i) = GasLimit (Gas (fromPact4ParsedInteger i)) - - fromPact4GasPrice :: Pact4.GasPrice -> GasPrice - fromPact4GasPrice (Pact4.GasPrice (Pact4.ParsedDecimal d)) = GasPrice d - - fromPact4TTLSeconds :: Pact4.TTLSeconds -> TTLSeconds - fromPact4TTLSeconds (Pact4.TTLSeconds (Pact4.ParsedInteger i)) = TTLSeconds i - - fromPact4TxCreationTime :: Pact4.TxCreationTime -> TxCreationTime - fromPact4TxCreationTime (Pact4.TxCreationTime (Pact4.ParsedInteger i)) = TxCreationTime i - --- decodePayload --- :: ByteString --- -> Either String PayloadWithText --- decodePayload bs = case Aeson.decodeStrict' bs of --- Just (payload :: Payload (StableEncoding PublicMeta) Text) -> do --- p <- traverse parseCode $ --- over pMeta _stableEncoding payload --- return $! PayloadWithText (SB.toShort bs) p --- Nothing -> Left "decoding Payload failed" diff --git a/src/Chainweb/Pact5/Types.hs b/src/Chainweb/Pact5/Types.hs deleted file mode 100644 index 785be552e8..0000000000 --- a/src/Chainweb/Pact5/Types.hs +++ /dev/null @@ -1,197 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE TemplateHaskell #-} - -module Chainweb.Pact5.Types - ( TxContext(..) - , guardCtx - , ctxCurrentBlockHeight - , GasSupply(..) - , PactBlockM(..) - , PactBlockState(..) - , pbBlockHandle - , pbServiceState - , runPactBlockM - , tracePactBlockM - , tracePactBlockM' - , liftPactServiceM - , pactTransaction - , localLabelBlock - -- * default values - , noInfo - , noPublicMeta - , noSpanInfo - , emptyCapState - ) - where - -import Chainweb.BlockHeader -import Chainweb.Miner.Pact (Miner) -import Chainweb.BlockHeight -import Chainweb.Version -import Chainweb.Pact.Types -import qualified Chainweb.ChainId -import Control.Lens -import Control.Exception.Safe -import Control.Monad.IO.Class -import Chainweb.Logger -import qualified Pact.Core.Evaluate as Pact5 -import Data.Decimal -import qualified Pact.JSON.Encode as J -import qualified Pact.Core.StableEncoding as Pact5 -import qualified Pact.Core.Literal as Pact5 -import Control.Monad.State.Strict -import Control.Monad.Reader -import Data.Aeson (ToJSON) -import Data.Text (Text) -import Utils.Logging.Trace -import Pact.Core.Persistence -import Chainweb.Pact5.Backend.ChainwebPactDb (Pact5Db(..)) -import qualified Pact.Core.Builtin as Pact5 -import Pact.Core.Command.Types (RequestKey) - -import qualified Pact.Core.Capabilities as Pact5 -import qualified Pact.Core.ChainData as Pact5 -import qualified Pact.Core.Info as Pact5 -import qualified Pact.Core.Gas.Types as Pact5 -import Chainweb.Pact.Backend.Types - --- | Pair parent header with transaction metadata. --- In cases where there is no transaction/Command, 'PublicMeta' --- default value is used. -data TxContext = TxContext - { _tcParentHeader :: !ParentHeader - , _tcMiner :: !Miner - } deriving Show - -instance HasChainId TxContext where - _chainId = Chainweb.ChainId._chainId . _tcParentHeader -instance HasChainwebVersion TxContext where - _chainwebVersion = _chainwebVersion . _tcParentHeader - --- | Retrieve parent header as 'BlockHeader' -ctxBlockHeader :: TxContext -> BlockHeader -ctxBlockHeader = _parentHeader . _tcParentHeader - --- | Get "current" block height, which means parent height + 1. --- This reflects Pact environment focus on current block height, --- which influenced legacy switch checks as well. -ctxCurrentBlockHeight :: TxContext -> BlockHeight -ctxCurrentBlockHeight = succ . view blockHeight . ctxBlockHeader - -ctxChainId :: TxContext -> Chainweb.ChainId.ChainId -ctxChainId = _chainId . ctxBlockHeader - -ctxVersion :: TxContext -> ChainwebVersion -ctxVersion = _chainwebVersion . ctxBlockHeader - -guardCtx :: (ChainwebVersion -> Chainweb.ChainId.ChainId -> BlockHeight -> a) -> TxContext -> a -guardCtx g txCtx = g (ctxVersion txCtx) (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) - -data PactBlockState = PactBlockState - { _pbServiceState :: !PactServiceState - , _pbBlockHandle :: !(BlockHandle Pact5) - } - -makeLenses ''PactBlockState - --- | A sub-monad of PactServiceM, for actions taking place at a particular block. -newtype PactBlockM logger tbl a = PactBlockM - { _unPactBlockM :: - ReaderT (PactBlockEnv logger Pact5 tbl) (StateT PactBlockState IO) a - } deriving newtype - ( Functor, Applicative, Monad - , MonadReader (PactBlockEnv logger Pact5 tbl) - , MonadState PactBlockState - , MonadThrow, MonadCatch, MonadMask - , MonadIO - ) - --- | Run 'PactBlockM' by providing the block context, in the form of --- a database snapshot at that block and information about the parent header. --- It is unsafe to use this function in an argument to `liftPactServiceM`. -runPactBlockM - :: ParentHeader -> Bool -> PactDbFor logger Pact5 -> BlockHandle Pact5 - -> PactBlockM logger tbl a -> PactServiceM logger tbl (a, BlockHandle Pact5) -runPactBlockM pctx isGenesis dbEnv startBlockHandle (PactBlockM act) = PactServiceM $ ReaderT $ \e -> StateT $ \s -> do - let blockEnv = PactBlockEnv - { _psServiceEnv = e - , _psParentHeader = pctx - , _psIsGenesis = isGenesis - , _psBlockDbEnv = dbEnv - } - (a, s') <- runStateT - (runReaderT act blockEnv) - (PactBlockState s startBlockHandle) - return ((a, _pbBlockHandle s'), _pbServiceState s') - -tracePactBlockM :: (Logger logger, ToJSON param) => Text -> param -> Int -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM label param weight a = tracePactBlockM' label (const param) (const weight) a - -tracePactBlockM' :: (Logger logger, ToJSON param) => Text -> (a -> param) -> (a -> Int) -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM' label calcParam calcWeight a = do - e <- ask - s <- get - (r, s') <- liftIO $ trace' (logJsonTrace_ (_psLogger $ _psServiceEnv e)) label (calcParam . fst) (calcWeight . fst) - $ runStateT (runReaderT (_unPactBlockM a) e) s - put s' - return r - --- | Lifts PactServiceM to PactBlockM by forgetting about the current block. --- It is unsafe to use `runPactBlockM` inside the argument to this function. -liftPactServiceM :: PactServiceM logger tbl a -> PactBlockM logger tbl a -liftPactServiceM (PactServiceM a) = - PactBlockM $ ReaderT $ \e -> StateT $ \s -> do - let sp = runReaderT a (_psServiceEnv e) - (r, s') <- runStateT sp (_pbServiceState s) - return (r, s { _pbServiceState = s' }) - -pactTransaction :: Maybe RequestKey -> (PactDb Pact5.CoreBuiltin Pact5.Info -> IO a) -> PactBlockM logger tbl a -pactTransaction rk k = do - e <- view psBlockDbEnv - h <- use pbBlockHandle - (r, h') <- liftIO $ doPact5DbTransaction e h rk k - pbBlockHandle .= h' - return r - --- | Indicates a computed gas charge (gas amount * gas price) -newtype GasSupply = GasSupply { _pact5GasSupply :: Decimal } - deriving (Eq,Ord) - deriving newtype (Num,Real,Fractional) - -instance J.Encode GasSupply where - build = J.build . Pact5.StableEncoding . Pact5.LDecimal . _pact5GasSupply -instance Show GasSupply where show (GasSupply g) = show g - -localLabelBlock :: (Logger logger) => (Text, Text) -> PactBlockM logger tbl x -> PactBlockM logger tbl x -localLabelBlock lbl x = do - locally (psServiceEnv . psLogger) (addLabel lbl) x - --- -------------------------------------------------------------------------- -- --- Default Values --- --- TODO: move to Pact5 - -noInfo :: Pact5.Info -noInfo = Pact5.LineInfo 0 - -emptyCapState :: Ord name => Ord v => Pact5.CapState name v -emptyCapState = Pact5.CapState mempty mempty mempty mempty mempty - -noSpanInfo :: Pact5.SpanInfo -noSpanInfo = Pact5.SpanInfo 0 0 0 0 - -noPublicMeta :: Pact5.PublicMeta -noPublicMeta = Pact5.PublicMeta - { Pact5._pmChainId = Pact5.ChainId "" - , Pact5._pmSender = "" - , Pact5._pmGasLimit = Pact5.GasLimit (Pact5.Gas 0) - , Pact5._pmGasPrice = Pact5.GasPrice 0 - , Pact5._pmTTL = Pact5.TTLSeconds 0 - , Pact5._pmCreationTime = Pact5.TxCreationTime 0 - } diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 95dd45fb60..1261a7b8e3 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -41,6 +41,7 @@ module Chainweb.PayloadProvider -- * Evaluation Context , EvaluationCtx(..) +, ConsensusPayload(..) , _evaluationCtxCurrentHeight , _evaluationCtxRankedPayloadHash , _evaluationCtxRankedParentHash @@ -279,8 +280,8 @@ instance ToJSON ConsensusState where -- Binary format: The concatenation of the binary serialization of the -- individual fields in the order as they appear in the data type definition. -- -data EvaluationCtx = EvaluationCtx - { _evaluationCtxParentCreationTime :: !BlockCreationTime +data EvaluationCtx p = EvaluationCtx + { _evaluationCtxParentCreationTime :: !(Parent BlockCreationTime) -- ^ Creation time of the parent block. If transactions in the block -- have a notion of "current" time, they should use this value. , _evaluationCtxParentHash :: !(Parent BlockHash) @@ -301,54 +302,53 @@ data EvaluationCtx = EvaluationCtx -- Internally, encoding and unit is provider specific. For Pact it is a -- Kda value for the EVM provider it is in Stu. Also the recipient and -- the mechanism how it is credited is provider specific. - , _evaluationCtxPayloadHash :: !BlockPayloadHash - -- ^ Payload hash of the block that is validated. This is used as a - -- checksum for the payload validation. For the last block of a ForkInfo - -- structure this value must match the respective value in the target - -- sync state. - -- - -- The BlockPayloadHash is first computed when the respective payload is - -- created for mining and before it is included in a block. - , _evaluationCtxPayloadData :: !(Maybe EncodedPayloadData) - -- ^ Optional external payload data. This may be - -- the complete, self contained block payload or it may just contain - -- complementary data that aids with the validation. - -- - -- The main purpose of this field is to allow consensus to gossip around - -- payload data along with new cuts in the P2P network, which allows for - -- more efficient synchronization and a reduction of block propagation - -- latencies. + , _evaluationCtxPayload :: !p + -- -- ^ Payload hash of the block that is validated. This is used as a + -- -- checksum for the payload validation. For the last block of a ForkInfo + -- -- structure this value must match the respective value in the target + -- -- sync state. + -- -- + -- -- The BlockPayloadHash is first computed when the respective payload is + -- -- created for mining and before it is included in a block. + -- , _evaluationCtxPayloadData :: !(Maybe EncodedPayloadData) + -- -- ^ Optional external payload data. This may be + -- -- the complete, self contained block payload or it may just contain + -- -- complementary data that aids with the validation. + -- -- + -- -- The main purpose of this field is to allow consensus to gossip around + -- -- payload data along with new cuts in the P2P network, which allows for + -- -- more efficient synchronization and a reduction of block propagation + -- -- latencies. } deriving (Show, Eq, Ord) -_evaluationCtxCurrentHeight :: EvaluationCtx -> BlockHeight +_evaluationCtxCurrentHeight :: EvaluationCtx p -> BlockHeight _evaluationCtxCurrentHeight = succ . unwrapParent . _evaluationCtxParentHeight _evaluationCtxRankedPayloadHash - :: EvaluationCtx + :: EvaluationCtx ConsensusPayload -> RankedBlockPayloadHash _evaluationCtxRankedPayloadHash ctx = RankedBlockPayloadHash (_evaluationCtxCurrentHeight ctx) - (_evaluationCtxPayloadHash ctx) + (_consensusPayloadHash $ _evaluationCtxPayload ctx) _evaluationCtxRankedParentHash - :: EvaluationCtx + :: EvaluationCtx p -> Parent RankedBlockHash _evaluationCtxRankedParentHash ctx = Parent $ RankedBlockHash (unwrapParent $ _evaluationCtxParentHeight ctx) (unwrapParent $ _evaluationCtxParentHash ctx) -evaluationCtxProperties :: forall e kv . KeyValue e kv => EvaluationCtx -> [kv] +evaluationCtxProperties :: forall e kv p . (KeyValue e kv, ToJSON p) => EvaluationCtx p -> [kv] evaluationCtxProperties a = [ "parentCreationTime" .= _evaluationCtxParentCreationTime a , "parentBlockHash" .= _evaluationCtxParentHash a , "parentHeight" .= _evaluationCtxParentHeight a , "minerReward" .= _evaluationCtxMinerReward a - , "payloadHash" .= _evaluationCtxPayloadHash a - , "payloadData" .= _evaluationCtxPayloadData a + , "payload" .= _evaluationCtxPayload a ] -instance ToJSON EvaluationCtx where +instance ToJSON p => ToJSON (EvaluationCtx p) where toEncoding = pairs . mconcat . evaluationCtxProperties toJSON = object . evaluationCtxProperties {-# INLINE toEncoding #-} @@ -383,16 +383,13 @@ data NewBlockCtx = NewBlockCtx -- blockHeaderToEvaluationCtx :: Parent BlockHeader - -> BlockPayloadHash - -> Maybe EncodedPayloadData - -> EvaluationCtx -blockHeaderToEvaluationCtx (Parent ph) pld pldData = EvaluationCtx - { _evaluationCtxParentCreationTime = view blockCreationTime ph + -> EvaluationCtx () +blockHeaderToEvaluationCtx (Parent ph) = EvaluationCtx + { _evaluationCtxParentCreationTime = Parent $ view blockCreationTime ph , _evaluationCtxParentHash = Parent $ view blockHash ph , _evaluationCtxParentHeight = parentHeight , _evaluationCtxMinerReward = blockMinerReward v height - , _evaluationCtxPayloadHash = pld - , _evaluationCtxPayloadData = pldData + , _evaluationCtxPayload = () } where parentHeight = Parent $ view blockHeight ph @@ -411,6 +408,25 @@ instance ToJSON NewBlockCtx where {-# INLINE toEncoding #-} {-# INLINE toJSON #-} +data ConsensusPayload = ConsensusPayload + { _consensusPayloadHash :: !BlockPayloadHash + , _consensusPayloadData :: !(Maybe EncodedPayloadData) + } + deriving (Show, Eq, Ord) + +consensusPayloadProperties :: forall e kv . KeyValue e kv => ConsensusPayload -> [kv] +consensusPayloadProperties a = + [ "hash" .= _consensusPayloadHash a + , "data" .= _consensusPayloadData a + ] + +instance ToJSON ConsensusPayload where + toEncoding = pairs . mconcat . consensusPayloadProperties + toJSON = object . consensusPayloadProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + + -- -------------------------------------------------------------------------- -- -- ForkInfo @@ -445,7 +461,7 @@ instance ToJSON NewBlockCtx where -- payload provider. -- data ForkInfo = ForkInfo - { _forkInfoTrace :: ![EvaluationCtx] + { _forkInfoTrace :: ![EvaluationCtx ConsensusPayload] -- ^ The payload evaluation contexts for a consecutive sequence of -- blocks. The first entry determines the fork point which must be -- known to the payload provider (although it is not necessary that the @@ -678,8 +694,8 @@ instance Hashable NewPayload where hashWithSalt s = hashWithSalt s . _newPayloadBlockPayloadHash {-# INLINE hashWithSalt #-} -_newPayloadRankedParentHash :: NewPayload -> RankedBlockHash -_newPayloadRankedParentHash np = RankedBlockHash +_newPayloadRankedParentHash :: NewPayload -> Parent RankedBlockHash +_newPayloadRankedParentHash np = Parent $ RankedBlockHash (_newPayloadParentHeight np) (_newPayloadParentHash np) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 8ff9b733fd..c5b0d7b736 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1208,14 +1208,14 @@ getPayloadForContext :: Logger logger => EvmPayloadProvider logger -> Maybe Hints - -> EvaluationCtx + -> EvaluationCtx ConsensusPayload -> IO Payload getPayloadForContext p h ctx = do - mapM_ insertPayloadData (_evaluationCtxPayloadData ctx) + mapM_ insertPayloadData (_consensusPayloadData $ _evaluationCtxPayload ctx) pld <- getPayload (_evmPayloadStore p) (_evmCandidatePayloads p) - (Priority $ negate $ int $ _evaluationCtxParentHeight ctx) + (Priority $ negate $ int $ unwrapParent $ _evaluationCtxParentHeight ctx) (_hintsOrigin <$> h) (_evaluationCtxRankedPayloadHash ctx) tableInsert (_evmCandidatePayloads p) rh pld @@ -1239,7 +1239,7 @@ getPayloadForContext p h ctx = do validatePayload :: EvmPayloadProvider logger -> Payload - -> EvaluationCtx + -> EvaluationCtx ConsensusPayload -> IO () validatePayload p pld ctx = return () diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index e3aca71c65..84988844fc 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -82,6 +82,7 @@ module Chainweb.PayloadProvider.Minimal , newMinimalPayloadProvider ) where +import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.BlockPayloadHash import Chainweb.Logger @@ -298,7 +299,7 @@ validatePayload . MonadThrow m => MinimalPayloadProvider -> Payload - -> EvaluationCtx + -> EvaluationCtx ConsensusPayload -> m () validatePayload p pld ctx = do checkEq PayloadInvalidChainwebVersion @@ -308,15 +309,15 @@ validatePayload p pld ctx = do (_chainId p) (_chainId pld) checkEq PayloadInvalidHeight - (_evaluationCtxParentHeight ctx + 1) + (_evaluationCtxCurrentHeight ctx) (view payloadBlockHeight pld) checkEq PayloadInvalidMinerReward (_evaluationCtxMinerReward ctx) (view payloadMinerReward pld) checkEq PayloadInvalidHash - (_evaluationCtxPayloadHash ctx) + (_consensusPayloadHash $ _evaluationCtxPayload ctx) (view payloadHash pld) - case _evaluationCtxPayloadData ctx of + case _consensusPayloadData $ _evaluationCtxPayload ctx of Nothing -> return () Just x -> checkEq PayloadInvalidPayloadData x (encodedPayloadData pld) where @@ -354,14 +355,14 @@ instance PayloadProvider MinimalPayloadProvider where getPayloadForContext :: MinimalPayloadProvider -> Maybe Hints - -> EvaluationCtx + -> EvaluationCtx ConsensusPayload -> IO Payload getPayloadForContext p h ctx = do - insertPayloadData (_evaluationCtxPayloadData ctx) + insertPayloadData (_consensusPayloadData $ _evaluationCtxPayload ctx) pld <- Rest.getPayload (_minimalPayloadStore p) (_minimalCandidatePayloads p) - (Priority $ negate $ int $ _evaluationCtxParentHeight ctx) + (Priority $ negate $ int $ unwrapParent $ _evaluationCtxParentHeight ctx) (_hintsOrigin <$> h) (_evaluationCtxRankedPayloadHash ctx) casInsert (_minimalCandidatePayloads p) pld @@ -521,4 +522,3 @@ pruneCandidates p s = deleteLt (_minimalCandidatePayloads p) lrh where lrh = latestRankedBlockPayloadHash s h = _rankedHeight lrh - diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 80ec2a672d..694b905696 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -37,7 +37,6 @@ data PactPayloadProvider logger = PactPayloadProvider -- ^ Maximum allowed execution time for the transactions validation. , _pactReorgLimit :: !RewindLimit -- ^ The limit of checkpointer's rewind in the `execValidationBlock` command. - , _pactOnFatalError :: !(forall a. PactException -> Text -> IO a) , _pactGasLogger :: !(Maybe logger) , _pactTxFailuresCounter :: !(Maybe (Counter "txFailures")) , _pactTxTimeLimit :: !(Maybe Micros) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index c0f23fadee..1d499d21dc 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -349,7 +349,7 @@ forkInfoForHeader wdb hdr pldData state <- consensusState wdb hdr return $ ForkInfo { _forkInfoTrace = [blockHeaderToEvaluationCtx phdr pld pldData] - , _forkInfoBasePayloadHash = view blockPayloadHash (_parentHeader phdr) + , _forkInfoBasePayloadHash = view blockPayloadHash (unwrapParent phdr) , _forkInfoTargetState = state , _forkInfoNewBlockCtx = Just nbctx } diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 98349f9b2c..eec867bf8f 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -21,6 +21,7 @@ {-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Version @@ -53,7 +54,6 @@ module Chainweb.Version , decodeChainwebVersionCode , ChainwebVersionName(..) , ChainwebVersion(..) - , pact4Upgrade , TxIdxInBlock(..) , _TxBlockIdx , VersionQuirks(..) @@ -84,15 +84,6 @@ module Chainweb.Version , genesisHeightAndGraph , PactUpgrade(..) - , PactVersion(..) - , PactVersionT(..) - , ForBothPactVersions(..) - , ForSomePactVersion(..) - , pattern ForPact4 - , _ForPact4 - , pattern ForPact5 - , _ForPact5 - , forAnyPactVersion -- * Typelevel ChainwebVersionName , ChainwebVersionT(..) @@ -189,8 +180,7 @@ import Chainweb.Graph import Chainweb.HostAddress import Chainweb.MerkleUniverse import Chainweb.Payload -import qualified Chainweb.Pact4.Transaction as Pact4 -import qualified Chainweb.Pact5.Transaction as Pact5 +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Utils import Chainweb.Utils.Rule import Chainweb.Utils.Serialization @@ -373,88 +363,27 @@ instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ChainwebVer toMerkleNode = encodeMerkleInputNode encodeChainwebVersionCode fromMerkleNode = decodeMerkleInputNode decodeChainwebVersionCode --- -------------------------------------------------------------------------- -- --- Pact Version - -data PactVersion = Pact4 | Pact5 - deriving stock (Eq, Show) -data PactVersionT (v :: PactVersion) where - Pact4T :: PactVersionT Pact4 - Pact5T :: PactVersionT Pact5 -deriving stock instance Eq (PactVersionT v) -deriving stock instance Show (PactVersionT v) -instance NFData (PactVersionT v) where - rnf Pact4T = () - rnf Pact5T = () - -data ForSomePactVersion f = forall pv. ForSomePactVersion (PactVersionT pv) (f pv) -forAnyPactVersion :: (forall pv. f pv -> a) -> ForSomePactVersion f -> a -forAnyPactVersion k (ForSomePactVersion _ f) = k f -instance (forall pv. Eq (f pv)) => Eq (ForSomePactVersion f) where - ForSomePactVersion Pact4T f == ForSomePactVersion Pact4T f' = f == f' - ForSomePactVersion Pact5T f == ForSomePactVersion Pact5T f' = f == f' - ForSomePactVersion _ _ == ForSomePactVersion _ _ = False -deriving stock instance (forall pv. Show (f pv)) => Show (ForSomePactVersion f) -instance (forall pv. NFData (f pv)) => NFData (ForSomePactVersion f) where - rnf (ForSomePactVersion pv f) = rnf pv `seq` rnf f -pattern ForPact4 :: f Pact4 -> ForSomePactVersion f -pattern ForPact4 x = ForSomePactVersion Pact4T x -_ForPact4 :: Prism' (ForSomePactVersion f) (f Pact4) -_ForPact4 = prism' ForPact4 $ \case - ForPact4 x -> Just x - _ -> Nothing -_ForPact5 :: Prism' (ForSomePactVersion f) (f Pact5) -_ForPact5 = prism' ForPact5 $ \case - ForPact5 x -> Just x - _ -> Nothing -pattern ForPact5 :: f Pact5 -> ForSomePactVersion f -pattern ForPact5 x = ForSomePactVersion Pact5T x -{-# COMPLETE ForPact4, ForPact5 #-} -data ForBothPactVersions f = ForBothPactVersions - { _forPact4 :: (f Pact4) - , _forPact5 :: (f Pact5) - } -deriving stock instance (Eq (f Pact4), Eq (f Pact5)) => Eq (ForBothPactVersions f) -deriving stock instance (Show (f Pact4), Show (f Pact5)) => Show (ForBothPactVersions f) -instance (NFData (f Pact4), NFData (f Pact5)) => NFData (ForBothPactVersions f) where - rnf b = rnf (_forPact4 b) `seq` rnf (_forPact5 b) - -- -------------------------------------------------------------------------- -- -- Pact Upgrade -- The type of upgrades, which are sets of transactions to run at certain block -- heights during coinbase. -data PactUpgrade where - Pact4Upgrade :: - { _pact4UpgradeTransactions :: [Pact4.Transaction] - , _legacyUpgradeIsPrecocious :: Bool - -- ^ when set to `True`, the upgrade transactions are executed using the - -- forks of the next block, rather than the block the upgrade - -- transactions are included in. do not use this for new upgrades - -- unless you are sure you need it, this mostly exists for old upgrades. - } -> PactUpgrade - Pact5Upgrade :: - { _pact5UpgradeTransactions :: [Pact5.Transaction] - } -> PactUpgrade +data PactUpgrade = + PactUpgrade + { _pactUpgradeTransactions :: [Pact.Transaction] + } instance Eq PactUpgrade where - Pact4Upgrade txs precocious == Pact4Upgrade txs' precocious' = - txs == txs' && precocious == precocious' - Pact5Upgrade txs == Pact5Upgrade txs' = + PactUpgrade txs == PactUpgrade txs' = txs == txs' _ == _ = False instance Show PactUpgrade where - show Pact4Upgrade {} = "" - show Pact5Upgrade {} = "" + show PactUpgrade {} = "" instance NFData PactUpgrade where - rnf (Pact4Upgrade txs precocious) = rnf txs `seq` rnf precocious - rnf (Pact5Upgrade txs) = rnf txs - -pact4Upgrade :: [Pact4.Transaction] -> PactUpgrade -pact4Upgrade txs = Pact4Upgrade txs False + rnf (PactUpgrade txs) = rnf txs -- -------------------------------------------------------------------------- -- -- Version Quirks diff --git a/src/Chainweb/Version/Guards.hs b/src/Chainweb/Version/Guards.hs index 0ba3839442..0b918f2e4f 100644 --- a/src/Chainweb/Version/Guards.hs +++ b/src/Chainweb/Version/Guards.hs @@ -52,10 +52,8 @@ module Chainweb.Version.Guards , chainweb229Pact , pact5 , pact44NewTrans - , pact4ParserVersion , maxBlockGasLimit , validPPKSchemes - , isWebAuthnPrefixLegal , validKeyFormats , pact5Serialiser @@ -77,7 +75,6 @@ import Pact.Core.Info qualified as Pact5 import Pact.Core.Serialise qualified as Pact5 import Pact.Types.KeySet (PublicKeyText, ed25519HexFormat, webAuthnFormat) import Pact.Types.Scheme (PPKScheme(ED25519, WebAuthn)) -import Chainweb.Pact4.Transaction qualified as Pact4 getForkHeight :: Fork -> ChainwebVersion -> ChainId -> ForkHeight getForkHeight fork v cid = v ^?! versionForks . at fork . _Just . atChain cid @@ -281,11 +278,6 @@ pact5Serialiser v cid bh | chainweb228Pact v cid bh = Pact5.serialisePact_lineinfo_pact51 | otherwise = Pact5.serialisePact_lineinfo_pact50 -pact4ParserVersion :: ChainwebVersion -> ChainId -> BlockHeight -> Pact4.PactParserVersion -pact4ParserVersion v cid bh - | chainweb213Pact v cid bh = Pact4.PactParserChainweb213 - | otherwise = Pact4.PactParserGenesis - maxBlockGasLimit :: ChainwebVersion -> BlockHeight -> Maybe Natural maxBlockGasLimit v bh = snd $ ruleZipperHere $ snd $ ruleSeek (\h _ -> bh >= h) (_versionMaxBlockGasLimit v) @@ -299,12 +291,6 @@ validPPKSchemes v cid bh = then [ED25519, WebAuthn] else [ED25519] -isWebAuthnPrefixLegal :: ChainwebVersion -> ChainId -> BlockHeight -> Pact4.IsWebAuthnPrefixLegal -isWebAuthnPrefixLegal v cid bh = - if chainweb222Pact v cid bh - then Pact4.WebAuthnPrefixLegal - else Pact4.WebAuthnPrefixIllegal - validKeyFormats :: ChainwebVersion -> ChainId -> BlockHeight -> [PublicKeyText -> Bool] validKeyFormats v cid bh = if chainweb222Pact v cid bh diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index d7289ab8ef..d2f87959f8 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -25,22 +25,6 @@ import P2P.BootstrapNodes import Pact.Types.Runtime (Gas(..)) import Pact.Types.Verifier -import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 -import qualified Chainweb.Pact.Transactions.CoinV4Transactions as CoinV4 -import qualified Chainweb.Pact.Transactions.CoinV5Transactions as CoinV5 -import qualified Chainweb.Pact.Transactions.CoinV6Transactions as CoinV6 -import qualified Chainweb.Pact.Transactions.Mainnet0Transactions as MN0 -import qualified Chainweb.Pact.Transactions.Mainnet1Transactions as MN1 -import qualified Chainweb.Pact.Transactions.Mainnet2Transactions as MN2 -import qualified Chainweb.Pact.Transactions.Mainnet3Transactions as MN3 -import qualified Chainweb.Pact.Transactions.Mainnet4Transactions as MN4 -import qualified Chainweb.Pact.Transactions.Mainnet5Transactions as MN5 -import qualified Chainweb.Pact.Transactions.Mainnet6Transactions as MN6 -import qualified Chainweb.Pact.Transactions.Mainnet7Transactions as MN7 -import qualified Chainweb.Pact.Transactions.Mainnet8Transactions as MN8 -import qualified Chainweb.Pact.Transactions.Mainnet9Transactions as MN9 -import qualified Chainweb.Pact.Transactions.MainnetKADTransactions as MNKAD - -- | Initial hash target for mainnet 20-chain transition. Difficulty on the new -- chains is 1/4 of the current difficulty. It is based on the following header -- from 2020-07-09. This value should be double checked after the testnet04 @@ -183,26 +167,7 @@ mainnet = ChainwebVersion , ( unsafeChainId 19, unsafeFromText "i-MN4AoxsaPds4M_MzwNSUygAkGnPZoCDvahfckowt4") ] } - , _versionUpgrades = chainZip HM.union - (indexByForkHeights mainnet - [ (CoinV2, onChains - [ (unsafeChainId 0, pact4Upgrade MN0.transactions) - , (unsafeChainId 1, pact4Upgrade MN1.transactions) - , (unsafeChainId 2, pact4Upgrade MN2.transactions) - , (unsafeChainId 3, pact4Upgrade MN3.transactions) - , (unsafeChainId 4, pact4Upgrade MN4.transactions) - , (unsafeChainId 5, pact4Upgrade MN5.transactions) - , (unsafeChainId 6, pact4Upgrade MN6.transactions) - , (unsafeChainId 7, pact4Upgrade MN7.transactions) - , (unsafeChainId 8, pact4Upgrade MN8.transactions) - , (unsafeChainId 9, pact4Upgrade MN9.transactions) - ]) - , (Pact4Coin3, AllChains $ Pact4Upgrade CoinV3.transactions True) - , (Chainweb214Pact, AllChains $ Pact4Upgrade CoinV4.transactions True) - , (Chainweb215Pact, AllChains $ Pact4Upgrade CoinV5.transactions True) - , (Chainweb223Pact, AllChains $ pact4Upgrade CoinV6.transactions) - ]) - (onChains [(unsafeChainId 0, HM.singleton to20ChainsMainnet (pact4Upgrade MNKAD.transactions))]) + , _versionUpgrades = onChains [] , _versionCheats = VersionCheats { _disablePow = False , _fakeFirstEpochStart = False diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index 22a6add81f..f5530199b3 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -7,7 +7,6 @@ module Chainweb.Version.RecapDevelopment(recapDevnet, pattern RecapDevelopment) where -import qualified Data.HashMap.Strict as HM import qualified Data.Set as Set import Chainweb.BlockCreationTime @@ -22,12 +21,6 @@ import Chainweb.Version import Pact.Types.Verifier -import qualified Chainweb.Pact.Transactions.RecapDevelopmentTransactions as RecapDevnet -import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 -import qualified Chainweb.Pact.Transactions.CoinV4Transactions as CoinV4 -import qualified Chainweb.Pact.Transactions.CoinV5Transactions as CoinV5 -import qualified Chainweb.Pact.Transactions.MainnetKADTransactions as MNKAD - to20ChainsHeight :: BlockHeight to20ChainsHeight = 60 @@ -41,50 +34,42 @@ recapDevnet = ChainwebVersion , _versionName = ChainwebVersionName "recap-development" , _versionForks = tabulateHashMap $ \case - SlowEpoch -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - Vuln797Fix -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - CoinV2 -> onChains $ [(unsafeChainId 0, ForkAtBlockHeight $ BlockHeight 3)] <> [(unsafeChainId i, ForkAtBlockHeight $ BlockHeight 4) | i <- [1..19]] - PactBackCompat_v16 -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - SkipTxTimingValidation -> AllChains $ ForkAtBlockHeight $ BlockHeight 2 - OldTargetGuard -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - SkipFeatureFlagValidation -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - ModuleNameFix -> AllChains $ ForkAtBlockHeight $ BlockHeight 2 - ModuleNameFix2 -> AllChains $ ForkAtBlockHeight $ BlockHeight 2 - OldDAGuard -> AllChains $ ForkAtBlockHeight $ BlockHeight 13 - PactEvents -> AllChains $ ForkAtBlockHeight $ BlockHeight 40 - SPVBridge -> AllChains $ ForkAtBlockHeight $ BlockHeight 50 - Pact4Coin3 -> AllChains $ ForkAtBlockHeight $ BlockHeight 80 - EnforceKeysetFormats -> AllChains $ ForkAtBlockHeight $ BlockHeight 100 - Pact42 -> AllChains $ ForkAtBlockHeight $ BlockHeight 90 - CheckTxHash -> AllChains $ ForkAtBlockHeight $ BlockHeight 110 - Chainweb213Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 95 - Chainweb214Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 115 - Chainweb215Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 165 - Pact44NewTrans -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - Chainweb216Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 215 - Chainweb217Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 470 - Chainweb218Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 500 - Chainweb219Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 550 - Chainweb220Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 560 - Chainweb221Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 580 - Chainweb222Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 590 - Chainweb223Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 600 - Chainweb224Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 610 - Chainweb225Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 620 - Chainweb226Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 630 - Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 640 - Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 650 - Chainweb229Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 660 + SlowEpoch -> AllChains ForkAtGenesis + Vuln797Fix -> AllChains ForkAtGenesis + CoinV2 -> AllChains ForkAtGenesis + PactBackCompat_v16 -> AllChains ForkAtGenesis + SkipTxTimingValidation -> AllChains ForkAtGenesis + OldTargetGuard -> AllChains ForkAtGenesis + SkipFeatureFlagValidation -> AllChains ForkAtGenesis + ModuleNameFix -> AllChains ForkAtGenesis + ModuleNameFix2 -> AllChains ForkAtGenesis + OldDAGuard -> AllChains ForkAtGenesis + PactEvents -> AllChains ForkAtGenesis + SPVBridge -> AllChains ForkAtGenesis + Pact4Coin3 -> AllChains ForkAtGenesis + EnforceKeysetFormats -> AllChains ForkAtGenesis + Pact42 -> AllChains ForkAtGenesis + CheckTxHash -> AllChains ForkAtGenesis + Chainweb213Pact -> AllChains ForkAtGenesis + Chainweb214Pact -> AllChains ForkAtGenesis + Chainweb215Pact -> AllChains ForkAtGenesis + Pact44NewTrans -> AllChains ForkAtGenesis + Chainweb216Pact -> AllChains ForkAtGenesis + Chainweb217Pact -> AllChains ForkAtGenesis + Chainweb218Pact -> AllChains ForkAtGenesis + Chainweb219Pact -> AllChains ForkAtGenesis + Chainweb220Pact -> AllChains ForkAtGenesis + Chainweb221Pact -> AllChains ForkAtGenesis + Chainweb222Pact -> AllChains ForkAtGenesis + Chainweb223Pact -> AllChains ForkAtGenesis + Chainweb224Pact -> AllChains ForkAtGenesis + Chainweb225Pact -> AllChains ForkAtGenesis + Chainweb226Pact -> AllChains ForkAtGenesis + Pact5Fork -> AllChains $ ForkAtGenesis + Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 10 + Chainweb229Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 - , _versionUpgrades = foldr (chainZip HM.union) (AllChains mempty) - [ indexByForkHeights recapDevnet - [ (CoinV2, onChains [(unsafeChainId i, pact4Upgrade RecapDevnet.transactions) | i <- [0..9]]) - , (Pact4Coin3, AllChains (Pact4Upgrade CoinV3.transactions True)) - , (Chainweb214Pact, AllChains (Pact4Upgrade CoinV4.transactions True)) - , (Chainweb215Pact, AllChains (Pact4Upgrade CoinV5.transactions True)) - ] - , onChains [(unsafeChainId 0, HM.singleton to20ChainsHeight (pact4Upgrade MNKAD.transactions))] - ] + , _versionUpgrades = onChains [] , _versionGraphs = (to20ChainsHeight, twentyChainGraph) `Above` diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index 5b6b83baf7..d8c0daa742 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -109,11 +109,7 @@ validateVersion v = do unless (null errors) $ error $ unlines $ ["errors encountered validating version", show v] <> errors where - -- TODO: this is an annoying type sig, can we use NoMonoLocalBinds and disable the warning - -- about matching on GADTs? - isUpgradeEmpty :: PactUpgrade -> Bool - isUpgradeEmpty Pact4Upgrade{_pact4UpgradeTransactions = upg} = null upg - isUpgradeEmpty Pact5Upgrade{_pact5UpgradeTransactions = upg} = null upg + isUpgradeEmpty PactUpgrade{_pactUpgradeTransactions = upg} = null upg -- | Look up a version in the registry by code. lookupVersionByCode :: HasCallStack => ChainwebVersionCode -> ChainwebVersion diff --git a/src/Chainweb/Version/Testnet04.hs b/src/Chainweb/Version/Testnet04.hs index b82c79673d..66ca055abb 100644 --- a/src/Chainweb/Version/Testnet04.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -25,22 +25,6 @@ import P2P.BootstrapNodes import Pact.Types.Runtime (Gas(..)) import Pact.Types.Verifier -import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 -import qualified Chainweb.Pact.Transactions.CoinV4Transactions as CoinV4 -import qualified Chainweb.Pact.Transactions.CoinV5Transactions as CoinV5 -import qualified Chainweb.Pact.Transactions.CoinV6Transactions as CoinV6 -import qualified Chainweb.Pact.Transactions.Mainnet0Transactions as MN0 -import qualified Chainweb.Pact.Transactions.Mainnet1Transactions as MN1 -import qualified Chainweb.Pact.Transactions.Mainnet2Transactions as MN2 -import qualified Chainweb.Pact.Transactions.Mainnet3Transactions as MN3 -import qualified Chainweb.Pact.Transactions.Mainnet4Transactions as MN4 -import qualified Chainweb.Pact.Transactions.Mainnet5Transactions as MN5 -import qualified Chainweb.Pact.Transactions.Mainnet6Transactions as MN6 -import qualified Chainweb.Pact.Transactions.Mainnet7Transactions as MN7 -import qualified Chainweb.Pact.Transactions.Mainnet8Transactions as MN8 -import qualified Chainweb.Pact.Transactions.Mainnet9Transactions as MN9 -import qualified Chainweb.Pact.Transactions.MainnetKADTransactions as MNKAD - -- | Initial hash target for testnet04 20-chain transition. Based on the following -- header from recap devnet running with 5 GPUs hash power. Using this target unchanged -- means, that we should do to the transition with the hash power of about @@ -172,26 +156,7 @@ testnet04 = ChainwebVersion , (unsafeChainId 19, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") ] } - , _versionUpgrades = chainZip HM.union - (indexByForkHeights testnet04 - [ (CoinV2, onChains $ - [ (unsafeChainId 0, pact4Upgrade MN0.transactions) - , (unsafeChainId 1, pact4Upgrade MN1.transactions) - , (unsafeChainId 2, pact4Upgrade MN2.transactions) - , (unsafeChainId 3, pact4Upgrade MN3.transactions) - , (unsafeChainId 4, pact4Upgrade MN4.transactions) - , (unsafeChainId 5, pact4Upgrade MN5.transactions) - , (unsafeChainId 6, pact4Upgrade MN6.transactions) - , (unsafeChainId 7, pact4Upgrade MN7.transactions) - , (unsafeChainId 8, pact4Upgrade MN8.transactions) - , (unsafeChainId 9, pact4Upgrade MN9.transactions) - ]) - , (Pact4Coin3, AllChains (Pact4Upgrade CoinV3.transactions True)) - , (Chainweb214Pact, AllChains (Pact4Upgrade CoinV4.transactions True)) - , (Chainweb215Pact, AllChains (Pact4Upgrade CoinV5.transactions True)) - , (Chainweb223Pact, AllChains (pact4Upgrade CoinV6.transactions)) - ]) - (onChains [(unsafeChainId 0, HM.singleton to20ChainsTestnet (pact4Upgrade MNKAD.transactions))]) + , _versionUpgrades = onChains [] , _versionCheats = VersionCheats { _disablePow = False , _fakeFirstEpochStart = False diff --git a/src/Chainweb/WebPactExecutionService.hs b/src/Chainweb/WebPactExecutionService.hs index 64017900d7..f2b9f7962d 100644 --- a/src/Chainweb/WebPactExecutionService.hs +++ b/src/Chainweb/WebPactExecutionService.hs @@ -39,7 +39,7 @@ import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Pact.Utils import Chainweb.Payload -import qualified Chainweb.Pact4.Transaction as Pact4 +import qualified Chainweb.Pact.Transaction as Pact4 import Chainweb.Utils import qualified Pact.Core.Persistence as Pact5 @@ -75,7 +75,7 @@ import qualified Data.ByteString as B -- data NewBlock = NewBlockInProgress !(ForSomePactVersion BlockInProgress) - | NewBlockPayload !ParentHeader !PayloadWithOutputs + | NewBlockPayload !(Parent BlockHeader) !PayloadWithOutputs deriving Show newBlockToPayloadWithOutputs :: NewBlock -> PayloadWithOutputs @@ -86,7 +86,7 @@ newBlockToPayloadWithOutputs (NewBlockPayload _ pwo) newBlockParent :: NewBlock -> (BlockHash, BlockHeight, BlockCreationTime) newBlockParent (NewBlockInProgress (ForSomePactVersion _ bip)) = blockInProgressParent bip -newBlockParent (NewBlockPayload (ParentHeader ph) _) = +newBlockParent (NewBlockPayload (Parent ph) _) = (view blockHash ph, view blockHeight ph, view blockCreationTime ph) instance HasChainwebVersion NewBlock where @@ -135,7 +135,7 @@ data PactExecutionService = PactExecutionService , _pactNewBlock :: !( ChainId -> NewBlockFill -> - ParentHeader -> + Parent BlockHeader -> IO (Historical NewPayload) ) , _pactContinueBlock :: !( @@ -203,7 +203,7 @@ _webPactNewBlock :: WebPactExecutionService -> ChainId -> NewBlockFill - -> ParentHeader + -> Parent BlockHeader -> IO (Historical NewPayload) _webPactNewBlock = _pactNewBlock . _webPactExecutionService {-# INLINE _webPactNewBlock #-} diff --git a/test/lib/Chainweb/Test/Pact4/Utils.hs b/test/lib/Chainweb/Test/Pact4/Utils.hs index 3ba7d3d710..bf51e08b4a 100644 --- a/test/lib/Chainweb/Test/Pact4/Utils.hs +++ b/test/lib/Chainweb/Test/Pact4/Utils.hs @@ -849,7 +849,7 @@ runCut v bdb pact genTime noncer miner = n <- noncer cid -- skip this chain if mining fails and retry with the next chain. - whenM (addTestBlockDb bdb (succ $ view blockHeight $ _parentHeader ph) n genTime cid pout) $ do + whenM (addTestBlockDb bdb (succ $ view blockHeight $ unwrapParent ph) n genTime cid pout) $ do h <- getParentTestBlockDb bdb cid void $ _webPactValidateBlock pact h (CheckablePayloadWithOutputs pout) diff --git a/test/lib/Chainweb/Test/Utils/BlockHeader.hs b/test/lib/Chainweb/Test/Utils/BlockHeader.hs index 785ed0c50e..d9fcfc550b 100644 --- a/test/lib/Chainweb/Test/Utils/BlockHeader.hs +++ b/test/lib/Chainweb/Test/Utils/BlockHeader.hs @@ -66,8 +66,8 @@ testPayload n = newPayloadWithOutputs -- Payloads that are created with this function match respective payloads -- that are created with 'testBlockPayload'. -- -testBlockPayloadFromParent :: ParentHeader -> PayloadWithOutputs -testBlockPayloadFromParent (ParentHeader b) = testPayload $ B8.intercalate "," +testBlockPayloadFromParent :: Parent BlockHeader -> PayloadWithOutputs +testBlockPayloadFromParent (Parent b) = testPayload $ B8.intercalate "," [ sshow (_chainwebVersion b) , sshow (view blockHeight b + 1) ] @@ -91,8 +91,8 @@ testBlockPayload b = testPayload $ B8.intercalate "," -- that are created with 'testBlockPayload_', assuming that the same nonce is -- used. -- -testBlockPayloadFromParent_ :: Nonce -> ParentHeader -> PayloadWithOutputs -testBlockPayloadFromParent_ n (ParentHeader b) = testPayload $ B8.intercalate "," +testBlockPayloadFromParent_ :: Nonce -> Parent BlockHeader -> PayloadWithOutputs +testBlockPayloadFromParent_ n (Parent b) = testPayload $ B8.intercalate "," [ sshow (_chainwebVersion b) , sshow (view blockHeight b + 1) , sshow n @@ -120,23 +120,23 @@ testGetNewAdjacentParentHeaders => ChainwebVersion -> (ChainValue BlockHash -> m BlockHeader) -> BlockHashRecord - -> m (HM.HashMap ChainId (Either BlockHash ParentHeader)) + -> m (HM.HashMap ChainId (Either BlockHash (Parent BlockHeader))) testGetNewAdjacentParentHeaders v hdb = itraverse select . _getBlockHashRecord where select cid h | h == genesisParentBlockHash v cid = pure $ Left h - | otherwise = Right . ParentHeader <$> hdb (ChainValue cid h) + | otherwise = Right . Parent <$> hdb (ChainValue cid h) testBlockHeader - :: HM.HashMap ChainId ParentHeader + :: HM.HashMap ChainId (Parent BlockHeader) -- ^ Adjacent parent hashes -> Nonce -- ^ Randomness to affect the block hash. It is also included into -- the payload - -> ParentHeader + -> Parent BlockHeader -- ^ parent block header -> BlockHeader -testBlockHeader adj nonce p@(ParentHeader b) = +testBlockHeader adj nonce p@(Parent b) = newBlockHeader adj payload nonce (BlockCreationTime $ add second t) p where payload = _payloadWithOutputsPayloadHash $ testBlockPayloadFromParent_ nonce p @@ -147,17 +147,17 @@ testBlockHeader adj nonce p@(ParentHeader b) = -- -- Should only be used for testing purposes. -- -testBlockHeaders :: ParentHeader -> [BlockHeader] -testBlockHeaders (ParentHeader p) = L.unfoldr (Just . (id &&& id) . f) p +testBlockHeaders :: Parent BlockHeader -> [BlockHeader] +testBlockHeaders (Parent p) = L.unfoldr (Just . (id &&& id) . f) p where - f b = testBlockHeader mempty (view blockNonce b) $ ParentHeader b + f b = testBlockHeader mempty (view blockNonce b) $ Parent b -- | Given a `BlockHeader` of some initial parent, generate an infinite stream -- of `BlockHeader`s which form a legal chain. -- -- Should only be used for testing purposes. -- -testBlockHeadersWithNonce :: Nonce -> ParentHeader -> [BlockHeader] -testBlockHeadersWithNonce n (ParentHeader p) = L.unfoldr (Just . (id &&& id) . f) p +testBlockHeadersWithNonce :: Nonce -> Parent BlockHeader -> [BlockHeader] +testBlockHeadersWithNonce n (Parent p) = L.unfoldr (Just . (id &&& id) . f) p where - f b = testBlockHeader mempty n $ ParentHeader b + f b = testBlockHeader mempty n $ Parent b diff --git a/test/lib/Chainweb/Test/Utils/TestHeader.hs b/test/lib/Chainweb/Test/Utils/TestHeader.hs index 7eb9a858d9..4d6e8d703f 100644 --- a/test/lib/Chainweb/Test/Utils/TestHeader.hs +++ b/test/lib/Chainweb/Test/Utils/TestHeader.hs @@ -90,7 +90,7 @@ testHeaderLookup :: TestHeader -> BlockHash -> Maybe BlockHeader testHeaderLookup testHdr x = lookup x tbl where h = _testHeaderHdr testHdr - p = _parentHeader $ _testHeaderParent testHdr + p = unwrapParent $ _testHeaderParent testHdr a = _testHeaderAdjs testHdr tbl = (view blockHash h, h) @@ -106,8 +106,8 @@ instance FromJSON TestHeader where instance ToJSON TestHeader where toJSON o = object [ "header" .= _testHeaderHdr o - , "parent" .= _parentHeader (_testHeaderParent o) - , "adjacents" .= fmap _parentHeader (_testHeaderAdjs o) + , "parent" .= unwrapParent (_testHeaderParent o) + , "adjacents" .= fmap unwrapParent (_testHeaderAdjs o) ] -- | An unsafe convenience functions for hard coding test headers in the code @@ -150,7 +150,7 @@ arbitraryTestHeaderHeight v cid h = do nonce <- arbitrary payloadHash <- arbitrary let pt = maximum $ _bct . view blockCreationTime - <$> HM.insert cid (_parentHeader parent) as + <$> HM.insert cid (unwrapParent parent) as t <- BlockCreationTime <$> chooseEnum (pt, maxBound) return $ TestHeader { _testHeaderHdr = newBlockHeader (ParentHeader <$> as) payloadHash nonce t parent diff --git a/test/unit/Chainweb/Test/BlockHeader/Validation.hs b/test/unit/Chainweb/Test/BlockHeader/Validation.hs index 031da3da83..6ae7228fdf 100644 --- a/test/unit/Chainweb/Test/BlockHeader/Validation.hs +++ b/test/unit/Chainweb/Test/BlockHeader/Validation.hs @@ -213,7 +213,7 @@ validationFailures = , ( hdr & testHeaderHdr . blockHash .~ nullBlockHash , [IncorrectHash] ) - , ( hdr & testHeaderHdr . blockCreationTime .~ (view blockCreationTime . _parentHeader $ _testHeaderParent hdr) + , ( hdr & testHeaderHdr . blockCreationTime .~ (view blockCreationTime . unwrapParent $ _testHeaderParent hdr) , [IncorrectHash, IncorrectPow, CreatedBeforeParent] ) , ( hdr & testHeaderHdr . blockHash %~ messWords encodeBlockHash decodeBlockHash (flip complementBit 0) diff --git a/test/unit/Chainweb/Test/Pact4/Checkpointer.hs b/test/unit/Chainweb/Test/Pact4/Checkpointer.hs deleted file mode 100644 index 29aae3758e..0000000000 --- a/test/unit/Chainweb/Test/Pact4/Checkpointer.hs +++ /dev/null @@ -1,872 +0,0 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE DataKinds #-} - -module Chainweb.Test.Pact4.Checkpointer (tests) where - -import Control.Concurrent.MVar -import Control.DeepSeq -import Control.Exception -import Control.Lens hiding ((.=)) -import Control.Monad (when, void) -import Control.Monad.Reader - -import Data.Aeson (Value(..), object, (.=), Key) -import Data.Function -import qualified Data.Map.Strict as M -import Data.Text (Text) -import qualified Data.Text as T - -import qualified Streaming.Prelude as Stream - -import Pact.Gas -import Pact.Interpreter (EvalResult(..), PactDbEnv(..), defaultInterpreter) -import Pact.JSON.Legacy.Value -import Pact.Native (nativeDefs) -import Pact.Repl -import Pact.Repl.Types -import Pact.Types.Command -import qualified Pact.Types.Hash as H -import Pact.Types.PactValue -import Pact.Types.RowData -import Pact.Types.Runtime hiding (ChainId) -import Pact.Types.SPV (noSPVSupport) -import Pact.Types.SQLite -import qualified Pact.JSON.Encode as J -import qualified Pact.Utils.StableHashMap as SHM - -import Test.Tasty -import Test.Tasty.HUnit - --- internal imports -import Chainweb.BlockHash -import Chainweb.BlockHeader.Internal -import Chainweb.BlockHeight -import Chainweb.Logger -import Chainweb.MerkleLogHash (merkleLogHash) -import Chainweb.MerkleUniverse -import Chainweb.Pact4.Backend.ChainwebPactDb - -import Chainweb.Pact.Backend.Utils -import Chainweb.Pact4.TransactionExec -import Chainweb.Pact.Types -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Utils -import Chainweb.Version - -import Chainweb.Test.Orphans.Internal ({- Arbitrary BlockHash -}) -import Chainweb.Pact.Backend.Types -import qualified Chainweb.Pact.PactService.Checkpointer.Internal as Checkpointer -import qualified Chainweb.Pact5.Backend.ChainwebPactDb as Pact5 - --- -------------------------------------------------------------------------- -- --- Tests - -tests :: TestTree -tests = testGroup "Checkpointer" - [ testRelational - , testKeyset - , testModuleName - , testCaseSteps "PactDb Regression" testRegress - , testCaseSteps "readRow unitTest" readRowUnitTest - ] - --- -------------------------------------------------------------------------- -- --- Module Name Test - -testModuleName :: TestTree -testModuleName = withResourceT withTempSQLiteResource $ \s -> - runSQLite' f s - where - f = \resIO -> testCase "testModuleName" $ do - - (cp, sql) <- resIO - - -- init genesis - let - hash00 = getArbitrary 0 - pc00 = childOf Nothing hash00 - cpRestoreAndSave cp Nothing - [(pc00, \_ -> return ())] - - let - hash01 = getArbitrary 1 - pc01 = childOf (Just pc00) hash01 - cpRestoreAndSave cp (Just pc00) - [(pc01, \_ -> return ()) - ] - let - hash02 = getArbitrary 2 - pc02 = childOf (Just pc01) hash02 - cpRestoreAndSave cp (Just pc01) - [(pc02, \(PactDbEnv pactdb mvar) -> do - -- block 2: write module records - (_,_,mod') <- loadModule - -- write qualified - _writeRow pactdb Insert Modules "nsname.qualmod" mod' mvar - -- write unqualified - _writeRow pactdb Insert Modules "baremod" mod' mvar - )] - - r1 <- qry_ sql "SELECT rowkey FROM [SYS:Modules] WHERE rowkey LIKE '%qual%'" [RText] - assertEqual "correct namespaced module name" [[SText "nsname.qualmod"]] r1 - - r2 <- qry_ sql "SELECT rowkey FROM [SYS:Modules] WHERE rowkey LIKE '%bare%'" [RText] - assertEqual "correct bare module name" [[SText "baremod"]] r2 - --- -------------------------------------------------------------------------- -- --- Key Set Test - -testKeyset :: TestTree -testKeyset = withResource initializeSQLite freeSQLiteResource $ \s -> runSQLite keysetTest s - -keysetTest :: Logger logger => IO (Checkpointer logger) -> TestTree -keysetTest c = testCaseSteps "Keyset test" $ \next -> do - cp <- c - let - hash00 = nullBlockHash - pc00 = childOf Nothing hash00 - - - next "init" - cpRestoreAndSave cp Nothing [(pc00, \_ -> pure ())] - - next "next block (blockheight 1, version 0)" - cpReadFrom cp (Just pc00) $ \dbEnv -> - addKeyset dbEnv "k2" (mkKeySet [] ">=") - - - next "fork on blockheight = 1" - hash11 <- BlockHash <$> liftIO (merkleLogHash @_ @ChainwebMerkleHashAlgorithm "0000000000000000000000000000001b") - let pc11 = childOf (Just pc00) hash11 - cpRestoreAndSave cp (Just pc00) [(pc11, \dbEnv -> - addKeyset dbEnv "k1" (mkKeySet [] ">=") - )] - --- -------------------------------------------------------------------------- -- --- CheckPointer Test - -testRelational :: TestTree -testRelational = - withRelationalCheckpointerResource $ - checkpointerTest "Relational Checkpointer" True - -checkpointerTest :: (Logger logger) => String -> Bool -> IO (Checkpointer logger) -> TestTree -checkpointerTest name relational cenvIO = testCaseSteps name $ \next -> do - cenv <- cenvIO - let - cp = cenv - readFrom = cpReadFrom cp - restoreAndSave = cpRestoreAndSave cp - ------------------------------------------------------------------ - -- s01 : new block workflow (restore -> discard), genesis - ------------------------------------------------------------------ - - runTwice next $ do - next "Step 1 : new block workflow (restore -> discard), genesis" - readFrom Nothing $ \dbEnv -> do - void $ runExec cenv dbEnv (Just $ ksData "1") $ defModule "1" - runExec cenv dbEnv Nothing "(m1.readTbl)" >>= - \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1]] - - - ----------------------------------------------------------- - -- s02 : validate block workflow (restore -> save), genesis - ----------------------------------------------------------- - - let hash00 = nullBlockHash - next "Step 2 : validate block workflow (restore -> save), genesis" - let pc00 = childOf Nothing hash00 - restoreAndSave Nothing - [(pc00, \dbEnv -> do - void $ runExec cenv dbEnv (Just $ ksData "1") $ defModule "1" - void $ runExec cenv dbEnv Nothing "(m1.readTbl)" - >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1]] - )] - - ------------------------------------------------------------------ - -- s03 : new block 00 - ------------------------------------------------------------------ - - next "Step 3 : new block 00" - let pactCheckStep = preview (_Just . peStep) . _erExec - let pactId = "DldRwCblQ7Loqy6wYJnaodHl30d3j3eH-qtFzfEv46g" - void $ readFrom (Just pc00) $ \dbEnv -> do - -- start a pact - -- test is that exec comes back with proper step - void $ runExec cenv dbEnv Nothing "(m1.insertTbl 'b 2)" - runExec cenv dbEnv Nothing "(m1.readTbl)" >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1,2]] - runExec cenv dbEnv Nothing "(m1.dopact 'pactA)" >>= ((Just 0 @=?) . pactCheckStep) - - ------------------------------------------------------------------ - -- s04: validate block 1 - ------------------------------------------------------------------ - - next "Step 4: validate block 1" - hash01 <- BlockHash <$> liftIO (merkleLogHash "0000000000000000000000000000001a") - let pc01 = childOf (Just pc00) hash01 - restoreAndSave (Just pc00) - [(pc01, \dbEnv -> do - void $ runExec cenv dbEnv Nothing "(m1.insertTbl 'b 2)" - runExec cenv dbEnv Nothing "(m1.readTbl)" - >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1,2]] - runExec cenv dbEnv Nothing "(m1.dopact 'pactA)" - >>= ((Just 0 @=?) . pactCheckStep) - )] - - ------------------------------------------------------------------ - -- s05: validate block 02 - -- create m2 module, exercise RefStore checkpoint - -- exec next part of pact - ------------------------------------------------------------------ - - let msg = "Step 5: validate block 02\n create m2 module, exercise RefStore checkpoint\n exec next part of pact" - next msg - hash02 <- BlockHash <$> merkleLogHash "0000000000000000000000000000002a" - let pc02 = childOf (Just pc01) hash02 - restoreAndSave (Just pc01) - [(pc02, \dbEnv -> do - void $ runExec cenv dbEnv (Just $ ksData "2") $ defModule "2" - runExec cenv dbEnv Nothing "(m2.readTbl)" - >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1]] - runCont cenv dbEnv pactId 1 - >>= ((Just 1 @=?) . pactCheckStep) - )] - - ------------------------------------------------------------------ - -- s06 : new block 03 - ------------------------------------------------------------------ - - next "Step 6 : new block 03" - readFrom (Just pc02) $ \dbEnv -> do - void $ runExec cenv dbEnv Nothing "(m2.insertTbl 'b 2)" - runExec cenv dbEnv Nothing "(m2.readTbl)" - >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1,2]] - - ------------------------------------------------------------------ - -- s07 : validate block 03 - ------------------------------------------------------------------ - - next "Step 7 : validate block 03" - hash03 <- BlockHash <$> merkleLogHash "0000000000000000000000000000003a" - let pc03 = childOf (Just pc02) hash03 - restoreAndSave (Just pc02) - [(pc03, \dbEnv -> do - void $ runExec cenv dbEnv Nothing "(m2.insertTbl 'b 2)" - runExec cenv dbEnv Nothing "(m2.readTbl)" - >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1,2]] - )] - -- insert here would fail if new block 03 had not been discarded - - ------------------------------------------------------------------ - -- s08: FORK! block 02, new hash - -- recreate m2 module, exercise RefStore checkpoint - -- exec next part of pact - ------------------------------------------------------------------ - - next "Step 8: FORK! block 02, new hash\n recreate m2 module, exercise RefStore checkpoint\n exec next part of pact" - hash02Fork <- BlockHash <$> merkleLogHash "0000000000000000000000000000002b" - let pc02Fork = childOf (Just pc01) hash02Fork - restoreAndSave (Just pc01) - [(pc02Fork, \dbEnv -> do - void $ runExec cenv dbEnv (Just $ ksData "2") $ defModule "2" - runExec cenv dbEnv Nothing "(m2.readTbl)" >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1]] - -- this would fail if not a fork - runCont cenv dbEnv pactId 1 >>= ((Just 1 @=?) . pactCheckStep) - )] - - next "step 9: test update row: new block 03" - readFrom (Just pc02Fork) $ \dbEnv -> do - void $ runExec cenv dbEnv Nothing "(m1.updateTbl 'b 3)" - runExec cenv dbEnv Nothing "(m1.readTbl)" >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1,3]] - -- updating key previously written at blockheight 1 (the "restore point") - - next "step 10: test update row: validate block 03" - hash13 <- BlockHash <$> merkleLogHash "0000000000000000000000000000003b" - let pc13 = childOf (Just pc02Fork) hash13 - restoreAndSave (Just pc02Fork) - [(pc13, \dbEnv -> do - void $ runExec cenv dbEnv Nothing "(m1.updateTbl 'b 3)" - runExec cenv dbEnv Nothing "(m1.readTbl)" >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1,3]] - )] - -- updating key previously written at blockheight 1 (the "restore point") - - next "mini-regression test for dropping user tables (part 1) empty block" - hash04 <- BlockHash <$> merkleLogHash "0000000000000000000000000000004a" - let pc04 = childOf (Just pc13) hash04 - restoreAndSave (Just pc13) - [(pc04, \_ -> return ())] - - next "mini-regression test for dropping user tables (part 2) (create module with offending table)" - hash05 <- BlockHash <$> merkleLogHash "0000000000000000000000000000005a" - let pc05 = childOf (Just pc04) hash05 - restoreAndSave (Just pc04) - [(pc05, \dbEnv -> do - void $ runExec cenv dbEnv (Just $ ksData "5") $ defModule "5" - runExec cenv dbEnv Nothing "(m5.readTbl)" >>= - \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1]] - )] - - next "mini-regression test for dropping user tables (part 3) (reload the offending table)" - hash05Fork <- BlockHash <$> merkleLogHash "0000000000000000000000000000005b" - let pc05Fork = childOf (Just pc04) hash05Fork - restoreAndSave (Just pc04) - [(pc05Fork, \dbEnv -> do - void $ runExec cenv dbEnv (Just $ ksData "5") $ defModule "5" - runExec cenv dbEnv Nothing "(m5.readTbl)" >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1]] - )] - - next "2nd mini-regression test for debugging updates (part 1) empty block" - hash14 <- BlockHash <$> merkleLogHash "0000000000000000000000000000004b" - let pc14 = childOf (Just pc13) hash14 - restoreAndSave (Just pc13) - [(pc14, \_ -> return ())] - - next "2nd mini-regression test for debugging updates (part 2) create module & table" - hash15 <- BlockHash <$> merkleLogHash "0000000000000000000000000000005c" - let pc15 = childOf (Just pc14) hash15 - restoreAndSave (Just pc14) - [(pc15, \dbEnv -> do - void $ runExec cenv dbEnv (Just $ ksData "6") $ defModule "6" - runExec cenv dbEnv Nothing "(m6.readTbl)" >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1]] - )] - - next "2nd mini-regression test for debugging updates (part 3) step 1 of insert value then update twice" - hash06 <- BlockHash <$> merkleLogHash "0000000000000000000000000000006a" - let pc06 = childOf (Just pc15) hash06 - restoreAndSave (Just pc15) - [(pc06, \dbEnv -> do - void $ runExec cenv dbEnv Nothing "(m6.insertTbl 'b 2)" - )] - - next "2nd mini-regression test for debugging updates (part 4) step 2 of insert value then update twice" - hash07 <- BlockHash <$> merkleLogHash "0000000000000000000000000000007a" - let pc07 = childOf (Just pc06) hash07 - restoreAndSave (Just pc06) - [(pc07, \dbEnv -> do - void $ runExec cenv dbEnv Nothing "(m6.weirdUpdateTbl 'b 4)" - )] - - next "2nd mini-regression test for debugging updates (part 5) step 3 of insert value then update twice" - hash08 <- BlockHash <$> merkleLogHash "0000000000000000000000000000008a" - let pc08 = childOf (Just pc07) hash08 - restoreAndSave (Just pc07) - [(pc08, \dbEnv -> do - runExec cenv dbEnv Nothing "(m6.readTbl)" >>= \EvalResult{..} -> Right _erOutput @?= traverse toPactValue [tIntList [1,4]] - )] - -- FOR DEBUGGING/INSPECTING VALUES AT SPECIFIC KEYS - -- void $ runExec cenv blockEnv09 Nothing "(let ((written (at 'col (read m6.tbl 'a [\"col\"])))) (enforce (= written 1) \"key a\"))" - -- void $ runExec cenv blockEnv09 Nothing "(let ((written (at 'col (read m6.tbl 'b [\"col\"])))) (enforce (= written 4) \"key b\"))" - -- FOR DEBUGGING/INSPECTING VALUES AT SPECIFIC KEYS - - next "Create the free namespace for the test" - hash09 <- BlockHash <$> merkleLogHash "0000000000000000000000000000009a" - let pc09 = childOf (Just pc08) hash09 - restoreAndSave (Just pc08) - [(pc09, \dbEnv -> do - void $ runExec cenv dbEnv Nothing defFree - )] - hash10 <- BlockHash <$> merkleLogHash "0000000000000000000000000000010a" - - next "Don't create the same table twice in the same block" - let tKeyset = object ["test-keyset" .= object ["keys" .= ([] :: [Text]), "pred" .= String ">="]] - readFrom (Just pc09) $ \dbEnv -> do - void $ runExec cenv dbEnv (Just tKeyset) tablecode - expectException "The table duplication somehow went through. Investigate this error." $ - runExec cenv dbEnv (Just tKeyset) tablecode - - - next "Don't create the same table twice in the same transaction." - - readFrom (Just pc09) $ \dbEnv -> - expectException "The table duplication somehow went through. Investigate this error." $ - runExec cenv dbEnv (Just tKeyset) (tablecode <> tablecode) - - - next "Don't create the same table twice over blocks." - - let pc10 = childOf (Just pc09) hash10 - restoreAndSave (Just pc09) - [(pc10, \dbEnv -> do - void $ runExec cenv dbEnv (Just tKeyset) tablecode - ) - ] - - - hash11 <- BlockHash <$> merkleLogHash "0000000000000000000000000000011a" - let pc11 = childOf (Just pc10) hash11 - restoreAndSave (Just pc10) - [(pc11, \dbEnv -> do - expectException "The table duplication somehow went through. Investigate this error." $ - runExec cenv dbEnv (Just tKeyset) tablecode - )] - - - next "Purposefully restore to an illegal checkpoint." - - let pc10Invalid = pc10 & blockHeight .~ 13 - void $ expectException "Illegal checkpoint successfully restored to" $ - readFrom (Just pc10Invalid) $ \_ -> return () - - when relational $ do - - next "Rewind to block 5" - - next "Run block 5b with pact 4.2.0 changes" - - readFrom (Just pc14) $ \dbEnv -> do - - void $ runExec cenv dbEnv (Just $ ksData "7") (defModule "7") - void $ runExec cenv dbEnv Nothing "(m7.insertTbl 'b 2)" - void $ runExec cenv dbEnv Nothing "(m7.insertTbl 'd 3)" - void $ runExec cenv dbEnv Nothing "(m7.insertTbl 'c 4)" - void $ runExec cenv dbEnv Nothing "(keys m7.tbl)" >>= \EvalResult{..} -> - Right _erOutput @?= traverse toPactValue [tStringList $ T.words "a b c d"] - - next "Rollback to block 4 and expect failure with pact 4.2.0 changes" - - readFrom (Just pc13) $ \dbEnv -> do - void $ runExec cenv dbEnv (Just $ ksData "7") (defModule "7") - void $ runExec cenv dbEnv Nothing "(m7.insertTbl 'b 2)" - void $ runExec cenv dbEnv Nothing "(m7.insertTbl 'd 3)" - void $ runExec cenv dbEnv Nothing "(m7.insertTbl 'c 4)" - runExec cenv dbEnv Nothing "(keys m7.tbl)" >>= \EvalResult{..} -> - assertBool "order should be different" - (Right _erOutput /= traverse toPactValue [tStringList $ T.words "a b c d"]) - - where - - h :: SomeException -> IO (Maybe String) - h = const (return Nothing) - - expectException msg act = do - result <- (act >> return (Just msg)) `catchAllSynchronous` h - maybe (return ()) (`assertBool` False) result - - ksData :: Key -> Value - ksData idx = object - [ ("k" <> idx) .= object - [ "keys" .= ([] :: [Text]) - , "pred" .= String ">=" - ] - ] - --- -------------------------------------------------------------------------- -- --- Read Row Unit Test - -readRowUnitTest :: (String -> IO ()) -> Assertion -readRowUnitTest logBackend = simpleBlockEnvInit logger runUnitTest - where - logger = hunitDummyLogger logBackend - writeRow' pactdb writeType conn i = - _writeRow pactdb writeType (UserTables "user1") "key1" - (RowData RDV1 (ObjectMap $ M.fromList [("f", (RDLiteral (LInteger i)))])) conn - runUnitTest pactdb e schemaInit = do - conn <- newMVar e - void $ schemaInit conn - let user1 = "user1" - usert = UserTables user1 - void $ begin pactdb conn - _createUserTable pactdb user1 "someModule" conn - writeRow' pactdb Insert conn 1 - void $ commit pactdb conn - let numWrites = 100 :: Int - loop current = - if current == numWrites - then return () - else do - void $ begin pactdb conn - writeRow' pactdb Update conn $ - fromIntegral $ current + 1 - void $ commit pactdb conn - loop (current + 1) - loop 1 - r <- _readRow pactdb usert "key1" conn - case r of - Nothing -> assertFailure "Unsuccessful write" - Just (RowData _ (ObjectMap m)) -> case M.lookup "f" m of - Just l -> assertEquals "Unsuccesful write at field key \"f\"" l (RDLiteral (LInteger 100)) - Nothing -> assertFailure "Field not found" - --- -------------------------------------------------------------------------- -- --- Test Regress - -testRegress :: (String -> IO ()) -> Assertion -testRegress logBackend = - regressChainwebPactDb logger - >>= fmap (toTup . _benvBlockState) . readMVar - >>= assertEquals "The final block state is" finalBlockState - where - logger = hunitDummyLogger logBackend - finalBlockState = 2 - toTup BlockState { _bsTxId = txid } = txid - -regressChainwebPactDb :: (Logger logger) => logger -> IO (MVar (BlockEnv logger)) -regressChainwebPactDb logger = simpleBlockEnvInit logger runRegression - -{- this should be moved to pact -} -runRegression - :: PactDb e -- your pactdb instance - -> e -- ambient environment - -> (MVar e -> IO ()) -- schema "creator" - -> IO (MVar e) -- the final state of the environment -runRegression pactdb e schemaInit = do - conn <- newMVar e - schemaInit conn - Just t1 <- begin pactdb conn - let user1 = "user1" - usert = UserTables user1 - toPV :: ToTerm a => a -> RowDataValue - toPV = pactValueToRowData . toPactValueLenient . toTerm' - _createUserTable pactdb user1 "someModule" conn - assertEquals' "output of commit2" - [ encodeTxLog $ TxLog "SYS:usertables" "user1" $ - J.object - [ "utModule" J..= J.object - [ "namespace" J..= J.null - , "name" J..= J.text "someModule" - ] - ] - ] - (commit pactdb conn) - - void $ begin pactdb conn - let row = RowData RDV1 $ ObjectMap $ M.fromList [("gah", RDLiteral (LDecimal 123.454345))] - _writeRow pactdb Insert usert "key1" row conn - assertEquals' "usert insert" (Just row) (_readRow pactdb usert "key1" conn) - let row' = RowData RDV1 $ ObjectMap $ M.fromList [("gah",toPV False),("fh",toPV (1 :: Int))] - _writeRow pactdb Update usert "key1" row' conn - assertEquals' "user update" (Just row') (_readRow pactdb usert "key1" conn) - let ks = mkKeySet [PublicKeyText "skdjhfskj"] "predfun" - _writeRow pactdb Write KeySets "ks1" ks conn - assertEquals' "keyset write" (Just ks) $ _readRow pactdb KeySets "ks1" conn - (modName,modRef,mod') <- loadModule - _writeRow pactdb Write Modules modName mod' conn - assertEquals' "module write" (Just mod') $ _readRow pactdb Modules modName conn - assertEquals "module native repopulation" (Right modRef) $ - traverse (traverse (fromPersistDirect nativeLookup)) mod' - assertEquals' "result of commit 3" - [ encodeTxLog TxLog - { _txDomain = "SYS:KeySets" - , _txKey = "ks1" - , _txValue = ks - } - , encodeTxLog TxLog - { _txDomain = "SYS:Modules" - , _txKey = asString modName - , _txValue = mod' - } - , encodeTxLog TxLog - { _txDomain = "user1" - , _txKey = "key1" - , _txValue = row - } - , encodeTxLog TxLog - { _txDomain = "user1" - , _txKey = "key1" - , _txValue = row' - } - ] - (commit pactdb conn) - void $ begin pactdb conn - tids <- _txids pactdb user1 t1 conn - assertEquals "user txids" [1] tids - -- assertEquals' "user txlogs" - -- [TxLog "user1" "key1" row, - -- TxLog "user1" "key1" row'] $ - -- _getTxLog chainwebpactdb usert (head tids) conn - assertEquals' "user txlogs" [TxLog "user1" "key1" (RowData RDV1 (ObjectMap $ on M.union (_objectMap . _rdData) row' row))] $ - _getTxLog pactdb usert (head tids) conn - _writeRow pactdb Insert usert "key2" row conn - assertEquals' "user insert key2 pre-rollback" (Just row) (_readRow pactdb usert "key2" conn) - assertEquals' "keys pre-rollback" ["key1","key2"] $ _keys pactdb (UserTables user1) conn - _rollbackTx pactdb conn - assertEquals' "rollback erases key2" Nothing $ _readRow pactdb usert "key2" conn - assertEquals' "keys" ["key1"] $ _keys pactdb (UserTables user1) conn - return conn - --- -------------------------------------------------------------------------- -- --- Chainweb Settings - -testVer :: ChainwebVersion -testVer = slowForkingCpmTestVersion peterson - -testChainId :: ChainId -testChainId = unsafeChainId 0 - --- -------------------------------------------------------------------------- -- --- Testing Utils - -assertEquals' :: (Eq a, Show a, NFData a) => String -> a -> IO a -> IO () -assertEquals' msg a b = assertEquals msg a =<< b - -assertEquals :: (Eq a,Show a,NFData a) => String -> a -> a -> IO () -assertEquals msg a b - | [a,b] `deepseq` a == b = return () - | otherwise = throwFail - $ "FAILURE: " ++ msg - ++ ": expected \n " ++ show a ++ "\n got \n " ++ show b - -throwFail :: String -> IO a -throwFail = throwIO . userError - --- -------------------------------------------------------------------------- -- --- Checkpointer Utils - -withRelationalCheckpointerResource - :: (Logger logger, logger ~ GenericLogger) - => (IO (Checkpointer logger) -> TestTree) - -> TestTree -withRelationalCheckpointerResource f = - withResource initializeSQLite freeSQLiteResource $ \s -> runSQLite f s - -addKeyset :: PactDbEnv (BlockEnv logger) -> KeySetName -> KeySet -> IO () -addKeyset (PactDbEnv pactdb mvar) keysetname keyset = - _writeRow pactdb Insert KeySets keysetname keyset mvar - -runTwice :: MonadIO m => (String -> IO ()) -> m () -> m () -runTwice step action = do - liftIO $ step "Running the first time!" - action - liftIO $ step "Running the second time!" - action - -runSQLite - :: (Logger logger, logger ~ GenericLogger) - => (IO (Checkpointer logger) -> TestTree) - -> IO SQLiteEnv - -> TestTree -runSQLite f = runSQLite' (f . fmap fst) - -runSQLite' - :: (Logger logger, logger ~ GenericLogger) - => (IO (Checkpointer logger, SQLiteEnv) -> TestTree) - -> IO SQLiteEnv - -> TestTree -runSQLite' runTest sqlEnvIO = runTest $ do - sqlenv <- sqlEnvIO - cp <- Checkpointer.initCheckpointerResources defaultModuleCacheLimit sqlenv DoNotPersistIntraBlockWrites logger testVer testChainId - return (cp, sqlenv) - where - logger = addLabel ("sub-component", "relational-checkpointer") $ dummyLogger - -runExec :: forall logger. (Logger logger) => Checkpointer logger -> PactDbEnv (BlockEnv logger) -> Maybe Value -> Text -> IO EvalResult -runExec cp pactdbenv eData eCode = do - execMsg <- buildExecParsedCode maxBound {- use latest parser version -} eData eCode - evalTransactionM cmdenv cmdst $ - applyExec' 0 defaultInterpreter execMsg [] [] h' permissiveNamespacePolicy - where - h' = H.toUntypedHash (H.hash "" :: H.PactHash) - cmdenv :: TransactionEnv logger (BlockEnv logger) - cmdenv = TransactionEnv - { _txMode = Transactional - , _txDbEnv = pactdbenv - , _txLogger = cpLogger cp - , _txGasLogger = Nothing - , _txPublicData = noPublicData - , _txSpvSupport = noSPVSupport - , _txNetworkId = Nothing - , _txGasPrice = 0.0 - , _txRequestKey = RequestKey h' - , _txGasLimit = 0 - , _txExecutionConfig = emptyExecutionConfig - , _txQuirkGasFee = Nothing - , _txTxFailuresCounter = Nothing - } - cmdst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty - -runCont :: Logger logger => Checkpointer logger -> PactDbEnv (BlockEnv logger) -> PactId -> Int -> IO EvalResult -runCont cp pactdbenv pactId step = do - evalTransactionM cmdenv cmdst $ - applyContinuation' 0 defaultInterpreter contMsg [] h' permissiveNamespacePolicy - where - contMsg = ContMsg pactId step False (toLegacyJson Null) Nothing - - h' = H.toUntypedHash (H.hash "" :: H.PactHash) - cmdenv = TransactionEnv - { _txMode = Transactional - , _txDbEnv = pactdbenv - , _txLogger = cpLogger cp - , _txGasLogger = Nothing - , _txPublicData = noPublicData - , _txSpvSupport = noSPVSupport - , _txNetworkId = Nothing - , _txGasPrice = 0.0 - , _txRequestKey = RequestKey h' - , _txGasLimit = 0 - , _txExecutionConfig = emptyExecutionConfig - , _txQuirkGasFee = Nothing - , _txTxFailuresCounter = Nothing - } - cmdst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty - --- -------------------------------------------------------------------------- -- --- Pact Utils - --- witnessing that we only use the PactDbEnv portion --- of the CurrentBlockDbEnv -cpReadFrom - :: Logger logger - => Checkpointer logger - -> Maybe BlockHeader - -> (PactDbEnv (BlockEnv logger) -> IO q) - -> IO q -cpReadFrom cp pc f = do - Checkpointer.readFrom - cp - (ParentHeader <$> pc) - Pact4T - (\env _blockHandle -> f $ (_cpPactDbEnv env)) >>= \case - NoHistory -> error $ unwords - [ "Chainweb.Test.Pact4.Checkpointer.cpReadFrom:" - , "parent header missing from the database" - ] - Historical r -> return r - --- allowing a straightforward list of blocks to be passed to the API, --- and only exposing the PactDbEnv part of the block context -cpRestoreAndSave - :: (Logger logger, Monoid q) - => Checkpointer logger - -> Maybe BlockHeader - -> [(BlockHeader, PactDbEnv (BlockEnv logger) -> IO q)] - -> IO q -cpRestoreAndSave cp pc blks = snd <$> Checkpointer.restoreAndSave cp (ParentHeader <$> pc) - (traverse Stream.yield - [Pact4RunnableBlock $ \dbEnv _ -> (,bh) <$> (fun $ _cpPactDbEnv dbEnv) | (bh, fun) <- blks]) - --- | fabricate a `BlockHeader` for a block given its hash and its parent. -childOf :: Maybe BlockHeader -> BlockHash -> BlockHeader -childOf m bhsh = case m of - Just bh -> bh - & blockHash .~ bhsh - & blockParent .~ view blockHash bh - & blockHeight .~ view blockHeight bh + 1 - Nothing -> genesisBlockHeader testVer testChainId - & blockHash .~ bhsh - --- initialize a block env without actually restoring the checkpointer, before --- genesis. -simpleBlockEnvInit - :: (Logger logger) - => logger - -> (PactDb (BlockEnv logger) -> BlockEnv logger -> (MVar (BlockEnv logger) -> IO ()) -> IO a) - -> IO a -simpleBlockEnvInit logger f = withTempSQLiteConnection chainwebPragmas $ \sqlenv -> - f chainwebPactDb (blockEnv sqlenv) (\_ -> Pact5.initSchema sqlenv) - where - blockEnv sqlenv = BlockEnv - (mkBlockHandlerEnv testVer testChainId (BlockHeight 0) sqlenv DoNotPersistIntraBlockWrites logger) - (initBlockState defaultModuleCacheLimit (TxId 0)) - -{- this should be moved to pact -} -begin :: PactDb e -> Method e (Maybe TxId) -begin pactdb = _beginTx pactdb Transactional - -{- this should be moved to pact -} -commit :: PactDb e -> Method e [TxLogJson] -commit pactdb = _commitTx pactdb - -loadModule :: IO (ModuleName, ModuleData Ref, PersistModuleData) -loadModule = do - (r,s) <- execScript' (Script False fn) fn - case r of - Left a -> throwFail $ "module load failed: " ++ show a - Right _ -> case preview (rEvalState . evalRefs . rsLoadedModules . ix mn) s of - Just (md,_) -> case traverse (traverse toPersistDirect) md of - Right md' -> return (mn,md,md') - Left e -> throwFail $ "toPersistDirect failed: " ++ show e - Nothing -> throwFail $ "Failed to find module 'simple': " ++ - show (view (rEvalState . evalRefs . rsLoadedModules) s) - where - mn = ModuleName "simple" Nothing - fn = "test/pact/simple.repl" - -nativeLookup :: NativeDefName -> Maybe (Term Name) -nativeLookup (NativeDefName n) = case SHM.lookup n nativeDefs of - Just (Direct t) -> Just t - _ -> Nothing - -tIntList :: [Int] -> Term Name -tIntList = toTList (TyPrim TyInteger) noInfo . map toTerm - -tStringList :: [Text] -> Term Name -tStringList = toTList (TyPrim TyString) noInfo . map toTerm - -toTerm' :: ToTerm a => a -> Term Name -toTerm' = toTerm - -defFree :: Text -defFree = T.unlines - [ "(module ezfree G (defcap G () true) (defun ALLOW () true))" - , "(define-namespace 'free (create-user-guard (ALLOW)) (create-user-guard (ALLOW)))" - ] - -defModule :: Text -> Text -defModule idx = T.unlines - [ " ;;" - , "" - , "(module m" <> idx <> " G" - , " (defcap G () true)" - , " (defschema sch col:integer)" - , "" - , " (deftable tbl:{sch})" - , "" - , " (defun insertTbl (a i)" - , " (insert tbl a { 'col: i }))" - , "" - , " (defun updateTbl (a i)" - , " (update tbl a { 'col: i}))" - , "" - , " (defun weirdUpdateTbl (a i)" - , " (update tbl a { 'col: 0})" - , " (update tbl a { 'col: i}))" - , "" - , " (defun readTbl ()" - , " (sort (map (at 'col)" - , " (select tbl (constantly true)))))" - , "" - , " (defpact dopact (n)" - , " (step { 'name: n, 'value: 1 })" - , " (step { 'name: n, 'value: 2 }))" - , "" - , ")" - , "(create-table tbl)" - , "(readTbl)" - , "(insertTbl \"a\" 1)" - ] - -tablecode :: Text -tablecode = T.unlines - [ "(namespace 'free)" - , "(define-keyset \"free.table-admin-keyset\"" - , " (read-keyset \"test-keyset\"))" - , "" - , "(module table-example \"free.table-admin-keyset\"" - , "" - , " (defschema test-schema" - , " content:string)" - , "" - , " (deftable test-table:{test-schema})" - , "" - , " (defun add-row (row:string content:string)" - , " (insert test-table row {" - , " \"content\": content" - , " })" - , " )" - , " (defun read-table ()" - , " (select test-table (constantly true))" - , " )" - , ")" - , "" - , "(create-table test-table)" - ] diff --git a/test/unit/Chainweb/Test/Pact4/DbCacheTest.hs b/test/unit/Chainweb/Test/Pact4/DbCacheTest.hs deleted file mode 100644 index 52cc6f07af..0000000000 --- a/test/unit/Chainweb/Test/Pact4/DbCacheTest.hs +++ /dev/null @@ -1,86 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE FlexibleContexts #-} - -module Chainweb.Test.Pact4.DbCacheTest (tests) where - -import Chainweb.Pact.Backend.DbCache - -import Control.Monad (void) -import Control.Monad.State.Strict - -import Data.Aeson -import Data.ByteString.Lazy (toStrict) - -import Database.SQLite3.Direct - -import GHC.Compact - -import Test.Tasty -import Test.Tasty.HUnit - -tests :: TestTree -tests = testGroup "Chainweb.Test.Pact4.DbCacheTest" - [ testCache ] - -entry :: MonadIO m => String -> m ([String], Int) -entry c = do - s <- liftIO $ compactSize =<< compact [c] - return ([c], fromIntegral s) - -testCache :: TestTree -testCache = testCase "testCache" $ do - - -- Create Items - (a, sa) <- entry "a" - (b0, sb0) <- entry "b0" - (b1, sb1) <- entry "b1" - (c, sc) <- entry "c" - (d, sd) <- entry "d" - - -- cache size (enough to hold a + b0 + b1 + c) - let cs = DbCacheLimitBytes . fromIntegral $ sa + sb0 + sb1 + sc + 1 - - void $ (`runStateT` (emptyDbCache cs :: DbCache [String])) $ do - - -- a: simple insert - doCheck "a insert @1 -> [a@1]" "a" a 1 - assertEqual' "size a" sa cacheSize - assertEqual' "count 1" 1 cacheCount - - -- b0: simple insert - doCheck "b->v0 insert @2 -> [a@1,b0@2]" "b" b0 2 - assertEqual' "size a + b0" (sa + sb0) cacheSize - assertEqual' "count 2" 2 cacheCount - - -- b1: insert with different values, same key+txid - doCheck "b->v1 insert @2 -> [a@1,b0@2,b1@2]" "b" b1 2 - assertEqual' "size a + b0 + b1" (sa + sb0 + sb1) cacheSize - assertEqual' "count 3" 3 cacheCount - - -- c: big insert - doCheck "c insert @3 -> [a@1,b0@2,b1@2,c@3]" "c" c 3 - assertEqual' "size a + b0 + b1 + c" (sa + sb0 + sb1 + sc) cacheSize - assertEqual' "count 4" 4 cacheCount - - -- d: small insert to trip limit, evict - doCheck "d insert @4, evict a@1 -> [b0@2,b1@2,c@3,d@4]" "d" d 4 - assertEqual' "size b0 + b1 + c + d" (sb0 + sb1 + sc + sd) cacheSize - assertEqual' "count 4" 4 cacheCount - - -- hit b->v0 to avoid eviction, cache stats unchanged - doCheck "b->v0 hit @5 -> [b1@2,c@3,d@4,b0@5]" "b" b0 5 - assertEqual' "size unchanged" (sb1 + sc + sd + sb0) cacheSize - assertEqual' "count unchanged" 4 cacheCount - - -- reinsert a to evict b->v1, c - doCheck "a reinsert, evict b->v1@2 -> [c@3,d@4,b0@5,a@6]" "a" a 6 - assertEqual' "size c + d + b0 + a) - a - b" (sc + sd + sb0 + sa) cacheSize - assertEqual' "count 4" 4 cacheCount - - where - - doCheck msg k v txid = do - mc <- StateT (checkDbCache (Utf8 k) decodeStrict (toStrict (encode v)) txid) - liftIO $ assertEqual msg (Just v) mc - - assertEqual' msg ex act = get >>= liftIO . assertEqual msg ex . act diff --git a/test/unit/Chainweb/Test/Pact4/GrandHash.hs b/test/unit/Chainweb/Test/Pact4/GrandHash.hs deleted file mode 100644 index f2eb40553c..0000000000 --- a/test/unit/Chainweb/Test/Pact4/GrandHash.hs +++ /dev/null @@ -1,190 +0,0 @@ -{-# LANGUAGE BinaryLiterals #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeApplications #-} - -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module Chainweb.Test.Pact4.GrandHash - ( tests - ) - where - -import Data.ByteArray qualified as Memory -import Data.Functor.Identity (Identity(..)) -import Data.ByteString.Base16 qualified as Base16 -import Data.Maybe (mapMaybe) -import Crypto.Hash (hashWith) -import Data.List qualified as List -import Chainweb.Pact.Backend.PactState (PactRow(..), Table(..)) -import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (TableHash(..), rowToHashInput, hashTable, tableNameToHashInput, hashStream, hashAlgorithm) -import Control.Monad (replicateM) -import Data.ByteString (ByteString) -import Data.ByteString.Builder qualified as BB -import Data.ByteString.Lazy qualified as BL -import Data.ByteString qualified as BS -import Data.Bytes qualified as Bytes -import Data.Bytes.Parser (Parser) -import Data.Bytes.Parser qualified as Smith -import Data.Bytes.Parser.Ascii qualified as Smith -import Data.Bytes.Parser.LittleEndian qualified as SmithLE -import Data.Int (Int64) -import Data.Text (Text) -import Data.Text qualified as Text -import Data.Text.Encoding qualified as Text -import Data.Vector qualified as Vector -import Data.Word (Word8, Word32, Word64) -import Streaming.Prelude qualified as S -import Test.QuickCheck (Property, Arbitrary, Gen, Positive(..), (===), arbitrary, elements) -import Test.Tasty (TestTree) -import Test.Tasty.HUnit (Assertion, testCase, (@?=)) -import Test.Tasty.QuickCheck (testProperty) - -import Chainweb.Test.Utils - -tests :: TestTree -tests = - independentSequentialTestGroup "Chainweb.Test.Pact4.GrandHash" - [ testCase "PactRow hash input roundtrip - habibti ascii" (testPactRow habibtiAscii) - , testCase "PactRow hash input roundtrip - habibti utf8" (testPactRow habibtiUtf8) - , testProperty "PactRow hash input roundtrip - arbitrary utf8" propPactRowHashInputRoundtrip - , testProperty "Table hash input roundtrip - arbitrary utf8" propTableHashInputRoundtrip - , testProperty "Table hash: incremental equals non-incremental" propHashWholeTableEqualsIncremental - , testProperty "Grand Hash of tables: incremental equals non-incremental" propHashWholeChainEqualsIncremental - ] - -habibtiAscii :: PactRow -habibtiAscii = PactRow - { rowKey = Text.encodeUtf8 "Habibti" - , rowData = Text.encodeUtf8 "Asophiel" - , txId = 2100 - } - -habibtiUtf8 :: PactRow -habibtiUtf8 = PactRow - { rowKey = Text.encodeUtf8 "حبيبتي" - , rowData = Text.encodeUtf8 "Kaspitell" - , txId = 2300 - } - -propPactRowHashInputRoundtrip :: PactRow -> Property -propPactRowHashInputRoundtrip row = - Right row === parseRowHashInput (rowToHashInput row) - -propTableHashInputRoundtrip :: Text -> Property -propTableHashInputRoundtrip tablename = - Right (len, tblName) === parseTableHashInput (tableNameToHashInput tablename) - where - tblName = Text.encodeUtf8 (Text.toLower tablename) - len = fromIntegral @Int @Word64 (BS.length tblName) - -propHashWholeTableEqualsIncremental :: Table -> Property -propHashWholeTableEqualsIncremental tbl = - let - incrementalHash :: Maybe TableHash - incrementalHash = hashTable tbl.name - $ Vector.fromList - $ List.sortOn (\pr -> pr.rowKey) tbl.rows - - wholeHash :: Maybe ByteString - wholeHash = testHashTableNotIncremental tbl - in - fmap (hex . getTableHash) incrementalHash === fmap hex wholeHash - -testHashTableNotIncremental :: Table -> Maybe ByteString -testHashTableNotIncremental tbl = if null tbl.rows - then Nothing - else Just - $ Memory.convert - $ hashWith hashAlgorithm - $ BL.toStrict - $ BB.toLazyByteString - $ (BB.byteString (tableNameToHashInput tbl.name) <>) - $ foldMap (BB.byteString . rowToHashInput) - $ List.sortOn (\pr -> pr.rowKey) tbl.rows - -propHashWholeChainEqualsIncremental :: [Table] -> Property -propHashWholeChainEqualsIncremental tbls = - let - sortedTables = List.sortOn (\tbl -> tbl.name) tbls - tableHashes = mapMaybe testHashTableNotIncremental sortedTables - - incrementalHash = fst $ runIdentity $ hashStream @Identity (S.each tableHashes) - - wholeHash = Memory.convert $ hashWith hashAlgorithm $ BS.concat tableHashes - in - incrementalHash === wholeHash - -instance Arbitrary PactRow where - arbitrary = genPactRow - -instance Arbitrary Table where - arbitrary = genTable - -genTable :: Gen Table -genTable = do - tblNameLen <- elements @Int [3 .. 20] - tblName <- Text.pack <$> replicateM tblNameLen (arbitrary @Char) - - numRows <- elements @Int [1 .. 10] - tblRows <- replicateM numRows genPactRow - - pure $ Table - { name = tblName - , rows = tblRows - } - -genPactRow :: Gen PactRow -genPactRow = do - txid <- arbitrary @Word32 - rkLen <- fmap (fromIntegral @_ @Int . getPositive) $ arbitrary @(Positive Word8) - rdLen <- fmap (fromIntegral @_ @Int . getPositive) $ arbitrary @(Positive Word8) - - rk <- Text.pack <$> replicateM rkLen (arbitrary @Char) - rd <- Text.pack <$> replicateM rdLen (arbitrary @Char) - - pure $ PactRow - { rowKey = Text.encodeUtf8 rk - , txId = fromIntegral @Word32 @Int64 txid - , rowData = Text.encodeUtf8 rd - } - -testPactRow :: PactRow -> Assertion -testPactRow row = do - Right row @?= parseRowHashInput (rowToHashInput row) - -parseRowHashInput :: ByteString -> Either Text PactRow -parseRowHashInput b = Smith.parseBytesEither parser (Bytes.fromByteString b) - where - parser :: Parser Text s PactRow - parser = do - _ <- Smith.char "rowkey tag" 'K' - rkLen <- SmithLE.word64 "rowkey len" - rk <- Smith.take "rowkey" (fromIntegral @Word64 @Int rkLen) - - _ <- Smith.char "txid tag" 'I' - txid <- SmithLE.word64 "txid" - - _ <- Smith.char "rowdata tag" 'D' - rdLen <- SmithLE.word64 "rowdata len" - rd <- Smith.take "rowdata" (fromIntegral @Word64 @Int rdLen) - - pure $ PactRow - { rowKey = Bytes.toByteString rk - , txId = fromIntegral @Word64 @Int64 txid - , rowData = Bytes.toByteString rd - } - -parseTableHashInput :: ByteString -> Either Text (Word64, ByteString) -parseTableHashInput b = Smith.parseBytesEither parser (Bytes.fromByteString b) - where - parser :: Parser Text s (Word64, ByteString) - parser = do - _ <- Smith.char "tablename tag" 'T' - len <- SmithLE.word64 "tablename len" - tablename <- Smith.take "tablename" (fromIntegral @Word64 @Int len) - pure (len, Bytes.toByteString tablename) - -hex :: ByteString -> Text -hex = Text.decodeUtf8 . Base16.encode diff --git a/test/unit/Chainweb/Test/Pact4/ModuleCacheOnRestart.hs b/test/unit/Chainweb/Test/Pact4/ModuleCacheOnRestart.hs deleted file mode 100644 index ec98b22b49..0000000000 --- a/test/unit/Chainweb/Test/Pact4/ModuleCacheOnRestart.hs +++ /dev/null @@ -1,306 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} - -module Chainweb.Test.Pact4.ModuleCacheOnRestart (tests) where - -import Control.Concurrent.MVar.Strict -import Control.DeepSeq (NFData) -import Control.Lens -import Control.Monad -import Control.Monad.IO.Class - -import qualified Data.Map.Strict as M -import Data.List (intercalate) -import qualified Data.Text as T - -import GHC.Generics - -import Test.Tasty.HUnit -import Test.Tasty - -import System.LogLevel - --- pact imports - -import Pact.Types.Runtime (mdModule) -import Pact.Types.Term -import qualified Pact.Utils.StableHashMap as SHM - --- chainweb imports - -import Chainweb.BlockHeader -import Chainweb.ChainId -import Chainweb.Graph -import Chainweb.Logger -import Chainweb.Miner.Pact - -import Chainweb.Pact.PactService -import Chainweb.Pact.Types -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Time -import Chainweb.Test.Cut -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Utils -import Chainweb.Test.Pact4.Utils(getPWOByHeader) -import Chainweb.Test.TestVersions(fastForkingCpmTestVersion) -import Chainweb.Utils -import Chainweb.Version -import Chainweb.WebBlockHeaderDB - -import Chainweb.Storage.Table.RocksDB -import qualified Chainweb.Pact4.ModuleCache as Pact4 -import Chainweb.Pact.Backend.Types - -testVer :: ChainwebVersion -testVer = fastForkingCpmTestVersion singletonChainGraph - -testChainId :: ChainId -testChainId = unsafeChainId 0 - -type RewindPoint = (BlockHeader, PayloadWithOutputs) - -data RewindData = RewindData - { afterV4 :: RewindPoint - , beforeV4 :: RewindPoint - , v3Cache :: SHM.StableHashMap ModuleName (Maybe ModuleHash) - } deriving Generic - -instance NFData RewindData - -tests :: RocksDb -> TestTree -tests rdb = - withResource' (newMVar mempty) $ \iom -> - withResource' newEmptyMVar $ \rewindDataM -> - withResourceT (mkTestBlockDb testVer rdb) $ \bdbio -> - withResourceT withTempSQLiteResource $ \ioSqlEnv -> - independentSequentialTestGroup "Chainweb.Test.Pact4.ModuleCacheOnRestart" - [ testCaseSteps "testInitial" $ withPact' bdbio ioSqlEnv iom testInitial - , testCaseSteps "testRestart1" $ withPact' bdbio ioSqlEnv iom testRestart - , testCaseSteps "testDoUpgrades" $ withPact' bdbio ioSqlEnv iom (testCoinbase bdbio) - , testCaseSteps "testRestart2" $ withPact' bdbio ioSqlEnv iom testRestart - , testCaseSteps "testV3" $ withPact' bdbio ioSqlEnv iom (testV3 bdbio rewindDataM) - , testCaseSteps "testRestart3"$ withPact' bdbio ioSqlEnv iom testRestart - , testCaseSteps "testV4" $ withPact' bdbio ioSqlEnv iom (testV4 bdbio rewindDataM) - , testCaseSteps "testRestart4" $ withPact' bdbio ioSqlEnv iom testRestart - , testCaseSteps "testRewindAfterFork" $ withPact' bdbio ioSqlEnv iom (testRewindAfterFork bdbio rewindDataM) - , testCaseSteps "testRewindBeforeFork" $ withPact' bdbio ioSqlEnv iom (testRewindBeforeFork bdbio rewindDataM) - , testCaseSteps "testCw217CoinOnly" $ withPact' bdbio ioSqlEnv iom $ - testCw217CoinOnly bdbio rewindDataM - , testCaseSteps "testRestartCw217" $ - withPact' bdbio ioSqlEnv iom testRestart - ] - -type CacheTest logger tbl = - (PactServiceM logger tbl () - ,IO (MVar ModuleInitCache) -> ModuleInitCache -> Assertion) - --- | Do genesis load, snapshot cache. -testInitial - :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) - => CacheTest logger tbl -testInitial = (initPayloadState,snapshotCache) - --- | Do restart load, test results of 'initialPayloadState' against snapshotted cache. -testRestart - :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) - => CacheTest logger tbl -testRestart = (initPayloadState,checkLoadedCache) - where - checkLoadedCache ioa initCache = do - a <- ioa >>= readMVar - (justModuleHashes a) `assertNoCacheMismatch` (justModuleHashes initCache) - --- | Run coinbase to do upgrade to v2, snapshot cache. -testCoinbase - :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) - => IO TestBlockDb - -> CacheTest logger tbl -testCoinbase iobdb = (initPayloadState >> doCoinbase,snapshotCache) - where - genHeight = genesisHeight testVer testChainId - doCoinbase = do - bdb <- liftIO iobdb - bip <- throwIfNoHistory =<< execNewBlock mempty noMiner NewBlockFill - (ParentHeader (genesisBlockHeader testVer testChainId)) - let pwo = forAnyPactVersion finalizeBlock bip - void $ liftIO $ addTestBlockDb bdb (succ genHeight) (Nonce 0) (offsetBlockTime second) testChainId pwo - nextH <- liftIO $ getParentTestBlockDb bdb testChainId - void $ execValidateBlock mempty nextH (CheckablePayloadWithOutputs pwo) - -testV3 - :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) - => IO TestBlockDb - -> IO (MVar RewindData) - -> CacheTest logger tbl -testV3 iobdb rewindM = (go,grabAndSnapshotCache) - where - go = do - initPayloadState - void $ doNextCoinbase iobdb - void $ doNextCoinbase iobdb - hpwo <- doNextCoinbase iobdb - liftIO (rewindM >>= \rewind -> putMVar rewind $ RewindData hpwo hpwo mempty) - grabAndSnapshotCache ioa initCache = do - rewindM >>= \rewind -> modifyMVar_ rewind $ \old -> pure $ old { v3Cache = justModuleHashes initCache } - snapshotCache ioa initCache - -testV4 - :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) - => IO TestBlockDb - -> IO (MVar RewindData) - -> CacheTest logger tbl -testV4 iobdb rewindM = (go,snapshotCache) - where - go = do - initPayloadState - -- at the upgrade/fork point - void $ doNextCoinbase iobdb - -- just after the upgrade/fork point - afterV4' <- doNextCoinbase iobdb - rewind <- liftIO rewindM - liftIO $ modifyMVar_ rewind $ \old -> pure $ old { afterV4 = afterV4' } - void $ doNextCoinbase iobdb - void $ doNextCoinbase iobdb - -testRewindAfterFork - :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) - => IO TestBlockDb - -> IO (MVar RewindData) - -> CacheTest logger tbl -testRewindAfterFork iobdb rewindM = (go, checkLoadedCache) - where - go = do - initPayloadState - liftIO rewindM >>= liftIO . readMVar >>= rewindToBlock . afterV4 - void $ doNextCoinbase iobdb - void $ doNextCoinbase iobdb - checkLoadedCache ioa initCache = do - a <- ioa >>= readMVar - case M.lookup 6 initCache of - Nothing -> assertFailure "Cache not found at height 6" - Just c -> (justModuleHashes a) `assertNoCacheMismatch` (justModuleHashes' c) - -testRewindBeforeFork - :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) - => IO TestBlockDb - -> IO (MVar RewindData) - -> CacheTest logger tbl -testRewindBeforeFork iobdb rewindM = (go, checkLoadedCache) - where - go = do - initPayloadState - liftIO rewindM >>= liftIO . readMVar >>= rewindToBlock . beforeV4 - void $ doNextCoinbase iobdb - void $ doNextCoinbase iobdb - checkLoadedCache ioa initCache = do - a <- ioa >>= readMVar - case (M.lookup 5 initCache, M.lookup 4 initCache) of - (Just c, Just d) -> do - (justModuleHashes a) `assertNoCacheMismatch` (justModuleHashes' c) - v3c <- rewindM >>= \rewind -> fmap v3Cache (readMVar rewind) - assertNoCacheMismatch v3c (justModuleHashes' d) - _ -> assertFailure "Failed to lookup either block 4 or 5." - -testCw217CoinOnly - :: (Logger logger, CanReadablePayloadCas cas, logger ~ GenericLogger) - => IO TestBlockDb - -> IO (MVar RewindData) - -> CacheTest logger cas -testCw217CoinOnly iobdb _rewindM = (go, go') - where - go = do - initPayloadState - void $ doNextCoinbaseN_ 8 iobdb - - go' ioa initCache = do - snapshotCache ioa initCache - case M.lookup 20 initCache of - Just a -> assertEqual "module init cache contains only coin" ["coin"] (Pact4.moduleCacheKeys a) - Nothing -> assertFailure "failed to lookup block at 20" - -assertNoCacheMismatch - :: SHM.StableHashMap ModuleName (Maybe ModuleHash) - -> SHM.StableHashMap ModuleName (Maybe ModuleHash) - -> Assertion -assertNoCacheMismatch c1 c2 = assertBool msg $ c1 == c2 - where - showCache = intercalate "\n" . map show . SHM.toList - msg = mconcat - [ - "Module cache mismatch, found: \n" - , showCache c1 - , "\n expected: \n" - , showCache c2 - ] - -rewindToBlock :: (Logger logger) => CanReadablePayloadCas tbl => RewindPoint -> PactServiceM logger tbl () -rewindToBlock (rewindHeader, pwo) = void $ execValidateBlock mempty rewindHeader (CheckablePayloadWithOutputs pwo) - -doNextCoinbase :: (Logger logger, CanReadablePayloadCas tbl) => IO TestBlockDb -> PactServiceM logger tbl (BlockHeader, PayloadWithOutputs) -doNextCoinbase iobdb = do - bdb <- liftIO iobdb - prevH <- liftIO $ getParentTestBlockDb bdb testChainId - -- we have to execValidateBlock on `prevH` block height to update the parent header - pwo' <- liftIO $ getPWOByHeader prevH bdb - _ <- execValidateBlock mempty prevH (CheckablePayloadWithOutputs pwo') - - bip <- throwIfNoHistory =<< execNewBlock mempty noMiner NewBlockFill (ParentHeader prevH) - let prevH' = forAnyPactVersion (fromJuste . _blockInProgressParentHeader) bip - let pwo = forAnyPactVersion finalizeBlock bip - liftIO $ ParentHeader prevH @?= prevH' - void $ liftIO $ addTestBlockDb bdb (succ $ view blockHeight prevH) (Nonce 0) (offsetBlockTime second) testChainId pwo - nextH <- liftIO $ getParentTestBlockDb bdb testChainId - (valPWO, _g) <- execValidateBlock mempty nextH (CheckablePayloadWithOutputs pwo) - return (nextH, valPWO) - -doNextCoinbaseN_ - :: (Logger logger, CanReadablePayloadCas cas) - => Int - -> IO TestBlockDb - -> PactServiceM logger cas (BlockHeader, PayloadWithOutputs) -doNextCoinbaseN_ n iobdb = fmap last $ replicateM n $ doNextCoinbase iobdb - --- | Interfaces can't be upgraded, but modules can, so verify hash in that case. -justModuleHashes :: ModuleInitCache -> SHM.StableHashMap ModuleName (Maybe ModuleHash) -justModuleHashes = justModuleHashes' . snd . last . M.toList - -justModuleHashes' :: Pact4.ModuleCache -> SHM.StableHashMap ModuleName (Maybe ModuleHash) -justModuleHashes' = - fmap (preview (_1 . mdModule . _MDModule . mHash)) . Pact4.moduleCacheToHashMap - -initPayloadState - :: (CanReadablePayloadCas tbl, Logger logger, logger ~ GenericLogger) - => PactServiceM logger tbl () -initPayloadState = initialPayloadState testVer testChainId - -snapshotCache :: IO (MVar ModuleInitCache) -> ModuleInitCache -> IO () -snapshotCache iomcache initCache = do - mcache <- iomcache - modifyMVar_ mcache (const (pure initCache)) - -withPact' - :: (Logger logger, logger ~ GenericLogger) - => IO TestBlockDb - -> IO SQLiteEnv - -> IO (MVar ModuleInitCache) - -> CacheTest logger RocksDbTable - -> (String -> IO ()) - -> Assertion -withPact' bdbio ioSqlEnv r (ps, cacheTest) tastylog = do - bdb <- bdbio - bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) testChainId - let pdb = _bdbPayloadDb bdb - sqlEnv <- ioSqlEnv - T2 _ pstate <- withPactService - testVer testChainId logger Nothing bhdb pdb sqlEnv testPactServiceConfig ps - cacheTest r (_psInitCache pstate) - where - logger = genericLogger Quiet (tastylog . T.unpack) diff --git a/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs b/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs deleted file mode 100644 index f1d13dc076..0000000000 --- a/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs +++ /dev/null @@ -1,37 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} - --- | --- Module: Chainweb.Test.Pact4.NoCoinbase --- Copyright: Copyright © 2020 Kadena LLC. --- License: MIT --- Maintainer: Lars Kuhtz --- Stability: experimental --- --- TODO --- -module Chainweb.Test.Pact4.NoCoinbase -( tests -) where - -import qualified Pact.JSON.Encode as J -import Pact.Types.Command -import Pact.Types.Hash - -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Chainweb.Pact4.NoCoinbase -import Chainweb.Payload - -tests :: TestTree -tests = testGroup "Chainweb.Test.Pact4.NoCoinbase" - [testCase "noCoinbaseOutput is consistent" test_noCoinbase] - -test_noCoinbase :: Assertion -test_noCoinbase = - noCoinbaseOutput - @=? - CoinbaseOutput (J.encodeStrict (noCoinbase :: CommandResult Hash)) diff --git a/test/unit/Chainweb/Test/Pact4/PactExec.hs b/test/unit/Chainweb/Test/Pact4/PactExec.hs deleted file mode 100644 index a00a97582a..0000000000 --- a/test/unit/Chainweb/Test/Pact4/PactExec.hs +++ /dev/null @@ -1,659 +0,0 @@ -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ViewPatterns #-} - --- | --- Module: Chainweb.Test.Pact --- Copyright: Copyright © 2018 Kadena LLC. --- License: See LICENSE file --- Maintainer: Mark Nichols --- Stability: experimental --- --- Unit test for Pact execution in Chainweb - -module Chainweb.Test.Pact4.PactExec -( tests -) where - -import Control.Lens hiding ((.=)) -import Control.Exception.Safe (tryAny) -import Control.Monad -import Data.Aeson -import qualified Data.ByteString.Lazy.Char8 as BL -import qualified Data.List as L -import Data.String -import Data.Text (Text, pack) -import qualified Data.Vector as V -import qualified Data.Yaml as Y - -import GHC.Generics (Generic) - -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB (BlockHeaderDb) -import Chainweb.ChainId -import Chainweb.Graph hiding (KnownGraph(Pair)) -import Chainweb.Logger -import Chainweb.Miner.Pact -import Chainweb.Pact.PactService -import Chainweb.Pact.PactService.Checkpointer -import Chainweb.Pact.PactService.Pact4.ExecBlock -import Chainweb.Pact.Types -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.InMemory (newPayloadDb) -import Chainweb.Storage.Table.RocksDB (RocksDb) -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import qualified Chainweb.Pact4.Transaction as Pact4 -import qualified Chainweb.Pact4.Types as Pact4 -import Chainweb.Version (ChainwebVersion(..), PactVersionT(..)) -import Chainweb.Version.Utils (someChainId) -import Chainweb.Utils hiding (check) - -import Pact.Types.Command -import Pact.Types.Hash -import Pact.Types.PactValue -import Pact.Types.Persistence -import Pact.Types.Pretty - -import qualified Pact.JSON.Encode as J -import Data.Functor.Product -import qualified Data.Aeson as Aeson -import qualified Data.Text.Lazy.Encoding as TL -import qualified Data.Text.Lazy as TL - -testVersion :: ChainwebVersion -testVersion = slowForkingCpmTestVersion petersonChainGraph - -testEventsVersion :: ChainwebVersion -testEventsVersion = fastForkingCpmTestVersion singletonChainGraph - -cid :: ChainId -cid = someChainId testVersion - -tests :: TestTree -tests = - withResource' newPayloadDb $ \pdb -> - withResourceT withRocksResource $ \rocksIO -> - testGroup label - - -- The test pact context evaluates the test code at block height 1. - -- fungible-v2 is installed at that block height 1. Because applying the - -- update twice results in an validation failures, we have to run each test on - -- a fresh pact environment. Unfortunately, that's a bit slow. - [ withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTest testVersion ctx testReq2 - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTest testVersion ctx testReq3 - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTest testVersion ctx testReq4 - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTest testVersion ctx testReq5 - , withPactCtxSQLite logger testEventsVersion (bhdbIO testEventsVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTxsTest testEventsVersion ctx "testTfrGas" testTfrGas - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTxsTest testVersion ctx "testGasPayer" testGasPayer - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTxsTest testVersion ctx "testContinuationGasPayer" testContinuationGasPayer - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTxsTest testVersion ctx "testExecGasPayer" testExecGasPayer - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTest testVersion ctx testReq6 - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTxsTest testVersion ctx "testTfrNoGasFails" testTfrNoGasFails - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTxsTest testVersion ctx "testBadSenderFails" testBadSenderFails - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execTxsTest testVersion ctx "testFailureRedeem" testFailureRedeem - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb testPactServiceConfig $ - \ctx -> execLocalTest ctx "testAllowReadsLocalFails" testAllowReadsLocalFails - , withPactCtxSQLite logger testVersion (bhdbIO testVersion rocksIO) pdb allowReads $ - \ctx -> execLocalTest ctx "testAllowReadsLocalSuccess" testAllowReadsLocalSuccess - ] - where - bhdbIO :: ChainwebVersion -> IO RocksDb -> IO BlockHeaderDb - bhdbIO v rocksIO = do - rdb <- rocksIO - let genesisHeader = genesisBlockHeader v cid - testBlockHeaderDb rdb genesisHeader - - label = "Chainweb.Test.Pact4.PactExec" - allowReads = testPactServiceConfig { _pactAllowReadsInLocal = True } - - logger = dummyLogger - --- -------------------------------------------------------------------------- -- --- Pact test datatypes - -type RunTest a = IO (TestResponse a) -> TestTree - --- | A test request is comprised of a list of commands, a textual discription, --- and an test runner function, that turns an IO acttion that produces are --- 'TestResponse' into a 'TestTree'. --- -data TestRequest = TestRequest - { _trCmds :: ![TestSource] - , _trDisplayStr :: !String - , _trEval :: !(RunTest TestSource) - } - - -data TestSource = File FilePath | Code String - deriving (Show, Generic, ToJSON) - -data TestResponse a = TestResponse - { _trOutputs :: ![(a, CommandResult Hash)] - , _trCoinBaseOutput :: !(CommandResult Hash) - } - deriving (Generic, Show) - -type TxsTest = (IO (V.Vector Pact4.Transaction), Either String (TestResponse String) -> Assertion) - --- -------------------------------------------------------------------------- -- --- sample data - -testReq2 :: TestRequest -testReq2 = TestRequest - { _trCmds = [ File "test1.pact" ] - , _trEval = checkSuccessOnly' "load module test1.pact" - , _trDisplayStr = "Loads a pact module" - } - -testReq3 :: TestRequest -testReq3 = TestRequest - { _trCmds = [ Code "(create-table test1.accounts)" ] - , _trEval = fileCompareTxLogs "create-table" - , _trDisplayStr = "Creates tables" - } - -testReq4 :: TestRequest -testReq4 = TestRequest - { _trCmds = [ Code "(test1.create-global-accounts)" ] - , _trEval = fileCompareTxLogs "create-accounts" - , _trDisplayStr = "Creates two accounts" - } - -testReq5 :: TestRequest -testReq5 = TestRequest - { _trCmds = [ Code "(test1.transfer \"Acct1\" \"Acct2\" 1.00)" ] - , _trEval = fileCompareTxLogs "transfer-accounts" - , _trDisplayStr = "Transfers from one account to another" - } - -testReq6 :: TestRequest -testReq6 = TestRequest - { _trCmds = - [ Code "(+ 1 1)" - , File "test1.pact" - , Code "(create-table test1.accounts)" - , Code "(test1.create-global-accounts)" - , Code "(test1.transfer \"Acct1\" \"Acct2\" 1.00)" - ] - , _trEval = checkSuccessOnly' "load test1.pact, create table, transfer" - , _trDisplayStr = "Transfers from one account to another" - } - - -assertResultFail :: Show a => HasCallStack => String -> String -> Either String a -> Assertion -assertResultFail msg expectErr (Left e) = assertSatisfies msg e ((L.isInfixOf expectErr).show) -assertResultFail msg _ (Right a) = assertFailure $ msg ++ ", received: " ++ show a - -checkResultSuccess :: HasCallStack => ([PactResult] -> Assertion) -> Either String (TestResponse String) -> Assertion -checkResultSuccess _ (Left e) = assertFailure $ "Expected success, got: " ++ show e -checkResultSuccess test (Right (TestResponse outs _)) = test $ map (_crResult . snd) outs - -checkPactResultSuccess :: HasCallStack => String -> PactResult -> (PactValue -> Assertion) -> Assertion -checkPactResultSuccess _ (PactResult (Right pv)) test = test pv -checkPactResultSuccess msg (PactResult (Left e)) _ = assertFailure $ msg ++ ": expected tx success, got " ++ show e - -checkPactResultSuccessLocal :: HasCallStack => String -> (PactValue -> Assertion) -> PactResult -> Assertion -checkPactResultSuccessLocal msg test r = checkPactResultSuccess msg r test - -checkPactResultFailure :: HasCallStack => String -> String -> PactResult -> Assertion -checkPactResultFailure msg _ (PactResult (Right pv)) = assertFailure $ msg ++ ": expected tx failure, got " ++ show pv -checkPactResultFailure msg expectErr (PactResult (Left e)) = assertSatisfies msg e ((L.isInfixOf expectErr).show) - -testTfrNoGasFails :: TxsTest -testTfrNoGasFails = - (V.singleton <$> tx, - assertResultFail "Expected missing (GAS) failure" "Keyset failure") - where - tx = buildCwCmd "testTfrNoGas" testVersion - $ set cbSigners - [ mkEd25519Signer' sender00 - [ mkTransferCap "sender00" "sender01" 1.0 ] - ] - $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") - $ defaultCmd - -testTfrGas :: TxsTest -testTfrGas = (V.singleton <$> tx,test) - where - tx = buildCwCmd "testTfrGas" testEventsVersion $ set cbSigners - [ mkEd25519Signer' sender00 - [ mkTransferCap "sender00" "sender01" 1.0 - , mkGasCap - ] - ] - $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") - $ defaultCmd - test (Left e) = assertFailure $ "Expected success, got " ++ show e - test (Right (TestResponse [(_,cr)] _)) = do - checkPactResultSuccess "transfer succeeds" (_crResult cr) $ \pv -> - assertEqual "transfer succeeds" (pString "Write succeeded") pv - e <- mkTransferEvent "sender00" "sender01" 1.0 "coin" "_S6HOO3J8-dEusvtnjSF4025dAxKu6eFSIOZocQwimA" - assertEqual "event found" [e] (_crEvents cr) - test r = assertFailure $ "expected single test response: " ++ show r - -testBadSenderFails :: TxsTest -testBadSenderFails = - (V.singleton <$> tx, - assertResultFail "Expected failure on bad sender" - "row not found: some-unknown-sender") - where - tx = buildCwCmd "testBadSenderFails" testVersion - $ set cbSigners [ mkEd25519Signer' sender00 [] ] - $ set cbSender "some-unknown-sender" - $ set cbRPC (mkExec' "(+ 1 2)") - $ defaultCmd - -testGasPayer :: TxsTest -testGasPayer = (txs,checkResultSuccess test) - where - loadCode = fmap V.fromList $ do - - impl <- loadGP - forM [ impl, setupUser, fundGasAcct ] $ \rpc -> - buildCwCmd "testGasPayer" testVersion $ - set cbSigners [s01] $ - set cbSender "sender01" $ - set cbRPC rpc $ - defaultCmd - - where - - loadGP = (`mkExec` mkKeySetData "gas-payer-operate" [sender01]) <$> - getPactCode (File "../../pact/gas-payer/gas-payer-v1-reference.pact") - - setupUser = mkExec - "(gas-payer-v1-reference.fund-user \"sender00\" (read-keyset \"sender00\") 100.0)" $ - mkKeySetData "sender00" [sender00] - - fundGasAcct = mkExec' - "(coin.transfer-create \"sender01\" \"gas-payer\" (gas-payer-v1-reference.create-gas-payer-guard) 100.0)" - - s01 = mkEd25519Signer' sender01 - [ mkTransferCap "sender01" "gas-payer" 100.0 - , mkGasCap - , mkCapability "user.gas-payer-v1-reference" "FUND_USER" [] - ] - - - runPaidTx = fmap V.singleton $ buildCwCmd "testGasPayer" testVersion $ - set cbSigners - [mkEd25519Signer' sender00 - [mkCapability "user.gas-payer-v1-reference" "GAS_PAYER" - [pString "sender00",pInteger 10_000,pDecimal 0.01]]] $ - set cbRPC (mkExec' "(+ 1 2)") $ - defaultCmd - - txs = do - l <- loadCode - r <- runPaidTx - return $! l <> r - - test [impl,setupUser,fundGasAcct,paidTx] = do - checkPactResultSuccess "impl" impl $ - assertEqual "impl" (pString "TableCreated") - checkPactResultSuccess "setupUser" setupUser $ - assertEqual "setupUser" (pString "Write succeeded") - checkPactResultSuccess "fundGasAcct" fundGasAcct $ - assertEqual "fundGasAcct" (pString "Write succeeded") - checkPactResultSuccess "paidTx" paidTx $ - assertEqual "paidTx" (pDecimal 3) - test r = assertFailure $ "Expected 4 results, got: " ++ show r - - -testContinuationGasPayer :: TxsTest -testContinuationGasPayer = (txs,checkResultSuccess test) - where - setupExprs = do - implCode <- getPactCode (File "../pact/continuation-gas-payer.pact") - return [ implCode - , "(coin.transfer-create \"sender00\" \"cont-gas-payer\" (gas-payer-for-cont.create-gas-payer-guard) 100.0)" - , "(simple-cont-module.some-two-step-pact)" - , "(coin.get-balance \"cont-gas-payer\")" ] - - setupTest = fmap V.fromList $ do - setupExprs' <- setupExprs - forM setupExprs' $ \se -> buildCwCmd "testContinuationGasPayer" testVersion $ - set cbSigners - [ mkEd25519Signer' sender00 - [ mkTransferCap "sender00" "cont-gas-payer" 100.0 - , mkGasCap - ]] $ - set cbRPC (mkExec' se) $ - defaultCmd - - contPactId = "9ylBanSjDGJJ6m0LgokZqb9P66P7JsQRWo9sYxqAjcQ" - - runStepTwoWithGasPayer = fmap V.singleton $ buildCwCmd "testContinuationGasPayer" testVersion $ - set cbSigners - [ mkEd25519Signer' sender01 - [ mkCapability "user.gas-payer-for-cont" "GAS_PAYER" - [pString "sender01",pInteger 10_000,pDecimal 0.01] - ]] $ - set cbSender "cont-gas-payer" $ - set cbRPC (mkCont $ mkContMsg (fromString contPactId) 1) $ - defaultCmd - - balanceCheck = fmap V.singleton $ buildCwCmd "testContinuationGasPayer2" testVersion $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbRPC (mkExec' "(coin.get-balance \"cont-gas-payer\")") $ - defaultCmd - - txs = do - s <- setupTest - assertEqual "pact ID correct" (Just $ "\"" <> contPactId <> "\"") $ - preview (ix 2 . cmdHash . to show) s - r <- runStepTwoWithGasPayer - b <- balanceCheck - return $! s <> r <> b - - test [impl,fundGasAcct,contFirstStep,balCheck1,paidSecondStep,balCheck2] = do - checkPactResultSuccess "impl" impl $ assertEqual "impl" - (pString "Loaded module user.simple-cont-module, hash pCtVh0IDPvRIdVFXxznBFTwsZcwbIcYAnfv7yzr4wRI") - checkPactResultSuccess "fundGasAcct" fundGasAcct $ assertEqual "fundGasAcct" - (pString "Write succeeded") - checkPactResultSuccess "contFirstStep" contFirstStep $ assertEqual "contFirstStep" - (pString "Step One") - checkPactResultSuccess "balCheck1" balCheck1 $ - assertEqual "balCheck1" (pDecimal 100) - checkPactResultSuccess "paidSecondStep" paidSecondStep $ - assertEqual "paidSecondStep" - (pString "Step Two") - checkPactResultSuccess "balCheck2" balCheck2 $ - assertEqual "balCheck2" (pDecimal 99.999_4) - test r = assertFailure $ "Expected 6 results, got: " ++ show r - -testExecGasPayer :: TxsTest -testExecGasPayer = (txs,checkResultSuccess test) - where - setupExprs = do - implCode <- getPactCode (File "../pact/exec-gas-payer.pact") - return [ implCode - , "(coin.transfer-create \"sender00\" \"exec-gas-payer\" (gas-payer-for-exec.create-gas-payer-guard) 100.0)" - , "(coin.get-balance \"exec-gas-payer\")" ] - setupTest = fmap V.fromList $ do - setupExprs' <- setupExprs - forM setupExprs' $ \se -> buildCwCmd "testExecGasPayer" testVersion $ - set cbSigners - [ mkEd25519Signer' sender00 - [ mkTransferCap "sender00" "exec-gas-payer" 100.0 - , mkGasCap - ]] $ - set cbRPC (mkExec' se) $ - defaultCmd - - runPaidTx = fmap V.singleton $ buildCwCmd "testExecGasPayer" testVersion $ - set cbSigners - [mkEd25519Signer' sender01 - [mkCapability "user.gas-payer-for-exec" "GAS_PAYER" - [pString "sender01",pInteger 10_000,pDecimal 0.01]]] $ - set cbSender "exec-gas-payer" $ - set cbRPC (mkExec' "(+ 1 2)") $ - defaultCmd - - balanceCheck = fmap V.singleton $ buildCwCmd "testExecGasPayer" testVersion $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbRPC (mkExec' "(coin.get-balance \"exec-gas-payer\")") $ - defaultCmd - - txs = do - s <- setupTest - r <- runPaidTx - b <- balanceCheck - return $! s <> r <> b - - test [impl,fundGasAcct,balCheck1,paidTx,balCheck2] = do - checkPactResultSuccess "impl" impl $ assertEqual "impl" - (pString "Loaded module user.gas-payer-for-exec, hash _S7ASfb_Lvr5wmjERG_XwPUoojW6GHBWI2u0W6jmID0") - checkPactResultSuccess "fundGasAcct" fundGasAcct $ assertEqual "fundGasAcct" - (pString "Write succeeded") - checkPactResultSuccess "balCheck1" balCheck1 $ assertEqual "balCheck1" (pDecimal 100) - checkPactResultSuccess "paidTx" paidTx $ assertEqual "paidTx" (pDecimal 3) - checkPactResultSuccess "balCheck2" balCheck2 $ assertEqual "balCheck2" (pDecimal 99.999_4) - test r = assertFailure $ "Expected 6 results, got: " ++ show r - -testFailureRedeem :: TxsTest -testFailureRedeem = (txs,checkResultSuccess test) - where - txs = fmap V.fromList $ forM exps $ \e -> buildCwCmd "testFailureRedeem" testVersion $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbGasPrice 0.01 $ - set cbGasLimit 1000 $ - set cbRPC (mkExec' e) $ - defaultCmd - - exps = - ["(coin.get-balance \"sender00\")" - ,"(coin.get-balance \"miner\")" - ,"(enforce false \"forced error\")" - ,"(coin.get-balance \"sender00\")" - ,"(coin.get-balance \"miner\")"] - test [sbal0,mbal0,ferror,sbal1,mbal1] = do - -- sender 00 first is 100000000 - full gas debit during tx (1) - checkPactResultSuccess "sender bal 0" sbal0 $ - assertEqual "sender bal 0" (pDecimal 99_999_990) - -- miner first is reward + tx gas for [0] - checkPactResultSuccess "miner bal 0" mbal0 $ - assertEqual "miner bal 0" (pDecimal 2.504523) - -- this should reward 10 more to miner - checkPactResultFailure "forced error" "forced error" ferror - -- sender 00 second is down epsilon size costs - -- from [0,1] + 10 for error + 10 full gas debit during tx ~ 99999980 - checkPactResultSuccess "sender bal 1" sbal1 $ - assertEqual "sender bal 1" (pDecimal 99_999_979.6) - -- miner second is up 10 from error plus epsilon from [1,2,3] ~ 12 - checkPactResultSuccess "miner bal 1" mbal1 $ - assertEqual "miner bal 1" (pDecimal 12.904523) - test r = assertFailure $ "Expected 5 results, got: " ++ show r - - -checkLocalSuccess :: HasCallStack => (PactResult -> Assertion) -> Either String (CommandResult Hash) -> Assertion -checkLocalSuccess _ (Left e) = assertFailure $ "Expected success, got: " ++ show e -checkLocalSuccess test (Right cr) = test $ _crResult cr - -testAllowReadsLocalFails :: LocalTest -testAllowReadsLocalFails = (tx,test) - where - tx = buildCwCmd "testAllowReadsLocalFails" testVersion $ - set cbRPC (mkExec' "(read coin.coin-table \"sender00\")") $ - defaultCmd - test = checkLocalSuccess $ - checkPactResultFailure "testAllowReadsLocalFails" "Enforce non-upgradeability" - -testAllowReadsLocalSuccess :: LocalTest -testAllowReadsLocalSuccess = (tx,test) - where - tx = buildCwCmd "testAllowReadsLocalSuccess" testVersion $ - set cbRPC (mkExec' "(at 'balance (read coin.coin-table \"sender00\"))") $ - defaultCmd - test = checkLocalSuccess $ - checkPactResultSuccessLocal "testAllowReadsLocalSuccess" $ - assertEqual "sender00 bal" (pDecimal 100_000_000.0) - - --- -------------------------------------------------------------------------- -- --- Utils - -execTest - :: (Logger logger) - => ChainwebVersion - -> WithPactCtxSQLite logger tbl - -> TestRequest - -> TestTree -execTest v runPact request = _trEval request $ do - cmdStrs <- mapM getPactCode $ _trCmds request - trans <- mkCmds cmdStrs - results <- runPact $ (throwIfNoHistory =<<) $ - readFrom (Just $ ParentHeader $ genesisBlockHeader v cid) $ - SomeBlockM $ Pair - (execTransactions defaultMiner - trans (Pact4.EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled True) Nothing Nothing - >>= throwCommandInvalidError - ) - (error "Pact5") - - let outputs = V.toList $ snd <$> _transactionPairs results - return $ TestResponse - (zip (_trCmds request) (hashPact4TxLogs <$> outputs)) - (hashPact4TxLogs $ _transactionCoinbase results) - where - mkCmds cmdStrs = - fmap V.fromList $ forM (zip cmdStrs [0..]) $ \(code,n :: Int) -> - buildCwCmd ("1" <> sshow n) testVersion $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbGasPrice 0.01 $ - set cbTTL 1_000_000 $ - set cbRPC (mkExec code $ mkKeySetData "test-admin-keyset" [sender00]) $ - defaultCmd - -execTxsTest - :: (Logger logger) - => ChainwebVersion - -> WithPactCtxSQLite logger tbl - -> String - -> TxsTest - -> TestTree -execTxsTest v runPact name (trans',check) = testCase name (go >>= check) - where - go = do - trans <- trans' - results' <- tryAllSynchronous $ runPact $ (throwIfNoHistory =<<) $ - readFrom (Just $ ParentHeader $ genesisBlockHeader v cid) $ - SomeBlockM $ Pair - (execTransactions defaultMiner trans - (Pact4.EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled True) Nothing Nothing - >>= throwCommandInvalidError - ) - (error "Pact5") - case results' of - Right results -> Right <$> do - let outputs = V.toList $ snd <$> _transactionPairs results - tcode = _pNonce . Pact4.payloadObj . _cmdPayload - inputs = map (showPretty . tcode) $ V.toList trans - return $ TestResponse - (zip inputs (hashPact4TxLogs <$> outputs)) - (hashPact4TxLogs $ _transactionCoinbase results) - Left e -> return $ Left $ show e - -type LocalTest = (IO Pact4.Transaction,Either String (CommandResult Hash) -> Assertion) - -execLocalTest - :: (Logger logger, CanReadablePayloadCas tbl) - => WithPactCtxSQLite logger tbl - -> String - -> LocalTest - -> TestTree -execLocalTest runPact name (trans',check) = testCase name (go >>= check) - where - go = do - trans <- trans' - results' <- tryAny $ runPact $ - execLocal (Pact4.unparseTransaction trans) Nothing Nothing Nothing - case results' of - Right (preview _MetadataValidationFailure -> Just e) -> - return $ Left $ show e - Right (preview _LocalTimeout -> Just ()) -> - return $ Left "LocalTimeout" - Right (preview _LocalResultLegacy -> Just cr) -> do - Just decodedCr <- return $ Aeson.decode (TL.encodeUtf8 $ TL.fromStrict $ J.getJsonText cr) - return $ Right decodedCr - Right (preview _LocalResultWithWarns -> Just (cr, _)) -> do - Just decodedCr <- return $ Aeson.decode (TL.encodeUtf8 $ TL.fromStrict $ J.getJsonText cr) - return $ Right decodedCr - Right _ -> error "unknown local result" - Left e -> - return $ Left $ show e - -getPactCode :: TestSource -> IO Text -getPactCode (Code str) = return (pack str) -getPactCode (File filePath) = pack <$> readFile' (testPactFilesDir ++ filePath) - -checkSuccessOnly :: CommandResult Hash -> Assertion -checkSuccessOnly cr = case _crResult cr of - PactResult (Right _) -> return () - r -> assertFailure $ "Failure status returned: " ++ show r - -checkSuccessOnly' :: String -> IO (TestResponse TestSource) -> TestTree -checkSuccessOnly' msg f = testCase msg $ f >>= \case - TestResponse res@(_:_) _ -> checkSuccessOnly (snd $ last res) - TestResponse res _ -> fail (show res) -- TODO - - --- | A test runner for golden tests. --- -fileCompareTxLogs :: String -> IO (TestResponse TestSource) -> TestTree -fileCompareTxLogs label respIO = golden label $ do - resp <- respIO - return $ BL.fromStrict $ Y.encode - $ coinbase (_trCoinBaseOutput resp) - : (result <$> _trOutputs resp) - where - result (cmd, out) = object - [ "output" .= J.toJsonViaEncode (_crLogs out) - , "cmd" .= cmd - ] - coinbase out = object - [ "output" .= J.toJsonViaEncode (_crLogs out) - , "cmd" .= ("coinbase" :: String) - ] - - -_showValidationFailure :: IO () -_showValidationFailure = do - txs <- fmap V.singleton $ buildCwCmd "nonce" testVersion $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") $ - defaultCmd - let cr1 = CommandResult - { _crReqKey = RequestKey pactInitialHash - , _crTxId = Nothing - , _crResult = PactResult $ Right $ pString "hi" - , _crGas = 0 - , _crLogs = Just [encodeTxLog $ TxLog "Domain" "Key" (object [ "stuff" .= True ])] - , _crContinuation = Nothing - , _crMetaData = Nothing - , _crEvents = [] - } - outs1 = Transactions - { _transactionPairs = V.zip txs (V.singleton cr1) - , _transactionCoinbase = cr1 - } - miner = defaultMiner - header = genesisBlockHeader testVersion $ someChainId testVersion - pwo = toPayloadWithOutputs Pact4T miner outs1 - cr2 = set crGas 1 cr1 - outs2 = Transactions - { _transactionPairs = V.zip txs (V.singleton cr2) - , _transactionCoinbase = cr2 - } - r = validateHashes header (CheckablePayloadWithOutputs pwo) miner outs2 - - BL.putStrLn $ case r of - Left e -> J.encode $ J.text (sshow e) - Right x -> encode x diff --git a/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs b/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs deleted file mode 100644 index adb21fa389..0000000000 --- a/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs +++ /dev/null @@ -1,1833 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE ViewPatterns #-} - -module Chainweb.Test.Pact4.PactMultiChainTest -( tests -) where - -import Control.Concurrent.MVar -import Control.Lens hiding ((.=)) -import Control.Monad -import Control.Monad.Catch -import Control.Monad.Reader -import Data.Aeson (Value, object, (.=)) -import qualified Data.ByteString.Base64.URL as B64U -import qualified Data.HashMap.Strict as HM -import Data.IORef -import Data.List(isPrefixOf) -import Data.List qualified as List -import qualified Data.Map as M -import Data.Maybe -import Data.Set (Set) -import Data.Set qualified as Set -import qualified Data.Text as T -import qualified Data.Vector as V -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Pact.Types.Capability -import Pact.Types.Command -import Pact.Types.Continuation -import Pact.Types.Gas -import Pact.Types.Hash -import Pact.Types.Lang(_LString) -import Pact.Types.PactError -import Pact.Types.PactValue -import Pact.Types.Pretty -import Pact.Types.RPC -import Pact.Types.Runtime (PactEvent) -import Pact.Types.SPV -import Pact.Types.Term - -import Chainweb.BlockCreationTime -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Cut -import Chainweb.Mempool.Mempool -import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Types -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Pact4.TransactionExec (listErrMsg) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore (lookupPayloadWithHeight) -import Chainweb.SPV.CreateProof -import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import Chainweb.Utils -import Chainweb.Version -import Chainweb.WebPactExecutionService - - -testVersion :: ChainwebVersion -testVersion = slowForkingCpmTestVersion peterson - -cid :: ChainId -cid = unsafeChainId 9 - -- several tests in this file expect chain 9 - -data MultiEnv = MultiEnv - { _menvBdb :: !TestBlockDb - , _menvPact :: !WebPactExecutionService - , _menvPacts :: !(HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) - , _menvCompactedPacts :: !(HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) - , _menvMpa :: !(ChainMap (IORef MemPoolAccess)) - , _menvMiner :: !Miner - , _menvChainId :: !ChainId - } - -instance HasChainwebVersion MultiEnv where - _chainwebVersion = _chainwebVersion . _menvBdb - -makeLenses ''MultiEnv - -type PactTestM = ReaderT MultiEnv IO - -newtype MempoolCmdBuilder = MempoolCmdBuilder - { _mempoolCmdBuilder :: ChainId -> BlockCreationTime -> CmdBuilder - } - --- | Block filler. A 'Nothing' result means "skip this filler". -newtype MempoolBlock = MempoolBlock - { _mempoolBlock :: ChainId -> BlockCreationTime -> Maybe [MempoolCmdBuilder] - } - --- | Mempool with an ordered list of fillers. -newtype PactMempool = PactMempool - { _pactMempool :: [MempoolBlock] - } - deriving (Semigroup, Monoid) - - --- | Pair a builder with a test -data PactTxTest = PactTxTest - { _pttBuilder :: MempoolCmdBuilder - , _pttTest :: CommandResult Hash -> Assertion - } - -tests :: RocksDb -> TestTree -tests rdb = testGroup testName - [ test generousConfig "pact4coin3UpgradeTest" pact4coin3UpgradeTest - , test generousConfig "pact42UpgradeTest" pact42UpgradeTest - , test generousConfig "minerKeysetTest" minerKeysetTest - , test timeoutConfig "txTimeoutTest" txTimeoutTest - , test generousConfig "chainweb213Test" chainweb213Test - , test generousConfig "pact43UpgradeTest" pact43UpgradeTest - , test generousConfig "pact431UpgradeTest" pact431UpgradeTest - , test generousConfig "chainweb215Test" chainweb215Test - , test generousConfig "chainweb216Test" chainweb216Test - , test generousConfig "pact45UpgradeTest" pact45UpgradeTest - , test generousConfig "pact46UpgradeTest" pact46UpgradeTest - , test generousConfig "chainweb219UpgradeTest" chainweb219UpgradeTest - , test generousConfig "pactLocalDepthTest" pactLocalDepthTest - , test generousConfig "pact48UpgradeTest" pact48UpgradeTest - , test generousConfig "pact49UpgradeTest" pact49UpgradeTest - , test generousConfig "pact410UpgradeTest" pact410UpgradeTest - , test generousConfig "chainweb223Test" chainweb223Test - , test generousConfig "compactAndSyncTest" compactAndSyncTest - , test generousConfig "compactionCompactsUnmodifiedTables" compactionCompactsUnmodifiedTables - , quirkTest rdb - , test generousConfig "checkTransferCreate" checkTransferCreate - ] - - where - testName = "Chainweb.Test.Pact4.PactMultiChainTest" - -- This is way more than what is used in production, but during testing - -- we can be generous. - generousConfig = testPactServiceConfig { _pactNewBlockGasLimit = 300_000 } - timeoutConfig = testPactServiceConfig - { _pactNewBlockGasLimit = 300_000_000 - , _pactTxTimeLimit = Just 10_000 - } - - test pactConfig tname f = - withResourceT (mkTestBlockDb testVersion rdb) $ \bdbIO -> do - testCaseSteps tname $ \step -> do - bdb <- bdbIO - mempools <- onAllChains testVersion $ \_chain -> do - r <- newIORef mempty - return (r, delegateMemPoolAccess r) - let logger = hunitDummyLogger step - withWebPactExecutionServiceCompaction logger testVersion pactConfig bdb (snd <$> mempools) $ \(pact,pacts,_cPact,cPacts) -> - runReaderT f $ - MultiEnv bdb pact pacts cPacts (fst <$> mempools) noMiner cid - -minerKeysetTest :: PactTestM () -minerKeysetTest = do - - -- run past genesis, upgrades - runToHeight 24 - - -- run block 4 - local (set menvMiner badMiner) $ do - void runCut' - - -- run block 5 (fork for chainweb213) - r <- try $ runCut' - assertSatisfies "badMiner fails after fork" r $ \case - Left (CoinbaseFailure (Pact4CoinbaseFailure t)) -> "Invalid miner key" `T.isInfixOf` t - _ -> False - - where - - badMiner = Miner (MinerId "miner") $ MinerKeys $ mkKeySet ["bad-bad-bad"] "keys-all" - -txTimeoutTest :: PactTestM () -txTimeoutTest = do - -- get access to `enumerate` - runToHeight 20 - - -- we inline some of runBlockTest here because its assertions - -- don't make sense for tx timeout - let pts = - [ buildBasicGas 400 $ mkExec' "(+ 1 1)" - , buildBasicGas 10_000 $ mkExec' $ foldr - (\(_ :: Int) expr -> "(map (lambda (x) (+ x 1))" <> expr <> ")") - "(enumerate 1 1000)" - [1..6_000] -- make a huge nested tx - , buildBasicGas 400 $ mkExec' "(+ 2 2)" - ] - chid <- view menvChainId - - mempoolBadlistRef <- setPactMempool chid - $ PactMempool - $ List.singleton - $ MempoolBlock $ \_ _ -> pure pts - - blockBefore <- currentCut <&> (^?! (cutMap . ix chid)) - - liftIO $ do - badlisted <- readIORef mempoolBadlistRef - assertEqual "number of badlisted transactions is 0 before runCut'" 0 (Set.size badlisted) - - runCut' - - blockAfter <- currentCut <&> (^?! (cutMap . ix chid)) - - -- Ideally, we want to check the actual hash, but the DSL in this module - -- doesn't make that possible - liftIO $ do - badlisted <- readIORef mempoolBadlistRef - assertEqual "number of badlisted transactions is 1 after runCut'" 1 (Set.size badlisted) - assertEqual "block is still made despite timeout" (succ (view blockHeight blockBefore)) (view blockHeight blockAfter) - - rs <- txResults - liftIO $ assertEqual "number of transactions in block should be one (1) when second transaction times out" 1 (length rs) - -chainweb213Test :: PactTestM () -chainweb213Test = do - - -- run past genesis, upgrades - runToHeight 24 - - -- run block 25 - runBlockTest - [ PactTxTest buildModCmd1 $ - assertTxGas "Old gas cost" 47 - , PactTxTest (buildSimpleCmd' "(list 1 2 3)") $ - assertTxFailure "list failure 1_1" - listErrMsg - , PactTxTest buildDbMod $ - assertTxSuccess "mod db installs" $ - pString "TableCreated" - , PactTxTest (buildSimpleCmd' "(free.dbmod.fkeys)") $ - assertTxGas "fkeys gas cost 1" 205 - , PactTxTest (buildSimpleCmd' "(free.dbmod.ffolddb)") $ - assertTxGas "ffolddb gas cost 1" 206 - , PactTxTest (buildSimpleCmd' "(free.dbmod.fselect)") $ - assertTxGas "fselect gas cost 1" 206 - ] - - - -- run block 26 - runBlockTest - [ PactTxTest buildModCmd2 $ - assertTxGas "New gas cost" 60056 - , PactTxTest (buildSimpleCmd' "(list 1 2 3)") $ - assertTxFailure "list failure 2_1" - "Gas limit (50000) exceeded: 1000004" - , PactTxTest (buildSimpleCmd' "(free.dbmod.fkeys)") $ - assertTxGas "fkeys gas cost 2" 40005 - , PactTxTest (buildSimpleCmd' "(free.dbmod.ffolddb)") $ - assertTxGas "ffolddb gas cost 2" 40006 - , PactTxTest (buildSimpleCmd' "(free.dbmod.fselect)") $ - assertTxGas "fselect gas cost 2" 40006 - ] - - - where - - buildSimpleCmd' code = buildBasicGas 50000 - $ mkExec' code - buildModCmd1 = buildBasic - $ mkExec' $ mconcat ["(namespace 'free)", "(module mtest G (defcap G () true) (defun a () true))"] - buildModCmd2 = buildBasicGas 70000 - $ mkExec' $ mconcat ["(namespace 'free)", "(module mtest2 G (defcap G () true) (defun a () false))"] - buildDbMod = buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module dbmod G (defcap G () true)" - , " (defschema sch i:integer) (deftable tbl:{sch})" - , " (defun fkeys () (keys tbl))" - , " (defun ffolddb () (fold-db tbl (lambda (a b) true) (constantly true)))" - , " (defun fselect () (select tbl (constantly true))))" - , "(create-table tbl)" - ] - -pactLocalDepthTest :: PactTestM () -pactLocalDepthTest = do - runToHeight 53 - runBlockTest - [ PactTxTest (buildCoinXfer "(coin.transfer 'sender00 'sender01 1.0)") $ - assertTxGas "Coin transfer pre-fork" 1574 - ] - runBlockTest - [ PactTxTest (buildCoinXfer "(coin.transfer 'sender00 'sender01 1.0)") $ - assertTxGas "Coin post-fork" 1574 - ] - - runLocalWithDepth "0" (Just $ RewindDepth 0) cid getSender00Balance >>= \r -> - checkLocalResult r $ assertTxSuccess "Should get the current balance" (pDecimal 99_999_997.6852) - - -- checking that `Just $ RewindDepth 0` has the same behaviour as `Nothing` - runLocalWithDepth "1" Nothing cid getSender00Balance >>= \r -> - checkLocalResult r $ assertTxSuccess "Should get the current balance as well" (pDecimal 99_999_997.6852) - - runLocalWithDepth "2" (Just $ RewindDepth 1) cid getSender00Balance >>= \r -> - checkLocalResult r $ assertTxSuccess "Should get the balance one block before" (pDecimal 99_999_998.8426) - - runLocalWithDepth "3" (Just $ RewindDepth 2) cid getSender00Balance >>= \r -> - checkLocalResult r $ assertTxSuccess "Should get the balance two blocks before" (pDecimal 100_000_000) - - -- the genesis depth - runLocalWithDepth "5" (Just $ RewindDepth 55) cid getSender00Balance >>= \r -> - checkLocalResult r $ assertTxSuccess "Should get the balance at the genesis block" (pDecimal 100000000) - - -- local rewinding past genesis should be the same as rewinding to genesis - runLocalWithDepth "6" (Just $ RewindDepth 56) cid getSender00Balance >>= \r -> - checkLocalResult r $ assertTxSuccess "Should get the balance at the genesis block" (pDecimal 100000000) - - where - checkLocalResult r checkResult = case r of - Right (preview _Pact4LocalResultLegacy -> Just cr) -> checkResult cr - res -> liftIO $ assertFailure $ "Expected LocalResultLegacy, but got: " ++ show res - getSender00Balance = set cbGasLimit 700 $ set cbRPC (mkExec' "(coin.get-balance \"sender00\")") $ defaultCmd - buildCoinXfer code = buildBasic' - (set cbSigners [mkEd25519Signer' sender00 coinCaps] . set cbGasLimit 3000) - $ mkExec' code - where - coinCaps = [ mkGasCap, mkTransferCap "sender00" "sender01" 1.0 ] - -pact45UpgradeTest :: PactTestM () -pact45UpgradeTest = do - runToHeight 53 -- 2 before fork - runBlockTest - [ PactTxTest - (buildBasicGas 70_000 $ tblModule "tbl") $ - assertTxSuccess "mod53 table created" $ pString "TableCreated" - ] - runBlockTest - [ PactTxTest (buildSimpleCmd "(enforce false 'hi)") $ - assertTxFailure "Should fail with the error from the enforce" "hi" - , PactTxTest (buildSimpleCmd "(enforce true (format \"{}-{}\" [12345, 657859]))") $ - assertTxGas "Enforce pre-fork evaluates the string with gas" 25 - , PactTxTest (buildSimpleCmd "(enumerate 0 10) (str-to-list 'hi) (make-list 10 'hi)") $ - assertTxGas "List functions pre-fork gas" 10 - , PactTxTest - (buildBasicGas 70_000 $ tblModule "Tbl") $ - assertTxSuccess "mod53 table update succeeds" $ pString "TableCreated" - , PactTxTest (buildCoinXfer "(coin.transfer 'sender00 'sender01 1.0)") $ - assertTxGas "Coin transfer pre-fork" 1574 - ] - -- chainweb217 fork - runBlockTest - [ PactTxTest (buildSimpleCmd "(+ 1 \'clearlyanerror)") $ - assertTxFailure "Should replace tx error with empty error" "" - , PactTxTest (buildSimpleCmd "(enforce true (format \"{}-{}\" [12345, 657859]))") $ - assertTxGas "Enforce post fork does not eval the string" (15 + coinTxBuyTransferGas) - , PactTxTest (buildSimpleCmd "(enumerate 0 10) (str-to-list 'hi) (make-list 10 'hi)") $ - assertTxGas "List functions post-fork change gas" (40 + coinTxBuyTransferGas) - , PactTxTest - (buildBasicGas 70_000 $ tblModule "tBl") $ - assertTxFailure "mod53 table update fails after fork" "" - , PactTxTest (buildCoinXfer "(coin.transfer 'sender00 'sender01 1.0)") $ - assertTxGas "Coin post-fork" 700 - ] - -- run local to check error - lr <- runLocal "0" cid $ set cbGasLimit 70_000 $ set cbRPC (tblModule "tBl") $ defaultCmd - assertLocalFailure "mod53 table update error" - (pretty $ show $ PactDuplicateTableError "free.mod53_tBl") - lr - - where - tblModule tn = mkExec' $ T.replace "$TABLE$" tn - " (namespace 'free) \ - \ (module mod53 G \ - \ (defcap G () true) \ - \ (defschema sch s:string) \ - \ (deftable $TABLE$:{sch})) \ - \ (create-table $TABLE$)" - buildCoinXfer code = buildBasic' - (set cbSigners [mkEd25519Signer' sender00 coinCaps] . set cbGasLimit 3000) - $ mkExec' code - where - coinCaps = [ mkGasCap, mkTransferCap "sender00" "sender01" 1.0 ] - coinTxBuyTransferGas = 206 - buildSimpleCmd code = buildBasicGas 3000 - $ mkExec' code - -runLocal :: T.Text -> ChainId -> CmdBuilder -> PactTestM (Either PactException LocalResult) -runLocal nonce cid' cmd = runLocalWithDepth nonce Nothing cid' cmd - -runLocalWithDepth :: T.Text -> Maybe RewindDepth -> ChainId -> CmdBuilder -> PactTestM (Either PactException LocalResult) -runLocalWithDepth nonce depth cid' cmd = do - pact <- getPactService cid' - cwCmd <- buildCwCmd nonce testVersion cmd - liftIO $ try @_ @PactException $ _pactLocal pact Nothing Nothing depth (Pact4.unparseTransaction cwCmd) - -getPactService :: ChainId -> PactTestM PactExecutionService -getPactService cid' = do - HM.lookup cid' <$> view menvPacts >>= \case - Just (_, pact) -> return pact - Nothing -> liftIO $ assertFailure $ "No pact service found at chain id " ++ show cid' - -assertLocalFailure - :: (HasCallStack, MonadIO m) - => String - -> Doc - -> Either PactException LocalResult - -> m () -assertLocalFailure s d lr = - liftIO $ assertEqual s (Just d) $ - lr ^? _Right . _Pact4LocalResultLegacy . crResult . to _pactResult . _Left . to peDoc - -assertLocalSuccess - :: (HasCallStack, MonadIO m) - => String - -> PactValue - -> Either PactException LocalResult - -> m () -assertLocalSuccess s pv lr = - liftIO $ assertEqual s (Just pv) $ - lr ^? _Right . _Pact4LocalResultLegacy . crResult . to _pactResult . _Right - -pact43UpgradeTest :: PactTestM () -pact43UpgradeTest = do - - -- run past genesis, upgrades - runToHeight 29 - - runBlockTest - [ PactTxTest buildMod $ - assertTxGas "Old gas cost" 120323 - , PactTxTest buildModPact $ - assertTxFailure - "Should not resolve new pact native: continue" - "Cannot resolve \"continue\"" - , PactTxTest (buildSimpleCmd "(create-principal (read-keyset 'k))") $ - assertTxFailure - "Should not resolve new pact native: create-principal" - "Cannot resolve create-principal" - , PactTxTest (buildSimpleCmd "(validate-principal (read-keyset 'k) \"k:368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca\")") $ - assertTxFailure - "Should not resolve new pact natives: validate-principal" - "Cannot resolve validate-principal" - , PactTxTest (buildXSend []) $ - assertTxSuccess "xsend success" $ - pObject [ ("amount",pDecimal 0.0123) - , ("receiver",pString "sender00") - , ("receiver-guard",pKeySet sender00Ks) - ] - ] - - tx30_4 <- txResult 4 - - runBlockTest - [ PactTxTest buildMod $ - assertTxGas "Old gas cost" 120287 - , PactTxTest buildModPact $ - assertTxSuccess - "Should resolve continue in a module defn" - (pString "Loaded module free.nestedMod, hash fDd0G7zvGar3ax2q0I0F9dISRq7Pjop5rUXOeokNIOU") - , PactTxTest (buildSimpleCmd "(free.modB.chain)") $ - assertTxSuccess - "Should resolve names properly post-fork" - -- Note: returns LDecimal because of toPactValueLenient in interpret - (pDecimal 11) - , PactTxTest (buildSimpleCmd "(free.modB.get-test)") $ - assertTxSuccess - "Should resolve names properly post-fork" - (pString "hello") - , PactTxTest (buildSimpleCmd "(create-principal (read-keyset 'k))") $ - assertTxSuccess - "Should resolve create-principal properly post-fork" - (pString "k:368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca") - , PactTxTest (buildSimpleCmd "(validate-principal (read-keyset 'k) \"k:368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca\")") $ - assertTxSuccess - "Should resolve validate-principal properly post-fork" - (pBool True) - ] - - resetMempool - runToHeight 33 - - xproof <- buildXProof cid 30 4 tx30_4 - - withChain chain0 $ runBlockTest - [ PactTxTest (buildXReceive xproof) $ - assertTxSuccess "xreceive success" (pString "Write succeeded") - ] - - where - - buildSimpleCmd code = buildBasicGas 1000 - $ mkExec code - $ mkKeySetData "k" [sender00] - buildModPact = buildBasicGas 70000 - $ mkExec' (mconcat - [ "(namespace 'free)" - , "(module nestedMod G" - , " (defcap G () true)" - , " (defpact test:string () (step \"1\") (step \"2\") (step \"3\"))" - , " (defpact test-nested:string () (step (test)) (step (continue (test))) (step (continue (test))))" - , ")" - ]) - buildMod = buildBasicGas 130000 - $ mkExec (mconcat - [ "(namespace 'free)" - , "(module modA G" - , " (defcap G () true)" - , " (defun func:integer (x:integer) (+ 1 x))" - , " (defun func2:integer (x:integer) (+ (func x) (func x)))" - , " (defconst test:string \"hi\")" - , ")" - , "(module modB G" - , " (defcap G () true)" - , " (defun chain:integer () (modA.func 10))" - , " (defconst test:string \"hello\")" - , " (defun get-test() test)" - , ")" - ]) - $ mkKeySetData "k" [sender00] - -chainweb215Test :: PactTestM () -chainweb215Test = do - - -- run past genesis, upgrades - runToHeight 30 -- 1->30 - - -- execute pre-fork xchain transfer (blocc0) - runBlockTest - [ PactTxTest xsend $ \cr -> do - evs <- mkSendEvents 0.0416 v4Hash - assertTxEvents "Transfer events @ block 31" evs cr - ] - send0 <- txResult 0 - - -- run past v5 upgrade, build proof of pre-fork xchain for tx31_0, save cut - resetMempool - runToHeight 34 - savedCut <- currentCut - runToHeight 41 - - xproof <- buildXProof cid 31 0 send0 - - let blockSend42 = - [ PactTxTest xsend $ \cr -> do - evs <- mkSendEvents 0.0419 v5Hash - assertTxEvents "Transfer events @ block 42 - post-fork send" evs cr - ] - blockRecv42 = - [ PactTxTest (buildXReceive xproof) $ \cr -> do - evs <- mkRecdEvents v5Hash v4Hash - assertTxEvents "Transfer events @ block 42" evs cr - ] - - void $ setPactMempool cid $ PactMempool - [ testsToBlock blockSend42 ] - - void $ setPactMempool chain0 $ PactMempool - [ testsToBlock blockRecv42 ] - - runCut' - withChain cid $ testCurrentBlock blockSend42 - withChain chain0 $ testCurrentBlock blockRecv42 - - send1 <- withChain cid $ txResult 0 - - currCut <- currentCut - - -- rewind to saved cut 43 - syncTo savedCut - runToHeight 43 - - -- resume on original cut - syncTo currCut - - -- run until post-fork xchain proof exists - resetMempool - runToHeight 50 - savedCut1 <- currentCut - runToHeight 52 - - xproof1 <- buildXProof cid 42 0 send1 - - withChain chain0 $ runBlockTest - [ PactTxTest (buildXReceive xproof1) $ \cr -> do - evs <- mkRecdEvents v5Hash v5Hash - assertTxEvents "Transfer events @ block 53" evs cr - ] - - -- rewind to saved cut 50 - syncTo savedCut1 - runToHeight 53 - - where - xsend = buildXSend - [ mkGasCap - , mkXChainTransferCap "sender00" "sender00" 0.0123 "0" - ] - - v4Hash = "BjZW0T2ac6qE_I5X8GE4fal6tTqjhLTC7my0ytQSxLU" - v5Hash = "rE7DU8jlQL9x_MPYuniZJf5ICBTAEHAIFQCB4blofP4" - - mkSendEvents cbCost h = sequence - [ mkTransferEvent "sender00" "NoMiner" cbCost "coin" h - , mkTransferXChainEvent "sender00" "sender00" 0.0123 "coin" h "0" - , mkTransferEvent "sender00" "" 0.0123 "coin" h - , mkXYieldEvent "sender00" "sender00" 0.0123 sender00Ks "pact" h "0" "0" - ] - - mkRecdEvents h h' = sequence - [ mkTransferEvent "sender00" "NoMiner" 0.0249 "coin" h - , mkTransferEvent "" "sender00" 0.0123 "coin" h - , mkTransferXChainRecdEvent "" "sender00" 0.0123 "coin" h (toText cid) - , mkXResumeEvent "sender00" "sender00" 0.0123 sender00Ks "pact" h' (toText cid) "0" - ] - -pact431UpgradeTest :: PactTestM () -pact431UpgradeTest = do - - -- run past genesis, upgrades - runToHeight 34 - - -- run block 35, pre fork - runBlockTest - [ PactTxTest describeModule $ - assertTxSuccess "describe-module legacy success" - $ pBool True - , PactTxTest isPrincipal $ - assertTxFailure "Should not resolve new pact native: is-principal" - "Cannot resolve is-principal" - , PactTxTest typeOfPrincipal $ - assertTxFailure "Should not resolve new pact native: typeof-principal" - "Cannot resolve typeof-principal" - , PactTxTest enforcePactVersion $ - assertTxSuccess "Enforce pact version passes pre-fork" - $ pBool True - , PactTxTest pactVersion $ - assertTxSuccess "Pact version is 4.2.1 for compat pre-fork" - $ pString "4.2.1" - ] - - -- run block 36, post fork - runBlockTest - [ PactTxTest describeModule $ - assertTxFailure "Should fail to execute describe-module" - "Operation only permitted in local execution mode" - , PactTxTest isPrincipal $ - assertTxSuccess "Should resolve new pact native: is-principal" - $ pBool True - , PactTxTest typeOfPrincipal $ - assertTxSuccess "Should resolve new pact native: typeof-principal" - $ pString "k:" - , PactTxTest enforcePactVersion $ - assertTxFailure "Should fail to execute enforce-pact-version" - "Operation only permitted in local execution mode" - , PactTxTest pactVersion $ - assertTxFailure "Should fail to execute pact-version" - "Operation only permitted in local execution mode" - ] - - - where - isPrincipal = - buildSimpleCmd "(is-principal (create-principal (read-keyset 'k)))" - typeOfPrincipal = - buildSimpleCmd "(typeof-principal (create-principal (read-keyset 'k)))" - enforcePactVersion = - buildSimpleCmd "(enforce-pact-version \"4.2.1\")" - pactVersion = - buildSimpleCmd "(pact-version)" - buildSimpleCmd code = buildBasicGas 1000 - $ mkExec code - $ mkKeySetData "k" [sender00] - describeModule = buildBasicGas 100000 - $ mkExec' (mconcat - [ "(namespace 'free)" - , "(module mod G" - , " (defcap G () true)" - , " (defun f () true)" - , ")" - , "(describe-module \"free.mod\") true" - ]) - - -pact42UpgradeTest :: PactTestM () -pact42UpgradeTest = do - - -- run past genesis, upgrades - runToHeight 3 - - -- run block 4 - runBlockTest - [ PactTxTest buildNewNatives420FoldDbCmd $ - assertTxFailure - "Should not resolve new pact natives" - "Cannot resolve fold-db" - , PactTxTest buildNewNatives420ZipCmd $ - assertTxFailure - "Should not resolve new pact natives" - "Cannot resolve zip" - , PactTxTest buildFdbCmd $ - assertTxSuccess - "Load fdb module" - (pString "Write succeeded") - ] - - cbResult >>= assertTxEvents "Coinbase events @ block 4" [] - - -- run block 5 - - -- runBlockTest - -- [ PactTxTest buildNewNatives420FoldDbCmd $ - -- assertTxSuccess - -- "Should resolve fold-db pact native" $ - -- pList [pObject [("a", pInteger 1),("b",pInteger 1)] - -- ,pObject [("a", pInteger 2),("b",pInteger 2)]] - -- , PactTxTest buildNewNatives420ZipCmd $ - -- assertTxSuccess - -- "Should resolve zip pact native" $ - -- pList $ pInteger <$> [5,7,9] - -- ] - - cbResult >>= assertTxEvents "Coinbase events @ block 5" [] - - where - - buildFdbCmd = buildBasic - $ mkExec' $ mconcat ["(namespace 'free)", moduleDeclaration, inserts] - where - moduleDeclaration = - "(module fdb G (defcap G () true) (defschema fdb-test a:integer b:integer) (deftable fdb-tbl:{fdb-test}))" - inserts = - mconcat - [ - "(create-table free.fdb.fdb-tbl)" - , "(insert free.fdb.fdb-tbl 'b {'a:2, 'b:2})" - , "(insert free.fdb.fdb-tbl 'd {'a:4, 'b:4})" - , "(insert free.fdb.fdb-tbl 'c {'a:3, 'b:3})" - , "(insert free.fdb.fdb-tbl 'a {'a:1, 'b:1})" - ] - - buildNewNatives420FoldDbCmd = buildBasic - $ mkExec' - "(let* ((qry (lambda (k o) (< k \"c\"))) (consume (lambda (k o) o))) (fold-db free.fdb.fdb-tbl (qry) (consume)))" - - - buildNewNatives420ZipCmd = buildBasic - $ mkExec' "(zip (+) [1 2 3] [4 5 6])" - -chainweb216Test :: PactTestM () -chainweb216Test = do - -- This test should handles for format and try as well as - -- keyset format changes and disallowances across fork boundaries. - -- - -- Namely, to test keys properly, we should: - -- - -- 1. Make sure keys defined before and after - -- fork boundaries pass enforcement. - -- - -- 2. Keys defined after the fork are only - -- definable if a namespace is present. - -- - - -- run past genesis, upgrades - runToHeight 52 - - runBlockTest - [ PactTxTest (buildSimpleCmd formatGas) $ - assertTxGas "Pre-fork format gas" 11 - , PactTxTest (buildSimpleCmd tryGas) $ - assertTxGas "Pre-fork try" 9 - , PactTxTest (buildSimpleCmd defineNonNamespacedPreFork) $ - assertTxSuccess - "Should pass when defining a non-namespaced keyset" - (pBool True) - , PactTxTest (buildSimpleCmd defineNamespacedPreFork) $ - -- Note, keysets technically are not namespaced pre-fork, the new parser isn't applied - assertTxSuccess - "Should pass when defining a \"namespaced\" keyset pre fork" - (pBool True) - ] - - runBlockTest - [ PactTxTest (buildSimpleCmd formatGas) $ - assertTxGas "Post-fork format gas increase" 38 - , PactTxTest (buildSimpleCmd tryGas) $ - assertTxGas "Post-fork try should charge a bit more gas" 10 - , PactTxTest (buildSimpleCmd defineNonNamespacedPostFork1) $ - assertTxFailure - "Should fail when defining a non-namespaced keyset post fork" - "Mismatching keyset namespace" - , PactTxTest (buildSimpleCmd defineNamespacedPostFork) $ - assertTxSuccess - "Pass when defining a namespaced keyset post fork" - (pBool True) - , PactTxTest (buildSimpleCmd enforceNamespacedFromPreFork) $ - assertTxSuccess - "Should work in enforcing a namespaced keyset created prefork" - (pBool True) - , PactTxTest (buildSimpleCmd enforceNonNamespacedFromPreFork) $ - assertTxSuccess - "Should work in enforcing a non-namespaced keyset created prefork" - (pBool True) - , PactTxTest (buildSimpleCmd defineNonNamespacedPostFork2) $ - assertTxFailure - "Should fail in defining a keyset outside a namespace" - "Cannot define a keyset outside of a namespace" - , PactTxTest buildModCommand $ - assertTxSuccess - "Should succeed in deploying a module guarded by a namespaced keyset" - (pString "Loaded module free.m1, hash nOHaU-gPtmZTj6ZA3VArh-r7LEiwVUMN_RLJeW2hNv0") - , PactTxTest (buildSimpleCmd rotateLegacyPostFork) $ - assertTxSuccess - "Should succeed in rotating and enforcing a legacy keyset" - (pBool True) - , PactTxTest (buildSimpleCmd rotateNamespacedPostFork) $ - assertTxSuccess - "Should succeed in rotating and enforcing a namespaced keyset" - (pBool True) - ] - - runBlockTest - [ PactTxTest (buildSimpleCmd "(free.m1.f)") $ - assertTxSuccess - "Should call a module with a namespaced keyset correctly" - (pDecimal 1) - , PactTxTest (buildSimpleCmd "(^ \ - \ 15.034465284692086701747761395233132973944448512421004399685858401206740385711739229018307610943234609057822959334669087436253689423614206061665462283698768757790600552385430913941421707844383369633809803959413869974997415115322843838226312287673293352959835 \ - \ 3.466120406090666777582519661568003549307295836842780244500133445635634490670936927006970368136648330889718447039413255137656971927890831071689768359173260960739254160211017410322799793419223796996260056081828170546988461285168124170297427792046640116184356)") $ - assertTxSuccess - "musl exponentiation regression" - (pDecimal 12020.67042599064370733685791492462158203125) - ] - - - where - defineNonNamespacedPreFork = mconcat - [ "(define-keyset \'k123)" - , "(enforce-guard (keyset-ref-guard \'k123))" - ] - defineNamespacedPreFork = mconcat - [ "(define-keyset \"free.k123\")" - , "(enforce-guard (keyset-ref-guard \"free.k123\"))" - ] - defineNonNamespacedPostFork1 = mconcat - [ "(namespace 'free)" - , "(define-keyset \'k456)" - ] - defineNonNamespacedPostFork2 = mconcat - [ "(define-keyset \'k456)" - ] - defineNamespacedPostFork = mconcat - [ "(namespace 'free)" - , "(define-keyset \"free.k456\")" - , "(enforce-guard (keyset-ref-guard \"free.k456\"))" - ] - rotateLegacyPostFork = mconcat - [ "(namespace 'free)" - , "(define-keyset \"k123\" (read-keyset 'k456))" - , "(enforce-guard (keyset-ref-guard \"k123\"))" - ] - rotateNamespacedPostFork = mconcat - [ "(namespace 'free)" - , "(define-keyset \"free.k123\" (read-keyset 'k456))" - , "(enforce-guard (keyset-ref-guard \"free.k123\"))" - ] - defineModulePostFork = mconcat - [ "(namespace 'free)" - , "(module m1 \"free.k456\" (defun f () 1))" - ] - enforceNamespacedFromPreFork = "(enforce-guard (keyset-ref-guard \"free.k123\"))" - enforceNonNamespacedFromPreFork = "(enforce-guard (keyset-ref-guard \"k123\"))" - tryGas = "(try (+ 1 1) (enforce false \"abc\"))" - formatGas = "(format \"{}-{}\" [1234567, 890111213141516])" - - - buildModCommand = buildBasicGas 70000 - $ mkExec' defineModulePostFork - - buildSimpleCmd code = buildBasicGas 10000 - $ mkExec code - $ object - [ "k123" .= map fst [sender00] - , "k456" .= map fst [sender00] - , "free.k123" .= map fst [sender00] - , "free.k456" .= map fst [sender00]] - - -pact46UpgradeTest :: PactTestM () -pact46UpgradeTest = do - - -- run past genesis, upgrades - runToHeight 58 - - -- Note: no error messages on-chain, so the error message is empty - runBlockTest - [ PactTxTest pointAddTx $ - assertTxFailure - "Should not resolve new pact native: point-add" - "" - , PactTxTest scalarMulTx $ - assertTxFailure - "Should not resolve new pact native: scalar-mult" - "" - , PactTxTest pairingTx $ - assertTxFailure - "Should not resolve new pact native: pairing-check" - "" - ] - - runBlockTest - [ PactTxTest pointAddTx $ - assertTxSuccess - "Should resolve point-add properly post-fork" - (pObject [("x", pInteger 1368015179489954701390400359078579693043519447331113978918064868415326638035) - , ("y", pInteger 9918110051302171585080402603319702774565515993150576347155970296011118125764)]) - , PactTxTest scalarMulTx $ - assertTxSuccess - "Should resolve scalar-mult properly post-fork" - (pObject [("x", pInteger 1) - , ("y", pInteger 2)]) - , PactTxTest pairingTx $ - assertTxSuccess - "Should resolve scalar-mult properly post-fork" - (pBool True) - ] - where - pointAddTx = buildBasicGas 10000 - $ mkExec' (mconcat - [ "(point-add 'g1 {'x:1, 'y:2} {'x:1, 'y:2})" - ]) - scalarMulTx = buildBasicGas 10000 - $ mkExec' (mconcat - [ "(scalar-mult 'g1 {'x: 1, 'y: 2} 1)" - ]) - pairingTx = buildBasicGas 30000 - $ mkExec' (mconcat - [ "(pairing-check [{'x: 1, 'y: 2}] [{'x:[0, 0], 'y:[0, 0]}])" - ]) - - -chainweb219UpgradeTest :: PactTestM () -chainweb219UpgradeTest = do - - -- run past genesis, upgrades - runToHeight 69 - - -- Block 70, pre-fork, no errors on chain - runBlockTest - [ PactTxTest addErrTx $ - assertTxFailure - "Should fail on + with incorrect argument types" - "" - , PactTxTest mapErrTx $ - assertTxFailure - "Should fail on map with the second argument not being a list" - "" - , PactTxTest nativeDetailsTx $ - assertTxSuccessWith - "Should print native details on chain" - (pString nativeDetailsMsg) - (over (_PLiteral . _LString) (T.take (T.length nativeDetailsMsg))) - , PactTxTest fnDetailsTx $ - assertTxSuccessWith - "Should display function preview string" - (pString fnDetailsMsg) - (over (_PLiteral . _LString) (T.take (T.length fnDetailsMsg))) - , PactTxTest decTx $ - assertTxFailure - "Should not resolve new pact native: dec" - -- Should be cannot resolve dec, but no errors pre-fork. - "" - , PactTxTest runIllTypedFunction $ - assertTxSuccess - "User function return value types should not be checked before the fork" - (pDecimal 1.0) - , PactTxTest tryReadString $ - assertTxFailure - "read-* errors are not recoverable before the fork" - "" - , PactTxTest tryReadInteger $ - assertTxFailure - "read-* errors are not recoverable before the fork" - "" - , PactTxTest tryReadKeyset $ - assertTxFailure - "read-* errors are not recoverable before the fork" - "" - , PactTxTest tryReadMsg $ - assertTxFailure - "read-* errors are not recoverable before the fork" - "" - ] - - -- Block 71, post-fork, errors should return on-chain but different - runBlockTest - [ PactTxTest addErrTx $ - assertTxFailureWith - "Should error with the argument types in +" - (isPrefixOf "Invalid arguments in call to +, received arguments of type" . show) - , PactTxTest mapErrTx $ - assertTxFailure - "Should fail on map with the second argument not being a list" - "map: expecting list, received argument of type: string" - , PactTxTest nativeDetailsTx $ - assertTxFailure - "Should print native details on chain" - "Cannot display native function details in non-repl context" - , PactTxTest fnDetailsTx $ - assertTxFailure - "Should not display function preview string" - "Cannot display function details in non-repl context" - , PactTxTest decTx $ - assertTxSuccess - "Should resolve new pact native: dec" - (pDecimal 1) - , PactTxTest runIllTypedFunction $ - assertTxSuccess - "User function return value types should not be checked after the fork" - (pDecimal 1.0) - , PactTxTest tryReadString $ - assertTxSuccess - "read-* errors are recoverable after the fork" - (pDecimal 1.0) - , PactTxTest tryReadInteger $ - assertTxSuccess - "read-* errors are recoverable after the fork" - (pDecimal 1.0) - , PactTxTest tryReadKeyset $ - assertTxSuccess - "read-* errors are recoverable after the fork" - (pDecimal 1.0) - , PactTxTest tryReadMsg $ - assertTxSuccess - "read-* errors are recoverable after the fork" - (pDecimal 1.0) - ] - - -- run local on RTC check - lr <- runLocal "0" cid $ set cbGasLimit 70_000 $ set cbRPC runIllTypedFunctionExec $ defaultCmd - assertLocalSuccess - "User function return value types should not be checked in local" - (pDecimal 1.0) - lr - - where - addErrTx = buildBasicGas 10_000 - $ mkExec' (mconcat - [ "(+ 1 \"a\")" - ]) - mapErrTx = buildBasicGas 10_000 - $ mkExec' (mconcat - [ "(map (+ 1) \"a\")" - ]) - decTx = buildBasicGas 10_000 - $ mkExec' "(dec 1)" - nativeDetailsMsg = "native `=` Compare alike terms for equality" - nativeDetailsTx = buildBasicGas 10_000 - $ mkExec' (mconcat - [ "=" - ]) - fnDetailsMsg = "(defun coin.transfer:string" - fnDetailsTx = buildBasicGas 10_000 - $ mkExec' (mconcat - [ "coin.transfer" - ]) - runIllTypedFunction = buildBasicGas 70_000 runIllTypedFunctionExec - runIllTypedFunctionExec = mkExec' (mconcat - [ "(namespace 'free)" - , "(module m g (defcap g () true)" - , " (defun foo:string () 1))" - , "(m.foo)" - ]) - tryReadInteger = buildBasicGas 1000 - $ mkExec' "(try 1 (read-integer \"somekey\"))" - tryReadString = buildBasicGas 1000 - $ mkExec' "(try 1 (read-string \"somekey\"))" - tryReadKeyset = buildBasicGas 1000 - $ mkExec' "(try 1 (read-keyset \"somekey\"))" - tryReadMsg = buildBasicGas 1000 - $ mkExec' "(try 1 (read-msg \"somekey\"))" - -pact48UpgradeTest :: PactTestM () -pact48UpgradeTest = do - runToHeight 83 - - -- run block 84 (before the pact48 fork) - runBlockTest - [ PactTxTest runConcat $ assertTxGas "Old concat gas cost" 222 - , PactTxTest runFormat $ assertTxGas "Old format gas cost" 229 - , PactTxTest runReverse $ assertTxGas "Old reverse gas cost" 4223 - ] - - -- run block 85 (after the pact 48 fork) - runBlockTest - [ PactTxTest runConcat $ assertTxGas "New concat gas cost" 271 - , PactTxTest runFormat $ assertTxGas "New format gas cost" 224 - , PactTxTest runReverse $ assertTxGas "New reverse gas cost" 4263 - ] - - where - runConcat = buildBasicGas 10000 $ mkExec' "(concat [\"hello\", \"world\"])" - runFormat = buildBasicGas 10000 $ mkExec' "(format \"{}\" [1,2,3])" - runReverse = buildBasicGas 10000 $ mkExec' "(reverse (enumerate 1 4000))" - -pact49UpgradeTest :: PactTestM () -pact49UpgradeTest = do - runToHeight 97 - - -- Run block 98. - -- WebAuthn is not yet a valid PPK scheme, so this transaction - -- is not valid for insertion into the mempool. - expectInvalid - "WebAuthn should not yet be supported" - [ webAuthnSignedTransaction - ] - - -- run block 99 (before the pact-4.9 fork) - runBlockTest - [ PactTxTest base64DecodeNonCanonical $ - assertTxSuccess - "Non-canonical messages decode before pact-4.9" - (pString "d") - , PactTxTest base64DecodeBadPadding $ assertTxFailure "decoding illegally padded string" "Could not decode string: Base64URL decode failed: invalid padding near offset 16" - ] - - -- run block 100 (after the pact-4.9 fork) - runBlockTest - [ PactTxTest base64DecodeNonCanonical $ - assertTxFailure "decoding non-canonical message" "Could not decode string: Could not base64-decode string" - , PactTxTest base64DecodeBadPadding $ assertTxFailure "decoding illegally padded string" "Could not decode string: Could not base64-decode string" - - , PactTxTest webAuthnSignedTransaction $ - assertTxSuccess - "WebAuthn signatures should be valid now" - (pDecimal 3) - - ] - - where - webAuthnSignedTransaction = buildBasicGasWebAuthnBareSigner 1000 $ mkExec' "(+ 1 2)" - base64DecodeNonCanonical = buildBasicGas 10000 $ mkExec' "(base64-decode \"ZE==\")" - base64DecodeBadPadding = buildBasicGas 10000 $ mkExec' "(base64-decode \"aGVsbG8gd29ybGQh%\")" - -pact410UpgradeTest :: PactTestM () -pact410UpgradeTest = do - runToHeight 110 - - expectInvalid - "WebAuthn prefixed keys should not yet be supported in signatures" - [ prefixedSigned - ] - runBlockTest [ - PactTxTest poseidonTx $ - assertTxFailure "Should not resolve new pact native: poseidon-hash-hack-a-chain" - "Cannot resolve poseidon-hash-hack-a-chain" - ] - - runToHeight 120 - runBlockTest - [ PactTxTest prefixedSigned $ - assertTxSuccess - "Prefixed WebAuthn signers should be legal" - (pDecimal 1) - - , PactTxTest prefixedSignerPrefixedKey $ - assertTxSuccess - "WebAuthn prefixed keys should be enforceable with prefixed signers" - (pBool True) - - , PactTxTest bareSignerPrefixedKey $ - assertTxFailure' - "WebAuthn prefixed keys should not be enforceable with bare signers" - "Keyset failure (keys-all): [WEBAUTHN...]" - - , PactTxTest definePrefixedKeySet $ - assertTxSuccess - "WebAuthn prefixed keys should be enforceable after defining them" - (pBool True) - - , PactTxTest prefixedSignerPrefixedKeyCreatePrincipal $ - assertTxSuccess - "WebAuthn prefixed keys in a keyset should be possible to make into w: principals" - (pString "w:XrscJ2X8aFxFF7oilzFyjQuA1mUN8jgwdxbAd8rt21M:keys-all") - - , PactTxTest prefixedSignerPrefixedKeyValidatePrincipal $ - assertTxSuccess - "WebAuthn prefixed keys in a keyset should be possible to make into *valid* w: principals" - (pBool True) - - , PactTxTest prefixedSignerBareKey $ - assertTxFailure' - "WebAuthn bare keys should throw an error when read" - "Invalid keyset" - - , PactTxTest invalidPrefixedKey $ - assertTxFailure' - "Invalid WebAuthn prefixed keys should throw an error when read" - "Invalid keyset" - - , PactTxTest poseidonTx $ - assertTxSuccess "Should resolve new pact native: poseidon-hash-hack-a-chain" - (pDecimal 18586133768512220936620570745912940619677854269274689475585506675881198879027) - ] - - where - poseidonTx = buildBasic $ mkExec' "(poseidon-hash-hack-a-chain 1)" - prefixedSigned = buildBasicGasWebAuthnPrefixedSigner 1000 $ mkExec' "1" - - prefixedSignerBareKey = buildBasicGasWebAuthnPrefixedSigner 1000 $ mkExec - "(enforce-keyset (read-keyset 'k))" - (mkKeyEnvData "a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf") - - bareSignerPrefixedKey = buildBasicGasWebAuthnBareSigner 1000 $ mkExec - "(enforce-keyset (read-keyset 'k))" - (mkKeyEnvData "WEBAUTHN-a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf") - - prefixedSignerPrefixedKey = buildBasicGasWebAuthnPrefixedSigner 1000 $ mkExec - "(enforce-keyset (read-keyset 'k))" - (mkKeyEnvData "WEBAUTHN-a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf") - - definePrefixedKeySet = buildBasicGasWebAuthnPrefixedSigner 1000 $ mkExec - "(namespace 'free) (define-keyset \"free.edmund\" (read-keyset 'k)) (enforce-keyset \"free.edmund\")" - (mkKeyEnvData "WEBAUTHN-a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf") - - prefixedSignerPrefixedKeyCreatePrincipal = buildBasicGasWebAuthnPrefixedSigner 1000 $ mkExec - "(create-principal (read-keyset 'k))" - (mkKeyEnvData "WEBAUTHN-a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf") - - prefixedSignerPrefixedKeyValidatePrincipal = buildBasicGasWebAuthnPrefixedSigner 1000 $ mkExec - "(let ((ks (read-keyset 'k))) (validate-principal ks (create-principal ks)))" - (mkKeyEnvData "WEBAUTHN-a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf") - - -- This hardcoded public key is the same as the valid one above, except that the first - -- character is changed. CBOR parsing will fail. - invalidPrefixedKey = buildBasicGas 1000 $ mkExec - "(read-keyset 'k)" - (mkKeyEnvData "WEBAUTHN-a401010327200add79710303bf") - - mkKeyEnvData :: String -> Value - mkKeyEnvData key = object [ "k" .= [key] ] - -chainweb223Test :: PactTestM () -chainweb223Test = do - - -- run past genesis, upgrades - runToHeight 120 - - let sender00KAccount = "k:" <> fst sender00 - -- run pre-fork, where rotating principals is allowed - -- runBlockTest - -- [ PactTxTest - -- (buildBasic' - -- (set cbGasLimit 10000 . - -- set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkCoinCap "ROTATE" [pString sender00KAccount]]] - -- ) $ mkExec - -- (T.unlines - -- [ "(coin.create-account (read-msg 'sender00KAcct) (read-keyset 'sender00))" - -- ,"(coin.rotate (read-msg 'sender00KAcct) (read-keyset 'sender01))" - -- ]) - -- (object ["sender00" .= [fst sender00], "sender00KAcct" .= sender00KAccount, "sender01" .= [fst sender01]])) - -- (assertTxSuccess "should allow rotating principals before fork" (pString "Write succeeded")) - -- ] - - -- run post-fork, where rotating principals is only allowed to get back to - -- their original guards - runBlockTest - [ - -- PactTxTest - -- (buildBasic' - -- (set cbGasLimit 10000 . - -- set cbSigners [mkEd25519Signer' sender00 [mkGasCap], mkEd25519Signer' sender01 [mkCoinCap "ROTATE" [pString sender00KAccount]]] - -- ) $ mkExec - -- "(coin.rotate (read-msg 'sender00KAcct) (read-keyset 'sender00))" - -- (object ["sender00" .= [fst sender00], "sender00KAcct" .= sender00KAccount, "sender01" .= [fst sender01]])) - -- (assertTxSuccess "should allow rotating principals back after fork" (pString "Write succeeded")) - PactTxTest - (buildBasic' - (set cbGasLimit 10000 . - set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkCoinCap "ROTATE" [pString sender00KAccount]]] - ) $ mkExec - "(coin.rotate (read-msg 'sender00KAcct) (read-keyset 'sender01))" - (object ["sender00" .= [fst sender00], "sender00KAcct" .= sender00KAccount, "sender01" .= [fst sender01]])) - (assertTxFailure "should not allow rotating principals after fork" "It is unsafe for principal accounts to rotate their guard") - ] - -compactAndSyncTest :: PactTestM () -compactAndSyncTest = do - -- start with all forks on. - let start = latestBehaviorAt testVersion - runToHeight start - -- we want to run a transaction but it doesn't matter what it does, as long - -- as it gets on-chain and thus affects the Pact state. - -- note that this is a decimal because we're parsing the CommandResult out of the payload, and - -- the json codec treats all numbers as decimals unless in a `{"int": }` object. - runBlockTest - [ PactTxTest (buildBasic $ mkExec' "1") (assertTxSuccess "should allow innocent transaction" (pDecimal 1)) - ] - -- save the cut with the tx, we'll return to it after compaction - cutWithTx <- currentCut - withCompacted start $ syncTo cutWithTx - -compactionCompactsUnmodifiedTables :: PactTestM () -compactionCompactsUnmodifiedTables = do - let start = latestBehaviorAt testVersion - runToHeight start - runBlockTest - -- create table - [ PactTxTest - (buildBasicGas 70_000 $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module dbmod G (defcap G () true)" - , " (defschema sch i:integer)" - , " (deftable tbl:{sch})" - , " (defun do-write () (insert tbl 'key {'i: 2})))" - , "(create-table tbl)" - ] - ) (assertTxSuccess "should create a table" (pString "TableCreated")) - ] - -- empty block, this regression requires an extra block before - -- any modifications - runBlockTest [] - - -- write to table - runBlockTest - [ PactTxTest - (buildBasic $ mkExec' "(free.dbmod.do-write)") - (assertTxSuccess "should write to that table" (pString "Write succeeded")) - ] - - -- grab current cut so that we can fast-forward to here after - -- compaction - afterWrite <- currentCut - - -- compact to the empty block, before we've written to the table but after - -- creating it - withCompacted (start + 2) $ do - -- fast forward to after we did the write, expecting the write to not fail - -- due to a duplicate row - syncTo afterWrite - -checkTransferCreate :: PactTestM () -checkTransferCreate = do - runToHeight 114 - runBlockTest - -- create table - [ PactTxTest - (buildBasic' (set cbSigners [mkEd25519Signer' sender00 coinCaps] . set cbGasLimit 70000) $ mkExec (mconcat - [ "(namespace 'free)" - -- , "(module dbmod G (defcap G () true)" - -- , " (defschema sch i:integer)" - -- , " (deftable tbl:{sch})" - -- , " (defun do-write () (insert tbl 'key {'i: 2})))" - , "(coin.transfer-create 'sender00 'sender01 (read-keyset 'test-keyset) 1.0)" - ]) (mkKeySetData "test-keyset" [sender01]) - ) (assertTxSuccess "should create a table" (pString "Write succeeded")) - ] - where - coinCaps = [ mkGasCap, mkTransferCap "sender00" "sender01" 1.0 ] - -quirkTest :: RocksDb -> TestTree -quirkTest rdb = do - let v = quirkedGasInstantCpmTestVersion peterson - let mempoolCmdBuilder = - buildBasic (mkExec' "(+ 1 2)") - withResourceT (mkTestBlockDb v rdb) $ \bdbIO -> - testCaseSteps "quirkTest" $ \step -> do - bdb <- bdbIO - mempools <- onAllChains v $ \_cid -> do - r <- newIORef mempty - return (r, delegateMemPoolAccess r) - let logger = hunitDummyLogger step - withWebPactExecutionService logger v testPactServiceConfig bdb (snd <$> mempools) $ \(pact,pacts) -> - flip runReaderT (MultiEnv bdb pact pacts pacts (fst <$> mempools) noMiner cid) $ do - -- run the command once without it being quirked, to establish - -- a baseline gas value - local (menvChainId .~ unsafeChainId 0) $ do - runBlockTest - [ PactTxTest mempoolCmdBuilder $ - assertTxGas "not-quirked gas" 219 - ] - runBlockTest - [ PactTxTest mempoolCmdBuilder $ - assertTxGas "quirked gas" 1 - ] - -pact4coin3UpgradeTest :: PactTestM () -pact4coin3UpgradeTest = do - - -- run past genesis, upgrades - runToHeight 6 - - -- run block 7 - runBlockTest - [ PactTxTest buildHashCmd $ \cr -> do - assertTxSuccess "Hash of coin @ block 7" - (pString "ut_J_ZNkoyaPUEJhiwVeWnkSQn9JT9sQCWKdjjVVrWo") - cr - assertTxEvents "Events for tx 0 @ block 7" [] cr - , PactTxTest (buildXSend []) $ - assertTxEvents "Events for tx 1 @ block 7" [] - , PactTxTest buildNewNatives40Cmd $ - assertTxFailure - "Should not resolve new pact natives" - "Cannot resolve distinct" - , PactTxTest badKeyset $ - assertTxSuccess - "Should allow bad keys" $ - pKeySet $ mkKeySet ["badkey"] "keys-all" - ] - - assertTxEvents "Coinbase events @ block 7" [] =<< cbResult - send0 <- txResult 1 - - -- run past v3 upgrade, pact 4 switch - resetMempool - runToHeight 18 - savedCut <- currentCut - runToHeight 21 - - -- block 22 - -- get proof - xproof <- buildXProof cid 7 1 send0 - cont <- buildCont send0 - - let v3Hash = "1os_sLAUYvBzspn5jjawtRpJWiH1WPfhyNraeVvSIwU" - block22 = - [ PactTxTest buildHashCmd $ \cr -> do - gasEv0 <- mkTransferEvent "sender00" "NoMiner" 0.0106 "coin" v3Hash - assertTxSuccess "Hash of coin @ block 22" (pString v3Hash) cr - assertTxEvents "Events for tx0 @ block 22" [gasEv0] cr - , PactTxTest buildReleaseCommand $ \cr -> do - gasEv1 <- mkTransferEvent "sender00" "NoMiner" 0.0425 "coin" v3Hash - allocTfr <- mkTransferEvent "" "allocation00" 1000000.0 "coin" v3Hash - allocEv <- mkEvent "RELEASE_ALLOCATION" [pString "allocation00",pDecimal 1000000.0] - "coin" v3Hash - assertTxEvents "Events for tx1 @ block 22" [gasEv1,allocEv,allocTfr] cr - , PactTxTest (buildXSend []) $ \cr -> do - gasEv2 <- mkTransferEvent "sender00" "NoMiner" 0.0369 "coin" v3Hash - sendTfr <- mkTransferEvent "sender00" "" 0.0123 "coin" v3Hash - yieldEv <- mkXYieldEvent "sender00" "sender00" 0.0123 sender00Ks "pact" v3Hash "0" "0" - assertTxEvents "Events for tx2 @ block 22" [gasEv2,sendTfr, yieldEv] cr - , PactTxTest buildNewNatives40Cmd $ - assertTxSuccess - "Should resolve enumerate pact native" - (pList $ pInteger <$> [1..10]) - , PactTxTest badKeyset $ - assertTxFailure - "Should not allow bad keys" - "Invalid keyset" - , PactTxTest cont $ - assertTxFailure' - "Attempt to continue xchain on same chain fails" - "yield provenance" - ] - block22_0 = - [ PactTxTest (buildXReceive xproof) $ \cr -> do - -- test receive XChain events - gasEvRcv <- mkTransferEvent "sender00" "NoMiner" 0.0247 "coin" v3Hash - rcvTfr <- mkTransferEvent "" "sender00" 0.0123 "coin" v3Hash - assertTxEvents "Events for txRcv" [gasEvRcv,rcvTfr] cr - ] - - void $ setPactMempool cid $ PactMempool - [ testsToBlock block22 ] - void $ setPactMempool chain0 $ PactMempool - [ testsToBlock block22_0 ] - runCut' - withChain cid $ do - testCurrentBlock block22 - cbEv <- mkTransferEvent "" "NoMiner" 2.304523 "coin" v3Hash - assertTxEvents "Coinbase events @ block 22" [cbEv] =<< cbResult - withChain chain0 $ testCurrentBlock block22_0 - - -- rewind to savedCut (cut 18) - syncTo savedCut - runToHeight 22 - - where - - buildHashCmd = buildBasic - $ mkExec' "(at 'hash (describe-module 'coin))" - - badKeyset = buildBasic - $ mkExec "(read-keyset 'ks)" $ object ["ks" .= ["badkey"::T.Text]] - - buildNewNatives40Cmd = buildBasic - $ mkExec' (mconcat expressions) - where - expressions = - [ - "(distinct [1 1 2 2 3 3])" - , "(concat [\"this\" \"is\" \"a\" \"test\"])" - , "(str-to-list \"test\")" - , "(enumerate 1 10)" - ] - - buildReleaseCommand = buildBasic' - (set cbSigners [ mkEd25519Signer' sender00 [] - , mkEd25519Signer' allocation00KeyPair []]) - $ mkExec' "(coin.release-allocation 'allocation00)" - - buildCont sendTx = do - pid <- getPactId sendTx - return $ buildBasic $ mkCont (mkContMsg pid 1) - - --- ========================================================= --- --- Fixture --- --- ========================================================= - - --- | Sets mempool with block fillers. A matched filler --- (returning a 'Just' result) is executed and removed from the list. --- Fillers are tested in order. -setPactMempool :: ChainId -> PactMempool -> PactTestM (IORef (Set TransactionHash)) -setPactMempool = setPactMempool' Nothing - -setPactMempool' :: Maybe BlockHeader -> ChainId -> PactMempool -> PactTestM (IORef (Set TransactionHash)) -setPactMempool' fakeParentBh chain (PactMempool fs) = do - mpa <- view menvMpa - mpsRef <- liftIO $ newIORef fs - badTxs <- liftIO $ newIORef Set.empty - v <- view chainwebVersion - liftIO $ writeIORef (mpa ^?! atChain chain) $ mempty { - mpaGetBlock = \_ -> go v mpsRef, - mpaBadlistTx = \txs -> modifyIORef' badTxs (\hashes -> foldr Set.insert hashes txs) - } - pure badTxs - where - go v ref mempoolPreBlockCheck bHeight bHash bCreationTime = do - mps <- readIORef ref - let runMps i = \case - [] -> return mempty - (mp:r) -> case _mempoolBlock mp chain bCreationTime of - Just bs -> do - writeIORef ref (take i mps ++ r) - let parentBct = fromMaybe bCreationTime (view blockCreationTime <$> fakeParentBh) - cmds <- fmap V.fromList $ forM bs $ \b -> - buildCwCmd (sshow bHash) v $ _mempoolCmdBuilder b chain parentBct - validationResults <- mempoolPreBlockCheck bHeight bHash $ - (fmap . fmap . fmap) _pcCode cmds - return $ V.fromList [ tOut | Right tOut <- V.toList validationResults ] - Nothing -> runMps (succ i) r - runMps 0 mps - -runCut' :: PactTestM () -runCut' = do - pact <- view menvPact - bdb <- view menvBdb - miner <- view menvMiner - liftIO $ runCut testVersion bdb pact (offsetBlockTime second) zeroNoncer miner - -resetMempool :: PactTestM () -resetMempool = do - mems <- view menvMpa - forM_ mems $ \ref -> - liftIO $ writeIORef ref mempty - -currentCut :: PactTestM Cut -currentCut = view menvBdb >>= liftIO . readMVar . _bdbCut - -syncTo :: Cut -> PactTestM () -syncTo c = do - pact <- view menvPact - bdb <- view menvBdb - - forM_ (chainIds testVersion) $ \cid' -> do - let - ph = case HM.lookup cid' (_cutMap c) of - Just h -> h - Nothing -> error $ "syncTo: can't find block header for " ++ show cid' - - -- reset the parent header using SyncToBlock - void $ liftIO $ _webPactSyncToBlock pact ph - - void $ liftIO $ swapMVar (_bdbCut bdb) c - -assertTxEvents :: (HasCallStack, MonadIO m) => String -> [PactEvent] -> CommandResult Hash -> m () -assertTxEvents msg evs = liftIO . assertEqual msg evs . _crEvents - -assertTxGas :: (HasCallStack, MonadIO m) => String -> Gas -> CommandResult Hash -> m () -assertTxGas msg g = liftIO . assertEqual msg g . _crGas - -assertTxSuccess - :: HasCallStack - => MonadIO m - => String - -> PactValue - -> CommandResult Hash - -> m () -assertTxSuccess msg r tx = do - liftIO $ assertEqual msg (Just r) - (tx ^? crResult . to _pactResult . _Right) - -assertTxSuccessWith - :: (HasCallStack, MonadIO m) - => String - -> PactValue - -> (PactValue -> PactValue) - -> CommandResult Hash - -> m () -assertTxSuccessWith msg r f tx = do - liftIO $ assertEqual msg (Just r) - (tx ^? crResult . to _pactResult . _Right . to f) - --- | Exact match on error doc -assertTxFailure :: (HasCallStack, MonadIO m) => String -> Doc -> CommandResult Hash -> m () -assertTxFailure msg d tx = - liftIO $ assertEqual msg (Just d) - (tx ^? crResult . to _pactResult . _Left . to peDoc) - -assertTxFailureWith - :: (HasCallStack, MonadIO m) - => String - -> (Doc -> Bool) - -> CommandResult Hash -> m () -assertTxFailureWith msg f tx = do - let mresult = tx ^? crResult . to _pactResult . _Left . to peDoc - liftIO $ assertBool (msg <> ". Tx Result: " <> show mresult) $ maybe False f mresult - --- | Partial match on show of error doc -assertTxFailure' :: (HasCallStack, MonadIO m) => String -> T.Text -> CommandResult Hash -> m () -assertTxFailure' msg needle tx = - liftIO $ assertSatisfies msg - (tx ^? crResult . to _pactResult . _Left . to peDoc) $ \case - Nothing -> False - Just d -> T.isInfixOf needle (sshow d) - --- | Run a single mempool block on current chain with tests for each tx. --- Limitations: can only run a single-chain, single-refill test for --- a given cut height. -runBlockTest :: HasCallStack => [PactTxTest] -> PactTestM () -runBlockTest pts = do - chid <- view menvChainId - void $ setPactMempool chid $ PactMempool [testsToBlock pts] - runCut' - testCurrentBlock pts - --- | Convert tests to block for specified chain. -testsToBlock :: [PactTxTest] -> MempoolBlock -testsToBlock pts = MempoolBlock $ \_ _ -> - pure $ map _pttBuilder pts - --- | No tests in this list should even be submitted to the mempool, --- they should be rejected early. -expectInvalid :: String -> [MempoolCmdBuilder] -> PactTestM () -expectInvalid msg pts = do - chid <- view menvChainId - void $ setPactMempool chid $ PactMempool [MempoolBlock $ \_ _ -> Just pts] - _ <- runCut' - rs <- txResults - liftIO $ assertEqual msg mempty rs - --- | Run tests on current cut and chain. -testCurrentBlock :: HasCallStack => [PactTxTest] -> PactTestM () -testCurrentBlock pts = do - rs <- txResults - liftIO $ assertEqual "Result length should equal transaction length" (length pts) (length rs) - zipWithM_ go pts (V.toList rs) - where - go :: PactTxTest -> CommandResult Hash -> PactTestM () - go (PactTxTest _ t) cr = liftIO $ t cr - --- | Run cuts to block height. -runToHeight :: BlockHeight -> PactTestM () -runToHeight bhi = do - chid <- view menvChainId - bh <- getHeader chid - when (view blockHeight bh < bhi) $ do - runCut' - runToHeight bhi - -buildXSend :: [SigCapability] -> MempoolCmdBuilder -buildXSend caps = MempoolCmdBuilder $ \chain bct -> - set cbSigners [mkEd25519Signer' sender00 caps] - $ set cbChainId chain - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC - (mkExec - "(coin.transfer-crosschain 'sender00 'sender00 (read-keyset 'k) \"0\" 0.0123)" $ - mkKeySetData "k" [sender00]) - $ defaultCmd - -chain0 :: ChainId -chain0 = unsafeChainId 0 - -withChain :: ChainId -> PactTestM a -> PactTestM a -withChain c = local (set menvChainId c) - -buildXProof - :: ChainId - -> BlockHeight - -> Int - -> CommandResult l - -> PactTestM (ContProof, PactId) -buildXProof scid bh i sendTx = do - bdb <- view menvBdb - proof <- liftIO $ ContProof . B64U.encode . encodeToByteString <$> - createTransactionOutputProof_ (_bdbWebBlockHeaderDb bdb) (_bdbPayloadDb bdb) chain0 scid bh i - pid <- getPactId sendTx - return (proof,pid) - -getPactId :: CommandResult l -> PactTestM PactId -getPactId = fromMaybeM (userError "no continuation") . - preview (crContinuation . _Just . pePactId) - -buildXReceive - :: (ContProof, PactId) - -> MempoolCmdBuilder -buildXReceive (proof,pid) = buildBasic $ - mkCont ((mkContMsg pid 1) { _cmProof = Just proof }) - -signWebAuthn00Prefixed :: CmdBuilder -> CmdBuilder -signWebAuthn00Prefixed = - set cbSigners [mkWebAuthnSigner' sender02WebAuthnPrefixed [] - ,mkEd25519Signer' sender00 [] - ] - -signWebAuthn00 :: CmdBuilder -> CmdBuilder -signWebAuthn00 = - set cbSigners [mkWebAuthnSigner' sender02WebAuthn [] - ,mkEd25519Signer' sender00 [] - ] - -signSender00 :: CmdBuilder -> CmdBuilder -signSender00 = set cbSigners [mkEd25519Signer' sender00 []] - -buildBasic - :: PactRPC T.Text - -> MempoolCmdBuilder -buildBasic = buildBasic' id - -buildBasicGas :: GasLimit -> PactRPC T.Text -> MempoolCmdBuilder -buildBasicGas g = buildBasic' (set cbGasLimit g) - --- | Build with specified setter to mutate defaults. -buildBasic' - :: (CmdBuilder -> CmdBuilder) - -> PactRPC T.Text - -> MempoolCmdBuilder -buildBasic' f r = MempoolCmdBuilder $ \chain bct -> - f $ signSender00 - $ set cbChainId chain - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC r - $ defaultCmd - -buildBasicWebAuthnBareSigner' - :: (CmdBuilder -> CmdBuilder) - -> PactRPC T.Text - -> MempoolCmdBuilder -buildBasicWebAuthnBareSigner' f r = MempoolCmdBuilder $ \chain bct -> - f $ signWebAuthn00 - $ set cbChainId chain - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC r - $ defaultCmd - -buildBasicWebAuthnPrefixedSigner' - :: (CmdBuilder -> CmdBuilder) - -> PactRPC T.Text - -> MempoolCmdBuilder -buildBasicWebAuthnPrefixedSigner' f r = MempoolCmdBuilder $ \chain bct -> - f $ signWebAuthn00Prefixed - $ set cbChainId chain - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC r - $ defaultCmd - -buildBasicGasWebAuthnPrefixedSigner :: GasLimit -> PactRPC T.Text -> MempoolCmdBuilder -buildBasicGasWebAuthnPrefixedSigner g = buildBasicWebAuthnPrefixedSigner' (set cbGasLimit g) - -buildBasicGasWebAuthnBareSigner :: GasLimit -> PactRPC T.Text -> MempoolCmdBuilder -buildBasicGasWebAuthnBareSigner g = buildBasicWebAuthnBareSigner' (set cbGasLimit g) - --- | Get output on latest cut for chain -getPWO :: ChainId -> PactTestM (PayloadWithOutputs,BlockHeader) -getPWO chid = do - (TestBlockDb _ pdb _) <- view menvBdb - h <- getHeader chid - Just pwo <- liftIO $ lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) - return (pwo,h) - -getHeader :: ChainId -> PactTestM BlockHeader -getHeader chid = do - (TestBlockDb _ _ cmv) <- view menvBdb - c <- liftIO $ readMVar cmv - fromMaybeM (userError $ "chain lookup failed for " ++ show chid) $ HM.lookup chid (_cutMap c) - --- | Get tx at index from output -txResult :: HasCallStack => Int -> PactTestM (CommandResult Hash) -txResult i = txResults >>= \rs -> case preview (ix i) rs of - Nothing -> - liftIO $ assertFailure $ "no tx at " ++ show i - Just r -> return r - -txResults :: HasCallStack => PactTestM (V.Vector (CommandResult Hash)) -txResults = do - chid <- view menvChainId - (o,_h) <- getPWO chid - forM (_payloadWithOutputsTransactions o) $ \(_,txo) -> - decodeStrictOrThrow @_ @(CommandResult Hash) (_transactionOutputBytes txo) - --- | Get coinbase from output -cbResult :: PactTestM (CommandResult Hash) -cbResult = do - chid <- view menvChainId - (o,_h) <- getPWO chid - liftIO $ - decodeStrictOrThrow @_ @(CommandResult Hash) (_coinbaseOutput $ _payloadWithOutputsCoinbase o) - --- Compact and return a new MultiEnv -withCompacted :: BlockHeight -> PactTestM x -> PactTestM x -withCompacted height pt = do - srcDbs <- fmap (M.fromList . HM.toList) $ view menvPacts - targetDbs <- fmap (M.fromList . HM.toList) $ view menvCompactedPacts - let dbs :: M.Map ChainId (SQLiteEnv, SQLiteEnv) - dbs = M.intersectionWith (\e1 e2 -> (fst e1, fst e2)) srcDbs targetDbs - forM_ (M.toList dbs) $ \(_, (srcDb, targetDb)) -> do - liftIO $ sigmaCompact srcDb targetDb height - - targetPacts <- view menvCompactedPacts - local (\me -> me & menvPacts .~ targetPacts) pt diff --git a/test/unit/Chainweb/Test/Pact4/PactReplay.hs b/test/unit/Chainweb/Test/Pact4/PactReplay.hs deleted file mode 100644 index 6479a4cd95..0000000000 --- a/test/unit/Chainweb/Test/Pact4/PactReplay.hs +++ /dev/null @@ -1,363 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE MultiWayIf #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - -module Chainweb.Test.Pact4.PactReplay where - -import Control.Monad (forM_, unless, void) -import Control.Monad.Catch -import Control.Monad.Reader -import Control.Monad.State -import Control.Lens - -import Data.IORef -import qualified Data.Text as T -import qualified Data.Vector as V -import Data.Word - -import System.Timeout - -import Test.Tasty -import Test.Tasty.HUnit - --- chainweb imports - -import Chainweb.BlockCreationTime -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB.Internal (unsafeInsertBlockHeaderDb) -import Chainweb.Graph -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Utils -import Chainweb.Miner.Pact - -import Chainweb.Pact.Service.BlockValidation -import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Types -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import Chainweb.TreeDB -import Chainweb.Utils hiding (len) -import Chainweb.Version -import Chainweb.Version.Utils - -import Chainweb.BlockHeaderDB.Internal (_chainDbCas, RankedBlockHeader(..)) - -import Chainweb.Storage.Table -import Chainweb.Storage.Table.RocksDB - -import Pact.Types.Exp(ParsedCode(..)) -import Data.Either -import Chainweb.Pact.Backend.Types - -testVer :: ChainwebVersion -testVer = instantCpmTestVersion petersonChainGraph - -cid :: ChainId -cid = someChainId testVer - -tests :: RocksDb -> TestTree -tests rdb = - withDelegateMempool $ \dmp -> - let mp = snd <$> dmp - mpio = fst <$> dmp - in - independentSequentialTestGroup label - [ withPactTestBlockDb testVer cid rdb mp (forkLimit $ RewindLimit 100_000) - (testCase "initial-playthrough" . firstPlayThrough mpio genblock) - , withPactTestBlockDb testVer cid rdb mp (forkLimit $ RewindLimit 100_000) - (testCase "service-init-after-fork" . serviceInitializationAfterFork mpio genblock) - , withPactTestBlockDb testVer cid rdb mp (forkLimit $ RewindLimit 100_000) - (testCaseSteps "on-restart" . onRestart mpio) - , withPactTestBlockDb testVer cid rdb mp (forkLimit $ RewindLimit 100_000) - (testCase "reject-dupes" . testDupes mpio genblock) - , let deepForkLimit = RewindLimit 4 - in withPactTestBlockDb testVer cid rdb mp (forkLimit deepForkLimit) - (testCaseSteps "deep-fork-limit" . testDeepForkLimit mpio deepForkLimit) - ] - where - genblock = genesisBlockHeader testVer cid - label = "Chainweb.Test.Pact4.PactReplay" - - forkLimit fl = testPactServiceConfig { _pactReorgLimit = fl } - - -onRestart - :: IO (IORef MemPoolAccess) - -> IO (SQLiteEnv, PactQueue, TestBlockDb) - -> (String -> IO ()) - -> Assertion -onRestart mpio iop step = do - setOneShotMempool mpio testMemPoolAccess - (_, _, bdb) <- iop - bhdb' <- getBlockHeaderDb cid bdb - block <- maxEntry bhdb' - step $ "max block has height " <> sshow (view blockHeight block) - let nonce = Nonce $ fromIntegral $ view blockHeight block - step "mine block on top of max block" - T3 _ b _ <- mineBlock (ParentHeader block) nonce iop - assertEqual "Invalid BlockHeight" 1 (view blockHeight b) - -testMemPoolAccess :: MemPoolAccess -testMemPoolAccess = mempty - { mpaGetBlock = \_g validate bh hash bct -> do - let (BlockCreationTime t) = bct - getTestBlock t validate bh hash - } - where - getTestBlock _ _ 1 _ = mempty - getTestBlock txOrigTime validate bHeight hash = do - let nonce = T.pack . show @(Time Micros) $ txOrigTime - tx <- buildCwCmd nonce testVer $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbCreationTime (toTxCreationTime txOrigTime) $ - set cbRPC (mkExec' "1") $ - defaultCmd - let outtxs = V.singleton tx - oks <- validate bHeight hash $ (fmap . fmap . fmap) _pcCode outtxs - unless (V.all isRight oks) $ fail $ mconcat - [ "testMemPoolAccess: tx failed validation! input list: \n" - , show tx - , "\n\nouttxs: " - , show outtxs - , "\n\noks: " - , show [ fmap (bimap (sshow @_ @String) (const ())) oks ] - ] - return $ V.fromList [ t | Right t <- V.toList oks ] - -dupegenMemPoolAccess :: IO MemPoolAccess -dupegenMemPoolAccess = do - hs <- newIORef [] - return $ mempty - { mpaGetBlock = \_g validate bHeight bHash _parentHeader -> do - hs' <- readIORef hs - if bHeight `elem` hs' then return mempty else do - writeIORef hs (bHeight:hs') - outtxs <- fmap V.singleton $ - buildCwCmd "0" testVer $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbRPC (mkExec' "1") $ - defaultCmd - oks <- validate bHeight bHash ((fmap . fmap . fmap) _pcCode outtxs) - unless (V.all isRight oks) $ fail $ mconcat - [ "dupegenMemPoolAccess: tx failed validation! input list: \n" - , show outtxs - , "\n\noks: " - , show [ fmap (bimap (sshow @_ @String) (const ())) oks ] - ] - return $ V.fromList $ [ t | Right t <- V.toList oks ] - } - --- | This is a regression test for correct initialization of the checkpointer --- during pact service initialization. --- --- Removing the call to 'initializeLatestBlock' in 'runPactService' causes --- this test to fail. --- -serviceInitializationAfterFork - :: IO (IORef MemPoolAccess) - -> BlockHeader - -> IO (SQLiteEnv, PactQueue, TestBlockDb) - -> Assertion -serviceInitializationAfterFork mpio genesisBlock iop = do - setOneShotMempool mpio testMemPoolAccess - nonceCounter <- newIORef (1 :: Word64) - mainlineblocks <- mineLine genesisBlock nonceCounter 10 - -- Delete latest block from block header db. This simulates the situation - -- when the latest block of the checkpointer gets orphaned during a restart - -- cycle. - pruneDbs - restartPact - let T3 _ line1 pwo1 = mainlineblocks !! 6 - (_, q, _) <- iop - -- reset the pact service state to line1 - void $ validateBlock line1 (CheckablePayloadWithOutputs pwo1) q - void $ mineLine line1 nonceCounter 4 - where - mineLine start ncounter len = - evalStateT (mapM (const go) [startHeight :: Word64 .. (startHeight + len)]) start - where - startHeight = fromIntegral $ view blockHeight start - go = do - pblock <- gets ParentHeader - n <- liftIO $ Nonce <$> readIORef ncounter - ret@(T3 _ newblock _) <- liftIO $ mineBlock pblock n iop - liftIO $ modifyIORef' ncounter succ - put newblock - return ret - - restartPact :: IO () - restartPact = do - (_, q, _) <- iop - submitRequestAndWait q CloseMsg - - pruneDbs = forM_ cids $ \c -> do - (_, _, dbs) <- iop - db <- getBlockHeaderDb c dbs - h <- maxEntry db - tableDelete (_chainDbCas db) (casKey $ RankedBlockHeader h) - - cids = chainIds testVer - -firstPlayThrough - :: IO (IORef MemPoolAccess) - -> BlockHeader - -> IO (SQLiteEnv, PactQueue, TestBlockDb) - -> Assertion -firstPlayThrough mpio genesisBlock iop = do - setOneShotMempool mpio testMemPoolAccess - nonceCounter <- newIORef (1 :: Word64) - mainlineblocks <- mineLine genesisBlock nonceCounter 7 - let T3 _ startline1 pwo1 = head mainlineblocks - let T3 _ startline2 pwo2 = mainlineblocks !! 1 - - (_, q, _) <- iop - - -- reset the pact service state to startline1 - void $ validateBlock startline1 (CheckablePayloadWithOutputs pwo1) q - - void $ mineLine startline1 nonceCounter 4 - - -- reset the pact service state to startline2 - void $ validateBlock startline2 (CheckablePayloadWithOutputs pwo2) q - - void $ mineLine startline2 nonceCounter 4 - where - mineLine start ncounter len = - evalStateT (mapM (const go) [startHeight :: Word64 .. (startHeight + len)]) start - where - startHeight = fromIntegral $ view blockHeight start - go = do - pblock <- gets ParentHeader - n <- liftIO $ Nonce <$> readIORef ncounter - ret@(T3 _ newblock _) <- liftIO $ mineBlock pblock n iop - liftIO $ modifyIORef' ncounter succ - put newblock - return ret - -testDupes - :: IO (IORef MemPoolAccess) - -> BlockHeader - -> IO (SQLiteEnv, PactQueue, TestBlockDb) - -> Assertion -testDupes mpio genesisBlock iop = do - setMempool mpio =<< dupegenMemPoolAccess - (T3 _ newblock payload) <- liftIO $ mineBlock (ParentHeader genesisBlock) (Nonce 1) iop - expectException newblock payload $ liftIO $ - mineBlock (ParentHeader newblock) (Nonce 3) iop - where - expectException newblock payload act = do - m <- wrap `catchAllSynchronous` h - maybe (return ()) (\msg -> assertBool msg False) m - where - wrap = do - (T3 _ newblock2 payload2) <- act - let msg = concat [ "expected exception on dupe block. new block header:\n" - , sshow newblock2 - , "\nnew payload: \n" - , sshow payload2 - , "\nprev block: \n" - , sshow newblock - , "\nprev payload: \n" - , sshow payload - ] - return $ Just msg - - h :: SomeException -> IO (Maybe String) - h _ = return Nothing - -testDeepForkLimit - :: IO (IORef MemPoolAccess) - -> RewindLimit - -> IO (SQLiteEnv, PactQueue,TestBlockDb) - -> (String -> IO ()) - -> Assertion -testDeepForkLimit mpio (RewindLimit deepForkLimit) iop step = do - setOneShotMempool mpio testMemPoolAccess - (_, q, bdb) <- iop - bhdb <- getBlockHeaderDb cid bdb - let pdb = _bdbPayloadDb bdb - step "query max db entry" - maxblock <- maxEntry bhdb - pd <- lookupPayloadWithHeight pdb (Just $ view blockHeight maxblock) (view blockPayloadHash maxblock) >>= \case - Nothing -> assertFailure "max block payload not found" - Just x -> return x - step $ "max block has height " <> sshow (view blockHeight maxblock) - nonceCounterMain <- newIORef (fromIntegral $ view blockHeight maxblock) - - -- mine the main line a bit more - step "mine (deepForkLimit + 1) many blocks on top of max block" - void $ mineLine maxblock nonceCounterMain (deepForkLimit + 1) - - step "try to rewind to max block" - try @_ @SomeException (validateBlock maxblock (CheckablePayloadWithOutputs pd) q) >>= \case - Left _ -> return () - _ -> assertBool msg False - - where - msg = "expected exception on a deep fork longer than " <> show deepForkLimit - - mineLine start ncounter len = - evalStateT (mapM (const go) [startHeight :: Word64 .. (startHeight + len)]) start - where - startHeight = fromIntegral $ view blockHeight start - go = do - pblock <- gets ParentHeader - n <- liftIO $ Nonce <$> readIORef ncounter - liftIO $ step $ "mine block on top of height " <> sshow (view blockHeight $ _parentHeader pblock) - ret@(T3 _ newblock _) <- liftIO $ mineBlock pblock n iop - liftIO $ modifyIORef' ncounter succ - put newblock - return ret - - -mineBlock - :: ParentHeader - -> Nonce - -> IO (SQLiteEnv, PactQueue, TestBlockDb) - -> IO (T3 ParentHeader BlockHeader PayloadWithOutputs) -mineBlock ph nonce iop = timeout 5000000 go >>= \case - Nothing -> error "PactReplay.mineBlock: Test timeout. Most likely a test case caused a pact service failure that wasn't caught, and the test was blocked while waiting for the result" - Just x -> return x - where - go = do - - -- assemble block without nonce and timestamp - (_, q, bdb) <- iop - bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill ph q - let payload = forAnyPactVersion finalizeBlock bip - - let - creationTime = BlockCreationTime - . add (TimeSpan 1_000_000) - . _bct . view blockCreationTime - $ _parentHeader ph - - let bh = newBlockHeader - mempty - (_payloadWithOutputsPayloadHash payload) - nonce - creationTime - ph - - _ <- validateBlock bh (CheckablePayloadWithOutputs payload) q - - let pdb = _bdbPayloadDb bdb - addNewPayload pdb (succ $ view blockHeight $ _parentHeader ph) payload - - bhdb <- getBlockHeaderDb cid bdb - unsafeInsertBlockHeaderDb bhdb bh - - return $ T3 ph bh payload - -assertNotLeft :: (MonadThrow m, Exception e) => Either e a -> m a -assertNotLeft (Left l) = throwM l -assertNotLeft (Right r) = return r diff --git a/test/unit/Chainweb/Test/Pact4/PactSingleChainTest.hs b/test/unit/Chainweb/Test/Pact4/PactSingleChainTest.hs deleted file mode 100644 index b3b4ad5214..0000000000 --- a/test/unit/Chainweb/Test/Pact4/PactSingleChainTest.hs +++ /dev/null @@ -1,1471 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DerivingVia #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ViewPatterns #-} - -module Chainweb.Test.Pact4.PactSingleChainTest -( tests -) where - -import Control.Arrow ((&&&)) -import Control.Concurrent.Async(withAsync) -import Control.Concurrent.MVar -import Control.DeepSeq -import Control.Lens hiding ((.=), matching) -import Control.Monad -import Control.Monad.Catch -import Patience.Map qualified as PatienceM -import Patience.Map (Delta(..)) - -import Data.Aeson (object, (.=), Value(..)) -import qualified Data.ByteString.Lazy as BL -import Data.Either -import Data.Foldable -import qualified Data.HashMap.Strict as HM -import Data.Int -import Data.IORef -import qualified Data.List as List -import qualified Data.Map.Strict as M -import Data.Maybe (isJust, isNothing) -import Data.Ord -import qualified Data.Text as T -import Data.Text (Text) -import qualified Data.Text.Encoding as T -import qualified Data.Text.IO as T -import qualified Data.Vector as V -import qualified Database.SQLite3 as Lite -import qualified Streaming.Prelude as S - -import GHC.Stack - -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Pact.Types.Command -import Pact.Types.Hash -import Pact.Types.Info -import Pact.Types.Persistence -import Pact.Types.PactError -import qualified Pact.Types.SQLite as Pact - -import Pact.JSON.Encode qualified as J -import Pact.JSON.Yaml - -import qualified Pact.Core.Persistence as PCore - -import Chainweb.BlockCreationTime -import Chainweb.BlockHash (BlockHash) -import Chainweb.BlockHeader.Internal -import Chainweb.BlockHeight (BlockHeight(..)) -import Chainweb.Graph -import Chainweb.Logger (genericLogger) -import Chainweb.Mempool.Mempool -import Chainweb.MerkleLogHash (unsafeMerkleLogHash) -import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (computeGrandHash) -import Chainweb.Pact.Backend.PactState qualified as PS -import Chainweb.Pact.Service.BlockValidation hiding (local) -import Chainweb.Pact.Service.PactQueue (PactQueue, newPactQueue) -import Chainweb.Pact.Types -import Chainweb.Pact.PactService (runPactService) -import Chainweb.Pact.Utils (emptyPayload) -import Chainweb.Payload -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils -import Chainweb.Version -import Chainweb.Version.Utils -import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) - -import Chainweb.Storage.Table.RocksDB - -import System.LogLevel (LogLevel(..)) -import qualified Pact.Core.Names as PCore -import qualified Data.ByteString.Short as SB -import Text.Show.Pretty (ppShow) -import qualified Pact.Core.StableEncoding as PCore -import Chainweb.Pact.Backend.Types - -testVersion :: ChainwebVersion -testVersion = slowForkingCpmTestVersion petersonChainGraph - -cid :: ChainId -cid = someChainId testVersion - -genesisHeader :: BlockHeader -genesisHeader = genesisBlockHeader testVersion cid - - -tests :: RocksDb -> TestTree -tests rdb = testGroup testName - [ test $ goldenNewBlock "new-block-0" goldenMemPool - , test $ goldenNewBlock "empty-block-tests" (return mempty) - , test newBlockAndValidate - , test newBlockNoFill - , test newBlockAndContinue - , test newBlockAndValidationFailure - -- this test needs to see all of the writes done in the block; - -- it uses the BlockTxHistory Pact request to see them. - , testWithConf getHistory - testPactServiceConfig { _pactPersistIntraBlockWrites = PersistIntraBlockWrites } - , test testHistLookup1 - , test testHistLookup2 - , test testHistLookup3 - , test badlistNewBlockTest - , test mempoolCreationTimeTest - , test moduleNameFork - , test mempoolRefillTest - , test blockGasLimitTest - , testTimeout preInsertCheckTimeoutTest - , rewindPastMinBlockHeightFails rdb - , pactStateSamePreAndPostCompaction rdb - , compactionIsIdempotent rdb - , compactionUserTablesDropped rdb - , compactionGrandHashUnchanged rdb - , compactionDoesNotDisruptDuplicateDetection rdb - , compactionResilientToRowIdOrdering rdb - ] - where - testName = "Chainweb.Test.Pact4.PactSingleChainTest" - test = test' rdb - testWithConf = testWithConf' rdb - testTimeout = testTimeout' rdb - - testHistLookup1 = getHistoricalLookupNoTxs "sender00" - (assertSender00Bal 100_000_000 "check latest entry for sender00 after a no txs block") - testHistLookup2 = getHistoricalLookupNoTxs "randomAccount" - (assertEqual "Return Nothing if key absent after a no txs block" Nothing) - testHistLookup3 = getHistoricalLookupWithTxs "sender00" - (assertSender00Bal 9.999998042e7 "check latest entry for sender00 after block with txs") - -testWithConf' :: () - => RocksDb - -> (IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree) - -> PactServiceConfig - -> TestTree -testWithConf' rdb f conf = - withDelegateMempool $ \dm -> - withPactTestBlockDb testVersion cid rdb (snd <$> dm) conf $ - f (fst <$> dm) - -test' :: () - => RocksDb - -> (IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree) - -> TestTree -test' rdb f = testWithConf' rdb f testPactServiceConfig - -testTimeout' :: () - => RocksDb - -> (IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree) - -> TestTree -testTimeout' rdb f = testWithConf' rdb f (testPactServiceConfig { _pactPreInsertCheckTimeout = 1 }) - -forSuccess :: (NFData a, HasCallStack) => String -> IO (Either PactException a) -> IO a -forSuccess msg act = (`catchAllSynchronous` handler) $ do - r <- act - case r of - Left e -> assertFailure $ msg ++ ": got failure result: " ++ show e - Right v -> return v - where - handler e = assertFailure $ msg ++ ": exception thrown: " ++ show e - -runBlockE :: (HasCallStack) => PactQueue -> TestBlockDb -> TimeSpan Micros -> IO (Either PactException PayloadWithOutputs) -runBlockE q bdb timeOffset = do - ph <- getParentTestBlockDb bdb cid - bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) q - let nb = forAnyPactVersion finalizeBlock bip - let blockTime = add timeOffset $ _bct $ view blockCreationTime ph - forM_ (chainIds testVersion) $ \c -> do - let o | c == cid = nb - | otherwise = emptyPayload - addTestBlockDb bdb (succ $ view blockHeight ph) (Nonce 0) (\_ _ -> blockTime) c o - nextH <- getParentTestBlockDb bdb cid - try (validateBlock nextH (CheckablePayloadWithOutputs nb) q) - --- edmundn: why does any of this return PayloadWithOutputs instead of a --- list of Pact CommandResult? -runBlock :: (HasCallStack) => PactQueue -> TestBlockDb -> TimeSpan Micros -> IO PayloadWithOutputs -runBlock q bdb timeOffset = do - forSuccess "newBlockAndValidate: validate" $ - runBlockE q bdb timeOffset - -newBlockAndValidate :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -newBlockAndValidate refIO reqIO = testCase "newBlockAndValidate" $ do - (_, q, bdb) <- reqIO - setOneShotMempool refIO =<< goldenMemPool - void $ runBlock q bdb second - -newBlockAndContinue :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -newBlockAndContinue refIO reqIO = testCase "newBlockAndContinue" $ do - (_, q, bdb) <- reqIO - let mk = signSender00 . set cbGasPrice 0.01 . set cbTTL 1_000_000 - c1 <- buildCwCmd "1" testVersion $ - mk $ - set cbRPC (mkExec "(+ 1 2)" (object [])) $ - defaultCmd - c2 <- buildCwCmd "2" testVersion $ - mk $ - set cbRPC (mkExec "(+ 3 4)" (object [])) $ - defaultCmd - c3 <- buildCwCmd "3" testVersion $ - mk $ - set cbRPC (mkExec "(+ 5 6)" (object [])) $ - defaultCmd - setMempool refIO =<< mempoolOf - [ V.fromList [ c1 ] - , mempty - , V.fromList [ c2 ] - , mempty - , V.fromList [ c3 ] - ] - - -- TODO: assert? - ForPact4 bipStart <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) q - let ParentHeader ph = fromJuste $ _blockInProgressParentHeader bipStart - bipContinued <- throwIfNoHistory =<< continueBlock bipStart q - bipFinal <- throwIfNoHistory =<< continueBlock bipContinued q - -- we must make progress on the same parent header - assertEqual "same parent header after continuing block" - (_blockInProgressParentHeader bipStart) (_blockInProgressParentHeader bipContinued) - assertBool "made progress (1)" - (bipStart /= bipContinued) - assertEqual "same parent header after finishing block" - (_blockInProgressParentHeader bipContinued) (_blockInProgressParentHeader bipFinal) - assertBool "made progress (2)" - (bipContinued /= bipFinal) - let nbContinued = finalizeBlock bipFinal - -- add block to database - let blockTime = add second $ _bct $ view blockCreationTime ph - forM_ (chainIds testVersion) $ \c -> do - let o | c == cid = nbContinued - | otherwise = emptyPayload - addTestBlockDb bdb (succ $ view blockHeight ph) (Nonce 0) (\_ _ -> blockTime) c o - nextH <- getParentTestBlockDb bdb cid - -- a continued block must be valid - _ <- validateBlock nextH (CheckablePayloadWithOutputs nbContinued) q - - -- reset to parent - pactSyncToBlock ph q - setMempool refIO =<< mempoolOf - [ V.fromList - [ c1, c2, c3 ] - ] - bipAllAtOnce <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) q - let nbAllAtOnce = forAnyPactVersion finalizeBlock bipAllAtOnce - assertEqual "a continued block, and one that's all done at once, should be exactly equal" - nbContinued nbAllAtOnce - _ <- validateBlock nextH (CheckablePayloadWithOutputs nbAllAtOnce) q - - return () - -newBlockNoFill :: IO (IORef MemPoolAccess) - -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -newBlockNoFill refIO reqIO = testCase "newBlockNoFill" $ do - (_, q, _) <- reqIO - c1 <- buildCwCmd "1" testVersion $ - signSender00 $ - set cbGasPrice 0.01 $ - set cbTTL 1_000_000 $ - set cbRPC (mkExec "1" (object [])) $ - defaultCmd - setMempool refIO =<< mempoolOf [V.fromList [c1]] - noFillPwo <- fmap (forAnyPactVersion finalizeBlock) . throwIfNoHistory =<< - newBlock noMiner NewBlockEmpty (ParentHeader genesisHeader) q - assertEqual - "an unfilled newblock must have no transactions, even with a full mempool" - mempty - (_payloadWithOutputsTransactions noFillPwo) - fillPwo <- fmap (forAnyPactVersion finalizeBlock) . throwIfNoHistory =<< - newBlock noMiner NewBlockFill (ParentHeader genesisHeader) q - assertEqual - "an filled newblock has transactions with a full mempool" - 1 - (V.length $ _payloadWithOutputsTransactions fillPwo) - -newBlockAndValidationFailure :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -newBlockAndValidationFailure refIO reqIO = testCase "newBlockAndValidationFailure" $ do - (_, q, bdb) <- reqIO - setOneShotMempool refIO =<< goldenMemPool - - bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) q - let nb = forAnyPactVersion finalizeBlock bip - let blockTime = add second $ _bct $ view blockCreationTime genesisHeader - forM_ (chainIds testVersion) $ \c -> do - let o | c == cid = nb - | otherwise = emptyPayload - addTestBlockDb bdb (succ $ view blockHeight genesisHeader) (Nonce 0) (\_ _ -> blockTime) c o - - nextH <- getParentTestBlockDb bdb cid - - let nextH' = nextH & blockPayloadHash .~ BlockPayloadHash (unsafeMerkleLogHash "0000000000000000000000000000001d") - let nb' = nb { _payloadWithOutputsOutputsHash = BlockOutputsHash (unsafeMerkleLogHash "0000000000000000000000000000001d")} - try (validateBlock nextH' (CheckablePayloadWithOutputs nb') q) >>= \case - Left BlockValidationFailure {} -> do - let txHash = SB.toShort "WgnuCg6L_l6lzbjWtBfMEuPtty_uGcNrUol5HGREO_o" - lookupRes <- lookupPactTxs Nothing (V.fromList [txHash]) q - assertEqual "The transaction from the latest block is not at the tip point" mempty lookupRes - - _ -> assertFailure "newBlockAndValidationFailure: expected BlockValidationFailure" - -toRowData :: HasCallStack => Value -> PCore.RowData -toRowData v = case PCore.decodeStable $ BL.toStrict encV of - Nothing -> error $ - "toRowData: failed to encode as row data. \n" <> show encV - Just r -> r - where - encV = J.encode v - -rewindPastMinBlockHeightFails :: () - => RocksDb - -> TestTree -rewindPastMinBlockHeightFails rdb = - compactionSetup "rewindPastMinBlockHeightFails" rdb testPactServiceConfig $ \cr -> do - replicateM_ 10 $ runBlock cr.srcPactQueue cr.blockDb second - - sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight 5) - - -- Genesis block header; compacted away by now - let bh = genesisBlockHeader testVersion cid - - syncResult <- try (pactSyncToBlock bh cr.targetPactQueue) - case syncResult of - Left (BlockHeaderLookupFailure {}) -> do - return () - Left err -> do - assertFailure $ "Expected a BlockHeaderLookupFailure, but got: " ++ show err - Right _ -> do - assertFailure "Expected an exception, but didn't encounter one." - -pactStateSamePreAndPostCompaction :: () - => RocksDb - -> TestTree -pactStateSamePreAndPostCompaction rdb = - compactionSetup "pactStateSamePreAndPostCompaction" rdb testPactServiceConfig $ \cr -> do - let numBlocks :: Num a => a - numBlocks = 100 - - let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction - makeTx nth bct = buildCwCmd (sshow (nth, bct)) testVersion - $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] - $ set cbChainId cid - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") - $ defaultCmd - - replicateM_ numBlocks $ do - runTxInBlock_ cr.mempoolRef cr.srcPactQueue cr.blockDb - $ \n _ _ bct -> makeTx n bct - - statePreCompaction <- getLatestPactState cr.srcSqlEnv - sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight numBlocks) - statePostCompaction <- getLatestPactState cr.targetSqlEnv - - comparePactStateBeforeAndAfter statePreCompaction statePostCompaction - -compactionIsIdempotent :: () - => RocksDb - -> TestTree -compactionIsIdempotent rdb = - -- This requires a bit more than 'compactionSetup', since we - -- are compacting more than once. - withResourceT (withTempDir "pact-dir") $ \twiceDir -> withSqliteDb cid twiceDir $ \twiceSqlEnvIO -> - compactionSetup "compactionIsIdempotent" rdb testPactServiceConfig $ \cr -> do - let numBlocks :: Num a => a - numBlocks = 100 - - let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction - makeTx nth bct = buildCwCmd (sshow (nth, bct)) testVersion - $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] - $ set cbChainId cid - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") - $ defaultCmd - - replicateM_ numBlocks $ do - runTxInBlock_ cr.mempoolRef cr.srcPactQueue cr.blockDb - $ \n _ _ bct -> makeTx n bct - - twiceSqlEnv <- twiceSqlEnvIO - let targetHeight = BlockHeight numBlocks - -- Compact 'src' into 'target' - sigmaCompact cr.srcSqlEnv cr.targetSqlEnv targetHeight - -- Get table contents of 'target' - statePostCompaction1 <- getPactUserTables cr.targetSqlEnv - -- Compact 'target' into 'twice' - sigmaCompact cr.targetSqlEnv twiceSqlEnv targetHeight - -- Get table state of 'twice' - statePostCompaction2 <- getPactUserTables twiceSqlEnv - - -- In order to use `comparePactStateBeforeAndAfter`, we need to ensure that the rows are properly compacted, - -- and then put them into a map. - let ensureIsCompactedAndSortRows :: M.Map Text [PactRow] -> IO (M.Map Text (M.Map Text PS.PactRowContents)) - ensureIsCompactedAndSortRows state = do - flip M.traverseWithKey state $ \_ rows -> do - let sortedRows = List.sort rows - assertBool "Each rowkey only has one entry" $ - List.sort (List.nubBy (\r1 r2 -> r1.rowKey == r2.rowKey) rows) == sortedRows - pure $ M.fromList $ List.map (\r -> (T.decodeUtf8 r.rowKey, PS.PactRowContents r.rowData r.txId)) sortedRows - - state1 <- ensureIsCompactedAndSortRows statePostCompaction1 - state2 <- ensureIsCompactedAndSortRows statePostCompaction2 - - comparePactStateBeforeAndAfter state1 state2 - -compactionDoesNotDisruptDuplicateDetection :: () - => RocksDb - -> TestTree -compactionDoesNotDisruptDuplicateDetection rdb = do - compactionSetup "compactionDoesNotDisruptDuplicateDetection" rdb testPactServiceConfig $ \cr -> do - let makeTx :: IO Pact4.Transaction - makeTx = buildCwCmd (sshow @Word 0) testVersion - $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] - $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") - $ defaultCmd - - e1 <- runTxInBlock cr.mempoolRef cr.srcPactQueue cr.blockDb (\_ _ _ _ -> makeTx) - assertBool "First tx submission succeeds" (isRight e1) - - sigmaCompact cr.srcSqlEnv cr.targetSqlEnv =<< PS.getLatestBlockHeight cr.srcSqlEnv - - e2 <- runTxInBlock cr.mempoolRef cr.targetPactQueue cr.blockDb (\_ _ _ _ -> makeTx) - assertBool "First tx submission fails" (all (null . _payloadWithOutputsTransactions) e2) - --- | Test that user tables created before the compaction height are kept, --- while those created after the compaction height are dropped. -compactionUserTablesDropped :: () - => RocksDb - -> TestTree -compactionUserTablesDropped rdb = - let - -- creating a module uses about 60k gas. this is - -- that plus some change. - gasLimit :: GasLimit - gasLimit = 70_000 - - pactCfg = testPactServiceConfig { - _pactNewBlockGasLimit = gasLimit - } - in - compactionSetup "compactionUserTablesDropped" rdb pactCfg $ \cr -> do - let numBlocks :: Num a => a - numBlocks = 100 - let halfwayPoint :: Integral a => a - halfwayPoint = numBlocks `div` 2 - - let createTable :: Word -> Text -> IO Pact4.Transaction - createTable n tblName = do - let tx = T.unlines - [ "(namespace 'free)" - , "(module m" <> sshow n <> " G" - , " (defcap G () true)" - , " (defschema empty-schema)" - , " (deftable " <> tblName <> ":{empty-schema})" - , ")" - , "(create-table " <> tblName <> ")" - ] - buildCwCmd (sshow n) testVersion - $ signSender00 - $ set cbGasLimit gasLimit - $ set cbRPC (mkExec tx (mkKeySetData "sender00" [sender00])) - $ defaultCmd - - let beforeTable = "test_before" - let afterTable = "test_after" - - supply <- newIORef @Word 0 - madeBeforeTable <- newIORef @Bool False - madeAfterTable <- newIORef @Bool False - replicateM_ numBlocks $ do - setMempool cr.mempoolRef $ mempty { - mpaGetBlock = \_ validate mBlockHeight mBlockHash _ -> do - let mkTable madeRef tbl = do - madeYet <- readIORef madeRef - if madeYet - then do - pure mempty - else do - n <- atomicModifyIORef' supply $ \a -> (a + 1, a) - tx <- createTable n tbl - writeIORef madeRef True - [Right t] <- - V.toList <$> validate mBlockHeight mBlockHash ((fmap . fmap . fmap) _pcCode $ V.singleton tx) - pure (V.singleton t) - - if mBlockHeight <= halfwayPoint - then do - mkTable madeBeforeTable beforeTable - else do - mkTable madeAfterTable afterTable - } - void $ runBlock cr.srcPactQueue cr.blockDb second - - let freeBeforeTbl = "free.m0_" <> beforeTable - let freeAfterTbl = "free.m1_" <> afterTable - - statePre <- getPactUserTables cr.srcSqlEnv - forM_ [freeBeforeTbl, freeAfterTbl] $ \tbl -> do - let msg = "Table " ++ T.unpack tbl ++ " should exist pre-compaction, but it doesn't." - assertBool msg (isJust (M.lookup tbl statePre)) - - sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight halfwayPoint) - - statePost <- getPactUserTables cr.targetSqlEnv - flip assertBool (isJust (M.lookup freeBeforeTbl statePost)) $ - T.unpack beforeTable ++ " was dropped; it wasn't supposed to be." - - flip assertBool (isNothing (M.lookup freeAfterTbl statePost)) $ - T.unpack afterTable ++ " wasn't dropped; it was supposed to be." - -compactionGrandHashUnchanged :: () - => RocksDb - -> TestTree -compactionGrandHashUnchanged rdb = - compactionSetup "compactionGrandHashUnchanged" rdb testPactServiceConfig $ \cr -> do - setOneShotMempool cr.mempoolRef =<< goldenMemPool - - let numBlocks :: Num a => a - numBlocks = 100 - - let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction - makeTx nth bct = buildCwCmd (sshow nth) testVersion - $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] - $ set cbChainId cid - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") - $ defaultCmd - - replicateM_ numBlocks - $ runTxInBlock_ cr.mempoolRef cr.srcPactQueue cr.blockDb - $ \n _ _ bct -> makeTx n bct - - let targetHeight = BlockHeight numBlocks - - hashPreCompaction <- computeGrandHash (PS.getLatestPactStateAt cr.srcSqlEnv targetHeight) - sigmaCompact cr.srcSqlEnv cr.targetSqlEnv targetHeight - hashPostCompaction <- computeGrandHash (PS.getLatestPactStateAt cr.targetSqlEnv targetHeight) - - assertEqual "GrandHash pre- and post-compaction are the same" hashPreCompaction hashPostCompaction - -compactionResilientToRowIdOrdering :: () - => RocksDb - -> TestTree -compactionResilientToRowIdOrdering rdb = - compactionSetup "compactionResilientToRowIdOrdering" rdb testPactServiceConfig $ \cr -> do - - let numBlocks :: Num a => a - numBlocks = 100 - - -- Just run a bunch of blocks - setOneShotMempool cr.mempoolRef =<< goldenMemPool - let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction - makeTx nth bct = buildCwCmd (sshow nth) testVersion - $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] - $ set cbChainId cid - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") - $ defaultCmd - replicateM_ numBlocks - $ runTxInBlock_ cr.mempoolRef cr.srcPactQueue cr.blockDb - $ \n _ _ bct -> makeTx n bct - - -- Get the state after running the blocks but before doing anything else - statePreCompaction <- getLatestPactState cr.srcSqlEnv - - -- Reverse all of the rowids in the table. We get all the rows in txid DESC order, like so: - -- rk1, txid=100, rowid=100 - -- rk1, txid=99, rowid=99 - -- ... - -- - -- Then we reverse the rowids, so that the table looks like this: - -- rk1, txid=100, rowid=10_000 - -- rk1, txid=99, rowid=10_001 - -- ... - -- - -- Since the compaction algorithm orders by rowid DESC, it will get the rows in reverse order to how they were inserted. - -- If compaction still results in the same end state, this confirms that the compaction algorithm is resilient to rowid ordering. - e <- PS.qryStream cr.srcSqlEnv "SELECT rowkey, txid FROM [coin_coin-table] ORDER BY txid ASC" [] [Pact.RText, Pact.RInt] $ \rows -> do - Lite.withStatement cr.srcSqlEnv "UPDATE [coin_coin-table] SET rowid = ?3 WHERE rowkey = ?1 AND txid = ?2" $ \stmt -> do - flip S.mapM_ (S.zip (S.enumFrom @_ @(Down Int64) 10_000) rows) $ \(Down newRowId, row) -> case row of - [Pact.SText rowkey, Pact.SInt txid] -> do - Pact.bindParams stmt [Pact.SText rowkey, Pact.SInt txid, Pact.SInt newRowId] - stepThenReset stmt - - _ -> error "unexpected row shape" - assertBool "Didn't encounter a sqlite error during rowid shuffling" (isRight e) - - -- Compact to the tip - sigmaCompact cr.srcSqlEnv cr.targetSqlEnv (BlockHeight numBlocks) - - -- Get the state post-randomisation and post-compaction - statePostCompaction <- getLatestPactState cr.targetSqlEnv - - -- Same logic as in 'pactStateSamePreAndPostCompaction' - comparePactStateBeforeAndAfter statePreCompaction statePostCompaction - -comparePactStateBeforeAndAfter :: (Ord k, Eq a, Show k, Show a) => M.Map Text (M.Map k a) -> M.Map Text (M.Map k a) -> IO () -comparePactStateBeforeAndAfter statePreCompaction statePostCompaction = do - let stateDiff = M.filter (not . PatienceM.isSame) (PatienceM.diff statePreCompaction statePostCompaction) - when (not (null stateDiff)) $ do - T.putStrLn "" - forM_ (M.toList stateDiff) $ \(tbl, delta) -> do - T.putStrLn "" - T.putStrLn tbl - case delta of - Same _ -> do - pure () - Old x -> do - putStrLn $ "a pre-only value appeared in the pre- and post-compaction diff: " ++ show x - New x -> do - putStrLn $ "a post-only value appeared in the pre- and post-compaction diff: " ++ show x - Delta x1 x2 -> do - let daDiff = M.filter (not . PatienceM.isSame) (PatienceM.diff x1 x2) - forM_ daDiff $ \item -> do - case item of - Old x -> do - putStrLn $ "old: " ++ show x - New x -> do - putStrLn $ "new: " ++ show x - Same _ -> do - pure () - Delta x y -> do - putStrLn $ "old: " ++ show x - putStrLn $ "new: " ++ show y - putStrLn "" - assertFailure "pact state check failed" - - --- pactStateSamePreAndPostCompaction :: () --- => RocksDb --- -> TestTree --- pactStateSamePreAndPostCompaction rdb = --- compactionSetup "pactStateSamePreAndPostCompaction" rdb testPactServiceConfig $ \cr -> do --- let numBlocks :: Num a => a --- numBlocks = 100 - --- let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction --- makeTx nth bct = buildCwCmd (sshow (nth, bct)) testVersion --- $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] --- $ set cbChainId cid --- $ set cbCreationTime (toTxCreationTime $ _bct bct) --- $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") --- $ defaultCmd - --- replicateM_ numBlocks $ do --- runTxInBlock_ cr.mempoolRef cr.pactQueue cr.blockDb --- $ \n _ _ bct -> makeTx n bct - --- let db = cr.sqlEnv - --- statePreCompaction <- getLatestPactState db --- compact Error [C.NoVacuum] cr.sqlEnv (C.Target (BlockHeight numBlocks)) - --- statePostCompaction <- getLatestPactState db - --- let stateDiff = M.filter (not . PatienceM.isSame) (PatienceM.diff statePreCompaction statePostCompaction) --- when (not (null stateDiff)) $ do --- T.putStrLn "" --- forM_ (M.toList stateDiff) $ \(tbl, delta) -> do --- T.putStrLn "" --- T.putStrLn tbl --- case delta of --- Same _ -> do --- pure () --- Old x -> do --- putStrLn $ "a pre-only value appeared in the pre- and post-compaction diff: " ++ show x --- New x -> do --- putStrLn $ "a post-only value appeared in the pre- and post-compaction diff: " ++ show x --- Delta x1 x2 -> do --- let daDiff = M.filter (not . PatienceM.isSame) (PatienceM.diff x1 x2) --- forM_ daDiff $ \item -> do --- case item of --- Old x -> do --- putStrLn $ "old: " ++ show x --- New x -> do --- putStrLn $ "new: " ++ show x --- Same _ -> do --- pure () --- Delta x y -> do --- putStrLn $ "old: " ++ show x --- putStrLn $ "new: " ++ show y --- putStrLn "" --- assertFailure "pact state check failed" - --- compactionIsIdempotent :: () --- => RocksDb --- -> TestTree --- compactionIsIdempotent rdb = --- compactionSetup "compactionIdempotent" rdb testPactServiceConfig $ \cr -> do --- let numBlocks :: Num a => a --- numBlocks = 100 - --- let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction --- makeTx nth bct = buildCwCmd (sshow (nth, bct)) testVersion --- $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] --- $ set cbChainId cid --- $ set cbCreationTime (toTxCreationTime $ _bct bct) --- $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") --- $ defaultCmd - --- replicateM_ numBlocks $ do --- runTxInBlock_ cr.mempoolRef cr.pactQueue cr.blockDb --- $ \n _ _ bct -> makeTx n bct - --- let db = cr.sqlEnv - --- let compact h = --- compact Error [C.NoVacuum] cr.sqlEnv h - --- let compactionHeight = C.Target (BlockHeight numBlocks) --- compact compactionHeight --- statePostCompaction1 <- getPactUserTables db --- compact compactionHeight --- statePostCompaction2 <- getPactUserTables db - --- let stateDiff = M.filter (not . PatienceM.isSame) (PatienceM.diff statePostCompaction1 statePostCompaction2) --- when (not (null stateDiff)) $ do --- T.putStrLn "" --- forM_ (M.toList stateDiff) $ \(tbl, delta) -> do --- T.putStrLn "" --- T.putStrLn tbl --- case delta of --- Same _ -> do --- pure () --- Old x -> do --- putStrLn $ "a pre-only value appeared in the compaction idempotency diff: " ++ show x --- New x -> do --- putStrLn $ "a post-only value appeared in the compaction idempotency diff: " ++ show x --- Delta x1 x2 -> do --- let daDiff = PatienceL.pairItems (\a b -> rowKey a == rowKey b) (PatienceL.diff x1 x2) --- forM_ daDiff $ \item -> do --- case item of --- Old x -> do --- putStrLn $ "old: " ++ show x --- New x -> do --- putStrLn $ "new: " ++ show x --- Same _ -> do --- pure () --- Delta x y -> do --- putStrLn $ "old: " ++ show x --- putStrLn $ "new: " ++ show y --- putStrLn "" --- assertFailure "pact state check failed" - --- compactionDoesNotDisruptDuplicateDetection :: () --- => RocksDb --- -> TestTree --- compactionDoesNotDisruptDuplicateDetection rdb = do --- compactionSetup "compactionDoesNotDisruptDuplicateDetection" rdb testPactServiceConfig $ \cr -> do --- let makeTx :: IO Pact4.Transaction --- makeTx = buildCwCmd (sshow @Word 0) testVersion --- $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] --- $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") --- $ defaultCmd - --- let run = do --- runTxInBlock cr.mempoolRef cr.pactQueue cr.blockDb --- $ \_ _ _ _ -> makeTx - --- run >>= \e -> assertBool "First tx submission succeeds" (isRight e) --- compact Error [C.NoVacuum] cr.sqlEnv C.LatestUnsafe --- run >>= \e -> assertEqual "First tx submission fails" --- (V.null . _payloadWithOutputsTransactions <$> e) --- (Right True) - --- pure () - --- -- | Test that user tables created before the compaction height are kept, --- -- while those created after the compaction height are dropped. --- compactionUserTablesDropped :: () --- => RocksDb --- -> TestTree --- compactionUserTablesDropped rdb = --- let --- -- creating a module uses about 60k gas. this is --- -- that plus some change. --- gasLimit :: GasLimit --- gasLimit = 70_000 - --- pactCfg = testPactServiceConfig { --- _pactNewBlockGasLimit = gasLimit --- } --- in --- compactionSetup "compactionUserTablesDropped" rdb pactCfg $ \cr -> do --- let numBlocks :: Num a => a --- numBlocks = 100 --- let halfwayPoint :: Integral a => a --- halfwayPoint = numBlocks `div` 2 - --- let createTable :: Word -> Text -> IO Pact4.Transaction --- createTable n tblName = do --- let tx = T.unlines --- [ "(namespace 'free)" --- , "(module m" <> sshow n <> " G" --- , " (defcap G () true)" --- , " (defschema empty-schema)" --- , " (deftable " <> tblName <> ":{empty-schema})" --- , ")" --- , "(create-table " <> tblName <> ")" --- ] --- buildCwCmd (sshow n) testVersion --- $ signSender00 --- $ set cbGasLimit gasLimit --- $ set cbRPC (mkExec tx (mkKeySetData "sender00" [sender00])) --- $ defaultCmd - --- let beforeTable = "test_before" --- let afterTable = "test_after" - --- supply <- newIORef @Word 0 --- madeBeforeTable <- newIORef @Bool False --- madeAfterTable <- newIORef @Bool False --- replicateM_ numBlocks $ do --- setMempool cr.mempoolRef $ mempty { --- mpaGetBlock = \_ validate mBlockHeight mBlockHash _ -> do --- let mkTable madeRef tbl = do --- madeYet <- readIORef madeRef --- if madeYet --- then do --- pure mempty --- else do --- n <- atomicModifyIORef' supply $ \a -> (a + 1, a) --- tx <- createTable n tbl --- writeIORef madeRef True --- [Right t] <- --- V.toList <$> validate mBlockHeight mBlockHash ((fmap . fmap . fmap) _pcCode $ V.singleton tx) --- pure (V.singleton t) - --- if mBlockHeight <= halfwayPoint --- then do --- mkTable madeBeforeTable beforeTable --- else do --- mkTable madeAfterTable afterTable --- } --- void $ runBlock cr.pactQueue cr.blockDb second - --- let freeBeforeTbl = "free.m0_" <> beforeTable --- let freeAfterTbl = "free.m1_" <> afterTable - --- let db = cr.sqlEnv - --- statePre <- getPactUserTables db --- let assertExists tbl = do --- let msg = "Table " ++ T.unpack tbl ++ " should exist pre-compaction, but it doesn't." --- assertBool msg (isJust (M.lookup tbl statePre)) --- assertExists freeBeforeTbl --- assertExists freeAfterTbl - --- compact Error [C.NoVacuum] cr.sqlEnv (C.Target (BlockHeight halfwayPoint)) - --- statePost <- getPactUserTables db --- flip assertBool (isJust (M.lookup freeBeforeTbl statePost)) $ --- T.unpack beforeTable ++ " was dropped; it wasn't supposed to be." - --- flip assertBool (isNothing (M.lookup freeAfterTbl statePost)) $ --- T.unpack afterTable ++ " wasn't dropped; it was supposed to be." - --- compactionGrandHashUnchanged :: () --- => RocksDb --- -> TestTree --- compactionGrandHashUnchanged rdb = --- compactionSetup "compactionGrandHashUnchanged" rdb testPactServiceConfig $ \cr -> do --- setOneShotMempool cr.mempoolRef =<< goldenMemPool - --- let numBlocks :: Num a => a --- numBlocks = 100 - --- let makeTx :: Word -> BlockCreationTime -> IO Pact4.Transaction --- makeTx nth bct = buildCwCmd (sshow nth) testVersion --- $ set cbSigners [mkEd25519Signer' sender00 [mkGasCap, mkTransferCap "sender00" "sender01" 1.0]] --- $ set cbCreationTime (toTxCreationTime $ _bct bct) --- $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 1.0)") --- $ defaultCmd - --- replicateM_ numBlocks --- $ runTxInBlock_ cr.mempoolRef cr.pactQueue cr.blockDb --- $ \n _ _ blockHeader -> makeTx n blockHeader - --- let db = cr.sqlEnv --- let targetHeight = BlockHeight numBlocks - --- hashPreCompaction <- computeGrandHash (PS.getLatestPactStateAt db targetHeight) --- compact Error [C.NoVacuum] cr.sqlEnv (C.Target targetHeight) --- hashPostCompaction <- computeGrandHash (PS.getLatestPactStateAt db targetHeight) - --- assertEqual "GrandHash pre- and post-compaction are the same" hashPreCompaction hashPostCompaction - -getHistory :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -getHistory refIO reqIO = testCase "getHistory" $ do - (_, q, bdb) <- reqIO - setOneShotMempool refIO =<< goldenMemPool - void $ runBlock q bdb second - h <- getParentTestBlockDb bdb cid - Historical (BlockTxHistory hist prevBals) <- pactBlockTxHistory h - (PCore.DUserTables (PCore.TableName "coin-table" (PCore.ModuleName "coin" Nothing))) - q - -- just check first one here - assertEqual "check first entry of history" - (Just [PCore.TxLog "coin_coin-table" "sender00" - (toRowData $ object - [ "guard" .= object - [ "pred" .= ("keys-all" :: T.Text) - , "keys" .= - ["368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca" :: T.Text] - ] - , "balance" .= Number 99_999_900.0 - ])]) - (M.lookup 10 hist) - -- and transaction txids - assertEqual "check txids" - [7,10,12,13,15,16,18,19,21,22,24,25,27,28,30,31,33,34,36,37,39,40,42] - (M.keys hist) - -- and last tx log change for accounts touched in given block - assertEqual "check previous balance" - (M.fromList - [(RowKey "sender00", - (PCore.TxLog "coin_coin-table" "sender00" - (toRowData $ object - [ "guard" .= object - [ "pred" .= ("keys-all" :: T.Text) - , "keys" .= - ["368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca" :: T.Text] - ] - , "balance" .= (Number 100000000.0) - ] - ) - ))]) - prevBals - -getHistoricalLookupNoTxs - :: T.Text - -> (Maybe (PCore.TxLog PCore.RowData) -> IO ()) - -> IO (IORef MemPoolAccess) - -> IO (SQLiteEnv, PactQueue, TestBlockDb) - -> TestTree -getHistoricalLookupNoTxs key assertF refIO reqIO = - testCase (T.unpack ("getHistoricalLookupNoTxs: " <> key)) $ do - (_, q, bdb) <- reqIO - setOneShotMempool refIO mempty - void $ runBlock q bdb second - h <- getParentTestBlockDb bdb cid - histLookup q h key >>= assertF - -getHistoricalLookupWithTxs - :: T.Text - -> (Maybe (PCore.TxLog PCore.RowData) -> IO ()) - -> IO (IORef MemPoolAccess) - -> IO (SQLiteEnv, PactQueue, TestBlockDb) - -> TestTree -getHistoricalLookupWithTxs key assertF refIO reqIO = - testCase (T.unpack ("getHistoricalLookupWithTxs: " <> key)) $ do - (_, q, bdb) <- reqIO - setOneShotMempool refIO =<< goldenMemPool - void $ runBlock q bdb second - h <- getParentTestBlockDb bdb cid - histLookup q h key >>= assertF - -histLookup :: PactQueue -> BlockHeader -> T.Text -> IO (Maybe (PCore.TxLog PCore.RowData)) -histLookup q bh k = - throwIfNoHistory =<< pactHistoricalLookup bh - (PCore.DUserTables (PCore.TableName "coin-table" (PCore.ModuleName "coin" Nothing))) - (PCore.RowKey k) q - -assertSender00Bal :: Rational -> String -> Maybe (PCore.TxLog PCore.RowData) -> Assertion -assertSender00Bal bal msg hist = - assertEqual msg - (Just (PCore.TxLog "coin_coin-table" "sender00" - (toRowData $ object - [ "guard" .= object - [ "pred" .= ("keys-all" :: T.Text) - , "keys" .= - ["368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca" :: T.Text] - ] - , "balance" .= Number (fromRational bal) - ]))) - hist - -signSender00 :: CmdBuilder -> CmdBuilder -signSender00 = set cbSigners [mkEd25519Signer' sender00 []] - --- this test relies on block gas errors being thrown before other Pact errors. -blockGasLimitTest :: HasCallStack => IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -blockGasLimitTest _ reqIO = testCase "blockGasLimitTest" $ do - (_, q, _) <- reqIO - - let - useGas g = do - bigTx <- buildCwCmd "cmd" testVersion - $ set cbGasLimit g $ signSender00 $ set cbRPC (mkExec' "TESTING") defaultCmd - let - cr = CommandResult - (RequestKey (Hash "0")) Nothing - (PactResult $ Left $ PactError EvalError (Pact.Types.Info.Info Nothing) [] mempty) - (fromIntegral g) Nothing Nothing Nothing [] - block = Transactions - (V.singleton (bigTx, cr)) - (CommandResult (RequestKey (Hash "h")) Nothing - (PactResult $ Right $ pString "output") 0 Nothing Nothing Nothing []) - payload = toPayloadWithOutputs Pact4T noMiner block - bh = newBlockHeader - mempty - (_payloadWithOutputsPayloadHash payload) - (Nonce 0) - (BlockCreationTime $ Time $ TimeSpan 0) - (ParentHeader $ genesisBlockHeader testVersion cid) - try $ validateBlock bh (CheckablePayloadWithOutputs payload) q - -- we consume slightly more than the maximum block gas limit and provoke an error. - useGas 2_000_001 >>= \case - Left (BlockGasLimitExceeded _) -> - return () - r -> - error $ "not a BlockGasLimitExceeded error: " <> ppShow r - -- we consume much more than the maximum block gas limit and expect an error. - useGas 3_000_000 >>= \case - Left (BlockGasLimitExceeded _) -> - return () - r -> - error $ "not a BlockGasLimitExceeded error: " <> ppShow r - -- we consume exactly the maximum block gas limit and expect no such error. - -- our block is otherwise invalid, so we do expect a validation error - useGas 2_000_000 >>= \case - Left (BlockGasLimitExceeded _) -> - error "consumed exactly block gas limit but errored" - Left (BlockValidationFailure _) -> - return () - Left err -> - error $ "failed with other error: " <> sshow err - Right _ -> - error "succeeded with invalid block" - -- we consume much less than the maximum block gas limit and expect no gas error. - -- again our block is otherwise invalid, so we do expect a validation error - useGas 1_000_000 >>= \case - Left (BlockGasLimitExceeded _) -> - error "consumed much less than block gas limit but errored" - Left (BlockValidationFailure _) -> - return () - Left err -> - error $ "failed with other error: " <> sshow err - Right _ -> - error "succeeded with invalid block" - -- we consume 1 gas and expect no gas error. again our block is otherwise - -- invalid, so we do expect an validation error. - -- we cannot consume 0 gas, or the coin contract will fail to buy gas. - useGas 1 >>= \case - Left (BlockGasLimitExceeded _) -> - error "consumed no gas but errored" - Left (BlockValidationFailure _) -> - return () - Left err -> - error $ "failed with other error: " <> sshow err - Right _ -> - error "succeeded with invalid block" - -mempoolRefillTest :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -mempoolRefillTest mpRefIO reqIO = testCase "mempoolRefillTest" $ do - - (_, q, bdb) <- reqIO - supply <- newMVar (0 :: Int) - - mp supply [ ( 0, [goodTx, goodTx] ), ( 1, [badTx] ) ] - runBlock q bdb second >>= checkCount 2 - - mp supply [ ( 0, [goodTx, goodTx] ), ( 1, [goodTx, badTx] ) ] - runBlock q bdb second >>= checkCount 3 - - mp supply [ ( 0, [badTx, goodTx] ), ( 1, [goodTx, badTx] ) ] - runBlock q bdb second >>= checkCount 2 - - mp supply [ ( 0, [badTx] ), ( 1, [goodTx, goodTx] ) ] - runBlock q bdb second >>= checkCount 2 - - mp supply [ ( 0, [goodTx, goodTx] ), ( 1, [badTx, badTx] ) ] - runBlock q bdb second >>= checkCount 2 - - where - - checkCount :: HasCallStack => Int -> PayloadWithOutputs_ a -> Assertion - checkCount n = assertEqual "tx return count" n . V.length . _payloadWithOutputsTransactions - - mp supply txRefillMap = setMempool mpRefIO $ mempty { - mpaGetBlock = \BlockFill{..} validate bheight bhash bct -> - case M.lookup _bfCount (M.fromList txRefillMap) of - Nothing -> return mempty - Just txs -> do - tos <- validate bheight bhash =<< (fmap (V.fromList . (fmap . fmap . fmap) _pcCode) $ sequence $ map (next supply bct) txs) - return $ V.fromList [t | Right t <- V.toList tos] - } - - next supply bh f = do - i <- modifyMVar supply $ return . (succ &&& id) - f i bh - - goodTx i bct = buildCwCmd (sshow (i, bct)) testVersion - $ signSender00 - $ set cbChainId cid - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC (mkExec' "(+ 1 2)") - $ defaultCmd - - badTx i bct = buildCwCmd (sshow (i, bct)) testVersion - $ signSender00 - $ set cbSender "bad" - $ set cbChainId cid - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC (mkExec' "(+ 1 2)") - $ defaultCmd - -moduleNameFork :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -moduleNameFork mpRefIO reqIO = testCase "moduleNameFork" $ do - - (_, q, bdb) <- reqIO - - -- install in free in block 1 - setOneShotMempool mpRefIO (moduleNameMempool "free" "test") - void $ runBlock q bdb second - - -- install in user in block 2 - setOneShotMempool mpRefIO (moduleNameMempool "user" "test") - void $ runBlock q bdb second - - -- do something else post-fork - setOneShotMempool mpRefIO (moduleNameMempool "free" "test2") - void $ runBlock q bdb second - setOneShotMempool mpRefIO (moduleNameMempool "user" "test2") - void $ runBlock q bdb second - - -- TODO this test doesn't actually validate, I turn on Debug and make sure it - -- goes well. - -moduleNameMempool :: T.Text -> T.Text -> MemPoolAccess -moduleNameMempool ns mn = mempty - { mpaGetBlock = \_ validate bheight bhash bct -> do - let txs = - [ "(namespace '" <> ns <> ") (module " <> mn <> " G (defcap G () (enforce false 'cannotupgrade)))" - , ns <> "." <> mn <> ".G" - ] - builtTxs <- fmap V.fromList $ forM (zip txs [0..]) $ \(code,n :: Int) -> - buildCwCmd ("1" <> sshow n) testVersion $ - signSender00 $ - set cbCreationTime (toTxCreationTime $ _bct bct) $ - set cbRPC (mkExec' code) $ - defaultCmd - tos <- validate bheight bhash $ (fmap . fmap . fmap) _pcCode builtTxs - return $ V.fromList [t | Right t <- V.toList tos ] - -- undefined - } - - -mempoolCreationTimeTest :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -mempoolCreationTimeTest mpRefIO reqIO = testCase "mempoolCreationTimeTest" $ do - - (_, q, bdb) <- reqIO - - let start@(Time startSpan) :: Time Micros = Time (TimeSpan (Micros 100_000_000)) - s30 = scaleTimeSpan (30 :: Int) second - s15 = scaleTimeSpan (15 :: Int) second - -- b1 block time is start - void $ runBlock q bdb startSpan - - - -- do pre-insert check with transaction at start + 15s - tx <- makeTx "tx-now" (add s15 start) - void $ pactPreInsertCheck (V.singleton $ fmap (fmap _pcCode) tx) q - - setOneShotMempool mpRefIO $ mp tx - -- b2 will be made at start + 30s - void $ runBlock q bdb s30 - - where - - makeTx nonce t = buildCwCmd (sshow t <> nonce) testVersion - $ signSender00 - $ set cbChainId cid - $ set cbCreationTime (toTxCreationTime t) - $ set cbTTL 300 - $ set cbRPC (mkExec' "1") - $ defaultCmd - mp tx = mempty { - mpaGetBlock = \_ valid bh bhash _ -> getBlock bh bhash tx valid - } - - getBlock bh bhash tx valid = do - let txs = V.singleton $ (fmap . fmap) _pcCode tx - [Right t] <- V.toList <$> valid bh bhash txs - return (V.singleton t) - -preInsertCheckTimeoutTest :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -preInsertCheckTimeoutTest _ reqIO = testCase "preInsertCheckTimeoutTest" $ do - (_, q, _) <- reqIO - - coinV3 <- T.readFile "pact/coin-contract/v3/coin-v3.pact" - coinV4 <- T.readFile "pact/coin-contract/v4/coin-v4.pact" - coinV5 <- T.readFile "pact/coin-contract/v5/coin-v5.pact" - - txCoinV3 <- buildCwCmd "tx-now-coinv3" testVersion - $ signSender00 - $ set cbChainId cid - $ set cbRPC (mkExec' coinV3) - $ defaultCmd - - txCoinV4 <- buildCwCmd "tx-now-coinv4" testVersion - $ signSender00 - $ set cbChainId cid - $ set cbRPC (mkExec' coinV4) - $ defaultCmd - - txCoinV5 <- buildCwCmd "tx-now-coinv5" testVersion - $ signSender00 - $ set cbChainId cid - $ set cbRPC (mkExec' coinV5) - $ defaultCmd - - -- timeouts are tricky to trigger in GH actions. - -- we're satisfied if it's triggered once in 100 runs. - rs <- replicateM 100 - (pactPreInsertCheck ((fmap . fmap . fmap) _pcCode $ V.fromList [txCoinV3, txCoinV4, txCoinV5]) q) - assertBool "should get at least one InsertErrorTimedOut" $ any - (V.all (== Just InsertErrorTimedOut)) - rs - -badlistNewBlockTest :: IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -badlistNewBlockTest mpRefIO reqIO = testCase "badlistNewBlockTest" $ do - (_, reqQ, _) <- reqIO - let hashToTxHashList = V.singleton . pact4RequestKeyToTransactionHash . RequestKey . toUntypedHash @'Blake2b_256 - badHashRef <- newIORef $ hashToTxHashList initialHash - badTx <- buildCwCmd "badListMPA" testVersion - $ signSender00 - -- this should exceed the account balance - $ set cbGasLimit 99_999 - $ set cbGasPrice 1_000_000_000_000_000 - $ set cbRPC (mkExec' "(+ 1 2)") - $ defaultCmd - setOneShotMempool mpRefIO (badlistMPA badTx badHashRef) - bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) reqQ - let resp = forAnyPactVersion finalizeBlock bip - assertEqual "bad tx filtered from block" mempty (_payloadWithOutputsTransactions resp) - badHash <- readIORef badHashRef - assertEqual "Badlist should have badtx hash" (hashToTxHashList $ _cmdHash badTx) badHash - where - badlistMPA badTx badHashRef = mempty - { mpaGetBlock = \_ valid bheight bhash _ -> do - [Right t] <- V.toList <$> valid bheight bhash (V.singleton $ (fmap . fmap) _pcCode badTx) - return (V.singleton t) - , mpaBadlistTx = \v -> writeIORef badHashRef v - } - -goldenNewBlock :: String -> IO MemPoolAccess -> IO (IORef MemPoolAccess) -> IO (SQLiteEnv, PactQueue, TestBlockDb) -> TestTree -goldenNewBlock name mpIO mpRefIO reqIO = golden name $ do - mp <- mpIO - (_, reqQ, _) <- reqIO - setOneShotMempool mpRefIO mp - blockInProgress <- throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader genesisHeader) reqQ - let resp = forAnyPactVersion finalizeBlock blockInProgress - -- ensure all golden txs succeed - forM_ (_payloadWithOutputsTransactions resp) $ \(txIn,TransactionOutput out) -> do - cr :: CommandResult Hash <- decodeStrictOrThrow out - assertSatisfies ("golden tx succeeds, input: " ++ show txIn) (_crResult cr) (isRight . (\(PactResult r) -> r)) - case blockInProgress of - ForPact4 bip -> goldenBytes resp bip - ForPact5 _ -> error "pact 5 in goldenNewBlock" - where - hmToSortedList :: Ord k => HM.HashMap k v -> [(k, v)] - hmToSortedList = List.sortOn fst . HM.toList - -- missing some fields, only includes the fields that are "outputs" of - -- running txs, but not the module cache - blockInProgressToJSON BlockInProgress {..} = J.object - [ "blockGasLimit" J..= J.Aeson (fromIntegral @_ @Int _blockInProgressRemainingGasLimit) - , "parentHeader" J..= J.encodeWithAeson (_parentHeader $ fromJuste _blockInProgressParentHeader) - , "pendingData" J..= J.object - [ "pendingSuccessfulTxs" J..= J.array - (encodeB64UrlNoPaddingText <$> List.sort (toList _pendingSuccessfulTxs)) - , "pendingTableCreation" J..= J.array - (List.sort (toList _pendingTableCreation)) - , "pendingWrites" J..= pendingWritesJson - ] - , "txId" J..= J.Aeson (fromIntegral @_ @Int $ _blockHandleTxId _blockInProgressHandle) - ] - where - SQLitePendingData{..} = _blockHandlePending _blockInProgressHandle - pendingWritesJson = J.Object - [ (_dkTable, J.Object - [ (T.decodeUtf8 _dkRowKey, J.Object - [ ((sshow @_ @T.Text. fromIntegral @TxId @Word) _deltaTxId, T.decodeUtf8 _deltaData) - | SQLiteRowDelta {..} <- toList rowKeyWrites - ]) - | (_dkRowKey, rowKeyWrites) <- hmToSortedList tableWrites - ]) - | (_dkTable, tableWrites) <- hmToSortedList _pendingWrites - ] - - goldenBytes :: PayloadWithOutputs -> BlockInProgress Pact4 -> IO BL.ByteString - goldenBytes a b = return $ BL.fromStrict $ encodeYaml $ J.object - [ "test-group" J..= ("new-block" :: T.Text) - , "results" J..= J.encodeWithAeson a - , "blockInProgress" J..= blockInProgressToJSON b - ] - -goldenMemPool :: IO MemPoolAccess -goldenMemPool = do - moduleStr <- readFile' $ testPactFilesDir ++ "test1.pact" - let txs = - [ (T.pack moduleStr) - , "(create-table free.test1.accounts)" - , "(free.test1.create-global-accounts)" - , "(free.test1.transfer \"Acct1\" \"Acct2\" 1.00)" - , "(at 'prev-block-hash (chain-data))" - , "(at 'block-time (chain-data))" - , "(at 'block-height (chain-data))" - , "(at 'gas-limit (chain-data))" - , "(at 'gas-price (chain-data))" - , "(at 'chain-id (chain-data))" - , "(at 'sender (chain-data))" - ] - outTxs <- mkTxs txs - mempoolOf [outTxs] - where - mkTxs txs = - fmap V.fromList $ forM (zip txs [0..]) $ \(code,n :: Int) -> - buildCwCmd ("1" <> sshow n) testVersion $ - signSender00 $ - set cbGasPrice 0.01 $ - set cbTTL 1_000_000 $ -- match old goldens - set cbRPC (mkExec code $ mkKeySetData "test-admin-keyset" [sender00]) $ - defaultCmd - -mempoolOf :: [V.Vector Pact4.Transaction] -> IO MemPoolAccess -mempoolOf blocks = do - blocksRemainingRef <- newIORef blocks - return mempty - { mpaGetBlock = getTestBlock blocksRemainingRef - } - where - getTestBlock blocksRemainingRef _ validate bHeight bHash _parent = do - outtxs <- atomicModifyIORef' blocksRemainingRef $ \case - (b:bs) -> (bs, b) - [] -> ([], mempty) - oks <- validate bHeight bHash $ (fmap . fmap . fmap) _pcCode outtxs - unless (V.all isRight oks) $ fail $ mconcat - [ "tx failed validation! \nouttxs: \n" - , show outtxs - , "\n\noks: \n" - , show [ err | Left err <- V.toList oks ] - ] - return $ V.fromList [ t | Right t <- V.toList oks ] - - -data CompactionResources = CompactionResources - { mempoolRef :: IO (IORef MemPoolAccess) - , mempool :: MemPoolAccess - , srcSqlEnv :: SQLiteEnv - , targetSqlEnv :: SQLiteEnv - , srcPactQueue :: PactQueue - , targetPactQueue :: PactQueue - , blockDb :: TestBlockDb - } - -compactionSetup :: () - => String - -- ^ test pattern - -> RocksDb - -> PactServiceConfig - -> (CompactionResources -> IO ()) - -> TestTree -compactionSetup pat rdb pactCfg f = - withResourceT (withTempDir "src-pact-dir") $ \srcDir -> withSqliteDb cid srcDir $ \srcSqlEnvIO -> - withResourceT (withTempDir "src-pact-dir") $ \targetDir -> withSqliteDb cid targetDir $ \targetSqlEnvIO -> - withResourceT (mkTestBlockDb testVersion rdb) $ \blockDbIO -> - withDelegateMempool $ \dm -> - testCase pat $ do - blockDb <- blockDbIO - bhDb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb blockDb) cid - let payloadDb = _bdbPayloadDb blockDb - srcSqlEnv <- srcSqlEnvIO - targetSqlEnv <- targetSqlEnvIO - (mempoolRef, mempool) <- do - (ref, nonRef) <- dm - pure (pure ref, nonRef) - srcPactQueue <- newPactQueue 2_000 - targetPactQueue <- newPactQueue 2_000 - - let logger = genericLogger System.LogLevel.Error (\_ -> return ()) - - -- Start pact service for the src and target - let srcPactService = runPactService testVersion cid logger Nothing srcPactQueue mempool bhDb payloadDb srcSqlEnv pactCfg - let targetPactService = runPactService testVersion cid logger Nothing targetPactQueue mempool bhDb payloadDb targetSqlEnv pactCfg - - setOneShotMempool mempoolRef =<< goldenMemPool - - withAsync srcPactService $ \_ -> do - withAsync targetPactService $ \_ -> do - f $ CompactionResources - { mempoolRef = mempoolRef - , mempool = mempool - , srcSqlEnv = srcSqlEnv - , targetSqlEnv = targetSqlEnv - , srcPactQueue = srcPactQueue - , targetPactQueue = targetPactQueue - , blockDb = blockDb - } - - -runTxInBlock :: () - => IO (IORef MemPoolAccess) -- ^ mempoolRef - -> PactQueue - -> TestBlockDb - -> (Word -> BlockHeight -> BlockHash -> BlockCreationTime -> IO Pact4.Transaction) - -> IO (Either PactException PayloadWithOutputs) -runTxInBlock mempoolRef pactQueue blockDb makeTx = do - madeTx <- newIORef @Bool False - supply <- newIORef @Word 0 - setMempool mempoolRef $ mempty { - mpaGetBlock = \_ valid bHeight bHash bct -> do - madeTxYet <- readIORef madeTx - if madeTxYet - then do - pure mempty - else do - n <- atomicModifyIORef' supply $ \a -> (a + 1, a) - tx <- makeTx n bHeight bHash bct - valids <- valid bHeight bHash (V.singleton $ (fmap . fmap) _pcCode tx) - writeIORef madeTx True - pure $ V.fromList - [ v - | Right v <- V.toList valids - ] - } - e <- runBlockE pactQueue blockDb second - writeIORef madeTx False - pure e - -runTxInBlock_ :: () - => IO (IORef MemPoolAccess) -- ^ mempoolRef - -> PactQueue - -> TestBlockDb - -> (Word -> BlockHeight -> BlockHash -> BlockCreationTime -> IO Pact4.Transaction) - -> IO PayloadWithOutputs -runTxInBlock_ mempoolRef pactQueue blockDb makeTx = do - runTxInBlock mempoolRef pactQueue blockDb makeTx >>= \case - Left e -> assertFailure $ "newBlockAndValidate: validate: got failure result: " ++ show e - Right v -> pure v - --- | Step through a prepared statement, then clear the statement's bindings --- and reset the statement. -stepThenReset :: Lite.Statement -> IO Lite.StepResult -stepThenReset stmt = do - Lite.stepNoCB stmt `finally` (Lite.clearBindings stmt >> Lite.reset stmt) diff --git a/test/unit/Chainweb/Test/Pact4/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact4/RemotePactTest.hs deleted file mode 100644 index bc43440e7a..0000000000 --- a/test/unit/Chainweb/Test/Pact4/RemotePactTest.hs +++ /dev/null @@ -1,1319 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE CPP #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE ViewPatterns #-} - --- | --- Module: Chainweb.Test.RemotePactTest --- Copyright: Copyright © 2018 - 2020 Kadena LLC. --- License: See LICENSE file --- Maintainer: Mark Nichols , Emily Pillmore --- Stability: experimental --- --- Unit test for Pact execution via the Http Pact interface (/send, --- etc.) (inprocess) API in Chainweb --- -module Chainweb.Test.Pact4.RemotePactTest -( tests -, withRequestKeys -, polling -, sending -, PollingExpectation(..) -) where - -import Control.Concurrent hiding (modifyMVar, newMVar, putMVar, readMVar) -import Control.Concurrent.MVar.Strict -import Control.DeepSeq -import Control.Lens -import Control.Monad -import Control.Monad.Catch -import Control.Monad.IO.Class -import Control.Monad.Trans.Resource - -import qualified Data.Aeson as A -import Data.Aeson.Lens hiding (values) -import Data.Bifunctor (first) -import Control.Monad.Trans.Except (runExceptT, except) -import Control.Monad.Except (throwError) -import qualified Data.ByteString as BS -import qualified Data.ByteString.Lazy as LBS -import qualified Data.ByteString.Short as SB -import Data.IORef (modifyIORef', newIORef, readIORef) -import Data.Word (Word64) -import Data.Foldable (toList) -import qualified Data.HashMap.Strict as HM -import qualified Data.List as L -import qualified Data.List.NonEmpty as NEL -import qualified Data.Map.Strict as M -import Data.Maybe -import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import GHC.Exts(the) -import System.LogLevel (LogLevel(..)) - -import Servant.Client - -import Test.Tasty -import Test.Tasty.HUnit - -import qualified Pact.ApiReq as Pact -import qualified Pact.JSON.Encode as J -import Pact.Types.API -import Pact.Types.Capability -import qualified Pact.Types.ChainId as Pact -import qualified Pact.Types.ChainMeta as Pact -import Pact.Types.Command -import Pact.Types.Continuation -import Pact.Types.Exp -import Pact.Types.Gas -import Pact.Types.Hash (Hash(..)) -import Pact.Types.Hash qualified as Pact -import Pact.Types.Info (noInfo) -import qualified Pact.Types.PactError as Pact -import Pact.Types.PactValue -import Pact.Types.Pretty -import Pact.Types.Persistence (RowKey(..), TxLog(..)) -import Pact.Types.RowData (RowData(..)) -import Pact.Types.Term - --- internal modules - -import Chainweb.ChainId -import Chainweb.Chainweb.Configuration -import Chainweb.Graph -import Chainweb.Mempool.Mempool -import Chainweb.Pact.Backend.Compaction qualified as Sigma -import Chainweb.Pact.Backend.PactState (getLatestBlockHeight) -import Chainweb.Pact.Backend.Utils qualified as Backend -import Chainweb.Pact.RestAPI.Client -import Chainweb.Pact.RestAPI.EthSpv -import Chainweb.Pact.Types -import Chainweb.Pact4.Validations (defaultMaxTTL) -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Pact4.Utils qualified as Utils -import Chainweb.Test.RestAPI.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import Chainweb.Utils hiding (check) -import Chainweb.Version -import Chainweb.Version.Mainnet -import Chainweb.Storage.Table.RocksDB -import qualified Pact.Core.Command.Server as Pact5 - --- -------------------------------------------------------------------------- -- --- Global Settings - -nNodes :: Word -nNodes = 1 - -v :: ChainwebVersion -v = instantCpmTestVersion petersonChainGraph - -vNetworkId :: Pact.NetworkId -vNetworkId = Pact.NetworkId $ getChainwebVersionName $ _versionName v - - -cid :: HasCallStack => ChainId -cid = head . toList $ chainIds v - -pactCid :: HasCallStack => Pact.ChainId -pactCid = Pact.ChainId $ chainIdToText cid - -gp :: GasPrice -gp = 0.1 - -withRequestKeys - :: Pact.TxCreationTime - -> ClientEnv - -> IO RequestKeys -withRequestKeys t cenv = do - mNonce <- newMVar 0 - testSend t mNonce cenv - --- ------------------------------------------------------------------------- -- --- Tests. GHCI use `runRocks tests` --- also: --- :set -package retry --- :set -package extra - --- | Note: These tests are intermittently non-deterministic due to the way --- random chain sampling works with our test harnesses. --- -tests :: RocksDb -> TestTree -tests rdb = testGroup "Chainweb.Test.Pact4.RemotePactTest" - [ withResourceT (withNodeDbDirs rdb nNodes) $ \dbDirs -> - withResourceT (withNodesAtLatestBehavior v id =<< liftIO dbDirs) $ \net -> - let cenv = _getServiceClientEnv <$> net - iot = toTxCreationTime @Integer <$> getCurrentTimeIntegral - - in independentSequentialTestGroup "remote pact tests" - [ withResourceT (liftIO $ join $ withRequestKeys <$> iot <*> cenv) $ \reqkeys -> golden "remote-golden" $ - join $ responseGolden <$> cenv <*> reqkeys - , testCaseSteps "remote spv" $ \step -> - join $ spvTest <$> iot <*> cenv <*> pure step - , testCaseSteps "remote eth spv" $ \step -> - join $ ethSpvTest <$> iot <*> cenv <*> pure step - , testCaseSteps "/send reports validation failure" $ \step -> - join $ sendValidationTest <$> iot <*> cenv <*> pure step - , testCase "/poll reports badlisted txs" $ - join $ pollingBadlistTest <$> cenv - , testCase "trivialLocalCheck" $ - join $ localTest <$> iot <*> cenv - , testCase "localChainData" $ - join $ localChainDataTest <$> iot <*> cenv - , testCaseSteps "transaction size gas tests" $ \step -> - join $ txTooBigGasTest <$> iot <*> cenv <*> pure step - , testCaseSteps "genesisAllocations" $ \step -> - join $ allocationTest <$> iot <*> cenv <*> pure step - , testCaseSteps "caplist TRANSFER and FUND_TX test" $ \step -> - join $ caplistTest <$> iot <*> cenv <*> pure step - , testCaseSteps "local continuation test" $ \step -> - join $ localContTest <$> iot <*> cenv <*> pure step - , testCaseSteps "poll confirmation depth test" $ \step -> - join $ pollingConfirmDepth <$> iot <*> cenv <*> pure step - , testCaseSteps "/poll rejects keys of incorrect length" $ \step -> - join $ pollBadKeyTest <$> cenv <*> pure step - , testCaseSteps "local preflight sim test" $ \step -> - join $ localPreflightSimTest <$> iot <*> cenv <*> pure step - - , testCaseSteps "poll correct results test" $ \step -> - join $ pollingCorrectResults <$> iot <*> cenv <*> pure step - , testCase "webauthn sig" $ - join $ webAuthnSignatureTest <$> iot <*> cenv - ] - , testCase "txlogsCompactionTest" $ txlogsCompactionTest rdb - , testCase "invalid command test" $ invalidCommandTest rdb - , testCase "db error test" $ dbErrorTest rdb - ] - -responseGolden :: ClientEnv -> RequestKeys -> IO LBS.ByteString -responseGolden cenv rks = do - PollResponses theMap <- polling v cid cenv rks ExpectPactResult - let values = mapMaybe (\rk -> _crResult <$> HM.lookup rk theMap) - (NEL.toList $ _rkRequestKeys rks) - return $ foldMap J.encode values - --- this tests the exact content of dberrors which make it into outputs -dbErrorTest :: RocksDb -> IO () -dbErrorTest rdb = runResourceT $ do - nodeDbDirs <- withNodeDbDirs rdb nNodes - net <- withNodesAtLatestBehavior v id nodeDbDirs - liftIO $ do - iot <- liftIO $ toTxCreationTime @Integer <$> getCurrentTimeIntegral - let cenv = _getServiceClientEnv net - cmd <- liftIO $ buildTextCmd "err" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbTTL defaultMaxTTL - $ set cbCreationTime iot - $ set cbChainId cid - $ set cbGasLimit 70000 - $ set cbRPC (mkExec - (T.unlines - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defschema s i:integer)" - , "(deftable tbl:{s})" - , "(defun f () (update tbl 'x {'i: 4})))" - , "(create-table tbl)" - , "(f)" - ] - ) - (mkKeySetData "sender00" [sender00])) - $ defaultCmd - let expectedMessage = - ": Failure: Database exception: {\"tag\":\"PactInternalError\",\"contents\":\"checkInsertIsOK: Update: no row found for key x\"}" - r <- local v cid cenv cmd - assertEqual "something" - (over _Left show $ _pactResult $ _crResult r) - (Left expectedMessage) - -invalidCommandTest :: RocksDb -> IO () -invalidCommandTest rdb = runResourceT $ do - nodeDbDirs <- withNodeDbDirs rdb nNodes - net <- withNodesAtLatestBehavior v id nodeDbDirs - let cenv = _getServiceClientEnv net - - let sendExpect :: (HasCallStack) => [Command Text] -> (Text -> Bool) -> ResourceT IO () - sendExpect txs p = do - e <- liftIO $ flip runClientM cenv $ - pactSendApiClient v cid $ SubmitBatch $ NEL.fromList txs - case e of - Right _ -> do - liftIO $ assertFailure "Expected an error message from /send, but didn't get it" - Left clientErr -> do - case clientErr of - FailureResponse _request resp -> do - let respBody = T.decodeUtf8 (LBS.toStrict (responseBody resp)) - if p respBody - then pure () - else liftIO $ assertFailure $ "Predicate failed, responseBody was: " ++ T.unpack respBody - _ -> do - liftIO $ assertFailure "Expected 'FailureResponse', got a different ClientError" - - iot <- liftIO $ toTxCreationTime @Integer <$> getCurrentTimeIntegral - - let prefix i cmd = "One or more transactions were invalid: Transaction " <> sshow (_cmdHash cmd) <> " at index " <> sshow @Int i <> " failed with: " - - cmdParseFailure <- liftIO $ buildTextCmd "bare-command" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbTTL defaultMaxTTL - $ set cbCreationTime iot - $ set cbChainId cid - $ set cbRPC (mkExec "(+ 1" (mkKeySetData "sender00" [sender00])) - $ defaultCmd - -- Why does pact just return 'mzero' here... - sendExpect [cmdParseFailure] (== (prefix 0 cmdParseFailure <> "Pact parse error: Failed reading: mzero")) - - cmdInvalidPayloadHash <- liftIO $ do - bareCmd <- buildTextCmd "bare-command" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbTTL defaultMaxTTL - $ set cbCreationTime iot - $ set cbChainId cid - $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) - $ defaultCmd - pure $ bareCmd - { _cmdHash = Pact.hash "fakehash" - } - sendExpect [cmdInvalidPayloadHash] (== (prefix 0 cmdInvalidPayloadHash <> "Invalid transaction hash")) - - cmdSignersSigsLengthMismatch1 <- liftIO $ do - bareCmd <- buildTextCmd "bare-command" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbTTL defaultMaxTTL - $ set cbCreationTime iot - $ set cbChainId cid - $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) - $ defaultCmd - pure $ bareCmd - { _cmdSigs = [] - } - sendExpect [cmdSignersSigsLengthMismatch1] (== (prefix 0 cmdSignersSigsLengthMismatch1 <> "Invalid transaction sigs: The number of signers and signatures do not match. Number of signers: 1. Number of signatures: 0.")) - - cmdSignersSigsLengthMismatch2 <- liftIO $ do - bareCmd <- buildTextCmd "bare-command" v - $ set cbSigners [] - $ set cbTTL defaultMaxTTL - $ set cbCreationTime iot - $ set cbChainId cid - $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) - $ defaultCmd - pure $ bareCmd - { -- This is an invalid ED25519 signature, but length signers == length signatures is checked first - _cmdSigs = [ED25519Sig "fakeSig"] - } - sendExpect [cmdSignersSigsLengthMismatch2] (== (prefix 0 cmdSignersSigsLengthMismatch2 <> "Invalid transaction sigs: The number of signers and signatures do not match. Number of signers: 0. Number of signatures: 1.")) - - -- TODO: It's hard to test for invalid schemes, because it's baked into - -- chainwebversion. - - -- TODO: It's hard to test for an invalid WebAuthn signer prefix, because it's - -- baked into chainweb version. - - cmdInvalidUserSig <- liftIO $ do - bareCmd <- buildTextCmd "bare-command" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbTTL defaultMaxTTL - $ set cbCreationTime iot - $ set cbChainId cid - $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) - $ defaultCmd - pure $ bareCmd - { _cmdSigs = [ED25519Sig "fakeSig"] - } - sendExpect [cmdInvalidUserSig] (== (prefix 0 cmdInvalidUserSig <> "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.")) - - cmdGood <- liftIO $ buildTextCmd "good-command" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbTTL defaultMaxTTL - $ set cbCreationTime iot - $ set cbChainId cid - $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) - $ defaultCmd - -- Test that [badCmd, goodCmd] fails on badCmd, and the batch is rejected. - -- We just re-use a previously built bad cmd. - sendExpect [cmdInvalidUserSig, cmdGood] (== (prefix 0 cmdInvalidUserSig <> "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.")) - -- Test that [goodCmd, badCmd] fails on badCmd, and the batch is rejected. - -- Order matters, and the error message also indicates the position of the - -- failing tx. - -- We just re-use a previously built bad cmd. - sendExpect [cmdGood, cmdInvalidUserSig] (== (prefix 1 cmdInvalidUserSig <> "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.")) - --- | Check that txlogs don't problematically access history --- post-compaction. --- --- At a high level, the test does this: --- - Submits a tx that creates a module with a table named `persons`. --- --- This module exposes a few functions for reading, inserting, --- and overwriting rows to the `persons` table. --- --- The tx also inserts some people into `persons` for --- some initial state. --- --- This module also exposes a way to access the `txlogs` --- of the `persons` table (what this test is concerned with). --- --- - Submits a tx that overwrites a row in the --- `persons` table. --- --- - Compacts to the latest blockheight on each chain. This should --- get rid of any out-of-date rows. --- --- - Submits a /local tx that reads the `txlogs` on the `persons` --- table. Call this `txLogs`. --- --- If this read fails, Pact is doing something problematic! --- --- If this read doesn't fail, we need to check that `txLogs` --- matches the latest pact state post-compaction. Because --- compaction sweeps away the out-of-date rows, they shouldn't --- appear in the `txLogs` anymore, and the two should be equivalent. -txlogsCompactionTest :: RocksDb -> IO () -txlogsCompactionTest rdb = runResourceT $ do - nodeDbDirs <- withNodeDbDirs rdb nNodes - -- This looks up the pactDbDir for node 0. This is - -- kind of a hack, because there is only one node in - -- this test. However, it doesn't matter much, because - -- we are dealing with both submitting /local txs - -- and compaction, so picking an arbitrary node - -- to run these two operations on is fine. - let srcPactDir = nodePactDbDir (head nodeDbDirs) - iot <- liftIO $ toTxCreationTime @Integer <$> getCurrentTimeIntegral - let cmd :: Text -> CmdBuilder - cmd tx = do - set cbSigners [mkEd25519Signer' sender00 []] - $ set cbTTL defaultMaxTTL - $ set cbCreationTime iot - $ set cbChainId cid - $ set cbRPC (mkExec tx (mkKeySetData "sender00" [sender00])) - $ defaultCmd - - nonceSupply <- liftIO $ newIORef @Word 1 -- starts at 1 since 0 is always the create-table tx - let nextNonce = liftIO $ do - cur <- readIORef nonceSupply - modifyIORef' nonceSupply (+ 1) - pure cur - - let submitAndCheckTx cenv tx = do - submitResult <- flip runClientM cenv $ - pactSendApiClient v cid $ SubmitBatch $ NEL.fromList [tx] - case submitResult of - Left err -> do - assertFailure $ "Error when sending tx: " ++ show err - Right rks -> do - PollResponses m <- polling v cid cenv rks ExpectPactResult - case HM.lookup (NEL.head (_rkRequestKeys rks)) m of - Just cr -> do - case _crResult cr of - PactResult (Left err) -> do - assertFailure $ "validation failure on tx: " ++ show err - PactResult _ -> do - pure () - Nothing -> do - assertFailure "impossible" - - -- phase 1: start nodes and populate tables - liftIO $ runResourceT $ do - net <- withNodesAtLatestBehavior v id nodeDbDirs - let cenv = _getServiceClientEnv net - - createTableTx <- liftIO $ buildTextCmd "create-table-persons" v - $ set cbGasLimit 300_000 - $ cmd - $ T.unlines - [ "(namespace 'free)" - , "(module m0 G" - , " (defcap G () true)" - , " (defschema person" - , " name:string" - , " age:integer" - , " )" - , " (deftable persons:{person})" - , " (defun read-persons (k) (read persons k))" - , " (defun insert-persons (id name age) (insert persons id { 'name:name, 'age:age }))" - , " (defun write-persons (id name age) (write persons id { 'name:name, 'age:age }))" - , " (defun persons-txlogs (i) (map (txlog persons) (txids persons i)))" - , ")" - , "(create-table persons)" - , "(insert-persons \"A\" \"Lindsey Lohan\" 42)" - , "(insert-persons \"B\" \"Nico Robin\" 30)" - , "(insert-persons \"C\" \"chessai\" 420)" - ] - - liftIO $ submitAndCheckTx cenv createTableTx - - let createWriteTx :: Word -> IO (Command Text) - createWriteTx n = liftIO $ do - let gasLimit = 500 - buildTextCmd ("test-write-" <> sshow n) v - $ set cbGasLimit gasLimit - $ cmd - $ "(free.m0.write-persons \"C\" \"chessai\" 69)" - - liftIO $ submitAndCheckTx cenv =<< createWriteTx =<< nextNonce - - -- phase 2: compact - targetPactDir <- withPactDir 0 - liftIO $ Sigma.withDefaultLogger Error $ \logger -> do - Backend.withSqliteDb cid logger srcPactDir False $ \srcDb -> do - Backend.withSqliteDb cid logger targetPactDir False $ \targetDb -> do - sigmaCompact srcDb targetDb =<< getLatestBlockHeight srcDb - - let newNodeDbDirs = (head nodeDbDirs) { nodePactDbDir = targetPactDir } : tail nodeDbDirs - - -- phase 3: restart nodes, query txlogs - liftIO $ runResourceT $ do - net <- withNodesAtLatestBehavior v (configFullHistoricPactState .~ False) newNodeDbDirs - let cenv = _getServiceClientEnv net - - let createTxLogsTx :: Word -> IO (Command Text) - createTxLogsTx n = liftIO $ do - -- cost is about 310k. - -- cost = flatCost(map) + flatCost(txIds) + numTxIds * (costOf(txlog)) + C - -- = 4 + 100_000 + 2 * 100_000 + C - -- = 300_004 + C - -- Note there are two transactions that write to `persons`, which is - -- why `numTxIds` = 2 (and not the number of rows). - let gasLimit = 310_000 - buildTextCmd ("test-txlogs-" <> sshow n) v - $ set cbGasLimit gasLimit - $ cmd - $ "(free.m0.persons-txlogs 0)" - - - let -- This can't be a Map because the RowKeys aren't - -- necessarily unique, unlike in `getLatestPactState`. - crGetTxLogs :: CommandResult Hash -> IO [(RowKey, A.Value)] - crGetTxLogs cr = do - e <- runExceptT $ do - pv0 <- except (first show (_pactResult (_crResult cr))) - case pv0 of - PList arr -> do - fmap concat $ forM arr $ \pv -> do - txLogs <- except (A.eitherDecode @[TxLog A.Value] (J.encode pv)) - pure $ flip map txLogs $ \txLog -> - (RowKey (_txKey txLog), _txValue txLog) - _ -> do - throwError "expected outermost PList when decoding TxLogs" - case e of - Left err -> do - assertFailure $ "crGetTxLogs failed: " ++ err - Right txlogs -> do - pure txlogs - - txLogs <- liftIO $ crGetTxLogs =<< local v cid cenv =<< createTxLogsTx =<< nextNonce - - let getLatestState :: IO (M.Map RowKey RowData) - getLatestState = Sigma.withDefaultLogger Error $ \logger -> do - Backend.withSqliteDb cid logger targetPactDir False $ \db -> do - st <- Utils.getLatestPactState db - case M.lookup "free.m0_persons" st of - Just ps -> fmap M.fromList $ forM (M.toList ps) $ \(rkBytes, rdBytes) -> do - let rk = RowKey (T.decodeUtf8 rkBytes) - case A.eitherDecodeStrict' @RowData rdBytes of - Left err -> do - assertFailure $ "Failed decoding rowdata: " ++ err - Right rd -> do - pure (rk, rd) - Nothing -> error "getting state of free.m0_persons failed" - - latestState <- liftIO getLatestState - liftIO $ assertEqual - "txlogs match latest state" - (map (\(rk, rd) -> (rk, J.toJsonViaEncode (_rdData rd))) (M.toAscList latestState)) - (L.sort txLogs) - -localTest :: Pact.TxCreationTime -> ClientEnv -> IO () -localTest t cenv = do - mv <- newMVar 0 - SubmitBatch batch <- testBatch t mv gp - let cmd = head $ toList batch - res <- local v (unsafeChainId 0) cenv cmd - let PactResult e = _crResult res - assertEqual "expect /local to return gas for tx" (_crGas res) 5 - assertEqual "expect /local to succeed and return 3" e (Right (PLiteral $ LDecimal 3)) - -localContTest :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -localContTest t cenv step = do - - step "execute /send with initial pact continuation tx" - cmd1 <- firstStep - rks <- sending v cid' cenv (SubmitBatch $ pure cmd1) - - step "check /poll responses to extract pact id for continuation" - PollResponses m <- polling v cid' cenv rks ExpectPactResult - pid <- case _rkRequestKeys rks of - rk NEL.:| [] -> maybe (assertFailure "impossible") (return . _pePactId) - $ HM.lookup rk m >>= _crContinuation - _ -> assertFailure "continuation did not succeed" - - step "execute /local continuation dry run" - cmd2 <- secondStep pid - r <- _pactResult . _crResult <$> local v cid' cenv cmd2 - case r of - Left err -> assertFailure (show err) - Right (PLiteral (LDecimal a)) | a == 2 -> return () - Right p -> assertFailure $ "unexpected cont return value: " ++ show p - - where - cid' = unsafeChainId 0 - tx = - "(namespace 'free)(module m G (defcap G () true) (defpact p () (step (yield { \"a\" : (+ 1 1) })) (step (resume { \"a\" := a } a))))(free.m.p)" - firstStep = do - buildTextCmd "nonce-cont-1" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbCreationTime t - $ set cbGasLimit 70_000 - $ set cbRPC (mkExec' tx) - $ defaultCmd - - secondStep pid = do - buildTextCmd "nonce-cont-2" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbCreationTime t - $ set cbRPC (mkCont (mkContMsg pid 1)) - $ defaultCmd - -pollingConfirmDepth :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -pollingConfirmDepth t cenv step = do - - step "/send transactions" - cmd1 <- firstStep tx - cmd2 <- firstStep tx' - rks <- sending v cid' cenv (SubmitBatch $ cmd1 NEL.:| [cmd2]) - - step "/poll for the transactions until they appear" - - PollResponses m <- pollingWithDepth v cid' cenv rks (Just $ ConfirmationDepth 10) ExpectPactResult - afterPolling <- getCurrentBlockHeight v cenv cid' - -- here we rely on both txs being in the same block. - let txHeight = the - (m ^.. to HM.elems . folded . crMetaData . _Just . key "blockHeight" . _Integer) - - assertBool "there are two command results" $ length (HM.keys m) == 2 - - -- we are checking that we have waited exactly 10 blocks using /poll for the transaction - -- this is technically a race, because a block could've been made after the poll response - -- and before we fetch the current height; so we give it some slack. - assertBool "the difference between heights should be within 1 block of the confirmation depth" - (afterPolling - int txHeight `elem` [10..20]) - where - cid' = unsafeChainId 0 - tx = - "42" - tx' = - "43" - firstStep transaction = do - buildTextCmd "nonce-cont-1" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbCreationTime t - $ set cbGasLimit 70_000 - $ set cbRPC (mkExec' transaction) - $ defaultCmd - -pollingCorrectResults :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -pollingCorrectResults t cenv step = do - step "/send transactions" - - -- submit the first one, then poll to confirm its in a block - cmd1 <- stepTx tx - rks1@(RequestKeys (rk1 NEL.:| [])) <- sending v cid' cenv (SubmitBatch $ cmd1 NEL.:| []) - PollResponses _ <- pollingWithDepth v cid' cenv rks1 (Just $ ConfirmationDepth 10) ExpectPactResult - - -- submit the second... - cmd2 <- stepTx tx' - RequestKeys (rk2 NEL.:| []) <- sending v cid' cenv (SubmitBatch $ cmd2 NEL.:| []) - - -- now request both. the second transaction will by definition go into another block. - -- do it in two different orders, and ensure it works either way. - let - together1 = RequestKeys $ rk1 NEL.:| [rk2] - together2 = RequestKeys $ rk2 NEL.:| [rk1] - - PollResponses resp1 <- polling v cid' cenv together1 ExpectPactResult - PollResponses resp2 <- polling v cid' cenv together2 ExpectPactResult - - assertEqual "the two responses should be the same" resp1 resp2 - where - cid' = unsafeChainId 0 - (tx, tx') = ("42", "43") - - stepTx transaction = do - buildTextCmd "nonce-cont-2" v - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbCreationTime t - $ set cbGasLimit 70_000 - $ set cbRPC (mkExec' transaction) - $ defaultCmd - -localChainDataTest :: Pact.TxCreationTime -> ClientEnv -> IO () -localChainDataTest t cenv = do - mv <- newMVar (0 :: Int) - SubmitBatch batch <- localTestBatch mv - let cmd = head $ toList batch - sid <- mkChainId v maxBound 0 - res <- flip runClientM cenv $ pactLocalApiClient v sid cmd - checkCommandResult res - where - - checkCommandResult (Left e) = throwM $ LocalFailure (show e) - checkCommandResult (Right cr) = - let (PactResult e) = _crResult cr - in mapM_ expectedResult e - - localTestBatch mnonce = modifyMVar mnonce $ \(!nn) -> do - let nonce = "nonce" <> sshow nn - kps <- testKeyPairs sender00 Nothing - c <- Pact.mkExec "(chain-data)" A.Null (pm t) kps [] (Just vNetworkId) (Just nonce) - pure (succ nn, SubmitBatch (pure c)) - where - pm = Pact.PublicMeta pactCid "sender00" 1000 0.1 defaultMaxTTL - - expectedResult (PObject (ObjectMap m)) = do - assert' "chain-id" (PLiteral (LString $ chainIdToText cid)) - assert' "gas-limit" (PLiteral (LInteger 1000)) - assert' "gas-price" (PLiteral (LDecimal 0.1)) - assert' "sender" (PLiteral (LString "sender00")) - where - assert' name value = assertEqual name (M.lookup (FieldKey (T.pack name)) m) (Just value) - expectedResult _ = assertFailure "Didn't get back an object map!" - -localPreflightSimTest :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -localPreflightSimTest t cenv step = do - mv <- newMVar (0 :: Int) - sid <- mkChainId v maxBound 0 - let sigs = [mkEd25519Signer' sender00 []] - - step "Execute preflight /local tx - preflight known /send success" - let psid = Pact.ChainId $ chainIdToText sid - psigs <- testKeyPairs sender00 Nothing - cmd0 <- mkRawTx mv psid psigs - runLocalPreflightClient sid cenv cmd0 >>= \case - Right Pact4LocalResultWithWarns{} -> pure () - Right r -> assertFailure $ "unexpected local result: " <> T.unpack (J.getJsonText $ J.encodeJsonText r) - Left e -> assertFailure $ show e - - step "Execute preflight /local tx - preflight+signoverify known /send success" - cmd0' <- mkRawTx mv psid psigs - cr <- runClientM - (pactLocalWithQueryApiClient v sid - (Just PreflightSimulation) (Just NoVerify) Nothing cmd0') cenv - void $ case cr of - Left e -> assertFailure $ show e - Right{} -> pure () - - step "Execute preflight /local tx - unparseable chain id" - sigs0 <- testKeyPairs sender00 Nothing - cmd1 <- mkRawTx mv (Pact.ChainId "fail") sigs0 - runClientFailureAssertion sid cenv cmd1 "Unparseable transaction chain id" - - step "Execute preflight /local tx - chain id mismatch" - let fcid = unsafeChainId maxBound - cmd2 <- mkTx v mv =<< mkCmdBuilder sigs fcid 1000 gp - runClientFailureAssertion sid cenv cmd2 "Chain id mismatch" - - step "Execute preflight /local tx - tx gas limit too high" - cmd3 <- mkTx v mv =<< mkCmdBuilder sigs sid 100000000000000 gp - runClientFailureAssertion sid cenv cmd3 - "Transaction Gas limit exceeds block gas limit" - - step "Execute preflight /local tx - tx gas price precision too high" - cmd4 <- mkTx v mv =<< mkCmdBuilder sigs sid 1000 0.00000000000000001 - runClientFailureAssertion sid cenv cmd4 - "Gas price decimal precision too high" - - step "Execute preflight /local tx - network id mismatch" - cmd5 <- mkTx Mainnet01 mv =<< mkCmdBuilder sigs sid 1000 gp - runClientFailureAssertion sid cenv cmd5 "Network id mismatch" - - step "Execute preflight /local tx - too many sigs" - let pcid = Pact.ChainId $ chainIdToText sid - sigs1' <- testKeyPairs sender00 Nothing >>= \case - [ks] -> pure $ replicate 101 ks - _ -> assertFailure "/local test keypair construction failed" - - cmd6 <- mkRawTx mv pcid sigs1' - runClientFailureAssertion sid cenv cmd6 "Signature list size too big" - - step "Execute preflight /local tx - collect warnings" - cmd7 <- mkRawTx' mv pcid sigs0 "(+ 1 2.0)" - - currentBlockHeight <- getCurrentBlockHeight v cenv sid - runLocalPreflightClient sid cenv cmd7 >>= \case - Right (Pact4LocalResultWithWarns cr' ws) -> do - let crbh :: Integer = fromIntegral $ fromMaybe 0 $ crGetBlockHeight cr' - expectedbh = 1 + fromIntegral currentBlockHeight - assertBool "Preflight's metadata should have increment block height" - -- we don't control the node in remote tests and the data can get oudated, - -- to make test less flaky we use a small range for validation - (abs (expectedbh - crbh) <= 2) - - case ws of - [w] | "decimal/integer operator overload" `T.isInfixOf` w -> - pure () - ws' -> assertFailure $ "Incorrect warns: " ++ show ws' - Right r -> assertFailure $ "invalid local result: " <> T.unpack (J.getJsonText $ J.encodeJsonText r) - Left e -> assertFailure $ show e - - let rewindDepth = 10 - currentBlockHeight' <- getCurrentBlockHeight v cenv sid - runLocalPreflightClientWithDepth sid cenv cmd7 rewindDepth >>= \case - Right (Pact4LocalResultWithWarns cr' ws) -> do - let crbh :: Integer = fromIntegral $ fromMaybe 0 $ crGetBlockHeight cr' - expectedbh = toInteger $ 1 + (fromIntegral currentBlockHeight') - rewindDepth - assertBool "Preflight's metadata block height should reflect the rewind depth" - -- we don't control the node in remote tests and the data can get oudated, - -- to make test less flaky we use a small range for validation - (abs (expectedbh - crbh) <= 2) - - case ws of - [w] | "decimal/integer operator overload" `T.isInfixOf` w -> - pure () - ws' -> assertFailure $ "Incorrect warns: " ++ show ws' - Right r -> assertFailure $ "invalid local result: " <> T.unpack (J.getJsonText $ J.encodeJsonText r) - Left e -> assertFailure $ show e - where - runLocalPreflightClient sid e cmd = flip runClientM e $ - pactLocalWithQueryApiClient v sid - (Just PreflightSimulation) - (Just Verify) Nothing cmd - - runLocalPreflightClientWithDepth sid e cmd d = flip runClientM e $ - pactLocalWithQueryApiClient v sid - (Just PreflightSimulation) - (Just Verify) (Just $ RewindDepth d) cmd - - runClientFailureAssertion sid e cmd msg = - runLocalPreflightClient sid e cmd >>= \case - Left err -> checkClientErrText err msg - r -> assertFailure $ "Unintended success: " ++ either show (T.unpack . J.getJsonText . J.encodeJsonText) r - - checkClientErrText (FailureResponse _ (Response _ _ _ body)) e - | BS.isInfixOf e $ LBS.toStrict body = pure () - checkClientErrText _ e = assertFailure $ show e - - -- cmd builder is more correct by construction than - -- would allow for us to modify the chain id to something - -- unparsable. Hence we need to do this in the unparsable - -- chain id case and nowhere else. - mkRawTx mv pcid kps = mkRawTx' mv pcid kps "(+ 1 2)" - - mkRawTx' mv pcid kps code = modifyMVar mv $ \(nn :: Int) -> do - let nonce = "nonce" <> sshow nn - pm = Pact.PublicMeta pcid "sender00" 1000 0.1 defaultMaxTTL - - c <- Pact.mkExec code A.Null (pm t) kps [] (Just vNetworkId) (Just nonce) - pure (succ nn, c) - - mkCmdBuilder sigs pcid limit price = do - pure - $ set cbGasLimit limit - $ set cbGasPrice price - $ set cbChainId pcid - $ set cbSigners sigs - $ set cbCreationTime t - $ set cbRPC (mkExec' "(+ 1 2)") - $ defaultCmd - - mkTx v' mv tx = modifyMVar mv $ \nn -> do - let n = "nonce-" <> sshow nn - tx' <- buildTextCmd n v' tx - pure (succ nn, tx') - --- FIXME: Polling no longer checks request key validity, so this test probably does not pass anymore. -pollingBadlistTest :: ClientEnv -> IO () -pollingBadlistTest cenv = do - let rks = RequestKeys $ NEL.fromList [pactDeadBeef] - sid <- mkChainId v maxBound 0 - void $ polling v sid cenv rks ExpectPactError - --- | Check request key length validation in the /poll endpoints --- -pollBadKeyTest :: ClientEnv -> (String -> IO ()) -> IO () -pollBadKeyTest cenv step = do - let tooBig = toRk $ BS.replicate 33 0x3d - tooSmall = toRk $ BS.replicate 31 0x3d - - sid <- liftIO $ mkChainId v maxBound 0 - - step "RequestKeys of length > 32 fail fast" - runClientM (pactPollWithQueryApiClient v sid Nothing (pact4Poll $ Poll tooBig)) cenv >>= \case - Left _ -> return () - Right (Pact5.PollResponse r) -> assertFailure $ "Poll succeeded with response: " <> show r - - step "RequestKeys of length < 32 fail fast" - runClientM (pactPollWithQueryApiClient v sid Nothing (pact4Poll $ Poll tooSmall)) cenv >>= \case - Left _ -> return () - Right (Pact5.PollResponse r) -> assertFailure $ "Poll succeeded with response: " <> show r - where - toRk = (NEL.:| []) . RequestKey . Hash . SB.toShort - -sendValidationTest :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -sendValidationTest t cenv step = do - step "check sending poisoned TTL batch" - mv <- newMVar 0 - SubmitBatch batch1 <- - testBatch' t 10_000 mv gp - SubmitBatch batch2 <- - testBatch' (toTxCreationTime (Time (TimeSpan 0) :: Time Micros)) 2 mv gp - let batch = SubmitBatch $ batch1 <> batch2 - expectSendFailure "Transaction time-to-live is expired" $ - flip runClientM cenv $ - pactSendApiClient v cid batch - - step "check sending mismatched chain id" - cid0 <- mkChainId v maxBound 0 - batch3 <- testBatch'' "40" t 20_000 mv gp - expectSendFailure "Transaction metadata (chain id, chainweb version) conflicts with this endpoint" $ - flip runClientM cenv $ - pactSendApiClient v cid0 batch3 - - step "check insufficient gas" - batch4 <- testBatch' t 10_000 mv 10_000_000_000 - expectSendFailure - "Attempt to buy gas failed with: (enforce (<= amount balance) \\\"...: Failure: Tx Failed: Insufficient funds\"" $ - flip runClientM cenv $ - pactSendApiClient v cid batch4 - - step "check bad sender" - batch5 <- mkBadGasTxBatch "(+ 1 2)" "invalid-sender" sender00 Nothing - expectSendFailure - "Attempt to buy gas failed with: (read coin-table sender): Failure: Tx Failed: read: row not found: invalid-sender" $ - flip runClientM cenv $ - pactSendApiClient v cid0 batch5 - - where - mkBadGasTxBatch code senderName senderKeyPair capList = do - ks <- testKeyPairs senderKeyPair capList - let pm = Pact.PublicMeta (Pact.ChainId "0") senderName 100_000 0.01 defaultMaxTTL t - let cmd (n :: Int) = liftIO $ Pact.mkExec code A.Null pm ks [] (Just vNetworkId) (Just $ sshow n) - cmds <- mapM cmd (0 NEL.:| [1..5]) - return $ SubmitBatch cmds - -expectSendFailure - :: NFData a - => NFData b - => Show a - => Show b - => String - -> IO (Either b a) - -> Assertion -expectSendFailure expectErr act = tryAllSynchronous act >>= \case - (Right (Left e)) -> test $ show e - (Right (Right out)) -> assertFailure $ "expected exception on bad tx, got: " <> show out - (Left e) -> test $ show e - where - test er = assertSatisfies ("Expected message containing '" ++ expectErr ++ "'") er (L.isInfixOf expectErr) - -ethSpvTest :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -ethSpvTest t cenv step = do - - req <- A.eitherDecodeFileStrict' "test/pact/eth-spv-request.json" >>= \case - Left e -> assertFailure $ "failed to decode test/pact/eth-spv-request: " <> e - Right x -> return (x :: EthSpvRequest) - - c <- mkChainId v maxBound 1 - r <- flip runClientM cenv $ do - - void $ liftIO $ step "ethSpvApiClient: submit eth proof request" - proof <- liftIO $ ethSpv v c cenv req - - batch <- liftIO $ mkTxBatch proof - - void $ liftIO $ step "sendApiClient: submit batch for proof validation" - rks <- liftIO $ sending v c cenv batch - - void $ liftIO $ step "pollApiClient: poll until key is found" - void $ liftIO $ polling v c cenv rks ExpectPactResult - - return () - - case r of - Left e -> assertFailure $ "eth proof roundtrip failed: " <> sshow e - Right _ -> return () - where - - mkTxBatch proof = do - ks <- liftIO $ testKeyPairs sender00 Nothing - let pm = Pact.PublicMeta (Pact.ChainId "1") "sender00" 100_000 0.01 defaultMaxTTL t - cmd <- liftIO $ Pact.mkExec txcode (txdata proof) pm ks [] (Just vNetworkId) (Just "1") - return $ SubmitBatch (pure cmd) - - txcode = "(verify-spv 'ETH (read-msg))" - - txdata proof = A.toJSON proof - -spvTest :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -spvTest t cenv step = do - batch <- mkTxBatch - sid <- mkChainId v maxBound 1 - r <- flip runClientM cenv $ do - - void $ liftIO $ step "sendApiClient: submit batch" - rks <- liftIO $ sending v sid cenv batch - - void $ liftIO $ step "pollApiClient: poll until key is found" - void $ liftIO $ polling v sid cenv rks ExpectPactResult - - void $ liftIO $ step "spvApiClient: submit request key" - liftIO $ spv v sid cenv (SpvRequest (NEL.head $ _rkRequestKeys rks) tid) - - case r of - Left e -> assertFailure $ "output proof failed: " <> sshow e - Right _ -> return () - where - tid = Pact.ChainId "2" - - mkTxBatch = do - ks <- liftIO $ testKeyPairs sender00 - (Just [mkGasCap, mkXChainTransferCap "sender00" "sender01" 1.0 "2"]) - let pm = Pact.PublicMeta (Pact.ChainId "1") "sender00" 100_000 0.01 defaultMaxTTL t - cmd1 <- liftIO $ Pact.mkExec txcode txdata pm ks [] (Just vNetworkId) (Just "1") - cmd2 <- liftIO $ Pact.mkExec txcode txdata pm ks [] (Just vNetworkId) (Just "2") - return $ SubmitBatch (pure cmd1 <> pure cmd2) - - txcode = T.unlines - [ "(coin.transfer-crosschain" - , " 'sender00" - , " 'sender01" - , " (read-keyset 'sender01-keyset)" - , " (read-msg 'target-chain-id)" - , " 1.0)" - ] - - txdata = A.object - [ "sender01-keyset" A..= [fst sender01] - , "target-chain-id" A..= J.toJsonViaEncode tid - ] - -txTooBigGasTest :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -txTooBigGasTest t cenv step = do - - let - runSend batch expectation = try @IO @PactTestFailure $ do - void $ step "sendApiClient: submit transaction" - rks <- sending v sid cenv batch - - void $ step "pollApiClient: polling for request key" - PollResponses resp <- polling v sid cenv rks expectation - return (HM.lookup (NEL.head $ _rkRequestKeys rks) resp) - - runLocal (SubmitBatch cmds) = do - void $ step "localApiClient: submit transaction" - local v sid cenv (head $ toList cmds) - - -- batch with big tx and insufficient gas - batch0 <- mkTxBatch txcode0 A.Null 1 (Just "0") - - -- batch to test that gas for tx size discounted from the total gas supply - batch1 <- mkTxBatch txcode1 A.Null 5 (Just "1") - - res0Send <- runSend batch0 ExpectPactError - res1Send <- runSend batch1 ExpectPactError - - res0Local <- runLocal batch0 - res1Local <- runLocal batch1 - - void $ liftIO $ step "when tx too big, gas pact error thrown" - assertEqual "LOCAL: expect gas error for big tx" gasError0 (Just $ resultOf res0Local) - case res0Send of - Left e -> assertFailure $ "test failure for big tx with insufficient gas: " <> show e - Right cr -> assertEqual "SEND: expect gas error for big tx" gasError0Mem (resultOf <$> cr) - - let getFailureMsg (Left (Pact.PactError _ _ _ m)) = m - getFailureMsg p = pretty $ "Expected failure result, got " ++ show p - - void $ liftIO $ step "discounts initial gas charge from gas available for pact execution" - assertEqual "LOCAL: expect gas error after discounting initial gas charge" - gasError1 (getFailureMsg $ resultOf res1Local) - - case res1Send of - Left e -> assertFailure $ "test failure for discounting initial gas charge: " <> show e - Right cr -> assertEqual "SEND: expect gas error after discounting initial gas charge" - (Just gasError1Mem) (getFailureMsg . resultOf <$> cr) - - where - sid = unsafeChainId 0 - gasError0 = Just $ Left $ - Pact.PactError Pact.GasError noInfo [] "Tx too big (4), limit 1" - gasError0Mem = Just $ Left $ - Pact.PactError Pact.TxFailure noInfo [] "Transaction is badlisted because it previously failed to validate." - gasError1 = "Gas limit (5) exceeded: 6" - gasError1Mem = "Transaction is badlisted because it previously failed to validate." - - mkTxBatch code cdata limit n = do - ks <- testKeyPairs sender00 Nothing - let pm = Pact.PublicMeta (Pact.ChainId "0") "sender00" limit 0.01 defaultMaxTTL t - cmd <- liftIO $ Pact.mkExec code cdata pm ks [] (Just vNetworkId) n - return $ SubmitBatch (pure cmd) - - txcode0 = T.concat ["[", T.replicate 10 " 1", "]"] - txcode1 = txcode0 <> "(identity 1)" - - -caplistTest :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -caplistTest t cenv step = do - let testCaseStep = liftIO . step - - r <- flip runClientM cenv $ do - batch <- liftIO - $ mkSingletonBatch t sender00 tx0 n0 (pm "sender00") clist - - testCaseStep "send transfer request with caplist sender00 -> sender01" - rks <- liftIO $ sending v sid cenv batch - - testCaseStep "poll for transfer results" - PollResponses rs <- liftIO $ polling v sid cenv rks ExpectPactResult - - return (HM.lookup (NEL.head $ _rkRequestKeys rks) rs) - - case r of - Left e -> assertFailure $ "test failure for TRANSFER + FUND_TX: " <> show e - Right res -> do - assertEqual "TRANSFER + FUND_TX test" result0 (resultOf <$> res) - assertSatisfies "meta in output" (preview (_Just . crMetaData . _Just . _Object . at "blockHash") res) isJust - - where - sid = unsafeChainId 0 - n0 = Just "transfer-clist0" - pm sendr = Pact.PublicMeta (Pact.ChainId "0") sendr 100_000 0.01 defaultMaxTTL - - result0 = Just (Right (PLiteral (LString "Write succeeded"))) - - clist :: Maybe [SigCapability] - clist = Just $ - [ mkCoinCap "GAS" [] - , mkCoinCap "TRANSFER" - [ PLiteral $ LString "sender00" - , PLiteral $ LString "sender01" - , PLiteral $ LDecimal 100.0 - ] - ] - - tx0 = PactTransaction "(coin.transfer \"sender00\" \"sender01\" 100.0)" Nothing - - - -allocation01KeyPair :: SimpleKeyPair -allocation01KeyPair = - ( "b4c8a3ea91d3146b0560994740f0e3eed91c59d2eeca1dc99f0c2872845c294d" - , "5dbbbd8b765b7d0cf8426d6992924b057c70a2138ecd4cf60cfcde643f304ea9" - ) - -allocation02KeyPair :: SimpleKeyPair -allocation02KeyPair = - ( "e9e4e71bd063dcf7e06bd5b1a16688897d15ca8bd2e509c453c616219c186cc5" - , "45f026b7a6bb278ed4099136c13e842cdd80138ab7c5acd4a1f0e6c97d1d1e3c" - ) - -allocation02KeyPair' :: SimpleKeyPair -allocation02KeyPair' = - ( "0c8212a903f6442c84acd0069acc263c69434b5af37b2997b16d6348b53fcd0a" - , "2f75b5d875dd7bf07cc1a6973232a9e53dc1d4ffde2bab0bbace65cd87e87f53" - ) - -allocationTest :: Pact.TxCreationTime -> ClientEnv -> (String -> IO ()) -> IO () -allocationTest t cenv step = do - - step "positive allocation test: allocation00 release" - p <- do - batch0 <- liftIO - $ mkSingletonBatch t allocation00KeyPair tx0 n0 (pm "allocation00") Nothing - - SubmitBatch batch1 <- liftIO - $ mkSingletonBatch t allocation00KeyPair tx1 n1 (pm "allocation00") Nothing - step "sendApiClient: submit allocation release request" - rks0 <- liftIO $ sending v sid cenv batch0 - - step "pollApiClient: polling for allocation key" - _ <- liftIO $ polling v sid cenv rks0 ExpectPactResult - - step "localApiClient: submit local account balance request" - liftIO $ localTestToRetry v sid cenv (head (toList batch1)) (localAfterBlockHeight 4) - - assertEqual "00 expect /local allocation balance" accountInfo (resultOf p) - - step "negative allocation test: allocation01 release" - do - batch0 <- mkSingletonBatch t allocation01KeyPair tx2 n2 (pm "allocation01") Nothing - - step "sendApiClient: submit allocation release request" - cr <- local v sid cenv (NEL.head $ _sbCmds batch0) - - case resultOf cr of - Left e -> do - assertBool "expect negative allocation test failure" - $ T.isInfixOf "Failure: Tx Failed: funds locked" - $ sshow e - _ -> assertFailure "unexpected pact result success in negative allocation test" - - step "positive key-rotation test: allocation2" - r <- do - - batch0 <- mkSingletonBatch t allocation02KeyPair tx3 n3 (pm "allocation02") Nothing - - step "senderApiClient: submit keyset rotation request" - rks <- sending v sid cenv batch0 - - step "pollApiClient: polling for successful rotation" - void $ polling v sid cenv rks ExpectPactResult - - step "senderApiClient: submit allocation release request" - batch1 <- mkSingletonBatch t allocation02KeyPair' tx4 n4 (pm "allocation02") Nothing - - rks' <- sending v sid cenv batch1 - step "pollingApiClient: polling for successful release" - pr <- polling v sid cenv rks' ExpectPactResult - - step "localApiClient: retrieving account info for allocation02" - SubmitBatch batch2 <- mkSingletonBatch t allocation02KeyPair' tx5 n5 (pm "allocation02") Nothing - - localTestToRetry v sid cenv (head (toList batch2)) (localAfterPollResponse pr) - - assertEqual "02 expect /local allocation balance" accountInfo' (resultOf r) - - where - n0 = Just "allocation-0" - n1 = Just "allocation-1" - n2 = Just "allocation-2" - n3 = Just "allocation-3" - n4 = Just "allocation-4" - n5 = Just "allocation-5" - - sid = unsafeChainId 0 - - localAfterPollResponse (PollResponses prs) cr = - crGetBlockHeight cr > crGetBlockHeight (snd $ head $ HM.toList prs) - - localAfterBlockHeight bh cr = - crGetBlockHeight cr > Just bh - - accountInfo = Right - $ PObject - $ ObjectMap - $ M.fromList - [ (FieldKey "account", PLiteral $ LString "allocation00") - , (FieldKey "balance", PLiteral $ LDecimal 1_099_993.91) -- balance = (1k + 1mm) - gas - , (FieldKey "guard", PGuard $ GKeySetRef (KeySetName "allocation00" Nothing)) - ] - - pm sendr = Pact.PublicMeta (Pact.ChainId "0") sendr 100_000 0.01 defaultMaxTTL - - tx0 = PactTransaction "(coin.release-allocation \"allocation00\")" Nothing - tx1 = PactTransaction "(coin.details \"allocation00\")" Nothing - tx2 = PactTransaction "(coin.release-allocation \"allocation01\")" Nothing - tx3 = - let - c = "(define-keyset \"allocation02\" (read-keyset \"allocation02-keyset\"))" - d = mkKeySet - ["0c8212a903f6442c84acd0069acc263c69434b5af37b2997b16d6348b53fcd0a"] - "keys-all" - in PactTransaction c $ Just (A.object [ "allocation02-keyset" A..= J.toJsonViaEncode d ]) - tx4 = PactTransaction "(coin.release-allocation \"allocation02\")" Nothing - tx5 = PactTransaction "(coin.details \"allocation02\")" Nothing - - accountInfo' = Right - $ PObject - $ ObjectMap - $ M.fromList - [ (FieldKey "account", PLiteral $ LString "allocation02") - , (FieldKey "balance", PLiteral $ LDecimal 1_099_991.05) -- 1k + 1mm - gas - , (FieldKey "guard", PGuard $ GKeySetRef (KeySetName "allocation02" Nothing)) - ] - --- Test that transactions signed with (mock) WebAuthn keypairs are accepted --- by the pact service. -webAuthnSignatureTest :: Pact.TxCreationTime -> ClientEnv -> IO () -webAuthnSignatureTest t cenv = do - - cmd1 <- buildTextCmd "nonce-webauthn-1" v - $ set cbSigners [mkWebAuthnSigner' sender02WebAuthn [], mkEd25519Signer' sender00 []] - $ set cbCreationTime t - $ set cbGasLimit 1000 - $ set cbRPC (mkExec' "(concat [\"chainweb-\" \"node\"])") - $ defaultCmd - - rks1 <- sending v cid' cenv (SubmitBatch $ pure cmd1) - PollResponses _resp1 <- polling v cid' cenv rks1 ExpectPactResult - - cmd2 <- buildTextCmd "nonce-webauthn-2" v - $ set cbSigners [mkWebAuthnSigner' sender02WebAuthn [], mkEd25519Signer' sender00 []] - $ set cbCreationTime t - $ set cbGasLimit 1000 - $ set cbRPC (mkExec' "(concat [\"chainweb-\" \"node\"])") - $ defaultCmd - - rks2 <- sending v cid' cenv (SubmitBatch $ pure cmd2) - PollResponses _resp2 <- polling v cid' cenv rks2 ExpectPactResult - - return () - - where - cid' = unsafeChainId 0 - - - --- -------------------------------------------------------------------------- -- --- Utils - - -data PactTransaction = PactTransaction - { _pactCode :: Text - , _pactData :: Maybe A.Value - } deriving (Eq, Show) - -resultOf :: CommandResult l -> Either Pact.PactError PactValue -resultOf = _pactResult . _crResult - -mkSingletonBatch - :: Pact.TxCreationTime - -> SimpleKeyPair - -> PactTransaction - -> Maybe Text - -> (Pact.TxCreationTime -> Pact.PublicMeta) - -> Maybe [SigCapability] - -> IO SubmitBatch -mkSingletonBatch t kps (PactTransaction c d) nonce pmk clist = do - ks <- testKeyPairs kps clist - let dd = fromMaybe A.Null d - cmd <- liftIO $ Pact.mkExec c dd (pmk t) ks [] (Just vNetworkId) nonce - return $ SubmitBatch (cmd NEL.:| []) - -testSend :: Pact.TxCreationTime -> MVar Int -> ClientEnv -> IO RequestKeys -testSend t mNonce env = testBatch t mNonce gp >>= sending v cid env - -testBatch'' :: Pact.ChainId -> Pact.TxCreationTime -> Pact.TTLSeconds -> MVar Int -> GasPrice -> IO SubmitBatch -testBatch'' chain t ttl mnonce gp' = modifyMVar mnonce $ \(!nn) -> do - let nonce = "nonce" <> sshow nn - kps <- testKeyPairs sender00 Nothing - c <- Pact.mkExec "(+ 1 2)" A.Null (pm t) kps [] (Just vNetworkId) (Just nonce) - pure (succ nn, SubmitBatch (pure c)) - where - pm :: Pact.TxCreationTime -> Pact.PublicMeta - pm = Pact.PublicMeta chain "sender00" 1000 gp' ttl - -testBatch' :: Pact.TxCreationTime -> Pact.TTLSeconds -> MVar Int -> GasPrice -> IO SubmitBatch -testBatch' = testBatch'' pactCid - -testBatch :: Pact.TxCreationTime -> MVar Int -> GasPrice -> IO SubmitBatch -testBatch t mnonce = testBatch' t defaultMaxTTL mnonce - -pactDeadBeef :: RequestKey -pactDeadBeef = let (TransactionHash b) = deadbeef - in RequestKey $ Hash b - --- avoiding `scientific` dep here -crGetBlockHeight :: CommandResult a -> Maybe Word64 -crGetBlockHeight = preview (crMetaData . _Just . key "blockHeight" . _Number . to ((fromIntegral :: Integer -> Word64 ) . round . toRational)) diff --git a/test/unit/Chainweb/Test/Pact4/RewardsTest.hs b/test/unit/Chainweb/Test/Pact4/RewardsTest.hs deleted file mode 100644 index ad8037200e..0000000000 --- a/test/unit/Chainweb/Test/Pact4/RewardsTest.hs +++ /dev/null @@ -1,40 +0,0 @@ - -module Chainweb.Test.Pact4.RewardsTest -( tests -) where - - -import Test.Tasty -import Test.Tasty.HUnit - -import Chainweb.Graph -import Chainweb.MinerReward -import Chainweb.Test.TestVersions -import Chainweb.Version - -v :: ChainwebVersion -v = instantCpmTestVersion petersonChainGraph - -tests :: TestTree -tests = testGroup "Chainweb.Test.Pact4.RewardsTest" - [ testGroup "Miner Rewards Unit Tests" - [ rewardsTest - ] - ] - -rewardsTest :: HasCallStack => TestTree -rewardsTest = testCaseSteps "rewards" $ \step -> do - - let k = _kda . minerRewardKda . blockMinerReward v - - step "block heights below initial threshold" - let a = k 0 - assertEqual "initial miner reward is 2.304523" 2.304523 a - - step "block heights at threshold" - let b = k 87600 - assertEqual "max threshold miner reward is 2.304523" 2.304523 b - - step "block heights exceeding thresholds change" - let c = k 87601 - assertEqual "max threshold miner reward is 2.297878" 2.297878 c diff --git a/test/unit/Chainweb/Test/Pact4/SPV.hs b/test/unit/Chainweb/Test/Pact4/SPV.hs deleted file mode 100644 index 0ce12c4e62..0000000000 --- a/test/unit/Chainweb/Test/Pact4/SPV.hs +++ /dev/null @@ -1,549 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - --- | --- Copyright: Copyright © 2018 - 2020 Kadena LLC. --- License: MIT --- Maintainer: Lars Kuhtz , Emily Pillmore --- Stability: experimental --- --- Pact Service SPV Support roundtrip tests --- -module Chainweb.Test.Pact4.SPV -( -- * test suite - tests - -- * repl tests -, standard -, invalidProof -) where - -import Control.Arrow ((***)) -import Control.Concurrent.MVar -import Control.Exception (SomeException, finally) -import Control.Monad -import Control.Lens hiding ((.=)) - -import Data.Aeson as Aeson -import qualified Data.ByteString.Base64.URL as B64U -import qualified Data.ByteString.Char8 as B8 - -import Data.ByteString.Lazy (toStrict) -import qualified Data.HashMap.Strict as HM -import Data.IORef -import Data.List (isInfixOf) -import Data.LogMessage -import Data.Text (pack,Text) -import qualified Data.Text.IO as T -import qualified Data.Text as T -import Data.Vector (Vector) -import qualified Data.Vector as Vector -import Data.Word - -import Ethereum.Block -import Ethereum.Receipt -import Ethereum.Receipt.ReceiptProof -import Ethereum.RLP - -import System.LogLevel - -import Test.Tasty -import Test.Tasty.HUnit - --- internal pact modules - -import qualified Pact.JSON.Encode as J -import Pact.Types.Command -import Pact.Types.Exp -import Pact.Types.Hash -import Pact.Types.PactValue -import Pact.Types.Runtime (toPactId) -import Pact.Types.SPV -import Pact.Types.Term - --- internal chainweb modules - -import Chainweb.BlockCreationTime -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Cut -import Chainweb.Graph -import Chainweb.Miner.Pact - -import Chainweb.Pact.Types (MemPoolAccess, mpaGetBlock) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.SPV.CreateProof -import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils hiding (check) -import Chainweb.Version as Chainweb -import Chainweb.WebPactExecutionService -import Control.Monad.Trans.Resource -import Control.Monad.IO.Class - - --- | Note: These tests are intermittently non-deterministic due to the way --- random chain sampling works with our test harnesses. --- -tests :: RocksDb -> TestTree -tests rdb = testGroup "Chainweb.Test.Pact4.SPV" - [ testCaseSteps "standard SPV verification round trip" $ standard rdb - , testCaseSteps "contTXOUTOld" $ contTXOUTOld rdb - , testCaseSteps "contTXOUTNew" $ contTXOUTNew rdb - , testCaseSteps "tfrTXOUTNew" $ tfrTXOUTNew rdb - , testCaseSteps "ethReceiptProof" $ ethReceiptProof rdb - , testCaseSteps "noEthReceiptProof" $ noEthReceiptProof rdb - , testCaseSteps "invalid proof formats fail" $ invalidProof rdb - ] - -testVer :: ChainwebVersion -testVer = noBridgeCpmTestVersion triangleChainGraph - -bridgeVer :: ChainwebVersion -bridgeVer = fastForkingCpmTestVersion pairChainGraph - --- Only use for debugging. Do not use in tests in the test suite! --- -logg :: LogMessage a => LogLevel -> a -> IO () -logg l - | l <= Warn = T.putStrLn . logText - | otherwise = const $ return () - - --- debugging -_handle' :: SomeException -> IO (Bool, String) -_handle' e = - let - s = show e - in logg System.LogLevel.Error (pack s) >> return (False, s) - --- -------------------------------------------------------------------------- -- --- tests - -standard :: RocksDb -> (String -> IO ()) -> Assertion -standard rdb step = do - (c1,c3) <- roundtrip rdb 0 1 burnGen createSuccess step - checkResult c1 0 "ObjectMap" - checkResult c3 1 "Write succeeded" - -contTXOUTOld :: RocksDb -> (String -> IO ()) -> Assertion -contTXOUTOld rdb step = do - code <- T.readFile "test/pact/contTXOUTOld.pact" - (c1,c3) <- roundtrip rdb 0 1 burnGen (createVerify False code mdata) step - checkResult c1 0 "ObjectMap" - checkResult' c3 1 $ PactResult $ Right $ PLiteral $ LString rSuccessTXOUT - where - mdata = toJSON [fst sender01] :: Value - - -contTXOUTNew :: RocksDb -> (String -> IO ()) -> Assertion -contTXOUTNew rdb step = do - code <- T.readFile "test/pact/contTXOUTNew.pact" - (c1,c3) <- roundtrip' rdb bridgeVer 0 1 burnGen (createVerify True code mdata) step - checkResult c1 0 "ObjectMap" - checkResult' c3 1 $ PactResult $ Right $ PLiteral $ LString rSuccessTXOUT - where - mdata = toJSON [fst sender01] - - -tfrTXOUTNew :: RocksDb -> (String -> IO ()) -> Assertion -tfrTXOUTNew rdb step = do - code <- T.readFile "test/pact/tfrTXOUTNew.pact" - (c1,c3) <- roundtrip' rdb bridgeVer 0 1 transferGen (createVerify True code mdata) step - checkResult c1 0 "Write succeeded" - checkResult' c3 1 $ PactResult $ Right $ PLiteral $ LString rSuccessTXOUT - where - mdata = toJSON [fst sender01] :: Value - -ethReceiptProof :: RocksDb -> (String -> IO ()) -> Assertion -ethReceiptProof rdb step = do - code <- T.readFile "test/pact/ethReceiptProof.pact" - (c1,c3) <- roundtrip' rdb bridgeVer 0 1 transferGen (createVerifyEth code) step - checkResult c1 0 "Write succeeded" - checkResult' c3 1 $ PactResult $ Right $ PLiteral $ LString "ETH Success" - -noEthReceiptProof :: RocksDb -> (String -> IO ()) -> Assertion -noEthReceiptProof rdb step = do - code <- T.readFile "test/pact/ethReceiptProof.pact" - (c1,c3) <- roundtrip' rdb testVer 0 1 transferGen (createVerifyEth code) step - checkResult c1 0 "Write succeeded" - checkResult c3 1 "unsupported SPV types: ETH" - -rSuccessTXOUT :: Text -rSuccessTXOUT = "TXOUT Success" - -invalidProof :: RocksDb -> (String -> IO ()) -> Assertion -invalidProof rdb step = do - (c1,c3) <- roundtrip rdb 0 1 burnGen createInvalidProof step - checkResult c1 0 "ObjectMap" - checkResult c3 1 "Failure: resumePact: no previous execution found" - -checkResult :: HasCallStack => CutOutputs -> Word32 -> String -> Assertion -checkResult co ci expect = - assertSatisfies ("result on chain " ++ show ci ++ " contains '" ++ show expect ++ "'") - (HM.lookup (unsafeChainId ci) co) (isInfixOf expect . show) - -checkResult' :: HasCallStack => CutOutputs -> Word32 -> PactResult -> Assertion -checkResult' co ci expect = case HM.lookup (unsafeChainId ci) co of - Nothing -> assertFailure $ "No result found for chain " ++ show ci - Just v -> case Vector.toList v of - [(_,cr)] -> assertEqual "pact results match" expect (_crResult cr) - _ -> assertFailure $ "expected single result, got " ++ show v - -getCutOutputs :: TestBlockDb -> IO CutOutputs -getCutOutputs (TestBlockDb _ pdb cmv) = do - c <- readMVar cmv - cutToPayloadOutputs c pdb - --- | Populate blocks for every chain of the current cut. Uses provided pact --- service to produce a new block, add it -runCut' :: ChainwebVersion -> TestBlockDb -> WebPactExecutionService -> IO CutOutputs -runCut' v bdb pact = do - runCut v bdb pact (offsetBlockTime second) zeroNoncer noMiner - getCutOutputs bdb - -roundtrip - :: RocksDb - -> Word32 - -- ^ source chain id - -> Word32 - -- ^ target chain id - -> BurnGenerator - -- ^ burn tx generator - -> CreatesGenerator - -- ^ create tx generator - -> (String -> IO ()) - -> IO (CutOutputs, CutOutputs) -roundtrip rdb = roundtrip' rdb testVer - -roundtrip' - :: RocksDb - -> ChainwebVersion - -> Word32 - -- ^ source chain id - -> Word32 - -- ^ target chain id - -> BurnGenerator - -- ^ burn tx generator - -> CreatesGenerator - -- ^ create tx generator - -> (String -> IO ()) - -- ^ logging backend - -> IO (CutOutputs, CutOutputs) -roundtrip' rdb v sid0 tid0 burn create step = runResourceT $ do - bdb <- mkTestBlockDb v rdb - liftIO $ do - tg <- newMVar mempty - let logger = hunitDummyLogger step - mempools <- onAllChains v $ \chain -> - return $ chainToMPA' chain tg - withWebPactExecutionService logger v testPactServiceConfig bdb mempools $ \(pact,_) -> do - - sid <- mkChainId v maxBound sid0 - tid <- mkChainId v maxBound tid0 - - -- track the continuation pact id - pidv <- newEmptyMVar @PactId - - -- cut 0: empty run (not sure why this is needed but test fails without it) - step "cut 0: empty run" - void $ runCut' v bdb pact - - -- cut 1: burn - step "cut 1: burn" - -- Creating the parent took at least 1 second. So 1s is fine as creation time - let t1 = add second epoch - txGen1 <- burn v t1 pidv sid tid - void $ swapMVar tg txGen1 - co1 <- runCut' v bdb pact - - -- setup create txgen with cut 1 - step "setup create txgen with cut 1" - (BlockCreationTime t2) <- view blockCreationTime <$> getParentTestBlockDb bdb tid - hi <- view blockHeight <$> getParentTestBlockDb bdb sid - txGen2 <- create v t2 bdb pidv sid tid hi - - -- cut 2: empty cut for diameter 1 - step "cut 2: empty cut for diameter 1" - void $ swapMVar tg mempty - void $ runCut' v bdb pact - - -- cut 3: create - step "cut 3: create" - void $ swapMVar tg txGen2 - co2 <- runCut' v bdb pact - - return (co1,co2) - - -_debugCut :: CanReadablePayloadCas tbl => String -> Cut -> PayloadDb tbl -> IO () -_debugCut msg c pdb = do - putStrLn $ "CUT: =============== " ++ msg - outs <- cutToPayloadOutputs c pdb - forM_ (HM.toList outs) $ \(cid,vs) -> do - putStrLn $ "Chain: " ++ show cid - forM_ vs $ \(cmd,pr) -> do - putStrLn $ show (_cmdHash cmd) ++ ": " ++ show pr - -type CutOutputs = HM.HashMap Chainweb.ChainId (Vector (Command Text, CommandResult Hash)) - -cutToPayloadOutputs - :: CanReadablePayloadCas tbl - => Cut - -> PayloadDb tbl - -> IO CutOutputs -cutToPayloadOutputs c pdb = do - forM (_cutMap c) $ \bh -> do - Just pwo <- lookupPayloadWithHeight pdb (Just $ view blockHeight bh) (view blockPayloadHash bh) - let txs = Vector.map (toTx *** toCR) (_payloadWithOutputsTransactions pwo) - toTx :: Transaction -> Command Text - toTx (Transaction t) = fromJuste $ decodeStrict' t - toCR :: TransactionOutput -> CommandResult Hash - toCR (TransactionOutput t) = fromJuste $ decodeStrict' t - return txs - -chainToMPA' :: ChainId -> MVar TransactionGenerator -> MemPoolAccess -chainToMPA' chain f = mempty - { mpaGetBlock = \_g pc hi ha he -> do - tg <- readMVar f - txs <- tg chain hi ha he - tos <- pc hi ha ((fmap . fmap . fmap) _pcCode txs) - forM tos $ \case - Left err -> error (sshow err) - Right t -> return t - } - - --- -------------------------------------------------------------------------- -- --- transaction generators - -type TransactionGenerator - = Chainweb.ChainId - -> BlockHeight - -> BlockHash - -> BlockCreationTime - -> IO (Vector Pact4.Transaction) - -type BurnGenerator - = ChainwebVersion -> Time Micros -> MVar PactId -> Chainweb.ChainId -> Chainweb.ChainId -> IO TransactionGenerator - -type CreatesGenerator - = ChainwebVersion - -> Time Micros - -> TestBlockDb - -> MVar PactId - -> Chainweb.ChainId - -> Chainweb.ChainId - -> BlockHeight - -> IO TransactionGenerator - --- | Generate burn/create Pact Service commands on arbitrarily many chains --- -burnGen :: BurnGenerator -burnGen v time pidv sid tid = do - ref0 <- newIORef False - ref1 <- newIORef False - return $ go ref0 ref1 - where - go ref0 ref1 _cid _bhe _bha _ - | sid /= _cid = return mempty - | otherwise = readIORef ref0 >>= \case - True -> return mempty - False -> do - readIORef ref1 >>= \case - True -> return mempty - False -> do - cmd <- buildCwCmd "0" v $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbCreationTime (toTxCreationTime time) $ - set cbChainId sid $ - set cbRPC (mkExec tx1Code tx1Data) $ - defaultCmd - writeIORef ref0 True - - let pid = toPactId $ toUntypedHash $ _cmdHash cmd - - putMVar pidv pid `finally` writeIORef ref1 True - return $ Vector.singleton cmd - - tx1Code = T.unlines - [ "(coin.transfer-crosschain" - , " 'sender00" - , " 'sender01" - , " (read-keyset 'sender01-keyset)" - , " (read-msg 'target-chain-id)" - , " 1.0)" - ] - - tx1Data = - -- sender01 keyset guard - let ks = mkKeySet - ["6be2f485a7af75fedb4b7f153a903f7e6000ca4aa501179c91a2450b777bd2a7"] - "keys-all" - - in object - [ "sender01-keyset" .= J.toJsonViaEncode ks - , "target-chain-id" .= chainIdToText tid - ] - --- | Generate arbitrary coin.transfer call. --- -transferGen :: BurnGenerator -transferGen v time pidv sid _tid = do - ref0 <- newIORef False - ref1 <- newIORef False - return $ go ref0 ref1 - where - go ref0 ref1 _cid _bhe _bha _ - | sid /= _cid = return mempty - | otherwise = readIORef ref0 >>= \case - True -> return mempty - False -> do - readIORef ref1 >>= \case - True -> return mempty - False -> do - cmd <- buildCwCmd "0" v $ - set cbSigners - [mkEd25519Signer' sender00 - [mkTransferCap "sender00" "sender01" 1.0 - ,mkGasCap]] $ - set cbCreationTime (toTxCreationTime time) $ - set cbChainId sid $ - set cbRPC (mkExec' tx1Code) $ - defaultCmd - writeIORef ref0 True - - let pid = toPactId $ toUntypedHash $ _cmdHash cmd - - putMVar pidv pid `finally` writeIORef ref1 True - return $ Vector.singleton cmd - - - - tx1Code = "(coin.transfer 'sender00 'sender01 1.0)" - -createCont - :: ChainwebVersion - -> ChainId - -> MVar PactId - -> Maybe ContProof - -> Time Micros - -> IO (Vector Pact4.Transaction) -createCont v cid pidv proof time = do - pid <- readMVar pidv - fmap Vector.singleton $ - buildCwCmd "1" v $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbCreationTime (toTxCreationTime time) $ - set cbChainId cid $ - set cbRPC (mkCont $ (mkContMsg pid 1) { _cmProof = proof }) $ - defaultCmd - --- | Generate a tx to run 'verify-spv' tests. --- -createVerify :: Bool -> Text -> Value -> CreatesGenerator -createVerify bridge code mdata v time (TestBlockDb wdb pdb _c) _pidv sid tid bhe = do - ref <- newIORef False - return $ go ref - where - go ref cid _bhe _bha _ - | tid /= cid = return mempty - | otherwise = readIORef ref >>= \case - True -> return mempty - False -> do - pf <- createTransactionOutputProof_ wdb pdb tid sid bhe 0 - let q | bridge = object - [ ("proof", String $ encodeB64UrlNoPaddingText $ encodeToByteString pf) - ] - | otherwise = toJSON pf - cmd <- buildCwCmd "0" v $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbCreationTime (toTxCreationTime time) $ - set cbChainId tid $ - set cbRPC (mkExec code (object [("proof",q),("data",mdata)])) $ - defaultCmd - return (Vector.singleton cmd) - `finally` writeIORef ref True - --- | Generate a tx to run 'verify-spv' tests. --- -createVerifyEth :: Text -> CreatesGenerator -createVerifyEth code v time (TestBlockDb _wdb _pdb _c) _pidv _sid tid _bhe = do - ref <- newIORef False - q <- encodeB64UrlNoPaddingText . putRlpByteString <$> receiptProofTest 2 - return $ go q ref - where - go q ref cid _bhe _bha _ - | tid /= cid = return mempty - | otherwise = readIORef ref >>= \case - True -> return mempty - False -> do - -- q <- toJSON <$> createTransactionOutputProof_ wdb pdb tid sid bhe 0 - cmd <- buildCwCmd "0" v $ - set cbSigners [mkEd25519Signer' sender00 []] $ - set cbCreationTime (toTxCreationTime time) $ - set cbChainId tid $ - set cbRPC (mkExec code (object [("proof", toJSON q)])) $ - defaultCmd - return (Vector.singleton cmd) - `finally` writeIORef ref True - -receiptProofTest :: Int -> IO ReceiptProof -receiptProofTest i = do - recps <- readFile "test/pact/receipts.json" - blk <- readFile "test/pact/block.json" - rs <- decodeStrictOrThrow @_ @[RpcReceipt] $ B8.pack recps - block <- decodeStrictOrThrow @_ @RpcBlock $ B8.pack blk - let hdr = _rpcBlockHeader block - rpcReceiptProof hdr [] rs (TransactionIndex $ fromIntegral i) - - --- | Generate the 'create-coin' command in response to the previous 'delete-coin' call. --- Note that we maintain an atomic update to make sure that if a given chain id --- has already called the 'create-coin' half of the transaction, it will not do so again. --- -createSuccess :: CreatesGenerator -createSuccess v time (TestBlockDb wdb pdb _c) pidv sid tid bhe = do - ref <- newIORef False - return $ go ref - where - go ref cid _bhe _bha _ - | tid /= cid = return mempty - | otherwise = readIORef ref >>= \case - True -> return mempty - False -> do - q <- toJSON <$> createTransactionOutputProof_ wdb pdb tid sid bhe 0 - let proof = Just . ContProof . B64U.encode . toStrict . Aeson.encode $ q - createCont v tid pidv proof time - `finally` writeIORef ref True - --- | Execute create-coin command with invalid proof --- -createInvalidProof :: CreatesGenerator -createInvalidProof v time _ pidv _ tid _ = do - ref <- newIORef False - return $ go ref - where - go ref cid _bhe _bha _ - | tid /= cid = return mempty - | otherwise = readIORef ref >>= \case - True -> return mempty - False -> - createCont v tid pidv Nothing time - `finally` writeIORef ref True diff --git a/test/unit/Chainweb/Test/Pact4/SQLite.hs b/test/unit/Chainweb/Test/Pact4/SQLite.hs deleted file mode 100644 index fcf60a7fab..0000000000 --- a/test/unit/Chainweb/Test/Pact4/SQLite.hs +++ /dev/null @@ -1,307 +0,0 @@ -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeApplications #-} - --- | --- Module: Chainweb.Test.Pact4.SQLite --- Copyright: Copyright © 2022 Kadena LLC. --- License: MIT --- Maintainer: Lars Kuhtz --- Stability: experimental --- --- TODO --- -module Chainweb.Test.Pact4.SQLite -( tests -) where - -import Control.Concurrent.MVar -import Control.Monad -import Control.Monad.Trans.State - -import Data.Bifunctor -import qualified Data.ByteString as B -import qualified Data.ByteString.Short as BS -import Data.Coerce -import qualified Data.Hash.SHA3 as SHA3 -import Data.Hash.SHA3 (Sha3_224(..), Sha3_256(..), Sha3_384(..), Sha3_512(..)) -import qualified Data.List as L -import Data.String - -import Pact.Types.SQLite - -import System.IO.Unsafe -import System.Random (genByteString, getStdRandom) - -import Test.Hash.SHA3 -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - - -import Chainweb.Test.Utils -import Chainweb.Pact.Backend.Types (SQLiteEnv) - --- -------------------------------------------------------------------------- -- --- Tests - -tests :: TestTree -tests = withResourceT withInMemSQLiteResource $ \dbIO -> - withResource' (dbIO >>= newMVar) $ \dbVarIO -> - let run = runMsgTest dbVarIO [] - runMonte = runMonteTest dbVarIO [] - - -- Split input - runVar = runMsgTest dbVarIO [1,2,17] - runMonteVar = runMonteTest dbVarIO [1,2,17] - - in testGroup "SQL Tests" - [ testGroup "sha3 single argument" - [ testGroup "ShortMsg" - [ testCase "-" $ run 0 sha3_256ShortMsg - , testCase "224" $ run 224 sha3_224ShortMsg - , testCase "256" $ run 256 sha3_256ShortMsg - , testCase "384" $ run 384 sha3_384ShortMsg - , testCase "512" $ run 512 sha3_512ShortMsg - ] - , testGroup "LongMsg" - [ testCase "-" $ run 0 sha3_256LongMsg - , testCase "224" $ run 224 sha3_224LongMsg - , testCase "256" $ run 256 sha3_256LongMsg - , testCase "384" $ run 384 sha3_384LongMsg - , testCase "512" $ run 512 sha3_512LongMsg - ] - , testGroup "Monte" - [ testCase "-" $ runMonte 0 sha3_256Monte - , testCase "224" $ runMonte 224 sha3_224Monte - , testCase "256" $ runMonte 256 sha3_256Monte - , testCase "384" $ runMonte 384 sha3_384Monte - , testCase "512" $ runMonte 512 sha3_512Monte - ] - ] - , testGroup "sha3 multiple arguments" - [ testGroup "ShortMsg" - [ testCase "-" $ runVar 0 sha3_256ShortMsg - , testCase "224" $ runVar 224 sha3_224ShortMsg - , testCase "256" $ runVar 256 sha3_256ShortMsg - , testCase "384" $ runVar 384 sha3_384ShortMsg - , testCase "512" $ runVar 512 sha3_512ShortMsg - ] - , testGroup "LongMsg" - [ testCase "-" $ runVar 0 sha3_256LongMsg - , testCase "224" $ runVar 224 sha3_224LongMsg - , testCase "256" $ runVar 256 sha3_256LongMsg - , testCase "384" $ runVar 384 sha3_384LongMsg - , testCase "512" $ runVar 512 sha3_512LongMsg - ] - , testGroup "Monte" - [ testCase "-" $ runMonteVar 0 sha3_256Monte - , testCase "224" $ runMonteVar 224 sha3_224Monte - , testCase "256" $ runMonteVar 256 sha3_256Monte - , testCase "384" $ runMonteVar 384 sha3_384Monte - , testCase "512" $ runMonteVar 512 sha3_512Monte - ] - ] - , withAggTable dbVarIO 512 128 $ \tbl -> testGroup "sha3 aggregation" - [ testCase "-" $ testAgg 0 dbVarIO tbl - , testCase "224" $ testAgg 224 dbVarIO tbl - , testCase "256" $ testAgg 256 dbVarIO tbl - , testCase "384" $ testAgg 384 dbVarIO tbl - , testCase "512" $ testAgg 512 dbVarIO tbl - ] - , testGroup "sha3 msgTable" - [ testCase "-" $ msgTableTest dbVarIO 0 sha3_256ShortMsg - , testCase "224" $ msgTableTest dbVarIO 224 sha3_224ShortMsg - , testCase "256" $ msgTableTest dbVarIO 256 sha3_256ShortMsg - , testCase "384" $ msgTableTest dbVarIO 384 sha3_384ShortMsg - , testCase "512" $ msgTableTest dbVarIO 512 sha3_512ShortMsg - ] - , testGroup "sha3 monteTable" - [ testCase "-" $ monteTableTest dbVarIO 0 sha3_256Monte - , testCase "sha224" $ monteTableTest dbVarIO 224 sha3_224Monte - , testCase "sha256" $ monteTableTest dbVarIO 256 sha3_256Monte - , testCase "sha384" $ monteTableTest dbVarIO 384 sha3_384Monte - , testCase "sha512" $ monteTableTest dbVarIO 512 sha3_512Monte - ] - ] - --- -------------------------------------------------------------------------- -- --- - -sha :: IsString a => Monoid a => Int -> a -sha 0 = "sha3" -sha i = "sha3_" <> fromString (show i) - -shaa :: IsString a => Monoid a => Int -> a -shaa 0 = "sha3a" -shaa i = "sha3a_" <> fromString (show i) - --- -------------------------------------------------------------------------- -- --- - -runMsgTest :: IO (MVar SQLiteEnv) -> [Int] -> Int -> MsgFile -> IO () -runMsgTest dbVarIO splitArg n f = do - dbVar <- dbVarIO - withMVar dbVar $ \db -> do - msgAssert (\_ a b -> a @?= b) (sqliteSha3 db n splitArg) f - -runMonteTest :: IO (MVar SQLiteEnv) -> [Int] -> Int -> MonteFile -> IO () -runMonteTest dbVarIO splitArg n f = do - dbVar <- dbVarIO - withMVar dbVar $ \db -> do - monteAssert (\_ a b -> a @?= b) (sqliteSha3 db n splitArg) f - --- -------------------------------------------------------------------------- -- --- Repeated use in a query - -msgTableTest :: IO (MVar SQLiteEnv) -> Int -> MsgFile -> IO () -msgTableTest dbVarIO n msgFile = do - dbVar <- dbVarIO - withMVar dbVar $ \db -> do - msgTable db name msgFile - rows <- qry_ db query [RInt] - h <- case rows of - [[SInt r]] -> return r - [[x]] -> error $ "unexpected return value: " <> show x - [a] -> error $ "unexpected number of result fields: " <> show (length a) - a -> error $ "unexpected number of result rows: " <> show (length a) - h @?= 0 - exec_ db ("DROP TABLE " <> fromString name) - where - query = "SELECT sum(" <> sha n <> "(substr(msg,1,len)) != md) FROM " <> fromString name - name = "msgTable_" <> show n - -msgTable :: SQLiteEnv -> String -> MsgFile -> IO () -msgTable db name msgFile = do - exec_ db ("CREATE TABLE " <> tbl <> " (len INT, msg BLOB, md BLOB)") - forM_ (_msgVectors msgFile) $ \i -> do - let l = fromIntegral $ _msgLen i - exec' - db - ("INSERT INTO " <> tbl <> " VALUES (?, ?, ?)") - [SInt l, SBlob (_msgMsg i), SBlob (_msgMd i)] - where - tbl = fromString name - --- -------------------------------------------------------------------------- -- --- Repeated use in query for MonteFile - -monteTableTest :: IO (MVar SQLiteEnv) -> Int -> MonteFile -> IO () -monteTableTest dbVarIO n monteFile = do - dbVar <- dbVarIO - withMVar dbVar $ \db -> - monteTableTest_ db n monteFile - -monteTableTest_ :: SQLiteEnv -> Int -> MonteFile -> IO () -monteTableTest_ db n monteFile = do - monteTable db monteTableName monteFile - let query = fromString $ unwords - [ "WITH RECURSIVE" - , " tmp(c, m) AS (" - , " SELECT 0, ? UNION ALL SELECT c + 1, " <> sha n <> "(m) FROM tmp" - , " WHERE c <= 100000" - , " )," - , " tmp2(count, md) AS (" - , " SELECT c / 1000 - 1 AS count, m AS md FROM tmp" - , " WHERE c % 1000 == 0 AND count >= 0" - , " )" - , "SELECT" - , " sum(tmp2.md != " <> monteTableName <> ".md)" - , "FROM tmp2" - , "LEFT JOIN " <> monteTableName - , "ON tmp2.count = " <> monteTableName <> ".count" - ] - rows <- qry db query [SBlob $ _monteSeed monteFile] [RInt] - case rows of - [[SInt r]] -> r @?= 0 - [[x]] -> error $ "unexpected return value: " <> show x - [a] -> error $ "unexpected number of result fields: " <> show (length a) - a -> error $ "unexpected number of result rows: " <> show (length a) - where - monteTableName = "monteTable_" <> show n - -monteTable :: SQLiteEnv -> String -> MonteFile -> IO () -monteTable db name monteFile = do - exec_ db ("CREATE TABLE " <> tbl <> " (count INT, md BLOB)") - forM_ (_monteVectors monteFile) $ \i -> do - exec' - db - ("INSERT INTO " <> tbl <> " VALUES (?, ?)") - [SInt (fromIntegral $ _monteCount i), SBlob (_monteMd i)] - where - tbl = fromString name - --- -------------------------------------------------------------------------- -- --- Aggregate functions --- --- split a large input accross table rows - -withAggTable - :: IO (MVar SQLiteEnv) - -> Int - -> Int - -> (IO (String, [B.ByteString]) -> TestTree) - -> TestTree -withAggTable dbVarIO rowCount chunkSize = - withResource' createAggTable - where - tbl = "bytesTbl" - createAggTable = do - dbVar <- dbVarIO - withMVar dbVar $ \db -> do - input <- getStdRandom $ runState $ - replicateM rowCount $ state (genByteString chunkSize) - exec_ db ("CREATE TABLE " <> fromString tbl <> " (bytes BLOB)") - forM_ input $ \i -> - exec' db ("INSERT INTO " <> fromString tbl <> " VALUES(?)") [SBlob i] - return (tbl, input) - -testAgg :: Int -> IO (MVar SQLiteEnv) -> IO (String, [B.ByteString]) -> IO () -testAgg n dbVarIO tblIO = do - dbVar <- dbVarIO - (tbl, input) <- first fromString <$> tblIO - withMVar dbVar $ \db -> do - rows <- qry_ db ("SELECT " <> shaa n <> "(bytes) FROM " <> tbl) [RBlob] - h <- case rows of - [[SBlob r]] -> return r - [[x]] -> error $ "unexpected return value: " <> show x - [a] -> error $ "unexpected number of result fields: " <> show (length a) - a -> error $ "unexpected number of result rows: " <> show (length a) - - hBytes <- hash n (mconcat input) - h @?= hBytes - where - hash :: Int -> B.ByteString -> IO B.ByteString - hash d b = case d of - 0 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_256 b - 224 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_224 b - 256 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_256 b - 384 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_384 b - 512 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_512 b - _ -> error $ "unsupported SHA3 digest size: " <> show d - -hashToByteString :: (SHA3.Hash a, Coercible a BS.ShortByteString) => a -> B.ByteString -hashToByteString = BS.fromShort . coerce - --- -------------------------------------------------------------------------- -- --- SHA3 Implementation - -sqliteSha3 :: SQLiteEnv -> Int -> [Int] -> B.ByteString -> B.ByteString -sqliteSha3 db n argSplit arg = unsafePerformIO $ do - rows <- qry db queryStr params [RBlob] - case rows of - [[SBlob r]] -> return r - [[x]] -> error $ "unexpected return value: " <> show x - [a] -> error $ "unexpected number of result fields: " <> show (length a) - a -> error $ "unexpected number of result rows: " <> show (length a) - where - argN = length argSplit - argStr = fromString $ L.intercalate "," $ replicate (argN + 1) "?" - - queryStr = "select " <> sha n <> "(" <> argStr <> ")" - - params = go argSplit arg - - go [] l = [SBlob l] - go (h:t) bs = let (a,b) = B.splitAt h bs in SBlob a : go t b diff --git a/test/unit/Chainweb/Test/Pact4/TTL.hs b/test/unit/Chainweb/Test/Pact4/TTL.hs deleted file mode 100644 index f199f2ce4c..0000000000 --- a/test/unit/Chainweb/Test/Pact4/TTL.hs +++ /dev/null @@ -1,302 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - -module Chainweb.Test.Pact4.TTL -( tests ) where - -import Control.Concurrent.MVar -import Control.Lens (set, view) -import Control.Monad -import Control.Monad.Catch - -import qualified Data.Vector as V - -import Pact.Types.ChainMeta -import Pact.Types.Exp(ParsedCode(..)) - -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Chainweb.BlockCreationTime -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB hiding (withBlockHeaderDb) -import Chainweb.BlockHeaderDB.Internal (unsafeInsertBlockHeaderDb) -import Chainweb.Miner.Pact - -import Chainweb.Pact.Service.BlockValidation -import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Types -import Chainweb.Pact4.Validations (defaultLenientTimeSlop) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import Chainweb.Utils -import Chainweb.Version -import Chainweb.Version.Utils - -import Chainweb.Storage.Table.RocksDB -import Data.Either (isRight) - --- -------------------------------------------------------------------------- -- --- Settings - -testVer :: ChainwebVersion -testVer = instantCpmTestVersion peterson - -defTtl :: Seconds -defTtl = 60 * 60 * 2 -- 2 hours - -genblock :: BlockHeader -genblock = genesisBlockHeader testVer (someChainId testVer) - --- -------------------------------------------------------------------------- -- --- Tests - --- | Test new block validation of transaction timings. --- --- New block validation must be stricter than block validation. I.e. each block --- that passes new block validation must also pass block validation, or, --- equivalently, each block that is rejected by block validation must also be --- rejected by new block validation. --- --- Thus, all failing tests are expected to already fail during pre block --- validation. --- -tests :: RocksDb -> TestTree -tests rdb = testGroup "Chainweb.Test.Pact4.TTL" - [ testGroup "timing tests" - [ withTestPact rdb testTxTime - , withTestPact rdb testTxTimeLenient - , withTestPact rdb testTxTimeFail1 - , withTestPact rdb testTxTimeFail2 - , withTestPact rdb testTtlTooLarge - , withTestPact rdb testTtlSmall - , withTestPact rdb testExpired - , withTestPact rdb testExpiredTight - , withTestPact rdb testJustMadeItSmall - , withTestPact rdb testJustMadeItLarge - ] - ] - --- -------------------------------------------------------------------------- -- --- Tests - -testTxTime :: IO Ctx -> TestTree -testTxTime ctxIO = - testCase "tx time of parent time and default ttl pass validation" $ do - T2 hdr1 _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 0) 1 - T2 hdr2 _ <- mineBlock ctxIO (offset 0) (ParentHeader hdr1) (Nonce 1) 1 - void $ mineBlock ctxIO (offset (-1)) (ParentHeader hdr2) (Nonce 2) 1 - -testTxTimeLenient :: IO Ctx -> TestTree -testTxTimeLenient ctxIO = - testCase "testTxTimeLenient: tx time of parent time + slop and default ttl succeeds during new block validation" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 1 - void $ doNewBlock ctxIO (offset defaultLenientTimeSlop) (ParentHeader hdr) (Nonce 2) 1 - -testTxTimeFail1 :: IO Ctx -> TestTree -testTxTimeFail1 ctxIO = - testCase "testTxTimeFail1: tx time of parent time + slop + 1 and default ttl fails during new block validation" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 1 - assertDoPreBlockFailure $ doNewBlock ctxIO (offset (succ defaultLenientTimeSlop)) (ParentHeader hdr) (Nonce 2) 1 - -testTxTimeFail2 :: IO Ctx -> TestTree -testTxTimeFail2 ctxIO = - testCase "tx time of parent time + 1000 and default ttl fails during new block validation" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 1 - assertDoPreBlockFailure $ doNewBlock ctxIO (offset 1000) (ParentHeader hdr) (Nonce 2) 1 - -testTtlTooLarge :: IO Ctx -> TestTree -testTtlTooLarge ctxIO = - testCase "too large TTL fails validation" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 1 - assertDoPreBlockFailure $ doNewBlock ctxIO (ttl (100 * 24 * 3600)) (ParentHeader hdr) (Nonce 2) 1 - -testTtlSmall :: IO Ctx -> TestTree -testTtlSmall ctxIO = - testCase "testTtlSmall: small TTL passes validation" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 1 - void $ doNewBlock ctxIO (ttl 5) (ParentHeader hdr) (Nonce 2) 1 - -testExpired :: IO Ctx -> TestTree -testExpired ctxIO = - testCase "expired transaction fails validation" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 500 - assertDoPreBlockFailure $ doNewBlock ctxIO (offsetTtl (-400) 300) (ParentHeader hdr) (Nonce 2) 1 - -testExpiredTight :: IO Ctx -> TestTree -testExpiredTight ctxIO = - testCase "tightly expired transaction fails validation" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 500 - assertDoPreBlockFailure $ doNewBlock ctxIO (offsetTtl (-300) 300) (ParentHeader hdr) (Nonce 2) 1 - -testJustMadeItSmall :: IO Ctx -> TestTree -testJustMadeItSmall ctxIO = - testCase "testJustMadeItSmall" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 100 - void $ doNewBlock ctxIO (offsetTtl (-99) 100) (ParentHeader hdr) (Nonce 2) 1 - -testJustMadeItLarge :: IO Ctx -> TestTree -testJustMadeItLarge ctxIO = - testCase "testJustMadeItLarge" $ do - T2 hdr _ <- mineBlock ctxIO mempty (ParentHeader genblock) (Nonce 1) 500 - void $ doNewBlock ctxIO (offsetTtl (-399) 400) (ParentHeader hdr) (Nonce 2) 1 - --- -------------------------------------------------------------------------- -- --- Mempool Access - --- Mempool access implementations that provide transactions with different --- values for creation time and TTL. - --- | Use parent block time for tx creation time, change ttl. -ttl :: Seconds -> MemPoolAccess -ttl = modAtTtl id - --- | Offset tx creation time relative to parent block time, use default ttl. -offset :: Seconds -> MemPoolAccess -offset = modAt . add . secondsToTimeSpan - --- | Offset tx creation time relative to parent block time, AND set ttl. -offsetTtl :: Seconds -> Seconds -> MemPoolAccess -offsetTtl off ttl' = modAtTtl (add (secondsToTimeSpan off)) ttl' - --- | Set tx creation time relative to parent block time, use default ttl. -modAt :: (Time Micros -> Time Micros) -> MemPoolAccess -modAt f = modAtTtl f defTtl - -modAtTtl :: (Time Micros -> Time Micros) -> Seconds -> MemPoolAccess -modAtTtl f (Seconds t) = mempty - { mpaGetBlock = \_ validate bh hash bct -> do - let txTime = toTxCreationTime $ f $ _bct bct - tt = TTLSeconds (int t) - outtxs <- fmap V.singleton $ buildCwCmd (sshow bh) testVer - $ set cbCreationTime txTime - $ set cbTTL tt - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbRPC (mkExec' "1") - $ defaultCmd - - tos <- validate bh hash $ (fmap . fmap . fmap) _pcCode outtxs - - unless (all isRight tos) $ throwM DoPreBlockFailure - return $ V.fromList [ to | Right to <- V.toList tos ] - } - --- -------------------------------------------------------------------------- -- --- Block Validation - -mineBlock - :: IO Ctx - -> MemPoolAccess - -> ParentHeader - -> Nonce - -> Seconds - -- ^ Block time - -> IO (T2 BlockHeader PayloadWithOutputs) -mineBlock ctxIO mempool parent nonce s = do - T2 hdr payload <- doNewBlock ctxIO mempool parent nonce s - doValidateBlock ctxIO hdr payload - return $ T2 hdr payload - --- | Create new block --- -doNewBlock - :: IO Ctx - -> MemPoolAccess - -> ParentHeader - -> Nonce - -> Seconds - -- ^ Block time - -> IO (T2 BlockHeader PayloadWithOutputs) -doNewBlock ctxIO mempool parent nonce t = do - ctx <- ctxIO - unlessM (tryPutMVar (_ctxMempool ctx) mempool) $ - error "Test failure: mempool access is not empty. Some previous test step failed unexpectedly" - - bip <- throwIfNoHistory =<< newBlock noMiner NewBlockFill parent (_ctxQueue ctx) - let payload = forAnyPactVersion finalizeBlock bip - let - creationTime = BlockCreationTime - . add (secondsToTimeSpan t) -- 10 seconds - . _bct . view blockCreationTime - $ _parentHeader parent - bh = newBlockHeader - mempty - (_payloadWithOutputsPayloadHash payload) - nonce - creationTime - parent - -- no need for mining, since testVer uses a trivial target - return $ T2 bh payload - where - --- | Validate Block --- -doValidateBlock - :: IO Ctx - -> BlockHeader - -> PayloadWithOutputs - -> IO () -doValidateBlock ctxIO header payload = do - ctx <- ctxIO - _mv' <- validateBlock header (CheckablePayloadWithOutputs payload) $ _ctxQueue ctx - addNewPayload (_ctxPdb ctx) (view blockHeight header) payload - unsafeInsertBlockHeaderDb (_ctxBdb ctx) header - -- FIXME FIXME FIXME: do at least some checks? - --- -------------------------------------------------------------------------- -- --- Misc Utils - -data ValidationFailure - = DoPreBlockFailure - deriving (Show) - -instance Exception ValidationFailure - -assertDoPreBlockFailure - :: IO a - -> IO () -assertDoPreBlockFailure action = try action >>= \case - Left DoPreBlockFailure -> return () - Right{} -> assertFailure "Expected DoPreBlockFailure but the action succeeded." - -data Ctx = Ctx - { _ctxMempool :: !(MVar MemPoolAccess) - , _ctxQueue :: !PactQueue - , _ctxPdb :: !(PayloadDb RocksDbTable) - , _ctxBdb :: !BlockHeaderDb - } - -withTestPact - :: RocksDb - -> (IO Ctx -> TestTree) - -> TestTree -withTestPact rdb test = - withResource' newEmptyMVar $ \mempoolVarIO -> - withPactTestBlockDb testVer cid rdb (mempool mempoolVarIO) testPactServiceConfig $ \ios -> - test $ do - (_, pq, bdb) <- ios - mp <- mempoolVarIO - bhdb <- getBlockHeaderDb cid bdb - return $ Ctx mp pq (_bdbPayloadDb bdb) bhdb - where - cid = someChainId testVer - mempool mempoolVarIO = return $ mempty - { mpaGetBlock = \g val he h p -> - mempoolVarIO >>= tryTakeMVar >>= \case - Nothing -> mempty - Just mp -> mpaGetBlock mp g val he h p - } diff --git a/test/unit/Chainweb/Test/Pact4/TransactionTests.hs b/test/unit/Chainweb/Test/Pact4/TransactionTests.hs deleted file mode 100644 index 88334342bb..0000000000 --- a/test/unit/Chainweb/Test/Pact4/TransactionTests.hs +++ /dev/null @@ -1,410 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE ScopedTypeVariables #-} - --- I have no idea why this warning is being triggered --- for things that are clearly used -{-# options_ghc -fno-warn-unused-top-binds #-} - --- | --- Module: Chainweb.Test.BlockHeaderDB --- Copyright: Copyright © 2018 Kadena LLC. --- License: MIT --- Maintainer: Emily Pillmore --- Stability: experimental --- --- Test func in TransactionExec --- -module Chainweb.Test.Pact4.TransactionTests ( tests ) where - -import Test.Tasty -import Test.Tasty.HUnit - -import Control.Concurrent (readMVar) -import Control.Lens hiding ((.=)) -import Control.Monad - -import Data.Aeson -import Data.Aeson.Lens -import Data.Foldable (for_, traverse_) -import Data.Function (on) -import Data.List (intercalate) -import Data.Text (Text,isInfixOf,unpack) -import qualified System.LogLevel as L - --- internal pact modules - -import Pact.Gas -import Pact.Interpreter -import Pact.Parse -import Pact.Repl -import Pact.Repl.Types -import Pact.Types.Command -import qualified Pact.Types.Hash as H -import Pact.Types.PactValue -import Pact.Types.RPC -import Pact.Types.Runtime -import Pact.Types.SPV - --- internal chainweb modules - -import Chainweb.BlockCreationTime -import Chainweb.BlockHeader.Internal -import Chainweb.BlockHeight -import Chainweb.Logger -import Chainweb.Miner.Pact -import Chainweb.Pact4.Templates -import Chainweb.Pact4.TransactionExec -import qualified Chainweb.Pact4.Types as Pact4 - -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import Chainweb.Utils -import Chainweb.Version as V -import Chainweb.Version.RecapDevelopment -import Chainweb.Version.Mainnet -import Chainweb.Test.Pact4.Utils -import qualified Chainweb.Pact4.ModuleCache as Pact4 - - --- ---------------------------------------------------------------------- -- --- Global settings - -v :: ChainwebVersion -v = RecapDevelopment - -coinReplV1 :: FilePath -coinReplV1 = "pact/coin-contract/v1/coin.repl" - -coinReplV4 :: FilePath -coinReplV4 = "pact/coin-contract/v4/coin-v4.repl" - -coinReplV5 :: FilePath -coinReplV5 = "pact/coin-contract/v5/coin-v5.repl" - -coinReplV6 :: FilePath -coinReplV6 = "pact/coin-contract/coin.repl" - -nsReplV1 :: FilePath -nsReplV1 = "pact/namespaces/v1/ns.repl" - -nsReplV2 :: FilePath -nsReplV2 = "pact/namespaces/ns.repl" - -logger :: GenericLogger -#if DEBUG_TEST -logger = genericLogger L.Info (step . T.unpack) -#else -logger = genericLogger L.Error (\_ -> return ()) -#endif - --- ---------------------------------------------------------------------- -- --- Tests - -tests :: TestTree -tests = testGroup "Chainweb.Test.Pact4.TransactionTests" - [ testGroup "Pact Command Parsing" - [ testCase "Build Exec with Data" buildExecWithData - , testCase "Build Exec without Data" buildExecWithoutData - ] - , testGroup "Pact Code Unit Tests" - [ testGroup "Coin Contract repl tests" - [ testCase "v1" (ccReplTests coinReplV1) - -- v2 and v3 repl tests were consolidated in v4 - , testCase "v4" (ccReplTests coinReplV4) - , testCase "v5" (ccReplTests coinReplV5) - , testCase "v6" (ccReplTests coinReplV6) - ] - , testGroup "Namespace repl unit tests" - [ testCase "Ns-v1 repl tests" $ ccReplTests nsReplV1 - , testCase "Ns-v2 repl tests" $ ccReplTests nsReplV2 - ] - , testCase "Payer Repl Tests" (ccReplTests "pact/gas-payer/gas-payer-v1.repl") - ] - , testGroup "Precompiled Statements Tests" - [ testCase "Basic Injection Test" baseInjTest - , testCase "Fixed Injection Test" fixedInjTest - ] - -- , testGroup "Coinbase Vuln Fix Tests" - -- [ testCoinbase797DateFix - -- , testCase "testCoinbaseEnforceFailure" testCoinbaseEnforceFailure - -- , testCase "testCoinbaseUpgradeDevnet0" (testCoinbaseUpgradeDevnet (unsafeChainId 0) 3) - -- , testCase "testCoinbaseUpgradeDevnet1" (testCoinbaseUpgradeDevnet (unsafeChainId 1) 4) - -- ] - -- , testGroup "20-Chain Fork Upgrade Tests" - -- [ testTwentyChainDevnetUpgrades - -- ] - ] - --- ---------------------------------------------------------------------- -- --- Coin Contract repl tests - -ccReplTests :: FilePath -> Assertion -ccReplTests ccFile = do - (r, rst) <- execScript' Quiet ccFile - either fail (\_ -> execRepl rst) r - where - execRepl rst = do - lst <- readMVar $! _eePactDbVar . _rEnv $ rst - for_ (_rlsTests lst) $ \tr -> - traverse_ (uncurry failCC) $ trFailure tr - - failCC i e = assertFailure $ renderInfo (_faInfo i) <> ": " <> unpack e - -loadCC :: FilePath -> IO (PactDbEnv LibState, Pact4.ModuleCache) -loadCC = loadScript - -loadScript :: FilePath -> IO (PactDbEnv LibState, Pact4.ModuleCache) -loadScript fp = do - (r, rst) <- execScript' Quiet fp - either fail (const $ return ()) r - let pdb = PactDbEnv - (view (rEnv . eePactDb) rst) - (view (rEnv . eePactDbVar) rst) - mc = view (rEvalState . evalRefs . rsLoadedModules) rst - -- TODO: setup eval env & run the code & and pass - return (pdb, Pact4.moduleCacheFromHashMap mc) - --- ---------------------------------------------------------------------- -- --- Template vuln tests - -baseInjTest :: Assertion -baseInjTest = mkCoinbaseCmd badMinerId minerKeys0 (ParsedDecimal 1.0) >>= \case - ExecMsg (ParsedCode pccode _pcexps) _pmdata -> - assertEqual "Precompiled exploit yields correct code" (unpack pccode) exploit - where - exploit = "(coin.coinbase \"alpha\" (read-keyset \"miner-keyset\") 9999999.99)" - <> "(coin.coinbase \"alpha\" (read-keyset \"miner-keyset\") (read-decimal \"reward\"))" - -fixedInjTest :: Assertion -fixedInjTest = case exec of - ExecMsg (ParsedCode pccode _pcexps) _pmdata - | isInfixOf "coinbase" pccode -> assertFailure - $ "Precompiled statement contains exploitable code: " - <> unpack pccode - | isInfixOf "read-keyset" pccode -> assertFailure - $ "Precompiled statement contains exploitable code: " - <> unpack pccode - | otherwise -> return () - where - (_, exec) = mkCoinbaseTerm badMinerId minerKeys0 (ParsedDecimal 1.0) - - -buildExecWithData :: Assertion -buildExecWithData = void $ buildExecParsedCode maxBound - (Just $ object [ "data" .= (1 :: Int) ]) "(+ 1 1)" - -buildExecWithoutData :: Assertion -buildExecWithoutData = void $ buildExecParsedCode maxBound Nothing "(+ 1 1)" - -badMinerId :: MinerId -badMinerId = MinerId "alpha\" (read-keyset \"miner-keyset\") 9999999.99)(coin.coinbase \"alpha" - -minerKeys0 :: MinerKeys -minerKeys0 = MinerKeys $ mkKeySet - ["f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27"] - "default" - --- ---------------------------------------------------------------------- -- --- Vuln 792 fork tests - -testCoinbase797DateFix :: TestTree -testCoinbase797DateFix = testCaseSteps "testCoinbase791Fix" $ \step -> do - (pdb,mc) <- loadCC coinReplV1 - - step "pre-fork code injection succeeds, no enforced precompile" - - cmd <- buildExecParsedCode maxBound Nothing "(coin.get-balance \"tester01\")" - - doCoinbaseExploit pdb mc preForkHeight cmd False $ \case - Left _ -> assertFailure "local call to get-balance failed" - Right (PLiteral (LDecimal d)) - | d == 1000.1 -> return () - | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d - Right l -> assertFailure $ "wrong return type: " <> show l - - step "post-fork code injection fails, no enforced precompile" - - cmd' <- buildExecParsedCode maxBound Nothing - "(coin.get-balance \"tester01\\\" (read-keyset \\\"miner-keyset\\\") 1000.0)(coin.coinbase \\\"tester01\")" - - doCoinbaseExploit pdb mc postForkHeight cmd' False $ \case - Left _ -> assertFailure "local call to get-balance failed" - Right (PLiteral (LDecimal d)) - | d == 0.1 -> return () - | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d - Right l -> assertFailure $ "wrong return type: " <> show l - - step "pre-fork code injection fails, enforced precompile" - - doCoinbaseExploit pdb mc preForkHeight cmd' True $ \case - Left _ -> assertFailure "local call to get-balance failed" - Right (PLiteral (LDecimal d)) - | d == 0.2 -> return () - | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d - Right l -> assertFailure $ "wrong return type: " <> show l - - step "post-fork code injection fails, enforced precompile" - - doCoinbaseExploit pdb mc postForkHeight cmd' True $ \case - Left _ -> assertFailure "local call to get-balance failed" - Right (PLiteral (LDecimal d)) - | d == 0.3 -> return () - | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d - Right l -> assertFailure $ "wrong return type: " <> show l - - where - doCoinbaseExploit pdb mc height localCmd precompile testResult = do - let ctx = Pact4.TxContext (mkTestParentHeader $ height - 1) noPublicMeta miner - - void $ applyCoinbase Mainnet01 logger pdb 0.1 ctx - (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled precompile) mc - - let h = H.toUntypedHash (H.hash "" :: H.PactHash) - tenv = TransactionEnv Transactional pdb logger Nothing noPublicData - noSPVSupport Nothing 0.0 (RequestKey h) 0 emptyExecutionConfig Nothing Nothing - txst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty - - CommandResult _ _ (PactResult pr) _ _ _ _ _ <- evalTransactionM tenv txst $! - applyExec 0 defaultInterpreter localCmd [] [] h permissiveNamespacePolicy - - testResult pr - - miner = Miner - (MinerId "tester01\" (read-keyset \"miner-keyset\") 1000.0)(coin.coinbase \"tester01") - (MinerKeys $ mkKeySet ["b67e109352e8e33c8fe427715daad57d35d25d025914dd705b97db35b1bfbaa5"] "keys-all") - - preForkHeight = 121451 - postForkHeight = 121452 - - -- | someBlockHeader is a bit slow for the vuln797Fix to trigger. So, instead - -- of mining a full chain we fake the height. - -- - mkTestParentHeader :: BlockHeight -> ParentHeader - mkTestParentHeader h = ParentHeader $ someBlockHeader (slowForkingCpmTestVersion singleton) 10 - & blockHeight .~ h - -testCoinbaseEnforceFailure :: Assertion -testCoinbaseEnforceFailure = do - (pdb,mc) <- loadCC coinReplV4 - r <- tryAllSynchronous $ - applyCoinbase toyVersion logger pdb 0.1 - (Pact4.TxContext someParentHeader noPublicMeta badMiner) - (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled False) mc - case r of - Left e -> - if isInfixOf "CoinbaseFailure" (sshow e) then - return () - else assertFailure $ "Coinbase failed for unknown reason: " <> show e - Right _ -> assertFailure "Coinbase did not fail for bad miner id" - where - badMiner = Miner (MinerId "") (MinerKeys $ mkKeySet [] "<") - blockHeight' = 123 - someParentHeader = ParentHeader $ someTestVersionHeader - & blockHeight .~ blockHeight' - & blockCreationTime .~ BlockCreationTime [timeMicrosQQ| 2019-12-10T01:00:00.0 |] - -testCoinbaseUpgradeDevnet :: V.ChainId -> BlockHeight -> Assertion -testCoinbaseUpgradeDevnet cid upgradeHeight = - testUpgradeScript "test/pact/coin-and-devaccts.repl" cid upgradeHeight test - where - test (T2 cr mcm) = case (_crLogs cr,mcm) of - (_,Nothing) -> assertFailure "Expected module cache from successful upgrade" - (Nothing,_) -> assertFailure "Expected logs from successful upgrade" - (Just logs,_) -> - matchLogs (logResults logs) expectedResults - - expectedResults = - [ ("USER_coin_coin-table", "NoMiner", Just (Number 0.1)) - , ("SYS_modules","fungible-v2",Nothing) - , ("SYS_modules","coin",Nothing) - , ("USER_coin_coin-table","sender07",Just (Number 998662.3)) - , ("USER_coin_coin-table","sender09",Just (Number 998662.1)) - ] - -testTwentyChainDevnetUpgrades :: TestTree -testTwentyChainDevnetUpgrades = testCaseSteps "Test 20-chain Devnet upgrades" $ \step -> do - step "Check that 20-chain upgrades fire at block height 60" - testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) 60 test0 - - step "Check that 20-chain upgrades do not fire at block heights < 60 and > 60" - testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) (60 - 1) test1 - testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) (60 + 1) test1 - - step "Check that 20-chain upgrades do not fire at on other chains" - testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 1) 60 test1 - - step "Check that 20-chain upgrades succeed even if e7f7 balance is insufficient" - testUpgradeScript "test/pact/twenty-chain-insufficient-bal.repl" (unsafeChainId 0) 60 test1 - where - test0 (T2 cr _) = case _crLogs cr of - Just logs -> matchLogs (logResults logs) - [ ("USER_coin_coin-table","NoMiner",Just (Number 0.1)) - , ( "USER_coin_coin-table" - , "e7f7634e925541f368b827ad5c72421905100f6205285a78c19d7b4a38711805" - , Just (Number 50.0) -- 100.0 tokens remediated - ) - ] - Nothing -> assertFailure "Expected logs from upgrade" - - test1 (T2 cr _) = case _crLogs cr of - Just logs -> matchLogs (logResults logs) - [ ("USER_coin_coin-table", "NoMiner", Just (Number 0.1)) - ] - Nothing -> assertFailure "Expected logs from upgrade" - --- ---------------------------------------------------------------------- -- --- Utils - -testUpgradeScript - :: FilePath - -> V.ChainId - -> BlockHeight - -> (T2 (CommandResult [TxLogJson]) (Maybe Pact4.ModuleCache) -> IO ()) - -> IO () -testUpgradeScript script cid bh test = do - (pdb, mc) <- loadScript script - r <- tryAllSynchronous $ applyCoinbase v logger pdb 0.1 (Pact4.TxContext parent noPublicMeta noMiner) - (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled False) mc - case r of - Left e -> assertFailure $ "tx execution failed: " ++ show e - Right cr -> test cr - where - parent = ParentHeader $ someBlockHeader v bh - & blockChainwebVersion .~ _versionCode v - & blockChainId .~ cid - & blockHeight .~ pred bh - -matchLogs :: [(Text, Text, Maybe Value)] -> [(Text, Text, Maybe Value)] -> IO () -matchLogs expectedResults actualResults - | length actualResults /= length expectedResults = void $ - assertFailure $ intercalate "\n" - [ "matchLogs: length mismatch " - <> show (length actualResults) <> " /= " <> show (length expectedResults) - , "actual: " ++ show actualResults - , "expected: " ++ show expectedResults - ] - | otherwise = void $ zipWithM matchLog actualResults expectedResults - where - matchLog actual expected = do - (assertEqual "domain matches" `on` view _1) actual expected - (assertEqual "key matches" `on` view _2) actual expected - (assertEqual "balance matches" `on` view _3) actual expected - -logResults :: [TxLogJson] -> [(Text, Text, Maybe Value)] -logResults = fmap go - where - go x = case decodeTxLogJson x of - Left e -> error $ "unable to parse TxLog: " <> show e - Right (r :: TxLog Value) -> f r - f l = - ( _txDomain l - , _txKey l - -- This lens is because some of the transacctions happen post 420 fork - -- So the object representation changes due to the RowData type. - , l ^? txValue . _Object . (ix "balance" `failing` ix "$d" . _Object . ix "balance") - ) diff --git a/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs b/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs deleted file mode 100644 index 16c0f69d1c..0000000000 --- a/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs +++ /dev/null @@ -1,16 +0,0 @@ -module Chainweb.Test.Pact4.VerifierPluginTest -( tests -) where - -import Test.Tasty - -import Chainweb.Storage.Table.RocksDB - -import qualified Chainweb.Test.Pact4.VerifierPluginTest.Transaction -import qualified Chainweb.Test.Pact4.VerifierPluginTest.Unit - -tests :: RocksDb -> TestTree -tests rdb = testGroup "Chainweb.Test.Pact4.VerifierPluginTest" - [ Chainweb.Test.Pact4.VerifierPluginTest.Unit.tests - , Chainweb.Test.Pact4.VerifierPluginTest.Transaction.tests rdb - ] From e7622cb8c818055dae336b4bff5e97bb51f15f8e Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 11 Mar 2025 17:01:31 -0400 Subject: [PATCH 100/378] Checkpointer work --- cabal.project | 4 +- chainweb.cabal | 8 +- src/Chainweb/BlockHash.hs | 26 +- src/Chainweb/BlockHeader.hs | 21 +- src/Chainweb/BlockHeader/Internal.hs | 84 +- src/Chainweb/BlockHeader/Validation.hs | 13 +- src/Chainweb/Chainweb.hs | 1 - src/Chainweb/Chainweb/Configuration.hs | 16 +- src/Chainweb/Core/Brief.hs | 14 +- src/Chainweb/Cut.hs | 7 +- src/Chainweb/Cut/Create.hs | 17 +- src/Chainweb/Mempool/Consensus.hs | 192 --- src/Chainweb/Mempool/InMem.hs | 10 +- .../Mempool/InMem/ValidatingConfig.hs | 41 +- src/Chainweb/Mempool/Mempool.hs | 2 +- src/Chainweb/Miner/Coordinator.hs | 13 +- src/Chainweb/Miner/Miners.hs | 15 +- src/Chainweb/Miner/PayloadCache.hs | 1 + src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 251 ++- .../Backend/PactState/GrandHash/Import.hs | 25 +- src/Chainweb/Pact/Backend/Types.hs | 43 +- src/Chainweb/Pact/Backend/Utils.hs | 99 +- src/Chainweb/Pact/PactService.hs | 1502 ++++++++--------- src/Chainweb/Pact/PactService/Checkpointer.hs | 644 +++---- .../Pact/PactService/Checkpointer/Internal.hs | 345 ---- src/Chainweb/Pact/PactService/ExecBlock.hs | 395 ++--- src/Chainweb/Pact/RestAPI.hs | 89 +- src/Chainweb/Pact/RestAPI/Client.hs | 72 +- src/Chainweb/Pact/RestAPI/SPV.hs | 3 +- src/Chainweb/Pact/RestAPI/Server.hs | 292 ++-- src/Chainweb/Pact/SPV.hs | 60 +- src/Chainweb/Pact/Service/PactInProcApi.hs | 176 -- src/Chainweb/Pact/Transaction.hs | 4 +- src/Chainweb/Pact/TransactionExec.hs | 156 +- src/Chainweb/Pact/Types.hs | 397 ++++- src/Chainweb/Pact/Validations.hs | 81 +- src/Chainweb/Parent.hs | 39 + src/Chainweb/PayloadProvider.hs | 5 +- src/Chainweb/PayloadProvider/EVM.hs | 2 +- src/Chainweb/PayloadProvider/Minimal.hs | 38 +- src/Chainweb/PayloadProvider/P2P.hs | 7 +- src/Chainweb/PayloadProvider/Pact.hs | 154 +- src/Chainweb/Ranked.hs | 13 +- src/Chainweb/RestAPI/Orphans.hs | 24 +- src/Chainweb/SPV/CreateProof.hs | 7 +- src/Chainweb/SPV/EventProof.hs | 46 +- src/Chainweb/SPV/OutputProof.hs | 9 +- src/Chainweb/SPV/PayloadProof.hs | 3 +- src/Chainweb/SPV/VerifyProof.hs | 1 + src/Chainweb/Sync/WebBlockHeaderStore.hs | 32 +- src/Chainweb/VerifierPlugin.hs | 15 +- src/Chainweb/VerifierPlugin/Allow.hs | 10 +- .../VerifierPlugin/Hyperlane/Announcement.hs | 23 +- .../VerifierPlugin/Hyperlane/Message.hs | 5 +- .../Hyperlane/Message/After225.hs | 33 +- .../Hyperlane/Message/Before225.hs | 121 -- src/Chainweb/Version.hs | 12 +- src/Chainweb/Version/Development.hs | 2 +- src/Chainweb/Version/EvmDevelopment.hs | 3 +- src/Chainweb/Version/Guards.hs | 12 +- src/Chainweb/Version/Mainnet.hs | 4 +- src/Chainweb/Version/RecapDevelopment.hs | 2 +- src/Chainweb/Version/Testnet04.hs | 4 +- src/Chainweb/Version/Testnet05.hs | 2 +- src/Chainweb/Version/Utils.hs | 2 +- src/Chainweb/WebBlockHeaderDB.hs | 13 +- src/Chainweb/WebPactExecutionService.hs | 298 ---- 67 files changed, 2615 insertions(+), 3445 deletions(-) delete mode 100644 src/Chainweb/Mempool/Consensus.hs delete mode 100644 src/Chainweb/Pact/PactService/Checkpointer/Internal.hs delete mode 100644 src/Chainweb/Pact/Service/PactInProcApi.hs create mode 100644 src/Chainweb/Parent.hs delete mode 100644 src/Chainweb/VerifierPlugin/Hyperlane/Message/Before225.hs delete mode 100644 src/Chainweb/WebPactExecutionService.hs diff --git a/cabal.project b/cabal.project index bd5d0b91b6..743f7fc72c 100644 --- a/cabal.project +++ b/cabal.project @@ -101,8 +101,8 @@ source-repository-package source-repository-package type: git location: https://github.com/kadena-io/pact-5.git - tag: 06f5d75996eba48e4ee165a993326606baaea98c - --sha256: 0wmipbxws45d1axplqx6q4naq0sm3vzh3r354706wiar6a1f556q + tag: 47b95f55ae71c1332d9f61dbd5f232b99ba95840 + --sha256: 1870dady4wldmh973wzi93rlbs4r0y277g0g1wmp4kvw9l8hqy1x source-repository-package type: git diff --git a/chainweb.cabal b/chainweb.cabal index c127217864..193fc0d9bb 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -194,7 +194,6 @@ library , Chainweb.Logger , Chainweb.Logging.Config , Chainweb.Logging.Miner - , Chainweb.Mempool.Consensus , Chainweb.Mempool.CurrentTxs , Chainweb.Mempool.InMem , Chainweb.Mempool.InMem.ValidatingConfig @@ -218,6 +217,7 @@ library , Chainweb.MinerReward , Chainweb.NodeVersion , Chainweb.OpenAPIValidation + , Chainweb.Parent , Chainweb.Payload , Chainweb.Payload.PayloadStore , Chainweb.Payload.PayloadStore.InMemory @@ -279,7 +279,6 @@ library , Chainweb.VerifierPlugin.Hyperlane.Binary , Chainweb.VerifierPlugin.Hyperlane.Message , Chainweb.VerifierPlugin.Hyperlane.Message.After225 - , Chainweb.VerifierPlugin.Hyperlane.Message.Before225 , Chainweb.VerifierPlugin.Hyperlane.Utils , Chainweb.Version , Chainweb.Version.Development @@ -292,7 +291,6 @@ library , Chainweb.Version.Testnet05 , Chainweb.Version.Utils , Chainweb.WebBlockHeaderDB - , Chainweb.WebPactExecutionService , Data.IVar , Data.LogMessage @@ -335,7 +333,6 @@ library , Chainweb.Pact.Conversion , Chainweb.Pact.PactService , Chainweb.Pact.PactService.Checkpointer - , Chainweb.Pact.PactService.Checkpointer.Internal , Chainweb.Pact.PactService.ExecBlock , Chainweb.Pact.RestAPI , Chainweb.Pact.RestAPI.Client @@ -343,7 +340,6 @@ library , Chainweb.Pact.RestAPI.SPV , Chainweb.Pact.RestAPI.Server , Chainweb.Pact.SPV - , Chainweb.Pact.Service.PactInProcApi , Chainweb.Pact.NoCoinbase , Chainweb.Pact.Templates , Chainweb.Pact.TransactionExec @@ -373,6 +369,7 @@ library , chainweb-storage >= 0.1 , chronos >= 1.1 , clock >= 0.7 + , comonad , configuration-tools >= 0.6 , containers >= 0.5 , crypton >= 0.31 @@ -392,6 +389,7 @@ library , exceptions >= 0.8 , file-embed >= 0.0 , filepath >= 1.4 + , free , ghc-compact >= 0.1 , growable-vector >= 0.1 , hashable >= 1.4 diff --git a/src/Chainweb/BlockHash.hs b/src/Chainweb/BlockHash.hs index 7c1f6f340b..6a15c8789d 100644 --- a/src/Chainweb/BlockHash.hs +++ b/src/Chainweb/BlockHash.hs @@ -92,6 +92,7 @@ import Chainweb.Crypto.MerkleLog import Chainweb.Graph import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse +import Chainweb.Parent import Chainweb.Ranked import Chainweb.Utils import Chainweb.Utils.Serialization @@ -122,8 +123,8 @@ instance Hashable (BlockHash_ a) where hashWithSalt s (BlockHash bytes) = hashWithSalt s bytes {-# INLINE hashWithSalt #-} -instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag (BlockHash_ a) where - type Tag (BlockHash_ a) = 'BlockHashTag +instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag (Parent (BlockHash_ a)) where + type Tag (Parent (BlockHash_ a)) = 'BlockHashTag toMerkleNode = encodeMerkleTreeNode fromMerkleNode = decodeMerkleTreeNode {-# INLINE toMerkleNode #-} @@ -189,7 +190,7 @@ instance MerkleHashAlgorithm a => HasTextRepresentation (BlockHash_ a) where -- TODO(greg): BlockHashRecord should be a sorted vector newtype BlockHashRecord = BlockHashRecord - { _getBlockHashRecord :: HM.HashMap ChainId BlockHash } + { _getBlockHashRecord :: HM.HashMap ChainId (Parent BlockHash) } deriving stock (Show, Eq, Generic) deriving anyclass (Hashable, NFData) deriving newtype (ToJSON, FromJSON) @@ -197,24 +198,24 @@ newtype BlockHashRecord = BlockHashRecord makeLenses ''BlockHashRecord type instance Index BlockHashRecord = ChainId -type instance IxValue BlockHashRecord = BlockHash +type instance IxValue BlockHashRecord = Parent BlockHash instance Ixed BlockHashRecord where ix i = getBlockHashRecord . ix i instance IxedGet BlockHashRecord -instance Each BlockHashRecord BlockHashRecord BlockHash BlockHash where +instance Each BlockHashRecord BlockHashRecord (Parent BlockHash) (Parent BlockHash) where each f = fmap BlockHashRecord . each f . _getBlockHashRecord encodeBlockHashRecord :: BlockHashRecord -> Put encodeBlockHashRecord (BlockHashRecord r) = do putWord16le (int $ length r) - traverse_ (bimapM_ encodeChainId encodeBlockHash) $ L.sort $ HM.toList r + traverse_ (bimapM_ encodeChainId (encodeBlockHash . unwrapParent)) $ L.sort $ HM.toList r decodeBlockHashWithChainId - :: Get (ChainId, BlockHash) -decodeBlockHashWithChainId = (,) <$!> decodeChainId <*> decodeBlockHash + :: Get (ChainId, Parent BlockHash) +decodeBlockHashWithChainId = (,) <$!> decodeChainId <*> (Parent <$> decodeBlockHash) decodeBlockHashRecord :: Get BlockHashRecord decodeBlockHashRecord = do @@ -225,10 +226,10 @@ decodeBlockHashRecord = do decodeBlockHashWithChainIdChecked :: HasChainId p => Expected p - -> Get (ChainId, BlockHash) + -> Get (ChainId, Parent BlockHash) decodeBlockHashWithChainIdChecked p = (,) <$!> decodeChainIdChecked p - <*> decodeBlockHash + <*> (Parent <$> decodeBlockHash) -- to use this wrap the runGet into some MonadThrow. -- @@ -242,7 +243,7 @@ decodeBlockHashRecordChecked ps = do hashes <- mapM decodeBlockHashWithChainIdChecked (Expected <$!> getExpected ps) return $! BlockHashRecord $! HM.fromList hashes -blockHashRecordToVector :: BlockHashRecord -> V.Vector BlockHash +blockHashRecordToVector :: BlockHashRecord -> V.Vector (Parent BlockHash) blockHashRecordToVector = V.fromList . fmap snd . L.sort . HM.toList . _getBlockHashRecord blockHashRecordChainIdx :: BlockHashRecord -> ChainId -> Maybe Int @@ -257,7 +258,7 @@ blockHashRecordFromVector => HasChainId c => g -> c - -> V.Vector BlockHash + -> V.Vector (Parent BlockHash) -> BlockHashRecord blockHashRecordFromVector g cid = BlockHashRecord . HM.fromList @@ -279,4 +280,3 @@ encodeRankedBlockHash = encodeRanked encodeBlockHash decodeRankedBlockHash :: Get RankedBlockHash decodeRankedBlockHash = decodeRanked decodeBlockHash - diff --git a/src/Chainweb/BlockHeader.hs b/src/Chainweb/BlockHeader.hs index ca25caf6a3..5dc47b1905 100644 --- a/src/Chainweb/BlockHeader.hs +++ b/src/Chainweb/BlockHeader.hs @@ -10,12 +10,8 @@ -- 'Setter', again only in tests, use 'Chainweb.BlockHeader.Internal' instead. module Chainweb.BlockHeader ( --- * Newtype wrappers for function parameters - I.Parent(..) -, I._Parent - -- * Block Payload Hash -, I.BlockPayloadHash + I.BlockPayloadHash , I.BlockPayloadHash_(..) , I.encodeBlockPayloadHash , I.decodeBlockPayloadHash @@ -86,6 +82,9 @@ module Chainweb.BlockHeader -- * Genesis BlockHeader , I.isGenesisBlockHeader +, I.isGenesisBlockHeader' +, I.childBlockHeight +, I.parentBlockHeight , I.genesisParentBlockHash , I.genesisBlockHeader , I.genesisBlockHeaders @@ -104,14 +103,16 @@ module Chainweb.BlockHeader where import Chainweb.ChainId (ChainId) -import Chainweb.BlockWeight (BlockWeight) +import Chainweb.BlockCreationTime (BlockCreationTime) +import Chainweb.BlockHash (BlockHash, BlockHashRecord) +import Chainweb.BlockHeader.Internal qualified as I import Chainweb.BlockHeight (BlockHeight) +import Chainweb.BlockWeight (BlockWeight) import Chainweb.Version (ChainwebVersionCode) +import Chainweb.Parent import Chainweb.Payload (BlockPayloadHash) import Chainweb.Difficulty (HashTarget) -import Chainweb.BlockHash (BlockHash, BlockHashRecord) -import Chainweb.BlockHeader.Internal qualified as I -import Chainweb.BlockCreationTime (BlockCreationTime) + import Control.Lens (Getter) blockFlags :: Getter I.BlockHeader I.FeatureFlags @@ -120,7 +121,7 @@ blockFlags = I.blockFlags blockCreationTime :: Getter I.BlockHeader BlockCreationTime blockCreationTime = I.blockCreationTime -blockParent :: Getter I.BlockHeader BlockHash +blockParent :: Getter I.BlockHeader (Parent BlockHash) blockParent = I.blockParent blockAdjacentHashes :: Getter I.BlockHeader BlockHashRecord diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index 2054bef8f0..2d30750ae2 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -44,12 +44,9 @@ -- are writing tests. module Chainweb.BlockHeader.Internal ( --- * Newtype wrappers for function parameters - Parent(..) -, _Parent -- * Block Payload Hash -, BlockPayloadHash + BlockPayloadHash , BlockPayloadHash_(..) , encodeBlockPayloadHash , decodeBlockPayloadHash @@ -120,6 +117,9 @@ module Chainweb.BlockHeader.Internal -- * Genesis BlockHeader , isGenesisBlockHeader +, isGenesisBlockHeader' +, childBlockHeight +, parentBlockHeight , genesisParentBlockHash , genesisBlockHeader , genesisBlockHeaders @@ -149,7 +149,9 @@ import Chainweb.Difficulty import Chainweb.Graph import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse +import Chainweb.Parent import Chainweb.PowHash +import Chainweb.Ranked import Chainweb.Storage.Table import Chainweb.Time import Chainweb.TreeDB (TreeDbEntry(..)) @@ -321,7 +323,7 @@ data BlockHeader :: Type where -- an advantage to an attacker that would increase the success -- probability for an attack. - , _blockParent :: {-# UNPACK #-} !BlockHash + , _blockParent :: {-# UNPACK #-} !(Parent BlockHash) -- ^ authoritative , _blockAdjacentHashes :: !BlockHashRecord @@ -566,7 +568,7 @@ epochStart ph@(Parent p) adj (BlockCreationTime bt) -- * Can handle non continuous non uniform distribution of hash power -- accross chains. -- - _adjustmentMax = maximum adjCreationTimes .-. _blockCreationTime p + _adjustmentMax = maximum adjCreationTimes .-. fmap _blockCreationTime ph -- the maximum is at least @_blockCreationTime p@ and thus the result is -- greater or equal 0. @@ -592,17 +594,17 @@ epochStart ph@(Parent p) adj (BlockCreationTime bt) _adjustmentAvg = x `divTimeSpan` length adjCreationTimes where x :: TimeSpan Micros - x = foldr1 addTimeSpan $ (.-. _blockCreationTime p) <$> adjCreationTimes + x = foldr1 addTimeSpan $ (.-. fmap _blockCreationTime ph) <$> adjCreationTimes -- This includes the parent header itself, but excludes any adjacent genesis -- headers which usually don't have accurate creation time. -- -- The result is guaranteed to be non-empty -- - adjCreationTimes = fmap (_blockCreationTime) - $ HM.insert cid (unwrapParent ph) - $ HM.filter (not . isGenesisBlockHeader) - $ fmap unwrapParent adj + adjCreationTimes = fmap (fmap _blockCreationTime) + $ HM.insert cid ph + $ HM.filter (not . isGenesisBlockHeader . unwrapParent) + $ adj parentIsFirstOnNewChain = _blockHeight p > 1 && _blockHeight p == genesisHeight ver cid + 1 @@ -611,28 +613,39 @@ epochStart ph@(Parent p) adj (BlockCreationTime bt) -- -------------------------------------------------------------------------- -- -- Newtype wrappers for function parameters -newtype Parent h = Parent { unwrapParent :: h } - deriving stock (Show, Functor, Foldable, Traversable, Eq, Ord, Generic) - deriving newtype (NFData, ToJSON, FromJSON, Hashable, LeftTorsor) - -instance HasChainId h => HasChainId (Parent h) where - _chainId = _chainId . unwrapParent -instance HasChainwebVersion h => HasChainwebVersion (Parent h) where - _chainwebVersion = _chainwebVersion . unwrapParent -instance HasChainGraph h => HasChainGraph (Parent h) where - _chainGraph = _chainGraph . unwrapParent - isGenesisBlockHeader :: BlockHeader -> Bool isGenesisBlockHeader b = _blockHeight b == genesisHeight (_chainwebVersion b) (_chainId b) +-- Alternate method of detecting a genesis block using only its parent + chain + version +isGenesisBlockHeader' :: ChainwebVersion -> ChainId -> Parent BlockHash -> Bool +isGenesisBlockHeader' v cid ph = + genesisParentBlockHash v cid == ph + +-- The height of a child of the given block. +childBlockHeight :: ChainwebVersion -> ChainId -> Parent (Ranked BlockHash) -> BlockHeight +childBlockHeight v cid (Parent rbh) + | isGenesisBlockHeader' v cid (Parent (_rankedBlockHashHash rbh)) = + _rankedBlockHashHeight rbh + | otherwise = + succ (_rankedBlockHashHeight rbh) + +-- | The height of a parent of the given block. Note that you need the hash of +-- the parent and the height of the child. +parentBlockHeight :: ChainwebVersion -> ChainId -> Ranked (Parent BlockHash) -> Parent (Ranked BlockHash) +parentBlockHeight v cid (Ranked height parentHash) + | isGenesisBlockHeader' v cid parentHash = + Parent $ Ranked height $ unwrapParent parentHash + | otherwise = + Parent $ Ranked (pred height) $ unwrapParent parentHash + -- | The genesis block hash includes the Chainweb version and the 'ChainId' -- within the Chainweb version. -- -- It is the '_blockParent' of the genesis block -- -genesisParentBlockHash :: HasChainId p => ChainwebVersion -> p -> BlockHash -genesisParentBlockHash v p = BlockHash $ MerkleLogHash +genesisParentBlockHash :: HasChainId p => ChainwebVersion -> p -> Parent BlockHash +genesisParentBlockHash v p = Parent $ BlockHash $ MerkleLogHash $ merkleRoot $ merkleTree @ChainwebMerkleHashAlgorithm [ InputNode "CHAINWEB_GENESIS" , encodeMerkleInputNode encodeChainwebVersionCode (_versionCode v) @@ -741,7 +754,7 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag BlockHeader wh type MerkleLogHeader BlockHeader = '[ FeatureFlags , BlockCreationTime - , BlockHash + , Parent BlockHash , HashTarget , BlockPayloadHash , ChainId @@ -751,7 +764,7 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag BlockHeader wh , EpochStartTime , Nonce ] - type MerkleLogBody BlockHeader = BlockHash + type MerkleLogBody BlockHeader = Parent BlockHash toLog bh = merkleLog @ChainwebMerkleHashAlgorithm root entries where @@ -809,7 +822,7 @@ encodeBlockHeaderWithoutHash :: BlockHeader -> Put encodeBlockHeaderWithoutHash b = do encodeFeatureFlags (_blockFlags b) encodeBlockCreationTime (_blockCreationTime b) - encodeBlockHash (_blockParent b) + encodeBlockHash (unwrapParent $ _blockParent b) encodeBlockHashRecord (_blockAdjacentHashes b) encodeHashTarget (_blockTarget b) encodeBlockPayloadHash (_blockPayloadHash b) @@ -857,7 +870,7 @@ decodeBlockHeaderWithoutHash :: Get BlockHeader decodeBlockHeaderWithoutHash = do a0 <- decodeFeatureFlags a1 <- decodeBlockCreationTime - a2 <- decodeBlockHash -- parent hash + a2 <- Parent <$> decodeBlockHash a3 <- decodeBlockHashRecord a4 <- decodeHashTarget a5 <- decodeBlockPayloadHash @@ -889,7 +902,7 @@ decodeBlockHeader :: Get BlockHeader decodeBlockHeader = BlockHeader <$> decodeFeatureFlags <*> decodeBlockCreationTime - <*> decodeBlockHash -- parent hash + <*> (Parent <$> decodeBlockHash) <*> decodeBlockHashRecord <*> decodeHashTarget <*> decodeBlockPayloadHash @@ -924,7 +937,7 @@ blockAdjacentChainIds = to _blockAdjacentChainIds -- throws a @ChainNotAdjacentException@ if @cid@ is not adajcent with @_chainId -- h@ in the chain graph of @h@. -- -getAdjacentHash :: MonadThrow m => HasChainId p => p -> BlockHeader -> m BlockHash +getAdjacentHash :: MonadThrow m => HasChainId p => p -> BlockHeader -> m (Parent BlockHash) getAdjacentHash p b = firstOf (blockAdjacentHashes . ixg (_chainId p)) b ??? ChainNotAdjacentException (_chainId b) @@ -1075,7 +1088,7 @@ newBlockHeader adj pay nonce t p@(Parent b) = fromLog @ChainwebMerkleHashAlgorithm $ newMerkleLog $ mkFeatureFlags :+: t - :+: _blockHash b + :+: Parent (_blockHash b) :+: target :+: pay :+: cid @@ -1089,7 +1102,7 @@ newBlockHeader adj pay nonce t p@(Parent b) = cid = _chainId p v = _chainwebVersion p target = powTarget p adj t - adjHashes = BlockHashRecord $ (_blockHash . unwrapParent) <$> adj + adjHashes = BlockHashRecord $ fmap _blockHash <$> adj -- -------------------------------------------------------------------------- -- -- TreeDBEntry instance @@ -1100,7 +1113,10 @@ instance TreeDbEntry BlockHeader where rank = int . _blockHeight parent e | isGenesisBlockHeader e = Nothing - | otherwise = Just (_blockParent e) + | otherwise = Just (unwrapParent $ _blockParent e) + +instance IsRanked BlockHeader where + rank = _blockHeight -- -------------------------------------------------------------------------- -- -- Misc @@ -1180,5 +1196,3 @@ _rankedBlockPayloadHash h = RankedBlockPayloadHash rankedBlockPayloadHash :: Getter BlockHeader RankedBlockPayloadHash rankedBlockPayloadHash = to _rankedBlockPayloadHash {-# INLINE rankedBlockPayloadHash #-} - -makePrisms ''Parent diff --git a/src/Chainweb/BlockHeader/Validation.hs b/src/Chainweb/BlockHeader/Validation.hs index ea126d0545..3d8517bc13 100644 --- a/src/Chainweb/BlockHeader/Validation.hs +++ b/src/Chainweb/BlockHeader/Validation.hs @@ -112,6 +112,7 @@ import Chainweb.BlockHeader import Chainweb.ChainId import Chainweb.ChainValue import Chainweb.Difficulty +import Chainweb.Parent import Chainweb.Time import Chainweb.Utils import Chainweb.Version @@ -212,7 +213,7 @@ chainStep -- ^ Block header under scrutiny -> m ChainStep chainStep p b - | view blockParent b == view blockHash (unwrapParent p) + | view blockParent b == fmap (view blockHash) p = return $ ChainStep p b | otherwise = throwM $ InvalidChainStepParameters p b @@ -237,12 +238,12 @@ webStep as hp@(ChainStep _ h) = WebStep <$> itraverse f hashes <*> pure hp where - hashes :: HM.HashMap ChainId BlockHash + hashes :: HM.HashMap ChainId (Parent BlockHash) hashes = view (blockAdjacentHashes . getBlockHashRecord) h f cid a = case HM.lookup cid as of Nothing -> throwM $ InvalidWebStepParameters as hp Just x - | view blockHash (unwrapParent x) == a -> return x + | fmap (view blockHash) x == a -> return x | otherwise -> throwM $ InvalidWebStepParameters as hp _webStepAdjs :: WebStep -> HM.HashMap ChainId (Parent BlockHeader) @@ -546,7 +547,7 @@ validateBlockParentExists -> m (Either ValidationFailureType ChainStep) validateBlockParentExists lookupParent h | isGenesisBlockHeader h = return $ Right $ ChainStep (Parent h) h - | otherwise = lookupParent (view blockParent h) >>= \case + | otherwise = lookupParent (unwrapParent $ view blockParent h) >>= \case (Just !p) -> return $ Right $ ChainStep (Parent p) h Nothing -> return $ Left MissingParent @@ -569,7 +570,7 @@ validateAllParentsExist lookupParent h = runExceptT $ WebStep f c ph | genesisParentBlockHash v c == ph = return $ Parent $ genesisBlockHeader v c - | otherwise = lift (lookupParent $ ChainValue c ph) >>= \case + | otherwise = lift (lookupParent $ fmap unwrapParent $ ChainValue c ph) >>= \case (Just !p) -> return $ Parent p Nothing -> throwError MissingAdjacentParent @@ -787,7 +788,7 @@ prop_block_adjacent_parents (WebStep as (ChainStep _ b)) -- chainId indexes in web adjadent parent record references the -- genesis block parent hashes | otherwise - = adjsHashes == (view blockHash . unwrapParent <$> as) + = adjsHashes == (fmap (view blockHash) <$> as) -- chainId indexes in web adjadent parent record and web step are -- referencing the same hashes && iall (\cid h -> cid == _chainId h) as diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 7d4ec74265..a3bdb428f2 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -383,7 +383,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir { _pactReorgLimit = _configReorgLimit conf , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf , _pactQueueSize = _configPactQueueSize conf - , _pactResetDb = resetDb , _pactAllowReadsInLocal = _configAllowReadsInLocal conf , _pactUnlimitedInitialRewind = isJust (_cutDbParamsInitialHeightLimit cutDbParams) || diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 16a3827a89..8b9019cba2 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -98,7 +98,6 @@ import Chainweb.HostAddress import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Config -import Chainweb.Pact.Backend.DbCache (DbCacheLimitBytes) import Chainweb.Pact.Types (RewindLimit(..)) import Chainweb.Pact.Types (defaultReorgLimit, defaultPreInsertCheckTimeout) import Chainweb.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) @@ -128,7 +127,7 @@ import Data.List qualified as L import Data.Maybe import Data.Text qualified as T import Data.Text.Read qualified as T -import GHC.Generics hiding (from) +import GHC.Generics hiding (from, to) import Network.Wai.Handler.Warp hiding (Port) import Numeric.Natural (Natural) import P2P.Node.Configuration @@ -136,6 +135,7 @@ import Pact.JSON.Encode qualified as J import Prelude hiding (log) import System.Directory import qualified Pact.Core.Gas as Pact +import Pact.Core.StableEncoding -- -------------------------------------------------------------------------- -- -- Payload Provider Configuration @@ -688,9 +688,9 @@ instance ToJSON ChainwebConfiguration where , "p2p" .= _configP2p o , "throttling" .= _configThrottling o , "mempoolP2p" .= _configMempoolP2p o - , "gasLimitOfBlock" .= J.toJsonViaEncode (_configBlockGasLimit o) + , "gasLimitOfBlock" .= J.toJsonViaEncode (StableEncoding $ _configBlockGasLimit o) , "logGas" .= _configLogGas o - , "minGasPrice" .= J.toJsonViaEncode (_configMinGasPrice o) + , "minGasPrice" .= J.toJsonViaEncode (StableEncoding $ _configMinGasPrice o) , "pactQueueSize" .= _configPactQueueSize o , "reorgLimit" .= _configReorgLimit o , "preInsertCheckTimeout" .= _configPreInsertCheckTimeout o @@ -719,9 +719,9 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where <*< configP2p %.: "p2p" % o <*< configThrottling %.: "throttling" % o <*< configMempoolP2p %.: "mempoolP2p" % o - <*< configBlockGasLimit ..: "gasLimitOfBlock" % o + <*< configBlockGasLimit . iso StableEncoding _stableEncoding ..: "gasLimitOfBlock" % o <*< configLogGas ..: "logGas" % o - <*< configMinGasPrice ..: "minGasPrice" % o + <*< configMinGasPrice . iso StableEncoding _stableEncoding ..: "minGasPrice" % o <*< configPactQueueSize ..: "pactQueueSize" % o <*< configReorgLimit ..: "reorgLimit" % o <*< configAllowReadsInLocal ..: "allowReadsInLocal" % o @@ -747,13 +747,13 @@ pChainwebConfiguration = id <*< configP2p %:: pP2pConfiguration <*< configMempoolP2p %:: pEnableConfig "mempool-p2p" pMempoolP2pConfig - <*< configBlockGasLimit .:: jsonOption + <*< configBlockGasLimit . iso StableEncoding _stableEncoding .:: jsonOption % long "block-gas-limit" <> help "the sum of all transaction gas fees in a block must not exceed this number" <*< configLogGas .:: boolOption_ % long "log-gas" <> help "log gas consumed by Pact commands" - <*< configMinGasPrice .:: jsonOption + <*< configMinGasPrice . iso StableEncoding _stableEncoding .:: jsonOption % long "min-gas-price" <> help "the gas price of an individual transaction in a block must not be beneath this number" <*< configPactQueueSize .:: jsonOption diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index daf446b0f8..324537e806 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -28,6 +28,7 @@ import Chainweb.ChainId import Chainweb.Cut import Chainweb.Cut.CutHashes import Chainweb.PayloadProvider +import Chainweb.Parent import Chainweb.Ranked import Chainweb.Utils import Control.Lens @@ -99,7 +100,18 @@ instance Brief NewPayload where <> ":" <> brief (_newPayloadRankedParentHash np) instance Brief SyncState where - brief = briefJson + brief ss = T.intercalate ":" + [ brief (_syncStateHeight ss) + , brief (_syncStateBlockHash ss) + , brief (_syncStateBlockPayloadHash ss) + ] + +instance Brief ConsensusState where + brief cs = + "{ \"latest\": \"" <> brief (_consensusStateLatest cs) + <> "\", \"safe\": \"" <> brief (_consensusStateSafe cs) + <> "\", \"final\": \"" <> brief (_consensusStateFinal cs) + <> "\" }" -- -------------------------------------------------------------------------- -- -- Utils diff --git a/src/Chainweb/Cut.hs b/src/Chainweb/Cut.hs index 3001aa06dc..911a7a20b5 100644 --- a/src/Chainweb/Cut.hs +++ b/src/Chainweb/Cut.hs @@ -141,6 +141,7 @@ import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB +import Chainweb.Parent -- -------------------------------------------------------------------------- -- -- Cut @@ -639,7 +640,7 @@ isBraidingOfCutPair a b = do ba <- getAdjacentHash a b -- adjacent of b on chain of a return $! (view blockParent a == ba && view blockParent b == ab) -- same graph - || (view blockHeight a > view blockHeight b) && ab == view blockHash b + || (view blockHeight a > view blockHeight b) && ab == Parent (view blockHash b) || (view blockHeight a < view blockHeight b) && True {- if same graph: ba == view blockHash a -} -- -------------------------------------------------------------------------- -- @@ -674,13 +675,13 @@ isMonotonicCutExtension c h = do where monotonic = view blockParent h == case c ^? ixg (_chainId h) . blockHash of Nothing -> error $ T.unpack $ "isMonotonicCutExtension.monotonic: missing parent in cut. " <> encodeToText h - Just x -> x + Just x -> Parent x validBraiding = getAll $ ifoldMap (\cid -> All . validBraidingCid cid) (_getBlockHashRecord $ view blockAdjacentHashes h) validBraidingCid cid a - | Just b <- c ^? ixg cid = view blockHash b == a || view blockParent b == a + | Just b <- c ^? ixg cid = Parent (view blockHash b) == a || view blockParent b == a | view blockHeight h == genesisHeight v cid = a == genesisParentBlockHash v cid | otherwise = error $ T.unpack $ "isMonotonicCutExtension.validBraiding: missing adjacent parent on chain " <> toText cid <> " in cut. " <> encodeToText h diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 0b7bc1ffa3..8b02e2c6fd 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -105,6 +105,7 @@ import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Utils +import Chainweb.Parent -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes @@ -230,7 +231,7 @@ getCutExtension c cid = do -- | Try to get all adjacent hashes dependencies for the given graph. -- - newAdjHashes :: ChainGraph -> Maybe (HM.HashMap ChainId BlockHash) + newAdjHashes :: ChainGraph -> Maybe (HM.HashMap ChainId (Parent BlockHash)) newAdjHashes g = imapM (\xcid _ -> hashForChain xcid) $ HS.toMap -- converts to Map Foo () @@ -248,7 +249,7 @@ getCutExtension c cid = do <> sshow acid <> ".\n Cut: " <> sshow c - tryAdj :: BlockHeader -> Maybe BlockHash + tryAdj :: BlockHeader -> Maybe (Parent BlockHash) tryAdj b -- When the block is behind, we can move ahead @@ -258,7 +259,7 @@ getCutExtension c cid = do | view blockHeight b + 1 == parentHeight = Nothing -- chain is blocked -- If this is not a graph transition cut we can move ahead - | view blockHeight b == parentHeight = Just $! view blockHash b + | view blockHeight b == parentHeight = Just $! Parent $ view blockHash b -- The cut is invalid | view blockHeight b > targetHeight = error $ T.unpack @@ -376,10 +377,10 @@ getAdjacentParentHeaders hdb extension where c = _cutExtensionCut extension - select cid h = case c ^? ixg cid of - Just ch -> Parent <$> if view blockHash ch == h - then pure ch - else hdb (ChainValue cid h) + select cid (Parent h) = case c ^? ixg cid of + Just ch -> if view blockHash ch == h + then pure (Parent ch) + else Parent <$> hdb (ChainValue cid h) Nothing -> error $ T.unpack $ "Chainweb.Cut.Create.getAdjacentParentHeaders: inconsistent cut extension detected" @@ -410,7 +411,7 @@ workParent = to _workParent _workParentsAdjacentHashes :: WorkParents -> BlockHashRecord _workParentsAdjacentHashes = BlockHashRecord - . fmap (view (_Parent . blockHash)) + . fmap (fmap (view blockHash)) . _workAdjacentParents' workParentsAdjacentHashes :: Getter WorkParents BlockHashRecord diff --git a/src/Chainweb/Mempool/Consensus.hs b/src/Chainweb/Mempool/Consensus.hs deleted file mode 100644 index 4de8ec0104..0000000000 --- a/src/Chainweb/Mempool/Consensus.hs +++ /dev/null @@ -1,192 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - -module Chainweb.Mempool.Consensus -( chainwebTxsFromPd -, MempoolConsensus(..) -, mkMempoolConsensus -, processFork -, processFork' -, processForkCheckTTL -, ReintroducedTxsLog (..) -) where - ------------------------------------------------------------------------------- -import Control.DeepSeq -import Control.Exception -import Control.Lens (view) -import Control.Monad - -import Data.Aeson -import Data.Either -import Data.Foldable (toList) -import Data.Hashable -import Data.HashSet (HashSet) -import qualified Data.HashSet as HS -import Data.IORef -import Data.Vector (Vector) -import qualified Data.Vector as V - -import GHC.Generics - -import System.LogLevel - ------------------------------------------------------------------------------- -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB -import Chainweb.Mempool.InMem -import Chainweb.Mempool.Mempool -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Time -import qualified Chainweb.Pact.Transaction as Pact -import Chainweb.TreeDB -import Chainweb.Utils - -import Data.LogMessage (JsonLog(..), LogFunction) -import Data.Coerce (coerce) - ------------------------------------------------------------------------------- -data MempoolConsensus = MempoolConsensus - { mpcMempool :: !(MempoolBackend Pact.Transaction) - , mpcLastNewBlockParent :: !(IORef (Maybe BlockHeader)) - , mpcProcessFork - :: LogFunction -> BlockHeader -> IO (Vector Pact.Transaction, Vector Pact.Transaction) - } - -data ReintroducedTxsLog = ReintroducedTxsLog - { oldForkHeader :: ObjectEncoded BlockHeader - , newForkHeader :: ObjectEncoded BlockHeader - , numReintroduced :: Int } - deriving (Eq, Show, Generic) - deriving anyclass (ToJSON, NFData) - -newtype MempoolException = MempoolConsensusException String - -instance Show MempoolException where - show (MempoolConsensusException s) = - "Error with mempool's consensus processing: " ++ s - -instance Exception MempoolException - ------------------------------------------------------------------------------- -mkMempoolConsensus - :: CanReadablePayloadCas tbl - => MempoolBackend Pact.Transaction - -> BlockHeaderDb - -> Maybe (PayloadDb tbl) - -> IO MempoolConsensus -mkMempoolConsensus mempool blockHeaderDb payloadStore = do - lastParentRef <- newIORef Nothing :: IO (IORef (Maybe BlockHeader)) - - return MempoolConsensus - { mpcMempool = mempool - , mpcLastNewBlockParent = lastParentRef - , mpcProcessFork = processFork blockHeaderDb payloadStore lastParentRef - } - - ------------------------------------------------------------------------------- -processFork - :: CanReadablePayloadCas tbl - => BlockHeaderDb - -> Maybe (PayloadDb tbl) - -> IORef (Maybe BlockHeader) - -> LogFunction - -> BlockHeader - -> IO (Vector Pact.Transaction, Vector Pact.Transaction) -processFork blockHeaderDb payloadStore lastHeaderRef logFun newHeader = do - now <- getCurrentTimeIntegral - lastHeader <- readIORef lastHeaderRef - (a, b) <- processFork' logFun blockHeaderDb newHeader lastHeader - (payloadLookup payloadStore) - (processForkCheckTTL now) - return (coerce a, coerce b) - - ------------------------------------------------------------------------------- -processForkCheckTTL - :: Time Micros - -> Pact.HashableTransaction -> Bool -processForkCheckTTL now (Pact.HashableTransaction t) = - either (const False) (const True) $ - txTTLCheck pactTransactionConfig now t - - ------------------------------------------------------------------------------- --- called directly from some unit tests... -processFork' - :: (Eq x, Hashable x) - => LogFunction - -> BlockHeaderDb - -> BlockHeader - -> Maybe BlockHeader - -> (BlockHeader -> IO (HashSet x)) - -> (x -> Bool) - -> IO (V.Vector x, V.Vector x) -processFork' logFun db newHeader lastHeaderM plLookup flt = - maybe (return (V.empty, V.empty)) go lastHeaderM - where - go lastHeader = do - (_, oldBlocks, newBlocks) <- collectForkBlocks db lastHeader newHeader - oldTrans <- foldM toSet mempty oldBlocks - newTrans <- foldM toSet mempty newBlocks - - -- before re-introducing the transactions from the losing fork (aka - -- oldBlocks), filter out any transactions that have been included in - -- the winning fork (aka newBlocks): - let !results = V.fromList $ filter flt - $ HS.toList - $ oldTrans `HS.difference` newTrans - let !deletes = V.fromList $ HS.toList newTrans - - unless (V.null results) $ do - -- create data for the dashboard showing number or reintroduced - -- transactions: - let !reIntro = ReintroducedTxsLog - { oldForkHeader = ObjectEncoded lastHeader - , newForkHeader = ObjectEncoded newHeader - , numReintroduced = V.length results - } - logFun @(JsonLog ReintroducedTxsLog) Info $ JsonLog reIntro - return (results, deletes) - where - toSet !trans !header = HS.union trans <$!> plLookup header - - ------------------------------------------------------------------------------- -payloadLookup - :: CanReadablePayloadCas tbl - => Maybe (PayloadDb tbl) - -> BlockHeader - -> IO (HashSet Pact.HashableTransaction) -payloadLookup payloadStore bh = - case payloadStore of - Nothing -> return mempty - Just s -> do - pd <- lookupPayloadDataWithHeight s (Just (view blockHeight bh)) (view blockPayloadHash bh) - pd' <- maybe (throwIO $ PayloadNotFoundException (view blockPayloadHash bh)) pure pd - chainwebTxsFromPd pd' - ------------------------------------------------------------------------------- -chainwebTxsFromPd - :: PayloadData - -> IO (HashSet Pact.HashableTransaction) -chainwebTxsFromPd pd = do - let transSeq = view payloadDataTransactions pd - let bytes = _transactionBytes <$> transSeq - let eithers = toCWTransaction <$> bytes - -- Note: if any transactions fail to convert, the final validation hash will fail to match - -- the one computed during newBlock - let theRights = rights $ toList eithers - return $! HS.fromList $ coerce theRights - where - toCWTransaction = codecDecode Pact.payloadCodec diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 5ef1d7e839..34c79c1ff3 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -57,6 +57,10 @@ import Data.Traversable (for) import Data.Vector (Vector) import qualified Data.Vector as V import qualified Data.Vector.Algorithms.Tim as TimSort +import Numeric.AffineSpace +import Data.ByteString (ByteString) +import Data.Either (partitionEithers) +import Control.Lens import Prelude hiding (init, lookup, pred) @@ -72,15 +76,11 @@ import Chainweb.Mempool.CurrentTxs import Chainweb.Mempool.InMemTypes import Chainweb.Mempool.Mempool import Chainweb.Pact.Validations (defaultMaxTTLSeconds, defaultMaxCoinDecimalPlaces) +import Chainweb.Parent import Chainweb.Time import Chainweb.Utils import Chainweb.Version (ChainwebVersion) -import Numeric.AffineSpace -import Data.ByteString (ByteString) -import Data.Either (partitionEithers) -import Control.Lens -import Chainweb.BlockHeader import Pact.Core.Gas ------------------------------------------------------------------------------ diff --git a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs index 2436dd7d4c..a35145d1d7 100644 --- a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs +++ b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs @@ -14,29 +14,28 @@ module Chainweb.Mempool.InMem.ValidatingConfig ( validatingMempoolConfig ) where +import Control.Lens import Chainweb.ChainId import Chainweb.Mempool.InMemTypes import Chainweb.Mempool.Mempool -import Chainweb.Pact.Transaction qualified as Pact4 +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Validations import Chainweb.Utils import Chainweb.Version -import Chainweb.WebPactExecutionService -import Control.Concurrent import Data.These import Data.Vector qualified as V -import Pact.Types.ChainMeta -import Pact.Types.Command +import qualified Pact.Core.Command.Types as Pact +import qualified Pact.Core.ChainData as Pact validatingMempoolConfig :: ChainId -> ChainwebVersion -> GasLimit -> GasPrice - -> MVar PactExecutionService - -> InMemConfig Pact4.UnparsedTransaction -validatingMempoolConfig cid v gl gp mv = InMemConfig - { _inmemTxCfg = txcfg + -> (V.Vector Pact.Transaction -> IO (V.Vector (Maybe InsertError))) + -> InMemConfig Pact.Transaction +validatingMempoolConfig cid v gl gp preInsertCheck = InMemConfig + { _inmemTxCfg = pactTransactionConfig , _inmemTxBlockSizeLimit = gl , _inmemTxMinGasPrice = gp , _inmemMaxRecentItems = maxRecentLog @@ -45,10 +44,6 @@ validatingMempoolConfig cid v gl gp mv = InMemConfig , _inmemCurrentTxsSize = currentTxsSize } where - txcfg = pact4TransactionConfig - -- The mempool doesn't provide a chain context to the codec which means - -- that the latest version of the parser is used. - maxRecentLog = 2048 currentTxsSize = 1024 * 1024 -- ~16MB per mempool @@ -58,14 +53,13 @@ validatingMempoolConfig cid v gl gp mv = InMemConfig -- | Validation: Is this TX associated with the correct `ChainId`? -- - preInsertSingle :: Pact4.UnparsedTransaction -> Either InsertError Pact4.UnparsedTransaction + preInsertSingle :: Pact.Transaction -> Either InsertError Pact.Transaction preInsertSingle tx = do - let !pay = Pact4.payloadObj . _cmdPayload $ tx - pcid = _pmChainId $ _pMeta pay - sigs = _cmdSigs tx - ver = _pNetworkId pay - if | not $ assertParseChainId pcid -> Left $ InsertErrorOther "Unparsable ChainId" - | not $ assertChainId cid pcid -> Left InsertErrorMetadataMismatch + let !pay = view Pact.payloadObj . Pact._cmdPayload $ tx + pcid = Pact._pmChainId $ Pact._pMeta pay + sigs = Pact._cmdSigs tx + ver = Pact._pNetworkId pay + if | not $ assertChainId cid pcid -> Left InsertErrorMetadataMismatch | not $ assertSigSize sigs -> Left $ InsertErrorOther "Too many signatures" | not $ assertNetworkId v ver -> Left InsertErrorMetadataMismatch | otherwise -> Right tx @@ -80,14 +74,13 @@ validatingMempoolConfig cid v gl gp mv = InMemConfig -- is gossiped to us from a peer's mempool. -- preInsertBatch - :: V.Vector (T2 TransactionHash Pact4.UnparsedTransaction) + :: V.Vector (T2 TransactionHash Pact.Transaction) -> IO (V.Vector (Either (T2 TransactionHash InsertError) - (T2 TransactionHash Pact4.UnparsedTransaction))) + (T2 TransactionHash Pact.Transaction))) preInsertBatch txs | V.null txs = return V.empty | otherwise = do - pex <- readMVar mv - rs <- _pactPreInsertCheck pex cid (V.map ssnd txs) + rs <- preInsertCheck (V.map ssnd txs) pure $ alignWithV f rs txs where f (These r (T2 h t)) = case r of diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Mempool/Mempool.hs index 1d7826043f..e8411ed735 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Mempool/Mempool.hs @@ -133,6 +133,7 @@ import qualified Pact.JSON.Encode as J import Chainweb.BlockHash import Chainweb.BlockHeight +import Chainweb.Parent import Chainweb.Time (Micros(..), Time(..), TimeSpan(..)) import Chainweb.Time qualified as Time import Chainweb.Pact.Transaction qualified as Pact @@ -144,7 +145,6 @@ import Pact.Core.Hash qualified as Pact import Pact.Core.Gas import Pact.Core.ChainData import qualified Pact.Core.ChainData as Pact -import Chainweb.BlockHeader ------------------------------------------------------------------------------ data LookupResult t = Missing diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 92ba9b7b53..ae0a1843c5 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -107,6 +107,7 @@ import System.Random (randomRIO) import Control.Concurrent.Async import qualified Data.Vector as V import Data.Hashable +import Chainweb.Parent -- -------------------------------------------------------------------------- -- -- Utils @@ -370,17 +371,17 @@ onSolved :: SolvedWork -> WorkState -> Maybe WorkState -onSolved (SolvedWork hdr) (WorkSolved (Parent rh) _ ps) +onSolved (SolvedWork hdr) (WorkSolved rh _ ps) -- If we solved this header in this cut before we do not change the work -- state, even if the payload differs. -- TODO: this might be wrong. `hdr` is the newly made work header, but `rh` looks to be the parent header of that. - | view blockParent hdr == _rankedBlockHashHash rh + | view blockParent hdr == fmap _rankedBlockHashHash rh && view blockAdjacentHashes hdr /= _workParentsAdjacentHashes ps = Nothing -onSolved (SolvedWork hdr) (WorkReady (Parent rh) pld ps _) +onSolved (SolvedWork hdr) (WorkReady rh pld ps _) -- If work is currently ready for this header in this cut, we mark it solved. - | view blockParent hdr == _rankedBlockHashHash rh + | view blockParent hdr == fmap _rankedBlockHashHash rh && view blockAdjacentHashes hdr /= _workParentsAdjacentHashes ps = - Just $ WorkSolved (Parent rh) pld ps + Just $ WorkSolved rh pld ps -- otherwise do not change the state. onSolved _ _ = Nothing @@ -904,7 +905,7 @@ solve mr solved@(SolvedWork hdr) = cdb = _coordCutDb mr caches = _coordPayloadCache mr cache = caches HM.! cid - cacheKey = Parent $ RankedBlockHash (view blockHeight hdr - 1) (view blockParent hdr) + cacheKey = Parent $ RankedBlockHash (view blockHeight hdr - 1) (unwrapParent $ view blockParent hdr) lf :: LogFunction lf = logFunction $ _coordLogger mr diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index 330268a710..186b764956 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -9,6 +9,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeOperators #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Miner.Miners @@ -38,16 +39,16 @@ import Control.Monad import Crypto.Hash.Algorithms (Blake2s_256) -import qualified Data.ByteString.Short as BS +import Data.ByteString.Short qualified as BS import Data.HashMap.Strict (HashMap) -import qualified Data.HashMap.Strict as HashMap +import Data.HashMap.Strict qualified as HashMap import Numeric.Natural (Natural) import GHC.Stack -import qualified System.Random.MWC as MWC -import qualified System.Random.MWC.Distributions as MWC +import System.Random.MWC qualified as MWC +import System.Random.MWC.Distributions qualified as MWC -- internal modules @@ -60,12 +61,12 @@ import Chainweb.Difficulty import Chainweb.Graph import Chainweb.Logger import Chainweb.Mempool.Mempool -import qualified Chainweb.Mempool.Mempool as Mempool +import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Miner.Config (MinerCount(..)) import Chainweb.Miner.Coordinator import Chainweb.Miner.Core import Chainweb.RestAPI.Orphans () -import qualified Chainweb.Pact.Transaction as Pact4 +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version @@ -123,7 +124,7 @@ localTest lf v coord cdb gen miners = -- mempoolNoopMiner :: LogFunction - -> HashMap ChainId (MempoolBackend Pact4.UnparsedTransaction) + -> HashMap ChainId (MempoolBackend Pact.Transaction) -> IO () mempoolNoopMiner lf chainRes = runForever lf "Chainweb.Miner.Miners.mempoolNoopMiner" $ do diff --git a/src/Chainweb/Miner/PayloadCache.hs b/src/Chainweb/Miner/PayloadCache.hs index 9a8ba29fa6..d7e09ca8ab 100644 --- a/src/Chainweb/Miner/PayloadCache.hs +++ b/src/Chainweb/Miner/PayloadCache.hs @@ -70,6 +70,7 @@ import Data.Map.Strict qualified as M import Numeric.Natural import Chainweb.BlockHeight import Chainweb.BlockHeader +import Chainweb.Parent -- | A new payload cache for a chain. -- diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index a880fa3af5..26fcfed54d 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -19,6 +19,8 @@ {-# OPTIONS_GHC -Wno-orphans #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE EmptyCase #-} -- | The database operations that manipulate and read the Pact state. @@ -66,23 +68,20 @@ module Chainweb.Pact.Backend.ChainwebPactDb , blockHandlerDb , blockHandlerLogger , toTxLog - , toPactTxLog , domainTableName , convRowKey , commitBlockStateToDatabase , initSchema + , lookupBlockWithHeight + , lookupParentBlockHash + , lookupParentBlockRanked + , getPayloadsAfter + , getEarliestBlock + , getConsensusState + , setConsensusState + , throwOnDbError ) where -import Chainweb.BlockHash -import Chainweb.BlockHeight -import Chainweb.Logger -import Chainweb.Pact.Backend.InMemDb qualified as InMemDb -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Backend.Utils -import Chainweb.Utils (sshow, T2) -import Chainweb.Utils.Serialization (runPutS) -import Chainweb.Version -import Chainweb.Version.Guards (pact5Serialiser) import Control.Applicative import Control.Concurrent.MVar import Control.Exception.Safe @@ -113,21 +112,38 @@ import Data.Text qualified as T import Data.Text.Encoding qualified as T import Database.SQLite3.Direct qualified as SQ3 import GHC.Stack + +import Prelude hiding (concat, log) + import Pact.Core.Builtin qualified as Pact import Pact.Core.Command.Types (RequestKey (..)) import Pact.Core.Errors qualified as Pact import Pact.Core.Evaluate qualified as Pact import Pact.Core.Gas qualified as Pact import Pact.Core.Guards qualified as Pact -import Pact.Core.Hash +import Pact.Core.Hash hiding (hash) import Pact.Core.Info qualified as Pact import Pact.Core.Names qualified as Pact import Pact.Core.Persistence (throwDbOpErrorGasM) import Pact.Core.Persistence qualified as Pact import Pact.Core.Serialise qualified as Pact import Pact.Core.StableEncoding (encodeStable) -import Pact.Types.Persistence qualified as Pact4 -import Prelude hiding (concat, log) + +import Chainweb.BlockHash +import Chainweb.BlockHeader (encodeBlockPayloadHash, decodeBlockPayloadHash, BlockPayloadHash) +import Chainweb.BlockHeight +import Chainweb.Logger +import Chainweb.Pact.Backend.InMemDb qualified as InMemDb +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils +import Chainweb.Pact.SPV (pactSPV) +import Chainweb.Parent +import Chainweb.PayloadProvider (ConsensusState (..), SyncState (..), EvaluationCtx (_evaluationCtxPayload, _evaluationCtxParentHash), _evaluationCtxCurrentHeight) +import Chainweb.Utils (sshow) +import Chainweb.Utils.Serialization (runPutS, runGetEitherS) +import Chainweb.Version +import Chainweb.Version.Guards (pact5Serialiser) +import Chainweb.Ranked data InternalDbException = InternalDbException CallStack Text instance Show InternalDbException where show = displayException @@ -213,6 +229,12 @@ data BlockHandlerEnv logger = BlockHandlerEnv , _blockHandlerAtTip :: Bool } +instance HasChainId (BlockHandlerEnv logger) where + _chainId = _blockHandlerChainId + +instance HasChainwebVersion (BlockHandlerEnv logger) where + _chainwebVersion = _blockHandlerVersion + -- | The state used by database operations. -- Includes both the state re: the whole block, and the state re: a transaction in progress. data BlockState = BlockState @@ -306,7 +328,12 @@ chainwebPactBlockDb env = ChainwebPactDb , Pact._pdbRollbackTx = runOnBlockGassed env stateVar doRollback } - r <- kont pactDb + let headerOracle = HeaderOracle + { chain = _chainId env + , consult = throwOnDbError . lookupParentBlockHash (_blockHandlerDb env) + } + let spv = pactSPV headerOracle + r <- kont pactDb spv finalState <- readMVar stateVar -- Register a successful transaction in the pending data for the block let registerRequestKey = case maybeRequestKey of @@ -673,11 +700,8 @@ toTxLog version cid bh d key value = do Nothing -> internalDbError $ "toTxLog: Unexpected value, unable to deserialize log: " <> sshow value Just v -> return $! Pact.TxLog d (fromUtf8 key) v -toPactTxLog :: Pact.TxLog Pact.RowData -> Pact4.TxLog Pact.RowData -toPactTxLog (Pact.TxLog d k v) = Pact4.TxLog d k v - -commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockHeight -> BlockHandle -> IO () -commitBlockStateToDatabase db hsh bh blockHandle = throwOnDbError $ do +commitBlockStateToDatabase :: SQLiteEnv -> EvaluationCtx BlockPayloadHash -> BlockHandle -> IO () +commitBlockStateToDatabase db evalCtx blockHandle = throwOnDbError $ do let newTables = _pendingTableCreation $ _blockHandlePending blockHandle mapM_ (\tn -> createUserTable (toUtf8 tn)) newTables backendWriteUpdateBatch (_pendingWrites (_blockHandlePending blockHandle)) @@ -729,27 +753,28 @@ commitBlockStateToDatabase db hsh bh blockHandle = throwOnDbError $ do writeTable :: SQ3.Utf8 -> [[SType]] -> ExceptT SQ3.Error IO () writeTable table writes = when (not (null writes)) $ do execMulti db q writes - markTableMutation table bh + markTableMutation table where q = "INSERT OR REPLACE INTO " <> tbl table <> "(rowkey,txid,rowdata) VALUES(?,?,?)" -- Mark the table as being mutated during this block, so that we know -- to delete from it if we rewind past this block. - markTableMutation tablename blockheight = do - exec' db mutq [SText tablename, SInt (fromIntegral blockheight)] + markTableMutation tablename = do + exec' db mutq [SText tablename, SInt (fromIntegral $ _evaluationCtxCurrentHeight evalCtx)] where mutq = "INSERT OR IGNORE INTO VersionedTableMutation VALUES (?,?);" -- | Record a block as being in the history of the checkpointer. - blockHistoryInsert :: Pact4.TxId -> ExceptT SQ3.Error IO () - blockHistoryInsert t = + blockHistoryInsert :: Pact.TxId -> ExceptT SQ3.Error IO () + blockHistoryInsert (Pact.TxId t) = exec' db stmt - [ SInt (fromIntegral bh) - , SBlob (runPutS (encodeBlockHash hsh)) + [ SInt (fromIntegral $ _evaluationCtxCurrentHeight evalCtx) + , SBlob (runPutS $ encodeBlockHash $ unwrapParent $ _evaluationCtxParentHash evalCtx) + , SBlob (runPutS $ encodeBlockPayloadHash $ _evaluationCtxPayload evalCtx) , SInt (fromIntegral t) ] where - stmt = "INSERT INTO BlockHistory ('blockheight','hash','endingtxid') VALUES (?,?,?);" + stmt = "INSERT INTO BlockHistory ('blockheight', 'parenthash', 'payloadhash', 'endingtxid') VALUES (?,?,?,?);" createUserTable :: SQ3.Utf8 -> ExceptT SQ3.Error IO () createUserTable tablename = do @@ -762,7 +787,7 @@ commitBlockStateToDatabase db hsh bh blockHandle = throwOnDbError $ do exec' db insertstmt insertargs where insertstmt = "INSERT OR IGNORE INTO VersionedTableCreation VALUES (?,?)" - insertargs = [SText tablename, SInt (fromIntegral bh)] + insertargs = [SText tablename, SInt (fromIntegral $ _evaluationCtxCurrentHeight evalCtx)] -- | Commit the index of pending successful transactions to the database indexPendingPactTransactions :: ExceptT SQ3.Error IO () @@ -771,7 +796,7 @@ commitBlockStateToDatabase db hsh bh blockHandle = throwOnDbError $ do dbIndexTransactions txs where - toRow b = [SBlob b, SInt (fromIntegral bh)] + toRow b = [SBlob b, SInt (fromIntegral $ _evaluationCtxCurrentHeight evalCtx)] dbIndexTransactions txs = do let rows = map toRow $ toList txs let q = "INSERT INTO TransactionIndex (txhash, blockheight) VALUES (?, ?)" @@ -792,12 +817,53 @@ createVersionedTable tablename db = do indexcreationstmt = "CREATE INDEX IF NOT EXISTS " <> tbl ixName <> " ON " <> tbl tablename <> "(txid DESC);" +setConsensusState :: SQ3.Database -> ConsensusState -> ExceptT SQ3.Error IO () +setConsensusState db cs = do + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, type) VALUES\ + \(?, ?, ?, 'final'), \ + \(?, ?, ?, 'latest'), \ + \(?, ?, ?, 'safe') \ + \ON CONFLICT REPLACE;" + $ concatMap toRow + [ _consensusStateFinal cs + , _consensusStateSafe cs + , _consensusStateLatest cs + ] + where + toRow SyncState {..} = + [ SInt $ fromIntegral @BlockHeight @Int64 _syncStateHeight + , SBlob $ runPutS (encodeBlockHash _syncStateBlockHash) + , SBlob $ runPutS (encodeBlockPayloadHash _syncStateBlockPayloadHash) + ] + +getConsensusState :: SQ3.Database -> ExceptT SQ3.Error IO ConsensusState +getConsensusState db = do + qry db "SELECT FROM ConsensusState (blockheight, hash, payloadhash, type) order by type" + [] [RInt, RBlob, RBlob, RText] >>= \case + [final, latest, safe] -> return ConsensusState + { _consensusStateFinal = readRow "final" final + , _consensusStateLatest = readRow "latest" latest + , _consensusStateSafe = readRow "safe" safe + } + inv -> error $ "invalid contents of the ConsensusState table: " <> sshow inv + where + readRow expectedType [SInt height, SBlob hash, SBlob payloadHash, SText type'] + | expectedType == type' = SyncState + { _syncStateHeight = fromIntegral @Int64 @BlockHeight height + , _syncStateBlockHash = either error id $ runGetEitherS decodeBlockHash hash + , _syncStateBlockPayloadHash = either error id $ runGetEitherS decodeBlockPayloadHash payloadHash + } + | otherwise = error $ "wrong type; expected " <> sshow expectedType <> " but got " <> sshow type' + readRow expectedType invalidRow + = error $ "invalid row: expected " <> sshow expectedType <> " but got row " <> sshow invalidRow -- | Create all tables that exist pre-genesis -- TODO: migrate this logic to the checkpointer itself? initSchema :: SQLiteEnv -> IO () initSchema sql = - withSavepoint sql DbTransaction $ throwOnDbError $ do + withSavepoint sql InitSchemaSavePoint $ throwOnDbError $ do + createConsensusStateTable createBlockHistoryTable createTableCreationTable createTableMutationTable @@ -807,45 +873,60 @@ initSchema sql = create (toUtf8 $ Pact.renderDomain Pact.DNamespaces) create (toUtf8 $ Pact.renderDomain Pact.DDefPacts) create (toUtf8 $ Pact.renderDomain Pact.DModuleSource) - where + where create tablename = do - createVersionedTable tablename sql + createVersionedTable tablename sql + + createConsensusStateTable :: ExceptT SQ3.Error IO () + createConsensusStateTable = do + exec_ sql + "CREATE TABLE IF NOT EXISTS ConsensusState \ + \(blockheight UNSIGNED BIGINT NOT NULL, \ + \ hash BLOB NOT NULL, \ + \ payloadhash BLOB NOT NULL, \ + \ type VARCHAR NOT NULL, \ + \ CONSTRAINT typeConstraint UNIQUE (type));" createBlockHistoryTable :: ExceptT SQ3.Error IO () - createBlockHistoryTable = - exec_ sql - "CREATE TABLE IF NOT EXISTS BlockHistory \ - \(blockheight UNSIGNED BIGINT NOT NULL,\ - \ hash BLOB NOT NULL,\ - \ endingtxid UNSIGNED BIGINT NOT NULL, \ - \ CONSTRAINT blockHashConstraint UNIQUE (blockheight));" + createBlockHistoryTable = do + exec_ sql + "CREATE TABLE IF NOT EXISTS BlockHistory \ + \(blockheight UNSIGNED BIGINT NOT NULL, \ + \ endingtxid UNSIGNED BIGINT NOT NULL, \ + \ parenthash BLOB NOT NULL, \ + \ payloadhash BLOB NOT NULL, \ + \ CONSTRAINT blockHeightConstraint UNIQUE (blockheight), \ + \ CONSTRAINT parentHashConstraint UNIQUE (parenthash), \ + \ CONSTRAINT payloadHashConstraint UNIQUE (payloadHash));" + -- TODO PP: payload hash should really be NOT NULL but there may exist old databases without it. + -- making a block hash index at block height 5,658,430 on us-e3 took around 2.5 minutes createTableCreationTable :: ExceptT SQ3.Error IO () createTableCreationTable = - exec_ sql - "CREATE TABLE IF NOT EXISTS VersionedTableCreation\ - \(tablename TEXT NOT NULL\ - \, createBlockheight UNSIGNED BIGINT NOT NULL\ - \, CONSTRAINT creation_unique UNIQUE(createBlockheight, tablename));" + exec_ sql + "CREATE TABLE IF NOT EXISTS VersionedTableCreation\ + \(tablename TEXT NOT NULL\ + \, createBlockheight UNSIGNED BIGINT NOT NULL\ + \, CONSTRAINT creation_unique UNIQUE(createBlockheight, tablename));" createTableMutationTable :: ExceptT SQ3.Error IO () createTableMutationTable = - exec_ sql - "CREATE TABLE IF NOT EXISTS VersionedTableMutation\ - \(tablename TEXT NOT NULL\ - \, blockheight UNSIGNED BIGINT NOT NULL\ - \, CONSTRAINT mutation_unique UNIQUE(blockheight, tablename));" + exec_ sql + "CREATE TABLE IF NOT EXISTS VersionedTableMutation\ + \(tablename TEXT NOT NULL\ + \, blockheight UNSIGNED BIGINT NOT NULL\ + \, CONSTRAINT mutation_unique UNIQUE(blockheight, tablename));" createTransactionIndexTable :: ExceptT SQ3.Error IO () createTransactionIndexTable = do - exec_ sql - "CREATE TABLE IF NOT EXISTS TransactionIndex \ - \ (txhash BLOB NOT NULL, \ - \ blockheight UNSIGNED BIGINT NOT NULL, \ - \ CONSTRAINT transactionIndexConstraint UNIQUE(txhash));" - exec_ sql - "CREATE INDEX IF NOT EXISTS \ - \ transactionIndexByBH ON TransactionIndex(blockheight)"; + exec_ sql + "CREATE TABLE IF NOT EXISTS TransactionIndex \ + \ (txhash BLOB NOT NULL, \ + \ blockheight UNSIGNED BIGINT NOT NULL, \ + \ CONSTRAINT transactionIndexConstraint UNIQUE(txhash));" + exec_ sql + "CREATE INDEX IF NOT EXISTS \ + \ transactionIndexByBH ON TransactionIndex(blockheight)"; getSerialiser :: BlockHandler logger (Pact.PactSerialise Pact.CoreBuiltin Pact.LineInfo) getSerialiser = do @@ -853,3 +934,57 @@ getSerialiser = do cid <- view blockHandlerChainId blockHeight <- view blockHandlerBlockHeight return $ pact5Serialiser version cid blockHeight + +getPayloadsAfter :: HasCallStack => SQLiteEnv -> Parent BlockHeight -> ExceptT SQ3.Error IO [Ranked BlockPayloadHash] +getPayloadsAfter db parentHeight = do + qry db "SELECT blockheight, payloadhash FROM BlockHistory WHERE blockheight > ?" + [SInt (fromIntegral @BlockHeight @Int64 (unwrapParent parentHeight))] + [RBlob] >>= traverse + \case + [SInt bh, SBlob bhash] -> + return $! Ranked (fromIntegral @Int64 @BlockHeight bh) $ either error id $ runGetEitherS decodeBlockPayloadHash bhash + _ -> error "incorrect column type" + +-- | Get the checkpointer's idea of the earliest block. The block height +-- is the height of the block of the block hash. +getEarliestBlock :: HasCallStack => SQLiteEnv -> ExceptT SQ3.Error IO (Maybe RankedBlockHash) +getEarliestBlock db = do + r <- qry db qtext [] [RInt, RBlob] >>= mapM go + case r of + [] -> return Nothing + (!o:_) -> return (Just o) + where + qtext = "SELECT blockheight, hash FROM BlockHistory ORDER BY blockheight ASC LIMIT 1" + + go [SInt hgt, SBlob blob] = + let hash = either error id $ runGetEitherS decodeBlockHash blob + in return (RankedBlockHash (fromIntegral hgt) hash) + go _ = fail "Chainweb.Pact.Backend.RelationalCheckpointer.doGetEarliest: impossible. This is a bug in chainweb-node." + +lookupBlockWithHeight :: SQ3.Database -> BlockHeight -> ExceptT SQ3.Error IO (Maybe (Ranked (Parent BlockHash))) +lookupBlockWithHeight db bheight = do + qry db qtext [SInt $ fromIntegral bheight] [RInt] >>= \case + [[SBlob parentHash]] -> return $! Just $! + Ranked bheight (either error Parent $ runGetEitherS decodeBlockHash parentHash) + [_] -> error "lookupBlock: output type mismatch" + _ -> error "Expected single-row result" + where + qtext = "SELECT parenthash FROM BlockHistory WHERE blockheight = ?;" + +lookupParentBlockHash :: SQ3.Database -> Parent BlockHash -> ExceptT SQ3.Error IO Bool +lookupParentBlockHash db (Parent parentHash) = do + qry db qtext [SBlob (runPutS (encodeBlockHash parentHash))] [RInt] >>= \case + [[SInt n]] -> return $! n == 1 + [_] -> error "lookupBlock: output type mismatch" + _ -> error "Expected single-row result" + where + qtext = "SELECT COUNT(*) FROM BlockHistory WHERE parenthash = ?;" + +lookupParentBlockRanked :: SQ3.Database -> Ranked (Parent BlockHash) -> ExceptT SQ3.Error IO Bool +lookupParentBlockRanked db (Ranked bheight (Parent parentHash)) = do + qry db qtext [SInt $ fromIntegral bheight, SBlob (runPutS (encodeBlockHash parentHash))] [RInt] >>= \case + [[SInt n]] -> return $! n == 1 + [_] -> error "lookupBlock: output type mismatch" + _ -> error "Expected single-row result" + where + qtext = "SELECT COUNT(*) FROM BlockHistory WHERE blockheight = ? AND parenthash = ?;" diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs index aa8fd1baee..0279d2a4b5 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs @@ -52,7 +52,7 @@ module Chainweb.Pact.Backend.PactState.GrandHash.Import ) where -import Chainweb.BlockHeader (ParentHeader(..), blockHash) +import Chainweb.BlockHeader (blockHash) import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.ChainId (ChainId, chainIdToText) import Chainweb.Logger (Logger, logFunctionText) @@ -62,7 +62,7 @@ import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..)) import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot.Mainnet qualified as MainnetSnapshots import Chainweb.Pact.Backend.PactState.GrandHash.Utils (resolveLatestCutHeaders, resolveCutHeadersAtHeight, computeGrandHashesAt, exitLog, withConnections, chainwebDbFilePath, rocksParser, cwvParser) import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Types +import Chainweb.Parent import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) import Chainweb.Utils (sshow) import Chainweb.Version (ChainwebVersion(..)) @@ -83,7 +83,10 @@ import Patience.Map qualified as P import System.Directory (copyFile, createDirectoryIfMissing) import System.Environment (setEnv) import System.LogLevel (LogLevel(..)) -import qualified Chainweb.Pact.PactService.Checkpointer.Internal as Checkpointer.Internal +import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer +import Chainweb.Pact.Types (BlockCtx(..)) +import Chainweb.PayloadProvider +import Chainweb.Miner.Pact (noMiner) -- | Verifies that the hashes and headers match @grands@. -- @@ -186,8 +189,20 @@ pactDropPostVerified logger v srcDir tgtDir snapshotBlockHeight snapshotChainHas let logger' = addChainIdLabel cid logger logFunctionText logger' Info $ "Dropping anything post verified state (BlockHeight " <> sshow snapshotBlockHeight <> ")" - Checkpointer.Internal.withCheckpointerResources logger sqliteEnv DoNotPersistIntraBlockWrites v cid $ \cp -> do - Checkpointer.Internal.rewindTo cp (Just $ ParentHeader $ blockHeader $ snapshotChainHashes ^?! ix cid) + Checkpointer.withCheckpointerResources logger sqliteEnv DoNotPersistIntraBlockWrites v cid $ \cp -> do + let parent = blockHeaderToEvaluationCtx $ Parent $ blockHeader $ snapshotChainHashes ^?! ix cid + let parentBlockCtx = BlockCtx + { _bctxParentCreationTime = _evaluationCtxParentCreationTime parent + , _bctxParentHash = _evaluationCtxParentHash parent + , _bctxParentHeight = _evaluationCtxParentHeight parent + , _bctxChainId = cid + , _bctxChainwebVersion = v + , _bctxMinerReward = _evaluationCtxMinerReward parent + } + + -- Checkpointer.rewindTo cp v cid parentBlockCtx + -- TODO: PP + undefined data PactImportConfig = PactImportConfig { sourcePactDir :: FilePath diff --git a/src/Chainweb/Pact/Backend/Types.hs b/src/Chainweb/Pact/Backend/Types.hs index 5bec23e14d..f41032c92e 100644 --- a/src/Chainweb/Pact/Backend/Types.hs +++ b/src/Chainweb/Pact/Backend/Types.hs @@ -18,11 +18,11 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE TypeFamilyDependencies #-} module Chainweb.Pact.Backend.Types - ( Checkpointer(..) - , SQLiteEnv + ( SQLiteEnv , IntraBlockPersistence(..) , BlockHandle(..) , blockHandleTxId @@ -39,7 +39,7 @@ module Chainweb.Pact.Backend.Types , _Historical , _NoHistory , ChainwebPactDb(..) - , RunnableBlock(..) + , HeaderOracle(..) ) where import Control.Lens @@ -53,11 +53,8 @@ import Data.HashSet (HashSet) import Control.DeepSeq (NFData) import GHC.Generics -import qualified Chainweb.Pact.Backend.InMemDb as InMemDb - import qualified Pact.Types.Persistence as Pact4 import qualified Pact.Types.Names as Pact4 -import Chainweb.BlockHeader import Chainweb.BlockHash import Pact.Core.Command.Types import qualified Pact.Core.Persistence as Pact @@ -65,9 +62,22 @@ import qualified Pact.Core.Builtin as Pact import qualified Pact.Core.Evaluate as Pact import Data.Vector (Vector) import Data.HashMap.Strict (HashMap) +import qualified Pact.Core.SPV as Pact + import Chainweb.BlockHeight +import qualified Chainweb.Pact.Backend.InMemDb as InMemDb +import Chainweb.Parent import Chainweb.Utils +data HeaderOracle = HeaderOracle + -- this hash must always have a child + { consult :: !(Parent BlockHash -> IO Bool) + , chain :: !ChainId + } + +instance HasChainId HeaderOracle where + _chainId oracle = oracle.chain + -- | Whether we write rows to the database that were already overwritten -- in the same block. data IntraBlockPersistence = PersistIntraBlockWrites | DoNotPersistIntraBlockWrites @@ -79,7 +89,7 @@ data ChainwebPactDb = ChainwebPactDb :: forall a . BlockHandle -> Maybe RequestKey - -> (Pact.PactDb Pact.CoreBuiltin Pact.Info -> IO a) + -> (Pact.PactDb Pact.CoreBuiltin Pact.Info -> Pact.SPVSupport -> IO a) -> IO (a, BlockHandle) -- ^ Give this function a BlockHandle representing the state of a pending -- block and it will pass you a PactDb which contains the Pact state as of @@ -92,21 +102,6 @@ data ChainwebPactDb = ChainwebPactDb -- ^ Used to implement transaction polling. } -newtype RunnableBlock logger a = RunnableBlock - ( ChainwebPactDb - -> Maybe (Parent RankedBlockHash) - -> BlockHandle -> IO ((a, RankedBlockHash), BlockHandle) - ) - -data Checkpointer logger - = Checkpointer - { cpLogger :: logger - , cpCwVersion :: ChainwebVersion - , cpChainId :: ChainId - , cpSql :: SQLiteEnv - , cpIntraBlockPersistence :: IntraBlockPersistence - } - type SQLiteEnv = Database -- | While a block is being run, mutations to the pact database are held @@ -165,12 +160,12 @@ emptySQLitePendingData :: SQLitePendingData emptySQLitePendingData = SQLitePendingData mempty InMemDb.empty mempty mempty data BlockHandle = BlockHandle - { _blockHandleTxId :: !Pact4.TxId + { _blockHandleTxId :: !Pact.TxId , _blockHandlePending :: !SQLitePendingData } deriving (Eq, Show) -emptyBlockHandle :: Pact4.TxId -> BlockHandle +emptyBlockHandle :: Pact.TxId -> BlockHandle emptyBlockHandle txid = BlockHandle txid emptySQLitePendingData -- | The result of a historical lookup which might fail to even find the diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 2fdd624149..69d5508786 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -82,7 +82,6 @@ import System.LogLevel -- pact -import qualified Pact.Types.Persistence as Pact4 import qualified Pact.Types.SQLite as Pact4 import Pact.Types.Util (AsString(..)) @@ -95,7 +94,9 @@ import Chainweb.Pact.Backend.SQLite.DirectV2 import Chainweb.Version import Chainweb.Utils import Chainweb.BlockHash +import Chainweb.BlockHeader import Chainweb.BlockHeight +import Chainweb.Parent import Database.SQLite3.Direct hiding (open2) import GHC.Stack import qualified Data.ByteString.Short as SB @@ -108,7 +109,7 @@ import Chainweb.Pact.Backend.Types import Data.HashSet (HashSet) import qualified Data.HashSet as HashSet import Data.Text (Text) -import Chainweb.BlockHeader +import qualified Pact.Core.Persistence as Pact -- -------------------------------------------------------------------------- -- -- SQ3.Utf8 Encodings @@ -130,22 +131,24 @@ asStringUtf8 = toUtf8 . asString -- withSavepoint - :: SQLiteEnv + :: (MonadMask m, MonadIO m) + => SQLiteEnv -> SavepointName - -> IO a - -> IO a + -> m a + -> m a withSavepoint db name action = mask $ \resetMask -> do - beginSavepoint db name + liftIO $ beginSavepoint db name go resetMask `catches` handlers where go resetMask = do - r <- resetMask action `onException` abortSavepoint db name + r <- resetMask action `onException` liftIO (abortSavepoint db name) liftIO $ commitSavepoint db name liftIO $ evaluate r throwErr s = error $ "withSavepoint (" <> show name <> "): " <> s - handlers = [ Handler $ \(e :: SomeAsyncException) -> throwM e - , Handler $ \(e :: SomeException) -> throwErr ("non-pact exception: " <> sshow e) - ] + handlers = + [ Handler $ \(e :: SomeAsyncException) -> throwM e + , Handler $ \(e :: SomeException) -> throwErr ("non-pact exception: " <> sshow e) + ] beginSavepoint :: SQLiteEnv -> SavepointName -> IO () beginSavepoint db name = @@ -179,21 +182,33 @@ abortSavepoint db name = do rollbackSavepoint db name commitSavepoint db name -data SavepointName = BatchSavepoint | DbTransaction | PreBlock - deriving (Eq, Ord, Enum, Bounded) +data SavepointName + = ReadFromSavepoint + | ReadFromNSavepoint + | RestoreAndSaveSavePoint + | RewindSavePoint + | InitSchemaSavePoint + | ValidateBlockSavePoint + deriving (Eq, Ord, Enum, Bounded) instance Show SavepointName where show = T.unpack . toText instance HasTextRepresentation SavepointName where - toText BatchSavepoint = "batch" - toText DbTransaction = "db-transaction" - toText PreBlock = "preblock" + toText ReadFromSavepoint = "read-from" + toText ReadFromNSavepoint = "read-from-n" + toText RestoreAndSaveSavePoint = "restore-and-save" + toText RewindSavePoint = "rewind" + toText InitSchemaSavePoint = "init-schema" + toText ValidateBlockSavePoint = "validate-block" {-# INLINE toText #-} - fromText "batch" = pure BatchSavepoint - fromText "db-transaction" = pure DbTransaction - fromText "preblock" = pure PreBlock + fromText "read-from" = pure ReadFromSavepoint + fromText "read-from-n" = pure ReadFromNSavepoint + fromText "restore-and-save" = pure RestoreAndSaveSavePoint + fromText "rewind" = pure RewindSavePoint + fromText "init-schema" = pure InitSchemaSavePoint + fromText "validate-block" = pure ValidateBlockSavePoint fromText t = throwM $ TextFormatException $ "failed to decode SavepointName " <> t <> ". Valid names are " <> T.intercalate ", " (toText @SavepointName <$> [minBound .. maxBound]) @@ -358,12 +373,14 @@ doLookupSuccessful db curHeight hashes = do return $! T3 txhash' (fromIntegral blockheight) blockhash' go _ = fail "impossible" -getEndTxId :: Text -> SQLiteEnv -> Maybe (Parent RankedBlockHash) -> IO (Historical Pact4.TxId) -getEndTxId msg sql pc = case pc of - Nothing -> return (Historical 0) - Just ph -> getEndTxId' msg sql ph +getEndTxId :: ChainwebVersion -> ChainId -> Text -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) +getEndTxId v cid msg sql pc + | isGenesisBlockHeader' v cid (_rankedBlockHashHash <$> pc) = + return (Historical (Pact.TxId 0)) + | otherwise = + getEndTxId' msg sql pc -getEndTxId' :: Text -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact4.TxId) +getEndTxId' :: Text -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) getEndTxId' msg sql (Parent rbh) = do r <- Pact4.qry sql "SELECT endingtxid FROM BlockHistory WHERE blockheight = ? and hash = ?;" @@ -372,7 +389,7 @@ getEndTxId' msg sql (Parent rbh) = do ] [Pact4.RInt] case r of - [[Pact4.SInt tid]] -> return $ Historical (Pact4.TxId (fromIntegral tid)) + [[Pact4.SInt tid]] -> return $ Historical (Pact.TxId (fromIntegral tid)) [] -> return NoHistory _ -> error $ T.unpack msg <> ".getEndTxId: expected single-row int result, got " <> sshow r @@ -380,22 +397,25 @@ getEndTxId' msg sql (Parent rbh) = do -- | Delete any state from the database newer than the input parent header. -- Returns the ending txid of the input parent header. rewindDbTo - :: SQLiteEnv - -> Maybe (Parent RankedBlockHash) - -> IO Pact4.TxId -rewindDbTo db Nothing = do - rewindDbToGenesis db - return 0 -rewindDbTo db mh@(Just ph) = do - !historicalEndingTxId <- getEndTxId "rewindDbToBlock" db mh + :: ChainwebVersion + -> ChainId + -> SQLiteEnv + -> RankedBlockHash + -> IO Pact.TxId +rewindDbTo v cid db pc + | isGenesisBlockHeader' v cid (Parent $ _rankedBlockHashHash pc) = do + rewindDbToGenesis db + return (Pact.TxId 0) + | otherwise = do + !historicalEndingTxId <- getEndTxId v cid "rewindDbToBlock" db (Parent pc) endingTxId <- case historicalEndingTxId of NoHistory -> error $ "rewindDbTo.getEndTxId: not in db: " - <> sshow ph + <> sshow pc Historical endingTxId -> return endingTxId - rewindDbToBlock db (_rankedBlockHashHeight <$> ph) endingTxId + rewindDbToBlock db (_rankedBlockHashHeight <$> Parent pc) endingTxId return endingTxId -- rewind before genesis, delete all user tables and all rows in all tables @@ -422,7 +442,7 @@ rewindDbToGenesis db = do rewindDbToBlock :: Database -> Parent BlockHeight - -> Pact4.TxId + -> Pact.TxId -> IO () rewindDbToBlock db (Parent bh) endingTxId = do tableMaintenanceRowsVersionedSystemTables @@ -458,8 +478,9 @@ rewindDbToBlock db (Parent bh) endingTxId = do vacuumTablesAtRewind droppedtbls = do let processMutatedTables ms = fmap HashSet.fromList . forM ms $ \case [Pact4.SText (Utf8 tn)] -> return tn - _ -> error "rewindBlock: vacuumTablesAtRewind: Couldn't resolve the name \ - \of the table to possibly vacuum." + _ -> error + "rewindBlock: vacuumTablesAtRewind: Couldn't resolve the name \ + \of the table to possibly vacuum." mutatedTables <- Pact4.qry db "SELECT DISTINCT tablename FROM VersionedTableMutation WHERE blockheight > ?;" [Pact4.SInt (fromIntegral bh)] @@ -468,7 +489,7 @@ rewindDbToBlock db (Parent bh) endingTxId = do let toVacuumTblNames = HashSet.difference mutatedTables droppedtbls forM_ toVacuumTblNames $ \tblname -> Pact4.exec' db ("DELETE FROM " <> tbl (Utf8 tblname) <> " WHERE txid >= ?") - [Pact4.SInt $! fromIntegral endingTxId] + [Pact4.SInt $! fromIntegral $ Pact._txId endingTxId] Pact4.exec' db "DELETE FROM VersionedTableMutation WHERE blockheight > ?;" [Pact4.SInt (fromIntegral bh)] @@ -480,7 +501,7 @@ rewindDbToBlock db (Parent bh) endingTxId = do Pact4.exec' db "DELETE FROM [SYS:Pacts] WHERE txid >= ?" tx Pact4.exec' db "DELETE FROM [SYS:ModuleSources] WHERE txid >= ?" tx where - tx = [Pact4.SInt $! fromIntegral endingTxId] + tx = [Pact4.SInt $! fromIntegral $ Pact._txId endingTxId] -- | Delete all future transactions from the index clearTxIndex :: IO () diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 537b634dc5..99e34433ae 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -15,6 +15,7 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE TypeAbstractions #-} -- | -- Module: Chainweb.Pact.PactService @@ -27,19 +28,19 @@ -- module Chainweb.Pact.PactService ( initialPayloadState - , execNewBlock - , execContinueBlock + -- , execNewBlock + -- , execContinueBlock , execValidateBlock - , execTransactions - , execLocal + -- , execTransactions + -- , execLocal , execLookupPactTxs - , execPreInsertCheckReq - , execBlockTxHistory - , execHistoricalLookup + -- , execPreInsertCheckReq + -- , execBlockTxHistory + -- , execHistoricalLookup , execReadOnlyReplay - , execSyncToBlock + -- , execSyncToBlock , withPactService - , execNewGenesisBlock + -- , execNewGenesisBlock ) where import Control.Concurrent hiding (throwTo) @@ -97,12 +98,13 @@ import Chainweb.Miner.Pact import Chainweb.Pact.PactService.ExecBlock import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact import Chainweb.Pact.Types -import Chainweb.Pact.SPV qualified as Pact +-- import Chainweb.Pact.SPV qualified as Pact +import Chainweb.Parent import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time import qualified Chainweb.Pact.Transaction as Pact -import Chainweb.TreeDB +import Chainweb.TreeDB hiding (rank) import Chainweb.Utils hiding (check) import Chainweb.Version import Chainweb.Version.Guards @@ -131,7 +133,6 @@ import qualified Chainweb.Pact.Validations as Pact import qualified Pact.Core.Errors as Pact import Chainweb.Pact.Backend.Types import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer -import Chainweb.Pact.PactService.Checkpointer (SomeBlockM(..)) import qualified Pact.Core.StableEncoding as Pact import Control.Monad.Cont (evalContT) import qualified Data.List.NonEmpty as NonEmpty @@ -142,87 +143,93 @@ import Chainweb.Storage.Table import qualified Chainweb.Storage.Table.Map as MapTable import Chainweb.PayloadProvider.P2P import P2P.TaskQueue (Priority(..)) - +import qualified Network.HTTP.Client as HTTP +import qualified Chainweb.PayloadProvider.P2P.RestAPI.Client as Rest +import Chainweb.MinerReward +import qualified Data.List.NonEmpty as NEL +import Chainweb.Pact.Backend.Utils (withSavepoint, SavepointName (..)) +import qualified Data.DList as DList +import Chainweb.Ranked +import Chainweb.Pact.Types (blockCtxOfEvaluationCtx) +import qualified Chainweb.Pact.Backend.ChainwebPactDb as ChainwebPactDb withPactService :: (Logger logger, CanReadablePayloadCas tbl) - => ChainwebVersion + => HTTP.Manager + -> ChainwebVersion -> ChainId + -> MemPoolAccess -> logger -> Maybe (Counter "txFailures") - -> BlockHeaderDb -> PayloadDb tbl -> SQLiteEnv -> PactServiceConfig -> PactServiceM logger tbl a - -> IO (T2 a PactServiceState) -withPactService ver cid chainwebLogger txFailuresCounter bhDb pdb sqlenv config act = do - Checkpointer.withCheckpointerResources checkpointerLogger (_pactModuleCacheLimit config) sqlenv (_pactPersistIntraBlockWrites config) ver cid $ \checkpointer -> do - candidatePdb <- MapTable.emptyTable - let !pse = PactServiceEnv - { _psMempoolAccess = Nothing - , _psCheckpointer = checkpointer - , _psPdb = pdb - , _psCandidatePdb = candidatePdb - , _psBlockHeaderDb = bhDb - , _psReorgLimit = _pactReorgLimit config - , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout config - , _psOnFatalError = defaultOnFatalError (logFunctionText chainwebLogger) - , _psVersion = ver - , _psAllowReadsInLocal = _pactAllowReadsInLocal config - , _psLogger = pactServiceLogger - , _psGasLogger = gasLogger <$ guard (_pactLogGas config) - , _psBlockGasLimit = _pactNewBlockGasLimit config - , _psEnableLocalTimeout = _pactEnableLocalTimeout config - , _psTxFailuresCounter = txFailuresCounter - , _psTxTimeLimit = _pactTxTimeLimit config - , _psMiner = _pactMiner config - } - !pst = PactServiceState mempty - - runPactServiceM pst pse $ do - when (_pactFullHistoryRequired config) $ do - mEarliestBlock <- Checkpointer.getEarliestBlock - case mEarliestBlock of - Nothing -> do - pure () - Just (earliestBlockHeight, _) -> do - let gHeight = genesisHeight ver cid - when (gHeight /= earliestBlockHeight) $ do - let msg = J.object - [ "details" J..= J.object - [ "earliest-block-height" J..= J.number (fromIntegral earliestBlockHeight) - , "genesis-height" J..= J.number (fromIntegral gHeight) - ] - , "message" J..= J.text "Your node has been configured\ - \ to require the full Pact history; however, the full\ - \ history is not available. Perhaps you have compacted\ - \ your Pact state?" - ] - logError_ chainwebLogger (J.encodeText msg) - throwM FullHistoryRequired - { _earliestBlockHeight = earliestBlockHeight - , _genesisHeight = gHeight - } - -- If the latest header that is stored in the checkpointer was on an - -- orphaned fork, there is no way to recover it in the call of - -- 'initalPayloadState.readContracts'. We therefore rewind to the latest - -- avaliable header in the block header database. - -- - Checkpointer.exitOnRewindLimitExceeded $ initializeLatestBlock (_pactUnlimitedInitialRewind config) - act + -> IO a +withPactService http ver cid memPoolAccess chainwebLogger txFailuresCounter pdb sqlenv config act = do + SomeChainwebVersionT @v _ <- pure $ someChainwebVersionVal ver + SomeChainIdT @c _ <- pure $ someChainIdVal cid + let payloadClient = Rest.payloadClient @v @c @'PactProvider + payloadStore <- newPayloadStore http (logFunction chainwebLogger) pdb payloadClient + miningPayloadVar <- newEmptyTMVarIO + ChainwebPactDb.initSchema sqlenv + candidatePdb <- MapTable.emptyTable + let !pse = PactServiceEnv + { _psVersion = ver + , _psChainId = cid + , _psLogger = pactServiceLogger + , _psGasLogger = gasLogger <$ guard (_pactLogGas config) + , _psReadWriteSql = sqlenv + , _psPdb = payloadStore + , _psCandidatePdb = candidatePdb + , _psMempoolAccess = memPoolAccess + , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout config + , _psAllowReadsInLocal = _pactAllowReadsInLocal config + , _psEnableLocalTimeout = _pactEnableLocalTimeout config + , _psTxFailuresCounter = txFailuresCounter + , _psNewPayloadTxTimeLimit = _pactTxTimeLimit config + , _psMiner = _pactMiner config + , _psNewBlockGasLimit = _pactNewBlockGasLimit config + , _psMiningPayloadVar = miningPayloadVar + } + + runPactServiceM pse $ do + -- TODO: PP + -- when (_pactFullHistoryRequired config) $ do + -- mEarliestBlock <- Checkpointer.getEarliestBlock + -- case mEarliestBlock of + -- Nothing -> do + -- pure () + -- Just (earliestBlockHeight, _) -> do + -- let gHeight = genesisHeight ver cid + -- when (gHeight /= earliestBlockHeight) $ do + -- let msg = J.object + -- [ "details" J..= J.object + -- [ "earliest-block-height" J..= J.number (fromIntegral earliestBlockHeight) + -- , "genesis-height" J..= J.number (fromIntegral gHeight) + -- ] + -- , "message" J..= J.text "Your node has been configured\ + -- \ to require the full Pact history; however, the full\ + -- \ history is not available. Perhaps you have compacted\ + -- \ your Pact state?" + -- ] + -- logError_ chainwebLogger (J.encodeText msg) + -- throwM FullHistoryRequired + -- { _earliestBlockHeight = earliestBlockHeight + -- , _genesisHeight = gHeight + -- } + -- If the latest header that is stored in the checkpointer was on an + -- orphaned fork, there is no way to recover it in the call of + -- 'initalPayloadState.readContracts'. We therefore rewind to the latest + -- avaliable header in the block header database. + -- + -- Checkpointer.exitOnRewindLimitExceeded $ initializeLatestBlock (_pactUnlimitedInitialRewind config) + act where pactServiceLogger = setComponent "pact" chainwebLogger checkpointerLogger = addLabel ("sub-component", "checkpointer") pactServiceLogger gasLogger = addLabel ("transaction", "GasLogs") pactServiceLogger -initializeLatestBlock :: (Logger logger) => CanReadablePayloadCas tbl => Bool -> PactServiceM logger tbl () -initializeLatestBlock unlimitedRewind = Checkpointer.findLatestValidBlockHeader' >>= \case - Nothing -> return () - Just b -> Checkpointer.rewindToIncremental initialRewindLimit (ParentHeader b) - where - initialRewindLimit = RewindLimit 1000 <$ guard (not unlimitedRewind) - initialPayloadState :: Logger logger => CanReadablePayloadCas tbl @@ -230,47 +237,36 @@ initialPayloadState -> ChainId -> PactServiceM logger tbl () initialPayloadState v cid + -- TODO PP: no more, once we can disable payload providers | v ^. versionCheats . disablePact = pure () - | otherwise = initializeCoinContract v cid $ - genesisPayload v ^?! atChain cid + | otherwise = runGenesisIfNeeded v cid -initializeCoinContract +runGenesisIfNeeded :: forall tbl logger. (CanReadablePayloadCas tbl, Logger logger) => ChainwebVersion -> ChainId - -> PayloadWithOutputs -> PactServiceM logger tbl () -initializeCoinContract v cid pwo = do - latestBlock <- Checkpointer.getLatestBlock >>= \case - Nothing -> return Nothing - Just (_, latestHash) -> do - latestHeader <- ParentHeader - <$!> lookupBlockHeader latestHash "initializeCoinContract.findLatestValidBlockHeader" - return $ Just latestHeader - - case latestBlock of - Nothing -> do - logWarnPact "initializeCoinContract: Checkpointer returned no latest block. Starting from genesis." - validateGenesis - Just currentBlockHeader -> - if currentBlockHeader /= ParentHeader genesisHeader - then - unless (pact5 v cid (view (parentHeader . blockHeight . to succ) currentBlockHeader)) $ do - !mc <- Checkpointer.readFrom (Just currentBlockHeader) - (SomeBlockM $ Pair Pact4.readInitModules (error "pact5")) >>= \case - NoHistory -> throwM $ BlockHeaderLookupFailure - $ "initializeCoinContract: internal error: latest block not found: " <> sshow currentBlockHeader - Historical mc -> return mc - Pact4.updateInitCache mc currentBlockHeader - else do - logWarnPact "initializeCoinContract: Starting from genesis." - validateGenesis - where - validateGenesis = void $! undefined - -- execValidateBlock mempty genesisHeader (CheckablePayloadWithOutputs pwo) - - genesisHeader :: BlockHeader - genesisHeader = genesisBlockHeader v cid +runGenesisIfNeeded v cid = do + latestBlock <- _consensusStateLatest <$> Checkpointer.getConsensusState + when (isGenesisBlockHeader' v cid (Parent $ _syncStateBlockHash latestBlock)) $ do + let payload = genesisPayload v ^?! atChain cid + let payloadHash = genesisBlockPayloadHash v cid + let genesisEvaluationCtx = EvaluationCtx + { _evaluationCtxParentCreationTime = Parent $ v ^?! versionGenesis . genesisTime . atChain cid + , _evaluationCtxParentHash = Parent $ _syncStateBlockHash latestBlock + , _evaluationCtxParentHeight = Parent $ _syncStateHeight latestBlock + -- should not be used + , _evaluationCtxMinerReward = MinerReward 0 + , _evaluationCtxPayload = ConsensusPayload + { _consensusPayloadHash = payloadHash + , _consensusPayloadData = Just $ EncodedPayloadData $ encodePayloadData $ payloadWithOutputsToPayloadData payload + } + } + let targetSyncState = genesisConsensusState v cid + actualSyncState <- execValidateBlock mempty Nothing + (ForkInfo [genesisEvaluationCtx] payloadHash targetSyncState Nothing) + when (targetSyncState /= actualSyncState) $ + error "failed to run genesis block" -- | Lookup a block header. -- @@ -284,512 +280,432 @@ initializeCoinContract v cid pwo = do -- 2. the header gets orphaned and the next 'execValidateBlock' call would cause -- a rewind to an ancestor, which is available in the db. -- -lookupBlockHeader :: BlockHash -> Text -> PactServiceM logger tbl BlockHeader -lookupBlockHeader bhash ctx = do - bhdb <- view psBlockHeaderDb - liftIO $! lookupM bhdb bhash `catchAllSynchronous` \e -> - throwM $ BlockHeaderLookupFailure $ - "failed lookup of parent header in " <> ctx <> ": " <> sshow e - -execNewBlock - :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) - => MemPoolAccess - -> NewBlockFill - -> ParentHeader - -> PactServiceM logger tbl (Historical (ForSomePactVersion BlockInProgress)) -execNewBlock mpAccess fill newBlockParent = pactLabel "execNewBlock" $ do - miner <- view psMiner >>= \case - Nothing -> internalError "Chainweb.Pact.PactService.execNewBlock: Mining is disabled. Please provide a valid miner in the pact service configuration" - Just x -> return x - let pHeight = view blockHeight $ _parentHeader newBlockParent - let pHash = view blockHash $ _parentHeader newBlockParent - logInfoPact $ "(parent height = " <> sshow pHeight <> ")" - <> " (parent hash = " <> sshow pHash <> ")" - blockGasLimit <- view psBlockGasLimit - v <- view chainwebVersion - cid <- view chainId - Checkpointer.readFrom (Just newBlockParent) $ - -- TODO: after the Pact 5 fork is complete, the Pact 4 case below will - -- be unnecessary; the genesis blocks are already handled by 'execNewGenesisBlock'. - SomeBlockM $ Pair - (do - blockDbEnv <- view psBlockDbEnv - initCache <- initModuleCacheForBlock - coinbaseOutput <- Pact4.runCoinbase - miner - (Pact4.EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled True) - initCache - let pactDb = Pact4._cpPactDbEnv blockDbEnv - finalBlockState <- fmap Pact4._benvBlockState - $ liftIO - $ readMVar - $ pdPactDbVar - $ pactDb - let blockInProgress = BlockInProgress - { _blockInProgressModuleCache = Pact4ModuleCache initCache - -- ^ we do not use the module cache populated by coinbase in - -- subsequent transactions - , _blockInProgressHandle = BlockHandle (Pact4._bsTxId finalBlockState) (Pact4._bsPendingBlock finalBlockState) - , _blockInProgressParentHeader = Just newBlockParent - , _blockInProgressRemainingGasLimit = blockGasLimit - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = coinbaseOutput - , _transactionPairs = mempty - } - , _blockInProgressMiner = miner - , _blockInProgressPactVersion = Pact4T - , _blockInProgressChainwebVersion = v - , _blockInProgressChainId = cid - } - case fill of - NewBlockFill -> ForPact4 <$> Pact4.continueBlock mpAccess blockInProgress - NewBlockEmpty -> return (ForPact4 blockInProgress) - ) - - (do - coinbaseOutput <- Pact5.runCoinbase miner >>= \case - Left coinbaseError -> internalError $ "Error during coinbase: " <> sshow coinbaseError - Right coinbaseOutput -> - -- pretend that coinbase can throw an error, when we know it can't. - -- perhaps we can make the Transactions express this, may not be worth it. - return $ coinbaseOutput & Pact5.crResult . Pact5._PactResultErr %~ absurd - hndl <- use Pact5.pbBlockHandle - let blockInProgress = BlockInProgress - { _blockInProgressModuleCache = Pact5NoModuleCache - , _blockInProgressHandle = hndl - , _blockInProgressParentHeader = Just newBlockParent - , _blockInProgressRemainingGasLimit = blockGasLimit - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = coinbaseOutput - , _transactionPairs = mempty - } - , _blockInProgressMiner = miner - , _blockInProgressPactVersion = Pact5T - , _blockInProgressChainwebVersion = v - , _blockInProgressChainId = cid - } - case fill of - NewBlockFill -> ForPact5 <$> Pact5.continueBlock mpAccess blockInProgress - NewBlockEmpty -> return (ForPact5 blockInProgress) - ) - -execContinueBlock - :: forall logger tbl pv. (Logger logger, CanReadablePayloadCas tbl) - => MemPoolAccess - -> BlockInProgress pv - -> PactServiceM logger tbl (Historical (BlockInProgress pv)) -execContinueBlock mpAccess blockInProgress = pactLabel "execNewBlock" $ do - Checkpointer.readFrom newBlockParent $ - case _blockInProgressPactVersion blockInProgress of - -- TODO: after the Pact 5 fork is complete, the Pact 4 case below will - -- be unnecessary; the genesis blocks are already handled by 'execNewGenesisBlock'. - Pact4T -> SomeBlockM $ Pair (Pact4.continueBlock mpAccess blockInProgress) (error "pact5") - Pact5T -> SomeBlockM $ Pair (error "pact4") (Pact5.continueBlock mpAccess blockInProgress) - where - newBlockParent = _blockInProgressParentHeader blockInProgress - --- | only for use in generating genesis blocks in tools. --- -execNewGenesisBlock - :: (Logger logger, CanReadablePayloadCas tbl) - => Miner - -> Vector Pact4.UnparsedTransaction - -> PactServiceM logger tbl PayloadWithOutputs -execNewGenesisBlock miner newTrans = pactLabel "execNewGenesisBlock" $ do - historicalBlock <- Checkpointer.readFrom Nothing $ SomeBlockM $ Pair - (do - logger <- view (psServiceEnv . psLogger) - v <- view chainwebVersion - cid <- view chainId - txs <- liftIO $ traverse (runExceptT . Pact4.checkParse logger v cid (genesisBlockHeight v cid)) newTrans - parsedTxs <- case partitionEithers (V.toList txs) of - ([], validTxs) -> return (V.fromList validTxs) - (errs, _) -> internalError $ "Invalid genesis txs: " <> sshow errs - -- NEW GENESIS COINBASE: Reject bad coinbase, use date rule for precompilation - results <- - Pact4.execTransactions miner parsedTxs - (Pact4.EnforceCoinbaseFailure True) - (Pact4.CoinbaseUsePrecompiled False) Nothing Nothing - >>= throwCommandInvalidError - return $! toPayloadWithOutputs Pact4T miner results - ) - (do - v <- view chainwebVersion - cid <- view chainId - let mempoolAccess = mempty - { mpaGetBlock = \bf pbc bh bhash _bheader -> do - if _bfCount bf == 0 - then do - maybeInvalidTxs <- pbc bh bhash newTrans - validTxs <- case partitionEithers (V.toList maybeInvalidTxs) of - ([], validTxs) -> return validTxs - (errs, _) -> throwM $ Pact5GenesisCommandsInvalid errs - V.fromList validTxs `Strategies.usingIO` traverse Strategies.rseq - else do - return V.empty - } - startHandle <- use Pact5.pbBlockHandle - let bipStart = BlockInProgress - { _blockInProgressHandle = startHandle - , _blockInProgressMiner = miner - , _blockInProgressModuleCache = Pact5NoModuleCache - , _blockInProgressPactVersion = Pact5T - , _blockInProgressParentHeader = Nothing - , _blockInProgressChainwebVersion = v - , _blockInProgressChainId = cid - -- fake gas limit, gas is free for genesis - , _blockInProgressRemainingGasLimit = GasLimit (Pact4.ParsedInteger 999_999_999) - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = absurd <$> Pact5.noCoinbase - , _transactionPairs = mempty - } - } - results <- Pact5.continueBlock mempoolAccess bipStart - return $! finalizeBlock results - ) - case historicalBlock of - NoHistory -> internalError "PactService.execNewGenesisBlock: Impossible error, unable to rewind before genesis" - Historical block -> return block +-- lookupBlockHeader :: BlockHash -> Text -> PactServiceM logger tbl BlockHeader +-- lookupBlockHeader bhash ctx = do +-- bhdb <- view psBlockHeaderDb +-- liftIO $! lookupM bhdb bhash `catchAllSynchronous` \e -> +-- throwM $ BlockHeaderLookupFailure $ +-- "failed lookup of parent header in " <> ctx <> ": " <> sshow e + +-- execNewBlock +-- :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) +-- => MemPoolAccess +-- -> NewBlockFill +-- -> ParentHeader +-- -> PactServiceM logger tbl (Historical (ForSomePactVersion BlockInProgress)) +-- execNewBlock mpAccess fill newBlockParent = pactLabel "execNewBlock" $ do +-- miner <- view psMiner >>= \case +-- Nothing -> internalError "Chainweb.Pact.PactService.execNewBlock: Mining is disabled. Please provide a valid miner in the pact service configuration" +-- Just x -> return x +-- let pHeight = view blockHeight $ _parentHeader newBlockParent +-- let pHash = view blockHash $ _parentHeader newBlockParent +-- logInfoPact $ "(parent height = " <> sshow pHeight <> ")" +-- <> " (parent hash = " <> sshow pHash <> ")" +-- blockGasLimit <- view psNewBlockGasLimit +-- v <- view chainwebVersion +-- cid <- view chainId +-- Checkpointer.readFrom (Just newBlockParent) $ +-- -- TODO: after the Pact 5 fork is complete, the Pact 4 case below will +-- -- be unnecessary; the genesis blocks are already handled by 'execNewGenesisBlock'. +-- SomeBlockM $ Pair +-- (do +-- blockDbEnv <- view psBlockDbEnv +-- initCache <- initModuleCacheForBlock +-- coinbaseOutput <- Pact4.runCoinbase +-- miner +-- (Pact4.EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled True) +-- initCache +-- let pactDb = Pact4._cpPactDbEnv blockDbEnv +-- finalBlockState <- fmap Pact4._benvBlockState +-- $ liftIO +-- $ readMVar +-- $ pdPactDbVar +-- $ pactDb +-- let blockInProgress = BlockInProgress +-- { _blockInProgressModuleCache = Pact4ModuleCache initCache +-- -- ^ we do not use the module cache populated by coinbase in +-- -- subsequent transactions +-- , _blockInProgressHandle = BlockHandle (Pact4._bsTxId finalBlockState) (Pact4._bsPendingBlock finalBlockState) +-- , _blockInProgressParentHeader = Just newBlockParent +-- , _blockInProgressRemainingGasLimit = blockGasLimit +-- , _blockInProgressTransactions = Transactions +-- { _transactionCoinbase = coinbaseOutput +-- , _transactionPairs = mempty +-- } +-- , _blockInProgressMiner = miner +-- , _blockInProgressPactVersion = Pact4T +-- , _blockInProgressChainwebVersion = v +-- , _blockInProgressChainId = cid +-- } +-- case fill of +-- NewBlockFill -> ForPact4 <$> Pact4.continueBlock mpAccess blockInProgress +-- NewBlockEmpty -> return (ForPact4 blockInProgress) +-- ) + +-- (do +-- coinbaseOutput <- Pact.runCoinbase miner >>= \case +-- Left coinbaseError -> internalError $ "Error during coinbase: " <> sshow coinbaseError +-- Right coinbaseOutput -> +-- -- pretend that coinbase can throw an error, when we know it can't. +-- -- perhaps we can make the Transactions express this, may not be worth it. +-- return $ coinbaseOutput & Pact.crResult . Pact._PactResultErr %~ absurd +-- hndl <- use Pact.pbBlockHandle +-- let blockInProgress = BlockInProgress +-- { _blockInProgressModuleCache = Pact5NoModuleCache +-- , _blockInProgressHandle = hndl +-- , _blockInProgressParentHeader = Just newBlockParent +-- , _blockInProgressRemainingGasLimit = blockGasLimit +-- , _blockInProgressTransactions = Transactions +-- { _transactionCoinbase = coinbaseOutput +-- , _transactionPairs = mempty +-- } +-- , _blockInProgressMiner = miner +-- , _blockInProgressPactVersion = Pact5T +-- , _blockInProgressChainwebVersion = v +-- , _blockInProgressChainId = cid +-- } +-- case fill of +-- NewBlockFill -> ForPact5 <$> Pact.continueBlock mpAccess blockInProgress +-- NewBlockEmpty -> return (ForPact5 blockInProgress) +-- ) + +-- execContinueBlock +-- :: forall logger tbl pv. (Logger logger, CanReadablePayloadCas tbl) +-- => MemPoolAccess +-- -> BlockInProgress pv +-- -> PactServiceM logger tbl (Historical (BlockInProgress pv)) +-- execContinueBlock mpAccess blockInProgress = pactLabel "execNewBlock" $ do +-- Checkpointer.readFrom newBlockParent $ +-- case _blockInProgressPactVersion blockInProgress of +-- -- TODO: after the Pact 5 fork is complete, the Pact 4 case below will +-- -- be unnecessary; the genesis blocks are already handled by 'execNewGenesisBlock'. +-- Pact4T -> SomeBlockM $ Pair (Pact4.continueBlock mpAccess blockInProgress) (error "pact5") +-- Pact5T -> SomeBlockM $ Pair (error "pact4") (Pact.continueBlock mpAccess blockInProgress) +-- where +-- newBlockParent = _blockInProgressParentHeader blockInProgress + +-- -- | only for use in generating genesis blocks in tools. +-- -- +-- execNewGenesisBlock +-- :: (Logger logger, CanReadablePayloadCas tbl) +-- => Miner +-- -> Vector Pact4.UnparsedTransaction +-- -> PactServiceM logger tbl PayloadWithOutputs +-- execNewGenesisBlock miner newTrans = pactLabel "execNewGenesisBlock" $ do +-- historicalBlock <- Checkpointer.readFrom Nothing $ SomeBlockM $ Pair +-- (do +-- logger <- view (psServiceEnv . psLogger) +-- v <- view chainwebVersion +-- cid <- view chainId +-- txs <- liftIO $ traverse (runExceptT . Pact4.checkParse logger v cid (genesisBlockHeight v cid)) newTrans +-- parsedTxs <- case partitionEithers (V.toList txs) of +-- ([], validTxs) -> return (V.fromList validTxs) +-- (errs, _) -> internalError $ "Invalid genesis txs: " <> sshow errs +-- -- NEW GENESIS COINBASE: Reject bad coinbase, use date rule for precompilation +-- results <- +-- Pact4.execTransactions miner parsedTxs +-- (Pact4.EnforceCoinbaseFailure True) +-- (Pact4.CoinbaseUsePrecompiled False) Nothing Nothing +-- >>= throwCommandInvalidError +-- return $! toPayloadWithOutputs Pact4T miner results +-- ) +-- (do +-- v <- view chainwebVersion +-- cid <- view chainId +-- let mempoolAccess = mempty +-- { mpaGetBlock = \bf pbc bh bhash _bheader -> do +-- if _bfCount bf == 0 +-- then do +-- maybeInvalidTxs <- pbc bh bhash newTrans +-- validTxs <- case partitionEithers (V.toList maybeInvalidTxs) of +-- ([], validTxs) -> return validTxs +-- (errs, _) -> throwM $ Pact5GenesisCommandsInvalid errs +-- V.fromList validTxs `Strategies.usingIO` traverse Strategies.rseq +-- else do +-- return V.empty +-- } +-- startHandle <- use Pact.pbBlockHandle +-- let bipStart = BlockInProgress +-- { _blockInProgressHandle = startHandle +-- , _blockInProgressMiner = miner +-- , _blockInProgressModuleCache = Pact5NoModuleCache +-- , _blockInProgressPactVersion = Pact5T +-- , _blockInProgressParentHeader = Nothing +-- , _blockInProgressChainwebVersion = v +-- , _blockInProgressChainId = cid +-- -- fake gas limit, gas is free for genesis +-- , _blockInProgressRemainingGasLimit = GasLimit (Pact4.ParsedInteger 999_999_999) +-- , _blockInProgressTransactions = Transactions +-- { _transactionCoinbase = absurd <$> Pact.noCoinbase +-- , _transactionPairs = mempty +-- } +-- } +-- results <- Pact.continueBlock mempoolAccess bipStart +-- return $! finalizeBlock results +-- ) +-- case historicalBlock of +-- NoHistory -> internalError "PactService.execNewGenesisBlock: Impossible error, unable to rewind before genesis" +-- Historical block -> return block execReadOnlyReplay - :: forall logger tbl + :: forall logger tbl p . (Logger logger, CanReadablePayloadCas tbl) - => BlockHeader - -> Maybe BlockHeader + => [EvaluationCtx p] -> PactServiceM logger tbl () -execReadOnlyReplay lowerBound maybeUpperBound = pactLabel "execReadOnlyReplay" $ do - ParentHeader cur <- Checkpointer.findLatestValidBlockHeader - logger <- view psLogger - bhdb <- view psBlockHeaderDb - pdb <- view psPdb - v <- view chainwebVersion - cid <- view chainId - -- lower bound must be an ancestor of upper. - upperBound <- case maybeUpperBound of - Just upperBound -> do - liftIO (ancestorOf bhdb (view blockHash lowerBound) (view blockHash upperBound)) >>= - flip unless (internalError "lower bound is not an ancestor of upper bound") - - -- upper bound must be an ancestor of latest header. - liftIO (ancestorOf bhdb (view blockHash upperBound) (view blockHash cur)) >>= - flip unless (internalError "upper bound is not an ancestor of latest header") - - return upperBound - Nothing -> do - liftIO (ancestorOf bhdb (view blockHash lowerBound) (view blockHash cur)) >>= - flip unless (internalError "lower bound is not an ancestor of latest header") - - return cur - liftIO $ logFunctionText logger Info $ "pact db replaying between blocks " - <> sshow (view blockHeight lowerBound, view blockHash lowerBound) <> " and " - <> sshow (view blockHeight upperBound, view blockHash upperBound) - - let genHeight = genesisHeight v cid - -- we don't want to replay the genesis header in here. - let lowerHeight = max (succ genHeight) (view blockHeight lowerBound) - withPactState $ \runPact -> - liftIO $ getBranchIncreasing bhdb upperBound (int lowerHeight) $ \blocks -> do - heightRef <- newIORef lowerHeight - withAsync (heightProgress lowerHeight (view blockHeight upperBound) heightRef (logInfo_ logger)) $ \_ -> do - blocks - & Stream.hoist liftIO - & play bhdb pdb heightRef runPact - where - - play - :: CanReadablePayloadCas tbl - => BlockHeaderDb - -> PayloadDb tbl - -> IORef BlockHeight - -> (forall a. PactServiceM logger tbl a -> IO a) - -> Stream.Stream (Stream.Of BlockHeader) IO r - -> IO r - play bhdb pdb heightRef runPact blocks = do - logger <- runPact $ view psLogger - validationFailedRef <- newIORef False - r <- blocks & Stream.mapM_ (\bh -> do - bhParent <- liftIO $ lookupParentM GenesisParentThrow bhdb bh - let - printValidationError (BlockValidationFailure (BlockValidationFailureMsg m)) = do - writeIORef validationFailedRef True - logFunctionText logger Error m - printValidationError e = throwM e - handleMissingBlock NoHistory = throwM $ BlockHeaderLookupFailure $ - "execReadOnlyReplay: missing block: " <> sshow bh - handleMissingBlock (Historical ()) = return () - payload <- liftIO $ fromJuste <$> - lookupPayloadWithHeight pdb (Just $ view blockHeight bh) (view blockPayloadHash bh) - let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload) - let isUpgradeBlock = isJust $ _chainwebVersion bhdb ^? versionUpgrades . atChain (_chainId bhdb) . ix (view blockHeight bh) - liftIO $ writeIORef heightRef (view blockHeight bh) - unless (isPayloadEmpty && not isUpgradeBlock) - $ handle printValidationError - $ (handleMissingBlock =<<) - $ runPact - $ Checkpointer.readFrom (Just $ ParentHeader bhParent) $ - SomeBlockM $ Pair - (void $ Pact4.execBlock bh (CheckablePayloadWithOutputs payload)) - (void $ Pact5.execExistingBlock bh (CheckablePayloadWithOutputs payload)) - ) - validationFailed <- readIORef validationFailedRef - when validationFailed $ - throwM $ BlockValidationFailure $ BlockValidationFailureMsg "Prior block validation errors" - return r - - heightProgress :: BlockHeight -> BlockHeight -> IORef BlockHeight -> (Text -> IO ()) -> IO () - heightProgress initialHeight endHeight ref logFun = do - heightAndRateRef <- newIORef (initialHeight, 0.0 :: Double) - let delayMicros = 20_000_000 - liftIO $ threadDelay (delayMicros `div` 2) - forever $ do - liftIO $ threadDelay delayMicros - (lastHeight, oldRate) <- readIORef heightAndRateRef - now' <- getCurrentTime - currentHeight <- readIORef ref - let blocksPerSecond - = 0.8 - * oldRate - + 0.2 - * fromIntegral (currentHeight - lastHeight) - / (fromIntegral delayMicros / 1_000_000) - writeIORef heightAndRateRef (currentHeight, blocksPerSecond) - let est = - flip addUTCTime now' - $ realToFrac @Double @NominalDiffTime - $ fromIntegral @BlockHeight @Double - (endHeight - initialHeight) - / blocksPerSecond - logFun - $ Text.pack $ printf "height: %d | rate: %.1f blocks/sec | est. %s" - (fromIntegral @BlockHeight @Int $ currentHeight - initialHeight) - blocksPerSecond - (formatShow iso8601Format est) - -execLocal - :: (Logger logger, CanReadablePayloadCas tbl) - => Pact4.UnparsedTransaction - -> Maybe LocalPreflightSimulation - -- ^ preflight flag - -> Maybe LocalSignatureVerification - -- ^ turn off signature verification checks? - -> Maybe RewindDepth - -- ^ rewind depth - -> PactServiceM logger tbl LocalResult -execLocal cwtx preflight sigVerify rdepth = pactLabel "execLocal" $ do - - e@PactServiceEnv{..} <- ask - - let !cmd = Pact4.payloadObj <$> cwtx - !pm = Pact4.publicMetaOf cmd - !v = _chainwebVersion e - !cid = _chainId e - - bhdb <- view psBlockHeaderDb - - -- when no depth is defined, treat - -- withCheckpointerRewind as readFrom - -- (i.e. setting rewind to 0). - let rewindDepth = maybe 0 _rewindDepth rdepth - - let timeoutLimit - | _psEnableLocalTimeout = Just (2 * 1_000_000) - | otherwise = Nothing - - let localPact4 = do - pc <- view psParentHeader - let spv = Pact4.pactSPV bhdb (_parentHeader pc) - ctx <- Pact4.getTxContext noMiner pm - let bh = Pact4.ctxCurrentBlockHeight ctx - let gasModel = Pact4.getGasModel ctx - mc <- Pact4.getInitCache - dbEnv <- Pact4._cpPactDbEnv <$> view psBlockDbEnv - logger <- view (psServiceEnv . psLogger) - - evalContT $ withEarlyReturn $ \earlyReturn -> do - pact4Cwtx <- liftIO (runExceptT (Pact4.checkParse logger v cid bh cwtx)) >>= \case - Left err -> earlyReturn $ - let - parseError = Pact4.CommandResult - { _crReqKey = Pact4.cmdToRequestKey cmd - , _crTxId = Nothing - , _crResult = Pact4.PactResult (Left (Pact4.PactError Pact4.SyntaxError Pact4.noInfo [] (sshow err))) - , _crGas = cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit . to int - , _crLogs = Nothing - , _crContinuation = Nothing - , _crMetaData = Nothing - , _crEvents = [] - } - in case preflight of - Just PreflightSimulation -> Pact4LocalResultWithWarns parseError [] - _ -> Pact4LocalResultLegacy parseError - Right pact4Cwtx -> return pact4Cwtx - case (preflight, sigVerify) of - (_, Just NoVerify) -> do - let payloadBS = SB.fromShort (Pact4._cmdPayload $ Pact4.payloadBytes <$> cwtx) - let validated = Pact4.verifyHash @'Pact4.Blake2b_256 (Pact4._cmdHash cmd) payloadBS - case validated of - Left err -> earlyReturn $ review _MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err - Right _ -> return () - _ -> do - let validated = Pact4.assertCommand pact4Cwtx (validPPKSchemes v cid bh) (isWebAuthnPrefixLegal v cid bh) - case validated of - Left err -> earlyReturn $ review _MetadataValidationFailure (pure $ displayAssertCommandError err) - Right () -> return () - - -- - -- if the ?preflight query parameter is set to True, we run the `applyCmd` workflow - -- otherwise, we prefer the old (default) behavior. When no preflight flag is - -- specified, we run the old behavior. When it is set to true, we also do metadata - -- validations. - -- - case preflight of - Just PreflightSimulation -> do - lift (Pact4.liftPactServiceM (Pact4.assertPreflightMetadata cmd ctx sigVerify)) >>= \case - Left err -> earlyReturn $ review _MetadataValidationFailure err - Right () -> return () - let initialGas = Pact4.initialGasOf $ Pact4._cmdPayload pact4Cwtx - T3 cr _mc warns <- liftIO $ Pact4.applyCmd - _psVersion _psLogger _psGasLogger Nothing dbEnv - noMiner gasModel ctx (TxBlockIdx 0) spv (Pact4.payloadObj <$> pact4Cwtx) - initialGas mc ApplyLocal - - let cr' = hashPact4TxLogs cr - warns' = Pact4.renderCompactText <$> toList warns - pure $ Pact4LocalResultWithWarns cr' warns' - _ -> liftIO $ do - let execConfig = Pact4.mkExecutionConfig $ - [ Pact4.FlagAllowReadInLocal | _psAllowReadsInLocal ] ++ - Pact4.enablePactEvents' v cid bh ++ - Pact4.enforceKeysetFormats' v cid bh ++ - Pact4.disableReturnRTC v cid bh - - cr <- Pact4.applyLocal - _psLogger _psGasLogger dbEnv - gasModel ctx spv - pact4Cwtx mc execConfig - - let cr' = hashPact4TxLogs cr - pure $ Pact4LocalResultLegacy cr' - - let localPact5 = do - ph <- view psParentHeader - let pact5RequestKey = Pact5.RequestKey (Pact5.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) - evalContT $ withEarlyReturn $ \earlyReturn -> do - pact5Cmd <- case Pact5.parsePact4Command cwtx of - Left (fmap Pact5.spanInfoToLineInfo -> parseError) -> - earlyReturn $ Pact5LocalResultLegacy Pact5.CommandResult - { _crReqKey = pact5RequestKey - , _crTxId = Nothing - , _crResult = Pact5.PactResultErr $ - Pact5.pactErrorToOnChainError parseError - , _crGas = Pact5.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit - , _crLogs = Nothing - , _crContinuation = Nothing - , _crMetaData = Nothing - , _crEvents = [] - } - Right pact5Cmd -> return pact5Cmd - - -- this is just one of our metadata validation passes. - -- in preflight, we do another one, which replicates some of this work; - -- TODO: unify preflight, newblock, and validateblock tx metadata validation - case (preflight, sigVerify) of - (_, Just NoVerify) -> do - let payloadBS = SB.fromShort (Pact4._cmdPayload $ Pact4.payloadBytes <$> cwtx) - let validated = Pact5.verifyHash (Pact5._cmdHash pact5Cmd) payloadBS - case validated of - Left err -> earlyReturn $ - review _MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err - Right _ -> return () - _ -> do - let validated = Pact5.assertCommand pact5Cmd - case validated of - Left err -> earlyReturn $ - review _MetadataValidationFailure (pure $ displayAssertCommandError err) - Right () -> return () - - let txCtx = Pact5.TxContext ph noMiner - let spvSupport = Pact5.pactSPV bhdb (_parentHeader ph) - case preflight of - Just PreflightSimulation -> do - -- preflight needs to do additional checks on the metadata - -- to match on-chain tx validation - lift (Pact5.liftPactServiceM (Pact5.assertPreflightMetadata (view Pact5.payloadObj <$> pact5Cmd) txCtx sigVerify)) >>= \case - Left err -> earlyReturn $ review _MetadataValidationFailure err - Right () -> return () - let initialGas = Pact5.initialGasOf $ Pact5._cmdPayload pact5Cmd - applyCmdResult <- lift $ Pact5.pactTransaction Nothing (\dbEnv -> - Pact5.applyCmd - _psLogger _psGasLogger dbEnv - txCtx (TxBlockIdx 0) spvSupport initialGas (view Pact5.payloadObj <$> pact5Cmd) - ) - commandResult <- case applyCmdResult of - Left err -> - earlyReturn $ Pact5LocalResultWithWarns Pact5.CommandResult - { _crReqKey = Pact5.RequestKey (Pact5.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) - , _crTxId = Nothing - , _crResult = Pact5.PactResultErr $ - Pact5.PactOnChainError - -- the only legal error type, once chainweaver is really gone, we - -- can use a real error type - (Pact5.ErrorType "EvalError") - (Pact5.mkBoundedText $ prettyPact5GasPurchaseFailure err) - (Pact5.LocatedErrorInfo Pact5.TopLevelErrorOrigin Pact5.noInfo) - , _crGas = Pact5.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit - , _crLogs = Nothing - , _crContinuation = Nothing - , _crMetaData = Nothing - , _crEvents = [] - } - [] - Right commandResult -> return commandResult - let pact5Pm = pact5Cmd ^. Pact5.cmdPayload . Pact5.payloadObj . Pact5.pMeta - let metadata = J.toJsonViaEncode $ Pact5.StableEncoding $ Pact5.ctxToPublicData pact5Pm txCtx - let commandResult' = hashPact5TxLogs $ set Pact5.crMetaData (Just metadata) commandResult - -- TODO: once Pact 5 has warnings, include them here. - pure $ Pact5LocalResultWithWarns - (Pact5.pactErrorToOnChainError <$> commandResult') - [] - _ -> lift $ do - -- default is legacy mode: use applyLocal, don't buy gas, don't do any - -- metadata checks beyond signature and hash checking - cr <- Pact5.pactTransaction Nothing $ \dbEnv -> do - fmap Pact5.pactErrorToOnChainError <$> Pact5.applyLocal _psLogger _psGasLogger dbEnv txCtx spvSupport (view Pact5.payloadObj <$> pact5Cmd) - pure $ Pact5LocalResultLegacy (hashPact5TxLogs cr) - - let doLocal = Checkpointer.readFromNthParent (fromIntegral rewindDepth) - $ SomeBlockM $ Pair localPact4 localPact5 - - case timeoutLimit of - Nothing -> doLocal - Just limit -> withPactState $ \run -> timeoutYield limit (run doLocal) >>= \case - Just r -> pure r - Nothing -> do - logError_ _psLogger $ "Local action timed out for cwtx:\n" <> sshow cwtx - pure $ review _LocalTimeout () - -execSyncToBlock - :: (CanReadablePayloadCas tbl, Logger logger) - => BlockHeader - -> PactServiceM logger tbl () -execSyncToBlock targetHeader = pactLabel "execSyncToBlock" $ do - latestHeader <- Checkpointer.findLatestValidBlockHeader' >>= maybe failNonGenesisOnEmptyDb return - if latestHeader == targetHeader - then do - logInfoPact $ "checkpointer at checkpointer target" - <> ". target height: " <> sshow (view blockHeight latestHeader) - <> "; target hash: " <> blockHashToText (view blockHash latestHeader) - else do - logInfoPact $ "rewind to checkpointer target" - <> ". current height: " <> sshow (view blockHeight latestHeader) - <> "; current hash: " <> blockHashToText (view blockHash latestHeader) - <> "; target height: " <> sshow targetHeight - <> "; target hash: " <> blockHashToText targetHash - Checkpointer.rewindToIncremental Nothing (ParentHeader targetHeader) - where - targetHeight = view blockHeight targetHeader - targetHash = view blockHash targetHeader - failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" +execReadOnlyReplay blocks = undefined -- pactLabel "execReadOnlyReplay" $ do + -- ParentHeader cur <- Checkpointer.findLatestValidBlockHeader + -- logger <- view psLogger + -- bhdb <- view psBlockHeaderDb + -- pdb <- view psPdb + -- v <- view chainwebVersion + -- cid <- view chainId + -- -- lower bound must be an ancestor of upper. + -- upperBound <- case maybeUpperBound of + -- Just upperBound -> do + -- liftIO (ancestorOf bhdb (view blockHash lowerBound) (view blockHash upperBound)) >>= + -- flip unless (internalError "lower bound is not an ancestor of upper bound") + + -- -- upper bound must be an ancestor of latest header. + -- liftIO (ancestorOf bhdb (view blockHash upperBound) (view blockHash cur)) >>= + -- flip unless (internalError "upper bound is not an ancestor of latest header") + + -- return upperBound + -- Nothing -> do + -- liftIO (ancestorOf bhdb (view blockHash lowerBound) (view blockHash cur)) >>= + -- flip unless (internalError "lower bound is not an ancestor of latest header") + + -- return cur + -- liftIO $ logFunctionText logger Info $ "pact db replaying between blocks " + -- <> sshow (view blockHeight lowerBound, view blockHash lowerBound) <> " and " + -- <> sshow (view blockHeight upperBound, view blockHash upperBound) + + -- let genHeight = genesisHeight v cid + -- -- we don't want to replay the genesis header in here. + -- let lowerHeight = max (succ genHeight) (view blockHeight lowerBound) + -- withPactState $ \runPact -> + -- liftIO $ getBranchIncreasing bhdb upperBound (int lowerHeight) $ \blocks -> do + -- heightRef <- newIORef lowerHeight + -- withAsync (heightProgress lowerHeight (view blockHeight upperBound) heightRef (logInfo_ logger)) $ \_ -> do + -- blocks + -- & Stream.hoist liftIO + -- & play bhdb pdb heightRef runPact + -- where + + -- play + -- :: CanReadablePayloadCas tbl + -- => BlockHeaderDb + -- -> PayloadDb tbl + -- -> IORef BlockHeight + -- -> (forall a. PactServiceM logger tbl a -> IO a) + -- -> Stream.Stream (Stream.Of BlockHeader) IO r + -- -> IO r + -- play bhdb pdb heightRef runPact blocks = do + -- logger <- runPact $ view psLogger + -- validationFailedRef <- newIORef False + -- r <- blocks & Stream.mapM_ (\bh -> do + -- bhParent <- liftIO $ lookupParentM GenesisParentThrow bhdb bh + -- let + -- printValidationError (BlockValidationFailure (BlockValidationFailureMsg m)) = do + -- writeIORef validationFailedRef True + -- logFunctionText logger Error m + -- printValidationError e = throwM e + -- handleMissingBlock NoHistory = throwM $ BlockHeaderLookupFailure $ + -- "execReadOnlyReplay: missing block: " <> sshow bh + -- handleMissingBlock (Historical ()) = return () + -- payload <- liftIO $ fromJuste <$> + -- lookupPayloadWithHeight pdb (Just $ view blockHeight bh) (view blockPayloadHash bh) + -- let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload) + -- let isUpgradeBlock = isJust $ _chainwebVersion bhdb ^? versionUpgrades . atChain (_chainId bhdb) . ix (view blockHeight bh) + -- liftIO $ writeIORef heightRef (view blockHeight bh) + -- unless (isPayloadEmpty && not isUpgradeBlock) + -- $ handle printValidationError + -- $ (handleMissingBlock =<<) + -- $ runPact + -- $ Checkpointer.readFrom (Just $ ParentHeader bhParent) $ + -- SomeBlockM $ Pair + -- (void $ Pact4.execBlock bh (CheckablePayloadWithOutputs payload)) + -- (void $ Pact.execExistingBlock bh (CheckablePayloadWithOutputs payload)) + -- ) + -- validationFailed <- readIORef validationFailedRef + -- when validationFailed $ + -- throwM $ BlockValidationFailure $ BlockValidationFailureMsg "Prior block validation errors" + -- return r + + -- heightProgress :: BlockHeight -> BlockHeight -> IORef BlockHeight -> (Text -> IO ()) -> IO () + -- heightProgress initialHeight endHeight ref logFun = do + -- heightAndRateRef <- newIORef (initialHeight, 0.0 :: Double) + -- let delayMicros = 20_000_000 + -- liftIO $ threadDelay (delayMicros `div` 2) + -- forever $ do + -- liftIO $ threadDelay delayMicros + -- (lastHeight, oldRate) <- readIORef heightAndRateRef + -- now' <- getCurrentTime + -- currentHeight <- readIORef ref + -- let blocksPerSecond + -- = 0.8 + -- * oldRate + -- + 0.2 + -- * fromIntegral (currentHeight - lastHeight) + -- / (fromIntegral delayMicros / 1_000_000) + -- writeIORef heightAndRateRef (currentHeight, blocksPerSecond) + -- let est = + -- flip addUTCTime now' + -- $ realToFrac @Double @NominalDiffTime + -- $ fromIntegral @BlockHeight @Double + -- (endHeight - initialHeight) + -- / blocksPerSecond + -- logFun + -- $ Text.pack $ printf "height: %d | rate: %.1f blocks/sec | est. %s" + -- (fromIntegral @BlockHeight @Int $ currentHeight - initialHeight) + -- blocksPerSecond + -- (formatShow iso8601Format est) + +-- execLocal +-- :: (Logger logger, CanReadablePayloadCas tbl) +-- => Pact.Transaction +-- -> Maybe LocalPreflightSimulation +-- -- ^ preflight flag +-- -> Maybe LocalSignatureVerification +-- -- ^ turn off signature verification checks? +-- -> Maybe RewindDepth +-- -- ^ rewind depth +-- -> PactServiceM logger tbl LocalResult +-- execLocal cwtx preflight sigVerify rdepth = pactLabel "execLocal" $ do + +-- e@PactServiceEnv{..} <- ask + +-- let !cmd = Pact4.payloadObj <$> cwtx +-- !pm = Pact4.publicMetaOf cmd +-- !v = _chainwebVersion e +-- !cid = _chainId e + +-- bhdb <- view psBlockHeaderDb + +-- -- when no depth is defined, treat +-- -- withCheckpointerRewind as readFrom +-- -- (i.e. setting rewind to 0). +-- let rewindDepth = maybe 0 _rewindDepth rdepth + +-- let timeoutLimit +-- | _psEnableLocalTimeout = Just (2 * 1_000_000) +-- | otherwise = Nothing + +-- let doLocal = Checkpointer.readFromNthParent (fromIntegral rewindDepth) $ do +-- ph <- view psParentHeader +-- let pact5RequestKey = Pact.RequestKey (Pact.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) +-- evalContT $ withEarlyReturn $ \earlyReturn -> do +-- pact5Cmd <- case Pact.parsePact4Command cwtx of +-- Left (fmap Pact.spanInfoToLineInfo -> parseError) -> +-- earlyReturn $ Pact5LocalResultLegacy Pact.CommandResult +-- { _crReqKey = pact5RequestKey +-- , _crTxId = Nothing +-- , _crResult = Pact.PactResultErr $ +-- Pact.pactErrorToOnChainError parseError +-- , _crGas = Pact.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit +-- , _crLogs = Nothing +-- , _crContinuation = Nothing +-- , _crMetaData = Nothing +-- , _crEvents = [] +-- } +-- Right pact5Cmd -> return pact5Cmd + +-- -- this is just one of our metadata validation passes. +-- -- in preflight, we do another one, which replicates some of this work; +-- -- TODO: unify preflight, newblock, and validateblock tx metadata validation +-- case (preflight, sigVerify) of +-- (_, Just NoVerify) -> do +-- let payloadBS = SB.fromShort (Pact4._cmdPayload $ Pact4.payloadBytes <$> cwtx) +-- let validated = Pact.verifyHash (Pact._cmdHash pact5Cmd) payloadBS +-- case validated of +-- Left err -> earlyReturn $ +-- review _MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err +-- Right _ -> return () +-- _ -> do +-- let validated = Pact.assertCommand pact5Cmd +-- case validated of +-- Left err -> earlyReturn $ +-- review _MetadataValidationFailure (pure $ displayAssertCommandError err) +-- Right () -> return () + +-- let txCtx = Pact.BlockCtx ph noMiner +-- let spvSupport = Pact.pactSPV bhdb (_parentHeader ph) +-- case preflight of +-- Just PreflightSimulation -> do +-- -- preflight needs to do additional checks on the metadata +-- -- to match on-chain tx validation +-- lift (Pact.liftPactServiceM (Pact.assertPreflightMetadata (view Pact.payloadObj <$> pact5Cmd) txCtx sigVerify)) >>= \case +-- Left err -> earlyReturn $ review _MetadataValidationFailure err +-- Right () -> return () +-- let initialGas = Pact.initialGasOf $ Pact._cmdPayload pact5Cmd +-- applyCmdResult <- lift $ Pact.pactTransaction Nothing (\dbEnv -> +-- Pact.applyCmd +-- _psLogger _psGasLogger dbEnv +-- txCtx (TxBlockIdx 0) spvSupport initialGas (view Pact.payloadObj <$> pact5Cmd) +-- ) +-- commandResult <- case applyCmdResult of +-- Left err -> +-- earlyReturn $ Pact5LocalResultWithWarns Pact.CommandResult +-- { _crReqKey = Pact.RequestKey (Pact.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) +-- , _crTxId = Nothing +-- , _crResult = Pact.PactResultErr $ +-- Pact.PactOnChainError +-- -- the only legal error type, once chainweaver is really gone, we +-- -- can use a real error type +-- (Pact.ErrorType "EvalError") +-- (Pact.mkBoundedText $ prettyPact5GasPurchaseFailure err) +-- (Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin Pact.noInfo) +-- , _crGas = Pact.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit +-- , _crLogs = Nothing +-- , _crContinuation = Nothing +-- , _crMetaData = Nothing +-- , _crEvents = [] +-- } +-- [] +-- Right commandResult -> return commandResult +-- let pact5Pm = pact5Cmd ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta +-- let metadata = J.toJsonViaEncode $ Pact.StableEncoding $ Pact.ctxToPublicData pact5Pm txCtx +-- let commandResult' = hashPact5TxLogs $ set Pact.crMetaData (Just metadata) commandResult +-- -- TODO: once Pact 5 has warnings, include them here. +-- pure $ Pact5LocalResultWithWarns +-- (Pact.pactErrorToOnChainError <$> commandResult') +-- [] +-- _ -> lift $ do +-- -- default is legacy mode: use applyLocal, don't buy gas, don't do any +-- -- metadata checks beyond signature and hash checking +-- cr <- Pact.pactTransaction Nothing $ \dbEnv -> do +-- fmap Pact.pactErrorToOnChainError <$> Pact.applyLocal _psLogger _psGasLogger dbEnv txCtx spvSupport (view Pact.payloadObj <$> pact5Cmd) +-- pure $ Pact5LocalResultLegacy (hashPact5TxLogs cr) + +-- case timeoutLimit of +-- Nothing -> doLocal +-- Just limit -> withPactState $ \run -> timeoutYield limit (run doLocal) >>= \case +-- Just r -> pure r +-- Nothing -> do +-- logError_ _psLogger $ "Local action timed out for cwtx:\n" <> sshow cwtx +-- pure $ review _LocalTimeout () + +-- execSyncToBlock +-- :: (CanReadablePayloadCas tbl, Logger logger) +-- => BlockHeader +-- -> PactServiceM logger tbl () +-- execSyncToBlock targetHeader = pactLabel "execSyncToBlock" $ do +-- latestHeader <- Checkpointer.findLatestValidBlockHeader' >>= maybe failNonGenesisOnEmptyDb return +-- if latestHeader == targetHeader +-- then do +-- logInfoPact $ "checkpointer at checkpointer target" +-- <> ". target height: " <> sshow (view blockHeight latestHeader) +-- <> "; target hash: " <> blockHashToText (view blockHash latestHeader) +-- else do +-- logInfoPact $ "rewind to checkpointer target" +-- <> ". current height: " <> sshow (view blockHeight latestHeader) +-- <> "; current hash: " <> blockHashToText (view blockHash latestHeader) +-- <> "; target height: " <> sshow targetHeight +-- <> "; target hash: " <> blockHashToText targetHash +-- Checkpointer.rewindToIncremental Nothing (ParentHeader targetHeader) +-- where +-- targetHeight = view blockHeight targetHeader +-- targetHash = view blockHash targetHeader +-- failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" -- | Validate a mined block `(headerToValidate, payloadToValidate). -- Note: The BlockHeader here is the header of the block being validated. @@ -801,78 +717,108 @@ execSyncToBlock targetHeader = pactLabel "execSyncToBlock" $ do -- - commit the result to the database. -- execValidateBlock - :: (CanReadablePayloadCas tbl, Logger logger) + :: forall tbl logger + . (CanReadablePayloadCas tbl, Logger logger) => MemPoolAccess -> Maybe Hints -> ForkInfo - -> PactServiceM logger tbl SyncState -execValidateBlock memPoolAccess hints forkInfo = pactLabel "execValidateBlock" $ do - bhdb <- view psBlockHeaderDb - payloadDb <- view psPdb - v <- view chainwebVersion - cid <- view chainId - RewindLimit reorgLimit <- view psReorgLimit - - currHeader <- Checkpointer.findLatestValidBlockHeader' >>= \case - -- TODO: deal with genesis - Nothing -> error "no latest header... what do we do?" - Just h -> return h - - let findForkPoint (tip:chain) = do - known <- Checkpointer.lookupBlock (_evaluationCtxRankedParentHash tip) - if known - then return (Just (tip, chain)) - else findForkPoint chain - findForkPoint [] = return Nothing - - atTarget <- Checkpointer.lookupBlock (_forkInfoBaseRankedBlockHash forkInfo) - if atTarget - then return $ forkInfo._forkInfoTargetState._consensusStateLatest - else do - let wholeChainTopToBottom = - reverse forkInfo._forkInfoTrace - findForkPoint wholeChainTopToBottom >>= \case - Nothing -> - return $ syncStateOfBlockHeader currHeader - Just (forkPoint, _) | _evaluationCtxRankedParentHash forkPoint == _rankedBlockHash currHeader -> - -- we're already done, find the payload with outputs and return - return forkInfo._forkInfoTargetState._consensusStateLatest - Just (forkPoint, reverse -> forkChainBottomToTop) -> do -- it - pdb <- view psPdb - unknowns' <- liftIO $ dropWhile (isJust . snd) . zip forkChainBottomToTop - <$> tableLookupBatch pdb (_evaluationCtxRankedPayloadHash <$> forkChainBottomToTop) - - -- assert db invariant - unless (all (isNothing . snd) unknowns') $ - error "Chainweb.PayloadProviders.Pact.syncToBlock: detected corrupted payload database" - - let unknowns = fst <$> unknowns' - - -- logDebug_ $ "unknown blocks in context: " <> sshow (length unknowns) - - -- fetch all unkown payloads - -- - -- FIXME do the right thing here. Ideally, fetch all - -- unknowns in batches without redundant local lookups. Then - -- validate all payloads together before sending them to the - -- EVM and inserting them into the DB. - -- - - plds <- forM unknowns $ \ctx -> do - pld <- getPayloadForContext hints ctx - return (ctx, pld) - - withPactState $ \runPact -> do - let runForkBlockHeaders = plds - & traverse Stream.yield - & Stream.map (\(ctx, payload) -> do - return $ SomeBlockM $ Pair - (void $ Pact4.execBlock forkBh (CheckablePayload payload)) - (void $ Pact5.execExistingBlock forkBh (CheckablePayload payload)) - ) - undefined - - undefined + -> PactServiceM logger tbl ConsensusState +execValidateBlock memPoolAccess hints forkInfo = do + sql <- view psReadWriteSql + pdb <- view psPdb + (rewoundTxs, validatedTxs, newConsensusState) <- withSavepoint sql ValidateBlockSavePoint $ do -- pactLabel "execValidateBlock" $ do + let + findForkChain (tip:chain) = go (NEL.singleton tip) chain + findForkChain [] = return Nothing + go !acc (tip:chain) = do + -- note that if we see the "parent hash" in the checkpointer, + -- that means that the *child* has been evaluated, thus we do + -- not include `tip` in the resulting list. + known <- Checkpointer.lookupParentBlockRanked (Ranked (_evaluationCtxCurrentHeight tip) (_evaluationCtxParentHash tip)) + if known + then return $ Just acc + -- if we don't know this block, remember it for later as we'll + -- need to execute it + else go (tip `NEL.cons` acc) chain + go _acc [] = return Nothing + + pactConsensusState <- Checkpointer.getConsensusState + let atTarget = _syncStateRankedBlockHash (_consensusStateLatest pactConsensusState) == _forkInfoBaseRankedBlockHash forkInfo + -- check if some past block had the target as its parent; if so, that + -- means we can rewind to it + latestBlockRewindable <- + Checkpointer.lookupParentBlockHash (Parent $ _syncStateBlockHash (_consensusStateLatest pactConsensusState)) + if atTarget + then do + -- no work to do at all except set consensus state + -- TODO PP: disallow rewinding final? + logDebugPact $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState + Checkpointer.setConsensusState forkInfo._forkInfoTargetState + return (mempty, mempty, forkInfo._forkInfoTargetState) + else if latestBlockRewindable + then do + -- we just have to rewind and set the final + safe blocks + -- TODO PP: disallow rewinding final? + logDebugPact $ "pure rewind to " <> sshow forkInfo._forkInfoTargetState + rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) + Checkpointer.rewindTo (_syncStateRankedBlockHash (_consensusStateLatest pactConsensusState)) + Checkpointer.setConsensusState forkInfo._forkInfoTargetState + return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) + else do + logDebugPact $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState + findForkChain forkInfo._forkInfoTrace >>= \case + Nothing -> do + logErrorPact $ "impossible to move to " <> sshow forkInfo._forkInfoTargetState + -- error: we have no way to get to the target block. just report + -- our current state and do nothing else. + return (mempty, mempty, pactConsensusState) + Just forkChainBottomToTop -> do + rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) + -- the happy case: we can find a way to get to the target block + -- look up all of the payloads to see if we've run them before + -- even then we still have to run them, because they aren't in the checkpointer + knownPayloads <- liftIO $ + tableLookupBatch' pdb (each . _2) ((\e -> (e, _evaluationCtxRankedPayloadHash e)) <$> forkChainBottomToTop) + + logDebugPact $ "unknown blocks in context: " <> sshow (length $ NEL.filter (isNothing . snd) knownPayloads) + + runnableBlocks <- forM knownPayloads $ \(evalCtx, maybePayload) -> do + payload <- case maybePayload of + -- fetch payload if missing + Nothing -> getPayloadForContext hints evalCtx + Just payload -> return payload + let runBlock = Pact.execExistingBlock (_consensusPayloadHash <$> evalCtx) (CheckablePayload payload) + return + ( DList.singleton <$> runBlock + , _consensusPayloadHash <$> evalCtx + ) + + runExceptT (Checkpointer.restoreAndSave runnableBlocks) >>= \case + Left err -> do + logErrorPact $ "Error in execValidateBlock: " <> sshow err + return (mempty, mempty, pactConsensusState) + Right blockResults -> do + let validatedTxHashes = V.concatMap + (fmap pactRequestKeyToTransactionHash . view _3) + (V.fromList $ DList.toList blockResults) + Checkpointer.setConsensusState forkInfo._forkInfoTargetState + return (rewoundTxs, validatedTxHashes, forkInfo._forkInfoTargetState) + liftIO $ mpaProcessFork memPoolAccess (rewoundTxs, validatedTxs) + return newConsensusState + where + -- remember to call this *before* executing the actual rewind, + -- and only alter the mempool *after* the db transaction is done. + getRewoundTxs :: Parent BlockHeight -> PactServiceM logger tbl (Vector Pact.Transaction) + getRewoundTxs rewindTargetHeight = do + pdb <- view psPdb + rewoundPayloadHashes <- Checkpointer.getPayloadsAfter rewindTargetHeight + rewoundPayloads <- liftIO $ fmap fromJuste <$> + lookupPayloadDataWithHeightBatch + (_payloadStoreTable pdb) + [(Just (rank rbph), _ranked rbph) | rbph <- rewoundPayloadHashes] + V.concat <$> traverse + (fmap (fromRight (error "invalid payload in database")) . runExceptT . pact5TransactionsFromPayload) + rewoundPayloads -- -- The parent block header must be available in the block header database. -- parentOfHeaderToValidate <- getTarget @@ -925,7 +871,7 @@ execValidateBlock memPoolAccess hints forkInfo = pactLabel "execValidateBlock" $ -- Just x -> return $ payloadWithOutputsToPayloadData x -- SomeBlockM $ Pair -- (void $ Pact4.execBlock forkBh (CheckablePayload payload)) - -- (void $ Pact5.execExistingBlock forkBh (CheckablePayload payload)) + -- (void $ Pact.execExistingBlock forkBh (CheckablePayload payload)) -- return ([], forkBh) -- ) @@ -937,8 +883,8 @@ execValidateBlock memPoolAccess hints forkInfo = pactLabel "execValidateBlock" $ -- return ([output], headerToValidate) -- ) -- (do - -- !(gas, pwo) <- Pact5.execExistingBlock headerToValidate payloadToValidate - -- return ([(fromIntegral (Pact5._gas gas), pwo)], headerToValidate) + -- !(gas, pwo) <- Pact.execExistingBlock headerToValidate payloadToValidate + -- return ([(fromIntegral (Pact._gas gas), pwo)], headerToValidate) -- ) -- -- here we rewind to the common ancestor block, run the @@ -977,21 +923,21 @@ execValidateBlock memPoolAccess hints forkInfo = pactLabel "execValidateBlock" $ -- return (result, totalGasUsed) - getPayloadForContext - :: Logger logger + :: CanReadablePayloadCas tbl + => Logger logger => Maybe Hints - -> EvaluationCtx + -> EvaluationCtx ConsensusPayload -> PactServiceM logger tbl PayloadData getPayloadForContext h ctx = do pdb <- view psPdb candPdb <- view psCandidatePdb - mapM_ (insertPayloadData candPdb) (_evaluationCtxPayloadData ctx) + mapM_ (insertPayloadData candPdb) (_consensusPayloadData $ _evaluationCtxPayload ctx) pld <- liftIO $ getPayload pdb candPdb - (Priority $ negate $ int $ _evaluationCtxParentHeight ctx) + (Priority $ negate $ int $ _evaluationCtxCurrentHeight ctx) (_hintsOrigin <$> h) (_evaluationCtxRankedPayloadHash ctx) liftIO $ tableInsert candPdb rh pld @@ -1004,122 +950,80 @@ getPayloadForContext h ctx = do Left e -> do logWarnPact $ "failed to decode encoded payload from evaluation ctx: " <> sshow e - -execBlockTxHistory - :: Logger logger - => BlockHeader - -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info - -> PactServiceM logger tbl (Historical BlockTxHistory) -execBlockTxHistory bh d = - pactLabel "execBlockTxHistory" $ Checkpointer.getBlockHistory bh d - -execHistoricalLookup - :: Logger logger - => BlockHeader - -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info - -> Pact5.RowKey - -> PactServiceM logger tbl (Historical (Maybe (Pact5.TxLog Pact5.RowData))) -execHistoricalLookup bh d k = - pactLabel "execHistoricalLookup" $ Checkpointer.lookupHistorical bh d k - -execPreInsertCheckReq - :: (CanReadablePayloadCas tbl, Logger logger) - => Vector Pact4.UnparsedTransaction - -> PactServiceM logger tbl (Vector (Maybe Mempool.InsertError)) -execPreInsertCheckReq txs = pactLabel "execPreInsertCheckReq" $ do - let requestKeys = V.map Pact4.cmdToRequestKey txs - logInfoPact $ "(request keys = " <> sshow requestKeys <> ")" - psEnv <- ask - psState <- get - logger <- view psLogger - let timeoutLimit = fromIntegral $ (\(Micros n) -> n) $ _psPreInsertCheckTimeout psEnv - let act = Checkpointer.readFromLatest $ SomeBlockM $ Pair - (do - pdb <- view psBlockDbEnv - pc <- view psParentHeader - let - parentTime = ParentCreationTime (view blockCreationTime $ _parentHeader pc) - currHeight = succ $ view blockHeight $ _parentHeader pc - v = _chainwebVersion pc - cid = _chainId pc - liftIO $ forM txs $ \tx -> do - let isGenesis = False - fmap (either Just (\_ -> Nothing)) $ runExceptT $ do - parsedTx <- Pact4.validateRawChainwebTx - logger v cid pdb parentTime currHeight tx - ExceptT $ evalPactServiceM psState psEnv . Pact4.runPactBlockM pc isGenesis pdb - $ attemptBuyGasPact4 noMiner parsedTx - return parsedTx - ) - (do - db <- view psBlockDbEnv - ph <- view psParentHeader - v <- view chainwebVersion - cid <- view chainId - initialBlockHandle <- use Pact5.pbBlockHandle - let - parentTime = ParentCreationTime (view blockCreationTime $ _parentHeader ph) - currHeight = succ $ view blockHeight $ _parentHeader ph - isGenesis = False - forM txs $ \tx -> - fmap (either Just (\_ -> Nothing)) $ runExceptT $ do - -- it's safe to use initialBlockHandle here because it's - -- only used to check for duplicate pending txs in a block - pact5Tx <- mapExceptT liftIO $ Pact5.validateRawChainwebTx - logger v cid db initialBlockHandle parentTime currHeight isGenesis tx - attemptBuyGasPact5 logger ph noMiner pact5Tx - ) - withPactState $ \run -> - timeoutYield timeoutLimit (run act) >>= \case - Just r -> do - logDebug_ logger $ "Mempool pre-insert check result: " <> sshow r - pure r - Nothing -> do - logError_ logger $ "Mempool pre-insert check timed out for txs:\n" <> sshow txs - let result = V.map (const $ Just Mempool.InsertErrorTimedOut) txs - logDebug_ logger $ "Mempool pre-insert check result: " <> sshow result - pure result - - where - attemptBuyGas - :: (Logger logger) - => logger - -> ParentHeader - -> Miner - -> Pact5.Transaction - -> ExceptT InsertError (Pact5.PactBlockM logger tbl) () - attemptBuyGas logger ph miner tx = do - let logger' = addLabel ("transaction", "attemptBuyGas") logger - result <- lift $ Pact5.pactTransaction Nothing $ \pactDb -> do - let txCtx = Pact5.TxContext ph miner - -- Note: `mempty` is fine here for the milligas limit. `buyGas` sets its own limit - -- by necessity - gasEnv <- Pact5.mkTableGasEnv (Pact5.MilliGasLimit mempty) Pact5.GasLogsDisabled - Pact5.buyGas logger' gasEnv pactDb txCtx (view Pact5.payloadObj <$> tx) - case result of - Left err -> do - throwError $ InsertErrorBuyGas $ prettyPact5GasPurchaseFailure $ BuyGasError (Pact5.cmdToRequestKey tx) err - Right (_ :: Pact5.EvalResult) -> return () +-- execPreInsertCheckReq +-- :: (CanReadablePayloadCas tbl, Logger logger) +-- => Vector Pact.Transaction +-- -> PactServiceM logger tbl (Vector (Maybe Mempool.InsertError)) +-- execPreInsertCheckReq txs = pactLabel "execPreInsertCheckReq" $ do +-- let requestKeys = V.map Pact.cmdToRequestKey txs +-- logInfoPact $ "(request keys = " <> sshow requestKeys <> ")" +-- psEnv <- ask +-- psState <- get +-- logger <- view psLogger +-- let timeoutLimit = fromIntegral $ (\(Micros n) -> n) $ _psPreInsertCheckTimeout psEnv +-- let act = Checkpointer.readFromLatest $ do +-- db <- view psBlockDbEnv +-- ph <- view psParentHeader +-- v <- view chainwebVersion +-- cid <- view chainId +-- initialBlockHandle <- use Pact.pbBlockHandle +-- let +-- parentTime = ParentCreationTime (view blockCreationTime $ _parentHeader ph) +-- currHeight = succ $ view blockHeight $ _parentHeader ph +-- isGenesis = False +-- forM txs $ \tx -> +-- fmap (either Just (\_ -> Nothing)) $ runExceptT $ do +-- -- it's safe to use initialBlockHandle here because it's +-- -- only used to check for duplicate pending txs in a block +-- pact5Tx <- mapExceptT liftIO $ Pact.validateParsedChainwebTx +-- logger v cid db initialBlockHandle parentTime currHeight isGenesis tx +-- attemptBuyGasPact5 logger ph noMiner pact5Tx +-- withPactState $ \run -> +-- timeoutYield timeoutLimit (run act) >>= \case +-- Just r -> do +-- logDebug_ logger $ "Mempool pre-insert check result: " <> sshow r +-- pure r +-- Nothing -> do +-- logError_ logger $ "Mempool pre-insert check timed out for txs:\n" <> sshow txs +-- let result = V.map (const $ Just Mempool.InsertErrorTimedOut) txs +-- logDebug_ logger $ "Mempool pre-insert check result: " <> sshow result +-- pure result + +-- where +-- attemptBuyGas +-- :: (Logger logger) +-- => logger +-- -> BlockCtx +-- -> Miner +-- -> Pact.Transaction +-- -> ExceptT InsertError (Pact.PactBlockM logger tbl) () +-- attemptBuyGas logger ph miner tx = do +-- let logger' = addLabel ("transaction", "attemptBuyGas") logger +-- result <- lift $ Pact.pactTransaction Nothing $ \pactDb -> do +-- let txCtx = Pact.BlockCtx ph miner +-- -- Note: `mempty` is fine here for the milligas limit. `buyGas` sets its own limit +-- -- by necessity +-- gasEnv <- Pact.mkTableGasEnv (Pact.MilliGasLimit mempty) Pact.GasLogsDisabled +-- Pact.buyGas logger' gasEnv pactDb txCtx (view Pact.payloadObj <$> tx) +-- case result of +-- Left err -> do +-- throwError $ InsertErrorBuyGas $ prettyPact5GasPurchaseFailure $ BuyGasError (Pact.cmdToRequestKey tx) err +-- Right (_ :: Pact.EvalResult) -> return () execLookupPactTxs :: (CanReadablePayloadCas tbl, Logger logger) => Maybe ConfirmationDepth -> Vector SB.ShortByteString - -> PactServiceM logger tbl (HM.HashMap SB.ShortByteString (T2 BlockHeight BlockHash)) -execLookupPactTxs confDepth txs = pactLabel "execLookupPactTxs" $ do - if V.null txs then return mempty else go + -> PactServiceM logger tbl (Historical (HM.HashMap SB.ShortByteString (T2 BlockHeight BlockHash))) +execLookupPactTxs confDepth txs = do -- pactLabel "execLookupPactTxs" $ do + if V.null txs then return (Historical mempty) else go where go = Checkpointer.readFromNthParent (maybe 0 (fromIntegral . _confirmationDepth) confDepth) $ - SomeBlockM $ Pair - (do - dbenv <- view psBlockDbEnv - fmap (HM.mapKeys coerce) $ liftIO $ Pact4._cpLookupProcessedTx dbenv (coerce txs) - ) (do dbenv <- view psBlockDbEnv - fmap (HM.mapKeys coerce) $ liftIO $ Pact5.lookupPactTransactions dbenv (coerce txs) + fmap (HM.mapKeys coerce) $ liftIO $ Pact.lookupPactTransactions dbenv (coerce txs) ) -pactLabel :: (Logger logger) => Text -> PactServiceM logger tbl x -> PactServiceM logger tbl x -pactLabel lbl x = localLabelPact ("pact-request", lbl) x +-- pactLabel :: (Logger logger) => Text -> PactServiceM logger tbl x -> PactServiceM logger tbl x +-- pactLabel lbl x = localLabelPact ("pact-request", lbl) x diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 741c222234..b7abcef7df 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -16,6 +16,11 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DeriveFoldable #-} +{-# LANGUAGE DeriveTraversable #-} -- | -- Module: Chainweb.Pact.PactService.Checkpointer @@ -31,86 +36,55 @@ module Chainweb.Pact.PactService.Checkpointer , readFromNthParent , readFrom , restoreAndSave - , findLatestValidBlockHeader' - , findLatestValidBlockHeader - , exitOnRewindLimitExceeded - , rewindToIncremental + , rewindTo + , getBlockCtx + -- , findLatestValidBlockHeader' + -- , findLatestValidBlockHeader + -- , exitOnRewindLimitExceeded , PactBlockM(..) , getEarliestBlock - , getLatestBlock - , lookupBlock - , lookupHistorical - , getBlockHistory - , Internal.withCheckpointerResources + -- , lookupBlock + , lookupParentBlockRanked + , lookupParentBlockHash + , lookupBlockWithHeight + , getPayloadsAfter + , getConsensusState + , setConsensusState ) where -import Control.Concurrent -import Control.Concurrent.Async -import Control.Lens hiding ((:>)) +import Control.Lens hiding ((:>), (:<)) import Control.Monad import Control.Monad.Catch +import Control.Monad.Except import Control.Monad.Reader -import Control.Monad.State - -import Data.Functor.Product -import Data.IORef +import Data.List.NonEmpty (NonEmpty ((:|))) +import qualified Data.List.NonEmpty as NEL import Data.Maybe -import Data.Monoid hiding (Product(..)) -import Data.Text (Text) - +import Data.Monoid hiding (Product (..)) import GHC.Stack - -import qualified Pact.JSON.Encode as J - import Prelude hiding (lookup) -import Streaming (Stream, Of(..)) -import qualified Streaming as S -import qualified Streaming.Prelude as S - --- internal modules +import qualified Pact.Core.Persistence.Types as Pact +import Chainweb.BlockCreationTime +import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger - -import qualified Chainweb.Pact.PactService.ExecBlock as Pact +import Chainweb.MinerReward +import qualified Chainweb.Pact.Backend.ChainwebPactDb as ChainwebPactDb +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils +import qualified Chainweb.Pact.Backend.Utils as PactDb import Chainweb.Pact.Types -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.TreeDB (getBranchIncreasing, forkEntry, lookup, seekAncestor) +import qualified Chainweb.Pact.Types as Pact +import Chainweb.Parent +import Chainweb.PayloadProvider +import Chainweb.Ranked +import Chainweb.Time import Chainweb.Utils hiding (check) import Chainweb.Version -import qualified Chainweb.Pact.Types as Pact4 -import qualified Chainweb.Pact.Types as Pact5 import Chainweb.Version.Guards (pact5) -import qualified Chainweb.Pact.PactService.Checkpointer.Internal as Internal -import Chainweb.BlockHash -import qualified Pact.Core.Names as Pact5 -import qualified Pact.Core.Persistence.Types as Pact5 -import qualified Pact.Core.Builtin as Pact5 -import qualified Pact.Core.Evaluate as Pact5 - -exitOnRewindLimitExceeded :: PactServiceM logger tbl a -> PactServiceM logger tbl a -exitOnRewindLimitExceeded = handle $ \case - e@RewindLimitExceeded{..} -> do - killFunction <- asks (\x -> _psOnFatalError x) - liftIO $ killFunction e (J.encodeText $ msg _rewindExceededLimit _rewindExceededLast _rewindExceededTarget) - e -> throwM e - where - msg (RewindLimit limit) lastHeader targetHeader = J.object - [ "details" J..= J.object - [ "limit" J..= J.number (fromIntegral limit) - , "last" J..= fmap (J.text . blockHeaderShortDescription) lastHeader - , "target" J..= fmap (J.text . blockHeaderShortDescription) targetHeader - ] - , "message" J..= J.text "Your node is part of a losing fork longer than your\ - \ reorg-limit, which is a situation that requires manual\ - \ intervention.\ - \ For information on recovering from this, please consult:\ - \ https://github.com/kadena-io/chainweb-node/blob/master/\ - \ docs/RecoveringFromDeepForks.md" - ] -- read-only rewind to the latest block. -- note: because there is a race between getting the latest header @@ -122,7 +96,9 @@ readFromLatest :: Logger logger => PactBlockM logger tbl a -> PactServiceM logger tbl a -readFromLatest doRead = readFromNthParent 0 doRead +readFromLatest doRead = readFromNthParent 0 doRead >>= \case + NoHistory -> error "readFromLatest: failed to grab the latest header, this is a bug in chainweb" + Historical a -> return a -- read-only rewind to the nth parent before the latest block. -- note: this function will never rewind before genesis. @@ -131,146 +107,176 @@ readFromNthParent . Logger logger => Word -> PactBlockM logger tbl a - -> PactServiceM logger tbl a -readFromNthParent n doRead = go 0 - where - go :: Int -> PactServiceM logger tbl a - go retryCount = do - bhdb <- view psBlockHeaderDb - ParentHeader latest <- findLatestValidBlockHeader - v <- view chainwebVersion - cid <- view chainId - let parentHeight = - -- guarantees that the subtraction doesn't overflow - -- this will never give us before genesis - fromIntegral $ max (genesisHeight v cid + fromIntegral n) (view blockHeight latest) - fromIntegral n - nthParent <- liftIO $ - seekAncestor bhdb latest parentHeight >>= \case - Nothing -> internalError - $ "readFromNthParent: Failed to lookup nth ancestor, block " <> sshow latest - <> ", depth " <> sshow n - Just nthParentHeader -> - return $ ParentHeader nthParentHeader - readFrom (Just nthParent) doRead >>= \case - -- note: because there is a race between getting the nth header - -- and doing the rewind, there's a chance that the nth header - -- will be unavailable when we do the rewind. in that case - -- we just keep grabbing the new "nth header" until we succeed. - NoHistory - | retryCount < 10 -> - go (retryCount + 1) - | otherwise -> internalError "readFromNthParent: failed after 10 retries" - Historical r -> return r + -> PactServiceM logger tbl (Historical a) +readFromNthParent n doRead = do + sql <- view psReadWriteSql + v <- view chainwebVersion + cid <- view chainId + withSavepoint sql ReadFromNSavepoint $ do + latest <- _consensusStateLatest <$> getConsensusState + if genesisHeight v cid + fromIntegral @Word @BlockHeight n < _syncStateHeight latest + then return NoHistory + else do + maybeNthBlock <- lookupBlockWithHeight + (_syncStateHeight latest - fromIntegral @Word @BlockHeight n) + case maybeNthBlock of + -- this case for shallow nodes without enough history + Nothing -> return NoHistory + Just nthBlock -> + readFrom (parentBlockHeight v cid nthBlock) doRead -- read-only rewind to a target block. -- if that target block is missing, return Nothing. readFrom :: Logger logger - => Maybe ParentHeader + => Parent RankedBlockHash -> PactBlockM logger tbl a -> PactServiceM logger tbl (Historical a) -readFrom ph doRead = do - cp <- view psCheckpointer - pactParent <- getPactParent ph - let bh = _parentHeader <$> ph - s <- get - e <- ask +readFrom parent doRead = do + sql <- view psReadWriteSql + logger <- view psLogger v <- view chainwebVersion cid <- view chainId - let currentHeight = maybe (genesisHeight v cid) (succ . view blockHeight) bh - let execPact4 act = - liftIO $ Internal.readFrom cp ph Pact4T $ \dbenv _ -> - evalPactServiceM s e $ - Pact4.runPactBlockM pactParent (isNothing ph) dbenv act - let execPact5 act = - liftIO $ Internal.readFrom cp ph Pact5T $ \dbenv blockHandle -> - evalPactServiceM s e $ do - fst <$> Pact5.runPactBlockM pactParent (isNothing ph) dbenv blockHandle act - case doRead of - PactBlockM (Pair forPact4 forPact5) - | pact5 v cid currentHeight -> execPact5 forPact5 - | otherwise -> execPact4 forPact4 - --- here we cheat, making the genesis block header's parent the genesis --- block header, only for Pact's information, *not* for the checkpointer; --- the checkpointer knows the difference between rewinding to before the --- genesis block and rewinding to just after the genesis block. -getPactParent :: Maybe ParentHeader -> PactServiceM logger tbl ParentHeader -getPactParent ph = do - case ph of - Nothing -> do - bh <- genesisBlockHeader <$> view chainwebVersion <*> view chainId - return (ParentHeader bh) - Just h -> return h - --- play multiple blocks starting at a given parent header. -restoreAndSave - :: (CanReadablePayloadCas tbl, Logger logger, Monoid q) - => Maybe ParentHeader - -> Stream (Of (PactBlockM logger tbl (q, BlockHeader))) IO r - -> PactServiceM logger tbl (r, q) -restoreAndSave ph blocks = do - cp <- view psCheckpointer + fakeCreationTime <- liftIO $ Parent . BlockCreationTime <$> getCurrentTimeIntegral + let blockCtx = BlockCtx + -- fake, we can't access this field without a block header db + { _bctxParentCreationTime = fakeCreationTime + , _bctxParentHash = _rankedBlockHashHash <$> parent + , _bctxParentHeight = _rankedBlockHashHeight <$> parent + -- fake, we can't access this field without a block header db + , _bctxMinerReward = MinerReward 1848 + , _bctxChainwebVersion = v + , _bctxChainId = cid + } + e <- ask + liftIO $ withSavepoint sql ReadFromSavepoint $ do + latestHeader <- _syncStateRankedBlockHash . _consensusStateLatest <$> + ChainwebPactDb.throwOnDbError (ChainwebPactDb.getConsensusState sql) + -- is the parent the latest header, i.e., can we get away without rewinding? + let parentIsLatestHeader = latestHeader == unwrapParent parent + let currentHeight = _bctxCurrentBlockHeight blockCtx + if pact5 v cid currentHeight + then PactDb.getEndTxId v cid "doReadFrom" sql parent >>= traverse \startTxId -> do + let + blockHandlerEnv = ChainwebPactDb.BlockHandlerEnv + { ChainwebPactDb._blockHandlerDb = sql + , ChainwebPactDb._blockHandlerLogger = logger + , ChainwebPactDb._blockHandlerVersion = v + , ChainwebPactDb._blockHandlerChainId = cid + , ChainwebPactDb._blockHandlerBlockHeight = currentHeight + , ChainwebPactDb._blockHandlerMode = Pact.Transactional + , ChainwebPactDb._blockHandlerUpperBoundTxId = startTxId + , ChainwebPactDb._blockHandlerAtTip = parentIsLatestHeader + } + let pactDb = ChainwebPactDb.chainwebPactBlockDb blockHandlerEnv + fmap fst $ + runPactServiceM e $ + Pact.runPactBlockM blockCtx pactDb (emptyBlockHandle startTxId) doRead + else error "Pact 4 blocks are not playable anymore" + +-- the special case where one doesn't want to extend the chain, just rewind it. +rewindTo :: Logger logger => RankedBlockHash -> PactServiceM logger tbl () +rewindTo ancestor = do + sql <- view psReadWriteSql v <- view chainwebVersion cid <- view chainId - -- the height of the first block in the stream - let firstBlockHeight = case ph of - Nothing -> genesisHeight v cid - Just (ParentHeader bh) -> succ (bh ^. blockHeight) - withPactState $ \runPact -> - Internal.restoreAndSave cp ph - $ blocks & S.zip (S.iterate succ firstBlockHeight) & S.map - (\case - (height, PactBlockM (Pair pact4Block pact5Block)) - | pact5 v cid height -> - Pact5RunnableBlock $ \dbEnv mph blockHandle -> runPact $ do - pactParent <- getPactParent mph - let isGenesis = isNothing mph - Pact5.runPactBlockM pactParent isGenesis dbEnv blockHandle pact5Block - | otherwise -> - Pact4RunnableBlock $ \dbEnv mph -> runPact $ do - pactParent <- getPactParent mph - let isGenesis = isNothing mph - Pact4.runPactBlockM pactParent isGenesis dbEnv pact4Block - ) - --- | Find the latest block stored in the checkpointer for which the respective --- block header is available in the block header database. A block header may be in --- the checkpointer but not in the block header database during deep catch-up --- or reorgs. Why? It appears to be a race, where a thread's insert into rocksdb --- may not be seen by another thread immediately. --- --- First the result of '_cpGetLatestBlock' is checked. If the respective block --- header isn't available, the function recursively checks the result of --- '_cpGetBlockParent'. + liftIO $ fmap fst $ generalBracket + (beginSavepoint sql RewindSavePoint) + (\_ -> \case + ExitCaseSuccess {} -> commitSavepoint sql RewindSavePoint + _ -> abortSavepoint sql RewindSavePoint + ) $ \_ -> do + _ <- PactDb.rewindDbTo v cid sql ancestor + return () + +getBlockCtx :: EvaluationCtx p -> PactServiceM logger tbl BlockCtx +getBlockCtx ec = do + cid <- view chainId + v <- view chainwebVersion + return $! blockCtxOfEvaluationCtx v cid ec + +-- TODO: log more? +-- | Given a list of blocks in ascending order, rewind to the first +-- of them and play all of the subsequent blocks, saving the result persistently. +-- this function takes care of making sure that this is done *atomically*. +-- The two following expressions should be equivalent: +-- restoreAndSave cp v cid blocks1 <> restoreAndSave cp v cid blocks2 +-- restoreAndSave cp v cid (blocks1 <> blocks2) -- -findLatestValidBlockHeader' :: (Logger logger) => PactServiceM logger tbl (Maybe BlockHeader) -findLatestValidBlockHeader' = do - cp <- view psCheckpointer - latestInCheckpointer <- liftIO (Internal.getLatestBlock cp.cpSql) - case latestInCheckpointer of - Nothing -> return Nothing - Just (height, hash) -> Just <$> go height hash +-- prerequisites: +-- - the first block's parent must be an ancestor of the latest block in the database. +-- - each subsequent block must be the direct child of the previous block. +restoreAndSave + :: forall logger err q tbl. + (Logger logger, Monoid q, HasCallStack) + => NonEmpty (ExceptT err (PactBlockM logger tbl) q, EvaluationCtx BlockPayloadHash) + -> ExceptT err (PactServiceM logger tbl) q +restoreAndSave blocks@((_, forkPoint) :| _) = do + sql <- view psReadWriteSql + e <- ask + fmap fst $ generalBracket + (liftIO $ beginSavepoint sql RestoreAndSaveSavePoint) + (\_ -> liftIO . \case + ExitCaseSuccess {} -> commitSavepoint sql RestoreAndSaveSavePoint + _ -> abortSavepoint sql RestoreAndSaveSavePoint + ) $ \_ -> do + -- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above. + txid <- liftIO $ PactDb.rewindDbTo + (_chainwebVersion e) + (_chainId e) + sql + (unwrapParent $ _evaluationCtxRankedParentHash forkPoint) + mapExceptT liftIO $ extend e (mempty, txid) (NEL.toList blocks) where - go height hash = do - bhdb <- view psBlockHeaderDb - liftIO (lookup bhdb hash) >>= \case - Nothing -> do - logInfoPact $ "Latest block isn't valid." - <> " Failed to lookup hash " <> sshow (height, hash) <> " in block header db." - <> " Continuing with parent." - cp <- view psCheckpointer - liftIO (Internal.getBlockParent cp.cpCwVersion cp.cpChainId cp.cpSql (height, hash)) >>= \case - Nothing -> internalError - $ "missing block parent of last hash " <> sshow (height, hash) - Just predHash -> go (pred height) predHash - Just x -> return x - -findLatestValidBlockHeader :: (Logger logger) => PactServiceM logger tbl ParentHeader -findLatestValidBlockHeader = do - findLatestValidBlockHeader' >>= - maybe (throwM NoBlockValidatedYet) (return . ParentHeader) - + extend e (m, startTxId) = \case + (blockAction, evalCtx) : subsequentBlocks -> let + !bh = _evaluationCtxCurrentHeight evalCtx + blockEnv = ChainwebPactDb.BlockHandlerEnv + { ChainwebPactDb._blockHandlerDb = _psReadWriteSql e + , ChainwebPactDb._blockHandlerLogger = _psLogger e + , ChainwebPactDb._blockHandlerVersion = _chainwebVersion e + , ChainwebPactDb._blockHandlerBlockHeight = bh + , ChainwebPactDb._blockHandlerChainId = _chainId e + , ChainwebPactDb._blockHandlerMode = Pact.Transactional + , ChainwebPactDb._blockHandlerUpperBoundTxId = startTxId + , ChainwebPactDb._blockHandlerAtTip = True + } + pactDb = ChainwebPactDb.chainwebPactBlockDb blockEnv + in if pact5 (_chainwebVersion e) (_chainId e) bh then do + + let blockCtx = blockCtxOfEvaluationCtx (_chainwebVersion e) (_chainId e) evalCtx + + let runBlock = mapExceptT + (\r -> runPactServiceM e $ fmap + (\(eitherR, hndl) -> (,hndl) <$> eitherR) + (Pact.runPactBlockM blockCtx pactDb (emptyBlockHandle startTxId) r) + ) + + -- run the block + (m', blockHandle) <- runBlock blockAction + -- compute the accumulator early + let !m'' = m <> m' + -- TODO PP: also check the child matches the parent height + -- case maybeParent of + -- Nothing + -- | genesisHeight res.cpCwVersion res.cpChainId /= succ (_rankedBlockHashHeight nextBlock) -> error + -- "doRestoreAndSave: block with no parent, genesis block, should have genesis height but doesn't," + -- Just (Parent ph) + -- | bh /= _rankedBlockHashHeight nextBlock -> error $ + -- "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " + -- <> sshow (_rankedBlockHashHeight ph) <> ", child height " <> sshow (_rankedBlockHashHeight nextBlock) + -- _ -> return () + liftIO $ ChainwebPactDb.commitBlockStateToDatabase + (_psReadWriteSql e) + evalCtx + blockHandle + + extend e (m'', _blockHandleTxId blockHandle) subsequentBlocks + + else error $ + "Pact 5 block executed on block height before Pact 5 fork, height: " <> sshow bh + [] -> return m +-- -- -------------------------------------------------------------------------- -- -- Incremental rewindTo @@ -285,136 +291,140 @@ findLatestValidBlockHeader = do -- If the rewind is deeper than the optionally provided rewind limit, an -- exception is raised. -- -rewindToIncremental - :: forall logger tbl - . (HasCallStack, CanReadablePayloadCas tbl, Logger logger) - => Maybe RewindLimit - -- ^ if set, limit rewinds to this delta - -> ParentHeader - -- ^ The parent header which is the rewind target - -> PactServiceM logger tbl () -rewindToIncremental rewindLimit (ParentHeader parent) = do - - latestHeader <- findLatestValidBlockHeader' >>= maybe failNonGenesisOnEmptyDb return - - failOnTooLowRequestedHeight latestHeader - playFork latestHeader - - where - parentHeight = view blockHeight parent - - - failOnTooLowRequestedHeight lastHeader = case rewindLimit of - Just limit - | let limitHeight = BlockHeight $ _rewindLimit limit - , parentHeight + 1 + limitHeight < lastHeight -> -- need to stick with addition because Word64 - throwM $ RewindLimitExceeded limit (Just lastHeader) (Just parent) - _ -> return () - where - lastHeight = view blockHeight lastHeader - - failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" - - playFork :: BlockHeader -> PactServiceM logger tbl () - playFork lastHeader = do - bhdb <- asks _psBlockHeaderDb - commonAncestor <- liftIO $ forkEntry bhdb lastHeader parent - cp <- view psCheckpointer - payloadDb <- view psPdb - let ancestorHeight = view blockHeight commonAncestor - - logger <- view psLogger - - -- 'getBranchIncreasing' expects an 'IO' callback because it - -- maintains an 'TreeDB' iterator. 'withPactState' allows us to call - -- pact service actions from the callback. - - (_ :> c) <- withPactState $ \runPact -> - getBranchIncreasing bhdb parent (int ancestorHeight) $ \newBlocks -> do - - -- fastforwards all blocks in a chunk in a single database - -- transaction. - -- invariant: when playing a chunk, the checkpointer must - -- already be at `cur`. - let playChunk :: IORef BlockHeight -> BlockHeader -> Stream (Of BlockHeader) IO r -> IO (Of BlockHeader r) - playChunk heightRef cur blockChunk = runPact $ do - (r, Last header) <- restoreAndSave - (Just $ ParentHeader cur) - $ blockChunk & S.map - (\blockHeader -> do - payload <- liftIO $ lookupPayloadWithHeight payloadDb (Just $ view blockHeight blockHeader) (view blockPayloadHash blockHeader) >>= \case - Nothing -> internalError - $ "Checkpointer.rewindTo.fastForward: lookup of payload failed" - <> ". BlockPayloadHash: " <> encodeToText (view blockPayloadHash blockHeader) - <> ". Block: "<> encodeToText (ObjectEncoded blockHeader) - Just x -> return $ payloadWithOutputsToPayloadData x - liftIO $ writeIORef heightRef (view blockHeight blockHeader) - PactBlockM $ Pair - (void $ Pact4.execBlock blockHeader (CheckablePayload payload)) - (void $ Pact5.execExistingBlock blockHeader (CheckablePayload payload)) - return (Last (Just blockHeader), blockHeader) - ) - - return $! fromJuste header :> r - - -- This stream is guaranteed to at least contain @e@. - (curHdr, remaining) <- fromJuste <$> S.uncons newBlocks - - -- we have to rewind to the current header to start, for - -- playChunk's invariant to be satisfied - Internal.rewindTo cp - (Just $ ParentHeader curHdr) - - heightRef <- newIORef (view blockHeight curHdr) - withAsync (heightProgress (view blockHeight curHdr) heightRef (logInfo_ logger)) $ \_ -> - remaining - & S.copy - & S.length_ - & S.chunksOf 1000 - & foldChunksM (playChunk heightRef) curHdr - - when (c /= 0) $ - logInfoPact $ "rewindTo.playFork: replayed " <> sshow c <> " blocks" - -getEarliestBlock :: PactServiceM logger tbl (Maybe (BlockHeight, BlockHash)) +-- rewindToIncremental +-- :: forall logger tbl +-- . (HasCallStack, CanReadablePayloadCas tbl, Logger logger) +-- => Maybe RewindLimit +-- -- ^ if set, limit rewinds to this delta +-- -> (Parent BlockHeader) +-- -- ^ The parent header which is the rewind target +-- -> PactServiceM logger tbl () +-- rewindToIncremental rewindLimit (Parent parent) = do + +-- latestHeader <- findLatestValidBlockHeader' >>= maybe failNonGenesisOnEmptyDb return + +-- failOnTooLowRequestedHeight latestHeader +-- playFork latestHeader + +-- where +-- parentHeight = view blockHeight parent + + +-- -- TODO: PP +-- -- failOnTooLowRequestedHeight lastHeader = case rewindLimit of +-- -- Just limit +-- -- | let limitHeight = BlockHeight $ _rewindLimit limit +-- -- , parentHeight + 1 + limitHeight < lastHeight -> -- need to stick with addition because Word64 +-- -- throwM $ RewindLimitExceeded limit (Just lastHeader) (Just parent) +-- -- _ -> return () +-- -- where +-- -- lastHeight = view blockHeight lastHeader + +-- failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" + +-- playFork :: BlockHeader -> PactServiceM logger tbl () +-- playFork lastHeader = do +-- bhdb <- asks _psBlockHeaderDb +-- commonAncestor <- liftIO $ forkEntry bhdb lastHeader parent +-- cp <- view psCheckpointer +-- payloadDb <- view psPdb +-- let ancestorHeight = view blockHeight commonAncestor + +-- logger <- view psLogger + +-- -- 'getBranchIncreasing' expects an 'IO' callback because it +-- -- maintains an 'TreeDB' iterator. 'withPactState' allows us to call +-- -- pact service actions from the callback. + +-- (_ :> c) <- withPactState $ \runPact -> +-- getBranchIncreasing bhdb parent (int ancestorHeight) $ \newBlocks -> do + +-- -- fastforwards all blocks in a chunk in a single database +-- -- transaction. +-- -- invariant: when playing a chunk, the checkpointer must +-- -- already be at `cur`. +-- let playChunk :: IORef BlockHeight -> BlockHeader -> Stream (Of BlockHeader) IO r -> IO (Of BlockHeader r) +-- playChunk heightRef cur blockChunk = runPact $ do +-- (r, Last header) <- restoreAndSave +-- (Just $ Parent cur) +-- $ blockChunk & S.map +-- (\blockHeader -> do +-- payload <- liftIO $ lookupPayloadWithHeight payloadDb (Just $ view blockHeight blockHeader) (view blockPayloadHash blockHeader) >>= \case +-- Nothing -> internalError +-- $ "Checkpointer.rewindTo.fastForward: lookup of payload failed" +-- <> ". BlockPayloadHash: " <> encodeToText (view blockPayloadHash blockHeader) +-- <> ". Block: "<> encodeToText (ObjectEncoded blockHeader) +-- Just x -> return $ payloadWithOutputsToPayloadData x +-- liftIO $ writeIORef heightRef (view blockHeight blockHeader) +-- PactBlockM $ Pair +-- (void $ Pact4.execBlock blockHeader (CheckablePayload payload)) +-- (void $ Pact.execExistingBlock blockHeader (CheckablePayload payload)) +-- return (Last (Just blockHeader), blockHeader) +-- ) + +-- return $! fromJuste header :> r + +-- -- This stream is guaranteed to at least contain @e@. +-- (curHdr, remaining) <- fromJuste <$> S.uncons newBlocks + +-- -- we have to rewind to the current header to start, for +-- -- playChunk's invariant to be satisfied +-- Internal.rewindTo cp +-- (Just $ Parent curHdr) + +-- heightRef <- newIORef (view blockHeight curHdr) +-- withAsync (heightProgress (view blockHeight curHdr) heightRef (logInfo_ logger)) $ \_ -> +-- remaining +-- & S.copy +-- & S.length_ +-- & S.chunksOf 1000 +-- & foldChunksM (playChunk heightRef) curHdr + +-- when (c /= 0) $ +-- logInfoPact $ "rewindTo.playFork: replayed " <> sshow c <> " blocks" + +getEarliestBlock :: PactServiceM logger tbl (Maybe RankedBlockHash) getEarliestBlock = do - cp <- view psCheckpointer - liftIO $ Internal.getEarliestBlock cp.cpSql - -getLatestBlock :: PactServiceM logger tbl (Maybe (BlockHeight, BlockHash)) -getLatestBlock = do - cp <- view psCheckpointer - liftIO $ Internal.getLatestBlock cp.cpSql - -lookupBlock :: RankedBlockHash -> PactServiceM logger tbl Bool -lookupBlock b = do - cp <- view psCheckpointer - liftIO $ Internal.lookupBlock cp.cpSql b - -lookupHistorical - :: BlockHeader - -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info - -> Pact5.RowKey - -> PactServiceM logger tbl (Historical (Maybe (Pact5.TxLog Pact5.RowData))) -lookupHistorical blockHeader d k = do - cp <- view psCheckpointer - liftIO $ Internal.lookupHistorical cp.cpSql blockHeader d k - -getBlockHistory - :: BlockHeader - -> Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info - -> PactServiceM logger tbl (Historical BlockTxHistory) -getBlockHistory blockHeader d = do - cp <- view psCheckpointer - liftIO $ Internal.getBlockHistory cp.cpSql blockHeader d + sql <- view psReadWriteSql + liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getEarliestBlock sql + +getConsensusState :: PactServiceM logger tbl ConsensusState +getConsensusState = do + sql <- view psReadWriteSql + liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getConsensusState sql + +setConsensusState :: ConsensusState -> PactServiceM logger tbl () +setConsensusState cs = do + sql <- view psReadWriteSql + liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.setConsensusState sql cs + +lookupBlockWithHeight :: BlockHeight -> PactServiceM logger tbl (Maybe (Ranked (Parent BlockHash))) +lookupBlockWithHeight bh = do + sql <- view psReadWriteSql + liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockWithHeight sql bh + +lookupParentBlockRanked :: Ranked (Parent BlockHash) -> PactServiceM logger tbl Bool +lookupParentBlockRanked rpbh = do + sql <- view psReadWriteSql + liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupParentBlockRanked sql rpbh + +lookupParentBlockHash :: Parent BlockHash -> PactServiceM logger tbl Bool +lookupParentBlockHash pbh = do + sql <- view psReadWriteSql + liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupParentBlockHash sql pbh + +getPayloadsAfter :: Parent BlockHeight -> PactServiceM logger tbl [Ranked BlockPayloadHash] +getPayloadsAfter b = do + sql <- view psReadWriteSql + liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getPayloadsAfter sql b -- -------------------------------------------------------------------------- -- -- Utils -heightProgress :: BlockHeight -> IORef BlockHeight -> (Text -> IO ()) -> IO () -heightProgress initialHeight ref logFun = forever $ do - threadDelay (20 * 1_000_000) - h <- readIORef ref - logFun - $ "processed blocks: " <> sshow (h - initialHeight) - <> ", current height: " <> sshow h +-- heightProgress :: BlockHeight -> IORef BlockHeight -> (Text -> IO ()) -> IO () +-- heightProgress initialHeight ref logFun = forever $ do +-- threadDelay (20 * 1_000_000) +-- h <- readIORef ref +-- logFun +-- $ "processed blocks: " <> sshow (h - initialHeight) +-- <> ", current height: " <> sshow h diff --git a/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs b/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs deleted file mode 100644 index 7bd0bd917b..0000000000 --- a/src/Chainweb/Pact/PactService/Checkpointer/Internal.hs +++ /dev/null @@ -1,345 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE CPP #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE BlockArguments #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE MultiWayIf #-} -{-# LANGUAGE TypeApplications #-} - --- | --- Module: Chainweb.Pact.Backend.RelationalCheckpointer --- Copyright: Copyright © 2018 - 2020 Kadena LLC. --- License: See LICENSE file --- Maintainers: Emmanuel Denloye --- Stability: experimental --- --- Pact Checkpointer for Chainweb -module Chainweb.Pact.PactService.Checkpointer.Internal - ( Checkpointer(..) - , initCheckpointerResources - , withCheckpointerResources - , rewindTo - , readFrom - , restoreAndSave - , lookupBlock - , getEarliestBlock - , getLatestBlock - , getBlockParent - ) where - -import Control.Concurrent (threadDelay) -import Control.Concurrent.Async -import Control.Concurrent.MVar -import Control.Lens (view) -import Control.Monad -import Control.Monad.Catch -import Control.Monad.IO.Class - -import qualified Data.ByteString.Short as BS -#if !MIN_VERSION_base(4,20,0) -import Data.Foldable (foldl') -#endif -import Data.Int -import Data.Coerce -import qualified Data.Map.Strict as M -import Data.Maybe -import qualified Data.HashMap.Strict as HashMap -import qualified Data.Set as S -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import GHC.Stack (HasCallStack) - -import Database.SQLite3.Direct - -import Prelude hiding (log) -import Streaming -import qualified Streaming.Prelude as Streaming - -import System.LogLevel - --- pact - -import Pact.Interpreter (PactDbEnv(..)) -import Pact.Types.Command(RequestKey(..)) -import Pact.Types.Hash (Hash(..)) -import Pact.Types.Persistence -import Pact.Types.SQLite - -import qualified Pact.Core.Persistence as Pact5 - --- chainweb -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Logger -import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact4 -import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact5 - -import Chainweb.Pact.Backend.DbCache -import Chainweb.Pact.Backend.Utils -import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Types -import Chainweb.Utils -import Chainweb.Utils.Serialization -import Chainweb.Version -import Chainweb.Version.Guards -import qualified Pact.Core.Builtin as Pact5 -import qualified Pact.Core.Evaluate as Pact5 -import qualified Pact.Core.Names as Pact5 -import qualified Chainweb.Pact.Backend.Utils as PactDb - -withCheckpointerResources - :: (Logger logger) - => logger - -> SQLiteEnv - -> IntraBlockPersistence - -> ChainwebVersion - -> ChainId - -> (Checkpointer logger -> IO a) - -> IO a -withCheckpointerResources logger sqlenv p v cid inner = do - inner =<< initCheckpointerResources sqlenv p logger v cid - -initCheckpointerResources - :: (Logger logger) - => SQLiteEnv - -> IntraBlockPersistence - -> logger - -> ChainwebVersion - -> ChainId - -> IO (Checkpointer logger) -initCheckpointerResources sql p loggr v cid = do - Pact5.initSchema sql - return Checkpointer - { cpLogger = loggr - , cpCwVersion = v - , cpChainId = cid - , cpSql = sql - , cpIntraBlockPersistence = p - } - --- | Rewind to a particular block *in-memory*, producing a read-write snapshot --- of the database at that block to compute some value, after which the snapshot --- is discarded and nothing is saved to the database. --- --- prerequisite: ParentHeader is an ancestor of the "latest block"; --- if that isn't the case, NoHistory is returned. -readFrom - :: forall logger a - . (Logger logger) - => Checkpointer logger - -> Maybe (Parent RankedBlockHash) - -> (ChainwebPactDb -> BlockHandle -> IO a) - -> IO (Historical a) -readFrom res maybeParent doRead = do - let currentHeight = case maybeParent of - Nothing -> genesisHeight res.cpCwVersion res.cpChainId - Just parent -> succ $ _rankedBlockHashHeight $ unwrapParent parent - - modifyMVar res.cpModuleCacheVar $ \sharedModuleCache -> do - bracket - (beginSavepoint res.cpSql BatchSavepoint) - (\_ -> abortSavepoint res.cpSql BatchSavepoint) \() -> do - -- NB it's important to do this *after* you start the savepoint (and thus - -- the db transaction) to make sure that the latestHeader check is up to date. - latestHeader <- getLatestBlock res.cpSql - -- is the parent the latest header, i.e., can we get away without rewinding? - let parentIsLatestHeader = fmap Parent latestHeader == maybeParent - h <- if - | pact5 res.cpCwVersion res.cpChainId currentHeight -> - PactDb.getEndTxId "doReadFrom" res.cpSql maybeParent >>= traverse \startTxId -> do - let - blockHandlerEnv = Pact5.BlockHandlerEnv - { Pact5._blockHandlerDb = res.cpSql - , Pact5._blockHandlerLogger = res.cpLogger - , Pact5._blockHandlerVersion = res.cpCwVersion - , Pact5._blockHandlerChainId = res.cpChainId - , Pact5._blockHandlerBlockHeight = currentHeight - , Pact5._blockHandlerMode = Pact5.Transactional - , Pact5._blockHandlerUpperBoundTxId = Pact5.TxId $ fromIntegral startTxId - , Pact5._blockHandlerAtTip = parentIsLatestHeader - } - let pactDb = Pact5.chainwebPactBlockDb blockHandlerEnv - r <- doRead pactDb (emptyBlockHandle startTxId) - return (r, sharedModuleCache) - | otherwise -> - error $ - "Pact 5 readFrom executed on block height before Pact 5 fork, height: " <> sshow currentHeight - case h of - NoHistory -> return (sharedModuleCache, NoHistory) - Historical (r, finalCache) -> return (finalCache, Historical r) - - - --- the special case where one doesn't want to extend the chain, just rewind it. -rewindTo :: Logger logger => Checkpointer logger -> Maybe (Parent RankedBlockHash) -> IO () -rewindTo cp ancestor = void $ restoreAndSave cp - ancestor - (pure () :: Stream (Of (RunnableBlock logger ())) IO ()) - --- TODO: log more? --- | Rewind to a particular block, and play a stream of blocks afterward, --- extending the chain and saving the result persistently. for example, --- to validate a block `vb`, we rewind to the common ancestor of `vb` and --- the latest block, and extend the chain with all of the blocks on `vb`'s --- fork, including `vb`. --- this function takes care of making sure that this is done *atomically*. --- TODO: fix the below w.r.t. its latest type --- promises: --- - excluding the fact that each _cpRestoreAndSave call is atomic, the --- following two expressions should be equivalent: --- do --- _cpRestoreAndSave cp p1 x --- ((,) <$> (bs1 <* Stream.yield p2) <*> bs2) runBlk --- do --- (r1, q1) <- _cpRestoreAndSave cp p1 x (bs1 <* Stream.yield p2) runBlk --- (r2, q2) <- _cpRestoreAndSave cp (Just (x p2)) x bs2 runBlk --- return ((r1, r2), q1 <> q2) --- i.e. rewinding, extending, then rewinding to the point you extended --- to and extending some more, should give the same result as rewinding --- once and extending to the same final point. --- - no block in the stream is used more than once. --- prerequisites: --- - the parent being rewound to must be a direct ancestor --- of the latest block, i.e. what's returned by _cpLatestBlock. --- - the stream must start with a block that is a child of the rewind --- target and each block after must be the child of the previous block. -restoreAndSave - :: forall logger r q. - (Logger logger, Monoid q, HasCallStack) - => Checkpointer logger - -> Maybe (Parent RankedBlockHash) - -> Stream (Of (RunnableBlock logger q)) IO r - -> IO (r, q) -restoreAndSave res rewindParent blocks = do - modifyMVar res.cpModuleCacheVar $ \moduleCache -> do - fmap fst $ generalBracket - (beginSavepoint res.cpSql BatchSavepoint) - (\_ -> \case - ExitCaseSuccess {} -> commitSavepoint res.cpSql BatchSavepoint - _ -> abortSavepoint res.cpSql BatchSavepoint - ) $ \_ -> do - startTxId <- PactDb.rewindDbTo res.cpSql rewindParent - ((q, _, _, finalModuleCache) :> r) <- extend startTxId moduleCache - return (finalModuleCache, (r, q)) - where - - extend - :: TxId -> DbCache PersistModuleData - -> IO (Of (q, Maybe (Parent RankedBlockHash), TxId, DbCache PersistModuleData) r) - extend startTxId startModuleCache = Streaming.foldM - (\(m, maybeParent, txid, moduleCache) block -> do - let - !bh = case maybeParent of - Nothing -> genesisHeight res.cpCwVersion res.cpChainId - Just (Parent parent) -> succ $ _rankedBlockHashHeight parent - case block of - RunnableBlock runBlock - | pact5 res.cpCwVersion res.cpChainId bh -> do - let - blockEnv = Pact5.BlockHandlerEnv - { Pact5._blockHandlerDb = res.cpSql - , Pact5._blockHandlerLogger = res.cpLogger - , Pact5._blockHandlerVersion = res.cpCwVersion - , Pact5._blockHandlerBlockHeight = bh - , Pact5._blockHandlerChainId = res.cpChainId - , Pact5._blockHandlerMode = Pact5.Transactional - , Pact5._blockHandlerUpperBoundTxId = Pact5.TxId $ fromIntegral txid - , Pact5._blockHandlerAtTip = True - } - pactDb = Pact5.chainwebPactBlockDb blockEnv - -- run the block - ((m', nextBlockHeader), blockHandle) <- runBlock pactDb maybeParent (emptyBlockHandle txid) - -- compute the accumulator early - let !m'' = m <> m' - case maybeParent of - Nothing - | genesisHeight res.cpCwVersion res.cpChainId /= succ (_rankedBlockHashHeight nextBlockHeader) -> error - "doRestoreAndSave: block with no parent, genesis block, should have genesis height but doesn't," - Just (Parent ph) - | bh /= _rankedBlockHashHeight nextBlockHeader -> error $ - "doRestoreAndSave: non-genesis block should be one higher than its parent. parent at " - <> sshow (_rankedBlockHashHeight ph) <> ", child height " <> sshow (_rankedBlockHashHeight nextBlockHeader) - _ -> return () - Pact5.commitBlockStateToDatabase res.cpSql - (_rankedBlockHashHash nextBlockHeader) (_rankedBlockHashHeight nextBlockHeader) - blockHandle - - return (m'', Just (Parent nextBlockHeader), _blockHandleTxId blockHandle, moduleCache) - - | otherwise -> error $ - "Pact 5 block executed on block height before Pact 5 fork, height: " <> sshow bh - ) - (return (mempty, rewindParent, startTxId, startModuleCache)) - return - blocks - --- | Get the checkpointer's idea of the earliest block. The block height --- is the height of the block of the block hash. -getEarliestBlock :: HasCallStack => SQLiteEnv -> IO (Maybe (BlockHeight, BlockHash)) -getEarliestBlock db = do - r <- qry_ db qtext [RInt, RBlob] >>= mapM go - case r of - [] -> return Nothing - (!o:_) -> return (Just o) - where - qtext = "SELECT blockheight, hash FROM BlockHistory ORDER BY blockheight ASC LIMIT 1" - - go [SInt hgt, SBlob blob] = - let hash = either error id $ runGetEitherS decodeBlockHash blob - in return (fromIntegral hgt, hash) - go _ = fail "Chainweb.Pact.Backend.RelationalCheckpointer.doGetEarliest: impossible. This is a bug in chainweb-node." - --- | Get the checkpointer's idea of the latest block. The block height is --- is the height of the block of the block hash. --- --- TODO: Under which circumstances does this return 'Nothing'? -getLatestBlock :: HasCallStack => SQLiteEnv -> IO (Maybe RankedBlockHash) -getLatestBlock db = do - r <- qry_ db qtext [RInt, RBlob] >>= mapM go - case r of - [] -> return Nothing - (!o:_) -> return (Just o) - where - qtext = "SELECT blockheight, hash FROM BlockHistory ORDER BY blockheight DESC LIMIT 1" - - go [SInt hgt, SBlob blob] = - let hash = either error id $ runGetEitherS decodeBlockHash blob - in return $ RankedBlockHash (fromIntegral hgt) hash - go _ = fail "Chainweb.Pact.Backend.RelationalCheckpointer.getLatest: impossible. This is a bug in chainweb-node." - --- | Ask: is the checkpointer aware of the given block? -lookupBlock :: SQLiteEnv -> RankedBlockHash -> IO Bool -lookupBlock db (RankedBlockHash bheight bhash) = do - r <- qry db qtext [SInt $ fromIntegral bheight, SBlob (runPutS (encodeBlockHash bhash))] - [RInt] - case r of - [[SInt n]] -> return $! n == 1 - [_] -> error "lookupBlock: output type mismatch" - _ -> error "Expected single-row result" - where - qtext = "SELECT COUNT(*) FROM BlockHistory WHERE blockheight = ? AND hash = ?;" - -getBlockParent :: ChainwebVersion -> ChainId -> SQLiteEnv -> (BlockHeight, BlockHash) -> IO (Maybe BlockHash) -getBlockParent v cid db (bh, hash) - | bh == genesisHeight v cid = return Nothing - | otherwise = do - blockFound <- lookupBlock db (RankedBlockHash bh hash) - if not blockFound - then return Nothing - else do - r <- qry db qtext [SInt (fromIntegral (pred bh))] [RBlob] - case r of - [[SBlob blob]] -> - either error (return . return) $! runGetEitherS decodeBlockHash blob - [] -> error "getBlockParent: block was found but its parent couldn't be found" - _ -> error "getBlockParent: output type mismatch" - where - qtext = "SELECT hash FROM BlockHistory WHERE blockheight = ?" diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index afb3873b9c..486f70f3e4 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -16,20 +16,20 @@ module Chainweb.Pact.PactService.ExecBlock ( runCoinbase - , continueBlock + -- , continueBlock , execExistingBlock - , validateRawChainwebTx , validateParsedChainwebTx + , pact5TransactionsFromPayload + , BlockInvalidError(..) ) where import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger -import Chainweb.Mempool.Mempool(BlockFill (..), pact5RequestKeyToTransactionHash, InsertError (..)) +import Chainweb.Mempool.Mempool(BlockFill (..), pactRequestKeyToTransactionHash, InsertError (..)) import Chainweb.MinerReward import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.ChainwebPactDb (Pact5Db(doPact5DbTransaction)) -import Chainweb.Pact.SPV qualified as Pact5 +import Chainweb.Pact.Backend.ChainwebPactDb (ChainwebPactDb(doChainwebPactDbTransaction)) import Chainweb.Pact.Types import Chainweb.Pact.Transaction import Chainweb.Pact.TransactionExec @@ -55,16 +55,17 @@ import Data.Decimal import Data.Either (partitionEithers) import Data.Foldable import Data.Maybe +import Data.Text (Text) import Data.Text qualified as T import Data.Vector (Vector) import Data.Vector qualified as V import Data.Void import Pact.Core.ChainData hiding (ChainId) -import Pact.Core.Command.Types qualified as Pact5 -import Pact.Core.Persistence qualified as Pact5 +import Pact.Core.Command.Types qualified as Pact +import Pact.Core.Persistence qualified as Pact import Pact.Core.Hash import Control.Exception.Safe -import qualified Pact.Core.Gas as Pact5 +import qualified Pact.Core.Gas as Pact import qualified Pact.JSON.Encode as J import System.Timeout import Utils.Logging.Trace @@ -73,21 +74,27 @@ import qualified Pact.Types.Gas as Pact4 import qualified Pact.Core.Gas as P import qualified Data.Text.Encoding as T import qualified Data.HashMap.Strict as HashMap -import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact5 -import qualified Chainweb.Pact.Transaction as Pact4 -import qualified Chainweb.Pact.Transaction as Pact5 -import qualified Chainweb.Pact.Validations as Pact5 -import Pact.Core.Pretty qualified as Pact5 +import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact +import qualified Chainweb.Pact.Transaction as Pact +import qualified Chainweb.Pact.Validations as Pact +import Chainweb.Pact.NoCoinbase +import Chainweb.Pact.Backend.Types +import Chainweb.Parent +import Chainweb.BlockCreationTime +import Pact.Core.Pretty qualified as Pact import qualified Data.ByteString.Short as SB -import qualified Pact.Core.Hash as Pact5 +import qualified Pact.Core.Hash as Pact import System.LogLevel import qualified Data.Aeson as Aeson import qualified Data.List.NonEmpty as NEL -import Chainweb.Pact.NoCoinbase -import qualified Pact.Core.Errors as Pact5 -import qualified Pact.Core.Evaluate as Pact5 -import Chainweb.Pact.Backend.Types -import qualified Pact.Core.ChainData as Pact5 +import qualified Pact.Core.Errors as Pact +import qualified Pact.Core.Evaluate as Pact +import qualified Pact.Core.ChainData as Pact +import qualified Pact.Core.Errors as Pact +import qualified Chainweb.Payload as Chainweb +import qualified Chainweb.Pact.Types as Pact +import qualified Chainweb.Pact.Backend.ChainwebPactDb as ChainwebPactDb +import Chainweb.PayloadProvider -- | Calculate miner reward. We want this to error hard in the case where -- block times have finally exceeded the 120-year range. Rewards are calculated @@ -105,56 +112,33 @@ minerReward v = _kda . minerRewardKda . blockMinerReward v runCoinbase :: (Logger logger) => Miner - -> PactBlockM logger tbl (Either Pact5CoinbaseError (Pact5.CommandResult [Pact5.TxLog ByteString] Void)) + -> PactBlockM logger tbl (Either (Pact.PactError Pact.Info) (Pact.CommandResult [Pact.TxLog ByteString] Void)) runCoinbase miner = do - isGenesis <- view psIsGenesis + isGenesis <- view (psBlockCtx . to _bctxIsGenesis) if isGenesis then return $ Right noCoinbase else do logger <- view (psServiceEnv . psLogger) - v <- view chainwebVersion - txCtx <- TxContext <$> view psParentHeader <*> pure miner - - let !bh = ctxCurrentBlockHeight txCtx - let reward = minerReward v bh + blockCtx <- view psBlockCtx -- the coinbase request key is not passed here because TransactionIndex -- does not contain coinbase transactions - pactTransaction Nothing $ \db -> - applyCoinbase logger db reward txCtx - -pact5TransactionsFromPayload - :: PayloadData - -> IO (Vector Pact5.Transaction) -pact5TransactionsFromPayload plData = do - vtrans <- mapM toCWTransaction $ - toList (view payloadDataTransactions plData) - let (theLefts, theRights) = partitionEithers vtrans - unless (null theLefts) $ do - let ls = map T.pack theLefts - throwM $ TransactionDecodeFailure $ "Failed to decode pact transactions: " - <> T.intercalate ". " ls - return $! V.fromList theRights - where - toCWTransaction bs = - evaluate (force (codecDecode payloadCodec $ _transactionBytes bs)) + pactTransaction Nothing $ \db _spv -> + applyCoinbase logger db miner blockCtx -- | Continue adding transactions to an existing block. continueBlock :: forall logger tbl . (Logger logger, CanReadablePayloadCas tbl) => MemPoolAccess - -> BlockInProgress Pact5 - -> PactBlockM logger tbl (BlockInProgress Pact5) + -> BlockInProgress + -> PactBlockM logger tbl BlockInProgress continueBlock mpAccess blockInProgress = do pbBlockHandle .= _blockInProgressHandle blockInProgress -- update the mempool, ensuring that we reintroduce any transactions that -- were removed due to being completed in a block on a different fork. case maybeBlockParentHeader of Just blockParentHeader -> do - liftIO $ do - mpaProcessFork mpAccess blockParentHeader - mpaSetLastHeader mpAccess $ blockParentHeader liftPactServiceM $ logInfoPact $ T.unwords [ "(parent height = " <> sshow (view blockHeight blockParentHeader) <> ")" @@ -163,8 +147,8 @@ continueBlock mpAccess blockInProgress = do Nothing -> liftPactServiceM $ logInfoPact "Continuing genesis block" - blockGasLimit <- view (psServiceEnv . psBlockGasLimit) - mTxTimeLimit <- view (psServiceEnv . psTxTimeLimit) + blockGasLimit <- view (psServiceEnv . psNewBlockGasLimit) + mTxTimeLimit <- view (psServiceEnv . psNewPayloadTxTimeLimit) let txTimeHeadroomFactor :: Double txTimeHeadroomFactor = 5 let txTimeLimit :: Micros @@ -180,7 +164,7 @@ continueBlock mpAccess blockInProgress = do let startTxs = _transactionPairs (_blockInProgressTransactions blockInProgress) let startTxsRequestKeys = - foldMap' (S.singleton . pact5RequestKeyToTransactionHash . view Pact5.crReqKey . snd) startTxs + foldMap' (S.singleton . pactRequestKeyToTransactionHash . view Pact.crReqKey . snd) startTxs let initState = BlockFill { _bfTxHashes = startTxsRequestKeys , _bfGasLimit = _blockInProgressRemainingGasLimit blockInProgress @@ -195,9 +179,9 @@ continueBlock mpAccess blockInProgress = do finalBlockHandle <- use pbBlockHandle liftIO $ mpaBadlistTx mpAccess - (V.fromList $ fmap pact5RequestKeyToTransactionHash $ concat invalids) + (V.fromList $ fmap pactRequestKeyToTransactionHash $ concat invalids) - liftPactServiceM $ logDebugPact $ "Order of completed transactions: " <> sshow (map (Pact5.unRequestKey . Pact5._crReqKey . snd) $ concat $ reverse valids) + liftPactServiceM $ logDebugPact $ "Order of completed transactions: " <> sshow (map (Pact.unRequestKey . Pact._crReqKey . snd) $ concat $ reverse valids) let !blockInProgress' = blockInProgress & blockInProgressHandle .~ finalBlockHandle @@ -206,12 +190,12 @@ continueBlock mpAccess blockInProgress = do & blockInProgressRemainingGasLimit .~ finalGasLimit - liftPactServiceM $ logDebugPact $ "Final block transaction order: " <> sshow (fmap (Pact5.unRequestKey . Pact5._crReqKey . snd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) + liftPactServiceM $ logDebugPact $ "Final block transaction order: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . snd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) return blockInProgress' where - maybeBlockParentHeader = _parentHeader <$> _blockInProgressParentHeader blockInProgress + maybeBlockParentHeader = unwrapParent <$> _blockInProgressParentHeader blockInProgress refill fetchLimit txTimeLimit blockFillState = over _2 reverse <$> go [] [] blockFillState where go @@ -240,18 +224,18 @@ continueBlock mpAccess blockInProgress = do execNewTransactions (_blockInProgressMiner blockInProgress) prevRemainingGas txTimeLimit newTxs liftPactServiceM $ do - logDebugPact $ "Refill: included request keys: " <> sshow @[Hash] (fmap (Pact5.unRequestKey . Pact5._crReqKey . snd) newCompletedTransactions) - logDebugPact $ "Refill: badlisted request keys: " <> sshow @[Hash] (fmap Pact5.unRequestKey newInvalidTransactions) + logDebugPact $ "Refill: included request keys: " <> sshow @[Hash] (fmap (Pact.unRequestKey . Pact._crReqKey . snd) newCompletedTransactions) + logDebugPact $ "Refill: badlisted request keys: " <> sshow @[Hash] (fmap Pact.unRequestKey newInvalidTransactions) let newBlockFillState = BlockFill { _bfCount = succ prevFillCount , _bfGasLimit = newBlockGasLimit , _bfTxHashes = flip - (foldr (S.insert . pact5RequestKeyToTransactionHash . view (_2 . Pact5.crReqKey))) + (foldr (S.insert . pactRequestKeyToTransactionHash . view (_2 . Pact.crReqKey))) newCompletedTransactions $ flip - (foldr (S.insert . pact5RequestKeyToTransactionHash)) + (foldr (S.insert . pactRequestKeyToTransactionHash)) newInvalidTransactions $ prevTxHashes } @@ -271,18 +255,18 @@ continueBlock mpAccess blockInProgress = do :: Miner -> Pact4.GasLimit -> Micros - -> Vector Pact5.Transaction + -> Vector Pact.Transaction -> PactBlockM logger tbl (CompletedTransactions, InvalidTransactions, Pact4.GasLimit, Bool) execNewTransactions miner remainingGas timeLimit txs = do env <- ask startBlockHandle <- use pbBlockHandle - let p5RemainingGas = Pact5.GasLimit $ Pact5.Gas $ fromIntegral remainingGas + let p5RemainingGas = Pact.GasLimit $ Pact.Gas $ fromIntegral remainingGas logger' <- view (psServiceEnv . psLogger) isGenesis <- view psIsGenesis ((txResults, timedOut), (finalBlockHandle, Identity finalRemainingGas)) <- liftIO $ flip runStateT (startBlockHandle, Identity p5RemainingGas) $ foldr (\(txIdxInBlock, tx) rest -> StateT $ \s -> do - let logger = addLabel ("transactionHash", sshow (Pact5._cmdHash tx)) logger' + let logger = addLabel ("transactionHash", sshow (Pact._cmdHash tx)) logger' let env' = env & psServiceEnv . psLogger .~ logger let timeoutFunc runTx = if isGenesis @@ -297,17 +281,17 @@ continueBlock mpAccess blockInProgress = do Nothing -> do logFunctionJson logger Warn $ Aeson.object [ "type" Aeson..= Aeson.String "newblock timeout" - , "hash" Aeson..= Aeson.String (sshow (Pact5._cmdHash tx)) + , "hash" Aeson..= Aeson.String (sshow (Pact._cmdHash tx)) , "payload" Aeson..= Aeson.String ( - T.decodeUtf8 $ SB.fromShort $ tx ^. Pact5.cmdPayload . Pact5.payloadBytes + T.decodeUtf8 $ SB.fromShort $ tx ^. Pact.cmdPayload . Pact.payloadBytes ) ] - return (([Left (Pact5._cmdHash tx)], True), s) + return (([Left (Pact._cmdHash tx)], True), s) Just (Left err) -> do logFunctionText logger Debug $ "applyCmd failed to buy gas: " <> prettyPact5GasPurchaseFailure err ((as, timedOut), s') <- runStateT rest s - return ((Left (Pact5._cmdHash tx):as, timedOut), s') + return ((Left (Pact._cmdHash tx):as, timedOut), s') Just (Right (a, s')) -> do logFunctionText logger Debug "applyCmd buy gas succeeded" ((as, timedOut), s'') <- runStateT rest s' @@ -317,10 +301,10 @@ continueBlock mpAccess blockInProgress = do (zip [0..] (V.toList txs)) pbBlockHandle .= finalBlockHandle let (invalidTxHashes, completedTxs) = partitionEithers txResults - let p4FinalRemainingGas = fromIntegral @Pact5.SatWord @Pact4.GasLimit $ finalRemainingGas ^. Pact5._GasLimit . to Pact5._gas - return (completedTxs, Pact5.RequestKey <$> invalidTxHashes, p4FinalRemainingGas, timedOut) + let p4FinalRemainingGas = fromIntegral @Pact.SatWord @Pact4.GasLimit $ finalRemainingGas ^. Pact._GasLimit . to Pact._gas + return (completedTxs, Pact.RequestKey <$> invalidTxHashes, p4FinalRemainingGas, timedOut) - getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector Pact5.Transaction) + getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector Pact.Transaction) getBlockTxs blockFillState = do liftPactServiceM $ logDebugPact "Refill: fetching transactions" v <- view chainwebVersion @@ -331,32 +315,33 @@ continueBlock mpAccess blockInProgress = do isGenesis <- view psIsGenesis let validate bhi _bha txs = do forM txs $ - runExceptT . validateRawChainwebTx logger v cid dbEnv (_blockInProgressHandle blockInProgress) (ParentCreationTime parentTime) bhi isGenesis + runExceptT . validateParsedChainwebTx logger v cid dbEnv (_blockInProgressHandle blockInProgress) (Parent $ parentTime) bhi isGenesis liftIO $ mpaGetBlock mpAccess blockFillState validate (succ pHeight) pHash parentTime -type CompletedTransactions = [(Pact5.Transaction, Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info))] -type InvalidTransactions = [Pact5.RequestKey] +type CompletedTransactions = [(Chainweb.Transaction, Pact.OffChainCommandResult)] +type InvalidTransactions = [Pact.RequestKey] -- Apply a Pact command in the current block. -- This function completely ignores timeouts! applyPactCmd :: (Traversable t, Logger logger) - => PactBlockEnv logger Pact5 tbl - -> Miner -> TxIdxInBlock -> Pact5.Transaction + => PactBlockEnv logger tbl + -> Miner + -> TxIdxInBlock -> Pact.Transaction -> StateT - (BlockHandle Pact5, t P.GasLimit) - (ExceptT Pact5GasPurchaseFailure IO) - (Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info)) + (BlockHandle, t P.GasLimit) + (ExceptT TxInvalidError IO) + (Pact.CommandResult [Pact.TxLog ByteString] (Pact.PactError Pact.Info)) applyPactCmd env miner txIdxInBlock tx = StateT $ \(blockHandle, blockGasRemaining) -> do -- we set the command gas limit to the minimum of its original value and the remaining gas in the block -- this way Pact never uses more gas than remains in the block, and the tx fails otherwise - let alteredTx = (view payloadObj <$> tx) & Pact5.cmdPayload . Pact5.pMeta . pmGasLimit %~ maybe id min (blockGasRemaining ^? traversed) + let alteredTx = (view payloadObj <$> tx) & Pact.cmdPayload . Pact.pMeta . pmGasLimit %~ maybe id min (blockGasRemaining ^? traversed) resultOrGasError <- liftIO $ runReaderT (unsafeApplyPactCmd blockHandle - (initialGasOf (tx ^. Pact5.cmdPayload)) + (initialGasOf (tx ^. Pact.cmdPayload)) alteredTx) env case resultOrGasError of @@ -365,96 +350,93 @@ applyPactCmd env miner txIdxInBlock tx = StateT $ \(blockHandle, blockGasRemaini -- if there is a fixed remaining amount of gas in the block | Just blockGas <- blockGasRemaining ^? traversed -- and the transaction gas limit is more than that - , let txGasLimit = tx ^. Pact5.cmdPayload . payloadObj . Pact5.pMeta . pmGasLimit + , let txGasLimit = tx ^. Pact.cmdPayload . payloadObj . Pact.pMeta . pmGasLimit , txGasLimit > blockGas -- then the transaction is not allowed to fail, or it would consume more gas than remains in the block - , Pact5.PactResultErr _ <- Pact5._crResult result - -> throwM $ BlockGasLimitExceeded (fromIntegral $ txGasLimit ^. Pact5._GasLimit . to Pact5._gas) + , Pact.PactResultErr _ <- Pact._crResult result + -> throwError $ TxExceedsBlockGasLimit (txGasLimit ^. Pact._GasLimit . to Pact._gas . to fromIntegral) | otherwise -> do let subtractGasLimit limit subtrahend = - let limitGas = limit ^. Pact5._GasLimit + let limitGas = limit ^. Pact._GasLimit in if limitGas < subtrahend -- this should be impossible. -- we never allow a transaction to run with a gas limit higher than the block gas limit. - then internalError $ + then error $ "subtractGasLimit: transaction ran with higher gas limit than block gas limit: " <> sshow subtrahend <> " > " <> sshow limit - else pure $ Pact5.GasLimit $ Pact5.Gas (Pact5._gas limitGas - Pact5._gas subtrahend) + else pure $ Pact.GasLimit $ Pact.Gas (Pact._gas limitGas - Pact._gas subtrahend) blockGasRemaining' <- - traverse (`subtractGasLimit` (Pact5._crGas result)) blockGasRemaining + traverse (`subtractGasLimit` (Pact._crGas result)) blockGasRemaining return (result, (nextHandle, blockGasRemaining')) where -- | Apply a Pact command in the current block. -- This function completely ignores timeouts and the block gas limit! unsafeApplyPactCmd :: (Logger logger) - => BlockHandle Pact5 - -> Pact5.Gas - -> Pact5.Command (Pact5.Payload PublicMeta Pact5.ParsedCode) + => BlockHandle + -> Pact.Gas + -> Pact.Command (Pact.Payload PublicMeta Pact.ParsedCode) -> ReaderT - (PactBlockEnv logger Pact5 tbl) + (PactBlockEnv logger tbl) IO - (Either Pact5GasPurchaseFailure - (Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info), BlockHandle Pact5)) + (Either TxInvalidError + (Pact.CommandResult [Pact.TxLog ByteString] (Pact.PactError Pact.Info), BlockHandle)) unsafeApplyPactCmd blockHandle initialGas cmd = do _txFailuresCounter <- view (psServiceEnv . psTxFailuresCounter) logger <- view (psServiceEnv . psLogger) gasLogger <- view (psServiceEnv . psGasLogger) dbEnv <- view psBlockDbEnv - bhdb <- view (psServiceEnv . psBlockHeaderDb) - parent <- view psParentHeader - let spv = Pact5.pactSPV bhdb (_parentHeader parent) - let txCtx = TxContext parent miner + blockCtx <- view psBlockCtx -- TODO: trace more info? - let rk = Pact5.RequestKey $ Pact5._cmdHash cmd + let rk = Pact.RequestKey $ Pact._cmdHash cmd (resultOrError, blockHandle') <- liftIO $ trace' (logFunction logger) "applyCmd" computeTrace (\_ -> 0) $ - doPact5DbTransaction dbEnv blockHandle (Just rk) $ \pactDb -> - if _psIsGenesis env + doChainwebPactDbTransaction dbEnv blockHandle (Just rk) $ \pactDb spv -> + if _bctxIsGenesis blockCtx then do logFunctionText logger Debug "running genesis command!" - r <- runGenesisPayload logger pactDb spv txCtx cmd + r <- runGenesisPayload logger pactDb spv blockCtx cmd case r of - Left genesisError -> throwM $ Pact5GenesisCommandFailed (Pact5._cmdHash cmd) (sshow genesisError) + Left genesisError -> error $ "Genesis failed: \n" <> sshow cmd <> "\n" <> sshow genesisError <> "\n" -- pretend that genesis commands can throw non-fatal errors, -- to make types line up Right res -> return (Right (absurd <$> res)) - else applyCmd logger gasLogger pactDb txCtx txIdxInBlock spv initialGas cmd + else applyCmd logger gasLogger pactDb miner blockCtx txIdxInBlock spv initialGas cmd liftIO $ case resultOrError of -- unknown exceptions are logged specially, because they indicate bugs in Pact or chainweb Right - Pact5.CommandResult + Pact.CommandResult { _crResult = - Pact5.PactResultErr (Pact5.PEExecutionError (Pact5.UnknownException unknownExceptionMessage) _ _) + Pact.PactResultErr (Pact.PEExecutionError (Pact.UnknownException unknownExceptionMessage) _ _) } -> logFunctionText logger Error $ "Unknown exception encountered " <> unknownExceptionMessage Left gasBuyError -> liftIO $ logFunction logger Debug -- TODO: replace with better print function for gas buy errors - (Pact5TxFailureLog rk (sshow gasBuyError)) - Right Pact5.CommandResult - { _crResult = Pact5.PactResultErr err + (PactTxFailureLog rk (sshow gasBuyError)) + Right Pact.CommandResult + { _crResult = Pact.PactResultErr err } -> liftIO $ logFunction logger Debug - (Pact5TxFailureLog rk (sshow err)) + (PactTxFailureLog rk (sshow err)) _ -> return () return $ (,blockHandle') <$> resultOrError where computeTrace (Left gasPurchaseFailure, _) = Aeson.object [ "result" Aeson..= Aeson.String "gas purchase failure" - , "hash" Aeson..= J.toJsonViaEncode (Pact5._cmdHash cmd) + , "hash" Aeson..= J.toJsonViaEncode (Pact._cmdHash cmd) , "error" Aeson..= Aeson.String (sshow gasPurchaseFailure) ] computeTrace (Right result, _) = Aeson.object - [ "gas" Aeson..= Pact5._gas (Pact5._crGas result) - , "result" Aeson..= Aeson.String (case Pact5._crResult result of - Pact5.PactResultOk _ -> + [ "gas" Aeson..= Pact._gas (Pact._crGas result) + , "result" Aeson..= Aeson.String (case Pact._crResult result of + Pact.PactResultOk _ -> "success" - Pact5.PactResultErr _ -> + Pact.PactResultErr _ -> "failure" ) - , "hash" Aeson..= J.toJsonViaEncode (Pact5._cmdHash cmd) + , "hash" Aeson..= J.toJsonViaEncode (Pact._cmdHash cmd) ] -- | The principal validation logic for groups of Pact Transactions. @@ -468,20 +450,13 @@ applyPactCmd env miner txIdxInBlock tx = StateT $ \(blockHandle, blockGasRemaini validateParsedChainwebTx :: (Logger logger) => logger - -> ChainwebVersion - -> ChainId - -> Pact5Db - -> BlockHandle Pact5 - -> ParentCreationTime + -> ChainwebPactDb + -> BlockCtx -- ^ reference time for tx validation. - -> BlockHeight - -- ^ Current block height - -> Bool - -- ^ Genesis? - -> Pact5.Transaction + -> Pact.Transaction -> ExceptT InsertError IO () -validateParsedChainwebTx _logger v cid db _blockHandle txValidationTime bh isGenesis tx - | isGenesis = pure () +validateParsedChainwebTx _logger db blockCtx tx + | _bctxIsGenesis blockCtx = pure () | otherwise = do checkUnique tx checkTxHash tx @@ -490,183 +465,173 @@ validateParsedChainwebTx _logger v cid db _blockHandle txValidationTime bh isGen checkTimes tx return () where + cid = blockCtx ^. chainId + v = blockCtx ^. chainwebVersion + bh = _bctxCurrentBlockHeight blockCtx + txValidationTime = undefined checkChain :: ExceptT InsertError IO () - checkChain = unless (Pact5.assertChainId cid txCid) $ - throwError $ InsertErrorWrongChain (chainIdToText cid) (Pact5._chainId txCid) + checkChain = unless (Pact.assertChainId cid txCid) $ + throwError $ InsertErrorWrongChain (chainIdToText cid) (Pact._chainId txCid) where - txCid = view (Pact5.cmdPayload . Pact5.payloadObj . Pact5.pMeta . Pact5.pmChainId) tx + txCid = view (Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmChainId) tx - checkUnique :: Pact5.Transaction -> ExceptT InsertError IO () + checkUnique :: Pact.Transaction -> ExceptT InsertError IO () checkUnique t = do found <- liftIO $ - HashMap.lookup (coerce $ Pact5._cmdHash t) <$> - Pact5.lookupPactTransactions db - (V.singleton $ coerce $ Pact5._cmdHash t) + HashMap.lookup (coerce $ Pact._cmdHash t) <$> + Pact.lookupPactTransactions db + (V.singleton $ coerce $ Pact._cmdHash t) case found of Nothing -> pure () Just _ -> throwError InsertErrorDuplicate - checkTimes :: Pact5.Transaction -> ExceptT InsertError IO () + checkTimes :: Pact.Transaction -> ExceptT InsertError IO () checkTimes t = do if | skipTxTimingValidation v cid bh -> pure () - | not (Pact5.assertTxNotInFuture txValidationTime (view Pact5.payloadObj <$> t)) -> do + | not (Pact.assertTxNotInFuture txValidationTime (view Pact.payloadObj <$> t)) -> do throwError InsertErrorTimeInFuture - | not (Pact5.assertTxTimeRelativeToParent txValidationTime (view Pact5.payloadObj <$> t)) -> do + | not (Pact.assertTxTimeRelativeToParent txValidationTime (view Pact.payloadObj <$> t)) -> do throwError InsertErrorTTLExpired | otherwise -> do pure () - checkTxHash :: Pact5.Transaction -> ExceptT InsertError IO () + checkTxHash :: Pact.Transaction -> ExceptT InsertError IO () checkTxHash t = do - case Pact5.verifyHash (Pact5._cmdHash t) (SB.fromShort $ view Pact5.payloadBytes $ Pact5._cmdPayload t) of + case Pact.verifyHash (Pact._cmdHash t) (SB.fromShort $ view Pact.payloadBytes $ Pact._cmdPayload t) of Left _ | doCheckTxHash v cid bh -> throwError InsertErrorInvalidHash | otherwise -> pure () Right _ -> pure () - checkTxSigs :: Pact5.Transaction -> ExceptT InsertError IO () + checkTxSigs :: Pact.Transaction -> ExceptT InsertError IO () checkTxSigs t = do - case Pact5.assertValidateSigs hsh signers sigs of + case Pact.assertValidateSigs hsh signers sigs of Right _ -> do pure () Left err -> do throwError $ InsertErrorInvalidSigs (displayAssertValidateSigsError err) where - hsh = Pact5._cmdHash t - sigs = Pact5._cmdSigs t - signers = Pact5._pSigners $ view Pact5.payloadObj $ Pact5._cmdPayload t + hsh = Pact._cmdHash t + sigs = Pact._cmdSigs t + signers = Pact._pSigners $ view Pact.payloadObj $ Pact._cmdPayload t --- | The validation logic for Pact Transactions that have not had their --- code parsed yet. This is used by the mempool to estimate tx validity --- before inclusion into blocks, but it's also used by ExecBlock to check --- if all of the txs in a block are valid. --- --- Skips validation for genesis transactions, since gas accounts, etc. don't --- exist yet. --- -validateRawChainwebTx - :: (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> Pact5Db - -> BlockHandle Pact5 - -> ParentCreationTime - -- ^ reference time for tx validation. - -> BlockHeight - -- ^ Current block height - -> Bool - -- ^ Genesis? - -> Pact4.UnparsedTransaction - -> ExceptT InsertError IO Pact5.Transaction -validateRawChainwebTx logger v cid db blockHandle parentTime bh isGenesis tx = do - tx' <- either (throwError . InsertErrorPactParseError . Pact5.renderText) return $ Pact5.parsePact4Command tx - liftIO $ do - logDebug_ logger $ "validateRawChainwebTx: parse succeeded" - validateParsedChainwebTx logger v cid db blockHandle parentTime bh isGenesis tx' - return $! tx' +pact5TransactionsFromPayload + :: forall m + . MonadIO m + => PayloadData + -> ExceptT BlockInvalidError m (Vector Pact.Transaction) +pact5TransactionsFromPayload plData = do + vtrans <- liftIO $ + mapM toCWTransaction $ + toList (view payloadDataTransactions plData) + let (theLefts, theRights) = partitionEithers vtrans + unless (null theLefts) $ do + let ls = map T.pack theLefts + throwError $ BlockInvalidDueToTxDecodeFailure ls + return $! V.fromList theRights + where + toCWTransaction bs = + evaluate (force (codecDecode payloadCodec $ _transactionBytes bs)) execExistingBlock :: (CanReadablePayloadCas tbl, Logger logger) - => BlockHeader + => EvaluationCtx BlockPayloadHash -> CheckablePayload - -> PactBlockM logger tbl (P.Gas, PayloadWithOutputs) -execExistingBlock currHeader payload = do - parentBlockHeader <- view psParentHeader + -> ExceptT BlockInvalidError (PactBlockM logger tbl) (P.Gas, PayloadWithOutputs, Vector Pact.RequestKey) +execExistingBlock evaluationCtx payload = do + blockCtx <- view psBlockCtx let plData = checkablePayloadToPayloadData payload miner :: Miner <- decodeStrictOrThrow (_minerData $ view payloadDataMiner plData) - txs <- liftIO $ pact5TransactionsFromPayload plData + txs <- pact5TransactionsFromPayload plData logger <- view (psServiceEnv . psLogger) -- TODO: Pact5: ACTUALLY log gas _gasLogger <- view (psServiceEnv . psGasLogger) v <- view chainwebVersion cid <- view chainId db <- view psBlockDbEnv - isGenesis <- view psIsGenesis blockHandlePreCoinbase <- use pbBlockHandle let - txValidationTime = ParentCreationTime (view blockCreationTime $ _parentHeader parentBlockHeader) + isGenesis = _bctxIsGenesis blockCtx + txValidationTime = _bctxParentCreationTime blockCtx errors <- liftIO $ flip foldMap txs $ \tx -> do errorOrSuccess <- runExceptT $ validateParsedChainwebTx logger v cid db blockHandlePreCoinbase txValidationTime - (view blockHeight currHeader) + (_bctxCurrentBlockHeight blockCtx) isGenesis tx case errorOrSuccess of Right () -> return [] - Left err -> return [(Pact5._cmdHash tx, sshow err)] + Left err -> return [(Pact.RequestKey (Pact._cmdHash tx), err)] case NEL.nonEmpty errors of Nothing -> return () - Just errorsNel -> throwM $ Pact5TransactionValidationException errorsNel + Just errorsNel -> throwError $ BlockInvalidDueToInvalidTxs errorsNel - coinbaseResult <- runCoinbase miner >>= \case - Left err -> throwM $ CoinbaseFailure (Pact5CoinbaseFailure err) + coinbaseResult <- lift (runCoinbase miner) >>= \case + Left err -> throwError $ BlockInvalidDueToCoinbaseFailure err Right r -> return (absurd <$> r) -- TODO pact 5: make this less nasty? postCoinbaseBlockHandle <- use pbBlockHandle let blockGasLimit = - Pact5.GasLimit . Pact5.Gas . fromIntegral <$> maxBlockGasLimit v (view blockHeight currHeader) + Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit v (_bctxCurrentBlockHeight blockCtx) env <- ask (V.fromList -> results, (finalHandle, _finalBlockGasLimit)) <- - liftIO $ flip runStateT (postCoinbaseBlockHandle, blockGasLimit) $ + flip runStateT (postCoinbaseBlockHandle, blockGasLimit) $ forM (zip [0..] (V.toList txs)) $ \(txIdxInBlock, tx) -> - (tx,) <$> mapStateT - (either (throwM . Pact5BuyGasFailure) return <=< runExceptT) - (applyPactCmd env miner (TxBlockIdx txIdxInBlock) tx) + (tx,) <$> + (mapStateT (mapExceptT (liftIO . fmap (over _Left BlockInvalidDueToInvalidTxAtRuntime))) $ + applyPactCmd env miner (TxBlockIdx txIdxInBlock) tx) -- incorporate the final state of the transactions into the block state pbBlockHandle .= finalHandle - let !totalGasUsed = foldOf (folded . _2 . to Pact5._crGas) results + let !totalGasUsed = foldOf (folded . _2 . to Pact._crGas) results - pwo <- either throwM return $ - validateHashes currHeader payload miner (Transactions results coinbaseResult) - return (totalGasUsed, pwo) + pwo <- liftEither . over _Left BlockInvalidDueToOutputMismatch $ + validateHashes evaluationCtx payload miner (Transactions results coinbaseResult) + let reqKeys = Pact._crReqKey . snd <$> results + return (totalGasUsed, pwo, reqKeys) -- | Check that the two payloads agree. If we have access to the outputs, we -- check those too. validateHashes - :: BlockHeader + :: EvaluationCtx BlockPayloadHash + -- ^ expected payload hash -> CheckablePayload -> Miner - -> Transactions Pact5 (Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info)) - -> Either PactException PayloadWithOutputs -validateHashes bHeader payload miner transactions = - if newHash == prevHash - then do + -> Transactions Pact.Transaction Pact.OffChainCommandResult + -> Either BlockOutputMismatchError PayloadWithOutputs +validateHashes evaluationCtx payload miner transactions = + if newHash == _evaluationCtxPayload evaluationCtx + then Right actualPwo - else do - let jsonText = - J.encodeText $ J.object - [ "header" J..= J.encodeWithAeson (ObjectEncoded bHeader) - , "actual" J..= J.encodeWithAeson actualPwo - , "expected" J..?= case payload of - CheckablePayload _ -> Nothing - CheckablePayloadWithOutputs pwo -> Just $ J.encodeWithAeson pwo - ] - - Left (BlockValidationFailure $ BlockValidationFailureMsg jsonText) + else + Left $ BlockOutputMismatchError + { blockOutputMismatchCtx = evaluationCtx + , blockOutputMismatchActualPayload = actualPwo + , blockOutputMismatchExpectedPayload = case payload of + CheckablePayload _ -> Nothing + CheckablePayloadWithOutputs pwo -> Just pwo + } where - actualPwo = toPayloadWithOutputs Pact5T miner transactions + actualPwo = toPayloadWithOutputs miner transactions newHash = _payloadWithOutputsPayloadHash actualPwo - prevHash = view blockPayloadHash bHeader -- The following JSON encodings are used in the BlockValidationFailure message -data CRLogPair = CRLogPair Hash [Pact5.TxLog ByteString] +data CRLogPair = CRLogPair Hash [Pact.TxLog ByteString] instance J.Encode CRLogPair where build (CRLogPair h logs) = J.object [ "hash" J..= h , "rawLogs" J..= J.Array [ J.text (_txDomain <> ": " <> _txKey <> " -> " <> T.decodeUtf8 _txValue) - | Pact5.TxLog {..} <- logs + | Pact.TxLog {..} <- logs ] ] {-# INLINE build #-} diff --git a/src/Chainweb/Pact/RestAPI.hs b/src/Chainweb/Pact/RestAPI.hs index 16a4ffff30..ea211737a1 100644 --- a/src/Chainweb/Pact/RestAPI.hs +++ b/src/Chainweb/Pact/RestAPI.hs @@ -22,16 +22,14 @@ module Chainweb.Pact.RestAPI , pactApi -- ** Pact APIs for Individual commands -, PactLocalApi +, ApiLocal , pactLocalApi -, PactListenApi +, ApiListen , pactListenApi -, PactSendApi +, ApiSend , pactSendApi -, PactLocalWithQueryApi -, pactLocalWithQueryApi -, PactPollWithQueryApi -, pactPollWithQueryApi +, ApiPoll +, pactPollApi -- * Pact Spv Api , PactSpvApi , pactSpvApi @@ -56,8 +54,6 @@ module Chainweb.Pact.RestAPI import Data.Text (Text) -import qualified Pact.Types.Command as Pact -import qualified Pact.Server.API as Pact4 import Pact.Utils.Servant import Servant @@ -71,7 +67,8 @@ import Chainweb.Pact.Types import Chainweb.RestAPI.Utils import Chainweb.SPV.PayloadProof import Chainweb.Version -import qualified Pact.Core.Command.Server as Pact5 +import qualified Pact.Core.Command.Server as Pact +import qualified Pact.Core.Command.Types as Pact -- -------------------------------------------------------------------------- -- -- @POST /chainweb///chain//pact/@ @@ -81,11 +78,12 @@ type PactApi_ = "pact" :> "api" :> "v1" - :> ( Pact4.ApiSend - :<|> PactPollWithQueryApi_ - :<|> ApiListen - :<|> PactLocalWithQueryApi_ - ) + :> + ( ApiSend + :<|> ApiPoll + :<|> ApiListen + :<|> ApiLocal + ) type PactApi (v :: ChainwebVersionT) (c :: ChainIdT) = 'ChainwebEndpoint v :> ChainEndpoint c :> Reassoc PactApi_ @@ -106,60 +104,43 @@ type PactV1ApiEndpoint (v :: ChainwebVersionT) (c :: ChainIdT) api :> "v1" :> api -type PactLocalApi v c = PactV1ApiEndpoint v c Pact4.ApiLocal -type PactSendApi v c = PactV1ApiEndpoint v c Pact4.ApiSend -type PactListenApi v c = PactV1ApiEndpoint v c ApiListen +type ApiPoll + = "poll" + :> QueryParam "confirmationDepth" ConfirmationDepth + :> ReqBody '[PactJson] Pact.PollRequest + :> Post '[PactJson] Pact.PollResponse + +type ApiSend = "send" :> ReqBody '[PactJson] Pact.SendRequest :> Post '[PactJson] Pact.SendResponse + +type ApiListen = "listen" :> ReqBody '[PactJson] Pact.ListenRequest :> Post '[PactJson] Pact.ListenResponse -type ApiListen = ("listen" :> ReqBody '[PactJson] Pact5.ListenRequest :> Post '[PactJson] Pact5.ListenResponse) +type ApiLocal + = "local" + :> QueryParam "preflight" LocalPreflightSimulation + :> QueryParam "signatureVerification" LocalSignatureVerification + :> QueryParam "rewindDepth" RewindDepth + :> ReqBody '[PactJson] (Pact.Command Text) + :> Post '[PactJson] LocalResult pactLocalApi :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . Proxy (PactLocalApi v c) + . Proxy (PactV1ApiEndpoint v c ApiLocal) pactLocalApi = Proxy pactSendApi :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . Proxy (PactSendApi v c) + . Proxy (PactV1ApiEndpoint v c ApiSend) pactSendApi = Proxy pactListenApi :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . Proxy (PactListenApi v c) + . Proxy (PactV1ApiEndpoint v c ApiListen) pactListenApi = Proxy --- -------------------------------------------------------------------------- -- --- POST Queries for Pact Local Pre-flight - -type PactLocalWithQueryApi_ - = "local" - :> QueryParam "preflight" LocalPreflightSimulation - :> QueryParam "signatureVerification" LocalSignatureVerification - :> QueryParam "rewindDepth" RewindDepth - :> ReqBody '[PactJson] (Pact.Command Text) - :> Post '[PactJson] LocalResult - -type PactLocalWithQueryApi v c = PactV1ApiEndpoint v c PactLocalWithQueryApi_ - -pactLocalWithQueryApi - :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . Proxy (PactLocalWithQueryApi v c) -pactLocalWithQueryApi = Proxy - --- -------------------------------------------------------------------------- -- --- POST Queries for Pact Poll - -type PactPollWithQueryApi_ - = "poll" - :> QueryParam "confirmationDepth" ConfirmationDepth - :> ReqBody '[PactJson] Pact5.PollRequest - :> Post '[PactJson] Pact5.PollResponse - -type PactPollWithQueryApi v c = PactV1ApiEndpoint v c PactPollWithQueryApi_ - -pactPollWithQueryApi +pactPollApi :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . Proxy (PactPollWithQueryApi v c) -pactPollWithQueryApi = Proxy + . Proxy (PactV1ApiEndpoint v c ApiPoll) +pactPollApi = Proxy -- -------------------------------------------------------------------------- -- -- POST Pact Spv Transaction Proof diff --git a/src/Chainweb/Pact/RestAPI/Client.hs b/src/Chainweb/Pact/RestAPI/Client.hs index e416b0961d..ae5eb9156e 100644 --- a/src/Chainweb/Pact/RestAPI/Client.hs +++ b/src/Chainweb/Pact/RestAPI/Client.hs @@ -25,18 +25,14 @@ module Chainweb.Pact.RestAPI.Client , pactSendApiClient , pactLocalApiClient_ , pactLocalApiClient -, pactLocalWithQueryApiClient_ -, pactLocalWithQueryApiClient -, pactPollWithQueryApiClient_ -, pactPollWithQueryApiClient +, pactPollApiClient_ +, pactPollApiClient ) where import qualified Data.Text as T -import Pact.Types.API -import Pact.Types.Command -import Pact.Types.Hash +import Pact.Core.Command.Types import Servant.Client @@ -49,7 +45,7 @@ import Chainweb.Pact.RestAPI.SPV import Chainweb.Pact.Types import Chainweb.SPV.PayloadProof import Chainweb.Version -import qualified Pact.Core.Command.Server as Pact5 +import qualified Pact.Core.Command.Server as Pact -- -------------------------------------------------------------------------- -- -- Pact Spv Transaction Output Proof Client @@ -135,24 +131,6 @@ pactSpv2ApiClient -- Pact local pactLocalApiClient_ - :: forall (v :: ChainwebVersionT) (c :: ChainIdT) - . KnownChainwebVersionSymbol v - => KnownChainIdSymbol c - => Command T.Text - -> ClientM (CommandResult Hash) -pactLocalApiClient_ = client (pactLocalApi @v @c) - -pactLocalApiClient - :: ChainwebVersion - -> ChainId - -> Command T.Text - -> ClientM (CommandResult Hash) -pactLocalApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = pactLocalApiClient_ @v @c - -pactLocalWithQueryApiClient_ :: forall (v :: ChainwebVersionT) (c :: ChainIdT) . KnownChainwebVersionSymbol v => KnownChainIdSymbol c @@ -161,9 +139,9 @@ pactLocalWithQueryApiClient_ -> Maybe RewindDepth -> Command T.Text -> ClientM LocalResult -pactLocalWithQueryApiClient_ = client (pactLocalWithQueryApi @v @c) +pactLocalApiClient_ = client (pactLocalApi @v @c) -pactLocalWithQueryApiClient +pactLocalApiClient :: ChainwebVersion -> ChainId -> Maybe LocalPreflightSimulation @@ -171,10 +149,10 @@ pactLocalWithQueryApiClient -> Maybe RewindDepth -> Command T.Text -> ClientM LocalResult -pactLocalWithQueryApiClient +pactLocalApiClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) - = pactLocalWithQueryApiClient_ @v @c + = pactLocalApiClient_ @v @c -- -------------------------------------------------------------------------- -- -- Pact Listen @@ -183,15 +161,15 @@ pactListenApiClient_ :: forall (v :: ChainwebVersionT) (c :: ChainIdT) . KnownChainwebVersionSymbol v => KnownChainIdSymbol c - => Pact5.ListenRequest - -> ClientM Pact5.ListenResponse + => Pact.ListenRequest + -> ClientM Pact.ListenResponse pactListenApiClient_ = client (pactListenApi @v @c) pactListenApiClient :: ChainwebVersion -> ChainId - -> Pact5.ListenRequest - -> ClientM Pact5.ListenResponse + -> Pact.ListenRequest + -> ClientM Pact.ListenResponse pactListenApiClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) @@ -204,15 +182,15 @@ pactSendApiClient_ :: forall (v :: ChainwebVersionT) (c :: ChainIdT) . KnownChainwebVersionSymbol v => KnownChainIdSymbol c - => SubmitBatch - -> ClientM RequestKeys + => Pact.SendRequest + -> ClientM Pact.SendResponse pactSendApiClient_ = client (pactSendApi @v @c) pactSendApiClient :: ChainwebVersion -> ChainId - -> SubmitBatch - -> ClientM RequestKeys + -> Pact.SendRequest + -> ClientM Pact.SendResponse pactSendApiClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) @@ -221,20 +199,20 @@ pactSendApiClient -- -------------------------------------------------------------------------- -- -- Pact Poll -pactPollWithQueryApiClient_ +pactPollApiClient_ :: forall (v :: ChainwebVersionT) (c :: ChainIdT) . KnownChainwebVersionSymbol v => KnownChainIdSymbol c => Maybe ConfirmationDepth - -> Pact5.PollRequest - -> ClientM Pact5.PollResponse -pactPollWithQueryApiClient_ = client (pactPollWithQueryApi @v @c) + -> Pact.PollRequest + -> ClientM Pact.PollResponse +pactPollApiClient_ = client (pactPollApi @v @c) -pactPollWithQueryApiClient +pactPollApiClient :: ChainwebVersion -> ChainId -> Maybe ConfirmationDepth - -> Pact5.PollRequest - -> ClientM Pact5.PollResponse -pactPollWithQueryApiClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) confirmationDepth poll = do - pactPollWithQueryApiClient_ @v @c confirmationDepth poll + -> Pact.PollRequest + -> ClientM Pact.PollResponse +pactPollApiClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) confirmationDepth poll = do + pactPollApiClient_ @v @c confirmationDepth poll diff --git a/src/Chainweb/Pact/RestAPI/SPV.hs b/src/Chainweb/Pact/RestAPI/SPV.hs index bee0c09456..e5895de533 100644 --- a/src/Chainweb/Pact/RestAPI/SPV.hs +++ b/src/Chainweb/Pact/RestAPI/SPV.hs @@ -27,7 +27,7 @@ import GHC.Generics import Numeric.Natural -import Pact.Types.Command +import Pact.Core.Command.Types import Pact.JSON.Legacy.Value -- internal modules @@ -139,4 +139,3 @@ instance FromJSON Spv2Request where <$> o .: "subjectIdentifier" <*> o .:? "minimalProofDepth" .!= Nothing <*> o .: "algorithm" - diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index 800b7ceb58..cfb552822b 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -98,13 +98,12 @@ import qualified Chainweb.CutDB as CutDB import Chainweb.Graph import Chainweb.Logger import Chainweb.Mempool.Mempool - (InsertError(..), InsertType(..), MempoolBackend(..), TransactionHash(..), pact5RequestKeyToTransactionHash) + (InsertError(..), InsertType(..), MempoolBackend(..), TransactionHash(..), pactRequestKeyToTransactionHash) import Chainweb.Pact.RestAPI import Chainweb.Pact.RestAPI.EthSpv import Chainweb.Pact.RestAPI.SPV import Chainweb.Pact.Types -import Chainweb.Pact.SPV qualified as Pact4 -import Pact.Types.ChainMeta qualified as Pact4 +import Chainweb.Pact.SPV qualified as SPV import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.RestAPI.Orphans () @@ -114,39 +113,35 @@ import Chainweb.SPV.CreateProof import Chainweb.SPV.EventProof import Chainweb.SPV.OutputProof import Chainweb.SPV.PayloadProof -import qualified Chainweb.Pact.Transaction as Pact4 hiding (parsePact) -import qualified Chainweb.TreeDB as TreeDB +import Chainweb.Pact.Transaction qualified as Pact hiding (parsePact) +import Chainweb.TreeDB qualified as TreeDB import Chainweb.Utils import Chainweb.Version -import qualified Chainweb.Pact.Validations as Pact4 -import Chainweb.Version.Guards (isWebAuthnPrefixLegal, validPPKSchemes) -import Chainweb.WebPactExecutionService +import Chainweb.Pact.Validations qualified as Pact +import Chainweb.Version.Guards (validPPKSchemes) import qualified Pact.JSON.Encode as J -import qualified Pact.Parse as Pact4 -import qualified Pact.Types.API as Pact4 -import qualified Pact.Types.ChainId as Pact4 -import qualified Pact.Types.Command as Pact4 -import qualified Pact.Types.Hash as Pact4 - -import qualified Pact.Core.Command.Types as Pact5 -import qualified Pact.Core.Pretty as Pact5 -import qualified Chainweb.Pact.Transaction as Pact5 -import qualified Chainweb.Pact.Types as Pact5 -import qualified Chainweb.Pact.Validations as Pact5 + +import qualified Pact.Core.Command.Types as Pact +import qualified Pact.Core.Pretty as Pact +import qualified Chainweb.Pact.Transaction as Pact +import qualified Chainweb.Pact.Types as Pact +import qualified Chainweb.Pact.Validations as Pact import Data.Coerce -import qualified Pact.Core.Command.Server as Pact5 -import qualified Pact.Core.Errors as Pact5 -import qualified Pact.Core.Hash as Pact5 -import qualified Pact.Core.Gas as Pact5 +import qualified Pact.Core.Command.Server as Pact +import qualified Pact.Core.Errors as Pact +import qualified Pact.Core.Hash as Pact +import qualified Pact.Core.Gas as Pact +import qualified Pact.Core.Command.Client as Pact +import qualified Pact.Core.ChainData as Pact -- -------------------------------------------------------------------------- -- data PactServerData logger tbl = PactServerData { _pactServerDataCutDb :: !CutDB.CutDb - , _pactServerDataMempool :: !(MempoolBackend Pact4.UnparsedTransaction) + , _pactServerDataMempool :: !(MempoolBackend Pact.Transaction) , _pactServerDataLogger :: !logger - , _pactServerDataPact :: !PactExecutionService + -- , _pactServerDataPact :: !PactExecutionService , _pactServerDataPayloadDb :: !(PayloadDb tbl) } @@ -220,10 +215,10 @@ somePactServers v = mconcat . fmap (somePactServer . uncurry (somePactServerData v)) data PactCmdLog - = PactCmdLogSend (NonEmpty (Pact4.Command Text)) + = PactCmdLogSend (NonEmpty (Pact.Command Text)) | PactCmdLogPoll (NonEmpty Text) | PactCmdLogListen Text - | PactCmdLogLocal (Pact4.Command Text) + | PactCmdLogLocal (Pact.Command Text) | PactCmdLogSpv Text deriving (Show, Generic, NFData) @@ -257,21 +252,20 @@ instance ToJSON PactCmdLog where sendHandler :: Logger logger => logger - -> MempoolBackend Pact4.UnparsedTransaction - -> Pact4.SubmitBatch - -> Handler Pact4.RequestKeys -sendHandler logger mempool (Pact4.SubmitBatch cmds) = Handler $ do + -> MempoolBackend Pact.Transaction + -> Pact.SendRequest + -> Handler Pact.RequestKeys +sendHandler logger mempool (Pact.SendRequest (Pact.SubmitBatch cmds)) = Handler $ do liftIO $ logg Info (PactCmdLogSend cmds) - let cmdPayloads :: Either String (NonEmpty (Pact4.Command (ByteString, Pact4.Payload Pact4.PublicMeta Text))) - cmdPayloads = traverse (traverse (\t -> (encodeUtf8 t,) <$> eitherDecodeStrictText t)) cmds - case cmdPayloads of - Right (fmap Pact4.mkPayloadWithText -> cmdsWithParsedPayloads) -> do - let cmdsWithParsedPayloadsV = V.fromList $ NEL.toList cmdsWithParsedPayloads - -- If any of the txs in the batch fail validation, we reject them all. - liftIO (mempoolInsertCheckVerbose mempool cmdsWithParsedPayloadsV) >>= checkResult - liftIO (mempoolInsert mempool UncheckedInsert cmdsWithParsedPayloadsV) - return $! Pact4.RequestKeys $ NEL.map Pact4.cmdToRequestKey cmdsWithParsedPayloads - Left err -> failWith $ "reading JSON for transaction failed: " <> T.pack err + undefined + -- case cmds of + -- Right (fmap Pact.mkPayloadWithText -> cmdsWithParsedPayloads) -> do + -- let cmdsWithParsedPayloadsV = V.fromList $ NEL.toList cmdsWithParsedPayloads + -- -- If any of the txs in the batch fail validation, we reject them all. + -- liftIO (mempoolInsertCheckVerbose mempool cmdsWithParsedPayloadsV) >>= checkResult + -- liftIO (mempoolInsert mempool UncheckedInsert cmdsWithParsedPayloadsV) + -- return $! Pact.RequestKeys $ NEL.map Pact.cmdToRequestKey cmdsWithParsedPayloads + -- Left err -> failWith $ "reading JSON for transaction failed: " <> T.pack err where failWith :: Text -> ExceptT ServerError IO a failWith err = do @@ -280,7 +274,7 @@ sendHandler logger mempool (Pact4.SubmitBatch cmds) = Handler $ do logg = logFunctionJson (setComponent "send-handler" logger) - checkResult :: Vector (T2 TransactionHash (Either InsertError Pact4.UnparsedTransaction)) -> ExceptT ServerError IO () + checkResult :: Vector (T2 TransactionHash (Either InsertError Pact.Transaction)) -> ExceptT ServerError IO () checkResult vec | V.null vec = return () | otherwise = do @@ -304,14 +298,14 @@ pollHandler -> CutDB.CutDb -> PayloadDb tbl -> ChainId - -> PactExecutionService - -> MempoolBackend Pact4.UnparsedTransaction + -> PactLookup + -> MempoolBackend Pact.Transaction -> Maybe ConfirmationDepth - -> Pact5.PollRequest - -> Handler Pact5.PollResponse -pollHandler logger cdb pdb cid pact mem confDepth (Pact5.PollRequest request) = do - liftIO $! logg Info $ PactCmdLogPoll $ fmap Pact5.requestKeyToB64Text request - Pact5.PollResponse <$!> liftIO (internalPoll logger pdb bdb mem pact confDepth request) + -> Pact.PollRequest + -> Handler Pact.PollResponse +pollHandler logger cdb pdb cid pact mem confDepth (Pact.PollRequest request) = do + liftIO $! logg Info $ PactCmdLogPoll $ fmap Pact.requestKeyToB64Text request + Pact.PollResponse <$!> liftIO (internalPoll logger pdb bdb mem pact confDepth request) where bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb logg = logFunctionJson (setComponent "poll-handler" logger) @@ -326,36 +320,36 @@ listenHandler -> CutDB.CutDb -> PayloadDb tbl -> ChainId - -> PactExecutionService - -> MempoolBackend Pact4.UnparsedTransaction - -> Pact5.ListenRequest - -> Handler Pact5.ListenResponse -listenHandler logger cdb pdb cid pact mem (Pact5.ListenRequest key) = do - liftIO $ logg Info $ PactCmdLogListen $ Pact5.requestKeyToB64Text key + -> PactLookup + -> MempoolBackend Pact.Transaction + -> Pact.ListenRequest + -> Handler Pact.ListenResponse +listenHandler logger cdb pdb cid pact mem (Pact.ListenRequest key) = do + liftIO $ logg Info $ PactCmdLogListen $ Pact.requestKeyToB64Text key liftIO (registerDelay defaultTimeout) >>= runListen where bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb logg = logFunctionJson (setComponent "listen-handler" logger) - runListen :: TVar Bool -> Handler Pact5.ListenResponse + runListen :: TVar Bool -> Handler Pact.ListenResponse runListen timedOut = do startCut <- liftIO $ CutDB._cut cdb case HM.lookup cid (_cutMap startCut) of Nothing -> throwError err504 Just bh -> poll bh where - go :: BlockHeader -> Handler Pact5.ListenResponse + go :: BlockHeader -> Handler Pact.ListenResponse go !prevBlock = do m <- liftIO $ waitForNewBlock prevBlock case m of Nothing -> throwError err504 Just block -> poll block - poll :: BlockHeader -> Handler Pact5.ListenResponse + poll :: BlockHeader -> Handler Pact.ListenResponse poll bh = do hm <- liftIO $ internalPoll logger pdb bdb mem pact Nothing (pure key) if HM.null hm then go bh - else pure $! Pact5.ListenResponse $ snd $ unsafeHead "Chainweb.Pact.RestAPI.Server.listenHandler.poll" $ HM.toList hm + else pure $! Pact.ListenResponse $ snd $ unsafeHead "Chainweb.Pact.RestAPI.Server.listenHandler.poll" $ HM.toList hm waitForNewBlock :: BlockHeader -> IO (Maybe BlockHeader) waitForNewBlock lastBlockHeader = atomically $ do @@ -372,18 +366,25 @@ listenHandler logger cdb pdb cid pact mem (Pact5.ListenRequest key) = do -- -------------------------------------------------------------------------- -- -- Local Handler +type PactLocal = + Maybe LocalPreflightSimulation + -> Maybe LocalSignatureVerification + -> Maybe RewindDepth + -> Transaction + -> IO LocalResult + -- TODO: convert to Pact 5? localHandler :: Logger logger => logger - -> PactExecutionService + -> PactLocal -> Maybe LocalPreflightSimulation -- ^ Preflight flag -> Maybe LocalSignatureVerification -- ^ No sig verification flag -> Maybe RewindDepth -- ^ Rewind depth - -> Pact4.Command Text + -> Pact.Command Text -> Handler LocalResult localHandler logger pact preflight sigVerify rewindDepth cmd = do liftIO $ logg Info $ PactCmdLogLocal cmd @@ -392,14 +393,12 @@ localHandler logger pact preflight sigVerify rewindDepth cmd = do Left err -> throwError $ setErrText ("Validation failed: " <> T.pack err) err400 - r <- liftIO $ try $ _pactLocal pact preflight sigVerify rewindDepth cmd' + r <- liftIO $ pact preflight sigVerify rewindDepth cmd' case r of - Left (err :: PactException) -> throwError $ setErrText - ("Execution failed: " <> T.pack (show err)) err400 - Right (preview _MetadataValidationFailure -> Just e) -> do - throwError $ setErrText - ("Metadata validation failed: " <> decodeUtf8 (BSL.toStrict (Aeson.encode e))) err400 - Right lr -> return $! lr + -- (preview _MetadataValidationFailure -> Just e) -> do + -- throwError $ setErrText + -- ("Metadata validation failed: " <> decodeUtf8 (BSL.toStrict (Aeson.encode e))) err400 + lr -> return $! lr where logg = logFunctionJson (setComponent "local-handler" logger) @@ -412,15 +411,16 @@ localHandler logger pact preflight sigVerify rewindDepth cmd = do -- down in the 'execLocal' code, 'noSigVerify' triggers a nop on -- checking again if 'preflight' is set. -- - let payloadBS = encodeUtf8 (Pact4._cmdPayload cmd) + let payloadBS = encodeUtf8 (Pact._cmdPayload cmd) - void $ Pact4.verifyHash @'Pact4.Blake2b_256 (Pact4._cmdHash cmd) payloadBS + void $ Pact.verifyHash (Pact._cmdHash cmd) payloadBS decoded <- eitherDecodeStrict' payloadBS - let cmd' = cmd { Pact4._cmdPayload = (payloadBS, decoded) } - pure $ Pact4.mkPayloadWithText cmd' - | otherwise = Pact4.mkPayloadWithText <$> - traverse (\bs -> (encodeUtf8 bs,) <$> eitherDecodeStrictText bs) cmd + let cmd' = cmd { Pact._cmdPayload = (payloadBS, decoded) } + undefined + -- pure $ Pact.mkPayloadWithText cmd' + | otherwise = undefined -- Pact.mkPayloadWithText <$> + -- traverse (\bs -> (encodeUtf8 bs,) <$> eitherDecodeStrictText bs) cmd -- -------------------------------------------------------------------------- -- -- Cross Chain SPV Handler @@ -433,7 +433,7 @@ spvHandler -> CutDB.CutDb -- ^ cut db -> PayloadDb tbl - -> PactExecutionService + -> PactLookup -> ChainId -- ^ the chain id of the source chain id used in the -- execution of a cross-chain-transfer. @@ -443,19 +443,18 @@ spvHandler -- Also contains the request key of of the cross-chain transfer -- tx request. -> Handler TransactionOutputProofB64 -spvHandler l cdb pdb pe cid (SpvRequest rk (Pact4.ChainId ptid)) = do - validateRequestKey rk +spvHandler l cdb pdb pe cid (SpvRequest rk (Pact.ChainId ptid)) = do liftIO $! logg (sshow ph) - T2 bhe _bha <- liftIO (try $ _pactLookup pe cid Nothing (pure $ coerce $ Pact4.toUntypedHash ph)) >>= \case - Left (e :: PactException) -> - toErr $ "Internal error: transaction hash lookup failed: " <> sshow e - Right v -> case HM.lookup (coerce $ Pact4.toUntypedHash ph) v of + T2 bhe _bha <- liftIO (pe cid Nothing (pure $ Pact.unHash ph)) >>= \v -> + -- Left (e :: PactException) -> + -- toErr $ "Internal error: transaction hash lookup failed: " <> sshow e + case HM.lookup (Pact.unHash ph) v of Nothing -> toErr $ "Transaction hash not found: " <> sshow ph Just t -> return t - idx <- liftIO (Pact4.getTxIdx bdb pdb bhe ph) >>= \case + idx <- undefined >>= \case -- liftIO (Pact.getTxIdx bdb pdb bhe ph) >>= \case Left e -> toErr $ "Internal error: Index lookup for hash failed: " <> sshow e @@ -473,7 +472,7 @@ spvHandler l cdb pdb pe cid (SpvRequest rk (Pact4.ChainId ptid)) = do return $! b64 p where - ph = Pact4.fromUntypedHash $ Pact4.unRequestKey rk + ph = Pact.unRequestKey rk bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb b64 = TransactionOutputProofB64 . encodeB64UrlNoPaddingText @@ -496,7 +495,7 @@ spv2Handler -> CutDB.CutDb -- ^ CutDb contains the cut, payload, and block db -> PayloadDb tbl - -> PactExecutionService + -> PactLookup -> ChainId -- ^ ChainId of the target -> Spv2Request @@ -505,7 +504,7 @@ spv2Handler -- Also contains the request key of of the cross-chain transfer -- tx request. -> Handler SomePayloadProof -spv2Handler l cdb pdb pe cid r = case _spvSubjectIdType sid of +spv2Handler l cdb pdb pactLookup cid r = case _spvSubjectIdType sid of SpvSubjectResult | _spv2ReqAlgorithm r /= SpvSHA512t_256 -> toErr $ "Algorithm " <> sshow r <> " is not supported with SPV result proofs." @@ -521,15 +520,14 @@ spv2Handler l cdb pdb pe cid r = case _spvSubjectIdType sid of :: forall a . MerkleHashAlgorithm a => MerkleHashAlgorithmName a - => (BlockHeaderDb -> PayloadDb tbl -> Natural -> BlockHash -> Pact4.RequestKey -> IO (PayloadProof a)) + => (BlockHeaderDb -> PayloadDb tbl -> Natural -> BlockHash -> Pact.RequestKey -> IO (PayloadProof a)) -> Handler SomePayloadProof proof f = SomePayloadProof <$> do - validateRequestKey rk liftIO $! logg (sshow ph) - T2 bhe bha <- liftIO (try $ _pactLookup pe cid Nothing (pure $ coerce ph)) >>= \case - Left (e :: PactException) -> - toErr $ "Internal error: transaction hash lookup failed: " <> sshow e - Right v -> case HM.lookup (coerce ph) v of + T2 bhe bha <- liftIO (pactLookup Nothing (pure $ coerce ph)) >>= + -- Left (e :: PactException) -> + -- toErr $ "Internal error: transaction hash lookup failed: " <> sshow e + \v -> case HM.lookup (coerce ph) v of Nothing -> toErr $ "Transaction hash not found: " <> sshow ph Just t -> return t @@ -542,7 +540,7 @@ spv2Handler l cdb pdb pe cid r = case _spvSubjectIdType sid of sid = _spv2ReqSubjectIdentifier r rk = _spvSubjectIdReqKey sid - ph = Pact4.unRequestKey rk + ph = Pact.unRequestKey rk bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb logg = logFunctionJson (setComponent "spv-handler" l) Info @@ -604,24 +602,29 @@ ethSpvHandler req = do -- --------------------------------------------------------------------------- -- -- Poll Helper +type PactLookup = + Maybe ConfirmationDepth + -> Vector SB.ShortByteString + -> IO (HashMap SB.ShortByteString (T2 BlockHeight BlockHash)) + internalPoll :: (CanReadablePayloadCas tbl, Logger logger) => logger -> PayloadDb tbl -> BlockHeaderDb - -> MempoolBackend Pact4.UnparsedTransaction - -> PactExecutionService + -> MempoolBackend Pact.Transaction + -> PactLookup -> Maybe ConfirmationDepth - -> NonEmpty Pact5.RequestKey - -> IO (HashMap Pact5.RequestKey (Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError)) -internalPoll logger pdb bhdb mempool pactEx confDepth requestKeys0 = do + -> NonEmpty Pact.RequestKey + -> IO (HashMap Pact.RequestKey (Pact.CommandResult Pact.Hash Pact.PactOnChainError)) +internalPoll logger pdb bhdb mempool pactLookup confDepth requestKeys0 = do let dbg txt = logFunctionText logger Debug txt -- get leaf block header for our chain from current best cut - results0 <- _pactLookup pactEx cid confDepth (coerce requestKeys) + results0 <- pactLookup confDepth (coerce requestKeys) dbg $ "internalPoll.results0: " <> sshow results0 -- TODO: are we sure that all of these are raised locally. This will cause the -- server to shut down the connection without returning a result to the user. - let results1 = V.map (\rk -> (rk, HM.lookup (coerce $ Pact5.unRequestKey rk) results0)) requestKeysV + let results1 = V.map (\rk -> (rk, HM.lookup (coerce $ Pact.unRequestKey rk) results0)) requestKeysV let (present0, missing) = V.unstablePartition (isJust . snd) results1 let present = V.map (second fromJuste) present0 badlisted <- V.toList <$> checkBadList (V.map fst missing) @@ -634,21 +637,21 @@ internalPoll logger pdb bhdb mempool pactEx confDepth requestKeys0 = do where cid = _chainId bhdb !requestKeysV = V.fromList $ NEL.toList requestKeys0 - !requestKeys = V.map Pact5.unRequestKey requestKeysV + !requestKeys = V.map Pact.unRequestKey requestKeysV lookup - :: (Pact5.RequestKey, T2 BlockHeight BlockHash) - -> IO (Either String (Maybe (Pact5.RequestKey, Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError))) + :: (Pact.RequestKey, T2 BlockHeight BlockHash) + -> IO (Either String (Maybe (Pact.RequestKey, Pact.CommandResult Pact.Hash Pact.PactOnChainError))) lookup (key, T2 _ ha) = (fmap . fmap . fmap) (key,) $ lookupRequestKey key ha -- TODO: group by block for performance (not very important right now) lookupRequestKey - :: Pact5.RequestKey + :: Pact.RequestKey -> BlockHash - -> IO (Either String (Maybe (Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError))) + -> IO (Either String (Maybe (Pact.CommandResult Pact.Hash Pact.PactOnChainError))) lookupRequestKey key bHash = runExceptT $ do - let pactHash = Pact5.unRequestKey key - let matchingHash = (== pactHash) . Pact5._cmdHash . fst + let pactHash = Pact.unRequestKey key + let matchingHash = (== pactHash) . Pact._cmdHash . fst blockHeader <- liftIO (TreeDB.lookup bhdb bHash) >>= \case Nothing -> throwError $ "missing block header: " <> sshow key Just x -> return x @@ -664,41 +667,41 @@ internalPoll logger pdb bhdb mempool pactEx confDepth requestKeys0 = do Just (_cmd, TransactionOutput output) -> do out <- case eitherDecodeStrict' output of Left err -> throwError $ - "error decoding tx output for command " <> sshow (Pact5._cmdHash _cmd) <> ": " <> err + "error decoding tx output for command " <> sshow (Pact._cmdHash _cmd) <> ": " <> err Right decodedOutput -> return decodedOutput - when (Pact5._crReqKey out /= key) $ + when (Pact._crReqKey out /= key) $ throwError "internal error: Transaction output doesn't match its hash!" return $ Just $ enrichCR blockHeader out Nothing -> return Nothing - fromTx :: (Transaction, TransactionOutput) -> ExceptT String IO (Pact5.Command Text, TransactionOutput) + fromTx :: (Transaction, TransactionOutput) -> ExceptT String IO (Pact.Command Text, TransactionOutput) fromTx (Transaction txBytes, !out) = do !tx' <- except $ eitherDecodeStrict' txBytes & _Left %~ (\decodeErr -> "Transaction failed to decode: " <> decodeErr) return (tx', out) - checkBadList :: Vector Pact5.RequestKey -> IO (Vector (Pact5.RequestKey, Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError)) + checkBadList :: Vector Pact.RequestKey -> IO (Vector (Pact.RequestKey, Pact.CommandResult Pact.Hash Pact.PactOnChainError)) checkBadList rkeys = do - let !hashes = V.map pact5RequestKeyToTransactionHash rkeys + let !hashes = V.map pactRequestKeyToTransactionHash rkeys out <- mempoolCheckBadList mempool hashes - let bad = V.map (Pact5.RequestKey . Pact5.Hash . unTransactionHash . fst) $ + let bad = V.map (Pact.RequestKey . Pact.Hash . unTransactionHash . fst) $ V.filter snd $ V.zip hashes out return $! V.map hashIsOnBadList bad - hashIsOnBadList :: Pact5.RequestKey -> (Pact5.RequestKey, Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError) + hashIsOnBadList :: Pact.RequestKey -> (Pact.RequestKey, Pact.CommandResult Pact.Hash Pact.PactOnChainError) hashIsOnBadList rk = - let res = Pact5.PactResultErr err - err = Pact5.PactOnChainError + let res = Pact.PactResultErr err + err = Pact.PactOnChainError -- the only legal error type, once chainweaver is really gone, we -- can use a real error type - (Pact5.ErrorType "TxFailure") - (Pact5.mkBoundedText "Transaction is badlisted because it previously failed to validate.") - (Pact5.LocatedErrorInfo Pact5.TopLevelErrorOrigin Pact5.noInfo) - !cr = Pact5.CommandResult rk Nothing res (mempty :: Pact5.Gas) Nothing Nothing Nothing [] + (Pact.ErrorType "TxFailure") + (Pact.mkBoundedText "Transaction is badlisted because it previously failed to validate.") + (Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin Pact.noInfo) + !cr = Pact.CommandResult rk Nothing res (mempty :: Pact.Gas) Nothing Nothing Nothing [] in (rk, cr) - enrichCR :: BlockHeader -> Pact5.CommandResult i e -> Pact5.CommandResult i e - enrichCR bh = set Pact5.crMetaData + enrichCR :: BlockHeader -> Pact.CommandResult i e -> Pact.CommandResult i e + enrichCR bh = set Pact.crMetaData (Just $ object [ "blockHeight" .= view blockHeight bh , "blockTime" .= view blockCreationTime bh @@ -714,51 +717,28 @@ barf e = maybe (throwError e) return -- TODO: all of the functions in this module can instead grab the current block height from consensus -- and pass it here to get a better estimate of what behavior is correct. -validateCommand :: ChainwebVersion -> ChainId -> Pact4.Command Text -> Either Text Pact4.Transaction +validateCommand :: ChainwebVersion -> ChainId -> Pact.Command Text -> Either Text Pact.Transaction validateCommand v cid (fmap encodeUtf8 -> cmdBs) = case parsedCmd of - Right (commandParsed :: Pact4.Transaction) -> - case Pact4.assertCommand commandParsed (validPPKSchemes v cid bh) (isWebAuthnPrefixLegal v cid bh) of - Left err -> Left $ "Command failed validation: " <> Pact4.displayAssertCommandError err + Right (commandParsed :: Pact.Transaction) -> + case Pact.assertCommand commandParsed of + Left err -> Left $ "Command failed validation: " <> Pact.displayAssertCommandError err Right () -> Right commandParsed Left e -> Left $ "Pact parsing error: " <> T.pack e where bh = maxBound :: BlockHeight decodeAndParse bs = - traverse (Pact4.parsePact) =<< Aeson.eitherDecodeStrict' bs - parsedCmd = Pact4.mkPayloadWithText <$> - Pact4.cmdPayload (\bs -> (bs,) <$> decodeAndParse bs) cmdBs + traverse (Pact.parsePact) =<< Aeson.eitherDecodeStrict' bs + parsedCmd = Pact.mkPayloadWithText <$> + Pact.cmdPayload (\bs -> (bs,) <$> decodeAndParse bs) cmdBs -- TODO: all of the functions in this module can instead grab the current block height from consensus -- and pass it here to get a better estimate of what behavior is correct. -validatePact5Command :: ChainwebVersion -> Pact5.Command Text -> Either String Pact5.Transaction +validatePact5Command :: ChainwebVersion -> Pact.Command Text -> Either String Pact.Transaction validatePact5Command _v cmdText = case parsedCmd of - Right (commandParsed :: Pact5.Transaction) -> - if isRight (Pact5.assertCommand commandParsed) + Right (commandParsed :: Pact.Transaction) -> + if isRight (Pact.assertCommand commandParsed) then Right commandParsed else Left "Command failed validation" - Left e -> Left $ "Pact parsing error: " ++ Pact5.renderCompactString e - where - parsedCmd = Pact5.parseCommand cmdText - --- | Validate the length of the request key's underlying hash. --- -validateRequestKey :: Pact4.RequestKey -> Handler () -validateRequestKey (Pact4.RequestKey h'@(Pact4.Hash h)) - | keyLength == blakeHashLength = return () - | otherwise = throwError $ setErrText - ( "Request Key " - <> Pact4.hashToText h' - <> " has incorrect hash of length " - <> sshow keyLength - ) err400 + Left e -> Left $ "Pact parsing error: " ++ Pact.renderCompactString e where - -- length of the encoded request key hash - -- - keyLength = SB.length h - - -- Blake hash length = 32 - the length of a - -- Blake2b_256 hash - -- - blakeHashLength :: Int - blakeHashLength = Pact4.hashLength Pact4.Blake2b_256 -{-# INLINE validateRequestKey #-} + parsedCmd = Pact.parseCommand cmdText diff --git a/src/Chainweb/Pact/SPV.hs b/src/Chainweb/Pact/SPV.hs index a0c167033e..70f45d390f 100644 --- a/src/Chainweb/Pact/SPV.hs +++ b/src/Chainweb/Pact/SPV.hs @@ -2,23 +2,21 @@ ImportQualifiedPost , LambdaCase , OverloadedStrings + , OverloadedRecordDot , ScopedTypeVariables , TypeApplications #-} +{-# LANGUAGE FlexibleContexts #-} module Chainweb.Pact.SPV (pactSPV) where -import Chainweb.BlockHeader (BlockHeader, blockHash, blockHeight) -import Chainweb.BlockHeaderDB (BlockHeaderDb) import Chainweb.Payload (TransactionOutput(..)) -import Chainweb.SPV (SpvException(..), TransactionOutputProof(..), outputProofChainId) -import Chainweb.SPV.VerifyProof (verifyTransactionOutputProofAt_) -import Chainweb.Utils (decodeB64UrlNoPaddingText) +import Chainweb.SPV (TransactionOutputProof(..), outputProofChainId) +import Chainweb.SPV.VerifyProof (runTransactionOutputProof) +import Chainweb.Utils (decodeB64UrlNoPaddingText, unlessM) import Chainweb.Version qualified as CW -import Chainweb.Version.Guards qualified as CW import Control.Lens import Control.Monad (when) -import Control.Monad.Catch (catch, throwM) import Control.Monad.Except (ExceptT, runExceptT, throwError) import Control.Monad.IO.Class (liftIO) import Crypto.Hash.Algorithms (SHA512t_256) @@ -29,24 +27,32 @@ import Pact.Core.Command.Types (CommandResult(..), PactResult(..)) import Pact.Core.DefPacts.Types (DefPactExec(..)) import Pact.Core.Hash (Hash(..)) import Pact.Core.PactValue (ObjectData(..), PactValue(..)) -import Pact.Core.SPV (ContProof(..), SPVSupport(..)) +import Pact.Core.SPV (SPVSupport(..), ContProof (..)) import Pact.Core.StableEncoding (encodeStable) +import Chainweb.Crypto.MerkleLog +import Chainweb.Pact.Backend.Types -pactSPV :: BlockHeaderDb -> BlockHeader -> SPVSupport -pactSPV bdb bh = SPVSupport - { _spvSupport = \proofType proof -> verifySPV bdb bh proofType proof - , _spvVerifyContinuation = \contProof -> verifyCont bdb bh contProof +pactSPV :: HeaderOracle -> SPVSupport +pactSPV oracle = SPVSupport + { _spvSupport = \proofType proof -> verifySPV oracle proofType proof + , _spvVerifyContinuation = \contProof -> verifyCont oracle contProof } +checkProofAndExtractOutput :: HeaderOracle -> TransactionOutputProof SHA512t_256 -> ExceptT Text IO TransactionOutput +checkProofAndExtractOutput oracle proof@(TransactionOutputProof _cid p) = do + let h = runTransactionOutputProof proof + unlessM (liftIO $ oracle.consult h) $ throwError + "spv verification failed: target header is not in the chain" + proofSubject p + -- | Attempt to verify an SPV proof of a continuation given -- a continuation payload object bytestring. On success, returns -- the 'DefPactExec' associated with the proof. verifyCont :: () - => BlockHeaderDb - -> BlockHeader + => HeaderOracle -> ContProof -> IO (Either Text DefPactExec) -verifyCont bdb bh (ContProof base64Proof) = runExceptT $ do +verifyCont bdb (ContProof base64Proof) = runExceptT $ do proofBytes <- case decodeB64UrlNoPaddingText (Text.decodeUtf8 base64Proof) of Left _ -> throwError "verifyCont: Invalid base64-encoded transaction output proof" Right bs -> return bs @@ -64,7 +70,7 @@ verifyCont bdb bh (ContProof base64Proof) = runExceptT $ do -- 1. Verify SPV TransactionOutput proof via Chainweb SPV API -- 2. Decode tx outputs to 'CommandResult' 'Hash' _ -- 3. Extract continuation 'DefPactExec' from decoded result and return the cont exec object - TransactionOutput proof <- catchAndDisplaySPVError bh $ liftIO $ verifyTransactionOutputProofAt_ bdb outputProof (view blockHash bh) + TransactionOutput proof <- checkProofAndExtractOutput bdb outputProof -- TODO: Do we care about the error type here? commandResult <- case Aeson.decodeStrict' @(CommandResult Hash Aeson.Value) proof of @@ -76,23 +82,20 @@ verifyCont bdb bh (ContProof base64Proof) = runExceptT $ do Just defpactExec -> return defpactExec verifySPV :: () - => BlockHeaderDb - -> BlockHeader + => HeaderOracle -> Text -- ^ ETH or TXOUT - defines the type of proof used in validation -> ObjectData PactValue -> IO (Either Text (ObjectData PactValue)) -verifySPV bdb bh proofType proof = runExceptT $ do - let cid = CW._chainId bdb - +verifySPV oracle proofType proof = runExceptT $ do case proofType of "ETH" -> do - throwError "verifySPV: ETH proof type is not yet supported in Pact5." + throwError "verifySPV: ETH proof type is not yet supported in Pact." "TXOUT" -> do outputProof <- case pactObjectOutputProof proof of Left err -> throwError err Right u -> return u - when (view outputProofChainId outputProof /= cid) $ + when (view outputProofChainId outputProof /= oracle.chain) $ throwError "verifySPV: cannot redeem spv proof on wrong target chain" -- SPV proof verification is a 3-step process: @@ -100,7 +103,8 @@ verifySPV bdb bh proofType proof = runExceptT $ do -- 2. Decode tx outputs to 'CommandResult' 'Hash' _ -- 3. Extract tx outputs as a pact object and return the object - TransactionOutput rawCommandResult <- catchAndDisplaySPVError bh $ liftIO $ verifyTransactionOutputProofAt_ bdb outputProof (view blockHash bh) + TransactionOutput rawCommandResult <- + checkProofAndExtractOutput oracle outputProof commandResult <- case Aeson.decodeStrict' @(CommandResult Hash Aeson.Value) rawCommandResult of Nothing -> throwError "verifySPV: Unable to decode SPV transaction output" @@ -123,11 +127,3 @@ pactObjectOutputProof (ObjectData o) = do case Aeson.decodeStrict' @(TransactionOutputProof SHA512t_256) $ encodeStable o of Nothing -> Left "pactObjectOutputProof: Failed to decode proof object" Just outputProof -> Right outputProof - -catchAndDisplaySPVError :: BlockHeader -> ExceptT Text IO a -> ExceptT Text IO a -catchAndDisplaySPVError bh eio = - if CW.chainweb219Pact (CW._chainwebVersion bh) (CW._chainId bh) (view blockHeight bh) - then catch eio $ \case - SpvExceptionVerificationFailed m -> throwError ("spv verification failed: " <> m) - spvErr -> throwM spvErr - else eio diff --git a/src/Chainweb/Pact/Service/PactInProcApi.hs b/src/Chainweb/Pact/Service/PactInProcApi.hs deleted file mode 100644 index 9678b994a6..0000000000 --- a/src/Chainweb/Pact/Service/PactInProcApi.hs +++ /dev/null @@ -1,176 +0,0 @@ -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeApplications #-} - --- | --- Module: Chainweb.Pact.Service.PactInProcApi --- Copyright: Copyright © 2018 Kadena LLC. --- License: See LICENSE file --- Maintainer: Mark Nichols --- Stability: experimental --- --- Pact execution (in-process) API for Chainweb - -module Chainweb.Pact.Service.PactInProcApi - ( withPactService - , withPactService' - , pactMemPoolAccess - ) where - -import Control.Concurrent.Async - -import Data.IORef -import Data.Vector (Vector) - -import System.LogLevel - -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Logger -import Chainweb.Mempool.Consensus -import Chainweb.Mempool.Mempool - -import Chainweb.Pact.Backend.Utils -import Chainweb.Pact.Types -import qualified Chainweb.Pact.PactService as PS -import Chainweb.Pact.Service.PactQueue -import Chainweb.Payload.PayloadStore -import qualified Chainweb.Pact.Transaction as Pact4 -import Chainweb.Utils -import Chainweb.Version - -import Data.LogMessage - -import GHC.Stack (HasCallStack) -import Chainweb.Counter (Counter) -import Chainweb.BlockCreationTime -import Chainweb.Pact.Backend.Types - --- | Initialization for Pact (in process) Api -withPactService - :: CanReadablePayloadCas tbl - => Logger logger - => ChainwebVersion - -> ChainId - -> logger - -> Maybe (Counter "txFailures") - -> MempoolConsensus - -> BlockHeaderDb - -> PayloadDb tbl - -> FilePath - -> PactServiceConfig - -> (PactQueue -> IO a) - -> IO a -withPactService ver cid logger txFailuresCounter mpc bhdb pdb pactDbDir config action = - withSqliteDb cid logger pactDbDir (_pactResetDb config) $ \sqlenv -> - withPactService' ver cid logger txFailuresCounter mpa bhdb pdb sqlenv config action - where - mpa = pactMemPoolAccess mpc $ addLabel ("sub-component", "MempoolAccess") logger - --- | Alternate Initialization for Pact (in process) Api, only used directly in --- tests to provide memPool with test transactions -withPactService' - :: CanReadablePayloadCas tbl - => Logger logger - => HasCallStack - => ChainwebVersion - -> ChainId - -> logger - -> Maybe (Counter "txFailures") - -> MemPoolAccess - -> BlockHeaderDb - -> PayloadDb tbl - -> SQLiteEnv - -> PactServiceConfig - -> (PactQueue -> IO a) - -> IO a -withPactService' ver cid logger txFailuresCounter memPoolAccess bhDb pdb sqlenv config action = do - reqQ <- newPactQueue (_pactQueueSize config) - race (concurrently_ (monitor reqQ) (server reqQ)) (action reqQ) >>= \case - Left () -> error "Chainweb.Pact.Service.PactInProcApi: pact service terminated unexpectedly" - Right a -> return a - where - server reqQ = runForever logg "pact-service" - $ PS.runPactService ver cid logger txFailuresCounter reqQ memPoolAccess bhDb pdb sqlenv config - logg = logFunction logger - monitor = runPactServiceQueueMonitor $ addLabel ("sub-component", "PactQueue") logger - -runPactServiceQueueMonitor :: Logger logger => logger -> PactQueue -> IO () -runPactServiceQueueMonitor l pq = do - let lf = logFunction l - runForeverThrottled lf "Chainweb.Pact.Service.PactInProcApi.runPactServiceQueueMonitor" 10 (10 * mega) $ do - queueStats <- getPactQueueStats pq - logFunctionText l Debug "got latest set of stats from PactQueueMonitor" - logFunctionJson l Info queueStats - resetPactQueueStats pq - approximateThreadDelay 60_000_000 {- 1 minute -} - -pactMemPoolAccess - :: Logger logger - => MempoolConsensus - -> logger - -> MemPoolAccess -pactMemPoolAccess mpc logger = MemPoolAccess - { mpaGetBlock = pactMemPoolGetBlock mpc logger - , mpaSetLastHeader = pactMempoolSetLastHeader mpc logger - , mpaProcessFork = pactProcessFork mpc logger - , mpaBadlistTx = mempoolAddToBadList (mpcMempool mpc) - } - -pactMemPoolGetBlock - :: Logger logger - => MempoolConsensus - -> logger - -> BlockFill - -> (MempoolPreBlockCheck Pact4.UnparsedTransaction to - -> BlockHeight - -> BlockHash - -> BlockCreationTime - -> IO (Vector to)) -pactMemPoolGetBlock mpc theLogger bf validate height hash _btime = do - logFn theLogger Debug $! "pactMemPoolAccess - getting new block of transactions for " - <> "height = " <> sshow height <> ", hash = " <> sshow hash - mempoolGetBlock (mpcMempool mpc) bf validate height hash - where - logFn :: Logger l => l -> LogFunctionText -- just for giving GHC some type hints - logFn l = logFunction l - - -pactProcessFork - :: Logger logger - => MempoolConsensus - -> logger - -> (BlockHeader -> IO ()) -pactProcessFork mpc theLogger bHeader = do - let forkFunc = (mpcProcessFork mpc) (logFunction theLogger) - (reintroTxs, validatedTxs) <- forkFunc bHeader - logFn theLogger Debug $! - "pactMemPoolAccess - " <> sshow (length reintroTxs) <> " transactions to reintroduce" - -- No need to run pre-insert check here -- we know these are ok, and - -- calling the pre-check would block here (it calls back into pact service) - mempoolInsert (mpcMempool mpc) UncheckedInsert reintroTxs - mempoolMarkValidated (mpcMempool mpc) validatedTxs - - where - logFn :: Logger l => l -> LogFunctionText - logFn lg = logFunction lg - -pactMempoolSetLastHeader - :: Logger logger - => MempoolConsensus - -> logger - -> (BlockHeader -> IO ()) -pactMempoolSetLastHeader mpc _theLogger bHeader = do - let headerRef = mpcLastNewBlockParent mpc - atomicWriteIORef headerRef (Just bHeader) diff --git a/src/Chainweb/Pact/Transaction.hs b/src/Chainweb/Pact/Transaction.hs index fd78d7618a..f376deeb30 100644 --- a/src/Chainweb/Pact/Transaction.hs +++ b/src/Chainweb/Pact/Transaction.hs @@ -34,7 +34,7 @@ import "pact-tng" Pact.Core.Command.Types import "pact-tng" Pact.Core.StableEncoding import "pact-tng" Pact.Core.Errors import "pact-tng" Pact.Core.Info -import "pact-tng" Pact.Core.Pretty qualified as Pact5 +import "pact-tng" Pact.Core.Pretty qualified as Pact import "text" Data.Text (Text) import "text" Data.Text.Encoding (decodeUtf8, encodeUtf8) import Chainweb.Utils @@ -78,7 +78,7 @@ payloadCodec = Codec enc dec -- Note: this can only ever emit a `ParseError`, which by default are quite small. -- Still, `pretty` instances are scary, but this cannot make it into block outputs so this should -- be okay - Just (cmd :: Command Text) -> over _Left Pact5.renderCompactString $ parseCommand cmd + Just (cmd :: Command Text) -> over _Left Pact.renderCompactString $ parseCommand cmd Nothing -> Left "decode PayloadWithText failed" parseCommand :: Command Text -> Either (PactError SpanInfo) (Command (PayloadWithText PublicMeta ParsedCode)) diff --git a/src/Chainweb/Pact/TransactionExec.hs b/src/Chainweb/Pact/TransactionExec.hs index 628d97527f..55f50c0995 100644 --- a/src/Chainweb/Pact/TransactionExec.hs +++ b/src/Chainweb/Pact/TransactionExec.hs @@ -70,10 +70,8 @@ import qualified Data.Text.Encoding as T import qualified System.LogLevel as L -- internal Pact modules -import qualified Pact.JSON.Decode as J import qualified Pact.JSON.Encode as J - import Pact.Core.Builtin import Pact.Core.Capabilities import Pact.Core.Compile @@ -91,20 +89,25 @@ import Pact.Core.SPV import Pact.Core.Serialise.LegacyPact () import Pact.Core.Signer import Pact.Core.StableEncoding -import Pact.Core.Verifiers import Pact.Core.Syntax.ParseTree qualified as Lisp -import Pact.Core.Gas.Utils qualified as Pact5 +import Pact.Core.Gas.Utils qualified as Pact + +import Pact.Core.Command.Types +import Pact.Core.Command.RPC +import qualified Pact.Core.Errors as Pact +import qualified Pact.Core.Gas as Pact +import qualified Pact.Core.Evaluate as Pact -- internal Chainweb modules import Chainweb.BlockCreationTime import Chainweb.BlockHash -import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Miner.Pact import Chainweb.Pact.Types import Chainweb.Pact.Templates +import Chainweb.Parent import Chainweb.Time import Chainweb.Pact.Transaction @@ -114,23 +117,14 @@ import Chainweb.Version as V import Chainweb.Version.Guards as V import Chainweb.Version.Utils as V -import Pact.Core.Command.Types import Data.ByteString (ByteString) -import Pact.Core.Command.RPC -import qualified Pact.Types.Gas as Pact4 import qualified Data.Set as Set import qualified Data.Text as T import qualified Data.Vector as Vector import Data.Set (Set) import Data.Void import Control.Monad.Except -import Data.Int -import qualified Pact.Types.Verifier as Pact4 -import qualified Pact.Types.Capability as Pact4 -import qualified Pact.Types.Names as Pact4 -import qualified Pact.Types.Runtime as Pact4 -import qualified Pact.Core.Errors as Pact5 -import qualified Pact.Core.Evaluate as Pact5 +import qualified Chainweb.MinerReward as MinerReward -- Note [Throw out verifier proofs eagerly] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -153,13 +147,15 @@ makeLenses ''TransactionEnv -- | TODO: document how TransactionM is just for the "paid-for" fragment of the transaction flow newtype TransactionM logger a - = TransactionM { runTransactionM :: ReaderT (TransactionEnv logger) (ExceptT (Pact5.PactError Info) IO) a } + = TransactionM + { runTransactionM :: ReaderT (TransactionEnv logger) (ExceptT (Pact.PactError Pact.Info) IO) a + } deriving newtype ( Functor , Applicative , Monad , MonadReader (TransactionEnv logger) - , MonadError (Pact5.PactError Info) + , MonadError (Pact.PactError Pact.Info) , MonadThrow , MonadIO ) @@ -173,7 +169,7 @@ chargeGas info gasArgs = do -- run verifiers -- nasty... perhaps later convert verifier plugins to use GasM instead of tracking "gas remaining" -- TODO: Verifiers are also tied to Pact enough that this is going to be an annoying migration -runVerifiers :: Logger logger => TxContext -> Command (Payload PublicMeta ParsedCode) -> TransactionM logger () +runVerifiers :: Logger logger => BlockCtx -> Command (Payload PublicMeta ParsedCode) -> TransactionM logger () runVerifiers txCtx cmd = do logger <- view txEnvLogger let v = _chainwebVersion txCtx @@ -181,46 +177,17 @@ runVerifiers txCtx cmd = do gasUsed <- liftIO . readIORef . _geGasRef . _txEnvGasEnv =<< ask let initGasRemaining = MilliGas $ case (gasToMilliGas (gasLimit ^. _GasLimit), gasUsed) of (MilliGas gasLimitMilliGasWord, MilliGas gasUsedMilliGasWord) -> gasLimitMilliGasWord - gasUsedMilliGasWord - let allVerifiers = verifiersAt v (_chainId txCtx) (ctxCurrentBlockHeight txCtx) - let toModuleName m = - Pact4.ModuleName - { Pact4._mnName = _mnName m - , Pact4._mnNamespace = coerce <$> _mnNamespace m - } - let toQualifiedName qn = - Pact4.QualifiedName - { Pact4._qnQual = toModuleName $ _qnModName qn - , Pact4._qnName = _qnName qn - , Pact4._qnInfo = Pact4.Info Nothing - } - -- TODO: correct error handling here? we should probably charge the user - let convertPactValue pv = fromJuste $ J.decodeStrict $ encodeStable pv - let pact4TxVerifiers = - [ Pact4.Verifier - { Pact4._verifierName = case _verifierName pact5Verifier of - VerifierName n -> Pact4.VerifierName n - , Pact4._verifierProof = - -- TODO: correct error handling here? we should probably charge the user - Pact4.ParsedVerifierProof $ fromJuste $ - convertPactValue $ coerce @ParsedVerifierProof @PactValue $ _verifierProof pact5Verifier - , Pact4._verifierCaps = - [ Pact4.SigCapability (toQualifiedName n) (convertPactValue <$> args) - | SigCapability (CapToken n args) <- _verifierCaps pact5Verifier - ] - } - | pact5Verifier <- fromMaybe [] $ cmd ^. cmdPayload . pVerifiers - ] + let allVerifiers = verifiersAt v (_chainId txCtx) (_bctxCurrentBlockHeight txCtx) + let txVerifiers = fromMaybe [] $ cmd ^. cmdPayload . pVerifiers verifierResult <- liftIO $ runVerifierPlugins - (_chainwebVersion txCtx, _chainId txCtx, ctxCurrentBlockHeight txCtx) logger + (_chainwebVersion txCtx, _chainId txCtx, _bctxCurrentBlockHeight txCtx) logger allVerifiers - (Pact4.Gas $ fromIntegral @SatWord @Int64 $ _gas $ milliGasToGas $ initGasRemaining) - pact4TxVerifiers + (milliGasToGas $ initGasRemaining) + txVerifiers case verifierResult of Left err -> do - throwError (Pact5.PEVerifierError err noInfo) - Right (Pact4.Gas pact4VerifierGasRemaining) -> do - -- TODO: crash properly on negative? - let verifierGasRemaining = fromIntegral @Int64 @SatWord pact4VerifierGasRemaining + throwError (Pact.PEVerifierError err noInfo) + Right (Pact.Gas verifierGasRemaining) -> do -- NB: this is not nice. -- TODO: better gas info here -- Explanation by cases: @@ -245,13 +212,13 @@ applyLocal -- ^ Pact gas logger -> PactDb CoreBuiltin Info -- ^ Pact db environment - -> TxContext + -> BlockCtx -- ^ tx metadata and parent header -> SPVSupport -- ^ SPV support (validates cont proofs) -> Command (Payload PublicMeta ParsedCode) -- ^ command with payload to execute - -> IO (CommandResult [TxLog ByteString] (Pact5.PactError Info)) + -> IO (CommandResult [TxLog ByteString] (Pact.PactError Info)) applyLocal logger maybeGasLogger coreDb txCtx spvSupport cmd = do let gasLogsEnabled = maybe GasLogsDisabled (const GasLogsEnabled) maybeGasLogger let gasLimitGas :: Gas = cmd ^. cmdPayload . pMeta . pmGasLimit . _GasLimit @@ -320,7 +287,9 @@ applyCmd -- ^ Pact gas logger -> PactDb CoreBuiltin Info -- ^ Pact db environment - -> TxContext + -> Miner + -- ^ miner + -> BlockCtx -- ^ tx metadata -> TxIdxInBlock -> SPVSupport @@ -329,8 +298,8 @@ applyCmd -- ^ initial gas cost -> Command (Payload PublicMeta ParsedCode) -- ^ command with payload to execute - -> IO (Either TxInvalidError (CommandResult [TxLog ByteString] (Pact5.PactError Info))) -applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do + -> IO (Either TxInvalidError (CommandResult [TxLog ByteString] (Pact.PactError Info))) +applyCmd logger maybeGasLogger db miner txCtx txIdxInBlock spv initialGas cmd = do logDebug_ logger $ "applyCmd: " <> sshow (_cmdHash cmd) let flags = Set.fromList [ FlagDisableRuntimeRTC @@ -362,7 +331,7 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do then do pure $ Left PurchaseGasTxTooBigForGasLimit else do - buyGas logger gasEnv db txCtx cmd >>= \case + buyGas logger gasEnv db miner txCtx cmd >>= \case Left buyGasError -> do pure $ Left (BuyGasError buyGasError) Right buyGasResult -> do @@ -382,7 +351,7 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do -- and all of the gas is sent to the miner. only buying gas and sending it to the miner are recorded. -- the Pact transaction is cancelled, and events and logs from the command are not recorded. eRedeemGasResult <- redeemGas - logger db txCtx + logger db miner txCtx (gasLimit ^. _GasLimit) (_peDefPactId <$> _erExec buyGasResult) cmd @@ -406,7 +375,7 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do -- return all unused gas to the user and send all used -- gas to the miner. eRedeemGasResult <- redeemGas - logger db txCtx + logger db miner txCtx gasUsed (_peDefPactId <$> _erExec buyGasResult) cmd @@ -435,7 +404,7 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do -- current blockheight, referencing the parent header (not grandparent!) -- hash and blocktime data. -- -ctxToPublicData :: PublicMeta -> TxContext -> PublicData +ctxToPublicData :: PublicMeta -> BlockCtx -> PublicData ctxToPublicData pm ctx = PublicData { _pdPublicMeta = pm , _pdBlockHeight = bh @@ -443,10 +412,10 @@ ctxToPublicData pm ctx = PublicData , _pdPrevBlockHash = toText h } where - BlockHeight !bh = succ $ unwrapParent $ _tcParentHeight ctx + BlockHeight !bh = succ $ unwrapParent $ _bctxParentHeight ctx Parent (BlockCreationTime (Time (TimeSpan (Micros !bt)))) = - _tcParentCreationTime ctx - Parent (BlockHash h) = _tcParentHash ctx + _bctxParentCreationTime ctx + Parent (BlockHash h) = _bctxParentHash ctx -- | 'applyCoinbase' performs upgrade transactions and constructs and executes -- a transaction which pays miners their block reward. @@ -456,20 +425,21 @@ applyCoinbase -- ^ Pact logger -> PactDb CoreBuiltin Info -- ^ Pact db environment - -> Decimal - -- ^ Miner reward - -> TxContext + -> Miner + -- ^ miner + -> BlockCtx -- ^ tx metadata and parent header - -> IO (Either (Pact5.PactError Pact5.Info) (CommandResult [TxLog ByteString] Void)) -applyCoinbase logger db reward txCtx = do + -> IO (Either (Pact.PactError Pact.Info) (CommandResult [TxLog ByteString] Void)) +applyCoinbase logger db (Miner mid mks) txCtx = do -- for some reason this is the base64-encoded hash, rather than the binary hash let coinbaseHash = Hash $ SB.toShort $ T.encodeUtf8 $ blockHashToText parentBlockHash -- applyCoinbase is when upgrades happen, so we call applyUpgrades first applyUpgrades logger db txCtx -- we construct the coinbase term and evaluate it freeGasEnv <- mkFreeGasEnv GasLogsDisabled + let MinerReward.Kda minerRewardKda = MinerReward.minerRewardKda $ _bctxMinerReward txCtx let - (coinbaseTerm, coinbaseData) = mkCoinbaseTerm mid mks reward + (coinbaseTerm, coinbaseData) = mkCoinbaseTerm mid mks minerRewardKda eCoinbaseTxResult <- evalExecTerm Transactional db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy @@ -502,8 +472,7 @@ applyCoinbase logger db reward txCtx = do } where - parentBlockHash = unwrapParent $ _tcParentHash txCtx - Miner mid mks = _tcMiner txCtx + parentBlockHash = unwrapParent $ _bctxParentHash txCtx -- | Apply (forking) upgrade transactions and module cache updates -- at a particular blockheight. @@ -518,7 +487,7 @@ applyUpgrades :: (Logger logger) => logger -> PactDb CoreBuiltin Info - -> TxContext + -> BlockCtx -> IO () applyUpgrades logger db txCtx | Just PactUpgrade{_pactUpgradeTransactions = upgradeTxs} <- @@ -526,7 +495,7 @@ applyUpgrades logger db txCtx | otherwise = return () where v = _chainwebVersion txCtx - currentHeight = ctxCurrentBlockHeight txCtx + currentHeight = _bctxCurrentBlockHeight txCtx cid = _chainId txCtx applyUpgrade :: [Transaction] -> IO () applyUpgrade upgradeTxs = do @@ -547,9 +516,9 @@ runGenesisPayload => logger -> PactDb CoreBuiltin Info -> SPVSupport - -> TxContext + -> BlockCtx -> Command (Payload PublicMeta ParsedCode) - -> IO (Either (Pact5.PactError Info) (CommandResult [TxLog ByteString] Void)) + -> IO (Either (Pact.PactError Info) (CommandResult [TxLog ByteString] Void)) runGenesisPayload logger db spv ctx cmd = do gasRef <- newIORef (MilliGas 0) let gasEnv = GasEnv gasRef Nothing freeGasModel @@ -596,7 +565,7 @@ runPayload -> [CapToken QualifiedName PactValue] -> NamespacePolicy -> GasEnv CoreBuiltin Info - -> TxContext + -> BlockCtx -> TxIdxInBlock -> Command (Payload PublicMeta ParsedCode) -> TransactionM logger EvalResult @@ -645,9 +614,8 @@ runPayload execMode execFlags db spv specialCaps namespacePolicy gasEnv txCtx tx case maybeQuirkGasFee of Nothing -> return result Just quirkGasFee -> do - let convertedQuirkGasFee = Gas $ fromIntegral quirkGasFee - liftIO $ writeIORef (_geGasRef gasEnv) $ gasToMilliGas convertedQuirkGasFee - return result { _erGas = convertedQuirkGasFee } + liftIO $ writeIORef (_geGasRef gasEnv) $ gasToMilliGas quirkGasFee + return result { _erGas = quirkGasFee } where payload = cmd ^. cmdPayload @@ -656,13 +624,13 @@ runPayload execMode execFlags db spv specialCaps namespacePolicy gasEnv txCtx tx publicMeta = cmd ^. cmdPayload . pMeta v = _chainwebVersion txCtx cid = _chainId txCtx - maybeQuirkGasFee = v ^? versionQuirks . quirkGasFees . ixg cid . ix (ctxCurrentBlockHeight txCtx, txIdxInBlock) + maybeQuirkGasFee = v ^? versionQuirks . quirkGasFees . ixg cid . ix (_bctxCurrentBlockHeight txCtx, txIdxInBlock) runUpgrade :: (Logger logger) => logger -> PactDb CoreBuiltin Info - -> TxContext + -> BlockCtx -> Command (Payload PublicMeta ParsedCode) -> IO () runUpgrade _logger db txContext cmd = case payload ^. pPayload of @@ -683,7 +651,7 @@ runUpgrade _logger db txContext cmd = case payload ^. pPayload of & csSlots .~ [CapSlot (CapToken (QualifiedName "REMEDIATE" (ModuleName "coin" Nothing)) []) []] & csModuleAdmin .~ S.singleton (ModuleName "coin" Nothing)) (fmap (noSpanInfo <$) $ _pcExps $ _pmCode pm) >>= \case - Left err -> error $ "Pact5.runGenesis: internal error " <> sshow err + Left err -> error $ "Pact.runGenesis: internal error " <> sshow err -- TODO: we should probably put these events somewhere! Right _r -> return () Continuation _ -> error "runGenesisCore Continuation not supported" @@ -727,10 +695,11 @@ buyGas => logger -> GasEnv CoreBuiltin Info -> PactDb CoreBuiltin Info - -> TxContext + -> Miner + -> BlockCtx -> Command (Payload PublicMeta ParsedCode) -> IO (Either BuyGasError EvalResult) -buyGas logger origGasEnv db txCtx cmd = do +buyGas logger origGasEnv db (Miner mid mks) txCtx cmd = do let gasEnv = origGasEnv & geGasModel . gmGasLimit .~ Just (MilliGasLimit (MilliGas 1_500_000)) logFunctionText logger L.Debug $ "buying gas for " <> sshow (_cmdHash cmd) @@ -824,8 +793,6 @@ buyGas logger origGasEnv db txCtx cmd = do signersWithDebit = fmap addDebit signers - Miner mid mks = _tcMiner txCtx - Hash chash = _cmdHash cmd bgHash = Hash (chash <> "-buygas") @@ -834,12 +801,14 @@ buyGas logger origGasEnv db txCtx cmd = do -- redeemGas :: (Logger logger) => logger - -> PactDb CoreBuiltin Info -> TxContext + -> PactDb CoreBuiltin Info + -> Miner + -> BlockCtx -> Gas -> Maybe DefPactId -> Command (Payload PublicMeta ParsedCode) -> IO (Either RedeemGasError EvalResult) -redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd +redeemGas logger db (Miner mid mks) txCtx gasUsed maybeFundTxPactId cmd | isChainweb224Pact, Nothing <- maybeFundTxPactId = do logFunctionText logger L.Debug $ "redeeming gas (post-2.24) for " <> sshow (_cmdHash cmd) @@ -899,7 +868,6 @@ redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd where Hash chash = _cmdHash cmd rgHash = Hash (chash <> "-redeemgas") - Miner mid mks = _tcMiner txCtx isChainweb224Pact = guardCtx chainweb224Pact txCtx publicMeta = cmd ^. cmdPayload . pMeta signers = cmd ^. cmdPayload . pSigners @@ -974,14 +942,14 @@ dumpGasLogs :: (Logger logger) dumpGasLogs ctx txHash maybeGasLogger gasEnv = do forM_ ((,) <$> _geGasLog gasEnv <*> maybeGasLogger) $ \(gasLogRef, gasLogger) -> do gasLogs <- reverse <$> readIORef gasLogRef - let prettyLogs = Pact5.prettyGasLogs (_geGasModel gasEnv) (_gleArgs <$> gasLogs) + let prettyLogs = Pact.prettyGasLogs (_geGasModel gasEnv) (_gleArgs <$> gasLogs) let logger = addLabel ("transactionExecContext", ctx) $ addLabel ("cmdHash", hashToText txHash) $ gasLogger logFunctionText logger L.Debug $ "gas logs: " <> prettyLogs -- After every dump, we clear the gas logs, so that each context only writes the gas logs it induced. writeIORef gasLogRef mempty -guardDisablePact51Flags :: TxContext -> Set ExecutionFlag +guardDisablePact51Flags :: BlockCtx -> Set ExecutionFlag guardDisablePact51Flags txCtx | guardCtx chainweb228Pact txCtx = Set.empty | otherwise = Set.singleton FlagDisablePact51 diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 2f634ef1ac..5bec2578c2 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -11,20 +11,62 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DeriveFoldable #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE StandaloneDeriving #-} module Chainweb.Pact.Types ( PactServiceEnv(..) - , psBlockGasLimit + , psVersion + , psChainId + , psLogger + , psGasLogger + , psReadWriteSql + , psPdb + , psCandidatePdb + , psMempoolAccess + , psPreInsertCheckTimeout + , psAllowReadsInLocal + , psEnableLocalTimeout + , psTxFailuresCounter + , psNewPayloadTxTimeLimit + , psMiner + , psMiningPayloadVar + , psNewBlockGasLimit + + , PactServiceConfig(..) , PactServiceM(..) + , runPactServiceM + , withPactState , PactBlockEnv(..) + , psBlockDbEnv + , psBlockCtx + , psServiceEnv , PactBlockState(..) , PactBlockM(..) + , Transactions(..) + , transactionPairs + , transactionCoinbase + , OffChainCommandResult + , OnChainCommandResult + , BlockInProgress(..) + , blockInProgressHandle + , blockInProgressBlockCtx + , blockInProgressRemainingGasLimit + , blockInProgressTransactions + , toPayloadWithOutputs + , MemPoolAccess(..) - , TxContext(..) + , BlockCtx(..) + , blockCtxOfEvaluationCtx + , _bctxParentRankedBlockHash + , _bctxIsGenesis , guardCtx - , ctxCurrentBlockHeight + , _bctxCurrentBlockHeight , GasSupply(..) , RewindLimit(..) @@ -59,6 +101,8 @@ module Chainweb.Pact.Types , TransactionOutputProofB64(..) , TxInvalidError(..) + , BlockInvalidError(..) + , BlockOutputMismatchError(..) , BuyGasError(..) , RedeemGasError(..) @@ -67,6 +111,13 @@ module Chainweb.Pact.Types , logInfo_ , logWarn_ , logError_ + + , logInfoPact + , logWarnPact + , logErrorPact + , logDebugPact + + , PactTxFailureLog(..) ) where @@ -78,10 +129,15 @@ import Control.Monad.IO.Class import Control.Monad.Reader import Control.Monad.State.Strict import Data.Aeson hiding (Error, (.=)) +import Data.Bool +import Data.ByteString (ByteString) +import Data.ByteString.Short qualified as SB import Data.Decimal -import qualified Data.List.NonEmpty as NE +import Data.List.NonEmpty qualified as NE import Data.LogMessage import Data.Text (Text) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T import Data.Vector (Vector) import Data.Word import GHC.Generics (Generic) @@ -93,6 +149,7 @@ import Pact.Core.Command.Types (RequestKey) import Pact.Core.Errors qualified as Pact import Pact.Core.Evaluate qualified as Pact import Pact.Core.Gas.Types qualified as Pact +import Pact.Core.Hash qualified as Pact import Pact.Core.Info qualified as Pact import Pact.Core.Literal qualified as Pact import Pact.Core.Persistence @@ -106,24 +163,40 @@ import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.BlockPayloadHash -import qualified Chainweb.ChainId as Chainweb +import Chainweb.ChainId qualified as Chainweb import Chainweb.Counter import Chainweb.Logger import Chainweb.Mempool.Mempool -import Chainweb.Miner.Pact (Miner) +import Chainweb.Miner.Pact (Miner, toMinerData) import Chainweb.MinerReward import Chainweb.Pact.Backend.ChainwebPactDb -import Chainweb.Pact.Backend.DbCache import Chainweb.Pact.Backend.Types -import qualified Chainweb.Pact.Transaction as Pact -import Chainweb.Payload +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Parent +import Chainweb.Payload qualified as Chainweb import Chainweb.Payload.PayloadStore import Chainweb.PayloadProvider.P2P import Chainweb.Storage.Table.Map import Chainweb.Time import Chainweb.Utils import Chainweb.Version -import qualified Pact.Core.Command.Types as Pact + +import Pact.Core.Command.Types qualified as Pact +import Pact.Core.Persistence qualified as Pact +import Pact.Core.SPV qualified as Pact +import Servant.API +import Data.List.NonEmpty (NonEmpty) +import Chainweb.PayloadProvider +import Control.Concurrent.STM + +data Transactions t r = Transactions + { _transactionPairs :: !(Vector (t, r)) + , _transactionCoinbase :: !r + } + deriving stock (Eq, Show, Functor, Foldable, Traversable, Generic) + deriving anyclass NFData + +makeLenses 'Transactions data AssertValidateSigsError = SignersAndSignaturesLengthMismatch @@ -264,6 +337,11 @@ instance FromJSON LocalResult where <*> o .: "preflightWarnings" legacyFallbackParser _ = LocalResultLegacy $ J.encodeJsonText v +type OnChainCommandResult = + Pact.CommandResult Pact.Hash Pact.PactOnChainError +type OffChainCommandResult = + Pact.CommandResult [Pact.TxLog ByteString] (Pact.PactError Pact.Info) + -- | Externally-injected PactService properties. -- @@ -283,8 +361,6 @@ data PactServiceConfig = PactServiceConfig -- ^ the gas limit for new block creation, not for validation , _pactLogGas :: !Bool -- ^ whether to write transaction gas logs at INFO - , _pactModuleCacheLimit :: !DbCacheLimitBytes - -- ^ limit of the database module cache in bytes of corresponding row data , _pactFullHistoryRequired :: !Bool -- ^ Whether or not the node requires that the full Pact history be -- available. Compaction can remove history. @@ -311,43 +387,58 @@ data MemPoolAccess = MemPoolAccess -> BlockCreationTime -> IO (Vector to) ) - , mpaSetLastHeader :: !(BlockHeader -> IO ()) - , mpaProcessFork :: !(BlockHeader -> IO ()) + , mpaProcessFork :: !((Vector Pact.Transaction, Vector TransactionHash) -> IO ()) , mpaBadlistTx :: !(Vector TransactionHash -> IO ()) } instance Semigroup MemPoolAccess where - MemPoolAccess f g h i <> MemPoolAccess t u v w = - MemPoolAccess (f <> t) (g <> u) (h <> v) (i <> w) + MemPoolAccess f g h <> MemPoolAccess t u v = + MemPoolAccess (f <> t) (g <> u) (h <> v) instance Monoid MemPoolAccess where - mempty = MemPoolAccess mempty mempty mempty mempty + mempty = MemPoolAccess mempty mempty mempty data PactServiceEnv logger tbl = PactServiceEnv - { _psMempoolAccess :: !(Maybe MemPoolAccess) - , _psCheckpointer :: !(Checkpointer logger) - , _psPdb :: !(PayloadStore (PayloadDb tbl) PayloadData) - , _psCandidatePdb :: !(MapTable RankedBlockPayloadHash PayloadData) - , _psPreInsertCheckTimeout :: !Micros - -- ^ Maximum allowed execution time for the transactions validation. - , _psReorgLimit :: !RewindLimit - -- ^ The limit of checkpointer's rewind in the `execValidationBlock` command. - , _psVersion :: !ChainwebVersion + { _psVersion :: !ChainwebVersion , _psChainId :: !ChainId - , _psAllowReadsInLocal :: !Bool , _psLogger :: !logger + , _psGasLogger :: !(Maybe logger) + -- ^ Used to emit gas logs of Pact code; gas logs are disabled if this is set to `Nothing`. + -- Gas logs have a non-zero cost, so usually this is disabled. + + , _psReadWriteSql :: !SQLiteEnv + -- ^ Database connection used to mutate the Pact state. + , _psPdb :: !(PayloadStore (PayloadDb tbl) Chainweb.PayloadData) + -- ^ Used to store payloads of validated blocks. + -- Contains outputs too. + , _psCandidatePdb :: !(MapTable RankedBlockPayloadHash Chainweb.PayloadData) + -- ^ Used to store payloads of blocks that have not yet been validated. + + , _psMempoolAccess :: !MemPoolAccess + -- ^ The mempool's limited interface as used by Pact. + , _psPreInsertCheckTimeout :: !Micros + -- ^ Maximum allowed execution time for mempool transaction validation. - , _psBlockGasLimit :: !Pact.GasLimit + , _psAllowReadsInLocal :: !Bool + -- ^ Whether to allow reads of arbitrary tables in /local. , _psEnableLocalTimeout :: !Bool + -- ^ Whether to have a timeout for /local calls. , _psTxFailuresCounter :: !(Maybe (Counter "txFailures")) - , _psTxTimeLimit :: !(Maybe Micros) + -- ^ Counter of the number of failed transactions. + , _psNewPayloadTxTimeLimit :: !(Maybe Micros) + -- ^ Maximum amount of time to validate transactions while constructing payload. + -- If unset, defaults to a reasonable value based on the transaction's gas limit. , _psMiner :: !(Maybe Miner) + -- ^ Miner identity for use in newly mined blocks. + , _psMiningPayloadVar :: !(TMVar NewPayload) + -- ^ Latest mining payload produced. + , _psNewBlockGasLimit :: !Pact.GasLimit + -- ^ Block gas limit in newly produced blocks. } makeLenses ''PactServiceEnv - instance HasChainwebVersion (PactServiceEnv logger c) where chainwebVersion = psVersion {-# INLINE chainwebVersion #-} @@ -359,7 +450,7 @@ instance HasChainId (PactServiceEnv logger c) where -- | The top level monad of PactService, notably allowing access to a -- checkpointer and module init cache and some configuration parameters. newtype PactServiceM logger tbl a = PactServiceM - { runPactServiceM :: + { unPactServiceM :: ReaderT (PactServiceEnv logger tbl) IO a } deriving newtype ( Functor, Applicative, Monad @@ -368,6 +459,9 @@ newtype PactServiceM logger tbl a = PactServiceM , MonadIO ) +runPactServiceM :: PactServiceEnv logger tbl -> PactServiceM logger tbl a -> IO a +runPactServiceM e a = runReaderT (unPactServiceM a) e + withPactState :: forall logger tbl b . Logger logger @@ -375,29 +469,24 @@ withPactState -> PactServiceM logger tbl b withPactState inner = do e <- ask - liftIO $ inner $ \act -> - runReaderT (runPactServiceM act) e + liftIO $ inner $ + runPactServiceM e data PactBlockEnv logger tbl = PactBlockEnv { _psServiceEnv :: !(PactServiceEnv logger tbl) - , _psParentHeader :: !(Parent BlockHeader) - , _psIsGenesis :: !Bool + , _psBlockCtx :: !BlockCtx , _psBlockDbEnv :: !ChainwebPactDb } -makeLenses ''PactBlockEnv - instance HasChainwebVersion (PactBlockEnv logger tbl) where - chainwebVersion = psServiceEnv . chainwebVersion + _chainwebVersion = _chainwebVersion . _psServiceEnv instance HasChainId (PactBlockEnv logger tbl) where - chainId = psServiceEnv . chainId + _chainId = _chainId . _psServiceEnv data PactBlockState = PactBlockState { _pbBlockHandle :: !BlockHandle } -makeLenses ''PactBlockState - -- | A sub-monad of PactServiceM, for actions taking place at a particular block. newtype PactBlockM logger tbl a = PactBlockM { _unPactBlockM :: @@ -421,13 +510,12 @@ liftPactServiceM (PactServiceM a) = -- a database snapshot at that block and information about the parent header. -- It is unsafe to use this function in an argument to `liftPactServiceM`. runPactBlockM - :: Parent BlockHeader -> Bool -> ChainwebPactDb -> BlockHandle + :: BlockCtx -> ChainwebPactDb -> BlockHandle -> PactBlockM logger tbl a -> PactServiceM logger tbl (a, BlockHandle) -runPactBlockM pctx isGenesis dbEnv startBlockHandle (PactBlockM act) = PactServiceM $ ReaderT $ \e -> do +runPactBlockM pctx dbEnv startBlockHandle (PactBlockM act) = PactServiceM $ ReaderT $ \e -> do let blockEnv = PactBlockEnv { _psServiceEnv = e - , _psParentHeader = pctx - , _psIsGenesis = isGenesis + , _psBlockCtx = pctx , _psBlockDbEnv = dbEnv } (a, s') <- runStateT @@ -453,37 +541,75 @@ logJsonTrace_ logger level msg = liftIO $ logFunction logger level msg -- | Pair parent header with transaction metadata. -- In cases where there is no transaction/Command, 'PublicMeta' -- default value is used. -data TxContext = TxContext - { _tcParentCreationTime :: !(Parent BlockCreationTime) - , _tcParentHash :: !(Parent BlockHash) - , _tcParentHeight :: !(Parent BlockHeight) - , _tcChainId :: !ChainId - , _tcChainwebVersion :: !ChainwebVersion - , _tcMinerReward :: !MinerReward - , _tcMiner :: !Miner - } deriving Show - -instance HasChainId TxContext where - _chainId = _tcChainId -instance HasChainwebVersion TxContext where - _chainwebVersion = _tcChainwebVersion - --- | Get "current" block height, which means parent height + 1. --- This reflects Pact environment focus on current block height, --- which influenced legacy switch checks as well. -ctxCurrentBlockHeight :: TxContext -> BlockHeight -ctxCurrentBlockHeight = succ . unwrapParent . _tcParentHeight - -guardCtx :: (ChainwebVersion -> Chainweb.ChainId -> BlockHeight -> a) -> TxContext -> a -guardCtx g txCtx = g (_chainwebVersion txCtx) (_chainId txCtx) (ctxCurrentBlockHeight txCtx) - -pactTransaction :: Maybe RequestKey -> (PactDb Pact.CoreBuiltin Pact.Info -> IO a) -> PactBlockM logger tbl a -pactTransaction rk k = do - e <- view psBlockDbEnv - h <- use pbBlockHandle - (r, h') <- liftIO $ doChainwebPactDbTransaction e h rk k - pbBlockHandle .= h' - return r +data BlockCtx = BlockCtx + { _bctxParentCreationTime :: !(Parent BlockCreationTime) + , _bctxParentHash :: !(Parent BlockHash) + , _bctxParentHeight :: !(Parent BlockHeight) + , _bctxChainId :: !ChainId + , _bctxChainwebVersion :: !ChainwebVersion + , _bctxMinerReward :: !MinerReward + } deriving (Eq, Show) + +blockCtxOfEvaluationCtx :: ChainwebVersion -> ChainId -> EvaluationCtx p -> BlockCtx +blockCtxOfEvaluationCtx v cid ec = BlockCtx + { _bctxParentCreationTime = _evaluationCtxParentCreationTime ec + , _bctxParentHash = _evaluationCtxParentHash ec + , _bctxParentHeight = _evaluationCtxParentHeight ec + , _bctxChainId = cid + , _bctxChainwebVersion = v + , _bctxMinerReward = _evaluationCtxMinerReward ec + } + +instance HasChainId BlockCtx where + _chainId = _bctxChainId +instance HasChainwebVersion BlockCtx where + _chainwebVersion = _bctxChainwebVersion + +_bctxIsGenesis :: BlockCtx -> Bool +_bctxIsGenesis bc = isGenesisBlockHeader' (_chainwebVersion bc) (_chainId bc) (_bctxParentHash bc) + +_bctxParentRankedBlockHash :: BlockCtx -> Parent RankedBlockHash +_bctxParentRankedBlockHash bc = Parent RankedBlockHash + { _rankedBlockHashHash = unwrapParent $ _bctxParentHash bc + , _rankedBlockHashHeight = unwrapParent $ _bctxParentHeight bc + } + +_bctxCurrentBlockHeight :: BlockCtx -> BlockHeight +_bctxCurrentBlockHeight bc = + childBlockHeight (_chainwebVersion bc) (_chainId bc) (_bctxParentRankedBlockHash bc) + +-- State from a block in progress, which is used to extend blocks after +-- running their payloads. +data BlockInProgress = BlockInProgress + { _blockInProgressHandle :: !BlockHandle + , _blockInProgressBlockCtx :: !BlockCtx + , _blockInProgressRemainingGasLimit :: !Pact.GasLimit + , _blockInProgressTransactions :: !(Transactions Chainweb.Transaction OffChainCommandResult) + } + +makeLenses ''BlockInProgress + +instance Eq BlockInProgress where + bip == bip' = + _blockInProgressHandle bip == _blockInProgressHandle bip' && + _blockInProgressBlockCtx bip == _blockInProgressBlockCtx bip' && + _blockInProgressRemainingGasLimit bip == _blockInProgressRemainingGasLimit bip' && + _blockInProgressTransactions bip == _blockInProgressTransactions bip' + +instance HasChainwebVersion BlockInProgress where + _chainwebVersion = _chainwebVersion . _blockInProgressBlockCtx + {-# INLINE _chainwebVersion #-} + +instance HasChainId BlockInProgress where + _chainId = _chainId . _blockInProgressBlockCtx + {-# INLINE _chainId #-} + +makeLenses ''PactBlockState + +makeLenses ''PactBlockEnv + +guardCtx :: (ChainwebVersion -> Chainweb.ChainId -> BlockHeight -> a) -> BlockCtx -> a +guardCtx g txCtx = g (_chainwebVersion txCtx) (_chainId txCtx) (_bctxCurrentBlockHeight txCtx) -- | Indicates a computed gas charge (gas amount * gas price) newtype GasSupply = GasSupply { _pact5GasSupply :: Decimal } @@ -494,6 +620,14 @@ instance J.Encode GasSupply where build = J.build . Pact.StableEncoding . Pact.LDecimal . _pact5GasSupply instance Show GasSupply where show (GasSupply g) = show g +pactTransaction :: Maybe RequestKey -> (PactDb Pact.CoreBuiltin Pact.Info -> Pact.SPVSupport -> IO a) -> PactBlockM logger tbl a +pactTransaction rk k = do + e <- view psBlockDbEnv + h <- use pbBlockHandle + (r, h') <- liftIO $ doChainwebPactDbTransaction e h rk k + pbBlockHandle .= h' + return r + localLabelBlock :: (Logger logger) => (Text, Text) -> PactBlockM logger tbl x -> PactBlockM logger tbl x localLabelBlock lbl x = do locally (psServiceEnv . psLogger) (addLabel lbl) x @@ -525,14 +659,41 @@ noPublicMeta = Pact.PublicMeta data BuyGasError = BuyGasPactError !(Pact.PactError Pact.Info) | BuyGasMultipleGasPayerCaps + deriving stock (Show, Eq, Generic) data RedeemGasError = RedeemGasPactError !(Pact.PactError Pact.Info) + deriving stock (Show, Eq, Generic) data TxInvalidError = BuyGasError !BuyGasError | RedeemGasError !RedeemGasError | PurchaseGasTxTooBigForGasLimit + | TxInsertError !InsertError + | TxExceedsBlockGasLimit !Int + deriving stock (Show, Eq, Generic) + +data BlockInvalidError + = BlockInvalidDueToOutputMismatch BlockOutputMismatchError + | BlockInvalidDueToInvalidTxs (NonEmpty (Pact.RequestKey, InsertError)) + | BlockInvalidDueToInvalidTxAtRuntime TxInvalidError + | BlockInvalidDueToTxDecodeFailure [Text] + | BlockInvalidDueToCoinbaseFailure (Pact.PactError Pact.Info) + deriving Show + +data BlockOutputMismatchError = BlockOutputMismatchError + { blockOutputMismatchCtx :: !(EvaluationCtx BlockPayloadHash) + , blockOutputMismatchActualPayload :: !Chainweb.PayloadWithOutputs + , blockOutputMismatchExpectedPayload :: !(Maybe Chainweb.PayloadWithOutputs) + } + deriving Show + +instance J.Encode BlockOutputMismatchError where + build bvf = J.object + [ "ctx" J..= J.encodeWithAeson (blockOutputMismatchCtx bvf) + , "actual" J..= J.encodeWithAeson (blockOutputMismatchActualPayload bvf) + , "expected" J..?= fmap J.encodeWithAeson (blockOutputMismatchExpectedPayload bvf) + ] -- | Write log message -- @@ -552,3 +713,87 @@ logError_ l = logg_ l Error logDebug_ :: (MonadIO m, Logger logger) => logger -> Text -> m () logDebug_ l = logg_ l Debug + +logInfoPact :: (Logger logger) => Text -> PactServiceM logger tbl () +logInfoPact msg = view psLogger >>= \l -> logInfo_ l msg + +logWarnPact :: (Logger logger) => Text -> PactServiceM logger tbl () +logWarnPact msg = view psLogger >>= \l -> logWarn_ l msg + +logErrorPact :: (Logger logger) => Text -> PactServiceM logger tbl () +logErrorPact msg = view psLogger >>= \l -> logError_ l msg + +logDebugPact :: (Logger logger) => Text -> PactServiceM logger tbl () +logDebugPact msg = view psLogger >>= \l -> logDebug_ l msg + +-- | This function converts CommandResults into bytes in a stable way that can +-- be stored on-chain. +pactCommandResultToBytes :: Pact.CommandResult Pact.Hash (Pact.PactError Pact.Info) -> ByteString +pactCommandResultToBytes cr = + J.encodeStrict (fmap Pact.pactErrorToOnChainError cr) + +hashPactTxLogs :: Pact.CommandResult [Pact.TxLog ByteString] err -> Pact.CommandResult Pact.Hash err +hashPactTxLogs cr = cr & over (Pact.crLogs . _Just) + (\ls -> Pact.hashTxLogs ls) + +toPayloadWithOutputs + :: Miner + -> Transactions Pact.Transaction OffChainCommandResult + -> Chainweb.PayloadWithOutputs +toPayloadWithOutputs mi ts = + let + oldSeq :: Vector (Pact.Transaction, OffChainCommandResult) + oldSeq = _transactionPairs ts + trans :: Vector Chainweb.Transaction + trans = cmdBSToTx . fst <$> oldSeq + transOuts :: Vector Chainweb.TransactionOutput + transOuts = Chainweb.TransactionOutput . pactCommandResultToBytes . hashPactTxLogs . snd <$> oldSeq + + miner :: Chainweb.MinerData + miner = toMinerData mi + cb :: Chainweb.CoinbaseOutput + cb = Chainweb.CoinbaseOutput $ pactCommandResultToBytes $ hashPactTxLogs $ _transactionCoinbase ts + blockTrans :: Chainweb.BlockTransactions + blockTrans = snd $ Chainweb.newBlockTransactions miner trans + cmdBSToTx :: Pact.Transaction -> Chainweb.Transaction + cmdBSToTx = Chainweb.Transaction . J.encodeStrict + . fmap (T.decodeUtf8 . SB.fromShort . view Pact.payloadBytes) + blockOuts :: Chainweb.BlockOutputs + blockOuts = snd $ Chainweb.newBlockOutputs cb transOuts + + blockPL :: Chainweb.BlockPayload + blockPL = Chainweb.blockPayload blockTrans blockOuts + plData :: Chainweb.PayloadData + plData = Chainweb.payloadData blockTrans blockPL + in Chainweb.payloadWithOutputs plData cb transOuts + +data PactTxFailureLog = PactTxFailureLog !Pact.RequestKey !Text + deriving stock (Generic) + deriving anyclass (NFData, Typeable) +instance LogMessage PactTxFailureLog where + logText (PactTxFailureLog rk msg) = + "Failed tx " <> sshow rk <> ": " <> msg +instance Show PactTxFailureLog where + show m = T.unpack (logText m) + +instance ToHttpApiData LocalPreflightSimulation where + toUrlPiece PreflightSimulation = toUrlPiece True + toUrlPiece LegacySimulation = toUrlPiece False + +instance FromHttpApiData LocalPreflightSimulation where + parseUrlPiece = fmap (bool LegacySimulation PreflightSimulation) . parseUrlPiece + +instance ToHttpApiData LocalSignatureVerification where + toUrlPiece Verify = toUrlPiece True + toUrlPiece NoVerify = toUrlPiece False + +instance FromHttpApiData LocalSignatureVerification where + parseUrlPiece = fmap (bool NoVerify Verify) . parseUrlPiece + +deriving newtype instance FromHttpApiData RewindDepth + +deriving newtype instance ToHttpApiData RewindDepth + +deriving newtype instance FromHttpApiData ConfirmationDepth + +deriving newtype instance ToHttpApiData ConfirmationDepth diff --git a/src/Chainweb/Pact/Validations.hs b/src/Chainweb/Pact/Validations.hs index 8e606f5fe4..06f8930bbb 100644 --- a/src/Chainweb/Pact/Validations.hs +++ b/src/Chainweb/Pact/Validations.hs @@ -46,36 +46,35 @@ import Data.Word (Word8) -- internal modules -import Chainweb.BlockHeader import Chainweb.BlockCreationTime (BlockCreationTime(..)) import Chainweb.Pact.Types +import Chainweb.Parent import Chainweb.Time (Seconds(..), Time(..), secondsToTimeSpan, scaleTimeSpan, second, add) import Chainweb.Version -import qualified Pact.Core.Command.Types as P -import qualified Pact.Core.ChainData as P -import qualified Pact.Core.Gas.Types as P -import qualified Pact.Core.Hash as P -import qualified Chainweb.Pact.Transaction as P -import qualified Chainweb.Pact.Transaction as Pact5 +import qualified Pact.Core.Command.Types as Pact +import qualified Pact.Core.ChainData as Pact +import qualified Pact.Core.Gas.Types as Pact +import qualified Pact.Core.Hash as Pact +import qualified Chainweb.Pact.Transaction as Pact import Chainweb.Utils (ebool_) -- | Check whether a local Api request has valid metadata -- assertPreflightMetadata - :: P.Command (P.Payload P.PublicMeta c) - -> TxContext + :: Pact.Command (Pact.Payload Pact.PublicMeta c) + -> BlockCtx -> Maybe LocalSignatureVerification -> PactServiceM logger tbl (Either (NonEmpty Text) ()) -assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do +assertPreflightMetadata cmd@(Pact.Command pay sigs hsh) txCtx sigVerify = do v <- view chainwebVersion cid <- view chainId - bgl <- view psBlockGasLimit + bgl <- view psNewBlockGasLimit - let P.PublicMeta pcid _ gl gp _ _ = P._pMeta pay - nid = P._pNetworkId pay - signers = P._pSigners pay + let Pact.PublicMeta pcid _ gl gp _ _ = Pact._pMeta pay + nid = Pact._pNetworkId pay + signers = Pact._pSigners pay let errs = catMaybes [ eUnless "Chain id mismatch" $ assertChainId cid pcid @@ -97,7 +96,7 @@ assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do | Just NoVerify <- sigVerify = True | otherwise = isRight $ assertValidateSigs hsh signers sigs - pct = _tcParentCreationTime txCtx + pct = _bctxParentCreationTime txCtx eUnless t assertion | assertion = Nothing @@ -109,46 +108,46 @@ assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do -- The supplied chain id should be derived from the current -- chainweb node structure -- -assertChainId :: ChainId -> P.ChainId -> Bool -assertChainId cid0 cid1 = chainIdToText cid0 == P._chainId cid1 +assertChainId :: ChainId -> Pact.ChainId -> Bool +assertChainId cid0 cid1 = chainIdToText cid0 == Pact._chainId cid1 -- | Check and assert that 'GasPrice' is rounded to at most 12 decimal -- places. -- -assertGasPrice :: P.GasPrice -> Bool -assertGasPrice (P.GasPrice gp) = decimalPlaces gp <= defaultMaxCoinDecimalPlaces +assertGasPrice :: Pact.GasPrice -> Bool +assertGasPrice (Pact.GasPrice gp) = decimalPlaces gp <= defaultMaxCoinDecimalPlaces -- | Check and assert that the 'GasLimit' of a transaction is less than or eqaul to -- the block gas limit -- -assertBlockGasLimit :: P.GasLimit -> P.GasLimit -> Bool +assertBlockGasLimit :: Pact.GasLimit -> Pact.GasLimit -> Bool assertBlockGasLimit bgl tgl = bgl >= tgl -- | Check and assert that 'ChainwebVersion' is equal to some pact 'NetworkId'. -- -assertNetworkId :: ChainwebVersion -> Maybe P.NetworkId -> Bool +assertNetworkId :: ChainwebVersion -> Maybe Pact.NetworkId -> Bool assertNetworkId _ Nothing = False -assertNetworkId v (Just (P.NetworkId nid)) = ChainwebVersionName nid == _versionName v +assertNetworkId v (Just (Pact.NetworkId nid)) = ChainwebVersionName nid == _versionName v -- | Check and assert that the number of signatures in a 'Command' is -- at most 100. -- -assertSigSize :: [P.UserSig] -> Bool +assertSigSize :: [Pact.UserSig] -> Bool assertSigSize sigs = length sigs <= defaultMaxCommandUserSigListSize -- | Check and assert that the initial 'Gas' cost of a transaction -- is less than the specified 'GasLimit'. -- -assertTxSize :: P.Gas -> P.GasLimit -> Bool -assertTxSize initialGas gasLimit = P.GasLimit initialGas < gasLimit +assertTxSize :: Pact.Gas -> Pact.GasLimit -> Bool +assertTxSize initialGas gasLimit = Pact.GasLimit initialGas < gasLimit -- | Check and assert that signers and user signatures are valid for a given -- transaction hash. -- assertValidateSigs :: () - => P.Hash - -> [P.Signer] - -> [P.UserSig] + => Pact.Hash + -> [Pact.Signer] + -> [Pact.UserSig] -> Either AssertValidateSigsError () assertValidateSigs hsh signers sigs = do let signersLength = length signers @@ -161,7 +160,7 @@ assertValidateSigs hsh signers sigs = do (signersLength == sigsLength) iforM_ (zip sigs signers) $ \pos (sig, signer) -> do - case P.verifyUserSig hsh sig signer of + case Pact.verifyUserSig hsh sig signer of Left errMsg -> Left (InvalidUserSig pos (Text.pack errMsg)) Right () -> Right () @@ -174,7 +173,7 @@ assertValidateSigs hsh signers sigs = do -- assertTxTimeRelativeToParent :: Parent BlockCreationTime - -> P.Command (P.Payload P.PublicMeta c) + -> Pact.Command (Pact.Payload Pact.PublicMeta c) -> Bool assertTxTimeRelativeToParent (Parent (BlockCreationTime txValidationTime)) tx = ttl > 0 @@ -183,35 +182,35 @@ assertTxTimeRelativeToParent (Parent (BlockCreationTime txValidationTime)) tx = && timeFromSeconds (txOriginationTime + ttl) > txValidationTime && ttl <= defaultMaxTTLSeconds where - P.TTLSeconds ttl = view (P.cmdPayload . P.pMeta . P.pmTTL) tx + Pact.TTLSeconds ttl = view (Pact.cmdPayload . Pact.pMeta . Pact.pmTTL) tx timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral - P.TxCreationTime txOriginationTime = view (P.cmdPayload . P.pMeta . P.pmCreationTime) tx + Pact.TxCreationTime txOriginationTime = view (Pact.cmdPayload . Pact.pMeta . Pact.pmCreationTime) tx -- | Check that the tx's creation time is not too far in the future relative -- to the block creation time assertTxNotInFuture :: Parent BlockCreationTime - -> P.Command (P.Payload P.PublicMeta c) + -> Pact.Command (Pact.Payload Pact.PublicMeta c) -> Bool assertTxNotInFuture (Parent (BlockCreationTime txValidationTime)) tx = timeFromSeconds txOriginationTime <= lenientTxValidationTime where timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral - P.TxCreationTime txOriginationTime = view (P.cmdPayload . P.pMeta . P.pmCreationTime) tx + Pact.TxCreationTime txOriginationTime = view (Pact.cmdPayload . Pact.pMeta . Pact.pmCreationTime) tx lenientTxValidationTime = add (scaleTimeSpan defaultLenientTimeSlop second) txValidationTime -- | Assert that the command hash matches its payload and -- its signatures are valid, without parsing the payload. -assertCommand :: Pact5.Transaction -> Either AssertCommandError () +assertCommand :: Pact.Transaction -> Either AssertCommandError () assertCommand cmd = do _ <- assertHash & _Left .~ InvalidPayloadHash - assertValidateSigs hsh signers (P._cmdSigs cmd) & _Left %~ AssertValidateSigsError + assertValidateSigs hsh signers (Pact._cmdSigs cmd) & _Left %~ AssertValidateSigsError where - hsh = P._cmdHash cmd - pwt = P._cmdPayload cmd - cmdBS = SBS.fromShort $ pwt ^. P.payloadBytes - signers = pwt ^. P.payloadObj . P.pSigners - assertHash = P.verifyHash hsh cmdBS + hsh = Pact._cmdHash cmd + pwt = Pact._cmdPayload cmd + cmdBS = SBS.fromShort $ pwt ^. Pact.payloadBytes + signers = pwt ^. Pact.payloadObj . Pact.pSigners + assertHash = Pact.verifyHash hsh cmdBS -- -------------------------------------------------------------------- -- -- defaults diff --git a/src/Chainweb/Parent.hs b/src/Chainweb/Parent.hs new file mode 100644 index 0000000000..198d922830 --- /dev/null +++ b/src/Chainweb/Parent.hs @@ -0,0 +1,39 @@ +{-# language DerivingStrategies #-} +{-# language GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DeriveFoldable #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE TemplateHaskell #-} + +module Chainweb.Parent + -- * Newtype wrappers for function parameters + ( Parent(..) + , _Parent + ) where + +import Control.DeepSeq +import Control.Lens +import Data.Aeson +import Data.Hashable +import Numeric.AffineSpace +import GHC.Generics + +import Chainweb.ChainId +import Chainweb.Version + +newtype Parent h = Parent { unwrapParent :: h } + deriving stock (Show, Functor, Foldable, Traversable, Eq, Ord, Generic) + deriving newtype (NFData, ToJSON, FromJSON, Hashable, LeftTorsor) + +instance HasChainId h => HasChainId (Parent h) where + _chainId = _chainId . unwrapParent +instance HasChainwebVersion h => HasChainwebVersion (Parent h) where + _chainwebVersion = _chainwebVersion . unwrapParent +instance HasChainGraph h => HasChainGraph (Parent h) where + _chainGraph = _chainGraph . unwrapParent + +makePrisms ''Parent + +-- NOTE: no instance for IsRanked a => IsRanked (Parent a) because that could be +-- confusing diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 1261a7b8e3..a7643e8bbc 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -8,6 +8,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DeriveFunctor #-} -- | -- Module: Chainweb.PayloadProvider @@ -102,8 +103,10 @@ import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.BlockPayloadHash import Chainweb.MinerReward +import Chainweb.Parent import Chainweb.Utils import Chainweb.Version + import Control.Concurrent.STM import Control.DeepSeq (NFData) import Control.Lens hiding ((.=)) @@ -320,7 +323,7 @@ data EvaluationCtx p = EvaluationCtx -- -- more efficient synchronization and a reduction of block propagation -- -- latencies. } - deriving (Show, Eq, Ord) + deriving (Functor, Show, Eq, Ord) _evaluationCtxCurrentHeight :: EvaluationCtx p -> BlockHeight _evaluationCtxCurrentHeight = succ . unwrapParent . _evaluationCtxParentHeight diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index c5b0d7b736..a7f86c6cc9 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1215,7 +1215,7 @@ getPayloadForContext p h ctx = do pld <- getPayload (_evmPayloadStore p) (_evmCandidatePayloads p) - (Priority $ negate $ int $ unwrapParent $ _evaluationCtxParentHeight ctx) + (Priority $ negate $ int $ _evaluationCtxCurrentHeight ctx) (_hintsOrigin <$> h) (_evaluationCtxRankedPayloadHash ctx) tableInsert (_evmCandidatePayloads p) rh pld diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index 84988844fc..480cb0455b 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -82,24 +82,6 @@ module Chainweb.PayloadProvider.Minimal , newMinimalPayloadProvider ) where -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.BlockPayloadHash -import Chainweb.Logger -import Chainweb.MinerReward -import Chainweb.PayloadProvider -import Chainweb.PayloadProvider.Minimal.Payload -import Chainweb.PayloadProvider.Minimal.PayloadDB qualified as PDB -import Chainweb.PayloadProvider.P2P -import Chainweb.PayloadProvider.P2P qualified as Rest -import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest -import Chainweb.Ranked -import Chainweb.Storage.Table -import Chainweb.Storage.Table.Map -import Chainweb.Storage.Table.RocksDB -import Chainweb.Utils -import Chainweb.Utils.Serialization -import Chainweb.Version import Configuration.Utils import Control.Concurrent.Async import Control.Concurrent.STM @@ -119,6 +101,26 @@ import P2P.TaskQueue import Servant.Client import System.LogLevel +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.Logger +import Chainweb.MinerReward +import Chainweb.Parent +import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.Minimal.Payload +import Chainweb.PayloadProvider.Minimal.PayloadDB qualified as PDB +import Chainweb.PayloadProvider.P2P +import Chainweb.PayloadProvider.P2P qualified as Rest +import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest +import Chainweb.Ranked +import Chainweb.Storage.Table +import Chainweb.Storage.Table.Map +import Chainweb.Storage.Table.RocksDB +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Chainweb.Version + -- -------------------------------------------------------------------------- -- data MinimalProviderConfig = MinimalProviderConfig diff --git a/src/Chainweb/PayloadProvider/P2P.hs b/src/Chainweb/PayloadProvider/P2P.hs index b148691670..54211fbb96 100644 --- a/src/Chainweb/PayloadProvider/P2P.hs +++ b/src/Chainweb/PayloadProvider/P2P.hs @@ -162,7 +162,7 @@ instance -- probably be passed as a parameter. -- newPayloadStore - :: Table tbl RankedBlockPayloadHash a + :: ReadableTable tbl RankedBlockPayloadHash a => HTTP.Manager -- ^ Manager for P2P networking. This manager should be shared by all -- P2P networks. @@ -193,7 +193,7 @@ newPayloadStore mgr logfun payloadDb cli = do -- getPayloadSimple :: forall a tbl - . Table tbl RankedBlockPayloadHash a + . ReadableTable tbl RankedBlockPayloadHash a => CasKeyType a ~ RankedBlockPayloadHash => PayloadStore tbl a -> RankedBlockPayloadHash @@ -211,7 +211,7 @@ getPayloadSimple s r = do -- getPayload :: forall a tbl candidateTbl - . Table tbl RankedBlockPayloadHash a + . ReadableTable tbl RankedBlockPayloadHash a => Table candidateTbl RankedBlockPayloadHash a => PayloadStore tbl a -> candidateTbl @@ -292,4 +292,3 @@ getPayload s candidateStore priority maybeOrigin payloadHash = do Left (e :: ClientError) -> do logg @T.Text Debug $ taskMsg k $ "failed: " <> sshow e throwM e - diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 694b905696..c6cc5c5dde 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -3,57 +3,129 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} + module Chainweb.PayloadProvider.Pact ( PactPayloadProvider(..) ) where -import Chainweb.Version -import Control.Concurrent.STM + +import Chainweb.BlockCreationTime +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.ChainId +import Chainweb.Counter +import Chainweb.Counter (Counter) +import Chainweb.Logger +import Chainweb.Mempool.Mempool +import Chainweb.Miner.Pact +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils +import qualified Chainweb.Pact.PactService as PS +import qualified Chainweb.Pact.Transaction as Pact +import Chainweb.Pact.Types +import Chainweb.Payload.PayloadStore +import qualified Chainweb.Payload.PayloadStore as PDB import Chainweb.PayloadProvider import Chainweb.PayloadProvider.P2P -import qualified Chainweb.Payload.PayloadStore as PDB -import Chainweb.Storage.Table.RocksDB import Chainweb.Storage.Table.Map -import Chainweb.BlockPayloadHash -import Chainweb.Pact.Backend.Types -import Data.LogMessage +import Chainweb.Storage.Table.RocksDB import Chainweb.Time -import Chainweb.Pact.Types +import Chainweb.Utils +import Chainweb.Version +import Control.Concurrent.Async +import Control.Concurrent.STM +import Data.IORef +import Data.LogMessage import Data.Text (Text) -import Chainweb.Counter -import Chainweb.Miner.Pact -import Chainweb.Logger +import Data.Vector (Vector) +import GHC.Stack (HasCallStack) +import System.LogLevel +import Chainweb.Payload (PayloadData) +import Data.Coerce +import Control.Lens +import qualified Network.HTTP.Client as HTTP -data Payload - -data PactPayloadProvider logger = PactPayloadProvider - { _pactChainwebVersion :: !ChainwebVersion - , _pactChainId :: !ChainId - , _pactPayloadVar :: !(TMVar NewPayload) - , _pactPayloadStore :: !(PDB.PayloadDb RocksDbTable) - , _pactCandidatePayloads :: !(MapTable RankedBlockPayloadHash Payload) - , _pactCheckpointer :: !(Checkpointer logger) - , _pactLogger :: !LogFunction - , _pactPreInsertCheckTimeout :: !Micros - -- ^ Maximum allowed execution time for the transactions validation. - , _pactReorgLimit :: !RewindLimit - -- ^ The limit of checkpointer's rewind in the `execValidationBlock` command. - , _pactGasLogger :: !(Maybe logger) - , _pactTxFailuresCounter :: !(Maybe (Counter "txFailures")) - , _pactTxTimeLimit :: !(Maybe Micros) - , _pactMiner :: !(Maybe Miner) - } - -instance HasChainId (PactPayloadProvider logger) where - _chainId = _pactChainId -instance HasChainwebVersion (PactPayloadProvider logger) where - _chainwebVersion = _pactChainwebVersion +newtype PactPayloadProvider logger tbl = PactPayloadProvider (PactServiceEnv logger tbl) +makePrisms ''PactPayloadProvider -instance Logger logger => PayloadProvider (PactPayloadProvider logger) where - prefetchPayloads :: Logger logger => PactPayloadProvider logger -> Maybe Hints -> ForkInfo -> IO () +instance (Logger logger, CanPayloadCas tbl) => PayloadProvider (PactPayloadProvider logger tbl) where + prefetchPayloads :: Logger logger => PactPayloadProvider logger tbl -> Maybe Hints -> ForkInfo -> IO () prefetchPayloads pp hints forkInfo = undefined - syncToBlock :: Logger logger => PactPayloadProvider logger -> Maybe Hints -> ForkInfo -> IO ConsensusState + syncToBlock :: Logger logger => PactPayloadProvider logger tbl -> Maybe Hints -> ForkInfo -> IO ConsensusState syncToBlock pp hints forkInfo = undefined - latestPayloadSTM :: Logger logger => PactPayloadProvider logger -> STM NewPayload - latestPayloadSTM = readTMVar . _pactPayloadVar - eventProof :: Logger logger => PactPayloadProvider logger -> XEventId -> IO SpvProof + latestPayloadSTM :: Logger logger => PactPayloadProvider logger tbl -> STM NewPayload + latestPayloadSTM = readTMVar . _psMiningPayloadVar . view _PactPayloadProvider + eventProof :: Logger logger => PactPayloadProvider logger tbl -> XEventId -> IO SpvProof eventProof = error "not figured out yet" + +-- | Initialization for Pact (in process) Api +withPactPayloadProvider + :: CanReadablePayloadCas tbl + => Logger logger + => HTTP.Manager + -> ChainwebVersion + -> ChainId + -> logger + -> Maybe (Counter "txFailures") + -> MempoolBackend Pact.Transaction + -> PayloadDb tbl + -> FilePath + -> PactServiceConfig + -> (PactPayloadProvider logger tbl -> IO a) + -> IO a +withPactPayloadProvider http ver cid logger txFailuresCounter mp pdb pactDbDir config action = + withSqliteDb cid logger pactDbDir False $ \sqlenv -> + PS.withPactService http ver cid mp logger txFailuresCounter mpa pdb sqlenv config action + where + mpa = pactMemPoolAccess mp $ addLabel ("sub-component", "MempoolAccess") logger + +pactMemPoolAccess + :: Logger logger + => MempoolBackend Pact.Transaction + -> logger + -> MemPoolAccess +pactMemPoolAccess mp logger = MemPoolAccess + { mpaGetBlock = pactMemPoolGetBlock mp logger + , mpaProcessFork = pactProcessFork mp logger + , mpaBadlistTx = mempoolAddToBadList mp + } + +pactMemPoolGetBlock + :: Logger logger + => MempoolBackend Pact.Transaction + -> logger + -> BlockFill + -> (MempoolPreBlockCheck Pact.Transaction to + -> BlockHeight + -> BlockHash + -> BlockCreationTime + -> IO (Vector to)) +pactMemPoolGetBlock mp theLogger bf validate height hash _btime = do + logFn theLogger Debug $! "pactMemPoolAccess - getting new block of transactions for " + <> "height = " <> sshow height <> ", hash = " <> sshow hash + mempoolGetBlock mp bf validate height hash + where + logFn :: Logger l => l -> LogFunctionText -- just for giving GHC some type hints + logFn l = logFunction l + +pactProcessFork + :: Logger logger + => MempoolBackend Pact.Transaction + -> logger + -> ((Vector Pact.Transaction, Vector TransactionHash) -> IO ()) +pactProcessFork mp theLogger (reintroTxs, validatedTxs) = do + logFn theLogger Debug $! + "pactMemPoolAccess - " <> sshow (length reintroTxs) <> " transactions to reintroduce" + -- No need to run pre-insert check here -- we know these are ok, and + -- calling the pre-check would block here (it calls back into pact service) + mempoolInsert mp UncheckedInsert reintroTxs + mempoolMarkValidated mp validatedTxs + + where + logFn :: Logger l => l -> LogFunctionText + logFn lg = logFunction lg diff --git a/src/Chainweb/Ranked.hs b/src/Chainweb/Ranked.hs index 1a6fafded2..4c5713382d 100644 --- a/src/Chainweb/Ranked.hs +++ b/src/Chainweb/Ranked.hs @@ -9,6 +9,7 @@ {-# LANGUAGE KindSignatures #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TemplateHaskell #-} -- | -- Module: Chainweb.Ranked @@ -25,6 +26,8 @@ -- module Chainweb.Ranked ( Ranked(..) +, rankedHeight +, ranked , encodeRanked , decodeRanked , JsonRanked(..) @@ -38,6 +41,7 @@ import Chainweb.Utils import Chainweb.Utils.Serialization import Control.DeepSeq +import Control.Lens hiding ((.=)) import Control.Monad import Data.Aeson @@ -46,7 +50,6 @@ import Data.Typeable (Proxy(..), Typeable, typeRep) import GHC.Generics (Generic) import GHC.TypeLits -import Data.Kind (Type) -- -------------------------------------------------------------------------- -- -- BlockHeight Ranked Data @@ -65,6 +68,7 @@ data Ranked a = Ranked } deriving (Show, Eq, Ord, Generic) deriving anyclass (Hashable, NFData) +makeLenses ''Ranked encodeRanked :: (a -> Put) -> Ranked a -> Put encodeRanked putA (Ranked r a) = do @@ -93,16 +97,11 @@ decodeRanked decodeA = Ranked -- abstract about both cases and to avoid cluttering the namespace. -- class Ord r => IsRanked r where - type Unranked r :: Type rank :: r -> BlockHeight - unranked :: r -> Unranked r - ranked :: BlockHeight -> Unranked r -> r instance Ord a => IsRanked (Ranked a) where - type Unranked (Ranked a) = a rank = _rankedHeight - unranked = _ranked - ranked = Ranked + -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/RestAPI/Orphans.hs b/src/Chainweb/RestAPI/Orphans.hs index 80e9843e48..cacdb8785e 100644 --- a/src/Chainweb/RestAPI/Orphans.hs +++ b/src/Chainweb/RestAPI/Orphans.hs @@ -53,7 +53,7 @@ import Chainweb.Utils import Chainweb.Utils.Paging import Chainweb.Utils.Serialization import Chainweb.Version -import Chainweb.Pact.Types +-- import Chainweb.Pact.Types import P2P.Peer @@ -172,25 +172,3 @@ instance where type MkLink (sym :> sub) a = MkLink sub a toLink toA _ = toLink toA (Proxy @(ChainIdSymbol sym :> sub)) - -instance ToHttpApiData LocalPreflightSimulation where - toUrlPiece PreflightSimulation = toUrlPiece True - toUrlPiece LegacySimulation = toUrlPiece False - -instance FromHttpApiData LocalPreflightSimulation where - parseUrlPiece = fmap (bool LegacySimulation PreflightSimulation) . parseUrlPiece - -instance ToHttpApiData LocalSignatureVerification where - toUrlPiece Verify = toUrlPiece True - toUrlPiece NoVerify = toUrlPiece False - -instance FromHttpApiData LocalSignatureVerification where - parseUrlPiece = fmap (bool NoVerify Verify) . parseUrlPiece - -deriving newtype instance FromHttpApiData RewindDepth - -deriving newtype instance ToHttpApiData RewindDepth - -deriving newtype instance FromHttpApiData ConfirmationDepth - -deriving newtype instance ToHttpApiData ConfirmationDepth diff --git a/src/Chainweb/SPV/CreateProof.hs b/src/Chainweb/SPV/CreateProof.hs index 182723d67c..c47667f3d5 100644 --- a/src/Chainweb/SPV/CreateProof.hs +++ b/src/Chainweb/SPV/CreateProof.hs @@ -50,6 +50,7 @@ import Chainweb.Crypto.MerkleLog import Chainweb.CutDB import Chainweb.Graph import Chainweb.MerkleUniverse +import Chainweb.Parent import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.SPV @@ -459,7 +460,7 @@ createPayloadProof_ getPrefix headerDb provider tcid scid txHeight txIx trgHeade -- 3. BlockHeader Chain Proof -- - let chainTrees = headerTree_ @BlockHash <$> chain + let chainTrees = headerTree_ @(Parent BlockHash) <$> chain -- 4. Cross Chain Proof -- @@ -496,7 +497,7 @@ crumbsOnChain db trgHeader srcHeight go cur acc | srcHeight == view blockHeight cur = return $! (cur N.:| acc) | otherwise = do - p <- lookupParentHeader db cur + Parent p <- lookupParentHeader db cur go p (cur : acc) -- | Create a path of bread crumbs from the source chain id to the target header @@ -524,7 +525,7 @@ crumbsToChain db srcCid trgHeader -> IO (BlockHeader, [(Int, BlockHeader)]) go !cur [] !acc = return (cur, acc) go !cur ((!h):t) !acc = do - adjpHdr <- lookupAdjacentParentHeader db cur h + Parent adjpHdr <- lookupAdjacentParentHeader db cur h unless (view blockHeight adjpHdr >= 0) $ throwM $ InternalInvariantViolation $ "crumbsToChain: Encountered Genesis block. Chain can't be reached for SPV proof." diff --git a/src/Chainweb/SPV/EventProof.hs b/src/Chainweb/SPV/EventProof.hs index a8320b7d66..8138fc2f57 100644 --- a/src/Chainweb/SPV/EventProof.hs +++ b/src/Chainweb/SPV/EventProof.hs @@ -124,10 +124,8 @@ import GHC.Generics import Numeric.Natural -import Pact.Types.Command -import Pact.Types.PactValue -import Pact.Types.Pretty -import Pact.Types.Runtime hiding (fromText) +import Pact.Core.Command.Types +import Pact.Core.PactValue -- internal modules @@ -145,6 +143,11 @@ import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Storage.Table +import Pact.Core.Names +import Pact.Core.Capabilities +import Pact.Core.Hash +import Pact.Core.ModRefs +import Pact.Core.Errors -- -------------------------------------------------------------------------- -- -- Pact Encoding Exceptions @@ -266,7 +269,7 @@ int256Hex x@(Int256 i) -- -------------------------------------------------------------------------- -- -- Pact Event Encoding -encodePactEvent :: PactEvent -> Put +encodePactEvent :: PactEvent PactValue -> Put encodePactEvent e = do encodeString $ _eventName e encodeModuleName $ _eventModule e @@ -305,8 +308,7 @@ encodeHash :: Hash -> Put encodeHash = encodeBytes . BS.fromShort . unHash encodeModRef :: ModRef -> Put -encodeModRef n@(ModRef _ (Just _) _) = throw $ UnsupportedModRefWithSpec (renderCompactText n) -encodeModRef n@(ModRef _ _ (Info (Just _))) = throw $ UnsupportedModRefWithInfo (renderCompactText n) +encodeModRef n@(ModRef (Just _) _) = throw $ UnsupportedModRefWithSpec (renderCompactText n) encodeModRef n = encodeString $ renderCompactText n -- | This throws a pure exception of type 'PactEventEncodingException', if the @@ -322,9 +324,9 @@ encodeArray a f | otherwise = putWord32le (int $ length a) >> traverse_ f a encodeParam :: PactValue -> Put -encodeParam (PLiteral (LString t)) = putWord8 0x0 >> encodeString t -encodeParam (PLiteral (LInteger i)) = putWord8 0x1 >> encodeInteger i -encodeParam (PLiteral (LDecimal i)) = putWord8 0x2 >> encodeDecimal i +encodeParam (PString t) = putWord8 0x0 >> encodeString t +encodeParam (PInteger i) = putWord8 0x1 >> encodeInteger i +encodeParam (PDecimal i) = putWord8 0x2 >> encodeDecimal i encodeParam (PModRef n) = putWord8 0x3 >> encodeModRef n encodeParam e = throw $ UnsupportedPactValueException e @@ -337,18 +339,17 @@ expect c = label "expect" $ do unless (c == c') $ fail $ "decodeOutputEvents: failed to decode, expected " <> show c <> " but got " <> show c' -decodePactEvent :: Get PactEvent +decodePactEvent :: Get (PactEvent PactValue) decodePactEvent = label "decodeEvent" $ do name <- decodeString m <- decodeModuleName mh <- ModuleHash <$> decodeHash params <- decodeArray decodeParam return $ PactEvent - { _eventModule = m - , _eventName = name - , _eventModuleHash = mh - , _eventParams = params - } + name + m + mh + params decodeArray :: Get a -> Get [a] decodeArray f = label "decodeArray" $ do @@ -380,9 +381,9 @@ decodeDecimal = label "decodeDecimal" $ integerToDecimal <$> decodeInteger decodeParam :: Get PactValue decodeParam = label "decodeParam" $ getWord8 >>= \case - 0x0 -> label "LString" $ PLiteral . LString <$> decodeString - 0x1 -> label "LInteger" $ PLiteral . LInteger <$> decodeInteger - 0x2 -> label "LDecimal" $ PLiteral . LDecimal <$> decodeDecimal + 0x0 -> label "LString" $ PString <$> decodeString + 0x1 -> label "LInteger" $ PInteger <$> decodeInteger + 0x2 -> label "LDecimal" $ PDecimal <$> decodeDecimal 0x3 -> label "PModRef" $ PModRef <$> decodeModRef e -> fail $ "decodeParam: unexpected parameter with type tag " <> show e @@ -395,8 +396,7 @@ decodeModuleName = label "decodeModuleName" $ decodeModRef :: Get ModRef decodeModRef = label "ModRef" $ ModRef <$> decodeModuleName - <*> pure Nothing - <*> pure (Info Nothing) + <*> pure mempty -- -------------------------------------------------------------------------- -- -- Block Events Hash @@ -444,7 +444,7 @@ instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag (BlockEvent -- data OutputEvents = OutputEvents { _outputEventsRequestKey :: !RequestKey - , _outputEventsEvents :: !(V.Vector PactEvent) + , _outputEventsEvents :: !(V.Vector (PactEvent PactValue)) } deriving (Show, Eq, Generic) @@ -517,7 +517,7 @@ getBlockEvents -> m (BlockEvents_ b) getBlockEvents p = fmap blockEvents $ forM (_payloadWithOutputsTransactions p) $ \(_, o) -> do - r <- decodeStrictOrThrow @_ @(CommandResult Hash) $ _transactionOutputBytes o + r <- decodeStrictOrThrow @_ @(CommandResult Hash PactOnChainError) $ _transactionOutputBytes o return $ OutputEvents (_crReqKey r) (V.fromList (_crEvents r)) -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/SPV/OutputProof.hs b/src/Chainweb/SPV/OutputProof.hs index adb6794501..3dc941b1df 100644 --- a/src/Chainweb/SPV/OutputProof.hs +++ b/src/Chainweb/SPV/OutputProof.hs @@ -46,8 +46,9 @@ import GHC.Stack import Numeric.Natural -import Pact.Types.Command -import Pact.Types.Runtime hiding (ChainId) +import Pact.Core.Command.Types +import Pact.Core.Errors +import Pact.Core.Hash -- internal modules @@ -79,7 +80,7 @@ findTxIdx findTxIdx p reqKey = do -- get request keys reqKeys <- forM (_payloadWithOutputsTransactions p) $ \(_, o) -> do - result <- decodeStrictOrThrow @_ @(CommandResult Hash) $ _transactionOutputBytes o + result <- decodeStrictOrThrow @_ @(CommandResult Hash PactOnChainError) $ _transactionOutputBytes o return (_crReqKey result) -- find tx index case V.findIndex (== reqKey) reqKeys of @@ -100,7 +101,7 @@ getRequestKey getRequestKey p txIdx = case _payloadWithOutputsTransactions p V.!? txIdx of Nothing -> throwM $ TxIndexOutOfBoundsException txIdx Just (_, o) -> _crReqKey - <$> decodeStrictOrThrow @_ @(CommandResult Hash) (_transactionOutputBytes o) + <$> decodeStrictOrThrow @_ @(CommandResult Hash PactOnChainError) (_transactionOutputBytes o) -- -------------------------------------------------------------------------- -- -- Transaction Output Proofs By Index diff --git a/src/Chainweb/SPV/PayloadProof.hs b/src/Chainweb/SPV/PayloadProof.hs index 23853a7700..ab3e0c1957 100644 --- a/src/Chainweb/SPV/PayloadProof.hs +++ b/src/Chainweb/SPV/PayloadProof.hs @@ -51,7 +51,7 @@ import qualified Data.Text as T import GHC.Generics -import Pact.Types.Command +import Pact.Core.Command.Types -- internal modules @@ -209,4 +209,3 @@ runPayloadProof p = (_payloadProofRootType p, root,) <$> proofSubject blob where root = MerkleLogHash $ runMerkleProof blob blob = _payloadProofBlob p - diff --git a/src/Chainweb/SPV/VerifyProof.hs b/src/Chainweb/SPV/VerifyProof.hs index 9d0074f5ec..47f8e2fb53 100644 --- a/src/Chainweb/SPV/VerifyProof.hs +++ b/src/Chainweb/SPV/VerifyProof.hs @@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE ScopedTypeVariables #-} -- | diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 1d499d21dc..c56a8064ca 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -40,7 +40,6 @@ module Chainweb.Sync.WebBlockHeaderStore -- * Utils , memoInsert -, PactExecutionService(..) ) where import Chainweb.BlockHash @@ -63,7 +62,6 @@ import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Utils (diameterAt) import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService import Control.Concurrent.Async import Control.Lens import Control.Monad @@ -82,6 +80,7 @@ import P2P.TaskQueue import Servant.Client import System.LogLevel import Utils.Logging.Trace +import Chainweb.Parent -- -------------------------------------------------------------------------- -- -- Response Timeout Constants @@ -132,9 +131,6 @@ data WebBlockPayloadStore tbl = WebBlockPayloadStore -- ^ LogFunction , _webBlockPayloadStoreMgr :: !HTTP.Manager -- ^ Manager object for making HTTP requests - , _webBlockPayloadStorePact :: !WebPactExecutionService - -- ^ handle to the pact execution service for validating transactions - -- and computing outputs. } -- -------------------------------------------------------------------------- -- @@ -343,12 +339,16 @@ forkInfoForHeader wdb hdr pldData } | otherwise = do - phdr <- ParentHeader <$> lookupParentHeader wdb hdr + phdr <- lookupParentHeader wdb hdr + let consensusPayload = ConsensusPayload + { _consensusPayloadHash = pld + , _consensusPayloadData = pldData + } -- TargetState state <- consensusState wdb hdr return $ ForkInfo - { _forkInfoTrace = [blockHeaderToEvaluationCtx phdr pld pldData] + { _forkInfoTrace = [consensusPayload <$ blockHeaderToEvaluationCtx phdr] , _forkInfoBasePayloadHash = view blockPayloadHash (unwrapParent phdr) , _forkInfoTargetState = state , _forkInfoNewBlockCtx = Just nbctx @@ -454,7 +454,7 @@ getBlockHeaderInternal let isGenesisParentHash p = _chainValueValue p == genesisParentBlockHash v p queryAdjacentParent p = Concurrently $ unless (isGenesisParentHash p) $ void $ do logg Debug $ taskMsg k - $ "getBlockHeaderInternal.getPrerequisteHeader (adjacent) for " <> sshow h + $ "getBlockHeaderInternal.getPrerequisiteHeader (adjacent) for " <> sshow h <> ": " <> sshow p getBlockHeaderInternal headerStore @@ -464,7 +464,7 @@ getBlockHeaderInternal localPayload priority maybeOrigin' - p + (unwrapParent <$> p) -- Perform inductive (involving the parent) validations on the block -- header. There's another complete pass of block header validations @@ -472,7 +472,7 @@ getBlockHeaderInternal -- queryParent p = Concurrently $ void $ do logg Debug $ taskMsg k - $ "getBlockHeaderInternal.getPrerequisteHeader (parent) for " <> sshow h + $ "getBlockHeaderInternal.getPrerequisiteHeader (parent) for " <> sshow h <> ": " <> sshow p void $ getBlockHeaderInternal headerStore @@ -482,7 +482,7 @@ getBlockHeaderInternal localPayload priority maybeOrigin' - p + (unwrapParent <$> p) chainDb <- getWebBlockHeaderDb wdb header validateInductiveChainM (tableLookup chainDb) header @@ -670,25 +670,23 @@ newEmptyWebPayloadStore :: CanPayloadCas tbl => ChainwebVersion -> HTTP.Manager - -> WebPactExecutionService -> LogFunction -> PayloadDb tbl -> IO (WebBlockPayloadStore tbl) -newEmptyWebPayloadStore v mgr pact logfun payloadDb = do +newEmptyWebPayloadStore v mgr logfun payloadDb = do initializePayloadDb v payloadDb - newWebPayloadStore mgr pact payloadDb logfun + newWebPayloadStore mgr payloadDb logfun newWebPayloadStore :: HTTP.Manager - -> WebPactExecutionService -> PayloadDb tbl -> LogFunction -> IO (WebBlockPayloadStore tbl) -newWebPayloadStore mgr pact payloadDb logfun = do +newWebPayloadStore mgr payloadDb logfun = do payloadTaskQueue <- newEmptyPQueue payloadMemo <- new return $! WebBlockPayloadStore - payloadDb payloadMemo payloadTaskQueue logfun mgr pact + payloadDb payloadMemo payloadTaskQueue logfun mgr getBlockHeader :: BlockHeaderCas candidateHeaderCas diff --git a/src/Chainweb/VerifierPlugin.hs b/src/Chainweb/VerifierPlugin.hs index 4399a5e066..6f0623f472 100644 --- a/src/Chainweb/VerifierPlugin.hs +++ b/src/Chainweb/VerifierPlugin.hs @@ -35,16 +35,17 @@ import Data.Set(Set) import qualified Data.Set as Set import Data.STRef -import Pact.Types.Capability -import Pact.Types.Gas -import Pact.Types.PactValue -import Pact.Types.Verifier +import Pact.Core.Errors (VerifierError(..)) +import Pact.Core.Gas +import Pact.Core.Names +import Pact.Core.PactValue +import Pact.Core.Signer (SigCapability) +import Pact.Core.Verifiers import Chainweb.Version import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Utils -import Pact.Core.Errors (VerifierError(..)) newtype VerifierPlugin = VerifierPlugin @@ -62,12 +63,12 @@ instance NFData VerifierPlugin where chargeGas :: STRef s Gas -> Gas -> ExceptT VerifierError (ST s) () chargeGas r g = do gasRemaining <- lift $ readSTRef r - when (g < 0) $ throwError $ VerifierError $ + when (_gas g < 0) $ throwError $ VerifierError $ "verifier attempted to charge negative gas amount: " <> sshow g when (g > gasRemaining) $ throwError $ VerifierError $ "gas exhausted in verifier. attempted to charge " <> sshow (case g of Gas g' -> g') <> " with only " <> sshow (case gasRemaining of Gas gasRemaining' -> gasRemaining') <> " remaining." - lift $ writeSTRef r (gasRemaining - g) + lift $ writeSTRef r (Gas $ _gas gasRemaining - _gas g) runVerifierPlugins :: Logger logger diff --git a/src/Chainweb/VerifierPlugin/Allow.hs b/src/Chainweb/VerifierPlugin/Allow.hs index 24676750ca..99298def62 100644 --- a/src/Chainweb/VerifierPlugin/Allow.hs +++ b/src/Chainweb/VerifierPlugin/Allow.hs @@ -10,24 +10,24 @@ import Data.Aeson import qualified Data.Set as Set import qualified Data.Text.Encoding as Text -import Pact.Types.Capability -import Pact.Types.Exp -import Pact.Types.PactValue +import Pact.Core.PactValue import Pact.Core.Errors (VerifierError(..)) import Chainweb.VerifierPlugin +import Pact.Core.Signer (SigCapability) +import Pact.Core.Gas -- This trivial verifier plugin takes as its "proof" a JSON-encoded capability, -- and grants only that capability. plugin :: VerifierPlugin plugin = VerifierPlugin $ \_ proof caps gasRef -> do - chargeGas gasRef 100 + chargeGas gasRef (Gas 100) decodedCap <- decodeArgToCap proof unless (caps == Set.singleton decodedCap) $ throwError $ VerifierError "granted capability is not the one in the proof" where decodeArgToCap :: MonadError VerifierError m => PactValue -> m SigCapability - decodeArgToCap (PLiteral (LString arg)) = + decodeArgToCap (PString arg) = case eitherDecodeStrict' (Text.encodeUtf8 arg) of Left _err -> throwError $ VerifierError $ "argument was not a JSON-encoded capability: " <> arg Right cap -> return cap diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Announcement.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Announcement.hs index fa60c09723..129131cf85 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Announcement.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Announcement.hs @@ -23,22 +23,23 @@ import qualified Data.Set as Set import Ethereum.Misc hiding (Word256) -import Pact.Types.Runtime -import Pact.Types.PactValue -import Pact.Types.Capability +import Pact.Core.Capabilities +import Pact.Core.Errors (VerifierError(..)) +import Pact.Core.Gas +import Pact.Core.PactValue +import Pact.Core.Signer import Chainweb.VerifierPlugin.Hyperlane.Utils import Chainweb.Utils.Serialization (putRawByteString, runPutS, putWord32be) import Chainweb.VerifierPlugin import Chainweb.Utils (decodeB64UrlNoPaddingText, sshow) -import Pact.Core.Errors (VerifierError(..)) plugin :: VerifierPlugin plugin = VerifierPlugin $ \_ proof caps gasRef -> do -- extract capability values (capLocation, capSigner, capMailboxAddress) <- case Set.toList caps of - [cap] -> case _scArgs cap of + [SigCapability cap] -> case _ctArgs cap of [location, sig, mailbox] -> return (location, sig, mailbox) _ -> throwError $ VerifierError "Incorrect number of capability arguments. Expected: storageLocation, signer." _ -> throwError $ VerifierError "Expected one capability." @@ -46,18 +47,18 @@ plugin = VerifierPlugin $ \_ proof caps gasRef -> do -- extract proof object values (storageLocationText, signatureBase64, mailboxAddressText) <- case proof of (PList values) - | [PLiteral (LString loc), PLiteral (LString sig), PLiteral (LString mailbox)] <- V.toList values + | [PString loc, PString sig, PString mailbox] <- V.toList values -> pure (loc, sig, mailbox) _ -> throwError $ VerifierError "Expected a proof data as a list" -- validate storage location - let storageLocationPactValue = PLiteral $ LString storageLocationText + let storageLocationPactValue = PString storageLocationText unless (storageLocationPactValue == capLocation) $ throwError $ VerifierError $ "Incorrect storageLocation. Expected: " <> sshow storageLocationPactValue <> " but got " <> sshow capLocation -- validate mailbox address - let mailboxAddressPactValue = PLiteral $ LString mailboxAddressText + let mailboxAddressPactValue = PString mailboxAddressText unless (mailboxAddressPactValue == capMailboxAddress) $ throwError $ VerifierError $ "Incorrect mailbox address. Expected: " <> sshow mailboxAddressPactValue <> " but got " <> sshow capMailboxAddress @@ -83,12 +84,12 @@ plugin = VerifierPlugin $ \_ proof caps gasRef -> do putRawByteString $ Text.encodeUtf8 storageLocationText signatureBinary <- do - chargeGas gasRef 5 + chargeGas gasRef (Gas 5) sig <- decodeB64UrlNoPaddingText signatureBase64 return sig - address <- chargeGas gasRef 16250 >> recoverAddress announcementDigest signatureBinary - let addressPactValue = PLiteral . LString . encodeHex <$> address + address <- chargeGas gasRef (Gas 16250) >> recoverAddress announcementDigest signatureBinary + let addressPactValue = PString . encodeHex <$> address case addressPactValue of Just addressPactValue' -> diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs index 891d100f4e..f5a6540636 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs @@ -17,10 +17,7 @@ module Chainweb.VerifierPlugin.Hyperlane.Message (plugin) where import Chainweb.Version.Guards import Chainweb.VerifierPlugin import qualified Chainweb.VerifierPlugin.Hyperlane.Message.After225 as After225 -import qualified Chainweb.VerifierPlugin.Hyperlane.Message.Before225 as Before225 plugin :: VerifierPlugin plugin = VerifierPlugin $ \(v, cid, bh) proof caps gasRef -> - if chainweb225Pact v cid bh - then After225.runPlugin proof caps gasRef - else Before225.runPlugin proof caps gasRef \ No newline at end of file + After225.runPlugin proof caps gasRef diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs index 4397c89cf4..d8ba043094 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs @@ -34,9 +34,13 @@ import Data.STRef import Ethereum.Misc hiding (Word256) -import Pact.Types.Runtime hiding (ChainId) -import Pact.Types.PactValue -import Pact.Types.Capability +import Pact.Core.Capabilities +import Pact.Core.Errors (VerifierError(..)) +import Pact.Core.Gas +import Pact.Core.Literal +import Pact.Core.Names +import Pact.Core.PactValue +import Pact.Core.Signer import Chainweb.Utils.Serialization (putRawByteString, runPutS, runGetS, putWord32be) @@ -44,13 +48,12 @@ import Chainweb.VerifierPlugin import Chainweb.VerifierPlugin.Hyperlane.Binary import Chainweb.VerifierPlugin.Hyperlane.Utils import Chainweb.Utils (encodeB64UrlNoPaddingText, decodeB64UrlNoPaddingText, sshow) -import Pact.Core.Errors (VerifierError(..)) evaluateST :: a -> ST s a evaluateST a = unsafeIOToST (evaluate a) base64DecodeGasCost :: Gas -base64DecodeGasCost = 5 +base64DecodeGasCost = Gas 5 runPlugin :: forall s . PactValue @@ -59,7 +62,7 @@ runPlugin :: forall s -> ExceptT VerifierError (ST s) () runPlugin proof caps gasRef = do -- extract capability values - SigCapability{..} <- case Set.toList caps of + SigCapability (CapToken { _ctArgs = capArgs }) <- case Set.toList caps of [cap] -> return cap _ -> throwError $ VerifierError "Expected one capability." @@ -71,18 +74,18 @@ runPlugin proof caps gasRef = do -> return i _ -> throwError $ VerifierError $ k <> " is not an integer" - (capMessageId, capMessage, capSigners, capThreshold) <- case _scArgs of + (capMessageId, capMessage, capSigners, capThreshold) <- case capArgs of [mid, mb, PList sigs, PLiteral literalThreshold] -> do threshold <- parseInt "Threshold" literalThreshold parsedSigners <- forM sigs $ \case - (PLiteral (LString v)) -> pure v + (PString v) -> pure v _ -> throwError $ VerifierError "Only string signers are supported" parsedObject <- case mb of - PObject (ObjectMap m) -> do + PObject m -> do let - parseField k = case (m ^? at (FieldKey k) . _Just . _PLiteral) of + parseField k = case (m ^? at (Field k) . _Just . _PLiteral) of Just l -> PLiteral . LInteger <$> parseInt k l _ -> throwError $ VerifierError $ k <> " is missing" @@ -91,7 +94,7 @@ runPlugin proof caps gasRef = do origin <- parseField "originDomain" destination <- parseField "destinationDomain" - return $ PObject $ ObjectMap $ m + return $ PObject $ m & at "version" .~ Just version & at "nonce" .~ Just nonce & at "originDomain" .~ Just origin @@ -106,7 +109,7 @@ runPlugin proof caps gasRef = do -- extract proof object values (hyperlaneMessageBase64, metadataBase64) <- case proof of PList values - | [PLiteral (LString msg), PLiteral (LString mtdt)] <- V.toList values -> + | [PString msg, PString mtdt] <- V.toList values -> pure (msg, mtdt) _ -> throwError $ VerifierError "Expected a proof data as a list" @@ -135,7 +138,7 @@ runPlugin proof caps gasRef = do hmRecipientPactValue = PLiteral $ LString $ encodeB64UrlNoPaddingText hmRecipient hmMessageBodyPactValue = PLiteral $ LString $ encodeB64UrlNoPaddingText hmMessageBody - hmMessagePactValue = PObject . ObjectMap . M.fromList $ + hmMessagePactValue = PObject . M.fromList $ [ ("version", hmVersionPactValue) , ("nonce", hmNoncePactValue) , ("originDomain", hmOriginDomainPactValue) @@ -167,7 +170,7 @@ runPlugin proof caps gasRef = do MerkleRootMultisigIsmMetadata{..} <- maybe (throwError $ VerifierError "error decoding metadata from bytes") return $ runGetS getMerkleRootMultisigIsmMetadata binMetadata - chargeGas gasRef 18 -- gas cost of the `branchRoot` + chargeGas gasRef (Gas 18) -- gas cost of the `branchRoot` root <- lift $ evaluateST $ branchRoot messageId mrmimMerkleProof mrmimMessageIdIndex let digestEnd = do putRawByteString root @@ -209,7 +212,7 @@ runPlugin proof caps gasRef = do verify [] _ = pure () verify (sig:sigs) validators = -- gas cost of the address recovery - chargeGas gasRef 16250 >> recoverAddress digest sig >>= \case + chargeGas gasRef (Gas 16250) >> recoverAddress digest sig >>= \case Just addr -> do case V.elemIndex (encodeHex addr) validators of Just i -> verify sigs (V.drop (i + 1) validators) diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message/Before225.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message/Before225.hs deleted file mode 100644 index 78f858859c..0000000000 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message/Before225.hs +++ /dev/null @@ -1,121 +0,0 @@ -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE RankNTypes #-} - --- | --- Chainweb.VerifierPlugin.Hyperlane.Message --- Copyright: Copyright © 2024 Kadena LLC. --- License: MIT --- --- Deprecated verifier plugin behaviour for Hyperlane Message. --- Verifies the message using the provided Message Id metadata. --- -module Chainweb.VerifierPlugin.Hyperlane.Message.Before225 (runPlugin) where - -import Control.Error -import Control.Monad (unless) -import Control.Monad.Except -import Control.Monad.ST - -import qualified Data.ByteString as BS -import qualified Data.Text.Encoding as Text -import qualified Data.Vector as V -import qualified Data.Set as Set -import Data.STRef - -import Ethereum.Misc hiding (Word256) - -import Pact.Types.Runtime hiding (ChainId) -import Pact.Types.PactValue -import Pact.Types.Capability - -import Chainweb.Utils.Serialization (putRawByteString, runPutS, runGetS, putWord32be) - -import Chainweb.VerifierPlugin -import Chainweb.VerifierPlugin.Hyperlane.Binary -import Chainweb.VerifierPlugin.Hyperlane.Utils -import Chainweb.Utils (encodeB64UrlNoPaddingText, decodeB64UrlNoPaddingText, sshow) -import Pact.Core.Errors (VerifierError(..)) - -base64DecodeGasCost :: Gas -base64DecodeGasCost = 5 - -runPlugin :: forall s - . PactValue - -> Set.Set SigCapability - -> STRef s Gas - -> ExceptT VerifierError (ST s) () -runPlugin proof caps gasRef = do - -- extract capability values - SigCapability{..} <- case Set.toList caps of - [cap] -> return cap - _ -> throwError $ VerifierError "Expected one capability." - - (capMessageBody, capRecipient, capSigners) <- case _scArgs of - [mb, r, sigs] -> return (mb, r, sigs) - _ -> throwError $ VerifierError $ "Incorrect number of capability arguments. Expected: messageBody, recipient, signers." - - -- extract proof object values - (hyperlaneMessageBase64, metadataBase64) <- case proof of - PList values - | [PLiteral (LString msg), PLiteral (LString mtdt)] <- V.toList values -> - pure (msg, mtdt) - _ -> throwError $ VerifierError "Expected a proof data as a list" - - (HyperlaneMessage{..}, hyperlaneMessageBinary) <- do - chargeGas gasRef base64DecodeGasCost - msg <- decodeB64UrlNoPaddingText hyperlaneMessageBase64 - decoded <- runGetS getHyperlaneMessage msg - return (decoded, msg) - - MessageIdMultisigIsmMetadata{..} <- do - chargeGas gasRef base64DecodeGasCost - metadata <- decodeB64UrlNoPaddingText metadataBase64 - runGetS getMessageIdMultisigIsmMetadata metadata - - -- validate recipient - let hmRecipientPactValue = PLiteral $ LString $ Text.decodeUtf8 $ BS.dropWhile (== 0) hmRecipient - unless (hmRecipientPactValue == capRecipient) $ - throwError $ VerifierError $ - "Recipients don't match. Expected: " <> sshow hmRecipientPactValue <> " but got " <> sshow capRecipient - - let - hmMessageBodyPactValue = PLiteral $ LString $ encodeB64UrlNoPaddingText hmMessageBody - - unless (hmMessageBodyPactValue == capMessageBody) $ - throwError $ VerifierError $ - "Invalid TokenMessage. Expected: " <> sshow hmMessageBodyPactValue <> " but got " <> sshow capMessageBody - - -- validate signers - let - domainHash = keccak256ByteString $ runPutS $ do - -- Corresponds to abi.encodePacked behaviour - putWord32be hmOriginDomain - putRawByteString mmimOriginMerkleTreeAddress - putRawByteString "HYPERLANE" - - let messageId = keccak256ByteString hyperlaneMessageBinary - - let - digest = keccak256 $ runPutS $ do - -- Corresponds to abi.encodePacked behaviour - putRawByteString ethereumHeader - putRawByteString $ - keccak256ByteString $ runPutS $ do - putRawByteString domainHash - putRawByteString mmimSignedCheckpointRoot - putWord32be mmimSignedCheckpointIndex - putRawByteString messageId - - -- 16250 is a gas cost of the address recovery - addresses <- catMaybes <$> mapM (\sig -> chargeGas gasRef 16250 >> recoverAddress digest sig) mmimSignatures - let addressesVals = PList $ V.fromList $ map (PLiteral . LString . encodeHex) addresses - - -- Note, that we check the signers for the full equality including their order and amount. - -- Hyperlane's ISM uses a threshold and inclusion check. - unless (addressesVals == capSigners) $ - throwError $ VerifierError $ - "Signers don't match. Expected: " <> sshow addressesVals <> " but got " <> sshow capSigners diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index eec867bf8f..aa2f3d98e3 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -169,7 +169,8 @@ import GHC.Stack -- internal modules -import Pact.Types.Runtime (Gas) +import Pact.Core.Gas (Gas) +import Pact.Core.Names (VerifierName) import Chainweb.BlockCreationTime import Chainweb.BlockHeight @@ -185,8 +186,6 @@ import Chainweb.Utils import Chainweb.Utils.Rule import Chainweb.Utils.Serialization -import Pact.Types.Verifier - import Data.Singletons import P2P.Peer @@ -372,12 +371,7 @@ instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ChainwebVer data PactUpgrade = PactUpgrade { _pactUpgradeTransactions :: [Pact.Transaction] - } - -instance Eq PactUpgrade where - PactUpgrade txs == PactUpgrade txs' = - txs == txs' - _ == _ = False + } deriving Eq instance Show PactUpgrade where show PactUpgrade {} = "" diff --git a/src/Chainweb/Version/Development.hs b/src/Chainweb/Version/Development.hs index 1f9413ed36..f2eefcc292 100644 --- a/src/Chainweb/Version/Development.hs +++ b/src/Chainweb/Version/Development.hs @@ -18,7 +18,7 @@ import Chainweb.Utils import Chainweb.Utils.Rule import Chainweb.Version -import Pact.Types.Verifier +import Pact.Core.Names pattern Development :: ChainwebVersion pattern Development <- ((== devnet) -> True) where diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index b6681bac1f..413893e2f5 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -21,7 +21,7 @@ import Chainweb.Utils import Chainweb.Utils.Rule import Chainweb.Version -import Pact.Types.Verifier +import Pact.Core.Names pattern EvmDevelopment :: ChainwebVersion pattern EvmDevelopment <- ((== evmDevnet) -> True) where @@ -95,4 +95,3 @@ evmDevnet = ChainwebVersion : (unsafeChainId 1, EvmProvider 1790) : [ (unsafeChainId i, MinimalProvider) | i <- [2..19] ] } - diff --git a/src/Chainweb/Version/Guards.hs b/src/Chainweb/Version/Guards.hs index 0b918f2e4f..1c5456b502 100644 --- a/src/Chainweb/Version/Guards.hs +++ b/src/Chainweb/Version/Guards.hs @@ -70,9 +70,9 @@ import Chainweb.Utils.Rule import Chainweb.Version import Control.Lens import Numeric.Natural -import Pact.Core.Builtin qualified as Pact5 -import Pact.Core.Info qualified as Pact5 -import Pact.Core.Serialise qualified as Pact5 +import Pact.Core.Builtin qualified as Pact +import Pact.Core.Info qualified as Pact +import Pact.Core.Serialise qualified as Pact import Pact.Types.KeySet (PublicKeyText, ed25519HexFormat, webAuthnFormat) import Pact.Types.Scheme (PPKScheme(ED25519, WebAuthn)) @@ -273,10 +273,10 @@ chainweb228Pact = checkFork atOrAfter Chainweb228Pact chainweb229Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool chainweb229Pact = checkFork atOrAfter Chainweb229Pact -pact5Serialiser :: ChainwebVersion -> ChainId -> BlockHeight -> Pact5.PactSerialise Pact5.CoreBuiltin Pact5.LineInfo +pact5Serialiser :: ChainwebVersion -> ChainId -> BlockHeight -> Pact.PactSerialise Pact.CoreBuiltin Pact.LineInfo pact5Serialiser v cid bh - | chainweb228Pact v cid bh = Pact5.serialisePact_lineinfo_pact51 - | otherwise = Pact5.serialisePact_lineinfo_pact50 + | chainweb228Pact v cid bh = Pact.serialisePact_lineinfo_pact51 + | otherwise = Pact.serialisePact_lineinfo_pact50 maxBlockGasLimit :: ChainwebVersion -> BlockHeight -> Maybe Natural maxBlockGasLimit v bh = snd $ ruleZipperHere $ snd diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index d2f87959f8..ca63c92655 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -22,8 +22,8 @@ import Chainweb.Utils.Rule import Chainweb.Version import P2P.BootstrapNodes -import Pact.Types.Runtime (Gas(..)) -import Pact.Types.Verifier +import Pact.Core.Gas(Gas(..)) +import Pact.Core.Names -- | Initial hash target for mainnet 20-chain transition. Difficulty on the new -- chains is 1/4 of the current difficulty. It is based on the following header diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index f5530199b3..58e4faac44 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -19,7 +19,7 @@ import Chainweb.Utils import Chainweb.Utils.Rule import Chainweb.Version -import Pact.Types.Verifier +import Pact.Core.Names to20ChainsHeight :: BlockHeight to20ChainsHeight = 60 diff --git a/src/Chainweb/Version/Testnet04.hs b/src/Chainweb/Version/Testnet04.hs index 66ca055abb..b7c3de1543 100644 --- a/src/Chainweb/Version/Testnet04.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -22,8 +22,8 @@ import Chainweb.Utils.Rule import Chainweb.Version import P2P.BootstrapNodes -import Pact.Types.Runtime (Gas(..)) -import Pact.Types.Verifier +import Pact.Core.Gas (Gas(..)) +import Pact.Core.Names -- | Initial hash target for testnet04 20-chain transition. Based on the following -- header from recap devnet running with 5 GPUs hash power. Using this target unchanged diff --git a/src/Chainweb/Version/Testnet05.hs b/src/Chainweb/Version/Testnet05.hs index 89991180a5..1f6107ed8c 100644 --- a/src/Chainweb/Version/Testnet05.hs +++ b/src/Chainweb/Version/Testnet05.hs @@ -20,7 +20,7 @@ import Chainweb.Utils.Rule import Chainweb.Version import P2P.BootstrapNodes -import Pact.Types.Verifier +import Pact.Core.Names pattern Testnet05 :: ChainwebVersion pattern Testnet05 <- ((== testnet05) -> True) where diff --git a/src/Chainweb/Version/Utils.hs b/src/Chainweb/Version/Utils.hs index c890d44467..cb623e19d6 100644 --- a/src/Chainweb/Version/Utils.hs +++ b/src/Chainweb/Version/Utils.hs @@ -89,7 +89,7 @@ import Chainweb.Utils.Rule import Chainweb.Version import Chainweb.Version.Mainnet -import Pact.Types.Verifier +import Pact.Core.Names (VerifierName(..)) -- -------------------------------------------------------------------------- -- -- Utils diff --git a/src/Chainweb/WebBlockHeaderDB.hs b/src/Chainweb/WebBlockHeaderDB.hs index 121a224338..3601fe33cd 100644 --- a/src/Chainweb/WebBlockHeaderDB.hs +++ b/src/Chainweb/WebBlockHeaderDB.hs @@ -63,6 +63,7 @@ import Chainweb.BlockHeaderDB.Internal import Chainweb.ChainId import Chainweb.ChainValue import Chainweb.Graph +import Chainweb.Parent import Chainweb.TreeDB import Chainweb.Utils import Chainweb.Version @@ -166,9 +167,9 @@ lookupWebBlockHeaderDb wdb c h = do blockAdjacentParentHeaders :: WebBlockHeaderDb -> BlockHeader - -> IO (HM.HashMap ChainId BlockHeader) + -> IO (HM.HashMap ChainId (Parent BlockHeader)) blockAdjacentParentHeaders db h - = itraverse (lookupWebBlockHeaderDb db) + = itraverse (traverse . lookupWebBlockHeaderDb db) $ _getBlockHashRecord $ view blockAdjacentHashes h @@ -176,19 +177,19 @@ lookupAdjacentParentHeader :: WebBlockHeaderDb -> BlockHeader -> ChainId - -> IO BlockHeader + -> IO (Parent BlockHeader) lookupAdjacentParentHeader db h cid = do checkWebChainId (db, view blockHeight h) h let ph = h ^?! (blockAdjacentHashes . ix cid) - lookupWebBlockHeaderDb db cid ph + traverse (lookupWebBlockHeaderDb db cid) ph lookupParentHeader :: WebBlockHeaderDb -> BlockHeader - -> IO BlockHeader + -> IO (Parent BlockHeader) lookupParentHeader db h = do checkWebChainId (db, view blockHeight h) h - lookupWebBlockHeaderDb db (_chainId h) (view blockParent h) + traverse (lookupWebBlockHeaderDb db (_chainId h)) (view blockParent h) -- -------------------------------------------------------------------------- -- -- Insertion diff --git a/src/Chainweb/WebPactExecutionService.hs b/src/Chainweb/WebPactExecutionService.hs deleted file mode 100644 index f2b9f7962d..0000000000 --- a/src/Chainweb/WebPactExecutionService.hs +++ /dev/null @@ -1,298 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE ImplicitParams #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} - -module Chainweb.WebPactExecutionService - ( WebPactExecutionService(..) - , _webPactNewBlock - , _webPactValidateBlock - , _webPactSyncToBlock - , PactExecutionService(..) - , mkWebPactExecutionService - , mkPactExecutionService - , emptyPactExecutionService - , NewBlock(..) - , newBlockToPayloadWithOutputs - , newBlockParent - , newBlockToNewPayload - ) where - -import Control.Lens -import Control.Monad.Catch - -import qualified Data.HashMap.Strict as HM -import Data.Vector (Vector) - -import GHC.Stack - --- internal modules - -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Mempool.Mempool (InsertError) -import Chainweb.Pact.Service.BlockValidation -import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Types -import Chainweb.Pact.Utils -import Chainweb.Payload -import qualified Chainweb.Pact.Transaction as Pact4 -import Chainweb.Utils - -import qualified Pact.Core.Persistence as Pact5 -import Chainweb.Version -import Data.ByteString.Short (ShortByteString) -import qualified Pact.Core.Names as Pact5 -import qualified Pact.Core.Builtin as Pact5 -import qualified Pact.Core.Evaluate as Pact5 -import qualified Pact.Types.Command as Pact4 -import qualified Pact.Types.ChainMeta as Pact4 -import Data.Text (Text) -import Chainweb.BlockCreationTime (BlockCreationTime) -import Chainweb.PayloadProvider -import qualified Data.ByteString as B - --- -------------------------------------------------------------------------- -- --- PactExecutionService --- --- FIXME: This section does not belong into this Module. It should be in pact --- service. This module is node supposed to implement functionality of pact --- service but only to be a proxy for the Pact service API. - --- FIXME: PactExecutionService is the public API of Pact service. Why does the --- API leak the internals of unfinished blocks? In what situation would the --- caller care about NewBlockInProgress? If it is for testing it should be --- exposed through an internal type. --- --- FIXME: Make sure that PayloadNumber is strictly monotonic --- --- FIXME: compute total gas --- --- Currently, blocks are finalized only within the miner, which seems wrong. --- -data NewBlock - = NewBlockInProgress !(ForSomePactVersion BlockInProgress) - | NewBlockPayload !(Parent BlockHeader) !PayloadWithOutputs - deriving Show - -newBlockToPayloadWithOutputs :: NewBlock -> PayloadWithOutputs -newBlockToPayloadWithOutputs (NewBlockInProgress bip) - = forAnyPactVersion finalizeBlock bip -newBlockToPayloadWithOutputs (NewBlockPayload _ pwo) - = pwo - -newBlockParent :: NewBlock -> (BlockHash, BlockHeight, BlockCreationTime) -newBlockParent (NewBlockInProgress (ForSomePactVersion _ bip)) = blockInProgressParent bip -newBlockParent (NewBlockPayload (Parent ph) _) = - (view blockHash ph, view blockHeight ph, view blockCreationTime ph) - -instance HasChainwebVersion NewBlock where - _chainwebVersion (NewBlockInProgress (ForSomePactVersion _ bip)) = _chainwebVersion bip - _chainwebVersion (NewBlockPayload ph _) = _chainwebVersion ph - -instance HasChainId NewBlock where - _chainId (NewBlockInProgress (ForSomePactVersion _ bip)) = _chainId bip - _chainId (NewBlockPayload ph _) = _chainId ph - -newBlockToNewPayload :: NewBlock -> NewPayload -newBlockToNewPayload nb = NewPayload - { _newPayloadChainwebVersion = _chainwebVersion nb - , _newPayloadChainId = _chainId nb - , _newPayloadParentHeight = height - , _newPayloadParentHash = h - , _newPayloadBlockPayloadHash = _payloadWithOutputsPayloadHash pwo - , _newPayloadEncodedPayloadData = Just epd - , _newPayloadEncodedPayloadOutputs = Just epo - , _newPayloadNumber = 0 -- FIXME - , _newPayloadTxCount = int $ length txs - , _newPayloadSize = int $ sum $ B.length . _transactionBytes . fst <$> txs - , _newPayloadOutputSize = int $ sum $ B.length . _transactionOutputBytes . snd <$> txs - , _newPayloadFees = 0 -- FIXME - } - where - (h, height, _) = newBlockParent nb - pwo = newBlockToPayloadWithOutputs nb - txs = _payloadWithOutputsTransactions pwo - - epd = EncodedPayloadData $ encodeToByteString $ payloadWithOutputsToPayloadData pwo - epo = EncodedPayloadOutputs $ encodeToByteString $ pwo - --- -------------------------------------------------------------------------- -- - --- | Service API for interacting with a single or multi-chain ("Web") pact service. --- Thread-safe to be called from multiple threads. Backend is queue-backed on a per-chain --- basis. -data PactExecutionService = PactExecutionService - { _pactValidateBlock :: !( - BlockHeader -> - CheckablePayload -> - IO PayloadWithOutputs - ) - -- ^ Validate block payload data by running through pact service. - , _pactNewBlock :: !( - ChainId -> - NewBlockFill -> - Parent BlockHeader -> - IO (Historical NewPayload) - ) - , _pactContinueBlock :: !( - forall pv. - ChainId -> - BlockInProgress pv -> - IO (Historical (BlockInProgress pv)) - ) - -- ^ Request a new block to be formed using mempool - , _pactLocal :: !( - Maybe LocalPreflightSimulation -> - Maybe LocalSignatureVerification -> - Maybe RewindDepth -> - Pact4.UnparsedTransaction -> - IO LocalResult) - -- ^ Directly execute a single transaction in "local" mode (all DB interactions rolled back). - -- Corresponds to `local` HTTP endpoint. - , _pactLookup :: !( - ChainId - -- for routing - -> Maybe ConfirmationDepth - -- confirmation depth - -> Vector ShortByteString - -- txs to lookup - -> IO (HM.HashMap ShortByteString (T2 BlockHeight BlockHash)) - ) - , _pactReadOnlyReplay :: !( - BlockHeader -> - Maybe BlockHeader -> - IO () - ) - -- ^ Lookup pact hashes as of a block header to detect duplicates - , _pactPreInsertCheck :: !( - ChainId - -> Vector (Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text)) - -> IO (Vector (Maybe InsertError))) - -- ^ Run speculative checks to find bad transactions (ie gas buy failures, etc) - , _pactBlockTxHistory :: !( - BlockHeader -> - Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info -> - IO (Historical BlockTxHistory) - ) - -- ^ Obtain all transaction history in block for specified table/domain. - , _pactHistoricalLookup :: !( - BlockHeader -> - Pact5.Domain Pact5.RowKey Pact5.RowData Pact5.CoreBuiltin Pact5.Info -> - Pact5.RowKey -> - IO (Historical (Maybe (Pact5.TxLog Pact5.RowData))) - ) - -- ^ Obtain latest entry at or before the given block for specified table/domain and row key. - , _pactSyncToBlock :: !( - BlockHeader -> - IO () - ) - } - --- | Newtype to indicate "routing"/multi-chain service. --- See 'mkWebPactExecutionService' for implementation. -newtype WebPactExecutionService = WebPactExecutionService - { _webPactExecutionService :: PactExecutionService - } - - -_webPactNewBlock - :: WebPactExecutionService - -> ChainId - -> NewBlockFill - -> Parent BlockHeader - -> IO (Historical NewPayload) -_webPactNewBlock = _pactNewBlock . _webPactExecutionService -{-# INLINE _webPactNewBlock #-} - -_webPactContinueBlock - :: WebPactExecutionService - -> ChainId - -> BlockInProgress pv - -> IO (Historical (BlockInProgress pv)) -_webPactContinueBlock w cid bip = _pactContinueBlock (_webPactExecutionService w) cid bip -{-# INLINE _webPactContinueBlock #-} - -_webPactValidateBlock - :: WebPactExecutionService - -> BlockHeader - -> CheckablePayload - -> IO PayloadWithOutputs -_webPactValidateBlock = _pactValidateBlock . _webPactExecutionService -{-# INLINE _webPactValidateBlock #-} - -_webPactSyncToBlock - :: WebPactExecutionService - -> BlockHeader - -> IO () -_webPactSyncToBlock = _pactSyncToBlock . _webPactExecutionService -{-# INLINE _webPactSyncToBlock #-} - -mkWebPactExecutionService - :: HasCallStack - => HM.HashMap ChainId PactExecutionService - -> WebPactExecutionService -mkWebPactExecutionService hm = WebPactExecutionService $ PactExecutionService - { _pactValidateBlock = \h pd -> withChainService (_chainId h) $ \p -> _pactValidateBlock p h pd - , _pactNewBlock = \cid fill parent -> withChainService cid $ \p -> _pactNewBlock p cid fill parent - , _pactContinueBlock = \cid bip -> withChainService cid $ \p -> _pactContinueBlock p cid bip - , _pactLocal = \_pf _sv _rd _ct -> throwM $ userError "Chainweb.WebPactExecutionService.mkPactExecutionService: No web-level local execution supported" - , _pactLookup = \cid cd txs -> withChainService cid $ \p -> _pactLookup p cid cd txs - , _pactPreInsertCheck = \cid txs -> withChainService cid $ \p -> _pactPreInsertCheck p cid txs - , _pactBlockTxHistory = \h d -> withChainService (_chainId h) $ \p -> _pactBlockTxHistory p h d - , _pactHistoricalLookup = \h d k -> withChainService (_chainId h) $ \p -> _pactHistoricalLookup p h d k - , _pactSyncToBlock = \h -> withChainService (_chainId h) $ \p -> _pactSyncToBlock p h - , _pactReadOnlyReplay = \l u -> withChainService (_chainId l) $ \p -> _pactReadOnlyReplay p l u - } - where - withChainService cid act = maybe (err cid) act $ HM.lookup cid hm - err cid = throwM $ userError - $ "PactExecutionService: Invalid chain ID: " - ++ show cid - -mkPactExecutionService - :: PactQueue - -> PactExecutionService -mkPactExecutionService q = PactExecutionService - { _pactValidateBlock = \h pd -> do - validateBlock h pd q - , _pactNewBlock = \_ fill parent -> do - -- FIXME: This seems wrong. This should really be implemented in pact. - -- PactExecutionService is supposed to be a thin an lightweight wrapper. - nb <- newBlock fill parent q - return $ newBlockToNewPayload . NewBlockInProgress <$> nb - , _pactContinueBlock = \_ bip -> do - continueBlock bip q - , _pactLocal = \pf sv rd ct -> - local pf sv rd ct q - , _pactLookup = \_ cd txs -> - lookupPactTxs cd txs q - , _pactPreInsertCheck = \_ txs -> - pactPreInsertCheck txs q - , _pactBlockTxHistory = \h d -> - pactBlockTxHistory h d q - , _pactHistoricalLookup = \h d k -> - pactHistoricalLookup h d k q - , _pactSyncToBlock = \h -> pactSyncToBlock h q - , _pactReadOnlyReplay = \l u -> pactReadOnlyReplay l u q - } - --- | A mock execution service for testing scenarios. Throws out anything it's --- given. --- -emptyPactExecutionService :: HasCallStack => PactExecutionService -emptyPactExecutionService = PactExecutionService - { _pactValidateBlock = \_ _ -> pure emptyPayload - , _pactNewBlock = \_ _ _ -> throwM (userError "emptyPactExecutionService: attempted `newBlock` call") - , _pactContinueBlock = \_ _ -> throwM (userError "emptyPactExecutionService: attempted `continueBlock` call") - , _pactLocal = \_ _ _ _ -> throwM (userError "emptyPactExecutionService: attempted `local` call") - , _pactLookup = \_ _ _ -> return $! HM.empty - , _pactPreInsertCheck = \_ txs -> return $ Nothing <$ txs - , _pactBlockTxHistory = \_ _ -> error "Chainweb.WebPactExecutionService.emptyPactExecutionService: pactBlockTxHistory unsupported" - , _pactHistoricalLookup = \_ _ _ -> error "Chainweb.WebPactExecutionService.emptyPactExecutionService: pactHistoryLookup unsupported" - , _pactSyncToBlock = \_ -> return () - , _pactReadOnlyReplay = \_ _ -> return () - } From 5ec25065c496d8b342179c99e85713b473b4a240 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 21 Mar 2025 10:07:33 -0400 Subject: [PATCH 101/378] continueBlock --- src/Chainweb/Mempool/Mempool.hs | 20 +- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 11 +- src/Chainweb/Pact/Backend/Utils.hs | 20 +- src/Chainweb/Pact/PactService.hs | 394 ++++++++---------- src/Chainweb/Pact/PactService/Checkpointer.hs | 80 ++-- src/Chainweb/Pact/PactService/ExecBlock.hs | 188 ++++----- src/Chainweb/Pact/Types.hs | 154 +++---- src/Chainweb/PayloadProvider.hs | 3 +- 8 files changed, 407 insertions(+), 463 deletions(-) diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Mempool/Mempool.hs index e8411ed735..5a8a8c8432 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Mempool/Mempool.hs @@ -131,6 +131,14 @@ import System.LogLevel import qualified Pact.JSON.Encode as J +import Pact.Core.Command.Types qualified as Pact +import Pact.Core.Hash qualified as Pact +import Pact.Core.Gas +import Pact.Core.ChainData +import Pact.Core.ChainData qualified as Pact + +import Data.LogMessage (LogFunctionText) + import Chainweb.BlockHash import Chainweb.BlockHeight import Chainweb.Parent @@ -139,12 +147,8 @@ import Chainweb.Time qualified as Time import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Utils import Chainweb.Utils.Serialization -import Data.LogMessage (LogFunctionText) -import Pact.Core.Command.Types qualified as Pact -import Pact.Core.Hash qualified as Pact -import Pact.Core.Gas -import Pact.Core.ChainData -import qualified Pact.Core.ChainData as Pact + +import Chainweb.PayloadProvider (EvaluationCtx(..)) ------------------------------------------------------------------------------ data LookupResult t = Missing @@ -185,7 +189,7 @@ instance Traversable LookupResult where Pending x -> Pending <$> f x ------------------------------------------------------------------------------ -type MempoolPreBlockCheck ti to = BlockHeight -> Parent BlockHash -> Vector ti -> IO (Vector (Either InsertError to)) +type MempoolPreBlockCheck ti to = EvaluationCtx () -> Vector ti -> IO (Vector (Either InsertError to)) ------------------------------------------------------------------------------ -- | Mempool operates over a transaction type @t@. Mempool needs several @@ -333,7 +337,7 @@ data MempoolBackend t = MempoolBackend { } noopMempoolPreBlockCheck :: MempoolPreBlockCheck t t -noopMempoolPreBlockCheck _ _ v = return $! V.map Right v +noopMempoolPreBlockCheck _ v = return $! V.map Right v noopMempool :: IO (MempoolBackend t) noopMempool = do diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 26fcfed54d..b4b68e7438 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -74,7 +74,7 @@ module Chainweb.Pact.Backend.ChainwebPactDb , initSchema , lookupBlockWithHeight , lookupParentBlockHash - , lookupParentBlockRanked + , lookupBlockByEvalCtx , getPayloadsAfter , getEarliestBlock , getConsensusState @@ -980,9 +980,12 @@ lookupParentBlockHash db (Parent parentHash) = do where qtext = "SELECT COUNT(*) FROM BlockHistory WHERE parenthash = ?;" -lookupParentBlockRanked :: SQ3.Database -> Ranked (Parent BlockHash) -> ExceptT SQ3.Error IO Bool -lookupParentBlockRanked db (Ranked bheight (Parent parentHash)) = do - qry db qtext [SInt $ fromIntegral bheight, SBlob (runPutS (encodeBlockHash parentHash))] [RInt] >>= \case +lookupBlockByEvalCtx :: SQ3.Database -> EvaluationCtx p -> ExceptT SQ3.Error IO Bool +lookupBlockByEvalCtx db evalCtx = do + qry db qtext + [ SInt $ fromIntegral (_evaluationCtxCurrentHeight evalCtx) + , SBlob $ runPutS $ encodeBlockHash $ unwrapParent (_evaluationCtxParentHash evalCtx) + ] [RInt] >>= \case [[SInt n]] -> return $! n == 1 [_] -> error "lookupBlock: output type mismatch" _ -> error "Expected single-row result" diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 69d5508786..3972fbb567 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -110,6 +110,7 @@ import Data.HashSet (HashSet) import qualified Data.HashSet as HashSet import Data.Text (Text) import qualified Pact.Core.Persistence as Pact +import Control.Monad.Catch (ExitCase(..)) -- -------------------------------------------------------------------------- -- -- SQ3.Utf8 Encodings @@ -136,19 +137,12 @@ withSavepoint -> SavepointName -> m a -> m a -withSavepoint db name action = mask $ \resetMask -> do - liftIO $ beginSavepoint db name - go resetMask `catches` handlers - where - go resetMask = do - r <- resetMask action `onException` liftIO (abortSavepoint db name) - liftIO $ commitSavepoint db name - liftIO $ evaluate r - throwErr s = error $ "withSavepoint (" <> show name <> "): " <> s - handlers = - [ Handler $ \(e :: SomeAsyncException) -> throwM e - , Handler $ \(e :: SomeException) -> throwErr ("non-pact exception: " <> sshow e) - ] +withSavepoint db name action = fmap fst $ generalBracket + (liftIO $ beginSavepoint db name) + (\_ -> liftIO . \case + ExitCaseSuccess {} -> commitSavepoint db name + _ -> abortSavepoint db name + ) $ \_ -> action beginSavepoint :: SQLiteEnv -> SavepointName -> IO () beginSavepoint db name = diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 99e34433ae..c58655dba9 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -30,7 +30,7 @@ module Chainweb.Pact.PactService ( initialPayloadState -- , execNewBlock -- , execContinueBlock - , execValidateBlock + , syncToFork -- , execTransactions -- , execLocal , execLookupPactTxs @@ -54,7 +54,7 @@ import Control.Monad.Reader import Control.Monad.State.Strict import Data.Either -import Data.Foldable (toList) +import Data.Foldable (toList, traverse_) import Data.IORef import qualified Data.HashMap.Strict as HM import Data.LogMessage @@ -174,6 +174,7 @@ withPactService http ver cid memPoolAccess chainwebLogger txFailuresCounter pdb miningPayloadVar <- newEmptyTMVarIO ChainwebPactDb.initSchema sqlenv candidatePdb <- MapTable.emptyTable + let !pse = PactServiceEnv { _psVersion = ver , _psChainId = cid @@ -193,38 +194,43 @@ withPactService http ver cid memPoolAccess chainwebLogger txFailuresCounter pdb , _psMiningPayloadVar = miningPayloadVar } - runPactServiceM pse $ do - -- TODO: PP - -- when (_pactFullHistoryRequired config) $ do - -- mEarliestBlock <- Checkpointer.getEarliestBlock - -- case mEarliestBlock of - -- Nothing -> do - -- pure () - -- Just (earliestBlockHeight, _) -> do - -- let gHeight = genesisHeight ver cid - -- when (gHeight /= earliestBlockHeight) $ do - -- let msg = J.object - -- [ "details" J..= J.object - -- [ "earliest-block-height" J..= J.number (fromIntegral earliestBlockHeight) - -- , "genesis-height" J..= J.number (fromIntegral gHeight) - -- ] - -- , "message" J..= J.text "Your node has been configured\ - -- \ to require the full Pact history; however, the full\ - -- \ history is not available. Perhaps you have compacted\ - -- \ your Pact state?" - -- ] - -- logError_ chainwebLogger (J.encodeText msg) - -- throwM FullHistoryRequired - -- { _earliestBlockHeight = earliestBlockHeight - -- , _genesisHeight = gHeight - -- } - -- If the latest header that is stored in the checkpointer was on an - -- orphaned fork, there is no way to recover it in the call of - -- 'initalPayloadState.readContracts'. We therefore rewind to the latest - -- avaliable header in the block header database. - -- - -- Checkpointer.exitOnRewindLimitExceeded $ initializeLatestBlock (_pactUnlimitedInitialRewind config) - act + let run = runPactServiceM pse $ do + -- TODO: PP + -- when (_pactFullHistoryRequired config) $ do + -- mEarliestBlock <- Checkpointer.getEarliestBlock + -- case mEarliestBlock of + -- Nothing -> do + -- pure () + -- Just (earliestBlockHeight, _) -> do + -- let gHeight = genesisHeight ver cid + -- when (gHeight /= earliestBlockHeight) $ do + -- let msg = J.object + -- [ "details" J..= J.object + -- [ "earliest-block-height" J..= J.number (fromIntegral earliestBlockHeight) + -- , "genesis-height" J..= J.number (fromIntegral gHeight) + -- ] + -- , "message" J..= J.text "Your node has been configured\ + -- \ to require the full Pact history; however, the full\ + -- \ history is not available. Perhaps you have compacted\ + -- \ your Pact state?" + -- ] + -- logError_ chainwebLogger (J.encodeText msg) + -- throwM FullHistoryRequired + -- { _earliestBlockHeight = earliestBlockHeight + -- , _genesisHeight = gHeight + -- } + -- If the latest header that is stored in the checkpointer was on an + -- orphaned fork, there is no way to recover it in the call of + -- 'initalPayloadState.readContracts'. We therefore rewind to the latest + -- avaliable header in the block header database. + -- + -- Checkpointer.exitOnRewindLimitExceeded $ initializeLatestBlock (_pactUnlimitedInitialRewind config) + act + let cancelRefresher = do + refresherThread <- fmap fst <$> atomically (tryReadTMVar (_psMiningPayloadVar pse)) + traverse_ cancel refresherThread + + run `finally` cancelRefresher where pactServiceLogger = setComponent "pact" chainwebLogger checkpointerLogger = addLabel ("sub-component", "checkpointer") pactServiceLogger @@ -263,126 +269,36 @@ runGenesisIfNeeded v cid = do } } let targetSyncState = genesisConsensusState v cid - actualSyncState <- execValidateBlock mempty Nothing + actualSyncState <- syncToFork mempty Nothing (ForkInfo [genesisEvaluationCtx] payloadHash targetSyncState Nothing) when (targetSyncState /= actualSyncState) $ error "failed to run genesis block" --- | Lookup a block header. --- --- The block header is expected to be either in the block header database or to --- be the the currently stored '_psParentHeader'. The latter addresses the case --- when a block has already been validate with 'execValidateBlock' but isn't (yet) --- available in the block header database. If that's the case two things can --- happen: --- --- 1. the header becomes available before the next 'execValidateBlock' call, or --- 2. the header gets orphaned and the next 'execValidateBlock' call would cause --- a rewind to an ancestor, which is available in the db. --- --- lookupBlockHeader :: BlockHash -> Text -> PactServiceM logger tbl BlockHeader --- lookupBlockHeader bhash ctx = do --- bhdb <- view psBlockHeaderDb --- liftIO $! lookupM bhdb bhash `catchAllSynchronous` \e -> --- throwM $ BlockHeaderLookupFailure $ --- "failed lookup of parent header in " <> ctx <> ": " <> sshow e - --- execNewBlock --- :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) --- => MemPoolAccess --- -> NewBlockFill --- -> ParentHeader --- -> PactServiceM logger tbl (Historical (ForSomePactVersion BlockInProgress)) --- execNewBlock mpAccess fill newBlockParent = pactLabel "execNewBlock" $ do --- miner <- view psMiner >>= \case --- Nothing -> internalError "Chainweb.Pact.PactService.execNewBlock: Mining is disabled. Please provide a valid miner in the pact service configuration" --- Just x -> return x --- let pHeight = view blockHeight $ _parentHeader newBlockParent --- let pHash = view blockHash $ _parentHeader newBlockParent --- logInfoPact $ "(parent height = " <> sshow pHeight <> ")" --- <> " (parent hash = " <> sshow pHash <> ")" --- blockGasLimit <- view psNewBlockGasLimit --- v <- view chainwebVersion --- cid <- view chainId --- Checkpointer.readFrom (Just newBlockParent) $ --- -- TODO: after the Pact 5 fork is complete, the Pact 4 case below will --- -- be unnecessary; the genesis blocks are already handled by 'execNewGenesisBlock'. --- SomeBlockM $ Pair --- (do --- blockDbEnv <- view psBlockDbEnv --- initCache <- initModuleCacheForBlock --- coinbaseOutput <- Pact4.runCoinbase --- miner --- (Pact4.EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled True) --- initCache --- let pactDb = Pact4._cpPactDbEnv blockDbEnv --- finalBlockState <- fmap Pact4._benvBlockState --- $ liftIO --- $ readMVar --- $ pdPactDbVar --- $ pactDb --- let blockInProgress = BlockInProgress --- { _blockInProgressModuleCache = Pact4ModuleCache initCache --- -- ^ we do not use the module cache populated by coinbase in --- -- subsequent transactions --- , _blockInProgressHandle = BlockHandle (Pact4._bsTxId finalBlockState) (Pact4._bsPendingBlock finalBlockState) --- , _blockInProgressParentHeader = Just newBlockParent --- , _blockInProgressRemainingGasLimit = blockGasLimit --- , _blockInProgressTransactions = Transactions --- { _transactionCoinbase = coinbaseOutput --- , _transactionPairs = mempty --- } --- , _blockInProgressMiner = miner --- , _blockInProgressPactVersion = Pact4T --- , _blockInProgressChainwebVersion = v --- , _blockInProgressChainId = cid --- } --- case fill of --- NewBlockFill -> ForPact4 <$> Pact4.continueBlock mpAccess blockInProgress --- NewBlockEmpty -> return (ForPact4 blockInProgress) --- ) - --- (do --- coinbaseOutput <- Pact.runCoinbase miner >>= \case --- Left coinbaseError -> internalError $ "Error during coinbase: " <> sshow coinbaseError --- Right coinbaseOutput -> --- -- pretend that coinbase can throw an error, when we know it can't. --- -- perhaps we can make the Transactions express this, may not be worth it. --- return $ coinbaseOutput & Pact.crResult . Pact._PactResultErr %~ absurd --- hndl <- use Pact.pbBlockHandle --- let blockInProgress = BlockInProgress --- { _blockInProgressModuleCache = Pact5NoModuleCache --- , _blockInProgressHandle = hndl --- , _blockInProgressParentHeader = Just newBlockParent --- , _blockInProgressRemainingGasLimit = blockGasLimit --- , _blockInProgressTransactions = Transactions --- { _transactionCoinbase = coinbaseOutput --- , _transactionPairs = mempty --- } --- , _blockInProgressMiner = miner --- , _blockInProgressPactVersion = Pact5T --- , _blockInProgressChainwebVersion = v --- , _blockInProgressChainId = cid --- } --- case fill of --- NewBlockFill -> ForPact5 <$> Pact.continueBlock mpAccess blockInProgress --- NewBlockEmpty -> return (ForPact5 blockInProgress) --- ) - --- execContinueBlock --- :: forall logger tbl pv. (Logger logger, CanReadablePayloadCas tbl) --- => MemPoolAccess --- -> BlockInProgress pv --- -> PactServiceM logger tbl (Historical (BlockInProgress pv)) --- execContinueBlock mpAccess blockInProgress = pactLabel "execNewBlock" $ do --- Checkpointer.readFrom newBlockParent $ --- case _blockInProgressPactVersion blockInProgress of --- -- TODO: after the Pact 5 fork is complete, the Pact 4 case below will --- -- be unnecessary; the genesis blocks are already handled by 'execNewGenesisBlock'. --- Pact4T -> SomeBlockM $ Pair (Pact4.continueBlock mpAccess blockInProgress) (error "pact5") --- Pact5T -> SomeBlockM $ Pair (error "pact4") (Pact.continueBlock mpAccess blockInProgress) --- where --- newBlockParent = _blockInProgressParentHeader blockInProgress +makeEmptyBlock + :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) + => PactBlockM logger tbl BlockInProgress +makeEmptyBlock = do + miner <- view (psServiceEnv . psMiner) >>= \case + Nothing -> error "Chainweb.Pact.PactService.execNewBlock: Mining is disabled. Please provide a valid miner in the pact service configuration" + Just x -> return x + blockGasLimit <- view (psServiceEnv . psNewBlockGasLimit) + coinbaseOutput <- Pact.runCoinbase miner >>= \case + Left coinbaseError -> error $ "Error during coinbase: " <> sshow coinbaseError + Right coinbaseOutput -> + -- pretend that coinbase can throw an error, when we know it can't. + -- perhaps we can make the Transactions express this, may not be worth it. + return $ coinbaseOutput & Pact.crResult . Pact._PactResultErr %~ absurd + hndl <- use pbBlockHandle + blockCtx <- view psBlockCtx + return BlockInProgress + { _blockInProgressHandle = hndl + , _blockInProgressBlockCtx = blockCtx + , _blockInProgressRemainingGasLimit = blockGasLimit + , _blockInProgressTransactions = Transactions + { _transactionCoinbase = coinbaseOutput + , _transactionPairs = mempty + } + } -- -- | only for use in generating genesis blocks in tools. -- -- @@ -448,9 +364,9 @@ runGenesisIfNeeded v cid = do -- Historical block -> return block execReadOnlyReplay - :: forall logger tbl p + :: forall logger tbl . (Logger logger, CanReadablePayloadCas tbl) - => [EvaluationCtx p] + => [EvaluationCtx BlockPayloadHash] -> PactServiceM logger tbl () execReadOnlyReplay blocks = undefined -- pactLabel "execReadOnlyReplay" $ do -- ParentHeader cur <- Checkpointer.findLatestValidBlockHeader @@ -707,34 +623,25 @@ execReadOnlyReplay blocks = undefined -- pactLabel "execReadOnlyReplay" $ do -- targetHash = view blockHash targetHeader -- failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" --- | Validate a mined block `(headerToValidate, payloadToValidate). --- Note: The BlockHeader here is the header of the block being validated. --- To do this, we atomically: --- - determine if the block is on a different fork from the checkpointer's --- current latest block, and execute all of the blocks on that fork if so, --- all the way to the parent of the block we're validating. --- - run the Pact transactions in the block. --- - commit the result to the database. --- -execValidateBlock +syncToFork :: forall tbl logger . (CanReadablePayloadCas tbl, Logger logger) => MemPoolAccess -> Maybe Hints -> ForkInfo -> PactServiceM logger tbl ConsensusState -execValidateBlock memPoolAccess hints forkInfo = do +syncToFork memPoolAccess hints forkInfo = do sql <- view psReadWriteSql pdb <- view psPdb - (rewoundTxs, validatedTxs, newConsensusState) <- withSavepoint sql ValidateBlockSavePoint $ do -- pactLabel "execValidateBlock" $ do + (rewoundTxs, validatedTxs, newConsensusState) <- withSavepoint sql ValidateBlockSavePoint $ do let findForkChain (tip:chain) = go (NEL.singleton tip) chain findForkChain [] = return Nothing go !acc (tip:chain) = do - -- note that if we see the "parent hash" in the checkpointer, - -- that means that the *child* has been evaluated, thus we do + -- note that if we see the eval ctx in the checkpointer, + -- that means that the block has been evaluated, thus we do -- not include `tip` in the resulting list. - known <- Checkpointer.lookupParentBlockRanked (Ranked (_evaluationCtxCurrentHeight tip) (_evaluationCtxParentHash tip)) + known <- Checkpointer.lookupBlockByEvalCtx tip if known then return $ Just acc -- if we don't know this block, remember it for later as we'll @@ -743,67 +650,93 @@ execValidateBlock memPoolAccess hints forkInfo = do go _acc [] = return Nothing pactConsensusState <- Checkpointer.getConsensusState - let atTarget = _syncStateRankedBlockHash (_consensusStateLatest pactConsensusState) == _forkInfoBaseRankedBlockHash forkInfo + let atTarget = + _syncStateBlockHash (_consensusStateLatest pactConsensusState) == + _latestBlockHash (forkInfo._forkInfoTargetState) -- check if some past block had the target as its parent; if so, that -- means we can rewind to it latestBlockRewindable <- Checkpointer.lookupParentBlockHash (Parent $ _syncStateBlockHash (_consensusStateLatest pactConsensusState)) if atTarget - then do - -- no work to do at all except set consensus state - -- TODO PP: disallow rewinding final? - logDebugPact $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState - Checkpointer.setConsensusState forkInfo._forkInfoTargetState - return (mempty, mempty, forkInfo._forkInfoTargetState) - else if latestBlockRewindable - then do - -- we just have to rewind and set the final + safe blocks - -- TODO PP: disallow rewinding final? - logDebugPact $ "pure rewind to " <> sshow forkInfo._forkInfoTargetState - rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) - Checkpointer.rewindTo (_syncStateRankedBlockHash (_consensusStateLatest pactConsensusState)) - Checkpointer.setConsensusState forkInfo._forkInfoTargetState - return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) - else do - logDebugPact $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState - findForkChain forkInfo._forkInfoTrace >>= \case - Nothing -> do - logErrorPact $ "impossible to move to " <> sshow forkInfo._forkInfoTargetState - -- error: we have no way to get to the target block. just report - -- our current state and do nothing else. - return (mempty, mempty, pactConsensusState) - Just forkChainBottomToTop -> do - rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) - -- the happy case: we can find a way to get to the target block - -- look up all of the payloads to see if we've run them before - -- even then we still have to run them, because they aren't in the checkpointer - knownPayloads <- liftIO $ - tableLookupBatch' pdb (each . _2) ((\e -> (e, _evaluationCtxRankedPayloadHash e)) <$> forkChainBottomToTop) - - logDebugPact $ "unknown blocks in context: " <> sshow (length $ NEL.filter (isNothing . snd) knownPayloads) - - runnableBlocks <- forM knownPayloads $ \(evalCtx, maybePayload) -> do - payload <- case maybePayload of - -- fetch payload if missing - Nothing -> getPayloadForContext hints evalCtx - Just payload -> return payload - let runBlock = Pact.execExistingBlock (_consensusPayloadHash <$> evalCtx) (CheckablePayload payload) - return - ( DList.singleton <$> runBlock - , _consensusPayloadHash <$> evalCtx - ) - - runExceptT (Checkpointer.restoreAndSave runnableBlocks) >>= \case - Left err -> do - logErrorPact $ "Error in execValidateBlock: " <> sshow err - return (mempty, mempty, pactConsensusState) - Right blockResults -> do - let validatedTxHashes = V.concatMap - (fmap pactRequestKeyToTransactionHash . view _3) - (V.fromList $ DList.toList blockResults) - Checkpointer.setConsensusState forkInfo._forkInfoTargetState - return (rewoundTxs, validatedTxHashes, forkInfo._forkInfoTargetState) + then do + -- no work to do at all except set consensus state + -- TODO PP: disallow rewinding final? + logDebugPact $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState + Checkpointer.setConsensusState forkInfo._forkInfoTargetState + return (mempty, mempty, forkInfo._forkInfoTargetState) + else if latestBlockRewindable + then do + -- we just have to rewind and set the final + safe blocks + -- TODO PP: disallow rewinding final? + logDebugPact $ "pure rewind to " <> sshow forkInfo._forkInfoTargetState + rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) + Checkpointer.rewindTo (_syncStateRankedBlockHash (_consensusStateLatest pactConsensusState)) + Checkpointer.setConsensusState forkInfo._forkInfoTargetState + return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) + else do + logDebugPact $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState + findForkChain forkInfo._forkInfoTrace >>= \case + Nothing -> do + logErrorPact $ "impossible to move to " <> sshow forkInfo._forkInfoTargetState + -- error: we have no way to get to the target block. just report + -- our current state and do nothing else. + return (mempty, mempty, pactConsensusState) + Just forkChainBottomToTop -> do + rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) + -- the happy case: we can find a way to get to the target block + -- look up all of the payloads to see if we've run them before + -- even then we still have to run them, because they aren't in the checkpointer + knownPayloads <- liftIO $ + tableLookupBatch' pdb (each . _2) ((\e -> (e, _evaluationCtxRankedPayloadHash e)) <$> forkChainBottomToTop) + + logDebugPact $ "unknown blocks in context: " <> sshow (length $ NEL.filter (isNothing . snd) knownPayloads) + + runnableBlocks <- forM knownPayloads $ \(evalCtx, maybePayload) -> do + payload <- case maybePayload of + -- fetch payload if missing + Nothing -> getPayloadForContext hints evalCtx + Just payload -> return payload + let runBlock = Pact.execExistingBlock (_consensusPayloadHash <$> evalCtx) (CheckablePayload payload) + return + ( DList.singleton <$> runBlock + , _consensusPayloadHash <$> evalCtx + ) + + runExceptT (Checkpointer.restoreAndSave runnableBlocks) >>= \case + Left err -> do + logErrorPact $ "Error in execValidateBlock: " <> sshow err + return (mempty, mempty, pactConsensusState) + Right blockResults -> do + let validatedTxHashes = V.concatMap + (fmap pactRequestKeyToTransactionHash . view _3) + (V.fromList $ DList.toList blockResults) + Checkpointer.setConsensusState forkInfo._forkInfoTargetState + return (rewoundTxs, validatedTxHashes, forkInfo._forkInfoTargetState) liftIO $ mpaProcessFork memPoolAccess (rewoundTxs, validatedTxs) + case forkInfo._forkInfoNewBlockCtx of + Just newBlockCtx + | _syncStateBlockHash (_consensusStateLatest newConsensusState) == + _latestBlockHash (forkInfo._forkInfoTargetState) -> do + -- if we're at the target block we were sent, and we were + -- told to start mining, we produce an empty block + -- immediately. then we set up a separate thread + -- to add new transactions to the block. + emptyBlock <- Checkpointer.readFromLatest newBlockCtx makeEmptyBlock + payloadVar <- view psMiningPayloadVar + + -- cancel payload refresher thread + liftIO $ + atomically (fmap fst <$> tryTakeTMVar payloadVar) + >>= traverse_ cancel + + e <- ask + + refresherThread <- liftIO $ async (runPactServiceM e refreshPayloads) + + liftIO $ + atomically $ writeTMVar payloadVar (refresherThread, emptyBlock) + + _ -> return () return newConsensusState where -- remember to call this *before* executing the actual rewind, @@ -820,6 +753,9 @@ execValidateBlock memPoolAccess hints forkInfo = do (fmap (fromRight (error "invalid payload in database")) . runExceptT . pact5TransactionsFromPayload) rewoundPayloads +refreshPayloads :: PactServiceM logger tbl () +refreshPayloads = undefined + -- -- The parent block header must be available in the block header database. -- parentOfHeaderToValidate <- getTarget @@ -1017,13 +953,15 @@ execLookupPactTxs -> Vector SB.ShortByteString -> PactServiceM logger tbl (Historical (HM.HashMap SB.ShortByteString (T2 BlockHeight BlockHash))) execLookupPactTxs confDepth txs = do -- pactLabel "execLookupPactTxs" $ do - if V.null txs then return (Historical mempty) else go + if V.null txs + then return (Historical mempty) + else do + go =<< liftIO Checkpointer.fakeNewBlockCtx where - go = Checkpointer.readFromNthParent (maybe 0 (fromIntegral . _confirmationDepth) confDepth) $ - (do - dbenv <- view psBlockDbEnv - fmap (HM.mapKeys coerce) $ liftIO $ Pact.lookupPactTransactions dbenv (coerce txs) - ) + depth = maybe 0 (fromIntegral . _confirmationDepth) confDepth + go ctx = Checkpointer.readFromNthParent ctx depth $ do + dbenv <- view psBlockDbEnv + fmap (HM.mapKeys coerce) $ liftIO $ Pact.lookupPactTransactions dbenv (coerce txs) -- pactLabel :: (Logger logger) => Text -> PactServiceM logger tbl x -> PactServiceM logger tbl x -- pactLabel lbl x = localLabelPact ("pact-request", lbl) x diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index b7abcef7df..d08e48625a 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -32,7 +32,8 @@ -- Checkpointer interaction for Pact service. -- module Chainweb.Pact.PactService.Checkpointer - ( readFromLatest + ( fakeNewBlockCtx + , readFromLatest , readFromNthParent , readFrom , restoreAndSave @@ -44,7 +45,7 @@ module Chainweb.Pact.PactService.Checkpointer , PactBlockM(..) , getEarliestBlock -- , lookupBlock - , lookupParentBlockRanked + , lookupBlockByEvalCtx , lookupParentBlockHash , lookupBlockWithHeight , getPayloadsAfter @@ -54,7 +55,6 @@ module Chainweb.Pact.PactService.Checkpointer import Control.Lens hiding ((:>), (:<)) import Control.Monad -import Control.Monad.Catch import Control.Monad.Except import Control.Monad.Reader import Data.List.NonEmpty (NonEmpty ((:|))) @@ -86,6 +86,18 @@ import Chainweb.Utils hiding (check) import Chainweb.Version import Chainweb.Version.Guards (pact5) +-- readFrom demands to know this context in case it's mining a block. +-- But it's not really used by /local, or polling, so we fabricate it +-- for those users. +fakeNewBlockCtx :: IO NewBlockCtx +fakeNewBlockCtx = do + fakeCreationTime <- Parent . BlockCreationTime <$> getCurrentTimeIntegral + return NewBlockCtx + -- fake + { _newBlockCtxMinerReward = MinerReward 1848 + , _newBlockCtxParentCreationTime = fakeCreationTime + } + -- read-only rewind to the latest block. -- note: because there is a race between getting the latest header -- and doing the rewind, there's a chance that the latest header @@ -94,9 +106,10 @@ import Chainweb.Version.Guards (pact5) -- note: this function will never rewind before genesis. readFromLatest :: Logger logger - => PactBlockM logger tbl a + => NewBlockCtx + -> PactBlockM logger tbl a -> PactServiceM logger tbl a -readFromLatest doRead = readFromNthParent 0 doRead >>= \case +readFromLatest newBlockCtx doRead = readFromNthParent newBlockCtx 0 doRead >>= \case NoHistory -> error "readFromLatest: failed to grab the latest header, this is a bug in chainweb" Historical a -> return a @@ -105,10 +118,11 @@ readFromLatest doRead = readFromNthParent 0 doRead >>= \case readFromNthParent :: forall logger tbl a . Logger logger - => Word + => NewBlockCtx + -> Word -> PactBlockM logger tbl a -> PactServiceM logger tbl (Historical a) -readFromNthParent n doRead = do +readFromNthParent newBlockCtx n doRead = do sql <- view psReadWriteSql v <- view chainwebVersion cid <- view chainId @@ -123,28 +137,27 @@ readFromNthParent n doRead = do -- this case for shallow nodes without enough history Nothing -> return NoHistory Just nthBlock -> - readFrom (parentBlockHeight v cid nthBlock) doRead + readFrom newBlockCtx (parentBlockHeight v cid nthBlock) doRead -- read-only rewind to a target block. -- if that target block is missing, return Nothing. readFrom :: Logger logger - => Parent RankedBlockHash + => NewBlockCtx + -- ^ you can fake this if you're not making a new block + -> Parent RankedBlockHash -> PactBlockM logger tbl a -> PactServiceM logger tbl (Historical a) -readFrom parent doRead = do +readFrom newBlockCtx parent doRead = do sql <- view psReadWriteSql logger <- view psLogger v <- view chainwebVersion cid <- view chainId - fakeCreationTime <- liftIO $ Parent . BlockCreationTime <$> getCurrentTimeIntegral let blockCtx = BlockCtx - -- fake, we can't access this field without a block header db - { _bctxParentCreationTime = fakeCreationTime + { _bctxParentCreationTime = _newBlockCtxParentCreationTime newBlockCtx , _bctxParentHash = _rankedBlockHashHash <$> parent , _bctxParentHeight = _rankedBlockHashHeight <$> parent - -- fake, we can't access this field without a block header db - , _bctxMinerReward = MinerReward 1848 + , _bctxMinerReward = _newBlockCtxMinerReward newBlockCtx , _bctxChainwebVersion = v , _bctxChainId = cid } @@ -180,14 +193,8 @@ rewindTo ancestor = do sql <- view psReadWriteSql v <- view chainwebVersion cid <- view chainId - liftIO $ fmap fst $ generalBracket - (beginSavepoint sql RewindSavePoint) - (\_ -> \case - ExitCaseSuccess {} -> commitSavepoint sql RewindSavePoint - _ -> abortSavepoint sql RewindSavePoint - ) $ \_ -> do - _ <- PactDb.rewindDbTo v cid sql ancestor - return () + liftIO $ withSavepoint sql RewindSavePoint $ + void $ PactDb.rewindDbTo v cid sql ancestor getBlockCtx :: EvaluationCtx p -> PactServiceM logger tbl BlockCtx getBlockCtx ec = do @@ -214,19 +221,14 @@ restoreAndSave restoreAndSave blocks@((_, forkPoint) :| _) = do sql <- view psReadWriteSql e <- ask - fmap fst $ generalBracket - (liftIO $ beginSavepoint sql RestoreAndSaveSavePoint) - (\_ -> liftIO . \case - ExitCaseSuccess {} -> commitSavepoint sql RestoreAndSaveSavePoint - _ -> abortSavepoint sql RestoreAndSaveSavePoint - ) $ \_ -> do - -- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above. - txid <- liftIO $ PactDb.rewindDbTo - (_chainwebVersion e) - (_chainId e) - sql - (unwrapParent $ _evaluationCtxRankedParentHash forkPoint) - mapExceptT liftIO $ extend e (mempty, txid) (NEL.toList blocks) + withSavepoint sql RestoreAndSaveSavePoint $ do + -- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above. + txid <- liftIO $ PactDb.rewindDbTo + (_chainwebVersion e) + (_chainId e) + sql + (unwrapParent $ _evaluationCtxRankedParentHash forkPoint) + mapExceptT liftIO $ extend e (mempty, txid) (NEL.toList blocks) where extend e (m, startTxId) = \case (blockAction, evalCtx) : subsequentBlocks -> let @@ -403,10 +405,10 @@ lookupBlockWithHeight bh = do sql <- view psReadWriteSql liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockWithHeight sql bh -lookupParentBlockRanked :: Ranked (Parent BlockHash) -> PactServiceM logger tbl Bool -lookupParentBlockRanked rpbh = do +lookupBlockByEvalCtx :: EvaluationCtx p -> PactServiceM logger tbl Bool +lookupBlockByEvalCtx rpbh = do sql <- view psReadWriteSql - liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupParentBlockRanked sql rpbh + liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockByEvalCtx sql rpbh lookupParentBlockHash :: Parent BlockHash -> PactServiceM logger tbl Bool lookupParentBlockHash pbh = do diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index 486f70f3e4..0b6a5b345f 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -16,7 +16,7 @@ module Chainweb.Pact.PactService.ExecBlock ( runCoinbase - -- , continueBlock + , continueBlock , execExistingBlock , validateParsedChainwebTx , pact5TransactionsFromPayload @@ -130,73 +130,80 @@ runCoinbase miner = do continueBlock :: forall logger tbl . (Logger logger, CanReadablePayloadCas tbl) - => MemPoolAccess + => ChainwebPactDb + -> MemPoolAccess -> BlockInProgress - -> PactBlockM logger tbl BlockInProgress -continueBlock mpAccess blockInProgress = do - pbBlockHandle .= _blockInProgressHandle blockInProgress - -- update the mempool, ensuring that we reintroduce any transactions that - -- were removed due to being completed in a block on a different fork. - case maybeBlockParentHeader of - Just blockParentHeader -> do - liftPactServiceM $ - logInfoPact $ T.unwords - [ "(parent height = " <> sshow (view blockHeight blockParentHeader) <> ")" - , "(parent hash = " <> sshow (view blockHash blockParentHeader) <> ")" - ] - Nothing -> - liftPactServiceM $ logInfoPact "Continuing genesis block" - - blockGasLimit <- view (psServiceEnv . psNewBlockGasLimit) - mTxTimeLimit <- view (psServiceEnv . psNewPayloadTxTimeLimit) - let txTimeHeadroomFactor :: Double - txTimeHeadroomFactor = 5 - let txTimeLimit :: Micros - -- 2.5 us per unit gas - txTimeLimit = fromMaybe (round $ 2.5 * txTimeHeadroomFactor * fromIntegral blockGasLimit) mTxTimeLimit - liftPactServiceM $ do - logDebugPact $ T.unwords - [ "Block gas limit:" - , sshow blockGasLimit <> "," - , "Transaction time limit:" - , sshow txTimeLimit - ] - - let startTxs = _transactionPairs (_blockInProgressTransactions blockInProgress) - let startTxsRequestKeys = - foldMap' (S.singleton . pactRequestKeyToTransactionHash . view Pact.crReqKey . snd) startTxs - let initState = BlockFill - { _bfTxHashes = startTxsRequestKeys - , _bfGasLimit = _blockInProgressRemainingGasLimit blockInProgress - , _bfCount = 0 - } - - let fetchLimit = fromIntegral $ blockGasLimit `div` 1000 - - (BlockFill { _bfGasLimit = finalGasLimit }, valids, invalids) <- - refill fetchLimit txTimeLimit initState - - finalBlockHandle <- use pbBlockHandle - - liftIO $ mpaBadlistTx mpAccess - (V.fromList $ fmap pactRequestKeyToTransactionHash $ concat invalids) - - liftPactServiceM $ logDebugPact $ "Order of completed transactions: " <> sshow (map (Pact.unRequestKey . Pact._crReqKey . snd) $ concat $ reverse valids) - let !blockInProgress' = blockInProgress - & blockInProgressHandle .~ - finalBlockHandle - & blockInProgressTransactions . transactionPairs .~ - startTxs <> V.fromList (concat valids) - & blockInProgressRemainingGasLimit .~ - finalGasLimit - - liftPactServiceM $ logDebugPact $ "Final block transaction order: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . snd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) + -> PactServiceM logger tbl BlockInProgress +continueBlock dbEnv mpAccess blockInProgress = do + miner <- maybe (error "no miner but tried continuing block") return =<< view psMiner + let runBlock = runPactBlockM + (_blockInProgressBlockCtx blockInProgress) + dbEnv + (_blockInProgressHandle blockInProgress) + (blockInProgress', _newHandle) <- runBlock $ do + -- update the mempool, ensuring that we reintroduce any transactions that + -- were removed due to being completed in a block on a different fork. + blockCtx <- view psBlockCtx + case _bctxIsGenesis blockCtx of + True -> do + liftPactServiceM $ + logInfoPact $ T.unwords + [ "(parent height = " <> sshow (_bctxParentHash blockCtx) <> ")" + , "(parent hash = " <> sshow (_bctxParentHeight blockCtx) <> ")" + ] + False -> + liftPactServiceM $ logInfoPact "Continuing genesis block" + + blockGasLimit <- view (psServiceEnv . psNewBlockGasLimit) + mTxTimeLimit <- view (psServiceEnv . psNewPayloadTxTimeLimit) + let txTimeHeadroomFactor :: Double + txTimeHeadroomFactor = 5 + let txTimeLimit :: Micros + -- 2.5 us per unit gas + txTimeLimit = fromMaybe (round $ 2.5 * txTimeHeadroomFactor * fromIntegral (view (Pact._GasLimit . to Pact._gas) blockGasLimit)) mTxTimeLimit + liftPactServiceM $ do + logDebugPact $ T.unwords + [ "Block gas limit:" + , sshow blockGasLimit <> "," + , "Transaction time limit:" + , sshow txTimeLimit + ] + + let startTxs = _transactionPairs (_blockInProgressTransactions blockInProgress) + let startTxsRequestKeys = + foldMap' (S.singleton . pactRequestKeyToTransactionHash . view Pact.crReqKey . snd) startTxs + let initState = BlockFill + { _bfTxHashes = startTxsRequestKeys + , _bfGasLimit = _blockInProgressRemainingGasLimit blockInProgress + , _bfCount = 0 + } + + let fetchLimit = fromIntegral $ (view (Pact._GasLimit . to Pact._gas) blockGasLimit) `div` 1000 + + (BlockFill { _bfGasLimit = finalGasLimit }, valids, invalids) <- + refill miner fetchLimit txTimeLimit initState + + finalBlockHandle <- use pbBlockHandle + liftIO $ mpaBadlistTx mpAccess + (V.fromList $ fmap pactRequestKeyToTransactionHash $ concat invalids) + + liftPactServiceM $ logDebugPact $ "Order of completed transactions: " <> sshow (map (Pact.unRequestKey . Pact._crReqKey . snd) $ concat $ reverse valids) + let !blockInProgress' = blockInProgress + & blockInProgressHandle .~ + finalBlockHandle + & blockInProgressTransactions . transactionPairs .~ + startTxs <> V.fromList (concat valids) + & blockInProgressRemainingGasLimit .~ + finalGasLimit + + liftPactServiceM $ logDebugPact $ "Final block transaction order: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . snd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) + + return blockInProgress' return blockInProgress' where - maybeBlockParentHeader = unwrapParent <$> _blockInProgressParentHeader blockInProgress - refill fetchLimit txTimeLimit blockFillState = over _2 reverse <$> go [] [] blockFillState + refill miner fetchLimit txTimeLimit blockFillState = over _2 reverse <$> go [] [] blockFillState where go :: [CompletedTransactions] @@ -208,9 +215,7 @@ continueBlock mpAccess blockInProgress = do | prevFillCount > fetchLimit = liftPactServiceM $ do logInfoPact $ "Refill fetch limit exceeded (" <> sshow fetchLimit <> ")" pure stop - | prevRemainingGas < 0 = - throwM $ MempoolFillFailure $ "Internal error, negative gas limit: " <> sshow prevBlockFillState - | prevRemainingGas == 0 = + | prevRemainingGas == Pact.GasLimit (Pact.Gas 0) = pure stop | otherwise = do newTxs <- getBlockTxs prevBlockFillState @@ -221,7 +226,7 @@ continueBlock mpAccess blockInProgress = do pure stop else do (newCompletedTransactions, newInvalidTransactions, newBlockGasLimit, timedOut) <- - execNewTransactions (_blockInProgressMiner blockInProgress) prevRemainingGas txTimeLimit newTxs + execNewTransactions prevRemainingGas txTimeLimit newTxs liftPactServiceM $ do logDebugPact $ "Refill: included request keys: " <> sshow @[Hash] (fmap (Pact.unRequestKey . Pact._crReqKey . snd) newCompletedTransactions) @@ -252,24 +257,22 @@ continueBlock mpAccess blockInProgress = do stop = (prevBlockFillState, completedTransactions, invalidTransactions) execNewTransactions - :: Miner - -> Pact4.GasLimit + :: P.GasLimit -> Micros -> Vector Pact.Transaction - -> PactBlockM logger tbl (CompletedTransactions, InvalidTransactions, Pact4.GasLimit, Bool) - execNewTransactions miner remainingGas timeLimit txs = do + -> PactBlockM logger tbl (CompletedTransactions, InvalidTransactions, P.GasLimit, Bool) + execNewTransactions remainingGas timeLimit txs = do env <- ask startBlockHandle <- use pbBlockHandle - let p5RemainingGas = Pact.GasLimit $ Pact.Gas $ fromIntegral remainingGas + blockCtx <- view psBlockCtx logger' <- view (psServiceEnv . psLogger) - isGenesis <- view psIsGenesis ((txResults, timedOut), (finalBlockHandle, Identity finalRemainingGas)) <- - liftIO $ flip runStateT (startBlockHandle, Identity p5RemainingGas) $ foldr + liftIO $ flip runStateT (startBlockHandle, Identity remainingGas) $ foldr (\(txIdxInBlock, tx) rest -> StateT $ \s -> do let logger = addLabel ("transactionHash", sshow (Pact._cmdHash tx)) logger' let env' = env & psServiceEnv . psLogger .~ logger let timeoutFunc runTx = - if isGenesis + if _bctxIsGenesis blockCtx then do logFunctionText logger Info $ "Running genesis command" fmap Just runTx @@ -289,37 +292,31 @@ continueBlock mpAccess blockInProgress = do return (([Left (Pact._cmdHash tx)], True), s) Just (Left err) -> do logFunctionText logger Debug $ - "applyCmd failed to buy gas: " <> prettyPact5GasPurchaseFailure err + -- TODO PP: prettify + "applyCmd failed to buy gas: " <> sshow err ((as, timedOut), s') <- runStateT rest s return ((Left (Pact._cmdHash tx):as, timedOut), s') Just (Right (a, s')) -> do logFunctionText logger Debug "applyCmd buy gas succeeded" ((as, timedOut), s'') <- runStateT rest s' - return ((Right (tx, a):as, timedOut), s'') + let !txBytes = commandToBytes tx + return ((Right (txBytes, a):as, timedOut), s'') ) (return ([], False)) (zip [0..] (V.toList txs)) pbBlockHandle .= finalBlockHandle let (invalidTxHashes, completedTxs) = partitionEithers txResults - let p4FinalRemainingGas = fromIntegral @Pact.SatWord @Pact4.GasLimit $ finalRemainingGas ^. Pact._GasLimit . to Pact._gas - return (completedTxs, Pact.RequestKey <$> invalidTxHashes, p4FinalRemainingGas, timedOut) + return (completedTxs, Pact.RequestKey <$> invalidTxHashes, finalRemainingGas, timedOut) getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector Pact.Transaction) getBlockTxs blockFillState = do liftPactServiceM $ logDebugPact "Refill: fetching transactions" - v <- view chainwebVersion - cid <- view chainId + blockCtx <- view psBlockCtx logger <- view (psServiceEnv . psLogger) - dbEnv <- view psBlockDbEnv - let (pHash, pHeight, parentTime) = blockInProgressParent blockInProgress - isGenesis <- view psIsGenesis - let validate bhi _bha txs = do - forM txs $ - runExceptT . validateParsedChainwebTx logger v cid dbEnv (_blockInProgressHandle blockInProgress) (Parent $ parentTime) bhi isGenesis - liftIO $ mpaGetBlock mpAccess blockFillState validate - (succ pHeight) - pHash - parentTime + let validate _bha txs = do + forM txs $ \tx -> + fmap (tx <$) $ runExceptT (validateParsedChainwebTx logger dbEnv blockCtx tx) + liftIO $ mpaGetBlock mpAccess blockFillState validate (evaluationCtxOfBlockCtx blockCtx) type CompletedTransactions = [(Chainweb.Transaction, Pact.OffChainCommandResult)] type InvalidTransactions = [Pact.RequestKey] @@ -549,18 +546,11 @@ execExistingBlock evaluationCtx payload = do -- TODO: Pact5: ACTUALLY log gas _gasLogger <- view (psServiceEnv . psGasLogger) v <- view chainwebVersion - cid <- view chainId db <- view psBlockDbEnv - blockHandlePreCoinbase <- use pbBlockHandle let - isGenesis = _bctxIsGenesis blockCtx - txValidationTime = _bctxParentCreationTime blockCtx errors <- liftIO $ flip foldMap txs $ \tx -> do errorOrSuccess <- runExceptT $ - validateParsedChainwebTx logger v cid db blockHandlePreCoinbase txValidationTime - (_bctxCurrentBlockHeight blockCtx) - isGenesis - tx + validateParsedChainwebTx logger db blockCtx tx case errorOrSuccess of Right () -> return [] Left err -> return [(Pact.RequestKey (Pact._cmdHash tx), err)] diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 5bec2578c2..1db3936032 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -36,6 +36,14 @@ module Chainweb.Pact.Types , psMiningPayloadVar , psNewBlockGasLimit + , BlockCtx(..) + , blockCtxOfEvaluationCtx + , evaluationCtxOfBlockCtx + , guardCtx + , _bctxParentRankedBlockHash + , _bctxIsGenesis + , _bctxCurrentBlockHeight + , PactServiceConfig(..) , PactServiceM(..) , runPactServiceM @@ -58,16 +66,10 @@ module Chainweb.Pact.Types , blockInProgressRemainingGasLimit , blockInProgressTransactions , toPayloadWithOutputs + , commandToBytes , MemPoolAccess(..) - , BlockCtx(..) - , blockCtxOfEvaluationCtx - , _bctxParentRankedBlockHash - , _bctxIsGenesis - , guardCtx - , _bctxCurrentBlockHeight - , GasSupply(..) , RewindLimit(..) , defaultReorgLimit @@ -158,21 +160,15 @@ import Pact.JSON.Encode qualified as J import System.LogLevel import Utils.Logging.Trace -import Chainweb.BlockCreationTime -import Chainweb.BlockHash import Chainweb.BlockHeader -import Chainweb.BlockHeight import Chainweb.BlockPayloadHash -import Chainweb.ChainId qualified as Chainweb import Chainweb.Counter import Chainweb.Logger import Chainweb.Mempool.Mempool import Chainweb.Miner.Pact (Miner, toMinerData) -import Chainweb.MinerReward import Chainweb.Pact.Backend.ChainwebPactDb import Chainweb.Pact.Backend.Types import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Parent import Chainweb.Payload qualified as Chainweb import Chainweb.Payload.PayloadStore import Chainweb.PayloadProvider.P2P @@ -188,6 +184,12 @@ import Servant.API import Data.List.NonEmpty (NonEmpty) import Chainweb.PayloadProvider import Control.Concurrent.STM +import Chainweb.BlockHeight +import Chainweb.BlockHash +import Chainweb.Parent +import Chainweb.MinerReward +import Chainweb.BlockCreationTime +import Control.Concurrent.Async data Transactions t r = Transactions { _transactionPairs :: !(Vector (t, r)) @@ -342,6 +344,59 @@ type OnChainCommandResult = type OffChainCommandResult = Pact.CommandResult [Pact.TxLog ByteString] (Pact.PactError Pact.Info) +-- | Pair parent header with transaction metadata. +-- In cases where there is no transaction/Command, 'PublicMeta' +-- default value is used. +data BlockCtx = BlockCtx + { _bctxParentCreationTime :: !(Parent BlockCreationTime) + , _bctxParentHash :: !(Parent BlockHash) + , _bctxParentHeight :: !(Parent BlockHeight) + , _bctxChainId :: !ChainId + , _bctxChainwebVersion :: !ChainwebVersion + , _bctxMinerReward :: !MinerReward + } deriving (Eq, Show) + +blockCtxOfEvaluationCtx :: ChainwebVersion -> ChainId -> EvaluationCtx p -> BlockCtx +blockCtxOfEvaluationCtx v cid ec = BlockCtx + { _bctxParentCreationTime = _evaluationCtxParentCreationTime ec + , _bctxParentHash = _evaluationCtxParentHash ec + , _bctxParentHeight = _evaluationCtxParentHeight ec + , _bctxChainId = cid + , _bctxChainwebVersion = v + , _bctxMinerReward = _evaluationCtxMinerReward ec + } + +evaluationCtxOfBlockCtx :: BlockCtx -> EvaluationCtx () +evaluationCtxOfBlockCtx bctx = EvaluationCtx + { _evaluationCtxParentCreationTime = _bctxParentCreationTime bctx + , _evaluationCtxParentHeight = _bctxParentHeight bctx + , _evaluationCtxParentHash = _bctxParentHash bctx + , _evaluationCtxMinerReward = _bctxMinerReward bctx + , _evaluationCtxPayload = () + } + +guardCtx :: (ChainwebVersion -> ChainId -> BlockHeight -> a) -> BlockCtx -> a +guardCtx g txCtx = g (_chainwebVersion txCtx) (_chainId txCtx) (_bctxCurrentBlockHeight txCtx) + +instance HasChainId BlockCtx where + _chainId = _bctxChainId +instance HasChainwebVersion BlockCtx where + _chainwebVersion = _bctxChainwebVersion + +_bctxIsGenesis :: BlockCtx -> Bool +_bctxIsGenesis bc = isGenesisBlockHeader' (_chainwebVersion bc) (_chainId bc) (_bctxParentHash bc) + +_bctxParentRankedBlockHash :: BlockCtx -> Parent RankedBlockHash +_bctxParentRankedBlockHash bc = Parent RankedBlockHash + { _rankedBlockHashHash = unwrapParent $ _bctxParentHash bc + , _rankedBlockHashHeight = unwrapParent $ _bctxParentHeight bc + } + +_bctxCurrentBlockHeight :: BlockCtx -> BlockHeight +_bctxCurrentBlockHeight bc = + childBlockHeight (_chainwebVersion bc) (_chainId bc) (_bctxParentRankedBlockHash bc) + + -- | Externally-injected PactService properties. -- @@ -380,11 +435,10 @@ data PactServiceConfig = PactServiceConfig -- TODO: get rid of this shim, it's probably not necessary data MemPoolAccess = MemPoolAccess { mpaGetBlock - :: !(forall to. BlockFill + :: !(forall to + . BlockFill -> MempoolPreBlockCheck Pact.Transaction to - -> BlockHeight - -> BlockHash - -> BlockCreationTime + -> EvaluationCtx () -> IO (Vector to) ) , mpaProcessFork :: !((Vector Pact.Transaction, Vector TransactionHash) -> IO ()) @@ -432,20 +486,19 @@ data PactServiceEnv logger tbl = PactServiceEnv -- If unset, defaults to a reasonable value based on the transaction's gas limit. , _psMiner :: !(Maybe Miner) -- ^ Miner identity for use in newly mined blocks. - , _psMiningPayloadVar :: !(TMVar NewPayload) - -- ^ Latest mining payload produced. + , _psMiningPayloadVar :: !(TMVar (Async (), BlockInProgress)) + -- ^ Latest mining payload produced, and block continuation thread. , _psNewBlockGasLimit :: !Pact.GasLimit -- ^ Block gas limit in newly produced blocks. } -makeLenses ''PactServiceEnv instance HasChainwebVersion (PactServiceEnv logger c) where - chainwebVersion = psVersion - {-# INLINE chainwebVersion #-} + _chainwebVersion = _psVersion + {-# INLINE _chainwebVersion #-} instance HasChainId (PactServiceEnv logger c) where - chainId = psChainId - {-# INLINE chainId #-} + _chainId = _psChainId + {-# INLINE _chainId #-} -- | The top level monad of PactService, notably allowing access to a -- checkpointer and module init cache and some configuration parameters. @@ -538,46 +591,6 @@ tracePactBlockM' label calcParam calcWeight a = do logJsonTrace_ :: (MonadIO m, ToJSON a, Typeable a, NFData a, Logger logger) => logger -> LogLevel -> JsonLog a -> m () logJsonTrace_ logger level msg = liftIO $ logFunction logger level msg --- | Pair parent header with transaction metadata. --- In cases where there is no transaction/Command, 'PublicMeta' --- default value is used. -data BlockCtx = BlockCtx - { _bctxParentCreationTime :: !(Parent BlockCreationTime) - , _bctxParentHash :: !(Parent BlockHash) - , _bctxParentHeight :: !(Parent BlockHeight) - , _bctxChainId :: !ChainId - , _bctxChainwebVersion :: !ChainwebVersion - , _bctxMinerReward :: !MinerReward - } deriving (Eq, Show) - -blockCtxOfEvaluationCtx :: ChainwebVersion -> ChainId -> EvaluationCtx p -> BlockCtx -blockCtxOfEvaluationCtx v cid ec = BlockCtx - { _bctxParentCreationTime = _evaluationCtxParentCreationTime ec - , _bctxParentHash = _evaluationCtxParentHash ec - , _bctxParentHeight = _evaluationCtxParentHeight ec - , _bctxChainId = cid - , _bctxChainwebVersion = v - , _bctxMinerReward = _evaluationCtxMinerReward ec - } - -instance HasChainId BlockCtx where - _chainId = _bctxChainId -instance HasChainwebVersion BlockCtx where - _chainwebVersion = _bctxChainwebVersion - -_bctxIsGenesis :: BlockCtx -> Bool -_bctxIsGenesis bc = isGenesisBlockHeader' (_chainwebVersion bc) (_chainId bc) (_bctxParentHash bc) - -_bctxParentRankedBlockHash :: BlockCtx -> Parent RankedBlockHash -_bctxParentRankedBlockHash bc = Parent RankedBlockHash - { _rankedBlockHashHash = unwrapParent $ _bctxParentHash bc - , _rankedBlockHashHeight = unwrapParent $ _bctxParentHeight bc - } - -_bctxCurrentBlockHeight :: BlockCtx -> BlockHeight -_bctxCurrentBlockHeight bc = - childBlockHeight (_chainwebVersion bc) (_chainId bc) (_bctxParentRankedBlockHash bc) - -- State from a block in progress, which is used to extend blocks after -- running their payloads. data BlockInProgress = BlockInProgress @@ -587,6 +600,7 @@ data BlockInProgress = BlockInProgress , _blockInProgressTransactions :: !(Transactions Chainweb.Transaction OffChainCommandResult) } +makeLenses ''PactServiceEnv makeLenses ''BlockInProgress instance Eq BlockInProgress where @@ -608,9 +622,6 @@ makeLenses ''PactBlockState makeLenses ''PactBlockEnv -guardCtx :: (ChainwebVersion -> Chainweb.ChainId -> BlockHeight -> a) -> BlockCtx -> a -guardCtx g txCtx = g (_chainwebVersion txCtx) (_chainId txCtx) (_bctxCurrentBlockHeight txCtx) - -- | Indicates a computed gas charge (gas amount * gas price) newtype GasSupply = GasSupply { _pact5GasSupply :: Decimal } deriving (Eq,Ord) @@ -736,6 +747,10 @@ hashPactTxLogs :: Pact.CommandResult [Pact.TxLog ByteString] err -> Pact.Command hashPactTxLogs cr = cr & over (Pact.crLogs . _Just) (\ls -> Pact.hashTxLogs ls) +commandToBytes :: Pact.Transaction -> Chainweb.Transaction +commandToBytes = Chainweb.Transaction . J.encodeStrict + . fmap (T.decodeUtf8 . SB.fromShort . view Pact.payloadBytes) + toPayloadWithOutputs :: Miner -> Transactions Pact.Transaction OffChainCommandResult @@ -745,7 +760,7 @@ toPayloadWithOutputs mi ts = oldSeq :: Vector (Pact.Transaction, OffChainCommandResult) oldSeq = _transactionPairs ts trans :: Vector Chainweb.Transaction - trans = cmdBSToTx . fst <$> oldSeq + trans = commandToBytes . fst <$> oldSeq transOuts :: Vector Chainweb.TransactionOutput transOuts = Chainweb.TransactionOutput . pactCommandResultToBytes . hashPactTxLogs . snd <$> oldSeq @@ -755,9 +770,6 @@ toPayloadWithOutputs mi ts = cb = Chainweb.CoinbaseOutput $ pactCommandResultToBytes $ hashPactTxLogs $ _transactionCoinbase ts blockTrans :: Chainweb.BlockTransactions blockTrans = snd $ Chainweb.newBlockTransactions miner trans - cmdBSToTx :: Pact.Transaction -> Chainweb.Transaction - cmdBSToTx = Chainweb.Transaction . J.encodeStrict - . fmap (T.decodeUtf8 . SB.fromShort . view Pact.payloadBytes) blockOuts :: Chainweb.BlockOutputs blockOuts = snd $ Chainweb.newBlockOutputs cb transOuts diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index a7643e8bbc..ee57c862b2 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -87,6 +87,7 @@ module Chainweb.PayloadProvider -- ** Consensus State Accessors , _latestBlockHash +, _latestRankedBlockHash , _latestPayloadHash , _latestHeight , _safeBlockHash @@ -377,7 +378,7 @@ instance ToJSON p => ToJSON (EvaluationCtx p) where data NewBlockCtx = NewBlockCtx { _newBlockCtxMinerReward :: !MinerReward -- ^ the miner reward for the new block. - , _newBlockCtxParentCreationTime :: !BlockCreationTime + , _newBlockCtxParentCreationTime :: !(Parent BlockCreationTime) -- ^ the creation time of the block on which the new is created. } deriving (Show, Eq, Ord) From 3816d85681ccde538485bb204f0ba197c1b9d6ff Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 3 Apr 2025 00:05:33 -0700 Subject: [PATCH 102/378] Fix compilation of chainweb-test-utils --- chainweb.cabal | 2 +- src/Chainweb/Chainweb/CutResources.hs | 9 +++ test/lib/Chainweb/Test/Cut.hs | 2 +- test/lib/Chainweb/Test/MultiNode.hs | 8 +-- test/lib/Chainweb/Test/Orphans/Internal.hs | 40 ++++++------- test/lib/Chainweb/Test/Pact4/Utils.hs | 34 ++++++----- .../VerifierPluginTest/Transaction/Utils.hs | 3 +- test/lib/Chainweb/Test/Pact5/CmdBuilder.hs | 2 +- test/lib/Chainweb/Test/RestAPI/Client_.hs | 29 ---------- test/lib/Chainweb/Test/TestVersions.hs | 56 +++++++++++++------ test/lib/Chainweb/Test/Utils.hs | 5 -- 11 files changed, 91 insertions(+), 99 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index c049880ace..26058b7afa 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -558,7 +558,6 @@ library chainweb-test-utils , chainweb-storage >= 0.1 , chronos >= 1.1 , containers >= 0.5 - , crypton >= 0.31 , crypton-connection >=0.4 , data-dword >= 0.3 , deepseq >= 1.4 @@ -566,6 +565,7 @@ library chainweb-test-utils , directory >= 1.2 , exceptions , filepath >= 1.4 + , hashes >=0.2.2.0 , http-client >= 0.5 , http-types >= 0.12 , lens >= 4.17 diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index bdd03408ab..c8816025dd 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -7,6 +7,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TemplateHaskell #-} -- | -- Module: Chainweb.Chainweb.CutResources @@ -22,10 +23,16 @@ -- module Chainweb.Chainweb.CutResources ( CutResources(..) +, cutResPeerDb +, cutResCutDb +, cutResCutP2pNode +, cutResHeaderP2pNode , withCutResources , cutNetworks ) where +import Control.Lens.TH + import Prelude hiding (log) import qualified Network.HTTP.Client as HTTP @@ -63,6 +70,8 @@ data CutResources = CutResources -- ^ P2P Network for fetching block headers on demand via a task queue. } +makeLenses ''CutResources + instance HasChainwebVersion CutResources where _chainwebVersion = _chainwebVersion . _cutResCutDb {-# INLINE _chainwebVersion #-} diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index 58b962510c..4b12ca1392 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -245,7 +245,7 @@ createNewCut createNewCut hdb n t pay i c = do extension <- fromMaybeM BadAdjacents $ getCutExtension c i work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay - (h, mc') <- extendCut c pay (solveWork work n t) + (h, mc') <- extendCut c (solveWork work n t) `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg c' <- fromMaybeM BadAdjacents mc' return $ T2 h c' diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index d825d3f974..d4e5b04550 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -98,7 +98,6 @@ import Chainweb.CutDB import Chainweb.Graph import Chainweb.Logger import Chainweb.Miner.Config -import Chainweb.Miner.Pact import Chainweb.Pact.Backend.PactState (allChains, getLatestBlockHeight, getLatestPactStateAtDiffable, TableDiffable(..), addChainIdLabel) import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (ChainGrandHash(..)) import Chainweb.Pact.Backend.PactState.GrandHash.Calc qualified as GrandHash.Calc @@ -185,7 +184,6 @@ multiConfig v n = defaultChainwebConfiguration v where miner = NodeMiningConfig { _nodeMiningEnabled = True - , _nodeMiner = noMiner , _nodeTestMiners = MinerCount n } @@ -229,8 +227,8 @@ harvestConsensusState logger stateVar nid (StartedChainweb cw) = do modifyMVar_ stateVar $ sampleConsensusState nid - (view (chainwebCutResources . cutsCutDb . cutDbWebBlockHeaderDb) cw) - (view (chainwebCutResources . cutsCutDb) cw) + (view (chainwebCutResources . cutResCutDb . cutDbWebBlockHeaderDb) cw) + (view (chainwebCutResources . cutResCutDb) cw) logFunctionText logger Info "shutdown node" multiNode @@ -707,7 +705,7 @@ sampleConsensusState :: Int -- ^ node Id -> WebBlockHeaderDb - -> CutDb tbl + -> CutDb -> ConsensusState -> IO ConsensusState sampleConsensusState nid bhdb cutdb s = do diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index 6e25f9c67f..d22c112aaa 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -64,8 +64,6 @@ import Control.Lens (view) import Control.Monad import Control.Monad.Catch -import Crypto.Hash.Algorithms - import Data.Aeson hiding (Error) import qualified Data.ByteString as B import qualified Data.ByteString.Short as BS @@ -73,9 +71,10 @@ import Data.Foldable import Data.Function import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS +import Data.Hash.Keccak import Data.Kind import qualified Data.List as L -import Data.MerkleLog +import Data.MerkleLog.V1 import Data.Streaming.Network.Internal import qualified Data.Text as T import qualified Data.Text.Encoding as T @@ -113,7 +112,6 @@ import Chainweb.BlockHeaderDB.RestAPI import Chainweb.BlockHeight import Chainweb.BlockWeight import Chainweb.ChainId -import Chainweb.Chainweb import Chainweb.Chainweb.Configuration import Chainweb.Crypto.MerkleLog import Chainweb.Cut.Create @@ -131,6 +129,7 @@ import Chainweb.NodeVersion import Chainweb.Pact.RestAPI.SPV import Chainweb.Pact.Types import Chainweb.Payload +import Chainweb.PayloadProvider import Chainweb.PowHash import Chainweb.RestAPI.NetworkID import Chainweb.RestAPI.NodeInfo @@ -393,8 +392,6 @@ instance Arbitrary HeaderUpdate where <$> (ObjectEncoded <$> arbitrary) <*> arbitrary <*> arbitrary - <*> arbitrary - <*> arbitrary instance Arbitrary BlockHashWithHeight where arbitrary = BlockHashWithHeight <$> arbitrary <*> arbitrary @@ -402,6 +399,9 @@ instance Arbitrary BlockHashWithHeight where -- -------------------------------------------------------------------------- -- -- Arbitrary CutHashes +instance Arbitrary EncodedPayloadData where + arbitrary = EncodedPayloadData <$> arbitrary + instance Arbitrary CutId where arbitrary = do bs <- arbitraryBytes 32 @@ -530,7 +530,7 @@ arbitraryMerkleProof = do (b, hs, bs) <- gen idx <- choose (0, hs + bs - 1) let result = if idx > (hs - 1) - then bodyProof @a b (idx - hs) + then bodyProofV1 @a b (idx - hs) else mkHeaderProof @a b (fromIntegral idx) case result of Left !e -> error $ "Chainweb.Test.Orphans.Internal.arbitraryMerkleProof: " <> show e @@ -551,7 +551,7 @@ arbitraryMerkleBodyProof arbitraryMerkleBodyProof = do (b, s) <- gen !idx <- choose (0, s - 1) - case bodyProof b idx of + case bodyProofV1 b idx of Left !e -> error $ "Chainweb.Test.Orphans.Internal.arbitraryMerkleBodyProof: " <> show e Right x -> return x where @@ -586,20 +586,20 @@ mkHeaderProof -> Natural -> m (MerkleProof a) mkHeaderProof b idx = case toLog @a b of - mlog@(MerkleLog _ (_ :+: _) _ :: MerkleLog a ChainwebHashTag (h ': t) (MerkleLogBody b)) -> do + mlog@(MerkleLog _ (_ :+: _) :: MerkleLog a ChainwebHashTag (h ': t) (MerkleLogBody b)) -> do case someN idx of -- Index 0 - SomeSing SZ -> headerProof @(AtIndex 'Z (h ': t)) @a b + SomeSing SZ -> headerProofV1 @(AtIndex 'Z (h ': t)) @a b -- Index > 0 SomeSing x@(SS _) -> case x of -- Assert (at runtime) that the index is within bounds (Sing :: Sing n) -> case lt x (headerSizeN mlog) of Just Refl -> case hasHeader @_ @_ @_ @_ @_ @_ @n mlog of - Dict _ -> headerProof @(AtIndex n (h ': t)) @a b + Dict _ -> headerProofV1 @(AtIndex n (h ': t)) @a b Nothing -> error "must not happen" headerSizeN :: MerkleLog a u t b -> Sing (Length t) -headerSizeN (MerkleLog _ l _) = go l +headerSizeN (MerkleLog _ l) = go l where go :: forall a u t b . MerkleLogEntries a u t b -> Sing (Length t) go MerkleLogBody{} = SZ @@ -658,8 +658,8 @@ hasHeader mlog = case go (sing @N @i) mlog of ) (MerkleLog a u (x' ': t') b) go SZ m = Dict m - go (SS (n :: Sing n)) m@(MerkleLog r (_ :+: t@(_ :+: _)) b) = - case go n (MerkleLog r t b) of + go (SS (n :: Sing n)) m@(MerkleLog r (_ :+: t@(_ :+: _))) = + case go n (MerkleLog r t) of -- FIXME: avoid use of unsafeCoerce -- @@ -822,16 +822,13 @@ instance Arbitrary CoordinationConfig where <$> arbitrary <*> arbitrary <*> arbitrary - <*> arbitrary - <*> arbitrary - <*> arbitrary instance Arbitrary NodeMiningConfig where arbitrary = NodeMiningConfig - <$> arbitrary <*> arbitrary <*> pure (MinerCount 10) + <$> arbitrary <*> pure (MinerCount 10) instance Arbitrary MiningConfig where - arbitrary = MiningConfig <$> arbitrary <*> arbitrary + arbitrary = MiningConfig <$> arbitrary <*> arbitrary <*> arbitrary -- -------------------------------------------------------------------------- -- -- Chainweb.SPV.EventProof @@ -904,16 +901,13 @@ instance Arbitrary SpvRequest where instance Arbitrary Spv2Request where arbitrary = Spv2Request <$> arbitrary <*> arbitrary <*> arbitrary -instance Arbitrary (TransactionProof ChainwebMerkleHashAlgorithm) where - arbitrary = TransactionProof <$> arbitrary <*> arbitrary - instance Arbitrary (TransactionOutputProof ChainwebMerkleHashAlgorithm) where arbitrary = TransactionOutputProof <$> arbitrary <*> arbitrary instance Arbitrary SomePayloadProof where arbitrary = oneof [ SomePayloadProof <$> arbitrary @(PayloadProof ChainwebMerkleHashAlgorithm) - , SomePayloadProof <$> arbitrary @(PayloadProof Keccak_256) + , SomePayloadProof <$> arbitrary @(PayloadProof Keccak256) ] -- Equality for SomePayloadProof is only used for testing. Using 'unsafeCoerce' diff --git a/test/lib/Chainweb/Test/Pact4/Utils.hs b/test/lib/Chainweb/Test/Pact4/Utils.hs index 3ba7d3d710..6825cc4941 100644 --- a/test/lib/Chainweb/Test/Pact4/Utils.hs +++ b/test/lib/Chainweb/Test/Pact4/Utils.hs @@ -219,7 +219,6 @@ import Chainweb.Test.TestVersions import Chainweb.Time import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -import Chainweb.Version (ChainwebVersion(..), chainIds) import qualified Chainweb.Version as Version import Chainweb.Version.Utils (someChainId) import Chainweb.WebBlockHeaderDB @@ -227,6 +226,8 @@ import Chainweb.WebPactExecutionService import Chainweb.Storage.Table.RocksDB import Chainweb.Pact.Backend.Types +import Chainweb.PayloadProvider +import Chainweb.Version -- ----------------------------------------------------------------------- -- -- Keys @@ -672,21 +673,21 @@ testPactCtxSQLite -> IO (TestPactCtx logger tbl) testPactCtxSQLite logger v cid bhdb pdb sqlenv conf = do cp <- initCheckpointerResources defaultModuleCacheLimit sqlenv DoNotPersistIntraBlockWrites cpLogger v cid - let rs = readRewards + let miner = Nothing !ctx <- TestPactCtx <$!> newMVar (PactServiceState mempty) - <*> pure (mkPactServiceEnv cp rs) + <*> pure (mkPactServiceEnv cp miner) evalPactServiceM_ ctx (initialPayloadState v cid) return ctx where cpLogger = addLabel ("chain-id", chainIdToText cid) $ addLabel ("sub-component", "checkpointer") $ logger - mkPactServiceEnv :: Checkpointer logger -> MinerRewards -> PactServiceEnv logger tbl - mkPactServiceEnv cp rs = PactServiceEnv + mkPactServiceEnv :: Checkpointer logger -> Maybe Miner -> PactServiceEnv logger tbl + mkPactServiceEnv cp miner = PactServiceEnv { _psMempoolAccess = Nothing , _psCheckpointer = cp , _psPdb = pdb , _psBlockHeaderDb = bhdb - , _psMinerRewards = rs + , _psMiner = miner , _psReorgLimit = _pactReorgLimit conf , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout conf , _psOnFatalError = defaultOnFatalError mempty @@ -745,6 +746,7 @@ withWebPactExecutionServiceCompaction logger v pactConfig bdb mempools act = withDb :: ([SQLiteEnv] -> IO x) -> [SQLiteEnv] -> IO x withDb g envs = withTempSQLiteConnection chainwebPragmas $ \s -> g (s : envs) + -- -> PactServiceM logger tbl (Historical (ForSomePactVersion BlockInProgress)) mkTestPactExecutionService :: () => SQLiteEnv -> ChainId @@ -753,8 +755,10 @@ withWebPactExecutionServiceCompaction logger v pactConfig bdb mempools act = bhdb <- getBlockHeaderDb c bdb ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig return $ PactExecutionService - { _pactNewBlock = \_ m fill ph -> - evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph + { _pactNewBlock = \_ fill ph -> + evalPactServiceM_ ctx $ + fmap (newBlockToNewPayload . NewBlockInProgress) + <$> execNewBlock (mempools ^?! atChain c) fill ph , _pactContinueBlock = \_ bip -> evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip , _pactValidateBlock = \h d -> @@ -803,8 +807,10 @@ withWebPactExecutionService logger v pactConfig bdb mempools act = bhdb <- getBlockHeaderDb c bdb ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig return $ PactExecutionService - { _pactNewBlock = \_ m fill ph -> - evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph + { _pactNewBlock = \_ fill ph -> + evalPactServiceM_ ctx $ + fmap (newBlockToNewPayload . NewBlockInProgress) + <$> execNewBlock (mempools ^?! atChain c) fill ph , _pactContinueBlock = \_ bip -> evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip , _pactValidateBlock = \h d -> @@ -839,13 +845,13 @@ runCut -> WebPactExecutionService -> GenBlockTime -> Noncer - -> Miner -> IO () -runCut v bdb pact genTime noncer miner = +runCut v bdb pact genTime noncer = forM_ (chainIds v) $ \cid -> do ph <- ParentHeader <$> getParentTestBlockDb bdb cid - !newBlock <- throwIfNoHistory =<< _webPactNewBlock pact cid miner NewBlockFill ph - let pout = newBlockToPayloadWithOutputs newBlock + !newPayload <- throwIfNoHistory =<< _webPactNewBlock pact cid NewBlockFill ph + let Just (EncodedPayloadOutputs epout) = _newPayloadEncodedPayloadOutputs newPayload + pout <- decodePayloadWithOutputs epout n <- noncer cid -- skip this chain if mining fails and retry with the next chain. diff --git a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs index 0b01b8e653..b9a0337e3b 100644 --- a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs +++ b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs @@ -141,8 +141,7 @@ runCut' :: PactTestM () runCut' = do pact <- view menvPact bdb <- view menvBdb - miner <- view menvMiner - liftIO $ runCut testVersion bdb pact (offsetBlockTime second) zeroNoncer miner + liftIO $ runCut testVersion bdb pact (offsetBlockTime second) zeroNoncer assertTxGas :: (HasCallStack, MonadIO m) => String -> Gas -> CommandResult Hash -> m () assertTxGas msg g = liftIO . assertEqual msg g . _crGas diff --git a/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs b/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs index 879018055b..f3e000e112 100644 --- a/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs +++ b/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs @@ -35,7 +35,7 @@ import Pact.Core.Gas.Types import Control.Exception.Safe import Control.Monad.IO.Class import Chainweb.Time -import Chainweb.Version +import Chainweb.Version hiding (ChainId) import qualified Chainweb.ChainId as Chainweb import Data.ByteString (ByteString) import qualified Chainweb.Pact5.Transaction as Pact5 diff --git a/test/lib/Chainweb/Test/RestAPI/Client_.hs b/test/lib/Chainweb/Test/RestAPI/Client_.hs index 9260da9d2c..23ce13e233 100644 --- a/test/lib/Chainweb/Test/RestAPI/Client_.hs +++ b/test/lib/Chainweb/Test/RestAPI/Client_.hs @@ -33,10 +33,8 @@ module Chainweb.Test.RestAPI.Client_ , headerClient' , hashesClient' , headersClient' -, blocksClient' , branchHashesClient' , branchHeadersClient' -, branchBlocksClient' ) where import Data.Functor.Identity @@ -45,7 +43,6 @@ import Servant.API.ContentTypes -- internal modules -import Chainweb.Block import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeaderDB @@ -142,19 +139,6 @@ headersClient' v c = runIdentity $ do (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(HeadersApi v c) -blocksClient' - :: ChainwebVersion - -> ChainId - -> Maybe Limit - -> Maybe (NextItem BlockHash) - -> Maybe MinRank - -> Maybe MaxRank - -> ClientM_ BlockPage -blocksClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) - (SomeSing (SChainId :: Sing c)) <- return $ toSing c - return $ client_ @(BlocksApi v c) - branchHashesClient' :: ChainwebVersion -> ChainId @@ -183,16 +167,3 @@ branchHeadersClient' v c = runIdentity $ do (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(BranchHeadersApi v c) -branchBlocksClient' - :: ChainwebVersion - -> ChainId - -> Maybe Limit - -> Maybe (NextItem BlockHash) - -> Maybe MinRank - -> Maybe MaxRank - -> BranchBounds BlockHeaderDb - -> ClientM_ (Page (NextItem BlockHash) Block) -branchBlocksClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) - (SomeSing (SChainId :: Sing c)) <- return $ toSing c - return $ client_ @(BranchBlocksApi v c) diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index c22a30998b..cc24d72d67 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -47,6 +47,7 @@ import Chainweb.Difficulty import Chainweb.Graph import Chainweb.HostAddress import Chainweb.Pact.Utils +import Chainweb.Payload import Chainweb.Time import Chainweb.Utils import Chainweb.Utils.Rule @@ -174,7 +175,7 @@ barebonesTestVersion g = buildTestVersion $ \v -> , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = AllChains emptyPayload + { _genesisBlockPayload = AllChains (_payloadWithOutputsPayloadHash emptyPayload) , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -208,8 +209,10 @@ timedConsensusVersion g1 g2 = buildTestVersion $ \v -> v } & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, TN0.payloadBlock) : - [(n, TNN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash TN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash TNN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -239,7 +242,10 @@ pact5CheckpointerTestVersion g1 = buildTestVersion $ \v -> v , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains [ (n, emptyPayload) | n <- HS.toList (chainIds v) ] + { _genesisBlockPayload = onChains + [ (n, _payloadWithOutputsPayloadHash emptyPayload) + | n <- HS.toList (chainIds v) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -262,8 +268,10 @@ cpmTestVersion g v = v } & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, TN0.payloadBlock) : - [(n, TNN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash TN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash TNN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -377,8 +385,10 @@ quirkedGasInstantCpmTestVersion g = buildTestVersion $ \v -> v } & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash INN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -399,8 +409,10 @@ quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ \v -> v } & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, QPIN0.payloadBlock) : - [(n, QPINN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash QPIN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash QPINN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -438,8 +450,10 @@ instantCpmTestVersion g = buildTestVersion $ \v -> v & versionQuirks .~ noQuirks & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash INN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -463,8 +477,10 @@ pact5InstantCpmTestVersion g = buildTestVersion $ \v -> v & versionQuirks .~ noQuirks & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, PIN0.payloadBlock) : - [(n, PINN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash PIN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash PINN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -493,8 +509,10 @@ pact5SlowCpmTestVersion g = buildTestVersion $ \v -> v & versionQuirks .~ noQuirks & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash INN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -521,8 +539,10 @@ instantCpmTransitionTestVersion g = buildTestVersion $ \v -> v & versionQuirks .~ noQuirks & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash INN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g) + ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index a49f4a2951..d6507c1df2 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -151,14 +151,12 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL import Data.Coerce (coerce) import Data.Foldable -import qualified Data.HashMap.Strict as HashMap import Data.IORef import Data.List (sortOn, isInfixOf) import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Tree import qualified Data.Tree.Lens as LT -import qualified Data.Vector as V import Data.Word import qualified Network.Connection as HTTP @@ -205,7 +203,6 @@ import Chainweb.BlockHeight import Chainweb.BlockWeight import Chainweb.ChainId import Chainweb.Chainweb -import Chainweb.Chainweb.ChainResources import Chainweb.Chainweb.Configuration import Chainweb.Chainweb.PeerResources import Chainweb.Crypto.MerkleLog hiding (header) @@ -219,10 +216,8 @@ import Chainweb.Logger import Chainweb.Mempool.Mempool (MempoolBackend(..), TransactionHash(..), BlockFill(..), mockBlockGasLimit) import Chainweb.MerkleUniverse import Chainweb.Miner.Config -import Chainweb.Miner.Pact import Chainweb.Pact.Backend.Types(SQLiteEnv) import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) -import Chainweb.Payload.PayloadStore import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID import Chainweb.Test.Pact5.Utils (getTestLogLevel) From 4c121a63bb7306b1d857e78811e3ee0ce2290517 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 24 Mar 2025 13:51:12 -0400 Subject: [PATCH 103/378] Rest of PactService Change-Id: Id00000000782778938baa62cb8146454d6fb2575 --- bench/Chainweb/MempoolBench.hs | 4 +- bench/Chainweb/Pact/Backend/ApplyCmd.hs | 28 +- bench/Chainweb/Pact/Backend/Bench.hs | 2 +- bench/Chainweb/Pact/Backend/ForkingBench.hs | 5 +- bench/Chainweb/Pact/Backend/PactService.hs | 8 +- bench/Chainweb/Utils/Bench.hs | 6 +- chainweb.cabal | 2 + cwtools/cwtools.cabal | 8 +- cwtools/ea/Ea.hs | 81 +- cwtools/ea/Ea/Genesis.hs | 57 +- node/src/ChainwebNode.hs | 6 +- src/Chainweb/BlockHeaderDB/PruneForks.hs | 5 +- src/Chainweb/Chainweb.hs | 31 +- src/Chainweb/Chainweb/ChainResources.hs | 4 +- src/Chainweb/Chainweb/CutResources.hs | 9 +- src/Chainweb/Mempool/InMem.hs | 13 +- src/Chainweb/Mempool/Mempool.hs | 7 +- src/Chainweb/Mempool/RestAPI/Client.hs | 2 +- src/Chainweb/Miner/Config.hs | 20 +- src/Chainweb/Miner/Coordinator.hs | 2 +- src/Chainweb/Miner/Pact.hs | 54 +- src/Chainweb/Miner/PayloadCache.hs | 4 +- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 209 +-- src/Chainweb/Pact/Backend/Compaction.hs | 66 +- src/Chainweb/Pact/Backend/PactState.hs | 42 +- .../Backend/PactState/GrandHash/Import.hs | 48 +- src/Chainweb/Pact/Backend/Types.hs | 39 +- src/Chainweb/Pact/Backend/Utils.hs | 274 ++-- src/Chainweb/Pact/PactService.hs | 1224 +++++++---------- src/Chainweb/Pact/PactService/Checkpointer.hs | 341 ++--- src/Chainweb/Pact/PactService/ExecBlock.hs | 272 ++-- src/Chainweb/Pact/RestAPI.hs | 6 +- src/Chainweb/Pact/RestAPI/Server.hs | 568 ++++---- src/Chainweb/Pact/SPV.hs | 14 +- src/Chainweb/Pact/Templates.hs | 99 +- src/Chainweb/Pact/Transaction.hs | 27 +- src/Chainweb/Pact/TransactionExec.hs | 7 +- src/Chainweb/Pact/Types.hs | 296 ++-- src/Chainweb/Pact/Utils.hs | 20 +- src/Chainweb/Pact/Validations.hs | 28 +- src/Chainweb/Payload.hs | 16 +- src/Chainweb/Payload/PayloadStore.hs | 31 +- src/Chainweb/PayloadProvider.hs | 20 +- src/Chainweb/PayloadProvider/EVM.hs | 9 +- src/Chainweb/PayloadProvider/EVM/Header.hs | 1 - src/Chainweb/PayloadProvider/Minimal.hs | 6 +- src/Chainweb/PayloadProvider/P2P.hs | 34 +- src/Chainweb/PayloadProvider/Pact.hs | 117 +- src/Chainweb/RestAPI/Utils.hs | 9 +- src/Chainweb/SPV/EventProof.hs | 19 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 15 +- test/lib/Chainweb/Test/Cut.hs | 2 +- test/lib/Chainweb/Test/Cut/TestBlockDb.hs | 4 +- test/lib/Chainweb/Test/MultiNode.hs | 268 ++-- test/lib/Chainweb/Test/Orphans/Internal.hs | 46 +- test/lib/Chainweb/Test/Orphans/Pact.hs | 93 +- .../Test/{Pact5 => Pact}/CmdBuilder.hs | 48 +- test/lib/Chainweb/Test/Pact/Utils.hs | 182 +++ test/lib/Chainweb/Test/Pact4/Utils.hs | 1118 --------------- .../Pact4/VerifierPluginTest/Transaction.hs | 279 ---- .../Transaction/Message/After225.hs | 358 ----- .../Transaction/Message/Before225.hs | 463 ------- .../VerifierPluginTest/Transaction/Utils.hs | 248 ---- .../Test/Pact4/VerifierPluginTest/Unit.hs | 56 - test/lib/Chainweb/Test/Pact5/Utils.hs | 231 ---- test/lib/Chainweb/Test/RestAPI/Client_.hs | 60 +- test/lib/Chainweb/Test/RestAPI/Utils.hs | 90 +- test/lib/Chainweb/Test/TestVersions.hs | 342 ++--- test/lib/Chainweb/Test/Utils.hs | 72 +- test/lib/Chainweb/Test/Utils/BlockHeader.hs | 7 +- test/lib/Chainweb/Test/Utils/TestHeader.hs | 21 +- test/unit/Chainweb/Test/CutDB.hs | 3 +- .../Test/{Pact5 => Pact}/CheckpointerTest.hs | 229 +-- .../Test/{Pact5 => Pact}/CutFixture.hs | 8 +- .../{Pact5 => Pact}/HyperlanePluginTests.hs | 10 +- .../Test/{Pact5 => Pact}/PactServiceTest.hs | 16 +- .../Test/{Pact5 => Pact}/RemotePactTest.hs | 16 +- .../Chainweb/Test/{Pact5 => Pact}/SPVTest.hs | 32 +- .../Chainweb/Test/Pact/TransactionExecTest.hs | 995 ++++++++++++++ .../Test/{Pact5 => Pact}/TransactionTests.hs | 8 +- .../Test/Pact5/TransactionExecTest.hs | 989 ------------- test/unit/Chainweb/Test/TreeDB.hs | 2 +- test/unit/ChainwebTests.hs | 28 +- 83 files changed, 3701 insertions(+), 6838 deletions(-) rename test/lib/Chainweb/Test/{Pact5 => Pact}/CmdBuilder.hs (85%) create mode 100644 test/lib/Chainweb/Test/Pact/Utils.hs delete mode 100644 test/lib/Chainweb/Test/Pact4/Utils.hs delete mode 100644 test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction.hs delete mode 100644 test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/After225.hs delete mode 100644 test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/Before225.hs delete mode 100644 test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs delete mode 100644 test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs delete mode 100644 test/lib/Chainweb/Test/Pact5/Utils.hs rename test/unit/Chainweb/Test/{Pact5 => Pact}/CheckpointerTest.hs (53%) rename test/unit/Chainweb/Test/{Pact5 => Pact}/CutFixture.hs (97%) rename test/unit/Chainweb/Test/{Pact5 => Pact}/HyperlanePluginTests.hs (99%) rename test/unit/Chainweb/Test/{Pact5 => Pact}/PactServiceTest.hs (98%) rename test/unit/Chainweb/Test/{Pact5 => Pact}/RemotePactTest.hs (99%) rename test/unit/Chainweb/Test/{Pact5 => Pact}/SPVTest.hs (91%) create mode 100644 test/unit/Chainweb/Test/Pact/TransactionExecTest.hs rename test/unit/Chainweb/Test/{Pact5 => Pact}/TransactionTests.hs (93%) delete mode 100644 test/unit/Chainweb/Test/Pact5/TransactionExecTest.hs diff --git a/bench/Chainweb/MempoolBench.hs b/bench/Chainweb/MempoolBench.hs index 9fdf8f3a1d..a690b410f0 100644 --- a/bench/Chainweb/MempoolBench.hs +++ b/bench/Chainweb/MempoolBench.hs @@ -19,8 +19,8 @@ import Chainweb.Graph (singletonChainGraph) import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Mempool.InMem qualified as InMem import Chainweb.Mempool.InMemTypes qualified as InMem -import Chainweb.Pact4.Transaction -import Chainweb.Test.Pact4.Utils +import Chainweb.Pact.Transaction +import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions import Chainweb.Utils import Chainweb.Utils.Bench diff --git a/bench/Chainweb/Pact/Backend/ApplyCmd.hs b/bench/Chainweb/Pact/Backend/ApplyCmd.hs index d581f826bc..9bf5542f74 100644 --- a/bench/Chainweb/Pact/Backend/ApplyCmd.hs +++ b/bench/Chainweb/Pact/Backend/ApplyCmd.hs @@ -33,17 +33,17 @@ import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, import Chainweb.Pact.PactService (initialPayloadState, withPactService) import Chainweb.Pact.PactService.Checkpointer (readFrom, SomeBlockM(..)) import Chainweb.Pact.Types -import Chainweb.Pact4.Backend.ChainwebPactDb qualified as Pact4 -import Chainweb.Pact4.Transaction qualified as Pact4 -import Chainweb.Pact4.TransactionExec qualified as Pact4 -import Chainweb.Pact4.Types qualified as Pact4 -import Chainweb.Pact5.Transaction -import Chainweb.Pact5.TransactionExec qualified as Pact5 -import Chainweb.Pact5.Types qualified as Pact5 +import Chainweb.Pact4.Backend.ChainwebPactDb qualified as Pact +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.TransactionExec qualified as Pact +import Chainweb.Pact4.Types qualified as Pact +import Chainweb.Pact.Transaction +import Chainweb.Pact.TransactionExec qualified as Pact5 +import Chainweb.Pact.Types qualified as Pact5 import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb(..), mkTestBlockDbIO) -import Chainweb.Test.Pact4.Utils qualified as Pact4 -import Chainweb.Test.Pact5.CmdBuilder qualified as Pact5 +import Chainweb.Test.Pact4.Utils qualified as Pact +import Chainweb.Test.Pact.CmdBuilder qualified as Pact5 import Chainweb.Test.TestVersions import Chainweb.Utils (T2(..), T3(..)) import Chainweb.Utils.Bench @@ -67,10 +67,10 @@ import Pact.Core.Evaluate qualified as Pact5 import Pact.Core.Gas.Types qualified as Pact5 import Pact.Core.Persistence qualified as Pact5 import Pact.Core.SPV qualified as Pact5 -import Pact.Types.Command qualified as Pact4 -import Pact.Types.Gas qualified as Pact4 -import Pact.Types.Runtime qualified as Pact4 -import Pact.Types.SPV qualified as Pact4 +import Pact.Types.Command qualified as Pact +import Pact.Types.Gas qualified as Pact +import Pact.Types.Runtime qualified as Pact +import Pact.Types.SPV qualified as Pact bench :: RocksDb -> C.Benchmark bench rdb = C.bgroup "applyCmd" @@ -85,7 +85,7 @@ data Env = Env , blockHeaderDb :: !BlockHeaderDb , logger :: !GenericLogger , pactServiceThreadId :: !ThreadId - , pactServiceEnv :: !(PactServiceEnv GenericLogger RocksDbTable) + , pactServiceEnv :: !(ServiceEnv GenericLogger RocksDbTable) } instance NFData Env where diff --git a/bench/Chainweb/Pact/Backend/Bench.hs b/bench/Chainweb/Pact/Backend/Bench.hs index dcae080b75..b877f64cde 100644 --- a/bench/Chainweb/Pact/Backend/Bench.hs +++ b/bench/Chainweb/Pact/Backend/Bench.hs @@ -61,7 +61,7 @@ import Chainweb.Test.TestVersions import Chainweb.Utils.Bench import Chainweb.Utils (sshow) import Chainweb.Version -import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact4 +import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact import qualified Pact.Types.Command as Pact testVer :: ChainwebVersion diff --git a/bench/Chainweb/Pact/Backend/ForkingBench.hs b/bench/Chainweb/Pact/Backend/ForkingBench.hs index 35ca351250..fe856f69af 100644 --- a/bench/Chainweb/Pact/Backend/ForkingBench.hs +++ b/bench/Chainweb/Pact/Backend/ForkingBench.hs @@ -37,7 +37,7 @@ import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Pact.Utils (toTxCreationTime) -import Chainweb.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.InMemory @@ -213,10 +213,9 @@ withResources :: () => RocksDb -> Word64 -> LogLevel - -> IntraBlockPersistence -> RunPactService -> C.Benchmark -withResources rdb trunkLength logLevel p f = C.envWithCleanup create destroy unwrap +withResources rdb trunkLength logLevel f = C.envWithCleanup create destroy unwrap where unwrap ~(NoopNFData (Resources {..})) = diff --git a/bench/Chainweb/Pact/Backend/PactService.hs b/bench/Chainweb/Pact/Backend/PactService.hs index b826eb5c96..c9cd7d1656 100644 --- a/bench/Chainweb/Pact/Backend/PactService.hs +++ b/bench/Chainweb/Pact/Backend/PactService.hs @@ -39,12 +39,12 @@ import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactInProcApi import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types -import Chainweb.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Payload import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb(..), addTestBlockDb, getCutTestBlockDb, setCutTestBlockDb, getParentTestBlockDb, mkTestBlockDbIO) -import Chainweb.Test.Pact5.CmdBuilder -import Chainweb.Test.Pact5.Utils hiding (withTempSQLiteResource) +import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Test.Pact.Utils hiding (withTempSQLiteResource) import Chainweb.Test.TestVersions import Chainweb.Test.Utils import Chainweb.Time @@ -73,7 +73,7 @@ import Pact.Core.Capabilities import Pact.Core.Gas.Types import Pact.Core.Names import Pact.Core.PactValue -import Pact.Types.Gas qualified as Pact4 +import Pact.Types.Gas qualified as Pact import PropertyMatchers qualified as P import Test.Tasty.HUnit (assertEqual) import Text.Printf (printf) diff --git a/bench/Chainweb/Utils/Bench.hs b/bench/Chainweb/Utils/Bench.hs index b4f9d4a2a3..6ed5215478 100644 --- a/bench/Chainweb/Utils/Bench.hs +++ b/bench/Chainweb/Utils/Bench.hs @@ -17,11 +17,11 @@ module Chainweb.Utils.Bench import Chainweb.Logger import Pact.Core.Errors import Chainweb.Test.Cut.TestBlockDb (TestBlockDb) -import Chainweb.Test.Pact5.Utils (getTestLogLevel) +import Chainweb.Test.Pact.Utils (getTestLogLevel) import Chainweb.Test.Utils () import Database.SQLite3.Direct (Database(..)) import Chainweb.WebBlockHeaderDB (WebBlockHeaderDb) -import Chainweb.Pact.Types (PactServiceEnv) +import Chainweb.Pact.Types (ServiceEnv) import Control.DeepSeq (NFData(..)) import Chainweb.Mempool.Mempool (MempoolBackend) import Chainweb.Pact.Service.PactQueue (PactQueue) @@ -49,7 +49,7 @@ instance NFData (NoopNFData a) where deriving newtype instance NFData Database -instance NFData (PactServiceEnv logger tbl) where +instance NFData (ServiceEnv logger tbl) where rnf !_ = () instance NFData WebBlockHeaderDb where diff --git a/chainweb.cabal b/chainweb.cabal index 193fc0d9bb..415535ca9c 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -96,6 +96,7 @@ common debugging-flags common warning-flags ghc-options: + -Wwarn -Wall -Wcompat -Wpartial-fields @@ -449,6 +450,7 @@ library , unliftio >= 0.2 , unordered-containers >= 0.2.20 , uuid >= 1.3.15 + , validation , vector >= 0.12.2 , vector-algorithms >= 0.7 , wai >= 3.2.2.1 diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index 9e5e225ca1..1186db69d7 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -146,7 +146,7 @@ executable db-checksum , directory , memory , mtl - , pact + , pact-tng , safe-exceptions , text , unordered-containers @@ -176,7 +176,10 @@ executable ea , chainweb-storage , lens , loglevel - , pact + , pact-json + , pact-tng:pact-request-api + , pact-tng + , resource-pool , temporary , text , vector @@ -350,4 +353,3 @@ executable evm-genesis , base , network-uri , http-client - diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index 44156de2f6..f5fd91adc0 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -34,9 +34,9 @@ import Chainweb.Miner.Pact (noMiner) import Chainweb.Pact.Backend.Utils import Chainweb.Pact.PactService import Chainweb.Pact.Types (testPactServiceConfig) -import Chainweb.Pact.Utils (toTxCreationTime) -import Chainweb.Pact4.Transaction qualified as Pact4 -import Chainweb.Pact4.Validations (defaultMaxTTL) +import Chainweb.Pact.Utils (toTxCreationTime, emptyPayload) +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.Validations (defaultMaxTTLSeconds) import Chainweb.Payload import Chainweb.Payload.PayloadStore.InMemory import Chainweb.Storage.Table.RocksDB @@ -52,8 +52,10 @@ import Control.Lens import Data.Aeson qualified as Aeson import Data.Foldable import Data.Functor +import Data.Pool qualified as Pool import Data.Text (Text) import Data.Text qualified as T +import Data.Text.Encoding qualified as T import Data.Text.IO qualified as TIO import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Builder qualified as TB @@ -61,12 +63,16 @@ import Data.Traversable import Data.Vector qualified as V import Ea.Genesis import GHC.Exts(the) -import Pact.ApiReq -import Pact.Types.ChainMeta -import Pact.Types.Command hiding (Payload) import System.LogLevel import System.IO.Temp import Text.Printf +import Pact.Core.Command.Types +import Pact.Core.Command.Client +import Pact.Core.ChainData +import qualified Pact.Core.Command.Types as Pact +import qualified Chainweb.ChainId as Chainweb +import qualified Pact.JSON.Encode as J +import Pact.Core.StableEncoding --- @@ -78,9 +84,9 @@ main = do mapConcurrently_ id [ recapDevnet , devnet - , fastnet + -- , fastnet , instantnet - , pact5Instantnet + -- , pact5Instantnet , quirkedPact5Instantnet , testnet04 , testnet05 @@ -102,9 +108,9 @@ main = do [ fastDevelopment0 , fastDevelopmentN ] - fastnet = mkPayloads [fastTimedCPM0, fastTimedCPMN] + -- fastnet = mkPayloads [fastTimedCPM0, fastTimedCPMN] instantnet = mkPayloads [instantCPM0, instantCPMN] - pact5Instantnet = mkPayloads [pact5InstantCPM0, pact5InstantCPMN] + -- pact5Instantnet = mkPayloads [pact5InstantCPM0, pact5InstantCPMN] quirkedPact5Instantnet = mkPayloads [quirkedPact5InstantCPM0, quirkedPact5InstantCPMN] testnet04 = mkPayloads [testnet040, testnet04N] testnet05 = mkPayloads [testnet050, testnet05N] @@ -178,34 +184,33 @@ genCoinV6Payloads = genTxModule "CoinV6" -- Payload Generation --------------------- -genPayloadModule :: ChainwebVersion -> Text -> ChainId -> [Pact4.UnparsedTransaction] -> IO Text -genPayloadModule v tag cid cwTxs = - withTempRocksDb "chainweb-ea" $ \rocks -> - withBlockHeaderDb rocks v cid $ \bhdb -> do - let logger = genericLogger Warn TIO.putStrLn - pdb <- newPayloadDb - withSystemTempDirectory "ea-pact-db" $ \pactDbDir -> do - T2 payloadWO _ <- withSqliteDb cid logger pactDbDir False $ \env -> - withPactService v cid logger Nothing bhdb pdb env testPactServiceConfig $ - execNewGenesisBlock noMiner (V.fromList cwTxs) - return $ TL.toStrict $ TB.toLazyText $ payloadModuleCode tag payloadWO - -mkChainwebTxs :: [FilePath] -> IO [Pact4.UnparsedTransaction] +genPayloadModule :: ChainwebVersion -> Text -> Chainweb.ChainId -> [Pact.Transaction] -> IO Text +genPayloadModule v tag cid cwTxs = do + let logger = genericLogger Warn TIO.putStrLn + pdb <- newPayloadDb + withSystemTempDirectory "ea-pact-db" $ \pactDbDir -> do + payloadWO <- withSqliteDb cid logger pactDbDir False $ \readWriteSql -> do + roPool <- Pool.newPool $ Pool.defaultPoolConfig (startReadSqliteDb cid logger pactDbDir) stopSqliteDb 10 10 + withPactService v cid Nothing mempty logger Nothing pdb roPool readWriteSql (testPactServiceConfig emptyPayload) $ \serviceEnv -> + execNewGenesisBlock logger serviceEnv (V.fromList cwTxs) + return $ TL.toStrict $ TB.toLazyText $ payloadModuleCode tag payloadWO + +mkChainwebTxs :: [FilePath] -> IO [Pact.Transaction] mkChainwebTxs txFiles = mkChainwebTxs' =<< traverse mkTx txFiles -mkChainwebTxs' :: [Command Text] -> IO [Pact4.UnparsedTransaction] +mkChainwebTxs' :: [Command Text] -> IO [Pact.Transaction] mkChainwebTxs' rawTxs = forM rawTxs $ \cmd -> do - let parsedCmd = - traverse Aeson.eitherDecodeStrictText cmd - case parsedCmd of - Left err -> error err - Right unparsedTx -> do - let t = toTxCreationTime (Time (TimeSpan 0)) - return $! Pact4.mkPayloadWithTextOldUnparsed <$> (unparsedTx & setTxTime t & setTTL defaultMaxTTL) - where - setTxTime = set (cmdPayload . pMeta . pmCreationTime) - setTTL = set (cmdPayload . pMeta . pmTTL) + let parsedTx = either (error . sshow) (cmdPayload . pMeta %~ _stableEncoding) $ Pact.unsafeParseCommand (T.encodeUtf8 <$> cmd) + -- TODO: why is this always the tx creation time? different versions + -- have different block creation times + let epochCreationTime = toTxCreationTime (Time (TimeSpan 0)) + let tx' = parsedTx + & set (cmdPayload . pMeta . pmCreationTime) epochCreationTime + & set (cmdPayload . pMeta . pmTTL) (TTLSeconds defaultMaxTTLSeconds) + return $! Pact.unsafeMkPayloadWithText + (tx' ^. cmdPayload) + (J.encodeStrict $ (tx' ^. cmdPayload) & pMeta %~ StableEncoding & fmap _pcCode) <$ parsedTx mkTx :: FilePath -> IO (Command Text) mkTx yamlFile = snd <$> mkApiReq yamlFile @@ -308,7 +313,7 @@ genTxModule tag txFiles = do let encTxs = map quoteTx cwTxs quoteTx tx = " \"" <> encTx tx <> "\"" - encTx = encodeB64UrlNoPaddingText . codecEncode Pact4.rawCommandCodec + encTx = encodeB64UrlNoPaddingText . codecEncode Pact.commandCodec modl = T.unlines $ startTxModule tag <> [T.intercalate "\n ,\n" encTxs] <> endTxModule fileName = "src/Chainweb/Pact/Transactions/" <> tag <> "Transactions.hs" @@ -325,13 +330,13 @@ startTxModule tag = , "import Data.Bifunctor (first)" , "import System.IO.Unsafe" , "" - , "import qualified Chainweb.Pact4.Transaction as Pact4" + , "import qualified Chainweb.Pact.Transaction as Pact" , "import Chainweb.Utils" , "" - , "transactions :: [Pact4.Transaction]" + , "transactions :: [Pact.Transaction]" , "transactions =" , " let decodeTx t =" - , " fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t" + , " fromEitherM . (first (userError . show)) . codecDecode (Pact.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t" , " in unsafePerformIO $ mapM decodeTx [" ] diff --git a/cwtools/ea/Ea/Genesis.hs b/cwtools/ea/Ea/Genesis.hs index fea1fb60ca..00cbbb2810 100644 --- a/cwtools/ea/Ea/Genesis.hs +++ b/cwtools/ea/Ea/Genesis.hs @@ -20,12 +20,12 @@ module Ea.Genesis , fastDevelopmentN -- * Testing Genesis Txs -, fastTimedCPM0 -, fastTimedCPMN +-- , fastTimedCPM0 +-- , fastTimedCPMN , instantCPM0 , instantCPMN -, pact5InstantCPM0 -, pact5InstantCPMN +-- , pact5InstantCPM0 +-- , pact5InstantCPMN , quirkedPact5InstantCPM0 , quirkedPact5InstantCPMN @@ -255,23 +255,6 @@ instantCPMN = instantCPM0 & txChainIds .~ mkChainIdRange 1 9 & coinbase ?~ fastNGrants -pact5InstantCPM0 :: Genesis -pact5InstantCPM0 = Genesis - { _version = pact5InstantCpmTestVersion petersonChainGraph - , _tag = "Pact5InstantTimedCPM" - , _txChainIds = onlyChainId 0 - , _coinbase = Just fast0Grants - , _keysets = Just fastKeysets - , _allocations = Just fastAllocations - , _namespaces = Just devNs2 - , _coinContract = [fungibleAssetV1, fungibleXChainV1, fungibleAssetV2, installCoinContractV6, gasPayer] - } - -pact5InstantCPMN :: Genesis -pact5InstantCPMN = pact5InstantCPM0 - & txChainIds .~ mkChainIdRange 1 9 - & coinbase ?~ fastNGrants - quirkedPact5InstantCPM0 :: Genesis quirkedPact5InstantCPM0 = Genesis { _version = quirkedGasPact5InstantCpmTestVersion petersonChainGraph @@ -289,22 +272,22 @@ quirkedPact5InstantCPMN = quirkedPact5InstantCPM0 & txChainIds .~ mkChainIdRange 1 9 & coinbase ?~ fastNGrants -fastTimedCPM0 :: Genesis -fastTimedCPM0 = Genesis - { _version = fastForkingCpmTestVersion petersonChainGraph - , _tag = "FastTimedCPM" - , _txChainIds = onlyChainId 0 - , _coinbase = Just fast0Grants - , _keysets = Just fastKeysets - , _allocations = Just fastAllocations - , _namespaces = Just fastNs - , _coinContract = [fungibleAssetV1, coinContractV1, gasPayer] - } - -fastTimedCPMN :: Genesis -fastTimedCPMN = fastTimedCPM0 - & txChainIds .~ mkChainIdRange 1 9 - & coinbase ?~ fastNGrants +-- fastTimedCPM0 :: Genesis +-- fastTimedCPM0 = Genesis +-- { _version = fastForkingCpmTestVersion petersonChainGraph +-- , _tag = "FastTimedCPM" +-- , _txChainIds = onlyChainId 0 +-- , _coinbase = Just fast0Grants +-- , _keysets = Just fastKeysets +-- , _allocations = Just fastAllocations +-- , _namespaces = Just fastNs +-- , _coinContract = [fungibleAssetV1, coinContractV1, gasPayer] +-- } + +-- fastTimedCPMN :: Genesis +-- fastTimedCPMN = fastTimedCPM0 +-- & txChainIds .~ mkChainIdRange 1 9 +-- & coinbase ?~ fastNGrants fastNs :: FilePath fastNs = "pact/genesis/ns-v1.yaml" diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index a48ad38bb2..ff01ad18f5 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -129,7 +129,6 @@ data ChainwebNodeConfiguration = ChainwebNodeConfiguration { _nodeConfigChainweb :: !ChainwebConfiguration , _nodeConfigLog :: !LogConfig , _nodeConfigDatabaseDirectory :: !(Maybe FilePath) - , _nodeConfigResetChainDbs :: !Bool } deriving (Show, Eq, Generic) @@ -141,7 +140,6 @@ defaultChainwebNodeConfiguration = ChainwebNodeConfiguration , _nodeConfigLog = defaultLogConfig & logConfigLogger . L.loggerConfigThreshold .~ level , _nodeConfigDatabaseDirectory = Nothing - , _nodeConfigResetChainDbs = False } where level = L.Info @@ -157,7 +155,6 @@ instance ToJSON ChainwebNodeConfiguration where [ "chainweb" .= _nodeConfigChainweb o , "logging" .= _nodeConfigLog o , "databaseDirectory" .= _nodeConfigDatabaseDirectory o - , "resetChainDatabases" .= _nodeConfigResetChainDbs o ] instance FromJSON (ChainwebNodeConfiguration -> ChainwebNodeConfiguration) where @@ -165,7 +162,6 @@ instance FromJSON (ChainwebNodeConfiguration -> ChainwebNodeConfiguration) where <$< nodeConfigChainweb %.: "chainweb" % o <*< nodeConfigLog %.: "logging" % o <*< nodeConfigDatabaseDirectory ..: "databaseDirectory" % o - <*< nodeConfigResetChainDbs ..: "resetChainDatabases" % o pChainwebNodeConfiguration :: MParser ChainwebNodeConfiguration pChainwebNodeConfiguration = id @@ -347,7 +343,7 @@ node conf logger = do withRocksDb' rocksDbDir modernDefaultOptions $ \rocksDb -> do logFunctionText logger Info $ "opened rocksdb in directory " <> sshow rocksDbDir logFunctionText logger Debug $ "backup config: " <> sshow (_configBackup cwConf) - withChainweb cwConf logger rocksDb pactDbDir dbBackupsDir (_nodeConfigResetChainDbs conf) $ \case + withChainweb cwConf logger rocksDb pactDbDir dbBackupsDir $ \case Replayed _ _ -> return () StartedChainweb cw -> do let telemetryEnabled = diff --git a/src/Chainweb/BlockHeaderDB/PruneForks.hs b/src/Chainweb/BlockHeaderDB/PruneForks.hs index d79f88c2f4..a6a7fef2d6 100644 --- a/src/Chainweb/BlockHeaderDB/PruneForks.hs +++ b/src/Chainweb/BlockHeaderDB/PruneForks.hs @@ -54,6 +54,7 @@ import Chainweb.BlockHeader import Chainweb.BlockHeaderDB.Internal import Chainweb.BlockHeight import Chainweb.Logger +import Chainweb.Parent import Chainweb.TreeDB import Chainweb.Utils hiding (Codec) import Chainweb.Version @@ -174,7 +175,7 @@ pruneForks_ logg cdb mar mir callback = do -- parent hashes of all blocks at height max rank @mar@. -- !pivots <- entries cdb Nothing Nothing (Just $ MinRank $ Min $ _getMaxRank mar) (Just mar) - $ fmap (force . L.nub) . S.toList_ . S.map (view blockParent) + $ fmap (force . L.nub) . S.toList_ . S.map (unwrapParent . view blockParent) -- the set of pivots is expected to be very small. In fact it is -- almost always a singleton set. @@ -210,7 +211,7 @@ pruneForks_ logg cdb mar mir callback = do <> ". Previous height: " <> sshow prevHeight | view blockHash cur `elem` pivots = do callback False cur - let !pivots' = force $ L.nub $ view blockParent cur : L.delete (view blockHash cur) pivots + let !pivots' = force $ L.nub $ unwrapParent (view blockParent cur) : L.delete (view blockHash cur) pivots return (pivots', curHeight, n) | otherwise = do deleteHdr cur diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index a3bdb428f2..637c925eac 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -72,7 +72,7 @@ module Chainweb.Chainweb , NowServing(..) -- ** Mempool integration -, Mempool.pact4TransactionConfig +, Mempool.pactTransactionConfig , validatingMempoolConfig , withChainweb @@ -118,7 +118,7 @@ import Data.LogMessage (LogFunctionText) import Data.Maybe import Data.Text qualified as T -import GHC.Generics +import GHC.Generics hiding (to) import Network.HTTP.Client qualified as HTTP import Network.HTTP2.Client qualified as HTTP2 @@ -156,10 +156,9 @@ import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Config import Chainweb.OpenAPIValidation qualified as OpenAPIValidation -import Chainweb.Pact.Backend.Types(IntraBlockPersistence(..)) import Chainweb.Pact.RestAPI.Server (PactServerData(..)) import Chainweb.Pact.Types (PactServiceConfig(..)) -import Chainweb.Pact.Transaction qualified as Pact4 +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.RocksDB import Chainweb.PayloadProvider @@ -176,6 +175,8 @@ import Chainweb.WebBlockHeaderDB import P2P.Node.Configuration import P2P.Node.PeerDB (PeerDb) import P2P.Peer +import qualified Pact.Core.Gas as Pact +import qualified Chainweb.PayloadProvider.Pact.Genesis as Pact -- -------------------------------------------------------------------------- -- -- Chainweb Resources @@ -218,10 +219,9 @@ withChainweb -> RocksDb -> FilePath -> FilePath - -> Bool -> (StartedChainweb logger -> IO ()) -> IO () -withChainweb c logger rocksDb pactDbDir backupDir resetDb inner = +withChainweb c logger rocksDb pactDbDir backupDir inner = withPeerResources v (_configP2p confWithBootstraps) logger $ \logger' peerRes -> withSocket serviceApiPort serviceApiHost $ \serviceSock -> do let conf' = confWithBootstraps @@ -235,7 +235,6 @@ withChainweb c logger rocksDb pactDbDir backupDir resetDb inner = rocksDb pactDbDir backupDir - resetDb inner where serviceApiPort = _serviceApiConfigPort $ _configServiceApi c @@ -282,10 +281,9 @@ withChainwebInternal -> RocksDb -> FilePath -> FilePath - -> Bool -> (StartedChainweb logger -> IO ()) -> IO () -withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir resetDb inner = do +withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir inner = do -- Garbage Collection -- performed before PayloadDb and BlockHeaderDb used by other components @@ -320,7 +318,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir -- FIXME: shouldn't this be done in a configuration validation? -- NOTE: the gas limit may be set based on block height in future, so this approach may not be valid. - let maxGasLimit = fromIntegral <$> maxBlockGasLimit v maxBound + let maxGasLimit = Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit v maxBound case maxGasLimit of Just maxGasLimit' | _configBlockGasLimit conf > maxGasLimit' -> @@ -338,7 +336,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir rocksDb (_peerResManager peerRes) pactDbDir - (pactConfig maxGasLimit) + (pactConfig cid maxGasLimit) (_peerResConfig peerRes) myInfo peerDb @@ -379,7 +377,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir p2pConfig :: P2pConfiguration p2pConfig = _peerResConfig peerRes - pactConfig maxGasLimit = PactServiceConfig + pactConfig chain maxGasLimit = PactServiceConfig { _pactReorgLimit = _configReorgLimit conf , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf , _pactQueueSize = _configPactQueueSize conf @@ -391,13 +389,10 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir , _pactLogGas = _configLogGas conf , _pactEnableLocalTimeout = _configEnableLocalTimeout conf , _pactFullHistoryRequired = _configFullHistoricPactState conf - , _pactPersistIntraBlockWrites = - if _configFullHistoricPactState conf - then PersistIntraBlockWrites - else DoNotPersistIntraBlockWrites , _pactTxTimeLimit = Nothing -- FIXME , _pactMiner = Nothing + , _pactGenesisPayload = Pact.genesisPayload v ^?! atChain chain } -- FIXME: make this configurable @@ -769,7 +764,7 @@ runChainweb cw nowServing = do chainDbsToServe :: [(ChainId, BlockHeaderDb)] chainDbsToServe = proj _chainResBlockHeaderDb - mempoolsToServe :: [(ChainId, Mempool.MempoolBackend Pact4.UnparsedTransaction)] + mempoolsToServe :: [(ChainId, Mempool.MempoolBackend Pact.Transaction)] -- mempoolsToServe = proj _chainResMempool mempoolsToServe = [] @@ -819,7 +814,7 @@ runChainweb cw nowServing = do logFunctionCounter (_chainwebLogger cw) Info . (:[]) =<< roll clientClosedConnectionsCounter - chainwebServerDbs :: ChainwebServerDbs Pact4.UnparsedTransaction + chainwebServerDbs :: ChainwebServerDbs Pact.Transaction chainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Just cutDb , _chainwebServerBlockHeaderDbs = chainDbsToServe diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 693efa1296..26d4d800c5 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -238,7 +238,7 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs -- provider. let config = _payloadProviderConfigMinimal configs - p <- newMinimalPayloadProvider logger v c rdb mgr config + p <- newMinimalPayloadProvider logger v c rdb (Just mgr) config let pdb = view minimalPayloadDb p let queue = view minimalPayloadQueue p p2pRes <- payloadP2pResources @v' @c' @'MinimalProvider @@ -264,7 +264,7 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs -- and answering API requests. -- It also starts to awaiting and devlivering new payloads if mining -- is enabled. - withEvmPayloadProvider logger v c rdb mgr config $ \p -> do + withEvmPayloadProvider logger v c rdb (Just mgr) config $ \p -> do let pdb = view evmPayloadDb p let queue = view evmPayloadQueue p p2pRes <- payloadP2pResources @v' @c' @('EvmProvider n) diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 27c0241c8a..20f181dc96 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -22,11 +22,15 @@ -- module Chainweb.Chainweb.CutResources ( CutResources(..) +, cutResPeerDb +, cutResCutDb +, cutResCutP2pNode +, cutResHeaderP2pNode , withCutResources , cutNetworks ) where -import Control.Monad.Catch +import Control.Lens import Prelude hiding (log) @@ -65,6 +69,8 @@ data CutResources = CutResources -- ^ P2P Network for fetching block headers on demand via a task queue. } +makeLenses ''CutResources + instance HasChainwebVersion CutResources where _chainwebVersion = _chainwebVersion . _cutResCutDb {-# INLINE _chainwebVersion #-} @@ -125,4 +131,3 @@ cutNetworks cuts = [ p2pRunNode (_cutResCutP2pNode cuts) , p2pRunNode (_cutResHeaderP2pNode cuts) ] - diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 34c79c1ff3..2df84d4aea 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -69,19 +69,17 @@ import System.Random -- internal imports -import Chainweb.BlockHash -import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Mempool.CurrentTxs import Chainweb.Mempool.InMemTypes import Chainweb.Mempool.Mempool import Chainweb.Pact.Validations (defaultMaxTTLSeconds, defaultMaxCoinDecimalPlaces) -import Chainweb.Parent import Chainweb.Time import Chainweb.Utils import Chainweb.Version (ChainwebVersion) import Pact.Core.Gas +import Chainweb.PayloadProvider (EvaluationCtx) ------------------------------------------------------------------------------ compareOnGasPrice :: TransactionConfig t -> t -> t -> Ordering @@ -567,11 +565,10 @@ getBlockInMem -> MVar (InMemoryMempoolData t) -> BlockFill -> MempoolPreBlockCheck t to - -> BlockHeight - -> Parent BlockHash + -> EvaluationCtx () -> IO (Vector to) -getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight phash = do - logFunctionText logg Debug $ "getBlockInMem: " <> sshow (gasLimit,bheight,phash) +getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate evalCtx = do + -- logFunctionText logg Debug $ "getBlockInMem: " <> sshow (gasLimit,evalCtx) withMVar lock $ \mdata -> do now <- getCurrentTimeIntegral @@ -650,7 +647,7 @@ getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate bheight p BadMap) validateBatch !psq0 !badmap q = do let txs = V.map (snd . snd) q - oks1 <- txValidate bheight phash txs + oks1 <- txValidate evalCtx txs let oks2 = V.map sizeOK txs let !oks = V.zipWith (\ok1 ok2 -> ok1 <* ok2) oks1 oks2 let (bad1, good) = diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Mempool/Mempool.hs index 5a8a8c8432..95bc7b809c 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Mempool/Mempool.hs @@ -141,7 +141,6 @@ import Data.LogMessage (LogFunctionText) import Chainweb.BlockHash import Chainweb.BlockHeight -import Chainweb.Parent import Chainweb.Time (Micros(..), Time(..), TimeSpan(..)) import Chainweb.Time qualified as Time import Chainweb.Pact.Transaction qualified as Pact @@ -317,7 +316,7 @@ data MempoolBackend t = MempoolBackend { -- for mining. -- , mempoolGetBlock - :: forall to. BlockFill -> MempoolPreBlockCheck t to -> BlockHeight -> Parent BlockHash -> IO (Vector to) + :: forall to. BlockFill -> MempoolPreBlockCheck t to -> EvaluationCtx () -> IO (Vector to) -- | Discard any expired transactions. , mempoolPrune :: IO () @@ -375,7 +374,7 @@ noopMempool = do noopMV = const $ return () noopAddToBadList = const $ return () noopCheckBadList v = return $ V.replicate (V.length v) False - noopGetBlock _ _ _ _ = return V.empty + noopGetBlock _ _ _ = return V.empty noopGetPending = const $ const $ return (0,0) noopClear = return () @@ -385,7 +384,7 @@ noopMempool = do pactTransactionConfig :: TransactionConfig Pact.Transaction pactTransactionConfig = TransactionConfig - { txCodec = Pact.payloadCodec + { txCodec = Pact.commandCodec , txHasher = commandHash , txHashMeta = chainwebTestHashMeta , txGasPrice = view (Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmGasPrice) diff --git a/src/Chainweb/Mempool/RestAPI/Client.hs b/src/Chainweb/Mempool/RestAPI/Client.hs index 0d9db652d6..6c61eb09e3 100644 --- a/src/Chainweb/Mempool/RestAPI/Client.hs +++ b/src/Chainweb/Mempool/RestAPI/Client.hs @@ -63,7 +63,7 @@ toMempool version chain txcfg env = , mempoolMarkValidated = const unsupported , mempoolAddToBadList = const unsupported , mempoolCheckBadList = const unsupported - , mempoolGetBlock = \_ _ _ _ -> unsupported + , mempoolGetBlock = \_ _ _ -> unsupported , mempoolGetPendingTransactions = getPending , mempoolPrune = unsupported , mempoolClear = clear diff --git a/src/Chainweb/Miner/Config.hs b/src/Chainweb/Miner/Config.hs index e19038b129..91bf146c0d 100644 --- a/src/Chainweb/Miner/Config.hs +++ b/src/Chainweb/Miner/Config.hs @@ -6,6 +6,7 @@ {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Miner.Config @@ -41,6 +42,7 @@ import Control.Lens (lens, view) import Control.Monad (when) import Control.Monad.Except (throwError) import Control.Monad.Writer (tell) +import Data.Set qualified as Set import GHC.Generics (Generic) @@ -48,14 +50,15 @@ import Numeric.Natural (Natural) import Options.Applicative -import qualified Pact.JSON.Encode as J -import Pact.Types.Term (mkKeySet, PublicKeyText(..)) +import Pact.JSON.Encode qualified as J -- internal modules -import Chainweb.Miner.Pact (Miner(..), MinerKeys(..), MinerId(..), minerId) +import Pact.Core.Guards qualified as Pact + +import Chainweb.Miner.Pact (Miner(..), MinerGuard(..), MinerId(..), minerId) import Chainweb.Time -import Chainweb.Utils (hostArch, sshow) +import Chainweb.Utils (hostArch, sshow, textOption) import Chainweb.Version import Chainweb.Version.Mainnet import Chainweb.Version.Testnet04 @@ -151,7 +154,7 @@ defaultMining = MiningConfig } invalidMiner :: Miner -invalidMiner = Miner "" . MinerKeys $ mkKeySet [] "keys-all" +invalidMiner = Miner "" . MinerGuard $ Pact.GKeyset (Pact.KeySet mempty Pact.KeysAll) -- -------------------------------------------------------------------------- -- -- Mining Coordination Config @@ -214,9 +217,9 @@ pMiner :: String -> Parser Miner pMiner prefix = pkToMiner <$> pPk where pkToMiner pk = Miner - (MinerId $ "k:" <> _pubKey pk) - (MinerKeys $ mkKeySet [pk] "keys-all") - pPk = strOption + (MinerId $ "k:" <> Pact._pubKey pk) + (MinerGuard $ Pact.GKeyset $ Pact.KeySet (Set.singleton pk) Pact.KeysAll) + pPk = Pact.PublicKeyText <$> textOption % long (prefix <> "mining-public-key") <> help "public key of a miner in hex decimal encoding. The account name is the public key prefix by 'k:'. (This option can be provided multiple times.)" @@ -257,4 +260,3 @@ defaultNodeMining = NodeMiningConfig { _nodeMiningEnabled = False , _nodeTestMiners = MinerCount 10 } - diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index ae0a1843c5..e38307a0ba 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -288,7 +288,7 @@ workReady t rbh pld ps' = WorkReady rbh pld ps' _newPayloadRankedHash :: NewPayload -> RankedBlockHash _newPayloadRankedHash p = - RankedBlockHash (_newPayloadParentHeight p) (_newPayloadParentHash p) + RankedBlockHash (unwrapParent $ _newPayloadParentHeight p) (unwrapParent $ _newPayloadParentHash p) instance Brief WorkState where brief (WorkNotReady rh) = "WorkNotReady" <> ":" <> brief rh diff --git a/src/Chainweb/Miner/Pact.hs b/src/Chainweb/Miner/Pact.hs index 878efade6d..0ce293eccf 100644 --- a/src/Chainweb/Miner/Pact.hs +++ b/src/Chainweb/Miner/Pact.hs @@ -19,7 +19,7 @@ module Chainweb.Miner.Pact ( -- * Data MinerId(..) -, MinerKeys(..) +, MinerGuard(..) , Miner(..) -- * Combinators , toMinerData @@ -49,7 +49,12 @@ import Chainweb.Payload import Chainweb.Utils import qualified Pact.JSON.Encode as J -import qualified Pact.Types.KeySet as Pact4 +import qualified Pact.Core.Guards as Pact +import qualified Pact.Core.Names as Pact +import qualified Pact.Core.PactValue as Pact +import qualified Pact.Core.StableEncoding as Pact +import Control.Applicative ((<|>)) +import qualified Data.Set as Set -- -------------------------------------------------------------------------- -- -- Miner data @@ -61,10 +66,10 @@ newtype MinerId = MinerId { _minerId :: Text } deriving stock (Eq, Ord, Generic) deriving newtype (Show, ToJSON, FromJSON, IsString, NFData, Hashable, J.Encode) --- | `MinerKeys` are a thin wrapper around a Pact `KeySet` to differentiate it --- from user keysets. +-- | `MinerGuard` is a thin wrapper around a Pact `Guard` to differentiate it +-- from user-level guards. -- -newtype MinerKeys = MinerKeys Pact4.KeySet +newtype MinerGuard = MinerGuard (Pact.Guard Pact.QualifiedName Pact.PactValue) deriving stock (Eq, Ord, Generic) deriving newtype (Show, NFData) @@ -73,7 +78,7 @@ newtype MinerKeys = MinerKeys Pact4.KeySet -- data Miner = Miner { _minerMinerId :: !MinerId - , _minerMinerKeys :: !MinerKeys + , _minerMinerGuard :: !MinerGuard } deriving stock (Eq, Ord, Show, Generic) deriving anyclass (NFData) @@ -85,17 +90,29 @@ data Miner = Miner -- the Genesis Payloads. If these change, the payloads become unreadable! -- instance J.Encode Miner where - build (Miner (MinerId m) (MinerKeys ks)) = J.object + build (Miner (MinerId m) (MinerGuard (Pact.GKeyset ks))) = J.object [ "account" J..= m - , "predicate" J..= Pact4._ksPredFun ks - , "public-keys" J..= J.Array (Pact4._ksKeys ks) + , "predicate" J..= Pact.StableEncoding (Pact._ksPredFun ks) + , "public-keys" J..= J.Array (Pact.StableEncoding <$> Set.toList (Pact._ksKeys ks)) + ] + build (Miner (MinerId m) (MinerGuard g)) = J.object + [ "account" J..= m + , "guard" J..= Pact.StableEncoding g ] {-# INLINE build #-} instance FromJSON Miner where parseJSON = withObject "Miner" $ \o -> Miner <$> (MinerId <$> o .: "account") - <*> (MinerKeys <$> (Pact4.KeySet <$> o .: "public-keys" <*> o .: "predicate")) + <*> + ( + MinerGuard . Pact.GKeyset <$> + (Pact.KeySet <$> + (Set.fromList . fmap Pact._stableEncoding <$> o .: "public-keys") <*> + (Pact._stableEncoding <$> o .: "predicate")) + <|> + MinerGuard . Pact._stableEncoding <$> o .: "guard" + ) -- | A lens into the miner id of a miner. -- @@ -105,8 +122,8 @@ minerId = lens (\(Miner i _) -> i) (\(Miner _ k) b -> Miner b k) -- | A lens into the miner keys of a miner. -- -minerKeys :: Lens' Miner MinerKeys -minerKeys = lens (\(Miner _ k) -> k) (\(Miner i _) b -> Miner i b) +minerKeys :: Lens' Miner MinerGuard +minerKeys = lens (\(Miner _ g) -> g) (\(Miner i _) b -> Miner i b) {-# INLINE minerKeys #-} -- | Keyset taken from cp examples in Pact @@ -114,17 +131,19 @@ minerKeys = lens (\(Miner _ k) -> k) (\(Miner i _) b -> Miner i b) -- defaultMiner :: Miner defaultMiner = Miner (MinerId "miner") - $ MinerKeys - $ Pact4.mkKeySet - ["f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27"] - "keys-all" + $ MinerGuard + $ Pact.GKeyset + $ Pact.KeySet + (Set.fromList [Pact.PublicKeyText "f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27"]) + Pact.KeysAll {-# NOINLINE defaultMiner #-} -- | A trivial Miner. -- noMiner :: Miner -noMiner = Miner (MinerId "NoMiner") (MinerKeys $ Pact4.mkKeySet [] "<") +noMiner = Miner (MinerId "NoMiner") + (MinerGuard $ Pact.GKeyset $ Pact.KeySet mempty (Pact.CustomPredicate (Pact.TBN (Pact.BareName "<")))) {-# NOINLINE noMiner #-} -- | Convert from Pact `Miner` to Chainweb `MinerData`. @@ -138,4 +157,3 @@ toMinerData = MinerData . J.encodeStrict fromMinerData :: MonadThrow m => MinerData -> m Miner fromMinerData = decodeStrictOrThrow' . _minerData {-# INLINABLE fromMinerData #-} - diff --git a/src/Chainweb/Miner/PayloadCache.hs b/src/Chainweb/Miner/PayloadCache.hs index d7e09ca8ab..09f731b85e 100644 --- a/src/Chainweb/Miner/PayloadCache.hs +++ b/src/Chainweb/Miner/PayloadCache.hs @@ -221,8 +221,8 @@ insertSTM pc pld = modifyTVar' (_payloadCacheMap pc) $ M.insert key pld . prune (_payloadCacheDepth pc) h where - h = _newPayloadParentHeight pld - p = _newPayloadParentHash pld + Parent h = _newPayloadParentHeight pld + Parent p = _newPayloadParentHash pld key = (Parent (RankedBlockHash h p), _newPayloadNumber pld) -- | Insert a new payload into the cache. The cache is pruned before the new diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index b4b68e7438..2751ae2f18 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -74,7 +74,7 @@ module Chainweb.Pact.Backend.ChainwebPactDb , initSchema , lookupBlockWithHeight , lookupParentBlockHash - , lookupBlockByEvalCtx + , lookupRankedBlockHash , getPayloadsAfter , getEarliestBlock , getConsensusState @@ -138,85 +138,13 @@ import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils import Chainweb.Pact.SPV (pactSPV) import Chainweb.Parent -import Chainweb.PayloadProvider (ConsensusState (..), SyncState (..), EvaluationCtx (_evaluationCtxPayload, _evaluationCtxParentHash), _evaluationCtxCurrentHeight) +import Chainweb.PayloadProvider (ConsensusState (..), SyncState (..)) import Chainweb.Utils (sshow) import Chainweb.Utils.Serialization (runPutS, runGetEitherS) import Chainweb.Version import Chainweb.Version.Guards (pact5Serialiser) import Chainweb.Ranked -data InternalDbException = InternalDbException CallStack Text -instance Show InternalDbException where show = displayException -instance Exception InternalDbException where - displayException (InternalDbException stack text) = - T.unpack text <> "\n\n" <> - prettyCallStack stack - -internalDbError :: HasCallStack => MonadThrow m => Text -> m a -internalDbError = throwM . InternalDbException callStack - -throwOnDbError :: (HasCallStack, MonadThrow m) => ExceptT SQ3.Error m a -> m a -throwOnDbError act = runExceptT act >>= either (internalDbError . sshow) return - --- | Statement input types -data SType = SInt Int64 | SDouble Double | SText SQ3.Utf8 | SBlob BS.ByteString deriving (Eq,Show) --- | Result types -data RType = RInt | RDouble | RText | RBlob deriving (Eq,Show) - -bindParams :: SQ3.Statement -> [SType] -> ExceptT SQ3.Error IO () -bindParams stmt as = - forM_ (zip as [1..]) $ \(a,i) -> ExceptT $ - case a of - SInt n -> SQ3.bindInt64 stmt i n - SDouble n -> SQ3.bindDouble stmt i n - SText n -> SQ3.bindText stmt i n - SBlob n -> SQ3.bindBlob stmt i n - -prepStmt :: HasCallStack => SQ3.Database -> SQ3.Utf8 -> ExceptT SQ3.Error IO SQ3.Statement -prepStmt c q = do - r <- ExceptT $ SQ3.prepare c q - case r of - Nothing -> internalDbError "No SQL statements in prepared statement" - Just s -> return s - -execMulti :: Traversable t => SQ3.Database -> SQ3.Utf8 -> t [SType] -> ExceptT SQ3.Error IO () -execMulti db q rows = bracket (prepStmt db q) (liftIO . SQ3.finalize) $ \stmt -> do - forM_ rows $ \row -> do - ExceptT $ SQ3.reset stmt - liftIO $ SQ3.clearBindings stmt - bindParams stmt row - ExceptT $ SQ3.step stmt - --- | Prepare/execute query with params -qry :: SQ3.Database -> SQ3.Utf8 -> [SType] -> [RType] -> ExceptT SQ3.Error IO [[SType]] -qry e q as rts = bracket (prepStmt e q) (ExceptT . SQ3.finalize) $ \stmt -> do - bindParams stmt as - reverse <$> stepStmt stmt rts - -stepStmt :: SQ3.Statement -> [RType] -> ExceptT SQ3.Error IO [[SType]] -stepStmt stmt rts = do - let acc rs SQ3.Done = return rs - acc rs SQ3.Row = do - as <- lift $ forM (zip rts [0..]) $ \(rt,ci) -> - case rt of - RInt -> SInt <$> SQ3.columnInt64 stmt ci - RDouble -> SDouble <$> SQ3.columnDouble stmt ci - RText -> SText <$> SQ3.columnText stmt ci - RBlob -> SBlob <$> SQ3.columnBlob stmt ci - sr <- ExceptT $ SQ3.step stmt - acc (as:rs) sr - sr <- ExceptT $ SQ3.step stmt - acc [] sr - --- | Prepare/exec statement with no params -exec_ :: SQ3.Database -> SQ3.Utf8 -> ExceptT SQ3.Error IO () -exec_ e q = ExceptT $ over _Left fst <$> SQ3.exec e q - --- | Prepare/exec statement with params -exec' :: SQ3.Database -> SQ3.Utf8 -> [SType] -> ExceptT SQ3.Error IO () -exec' e q as = bracket (prepStmt e q) (ExceptT . SQ3.finalize) $ \stmt -> do - bindParams stmt as - void $ ExceptT (SQ3.step stmt) data BlockHandlerEnv logger = BlockHandlerEnv { _blockHandlerDb :: !SQLiteEnv @@ -401,7 +329,7 @@ doReadRow d k = do fmap join $ withTableExistenceCheck pactTableName fetchRowFromDb _ -> throwOnDbError $ fetchRowFromDb where - fetchRowFromDb :: ExceptT SQ3.Error (BlockHandler logger) (Maybe (Int, v)) + fetchRowFromDb :: ExceptT LocatedSQ3Error (BlockHandler logger) (Maybe (Int, v)) fetchRowFromDb = do (decodeValueDoc, encodedKey) <- lift codec let decodeValue = fmap (view Pact.document) . decodeValueDoc @@ -417,7 +345,7 @@ doReadRow d k = do case result of [] -> return Nothing [[SBlob a]] -> return $ (BS.length a,) <$> decodeValue a - err -> internalDbError $ + err -> error $ "doReadRow: Expected (at most) a single result, but got: " <> sshow err @@ -472,20 +400,20 @@ checkTableStatus tableName = do -- `checkTableStatus`, too. returns `Nothing` if the table is pending creation -- in this block; usually, this means that we halt before accessing the db to -- look in the table. -withTableExistenceCheck :: HasCallStack => Pact.TableName -> ExceptT SQ3.Error (BlockHandler logger) a -> BlockHandler logger (Maybe a) +withTableExistenceCheck :: HasCallStack => Pact.TableName -> ExceptT LocatedSQ3Error (BlockHandler logger) a -> BlockHandler logger (Maybe a) withTableExistenceCheck tableName action = do atTip <- view blockHandlerAtTip if atTip -- at tip, speculatively execute the statement, and only check if the table -- was missing if the statement threw an error then runExceptT action >>= \case - Left err@SQ3.ErrorError -> do + Left err@(LocatedSQ3Error _ SQ3.ErrorError) -> do tableStatus <- checkTableStatus tableName case tableStatus of TableDoesNotExist -> liftGas $ throwDbOpErrorGasM $ Pact.NoSuchTable tableName TableCreationPending -> return Nothing - TableExists -> internalDbError (sshow err) - Left err -> internalDbError (sshow err) + TableExists -> liftIO (putStrLn "WAT1") >> error (sshow err) + Left err -> liftIO (putStrLn "WAT2") >> error (sshow err) Right result -> return (Just result) else do -- if we're rewound, we just check if the table exists first @@ -582,12 +510,12 @@ doKeys d = do Pact.DKeySets -> do let parsed = traverse Pact.parseAnyKeysetName dbKeys case parsed of - Left msg -> internalDbError $ "doKeys.DKeySets: unexpected decoding " <> T.pack msg + Left msg -> error $ "doKeys.DKeySets: unexpected decoding " <> msg Right v -> pure (v, Dict ()) Pact.DModules -> do let parsed = traverse Pact.parseModuleName dbKeys case parsed of - Nothing -> internalDbError $ "doKeys.DModules: unexpected decoding" + Nothing -> error $ "doKeys.DModules: unexpected decoding" Just v -> pure (v, Dict ()) Pact.DNamespaces -> pure (map Pact.NamespaceName dbKeys, Dict ()) Pact.DDefPacts -> pure (map Pact.DefPactId dbKeys, Dict ()) @@ -596,7 +524,7 @@ doKeys d = do let parsed = map Pact.parseHashedModuleName dbKeys case sequence parsed of Just v -> pure (v, Dict ()) - Nothing -> internalDbError $ "doKeys.DModuleSources: unexpected decoding" + Nothing -> error $ "doKeys.DModuleSources: unexpected decoding" case ordDict of Dict () -> return $ sort (memKeys ++ parsedKeys) @@ -609,7 +537,7 @@ doKeys d = do fromMaybe [] <$> withTableExistenceCheck pactTableName fetchKeys _ -> throwOnDbError fetchKeys where - fetchKeys :: ExceptT SQ3.Error (BlockHandler logger) [Text] + fetchKeys :: ExceptT LocatedSQ3Error (BlockHandler logger) [Text] fetchKeys = do Pact.TxId txIdUpperBoundWord64 <- view blockHandlerUpperBoundTxId db <- view blockHandlerDb @@ -619,7 +547,7 @@ doKeys d = do forM ks $ \row -> do case row of [SText k] -> return $ fromUtf8 k - _ -> internalDbError "doKeys: The impossible happened." + _ -> error "doKeys: The impossible happened." tn = toUtf8 $ Pact.renderDomain d collect p = InMemDb.keys d (_pendingWrites p) @@ -697,11 +625,11 @@ toTxLog :: MonadThrow m => ChainwebVersion -> ChainId -> BlockHeight -> T.Text - toTxLog version cid bh d key value = do let serialiser = pact5Serialiser version cid bh case fmap (view Pact.document) $ Pact._decodeRowData serialiser value of - Nothing -> internalDbError $ "toTxLog: Unexpected value, unable to deserialize log: " <> sshow value + Nothing -> error $ "toTxLog: Unexpected value, unable to deserialize log: " <> sshow value Just v -> return $! Pact.TxLog d (fromUtf8 key) v -commitBlockStateToDatabase :: SQLiteEnv -> EvaluationCtx BlockPayloadHash -> BlockHandle -> IO () -commitBlockStateToDatabase db evalCtx blockHandle = throwOnDbError $ do +commitBlockStateToDatabase :: SQLiteEnv -> Ranked (BlockHash, BlockPayloadHash) -> BlockHandle -> IO () +commitBlockStateToDatabase db blockInfo blockHandle = throwOnDbError $ do let newTables = _pendingTableCreation $ _blockHandlePending blockHandle mapM_ (\tn -> createUserTable (toUtf8 tn)) newTables backendWriteUpdateBatch (_pendingWrites (_blockHandlePending blockHandle)) @@ -712,7 +640,7 @@ commitBlockStateToDatabase db evalCtx blockHandle = throwOnDbError $ do backendWriteUpdateBatch :: InMemDb.Store - -> ExceptT SQ3.Error IO () + -> ExceptT LocatedSQ3Error IO () backendWriteUpdateBatch store = do writeTable (domainToTableName Pact.DKeySets) $ mapMaybe (uncurry $ prepRow . convKeySetName) @@ -750,7 +678,7 @@ commitBlockStateToDatabase db evalCtx blockHandle = throwOnDbError $ do ] prepRow _ InMemDb.ReadEntry {} = Nothing - writeTable :: SQ3.Utf8 -> [[SType]] -> ExceptT SQ3.Error IO () + writeTable :: SQ3.Utf8 -> [[SType]] -> ExceptT LocatedSQ3Error IO () writeTable table writes = when (not (null writes)) $ do execMulti db q writes markTableMutation table @@ -760,23 +688,23 @@ commitBlockStateToDatabase db evalCtx blockHandle = throwOnDbError $ do -- Mark the table as being mutated during this block, so that we know -- to delete from it if we rewind past this block. markTableMutation tablename = do - exec' db mutq [SText tablename, SInt (fromIntegral $ _evaluationCtxCurrentHeight evalCtx)] + exec' db mutq [SText tablename, SInt (fromIntegral $ rank blockInfo)] where mutq = "INSERT OR IGNORE INTO VersionedTableMutation VALUES (?,?);" -- | Record a block as being in the history of the checkpointer. - blockHistoryInsert :: Pact.TxId -> ExceptT SQ3.Error IO () + blockHistoryInsert :: Pact.TxId -> ExceptT LocatedSQ3Error IO () blockHistoryInsert (Pact.TxId t) = exec' db stmt - [ SInt (fromIntegral $ _evaluationCtxCurrentHeight evalCtx) - , SBlob (runPutS $ encodeBlockHash $ unwrapParent $ _evaluationCtxParentHash evalCtx) - , SBlob (runPutS $ encodeBlockPayloadHash $ _evaluationCtxPayload evalCtx) + [ SInt (fromIntegral $ rank blockInfo) + , SBlob (runPutS $ encodeBlockHash $ fst $ _ranked blockInfo) + , SBlob (runPutS $ encodeBlockPayloadHash $ snd $ _ranked blockInfo) , SInt (fromIntegral t) ] where - stmt = "INSERT INTO BlockHistory ('blockheight', 'parenthash', 'payloadhash', 'endingtxid') VALUES (?,?,?,?);" + stmt = "INSERT INTO BlockHistory ('blockheight', 'hash', 'payloadhash', 'endingtxid') VALUES (?,?,?,?);" - createUserTable :: SQ3.Utf8 -> ExceptT SQ3.Error IO () + createUserTable :: SQ3.Utf8 -> ExceptT LocatedSQ3Error IO () createUserTable tablename = do createVersionedTable tablename db markTableCreation tablename @@ -787,22 +715,22 @@ commitBlockStateToDatabase db evalCtx blockHandle = throwOnDbError $ do exec' db insertstmt insertargs where insertstmt = "INSERT OR IGNORE INTO VersionedTableCreation VALUES (?,?)" - insertargs = [SText tablename, SInt (fromIntegral $ _evaluationCtxCurrentHeight evalCtx)] + insertargs = [SText tablename, SInt (fromIntegral $ rank blockInfo)] -- | Commit the index of pending successful transactions to the database - indexPendingPactTransactions :: ExceptT SQ3.Error IO () + indexPendingPactTransactions :: ExceptT LocatedSQ3Error IO () indexPendingPactTransactions = do let txs = _pendingSuccessfulTxs $ _blockHandlePending blockHandle dbIndexTransactions txs where - toRow b = [SBlob b, SInt (fromIntegral $ _evaluationCtxCurrentHeight evalCtx)] + toRow b = [SBlob b, SInt (fromIntegral $ rank blockInfo)] dbIndexTransactions txs = do let rows = map toRow $ toList txs let q = "INSERT INTO TransactionIndex (txhash, blockheight) VALUES (?, ?)" execMulti db q rows -createVersionedTable :: SQ3.Utf8 -> SQ3.Database -> ExceptT SQ3.Error IO () +createVersionedTable :: SQ3.Utf8 -> SQ3.Database -> ExceptT LocatedSQ3Error IO () createVersionedTable tablename db = do exec_ db createtablestmt exec_ db indexcreationstmt @@ -817,36 +745,39 @@ createVersionedTable tablename db = do indexcreationstmt = "CREATE INDEX IF NOT EXISTS " <> tbl ixName <> " ON " <> tbl tablename <> "(txid DESC);" -setConsensusState :: SQ3.Database -> ConsensusState -> ExceptT SQ3.Error IO () +setConsensusState :: SQ3.Database -> ConsensusState -> ExceptT LocatedSQ3Error IO () setConsensusState db cs = do exec' db - "INSERT INTO ConsensusState (blockheight, hash, payloadhash, type) VALUES\ - \(?, ?, ?, 'final'), \ - \(?, ?, ?, 'latest'), \ - \(?, ?, ?, 'safe') \ - \ON CONFLICT REPLACE;" - $ concatMap toRow - [ _consensusStateFinal cs - , _consensusStateSafe cs - , _consensusStateLatest cs - ] + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "final" $ _consensusStateFinal cs) + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "safe" $ _consensusStateSafe cs) + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "latest" $ _consensusStateLatest cs) where - toRow SyncState {..} = + toRow safety SyncState {..} = [ SInt $ fromIntegral @BlockHeight @Int64 _syncStateHeight , SBlob $ runPutS (encodeBlockHash _syncStateBlockHash) , SBlob $ runPutS (encodeBlockPayloadHash _syncStateBlockPayloadHash) + , SText safety ] -getConsensusState :: SQ3.Database -> ExceptT SQ3.Error IO ConsensusState +getConsensusState :: SQ3.Database -> ExceptT LocatedSQ3Error IO (Maybe ConsensusState) getConsensusState db = do - qry db "SELECT FROM ConsensusState (blockheight, hash, payloadhash, type) order by type" + qry db "SELECT blockheight, hash, payloadhash, safety FROM ConsensusState ORDER BY safety ASC;" [] [RInt, RBlob, RBlob, RText] >>= \case - [final, latest, safe] -> return ConsensusState - { _consensusStateFinal = readRow "final" final - , _consensusStateLatest = readRow "latest" latest - , _consensusStateSafe = readRow "safe" safe - } - inv -> error $ "invalid contents of the ConsensusState table: " <> sshow inv + [final, latest, safe] -> return $ Just ConsensusState + { _consensusStateFinal = readRow "final" final + , _consensusStateLatest = readRow "latest" latest + , _consensusStateSafe = readRow "safe" safe + } + [] -> return Nothing + inv -> error $ "invalid contents of the ConsensusState table: " <> sshow inv where readRow expectedType [SInt height, SBlob hash, SBlob payloadHash, SText type'] | expectedType == type' = SyncState @@ -877,31 +808,31 @@ initSchema sql = create tablename = do createVersionedTable tablename sql - createConsensusStateTable :: ExceptT SQ3.Error IO () + createConsensusStateTable :: ExceptT LocatedSQ3Error IO () createConsensusStateTable = do exec_ sql "CREATE TABLE IF NOT EXISTS ConsensusState \ \(blockheight UNSIGNED BIGINT NOT NULL, \ \ hash BLOB NOT NULL, \ \ payloadhash BLOB NOT NULL, \ - \ type VARCHAR NOT NULL, \ - \ CONSTRAINT typeConstraint UNIQUE (type));" + \ safety TEXT NOT NULL, \ + \ CONSTRAINT safetyConstraint UNIQUE (safety) \ + \ ON CONFLICT REPLACE);" - createBlockHistoryTable :: ExceptT SQ3.Error IO () + createBlockHistoryTable :: ExceptT LocatedSQ3Error IO () createBlockHistoryTable = do exec_ sql "CREATE TABLE IF NOT EXISTS BlockHistory \ \(blockheight UNSIGNED BIGINT NOT NULL, \ \ endingtxid UNSIGNED BIGINT NOT NULL, \ - \ parenthash BLOB NOT NULL, \ + \ hash BLOB NOT NULL, \ \ payloadhash BLOB NOT NULL, \ \ CONSTRAINT blockHeightConstraint UNIQUE (blockheight), \ - \ CONSTRAINT parentHashConstraint UNIQUE (parenthash), \ - \ CONSTRAINT payloadHashConstraint UNIQUE (payloadHash));" + \ CONSTRAINT hashConstraint UNIQUE (hash));" -- TODO PP: payload hash should really be NOT NULL but there may exist old databases without it. -- making a block hash index at block height 5,658,430 on us-e3 took around 2.5 minutes - createTableCreationTable :: ExceptT SQ3.Error IO () + createTableCreationTable :: ExceptT LocatedSQ3Error IO () createTableCreationTable = exec_ sql "CREATE TABLE IF NOT EXISTS VersionedTableCreation\ @@ -909,7 +840,7 @@ initSchema sql = \, createBlockheight UNSIGNED BIGINT NOT NULL\ \, CONSTRAINT creation_unique UNIQUE(createBlockheight, tablename));" - createTableMutationTable :: ExceptT SQ3.Error IO () + createTableMutationTable :: ExceptT LocatedSQ3Error IO () createTableMutationTable = exec_ sql "CREATE TABLE IF NOT EXISTS VersionedTableMutation\ @@ -917,7 +848,7 @@ initSchema sql = \, blockheight UNSIGNED BIGINT NOT NULL\ \, CONSTRAINT mutation_unique UNIQUE(blockheight, tablename));" - createTransactionIndexTable :: ExceptT SQ3.Error IO () + createTransactionIndexTable :: ExceptT LocatedSQ3Error IO () createTransactionIndexTable = do exec_ sql "CREATE TABLE IF NOT EXISTS TransactionIndex \ @@ -935,7 +866,7 @@ getSerialiser = do blockHeight <- view blockHandlerBlockHeight return $ pact5Serialiser version cid blockHeight -getPayloadsAfter :: HasCallStack => SQLiteEnv -> Parent BlockHeight -> ExceptT SQ3.Error IO [Ranked BlockPayloadHash] +getPayloadsAfter :: HasCallStack => SQLiteEnv -> Parent BlockHeight -> ExceptT LocatedSQ3Error IO [Ranked BlockPayloadHash] getPayloadsAfter db parentHeight = do qry db "SELECT blockheight, payloadhash FROM BlockHistory WHERE blockheight > ?" [SInt (fromIntegral @BlockHeight @Int64 (unwrapParent parentHeight))] @@ -947,7 +878,7 @@ getPayloadsAfter db parentHeight = do -- | Get the checkpointer's idea of the earliest block. The block height -- is the height of the block of the block hash. -getEarliestBlock :: HasCallStack => SQLiteEnv -> ExceptT SQ3.Error IO (Maybe RankedBlockHash) +getEarliestBlock :: HasCallStack => SQLiteEnv -> ExceptT LocatedSQ3Error IO (Maybe RankedBlockHash) getEarliestBlock db = do r <- qry db qtext [] [RInt, RBlob] >>= mapM go case r of @@ -961,7 +892,7 @@ getEarliestBlock db = do in return (RankedBlockHash (fromIntegral hgt) hash) go _ = fail "Chainweb.Pact.Backend.RelationalCheckpointer.doGetEarliest: impossible. This is a bug in chainweb-node." -lookupBlockWithHeight :: SQ3.Database -> BlockHeight -> ExceptT SQ3.Error IO (Maybe (Ranked (Parent BlockHash))) +lookupBlockWithHeight :: SQ3.Database -> BlockHeight -> ExceptT LocatedSQ3Error IO (Maybe (Ranked (Parent BlockHash))) lookupBlockWithHeight db bheight = do qry db qtext [SInt $ fromIntegral bheight] [RInt] >>= \case [[SBlob parentHash]] -> return $! Just $! @@ -971,7 +902,7 @@ lookupBlockWithHeight db bheight = do where qtext = "SELECT parenthash FROM BlockHistory WHERE blockheight = ?;" -lookupParentBlockHash :: SQ3.Database -> Parent BlockHash -> ExceptT SQ3.Error IO Bool +lookupParentBlockHash :: SQ3.Database -> Parent BlockHash -> ExceptT LocatedSQ3Error IO Bool lookupParentBlockHash db (Parent parentHash) = do qry db qtext [SBlob (runPutS (encodeBlockHash parentHash))] [RInt] >>= \case [[SInt n]] -> return $! n == 1 @@ -980,11 +911,11 @@ lookupParentBlockHash db (Parent parentHash) = do where qtext = "SELECT COUNT(*) FROM BlockHistory WHERE parenthash = ?;" -lookupBlockByEvalCtx :: SQ3.Database -> EvaluationCtx p -> ExceptT SQ3.Error IO Bool -lookupBlockByEvalCtx db evalCtx = do +lookupRankedBlockHash :: SQ3.Database -> RankedBlockHash -> IO Bool +lookupRankedBlockHash db rankedBHash = throwOnDbError $ do qry db qtext - [ SInt $ fromIntegral (_evaluationCtxCurrentHeight evalCtx) - , SBlob $ runPutS $ encodeBlockHash $ unwrapParent (_evaluationCtxParentHash evalCtx) + [ SInt $ fromIntegral (_rankedBlockHashHeight rankedBHash) + , SBlob $ runPutS $ encodeBlockHash $ _rankedBlockHashHash rankedBHash ] [RInt] >>= \case [[SInt n]] -> return $! n == 1 [_] -> error "lookupBlock: output type mismatch" diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index fc80e528e7..9272df2c7d 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -54,8 +54,6 @@ import "lens" Control.Lens (set, over, (^.), _3, view) import "loglevel" System.LogLevel qualified as LL import "monad-control" Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp) import "optparse-applicative" Options.Applicative qualified as O -import "pact" Pact.Types.SQLite (SType(..), RType(..)) -import "pact" Pact.Types.SQLite qualified as Pact import "rocksdb-haskell-kadena" Database.RocksDB.Types (Options(..), Compression(..)) import "streaming" Streaming qualified as S import "streaming" Streaming.Prelude qualified as S @@ -76,7 +74,7 @@ import Chainweb.Pact.Backend.ChainwebPactDb () import Chainweb.Pact.Backend.PactState import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils -import Chainweb.Payload.PayloadStore (initializePayloadDb, addNewPayload, lookupPayloadWithHeight) +import Chainweb.Payload.PayloadStore (addNewPayload, lookupPayloadWithHeight) import Chainweb.Payload.PayloadStore.RocksDB (newPayloadDb) import Chainweb.Utils (sshow, fromText, toText, int) import Chainweb.Version (ChainId, ChainwebVersion(..), chainIdToText) @@ -224,7 +222,7 @@ compactPactState logger rt targetBlockHeight srcDb targetDb = do -- Note that we can't apply pragmas to the src -- because we can't guarantee it's not being accessed -- by another process. - Pact.runPragmas targetDb fastBulkInsertPragmas + runPragmas targetDb fastBulkInsertPragmas -- Create checkpointer tables on the target createCheckpointerTables targetDb logger @@ -234,7 +232,7 @@ compactPactState logger rt targetBlockHeight srcDb targetDb = do do log LL.Info "Compacting BlockHistory" activeRow <- getBlockHistoryRowAt logger srcDb targetBlockHeight - Pact.exec' targetDb "INSERT INTO BlockHistory VALUES (?1, ?2, ?3)" activeRow + throwOnDbError $ exec' targetDb "INSERT INTO BlockHistory VALUES (?1, ?2, ?3)" activeRow -- Compact VersionedTableMutation -- This is extremely fast and low residency @@ -243,7 +241,7 @@ compactPactState logger rt targetBlockHeight srcDb targetDb = do activeRows <- getVersionedTableMutationRowsAt logger srcDb targetBlockHeight Lite.withStatement targetDb "INSERT INTO VersionedTableMutation VALUES (?1, ?2)" $ \stmt -> do forM_ activeRows $ \row -> do - Pact.bindParams stmt row + throwOnDbError $ bindParams stmt row void $ stepThenReset stmt -- Copy over VersionedTableCreation. Read-only rewind needs to know @@ -256,7 +254,7 @@ compactPactState logger rt targetBlockHeight srcDb targetDb = do throwSqlError $ qryStream srcDb wholeTableQuery [] [RText, RInt] $ \tblRows -> do Lite.withStatement targetDb "INSERT INTO VersionedTableCreation VALUES (?1, ?2)" $ \stmt -> do flip S.mapM_ tblRows $ \row -> do - Pact.bindParams stmt row + throwOnDbError $ bindParams stmt row void $ stepThenReset stmt -- Copy over TransactionIndex. @@ -270,7 +268,7 @@ compactPactState logger rt targetBlockHeight srcDb targetDb = do -- Maybe consider -- https://tableplus.com/blog/2018/07/sqlite-how-to-copy-table-to-another-database.html do - (qry, args) <- + (query, args) <- if rt.keepFullTransactionIndex then do log LL.Info "Copying over entire TransactionIndex table. This could take a while" @@ -281,7 +279,7 @@ compactPactState logger rt targetBlockHeight srcDb targetDb = do let wholeTableQuery = "SELECT txhash, blockheight FROM TransactionIndex WHERE blockheight >= ?1 ORDER BY blockheight" pure (wholeTableQuery, [SInt (int (targetBlockHeight - blockHeightKeepDepth))]) - throwSqlError $ qryStream srcDb qry args [RBlob, RInt] $ \tblRows -> do + throwSqlError $ qryStream srcDb query args [RBlob, RInt] $ \tblRows -> do Lite.withStatement targetDb "INSERT INTO TransactionIndex VALUES (?1, ?2)" $ \stmt -> do -- I experimented a bunch with chunk sizes, to keep transactions -- small. As far as I can tell, there isn't really much @@ -291,14 +289,14 @@ compactPactState logger rt targetBlockHeight srcDb targetDb = do S.chunksOf 10_000 tblRows & S.mapsM_ (\chunk -> do inTx targetDb $ flip S.mapM_ chunk $ \row -> do - Pact.bindParams stmt row + throwOnDbError $ bindParams stmt row void (stepThenReset stmt) ) -- Vacuuming after copying over all of the TransactionIndex data, -- but before creating its indices, makes a big differences in -- memory residency (~0.5G), at the expense of speed (~20s increase) - Pact.exec_ targetDb "VACUUM;" + throwOnDbError $ exec_ targetDb "VACUUM;" -- Create the checkpointer table indices after bulk-inserting into them -- This is faster than creating the indices before @@ -426,7 +424,7 @@ compactTable logger srcDb targetDb tblname endingTxId = do -- Create a temporary index on 'rowkey' for a user table, so that upserts work correctly. inTx targetDb $ do - Pact.exec_ targetDb $ mconcat + throwOnDbError $ exec_ targetDb $ mconcat [ "CREATE UNIQUE INDEX IF NOT EXISTS ", tbl (tblnameUtf8 <> "_rowkey_unique_ix_TEMP"), " ON " , tbl tblnameUtf8, " (rowkey)" ] @@ -466,7 +464,7 @@ compactTable logger srcDb targetDb tblname endingTxId = do inTx targetDb $ flip S.mapM_ chunk $ \row -> do case row of [SText _, SInt _, SBlob _] -> do - Pact.bindParams upsertRow row + throwOnDbError $ bindParams upsertRow row void $ stepThenReset upsertRow _badRowShape -> do exitLog logger "Encountered invalid row shape while compacting" @@ -476,7 +474,7 @@ compactTable logger srcDb targetDb tblname endingTxId = do -- If we were to keep this index around, the node would not be able to operate, since -- we need to update new rows for the same rowkey. inTx targetDb $ do - Pact.exec_ targetDb $ mconcat + throwOnDbError $ exec_ targetDb $ mconcat [ "DROP INDEX IF EXISTS ", tbl (tblnameUtf8 <> "_rowkey_unique_ix_TEMP") ] @@ -496,7 +494,7 @@ createCheckpointerTables db logger = do let log = logFunctionText logger LL.Info log "Creating Checkpointer table BlockHistory" - inTx db $ Pact.exec_ db $ mconcat + inTx db $ throwOnDbError $ exec_ db $ mconcat [ "CREATE TABLE IF NOT EXISTS BlockHistory " , "(blockheight UNSIGNED BIGINT NOT NULL" , ", hash BLOB NOT NULL" @@ -505,7 +503,7 @@ createCheckpointerTables db logger = do ] log "Creating Checkpointer table VersionedTableCreation" - inTx db $ Pact.exec_ db $ mconcat + inTx db $ throwOnDbError $ exec_ db $ mconcat [ "CREATE TABLE IF NOT EXISTS VersionedTableCreation " , "(tablename TEXT NOT NULL" , ", createBlockheight UNSIGNED BIGINT NOT NULL" @@ -513,7 +511,7 @@ createCheckpointerTables db logger = do ] log "Creating Checkpointer table VersionedTableMutation" - inTx db $ Pact.exec_ db $ mconcat + inTx db $ throwOnDbError $ exec_ db $ mconcat [ "CREATE TABLE IF NOT EXISTS VersionedTableMutation " , "(tablename TEXT NOT NULL" , ", blockheight UNSIGNED BIGINT NOT NULL" @@ -521,7 +519,7 @@ createCheckpointerTables db logger = do ] log "Creating Checkpointer table TransactionIndex" - inTx db $ Pact.exec_ db $ mconcat + inTx db $ throwOnDbError $ exec_ db $ mconcat [ "CREATE TABLE IF NOT EXISTS TransactionIndex " , "(txhash BLOB NOT NULL" , ", blockheight UNSIGNED BIGINT NOT NULL" @@ -532,7 +530,7 @@ createCheckpointerTables db logger = do -- Ideally in the future this can be removed. forM_ ["BlockHistory", "VersionedTableCreation", "VersionedTableMutation", "TransactionIndex"] $ \tblname -> do log $ "Deleting from table " <> fromUtf8 tblname - Pact.exec_ db $ "DELETE FROM " <> tbl tblname + throwOnDbError $ exec_ db $ "DELETE FROM " <> tbl tblname -- | Create all the indexes for the checkpointer tables. createCheckpointerIndexes :: (Logger logger) => Database -> logger -> IO () @@ -540,27 +538,27 @@ createCheckpointerIndexes db logger = do let log = logFunctionText logger LL.Info log "Creating BlockHistory index" - inTx db $ Pact.exec_ db + inTx db $ throwOnDbError $ exec_ db "CREATE UNIQUE INDEX IF NOT EXISTS BlockHistory_blockheight_unique_ix ON BlockHistory (blockheight)" log "Creating VersionedTableCreation index" - inTx db $ Pact.exec_ db + inTx db $ throwOnDbError $ exec_ db "CREATE UNIQUE INDEX IF NOT EXISTS VersionedTableCreation_createBlockheight_tablename_unique_ix ON VersionedTableCreation (createBlockheight, tablename)" log "Creating VersionedTableMutation index" - inTx db $ Pact.exec_ db + inTx db $ throwOnDbError $ exec_ db "CREATE UNIQUE INDEX IF NOT EXISTS VersionedTableMutation_blockheight_tablename_unique_ix ON VersionedTableMutation (blockheight, tablename)" log "Creating TransactionIndex indexes" - inTx db $ Pact.exec_ db + inTx db $ throwOnDbError $ exec_ db "CREATE UNIQUE INDEX IF NOT EXISTS TransactionIndex_txhash_unique_ix ON TransactionIndex (txhash)" - inTx db $ Pact.exec_ db + inTx db $ throwOnDbError $ exec_ db "CREATE INDEX IF NOT EXISTS TransactionIndex_blockheight_ix ON TransactionIndex (blockheight)" -- | Create a single user table createUserTable :: Database -> Utf8 -> IO () createUserTable db tblname = do - Pact.exec_ db $ mconcat + throwOnDbError $ exec_ db $ mconcat [ "CREATE TABLE IF NOT EXISTS ", tbl tblname, " " , "(rowid INTEGER PRIMARY KEY AUTOINCREMENT" , ", rowkey TEXT" -- This should be NOT NULL; we need to make a follow-up PR to chainweb-node to update this here and the checkpointer schema @@ -571,17 +569,17 @@ createUserTable db tblname = do -- We have to delete from the table because of the way the test harnesses work. -- Ideally in the future this can be removed. - Pact.exec_ db $ "DELETE FROM " <> tbl tblname + throwOnDbError $ exec_ db $ "DELETE FROM " <> tbl tblname -- | Create the indexes for a single user table createUserTableIndex :: Database -> Utf8 -> IO () createUserTableIndex db tblname = do inTx db $ do - Pact.exec_ db $ mconcat + throwOnDbError $ exec_ db $ mconcat [ "CREATE UNIQUE INDEX IF NOT EXISTS ", tbl (tblname <> "_rowkey_txid_unique_ix"), " ON " , tbl tblname, " (rowkey, txid)" ] - Pact.exec_ db $ mconcat + throwOnDbError $ exec_ db $ mconcat [ "CREATE INDEX IF NOT EXISTS ", tbl (tblname <> "_txid_ix"), " ON " , tbl tblname, " (txid DESC)" ] @@ -593,7 +591,7 @@ getBlockHistoryRowAt :: (Logger logger) -> BlockHeight -> IO [SType] getBlockHistoryRowAt logger db target = do - r <- Pact.qry db "SELECT blockheight, hash, endingtxid FROM BlockHistory WHERE blockheight = ?1" [SInt (int target)] [RInt, RBlob, RInt] + r <- throwOnDbError $ qry db "SELECT blockheight, hash, endingtxid FROM BlockHistory WHERE blockheight = ?1" [SInt (int target)] [RInt, RBlob, RInt] case r of [row@[SInt bh, SBlob _hash, SInt _endingTxId]] -> do unless (target == int bh) $ do @@ -609,7 +607,7 @@ getVersionedTableMutationRowsAt :: (Logger logger) -> BlockHeight -> IO [[SType]] getVersionedTableMutationRowsAt logger db target = do - r <- Pact.qry db "SELECT tablename, blockheight FROM VersionedTableMutation WHERE blockheight = ?1" [SInt (int target)] [RText, RInt] + r <- throwOnDbError $ qry db "SELECT tablename, blockheight FROM VersionedTableMutation WHERE blockheight = ?1" [SInt (int target)] [RText, RInt] forM r $ \case row@[SText _, SInt bh] -> do unless (target == int bh) $ do @@ -689,8 +687,8 @@ throwSqlError ioe = do inTx :: Database -> IO a -> IO a inTx db io = do bracket_ - (Pact.exec_ db "BEGIN;") - (Pact.exec_ db "COMMIT;") + (throwOnDbError $ exec_ db "BEGIN;") + (throwOnDbError $ exec_ db "COMMIT;") io pactDir :: FilePath -> FilePath @@ -731,10 +729,8 @@ compactRocksDb logger cwVersion cids minBlockHeight srcDb targetDb = do let srcPayloads = newPayloadDb srcDb let targetPayloads = newPayloadDb targetDb - -- The target payload db has to be initialised. + -- The target payload db has to be initialised. TODO PP: does it? log LL.Info "Initializing payload db" - initializePayloadDb cwVersion targetPayloads - srcWbhdb <- initWebBlockHeaderDb srcDb cwVersion targetWbhdb <- initWebBlockHeaderDb targetDb cwVersion forM_ cids $ \cid -> do diff --git a/src/Chainweb/Pact/Backend/PactState.hs b/src/Chainweb/Pact/Backend/PactState.hs index 256819a82f..0d01bde6b1 100644 --- a/src/Chainweb/Pact/Backend/PactState.hs +++ b/src/Chainweb/Pact/Backend/PactState.hs @@ -55,12 +55,11 @@ module Chainweb.Pact.Backend.PactState import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.Logger (Logger, addLabel) -import Chainweb.Pact.Backend.Utils (fromUtf8, withSqliteDb) import Chainweb.Utils (T2(..), int) import Chainweb.Version (ChainId, ChainwebVersion, chainIdToText) import Chainweb.Version.Utils (chainIdsAt) import Control.Exception (bracket) -import Control.Monad (forM, forM_, when) +import Control.Monad (forM, when) import Control.Monad.Except (ExceptT(..), runExceptT, throwError) import Control.Monad.IO.Class (MonadIO(liftIO)) import Control.Monad.Trans.Class (lift) @@ -85,11 +84,10 @@ import Database.SQLite3.Direct qualified as Direct import System.Directory (doesFileExist) import System.FilePath (()) -import Pact.Types.SQLite (SType(..), RType(..)) -import Pact.Types.SQLite qualified as Pact import Streaming.Prelude (Stream, Of) import Streaming.Prelude qualified as S import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils hiding (tbl) excludedTables :: [Utf8] excludedTables = checkpointerTables ++ compactionTables @@ -101,14 +99,14 @@ excludedTables = checkpointerTables ++ compactionTables getLatestBlockHeight :: Database -> IO BlockHeight getLatestBlockHeight db = do let qryText = "SELECT MAX(blockheight) FROM BlockHistory" - Pact.qry db qryText [] [RInt] >>= \case + throwOnDbError $ qry db qryText [] [RInt] >>= \case [[SInt bh]] -> pure (BlockHeight (int bh)) _ -> error "getLatestBlockHeight: expected int" getEarliestBlockHeight :: Database -> IO BlockHeight getEarliestBlockHeight db = do let qryText = "SELECT MIN(blockheight) FROM BlockHistory" - Pact.qry db qryText [] [RInt] >>= \case + throwOnDbError $ qry db qryText [] [RInt] >>= \case [[SInt bh]] -> pure (BlockHeight (int bh)) _ -> error "getEarliestBlockHeight: expected int" @@ -117,7 +115,7 @@ getEarliestBlockHeight db = do -- Throws an exception if it doesn't. ensureBlockHeightExists :: Database -> BlockHeight -> IO () ensureBlockHeightExists db bh = do - r <- Pact.qry db "SELECT blockheight FROM BlockHistory WHERE blockheight = ?1" [SInt (fromIntegral bh)] [RInt] + r <- throwOnDbError $ qry db "SELECT blockheight FROM BlockHistory WHERE blockheight = ?1" [SInt (fromIntegral bh)] [RInt] case r of [[SInt rBH]] -> do when (fromIntegral bh /= rBH) $ do @@ -171,7 +169,7 @@ getPactTableNames db = S.each =<< liftIO (do \ type = 'table' \ \AND \ \ name NOT LIKE 'sqlite_%'" - Pact.qry db qryText [] [RText]) + throwOnDbError $ qry db qryText [] [RText]) -- | Get all of the rows for each table. The tables will be appear sorted -- lexicographically by table name. @@ -184,7 +182,7 @@ getPactTables db = do & S.mapM (\tbl -> do let qryText = "SELECT rowkey, rowdata, txid FROM " <> fmtTable tbl - userRows <- liftIO $ Pact.qry db qryText [] [RText, RBlob, RInt] + userRows <- throwOnDbError $ qry db qryText [] [RText, RBlob, RInt] shapedRows <- forM userRows $ \case [SText (Utf8 rowKey), SBlob rowData, SInt txId] -> do pure $ PactRow {..} @@ -227,16 +225,8 @@ qryStream :: () -> IO x qryStream db qryText args returnTypes k = do bracket (SQL.prepareUtf8 db qryText) Direct.finalize $ \stmt -> do - bindParams stmt args + throwOnDbError $ bindParams stmt args k (stepStatement stmt returnTypes) - where - bindParams :: Direct.Statement -> [SType] -> IO () - bindParams s as = forM_ (List.zip [1..] as) $ \(argIndex, arg) -> do - case arg of - SInt a -> Direct.bindInt64 s argIndex a - SDouble a -> Direct.bindDouble s argIndex a - SText a -> Direct.bindText s argIndex a - SBlob a -> Direct.bindBlob s argIndex a -- | Get the latest Pact state (in a ready-to-diff form). getLatestPactStateDiffable :: Database -> Stream (Of TableDiffable) IO () @@ -258,7 +248,7 @@ getEndingTxId :: () -> BlockHeight -> IO Int64 getEndingTxId db bh = do - r <- liftIO $ Pact.qry db + r <- throwOnDbError $ qry db "SELECT endingtxid FROM BlockHistory WHERE blockheight=?" [SInt (int bh)] [RInt] @@ -296,7 +286,7 @@ getLatestPactTableNamesAt :: () getLatestPactTableNamesAt db bh = do tablesCreatedAfter <- liftIO $ do let qryText = "SELECT tablename FROM VersionedTableCreation WHERE createBlockheight > ?1" - rows <- Pact.qry db qryText [SInt (int bh)] [RText] + rows <- throwOnDbError $ qry db qryText [SInt (int bh)] [RText] forM rows $ \case [SText tbl] -> pure tbl _ -> error "getLatestPactStateAt.tablesCreatedAfter: expected text" @@ -404,17 +394,7 @@ data PactRowContents = PactRowContents -- contains the pact db for the given ChainId. doesPactDbExist :: ChainId -> FilePath -> IO Bool doesPactDbExist cid dbDir = do - doesFileExist (chainDbFileName cid dbDir) - --- | Given a pact database directory, return the SQLite --- path chainweb uses for the given ChainId. -chainDbFileName :: ChainId -> FilePath -> FilePath -chainDbFileName cid dbDir = dbDir mconcat - [ "pact-v1-chain-" - , Text.unpack (chainIdToText cid) - , ".sqlite" - ] - + doesFileExist (dbDir chainDbFileName cid) addChainIdLabel :: (Logger logger) => ChainId diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs index 0279d2a4b5..a1550861ae 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs @@ -52,20 +52,6 @@ module Chainweb.Pact.Backend.PactState.GrandHash.Import ) where -import Chainweb.BlockHeader (blockHash) -import Chainweb.BlockHeight (BlockHeight(..)) -import Chainweb.ChainId (ChainId, chainIdToText) -import Chainweb.Logger (Logger, logFunctionText) -import Chainweb.Pact.Backend.Compaction qualified as C -import Chainweb.Pact.Backend.PactState (addChainIdLabel, allChains) -import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..)) -import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot.Mainnet qualified as MainnetSnapshots -import Chainweb.Pact.Backend.PactState.GrandHash.Utils (resolveLatestCutHeaders, resolveCutHeadersAtHeight, computeGrandHashesAt, exitLog, withConnections, chainwebDbFilePath, rocksParser, cwvParser) -import Chainweb.Pact.Backend.Types -import Chainweb.Parent -import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) -import Chainweb.Utils (sshow) -import Chainweb.Version (ChainwebVersion(..)) import Control.Applicative (optional) import Control.Lens ((^?!), ix, view) import Control.Monad (forM_, when) @@ -83,10 +69,21 @@ import Patience.Map qualified as P import System.Directory (copyFile, createDirectoryIfMissing) import System.Environment (setEnv) import System.LogLevel (LogLevel(..)) -import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer -import Chainweb.Pact.Types (BlockCtx(..)) -import Chainweb.PayloadProvider -import Chainweb.Miner.Pact (noMiner) +import qualified Chainweb.Pact.Backend.Utils as PactDb + +import Chainweb.BlockHeader (blockHash, rankedBlockHash) +import Chainweb.BlockHeight (BlockHeight(..)) +import Chainweb.ChainId (ChainId, chainIdToText) +import Chainweb.Logger (Logger, logFunctionText) +import Chainweb.Pact.Backend.Compaction qualified as C +import Chainweb.Pact.Backend.PactState (addChainIdLabel, allChains) +import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..)) +import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot.Mainnet qualified as MainnetSnapshots +import Chainweb.Pact.Backend.PactState.GrandHash.Utils (resolveLatestCutHeaders, resolveCutHeadersAtHeight, computeGrandHashesAt, exitLog, withConnections, chainwebDbFilePath, rocksParser, cwvParser) +import Chainweb.Pact.Backend.Types +import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) +import Chainweb.Utils (sshow) +import Chainweb.Version (ChainwebVersion(..)) -- | Verifies that the hashes and headers match @grands@. -- @@ -189,20 +186,7 @@ pactDropPostVerified logger v srcDir tgtDir snapshotBlockHeight snapshotChainHas let logger' = addChainIdLabel cid logger logFunctionText logger' Info $ "Dropping anything post verified state (BlockHeight " <> sshow snapshotBlockHeight <> ")" - Checkpointer.withCheckpointerResources logger sqliteEnv DoNotPersistIntraBlockWrites v cid $ \cp -> do - let parent = blockHeaderToEvaluationCtx $ Parent $ blockHeader $ snapshotChainHashes ^?! ix cid - let parentBlockCtx = BlockCtx - { _bctxParentCreationTime = _evaluationCtxParentCreationTime parent - , _bctxParentHash = _evaluationCtxParentHash parent - , _bctxParentHeight = _evaluationCtxParentHeight parent - , _bctxChainId = cid - , _bctxChainwebVersion = v - , _bctxMinerReward = _evaluationCtxMinerReward parent - } - - -- Checkpointer.rewindTo cp v cid parentBlockCtx - -- TODO: PP - undefined + PactDb.rewindDbTo v cid sqliteEnv $ view rankedBlockHash $ blockHeader $ snapshotChainHashes ^?! ix cid data PactImportConfig = PactImportConfig { sourcePactDir :: FilePath diff --git a/src/Chainweb/Pact/Backend/Types.hs b/src/Chainweb/Pact/Backend/Types.hs index f41032c92e..3dd1da0031 100644 --- a/src/Chainweb/Pact/Backend/Types.hs +++ b/src/Chainweb/Pact/Backend/Types.hs @@ -23,7 +23,6 @@ module Chainweb.Pact.Backend.Types ( SQLiteEnv - , IntraBlockPersistence(..) , BlockHandle(..) , blockHandleTxId , blockHandlePending @@ -33,28 +32,26 @@ module Chainweb.Pact.Backend.Types , pendingWrites , pendingSuccessfulTxs , pendingTableCreation - , pendingTxLogMap , SQLiteRowDelta(..) , Historical(..) + , throwIfNoHistory , _Historical , _NoHistory , ChainwebPactDb(..) , HeaderOracle(..) ) where +import Control.Exception.Safe (MonadThrow) import Control.Lens import Chainweb.Version import Database.SQLite3.Direct (Database) import Data.ByteString (ByteString) import Data.Text (Text) -import Data.DList (DList) -import Data.Map (Map) import Data.HashSet (HashSet) import Control.DeepSeq (NFData) import GHC.Generics +import GHC.Stack -import qualified Pact.Types.Persistence as Pact4 -import qualified Pact.Types.Names as Pact4 import Chainweb.BlockHash import Pact.Core.Command.Types import qualified Pact.Core.Persistence as Pact @@ -68,6 +65,7 @@ import Chainweb.BlockHeight import qualified Chainweb.Pact.Backend.InMemDb as InMemDb import Chainweb.Parent import Chainweb.Utils +import Chainweb.BlockPayloadHash data HeaderOracle = HeaderOracle -- this hash must always have a child @@ -78,11 +76,6 @@ data HeaderOracle = HeaderOracle instance HasChainId HeaderOracle where _chainId oracle = oracle.chain --- | Whether we write rows to the database that were already overwritten --- in the same block. -data IntraBlockPersistence = PersistIntraBlockWrites | DoNotPersistIntraBlockWrites - deriving (Eq, Ord, Show) - -- | The Pact database as it's provided by the checkpointer. data ChainwebPactDb = ChainwebPactDb { doChainwebPactDbTransaction @@ -98,7 +91,7 @@ data ChainwebPactDb = ChainwebPactDb -- you made to the PactDb. -- Note also that this function handles registering -- transactions as completed, if you pass it a RequestKey. - , lookupPactTransactions :: Vector RequestKey -> IO (HashMap RequestKey (T2 BlockHeight BlockHash)) + , lookupPactTransactions :: Vector RequestKey -> IO (HashMap RequestKey (T3 BlockHeight BlockPayloadHash BlockHash)) -- ^ Used to implement transaction polling. } @@ -111,7 +104,7 @@ type SQLiteEnv = Database -- data SQLiteRowDelta = SQLiteRowDelta { _deltaTableName :: !Text - , _deltaTxId :: {-# UNPACK #-} !Pact4.TxId + , _deltaTxId :: {-# UNPACK #-} !Pact.TxId , _deltaRowKey :: !ByteString , _deltaData :: !ByteString } deriving (Show, Generic, Eq) @@ -123,10 +116,6 @@ instance Ord SQLiteRowDelta where bb = (_deltaTableName b, _deltaRowKey b, _deltaTxId b) {-# INLINE compare #-} --- | A map from table name to a list of 'TxLog' entries. This is maintained in --- 'BlockState' and is cleared upon pact transaction commit. -type TxLogMap = Map Pact4.TableName (DList Pact4.TxLogJson) - -- | Between a @restore..save@ bracket, we also need to record which tables -- were created during this block (so the necessary @CREATE TABLE@ statements -- can be performed upon block save). @@ -135,14 +124,6 @@ type SQLitePendingTableCreations = HashSet Text -- | Pact transaction hashes resolved during this block. type SQLitePendingSuccessfulTxs = HashSet ByteString --- Note [TxLogs in SQLitePendingData] --- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --- We should really not store TxLogs in SQLitePendingData, --- because this data structure is specifically for things that --- can exist both for the whole block and for specific transactions, --- and txlogs only exist on the transaction level. --- We don't do this in Pact 5 at all. - -- | A collection of pending mutations to the pact db. We maintain two of -- these; one for the block as a whole, and one for any pending pact -- transaction. Upon pact transaction commit, the two 'SQLitePendingData' @@ -150,14 +131,12 @@ type SQLitePendingSuccessfulTxs = HashSet ByteString data SQLitePendingData = SQLitePendingData { _pendingTableCreation :: !SQLitePendingTableCreations , _pendingWrites :: !InMemDb.Store - -- See Note [TxLogs in SQLitePendingData] - , _pendingTxLogMap :: !TxLogMap , _pendingSuccessfulTxs :: !SQLitePendingSuccessfulTxs } deriving (Eq, Show) emptySQLitePendingData :: SQLitePendingData -emptySQLitePendingData = SQLitePendingData mempty InMemDb.empty mempty mempty +emptySQLitePendingData = SQLitePendingData mempty InMemDb.empty mempty data BlockHandle = BlockHandle { _blockHandleTxId :: !Pact.TxId @@ -176,6 +155,10 @@ data Historical a deriving stock (Eq, Foldable, Functor, Generic, Traversable, Show) deriving anyclass NFData +throwIfNoHistory :: (HasCallStack, MonadThrow m) => Historical a -> m a +throwIfNoHistory NoHistory = error "missing history" +throwIfNoHistory (Historical a) = return a + makePrisms ''Historical makeLenses ''BlockHandle makeLenses ''SQLitePendingData diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 3972fbb567..647c3a8cbf 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -14,6 +14,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE DataKinds #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module: Chainweb.Pact.ChainwebPactDb @@ -35,7 +36,6 @@ module Chainweb.Pact.Backend.Utils , rewindDbToBlock , rewindDbToGenesis , getEndTxId - , getEndTxId' -- * Savepoints , withSavepoint , beginSavepoint @@ -50,6 +50,7 @@ module Chainweb.Pact.Backend.Utils , convSavepointName -- * SQLite runners , withSqliteDb + , withReadSqliteDb , startSqliteDb , startReadSqliteDb , stopSqliteDb @@ -58,11 +59,24 @@ module Chainweb.Pact.Backend.Utils , closeSQLiteConnection , withTempSQLiteConnection , withInMemSQLiteConnection - -- * SQLite Pragmas + -- * SQLite , chainwebPragmas + , LocatedSQ3Error(..) + , execMulti + , exec + , exec' + , exec_ + , Pragma(..) + , runPragmas + , qry + , qry_ + , bindParams + , SType(..) + , RType(..) + , throwOnDbError + , locateSQ3Error ) where -import Control.Exception (evaluate) import Control.Exception.Safe import Control.Monad import Control.Monad.State.Strict @@ -96,7 +110,6 @@ import Chainweb.Utils import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight -import Chainweb.Parent import Database.SQLite3.Direct hiding (open2) import GHC.Stack import qualified Data.ByteString.Short as SB @@ -111,6 +124,13 @@ import qualified Data.HashSet as HashSet import Data.Text (Text) import qualified Pact.Core.Persistence as Pact import Control.Monad.Catch (ExitCase(..)) +import Control.Monad.Except +import Data.Int +import Control.Lens +import qualified Pact.JSON.Encode as J +import Data.Aeson (FromJSON) +import Chainweb.Ranked +import Chainweb.Parent -- -------------------------------------------------------------------------- -- -- SQ3.Utf8 Encodings @@ -144,13 +164,13 @@ withSavepoint db name action = fmap fst $ generalBracket _ -> abortSavepoint db name ) $ \_ -> action -beginSavepoint :: SQLiteEnv -> SavepointName -> IO () +beginSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO () beginSavepoint db name = - Pact4.exec_ db $ "SAVEPOINT [" <> convSavepointName name <> "];" + throwOnDbError $ exec_ db $ "SAVEPOINT [" <> convSavepointName name <> "];" -commitSavepoint :: SQLiteEnv -> SavepointName -> IO () +commitSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO () commitSavepoint db name = - Pact4.exec_ db $ "RELEASE SAVEPOINT [" <> convSavepointName name <> "];" + throwOnDbError $ exec_ db $ "RELEASE SAVEPOINT [" <> convSavepointName name <> "];" convSavepointName :: SavepointName -> SQ3.Utf8 convSavepointName = toUtf8 . toText @@ -165,9 +185,9 @@ convSavepointName = toUtf8 . toText -- Cf. for details about -- savepoints. -- -rollbackSavepoint :: SQLiteEnv -> SavepointName -> IO () +rollbackSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO () rollbackSavepoint db name = - Pact4.exec_ db $ "ROLLBACK TRANSACTION TO SAVEPOINT [" <> convSavepointName name <> "];" + throwOnDbError $ exec_ db $ "ROLLBACK TRANSACTION TO SAVEPOINT [" <> convSavepointName name <> "];" -- | @abortSavepoint n@ rolls back all database updates since the most recent -- savepoint with the name @n@ and removes it from the savepoint stack. @@ -266,6 +286,17 @@ startReadSqliteDb cid logger dbDir = do where sqliteFile = dbDir chainDbFileName cid +withReadSqliteDb + :: Logger logger + => ChainId + -> logger + -> FilePath + -> (SQLiteEnv -> IO a) + -> IO a +withReadSqliteDb cid logger dbDir = bracket + (startReadSqliteDb cid logger dbDir) + stopSqliteDb + chainDbFileName :: ChainId -> FilePath chainDbFileName cid = fold [ "pact-v1-chain-" @@ -331,62 +362,62 @@ tbl t@(Utf8 b) | B8.elem ']' b = error $ "Chainweb.Pact.Backend.ChainwebPactDb: Code invariant violation. Illegal SQL table name " <> sshow b <> ". Please report this as a bug." | otherwise = "[" <> t <> "]" -doLookupSuccessful :: Database -> BlockHeight -> V.Vector SB.ShortByteString -> IO (HashMap.HashMap SB.ShortByteString (T2 BlockHeight BlockHash)) -doLookupSuccessful db curHeight hashes = do +doLookupSuccessful :: Database -> BlockHeight -> V.Vector SB.ShortByteString -> IO (HashMap.HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash)) +doLookupSuccessful db curHeight hashes = throwOnDbError $ do fmap buildResultMap $ do -- swizzle results of query into a HashMap let hss = V.toList hashes params = BS.intercalate "," (map (const "?") hss) qtext = Utf8 $ BS.intercalate " " - [ "SELECT blockheight, hash, txhash" + [ "SELECT blockheight, payloadhash, hash, txhash" , "FROM TransactionIndex" , "INNER JOIN BlockHistory USING (blockheight)" , "WHERE txhash IN (" <> params <> ")" <> " AND blockheight < ?;" ] qvals -- match query params above. first, hashes - = map (\h -> Pact4.SBlob $ SB.fromShort h) hss + = map (\h -> SBlob $ SB.fromShort h) hss -- then, the block height; we don't want to see txs from the -- current block in the db, because they'd show up in pending data - ++ [Pact4.SInt $ fromIntegral curHeight] + ++ [SInt $ fromIntegral curHeight] - Pact4.qry db qtext qvals [Pact4.RInt, Pact4.RBlob, Pact4.RBlob] >>= mapM go + qry db qtext qvals [RInt, RBlob, RBlob, RBlob] >>= mapM go where -- NOTE: it's useful to keep the types of 'go' and 'buildResultMap' in sync -- for readability but also to ensure the compiler and reader infer the -- right result types from the db query. - buildResultMap :: [T3 SB.ShortByteString BlockHeight BlockHash] -> HashMap.HashMap SB.ShortByteString (T2 BlockHeight BlockHash) + buildResultMap :: [T4 SB.ShortByteString BlockHeight BlockPayloadHash BlockHash] -> HashMap.HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash) buildResultMap xs = HashMap.fromList $ - map (\(T3 txhash blockheight blockhash) -> (txhash, T2 blockheight blockhash)) xs + map (\(T4 txhash blockheight payloadhash blockhash) -> (txhash, T3 blockheight payloadhash blockhash)) xs - go :: [Pact4.SType] -> IO (T3 SB.ShortByteString BlockHeight BlockHash) - go (Pact4.SInt blockheight:Pact4.SBlob blockhash:Pact4.SBlob txhash:_) = do + go :: [SType] -> ExceptT LocatedSQ3Error IO (T4 SB.ShortByteString BlockHeight BlockPayloadHash BlockHash) + go (SInt blockheight:SBlob payloadhash:SBlob blockhash:SBlob txhash:_) = do !blockhash' <- either fail return $ runGetEitherS decodeBlockHash blockhash + !payloadhash' <- either fail return $ runGetEitherS decodeBlockPayloadHash payloadhash let !txhash' = SB.toShort txhash - return $! T3 txhash' (fromIntegral blockheight) blockhash' + return $! T4 txhash' (fromIntegral blockheight) payloadhash' blockhash' go _ = fail "impossible" -getEndTxId :: ChainwebVersion -> ChainId -> Text -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) -getEndTxId v cid msg sql pc +getEndTxId :: HasCallStack => ChainwebVersion -> ChainId -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) +getEndTxId v cid sql pc | isGenesisBlockHeader' v cid (_rankedBlockHashHash <$> pc) = return (Historical (Pact.TxId 0)) | otherwise = - getEndTxId' msg sql pc + getEndTxId' sql pc -getEndTxId' :: Text -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) -getEndTxId' msg sql (Parent rbh) = do - r <- Pact4.qry sql +getEndTxId' :: HasCallStack => SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) +getEndTxId' sql (Parent rbh) = throwOnDbError $ do + r <- qry sql "SELECT endingtxid FROM BlockHistory WHERE blockheight = ? and hash = ?;" - [ Pact4.SInt $ fromIntegral $ _rankedBlockHashHeight rbh - , Pact4.SBlob $ runPutS (encodeBlockHash $ _rankedBlockHashHash rbh) + [ SInt $ fromIntegral $ _rankedBlockHashHeight rbh + , SBlob $ runPutS (encodeBlockHash $ _rankedBlockHashHash rbh) ] - [Pact4.RInt] + [RInt] case r of - [[Pact4.SInt tid]] -> return $ Historical (Pact.TxId (fromIntegral tid)) + [[SInt tid]] -> return $ Historical (Pact.TxId (fromIntegral tid)) [] -> return NoHistory - _ -> error $ T.unpack msg <> ".getEndTxId: expected single-row int result, got " <> sshow r - + _ -> error $ "getEndTxId: expected single-row int result, got " <> sshow r -- | Delete any state from the database newer than the input parent header. -- Returns the ending txid of the input parent header. @@ -401,7 +432,7 @@ rewindDbTo v cid db pc rewindDbToGenesis db return (Pact.TxId 0) | otherwise = do - !historicalEndingTxId <- getEndTxId v cid "rewindDbToBlock" db (Parent pc) + !historicalEndingTxId <- getEndTxId v cid db (Parent pc) endingTxId <- case historicalEndingTxId of NoHistory -> error @@ -409,96 +440,183 @@ rewindDbTo v cid db pc <> sshow pc Historical endingTxId -> return endingTxId - rewindDbToBlock db (_rankedBlockHashHeight <$> Parent pc) endingTxId + rewindDbToBlock db (rank pc) endingTxId return endingTxId -- rewind before genesis, delete all user tables and all rows in all tables rewindDbToGenesis :: SQLiteEnv -> IO () -rewindDbToGenesis db = do - Pact4.exec_ db "DELETE FROM BlockHistory;" - Pact4.exec_ db "DELETE FROM [SYS:KeySets];" - Pact4.exec_ db "DELETE FROM [SYS:Modules];" - Pact4.exec_ db "DELETE FROM [SYS:Namespaces];" - Pact4.exec_ db "DELETE FROM [SYS:Pacts];" - Pact4.exec_ db "DELETE FROM [SYS:ModuleSources];" - tblNames <- Pact4.qry_ db "SELECT tablename FROM VersionedTableCreation;" [Pact4.RText] +rewindDbToGenesis db = throwOnDbError $ do + exec_ db "DELETE FROM BlockHistory;" + exec_ db "DELETE FROM [SYS:KeySets];" + exec_ db "DELETE FROM [SYS:Modules];" + exec_ db "DELETE FROM [SYS:Namespaces];" + exec_ db "DELETE FROM [SYS:Pacts];" + exec_ db "DELETE FROM [SYS:ModuleSources];" + tblNames <- liftIO $ Pact4.qry_ db "SELECT tablename FROM VersionedTableCreation;" [Pact4.RText] forM_ tblNames $ \t -> case t of - [Pact4.SText tn] -> Pact4.exec_ db ("DROP TABLE [" <> tn <> "];") + [Pact4.SText tn] -> exec_ db ("DROP TABLE [" <> tn <> "];") _ -> error "Something went wrong when resetting tables." - Pact4.exec_ db "DELETE FROM VersionedTableCreation;" - Pact4.exec_ db "DELETE FROM VersionedTableMutation;" - Pact4.exec_ db "DELETE FROM TransactionIndex;" + exec_ db "DELETE FROM VersionedTableCreation;" + exec_ db "DELETE FROM VersionedTableMutation;" + exec_ db "DELETE FROM TransactionIndex;" -- | Rewind the database to a particular block, given the end tx id of that -- block. rewindDbToBlock :: Database - -> Parent BlockHeight + -> BlockHeight -> Pact.TxId -> IO () -rewindDbToBlock db (Parent bh) endingTxId = do +rewindDbToBlock db bh endingTxId = throwOnDbError $ do tableMaintenanceRowsVersionedSystemTables droppedtbls <- dropTablesAtRewind vacuumTablesAtRewind droppedtbls deleteHistory clearTxIndex where - dropTablesAtRewind :: IO (HashSet BS.ByteString) + dropTablesAtRewind :: ExceptT LocatedSQ3Error IO (HashSet BS.ByteString) dropTablesAtRewind = do - toDropTblNames <- Pact4.qry db findTablesToDropStmt - [Pact4.SInt (fromIntegral bh)] [Pact4.RText] + toDropTblNames <- qry db findTablesToDropStmt + [SInt (fromIntegral bh)] [RText] tbls <- fmap HashSet.fromList . forM toDropTblNames $ \case - [Pact4.SText tblname@(Utf8 tn)] -> do - Pact4.exec_ db $ "DROP TABLE IF EXISTS " <> tbl tblname + [SText tblname@(Utf8 tn)] -> do + exec_ db $ "DROP TABLE IF EXISTS " <> tbl tblname return tn _ -> error rewindmsg - Pact4.exec' db + exec' db "DELETE FROM VersionedTableCreation WHERE createBlockheight > ?" - [Pact4.SInt (fromIntegral bh)] + [SInt (fromIntegral bh)] return tbls findTablesToDropStmt = "SELECT tablename FROM VersionedTableCreation WHERE createBlockheight > ?;" rewindmsg = "rewindBlock: dropTablesAtRewind: Couldn't resolve the name of the table to drop." - deleteHistory :: IO () + deleteHistory :: ExceptT LocatedSQ3Error IO () deleteHistory = - Pact4.exec' db "DELETE FROM BlockHistory WHERE blockheight > ?" - [Pact4.SInt (fromIntegral bh)] + exec' db "DELETE FROM BlockHistory WHERE blockheight > ?" + [SInt (fromIntegral bh)] - vacuumTablesAtRewind :: HashSet BS.ByteString -> IO () + vacuumTablesAtRewind :: HashSet BS.ByteString -> ExceptT LocatedSQ3Error IO () vacuumTablesAtRewind droppedtbls = do let processMutatedTables ms = fmap HashSet.fromList . forM ms $ \case - [Pact4.SText (Utf8 tn)] -> return tn + [SText (Utf8 tn)] -> return tn _ -> error "rewindBlock: vacuumTablesAtRewind: Couldn't resolve the name \ \of the table to possibly vacuum." - mutatedTables <- Pact4.qry db + mutatedTables <- qry db "SELECT DISTINCT tablename FROM VersionedTableMutation WHERE blockheight > ?;" - [Pact4.SInt (fromIntegral bh)] - [Pact4.RText] + [SInt (fromIntegral bh)] + [RText] >>= processMutatedTables let toVacuumTblNames = HashSet.difference mutatedTables droppedtbls forM_ toVacuumTblNames $ \tblname -> - Pact4.exec' db ("DELETE FROM " <> tbl (Utf8 tblname) <> " WHERE txid >= ?") - [Pact4.SInt $! fromIntegral $ Pact._txId endingTxId] - Pact4.exec' db "DELETE FROM VersionedTableMutation WHERE blockheight > ?;" - [Pact4.SInt (fromIntegral bh)] + exec' db ("DELETE FROM " <> tbl (Utf8 tblname) <> " WHERE txid >= ?") + [SInt $! fromIntegral $ Pact._txId endingTxId] + exec' db "DELETE FROM VersionedTableMutation WHERE blockheight > ?;" + [SInt (fromIntegral bh)] - tableMaintenanceRowsVersionedSystemTables :: IO () + tableMaintenanceRowsVersionedSystemTables :: ExceptT LocatedSQ3Error IO () tableMaintenanceRowsVersionedSystemTables = do - Pact4.exec' db "DELETE FROM [SYS:KeySets] WHERE txid >= ?" tx - Pact4.exec' db "DELETE FROM [SYS:Modules] WHERE txid >= ?" tx - Pact4.exec' db "DELETE FROM [SYS:Namespaces] WHERE txid >= ?" tx - Pact4.exec' db "DELETE FROM [SYS:Pacts] WHERE txid >= ?" tx - Pact4.exec' db "DELETE FROM [SYS:ModuleSources] WHERE txid >= ?" tx + exec' db "DELETE FROM [SYS:KeySets] WHERE txid >= ?" tx + exec' db "DELETE FROM [SYS:Modules] WHERE txid >= ?" tx + exec' db "DELETE FROM [SYS:Namespaces] WHERE txid >= ?" tx + exec' db "DELETE FROM [SYS:Pacts] WHERE txid >= ?" tx + exec' db "DELETE FROM [SYS:ModuleSources] WHERE txid >= ?" tx where - tx = [Pact4.SInt $! fromIntegral $ Pact._txId endingTxId] + tx = [SInt $! fromIntegral $ Pact._txId endingTxId] -- | Delete all future transactions from the index - clearTxIndex :: IO () + clearTxIndex :: ExceptT LocatedSQ3Error IO () clearTxIndex = - Pact4.exec' db "DELETE FROM TransactionIndex WHERE blockheight > ?;" - [ Pact4.SInt (fromIntegral bh) ] + exec' db "DELETE FROM TransactionIndex WHERE blockheight > ?;" + [ SInt (fromIntegral bh) ] + +data LocatedSQ3Error = LocatedSQ3Error !CallStack !SQ3.Error +instance Show LocatedSQ3Error where + show (LocatedSQ3Error cs e) = + sshow e <> "\n\n" <> + prettyCallStack cs + +throwOnDbError :: (HasCallStack, MonadThrow m) => ExceptT LocatedSQ3Error m a -> m a +throwOnDbError act = runExceptT act >>= either (error . sshow) return + +locateSQ3Error :: (HasCallStack, Functor m) => m (Either SQ3.Error a) -> ExceptT LocatedSQ3Error m a +locateSQ3Error = ExceptT . fmap (_Left %~ LocatedSQ3Error callStack) + +-- | Statement input types +data SType = SInt Int64 | SDouble Double | SText SQ3.Utf8 | SBlob BS.ByteString deriving (Eq,Show) +-- | Result types +data RType = RInt | RDouble | RText | RBlob deriving (Eq,Show) + +bindParams :: HasCallStack => SQ3.Statement -> [SType] -> ExceptT LocatedSQ3Error IO () +bindParams stmt as = + forM_ (zip as [1..]) $ \(a,i) -> locateSQ3Error $ + case a of + SInt n -> SQ3.bindInt64 stmt i n + SDouble n -> SQ3.bindDouble stmt i n + SText n -> SQ3.bindText stmt i n + SBlob n -> SQ3.bindBlob stmt i n + +prepStmt :: HasCallStack => SQ3.Database -> SQ3.Utf8 -> ExceptT LocatedSQ3Error IO SQ3.Statement +prepStmt c q = do + r <- locateSQ3Error $ SQ3.prepare c q + case r of + Nothing -> error "No SQL statements in prepared statement" + Just s -> return s + +execMulti :: HasCallStack => Traversable t => SQ3.Database -> SQ3.Utf8 -> t [SType] -> ExceptT LocatedSQ3Error IO () +execMulti db q rows = bracket (prepStmt db q) (liftIO . SQ3.finalize) $ \stmt -> do + forM_ rows $ \row -> do + locateSQ3Error $ SQ3.reset stmt + liftIO $ SQ3.clearBindings stmt + bindParams stmt row + locateSQ3Error $ SQ3.step stmt + +-- | Prepare/execute query with params +qry :: HasCallStack => SQ3.Database -> SQ3.Utf8 -> [SType] -> [RType] -> ExceptT LocatedSQ3Error IO [[SType]] +qry e q as rts = bracket (prepStmt e q) (locateSQ3Error . SQ3.finalize) $ \stmt -> do + bindParams stmt as + reverse <$> stepStmt stmt rts + +-- | Prepare/execute query with no params +qry_ :: HasCallStack => SQ3.Database -> SQ3.Utf8 -> [RType] -> ExceptT LocatedSQ3Error IO [[SType]] +qry_ e q rts = bracket (prepStmt e q) (locateSQ3Error . finalize) $ \stmt -> + reverse <$> stepStmt stmt rts + +stepStmt :: HasCallStack => SQ3.Statement -> [RType] -> ExceptT LocatedSQ3Error IO [[SType]] +stepStmt stmt rts = do + let acc rs SQ3.Done = return rs + acc rs SQ3.Row = do + as <- lift $ forM (zip rts [0..]) $ \(rt,ci) -> + case rt of + RInt -> SInt <$> SQ3.columnInt64 stmt ci + RDouble -> SDouble <$> SQ3.columnDouble stmt ci + RText -> SText <$> SQ3.columnText stmt ci + RBlob -> SBlob <$> SQ3.columnBlob stmt ci + sr <- locateSQ3Error $ SQ3.step stmt + acc (as:rs) sr + sr <- locateSQ3Error $ SQ3.step stmt + acc [] sr + +-- | Prepare/exec statement with no params +exec_ :: HasCallStack => SQ3.Database -> SQ3.Utf8 -> ExceptT LocatedSQ3Error IO () +exec_ e q = locateSQ3Error $ over _Left fst <$> SQ3.exec e q + +-- | Prepare/exec statement with params +exec' :: HasCallStack => SQ3.Database -> SQ3.Utf8 -> [SType] -> ExceptT LocatedSQ3Error IO () +exec' e q as = bracket (prepStmt e q) (locateSQ3Error . SQ3.finalize) $ \stmt -> do + bindParams stmt as + void $ locateSQ3Error (SQ3.step stmt) + +newtype Pragma = Pragma String + deriving (Eq,Show) + deriving newtype (FromJSON, IsString) + +instance J.Encode Pragma where + build (Pragma s) = J.string s + +runPragmas :: Database -> [Pragma] -> IO () +runPragmas c = throwOnDbError . mapM_ (\(Pragma s) -> exec_ c (fromString ("PRAGMA " ++ s))) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index c58655dba9..2a910873f0 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -32,54 +32,39 @@ module Chainweb.Pact.PactService -- , execContinueBlock , syncToFork -- , execTransactions - -- , execLocal + , execLocal , execLookupPactTxs - -- , execPreInsertCheckReq - -- , execBlockTxHistory - -- , execHistoricalLookup + , execPreInsertCheckReq , execReadOnlyReplay - -- , execSyncToBlock , withPactService - -- , execNewGenesisBlock + , execNewGenesisBlock ) where -import Control.Concurrent hiding (throwTo) import Control.Concurrent.Async import Control.Concurrent.STM -import Control.Exception (AsyncException(ThreadKilled)) import Control.Exception.Safe import Control.Lens hiding ((:>)) import Control.Monad import Control.Monad.Reader -import Control.Monad.State.Strict import Data.Either -import Data.Foldable (toList, traverse_) -import Data.IORef +import Data.Foldable (traverse_) import qualified Data.HashMap.Strict as HM -import Data.LogMessage import Data.Maybe import Data.Monoid +import Data.Pool (Pool) import Data.Text (Text) import qualified Data.Text as Text import Data.Vector (Vector) import qualified Data.Vector as V -import qualified Data.UUID as UUID -import qualified Data.UUID.V4 as UUID import System.IO import System.LogLevel import Prelude hiding (lookup) -import qualified Streaming as Stream -import qualified Streaming.Prelude as Stream - -import Pact.Interpreter(PactDbEnv(..)) import qualified Pact.JSON.Encode as J -import qualified Pact.Core.Builtin as Pact -import qualified Pact.Core.Persistence as Pact import qualified Pact.Core.Gas as Pact import qualified Pact.Core.Info as Pact @@ -88,7 +73,6 @@ import qualified Chainweb.Pact.Validations as Pact import Chainweb.BlockHash import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB import Chainweb.BlockHeight import Chainweb.ChainId import Chainweb.Logger @@ -104,83 +88,73 @@ import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time import qualified Chainweb.Pact.Transaction as Pact -import Chainweb.TreeDB hiding (rank) import Chainweb.Utils hiding (check) import Chainweb.Version -import Chainweb.Version.Guards -import Utils.Logging.Trace import Chainweb.Counter -import Data.Time.Clock -import Text.Printf -import Data.Time.Format.ISO8601 -import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact import Pact.Core.Command.Types qualified as Pact import Pact.Core.Hash qualified as Pact import Data.ByteString.Short qualified as SB import Data.Coerce (coerce) import Data.Void -import Chainweb.Pact.Types qualified as Pact import Chainweb.Pact.PactService.ExecBlock qualified as Pact import qualified Pact.Core.Evaluate as Pact -import qualified Pact.Core.Names as Pact -import Data.Functor.Product -import qualified Chainweb.Pact.TransactionExec as Pact -import qualified Chainweb.Pact.Transaction as Pact import Control.Monad.Except -import qualified Chainweb.Pact.NoCoinbase as Pact -import qualified Control.Parallel.Strategies as Strategies -import qualified Chainweb.Pact.Validations as Pact import qualified Pact.Core.Errors as Pact import Chainweb.Pact.Backend.Types import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer import qualified Pact.Core.StableEncoding as Pact import Control.Monad.Cont (evalContT) import qualified Data.List.NonEmpty as NonEmpty -import Chainweb.PayloadProvider.Pact.Genesis (genesisPayload) import Chainweb.PayloadProvider -import Data.Function import Chainweb.Storage.Table import qualified Chainweb.Storage.Table.Map as MapTable import Chainweb.PayloadProvider.P2P import P2P.TaskQueue (Priority(..)) import qualified Network.HTTP.Client as HTTP import qualified Chainweb.PayloadProvider.P2P.RestAPI.Client as Rest -import Chainweb.MinerReward -import qualified Data.List.NonEmpty as NEL import Chainweb.Pact.Backend.Utils (withSavepoint, SavepointName (..)) import qualified Data.DList as DList import Chainweb.Ranked -import Chainweb.Pact.Types (blockCtxOfEvaluationCtx) import qualified Chainweb.Pact.Backend.ChainwebPactDb as ChainwebPactDb +import qualified Pact.Core.ChainData as Pact +import Control.Monad.State.Strict +import GHC.Stack (HasCallStack) +import qualified Data.Pool as Pool +import qualified Data.List.NonEmpty as NEL +import Chainweb.Pact.PactService.Checkpointer (mkFakeNewBlockCtx) +import qualified Control.Parallel.Strategies as Strategies +import qualified Chainweb.Pact.NoCoinbase as Pact withPactService :: (Logger logger, CanReadablePayloadCas tbl) - => HTTP.Manager - -> ChainwebVersion + => ChainwebVersion -> ChainId + -> Maybe HTTP.Manager -> MemPoolAccess -> logger -> Maybe (Counter "txFailures") -> PayloadDb tbl + -> Pool SQLiteEnv -> SQLiteEnv -> PactServiceConfig - -> PactServiceM logger tbl a + -> (ServiceEnv tbl -> IO a) -> IO a -withPactService http ver cid memPoolAccess chainwebLogger txFailuresCounter pdb sqlenv config act = do +withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config act = do SomeChainwebVersionT @v _ <- pure $ someChainwebVersionVal ver SomeChainIdT @c _ <- pure $ someChainIdVal cid let payloadClient = Rest.payloadClient @v @c @'PactProvider payloadStore <- newPayloadStore http (logFunction chainwebLogger) pdb payloadClient miningPayloadVar <- newEmptyTMVarIO - ChainwebPactDb.initSchema sqlenv + ChainwebPactDb.initSchema readWriteSqlenv candidatePdb <- MapTable.emptyTable - let !pse = PactServiceEnv + let !pse = ServiceEnv { _psVersion = ver , _psChainId = cid - , _psLogger = pactServiceLogger - , _psGasLogger = gasLogger <$ guard (_pactLogGas config) - , _psReadWriteSql = sqlenv + -- TODO: PPgaslog + , _psGasLogger = undefined <$ guard (_pactLogGas config) + , _psReadSqlPool = readSqlPool + , _psReadWriteSql = readWriteSqlenv , _psPdb = payloadStore , _psCandidatePdb = candidatePdb , _psMempoolAccess = memPoolAccess @@ -192,526 +166,374 @@ withPactService http ver cid memPoolAccess chainwebLogger txFailuresCounter pdb , _psMiner = _pactMiner config , _psNewBlockGasLimit = _pactNewBlockGasLimit config , _psMiningPayloadVar = miningPayloadVar + , _psGenesisPayload = _pactGenesisPayload config } - let run = runPactServiceM pse $ do - -- TODO: PP - -- when (_pactFullHistoryRequired config) $ do - -- mEarliestBlock <- Checkpointer.getEarliestBlock - -- case mEarliestBlock of - -- Nothing -> do - -- pure () - -- Just (earliestBlockHeight, _) -> do - -- let gHeight = genesisHeight ver cid - -- when (gHeight /= earliestBlockHeight) $ do - -- let msg = J.object - -- [ "details" J..= J.object - -- [ "earliest-block-height" J..= J.number (fromIntegral earliestBlockHeight) - -- , "genesis-height" J..= J.number (fromIntegral gHeight) - -- ] - -- , "message" J..= J.text "Your node has been configured\ - -- \ to require the full Pact history; however, the full\ - -- \ history is not available. Perhaps you have compacted\ - -- \ your Pact state?" - -- ] - -- logError_ chainwebLogger (J.encodeText msg) - -- throwM FullHistoryRequired - -- { _earliestBlockHeight = earliestBlockHeight - -- , _genesisHeight = gHeight - -- } - -- If the latest header that is stored in the checkpointer was on an - -- orphaned fork, there is no way to recover it in the call of - -- 'initalPayloadState.readContracts'. We therefore rewind to the latest - -- avaliable header in the block header database. - -- - -- Checkpointer.exitOnRewindLimitExceeded $ initializeLatestBlock (_pactUnlimitedInitialRewind config) - act + let run = act pse let cancelRefresher = do - refresherThread <- fmap fst <$> atomically (tryReadTMVar (_psMiningPayloadVar pse)) + refresherThread <- fmap (view _1) <$> atomically (tryReadTMVar (_psMiningPayloadVar pse)) traverse_ cancel refresherThread run `finally` cancelRefresher - where - pactServiceLogger = setComponent "pact" chainwebLogger - checkpointerLogger = addLabel ("sub-component", "checkpointer") pactServiceLogger - gasLogger = addLabel ("transaction", "GasLogs") pactServiceLogger initialPayloadState :: Logger logger - => CanReadablePayloadCas tbl - => ChainwebVersion - -> ChainId - -> PactServiceM logger tbl () -initialPayloadState v cid + => CanPayloadCas tbl + => logger + -> ServiceEnv tbl + -> IO () +initialPayloadState logger serviceEnv -- TODO PP: no more, once we can disable payload providers - | v ^. versionCheats . disablePact = pure () - | otherwise = runGenesisIfNeeded v cid + | serviceEnv ^. chainwebVersion . versionCheats . disablePact = pure () + | otherwise = runGenesisIfNeeded logger serviceEnv runGenesisIfNeeded - :: forall tbl logger. (CanReadablePayloadCas tbl, Logger logger) - => ChainwebVersion - -> ChainId - -> PactServiceM logger tbl () -runGenesisIfNeeded v cid = do - latestBlock <- _consensusStateLatest <$> Checkpointer.getConsensusState - when (isGenesisBlockHeader' v cid (Parent $ _syncStateBlockHash latestBlock)) $ do - let payload = genesisPayload v ^?! atChain cid - let payloadHash = genesisBlockPayloadHash v cid - let genesisEvaluationCtx = EvaluationCtx - { _evaluationCtxParentCreationTime = Parent $ v ^?! versionGenesis . genesisTime . atChain cid - , _evaluationCtxParentHash = Parent $ _syncStateBlockHash latestBlock - , _evaluationCtxParentHeight = Parent $ _syncStateHeight latestBlock - -- should not be used - , _evaluationCtxMinerReward = MinerReward 0 - , _evaluationCtxPayload = ConsensusPayload - { _consensusPayloadHash = payloadHash - , _consensusPayloadData = Just $ EncodedPayloadData $ encodePayloadData $ payloadWithOutputsToPayloadData payload - } - } + :: forall tbl logger. (CanPayloadCas tbl, Logger logger) + => logger + -> ServiceEnv tbl + -> IO () +runGenesisIfNeeded logger serviceEnv = do + latestBlock <- fmap _consensusStateLatest <$> Checkpointer.getConsensusState (_psReadWriteSql serviceEnv) + when (maybe True (isGenesisBlockHeader' v cid . Parent . _syncStateBlockHash) latestBlock) $ do + let genesisBlockHash = genesisBlockHeader v cid ^. blockHash + let genesisPayloadHash = genesisBlockPayloadHash v cid let targetSyncState = genesisConsensusState v cid - actualSyncState <- syncToFork mempty Nothing - (ForkInfo [genesisEvaluationCtx] payloadHash targetSyncState Nothing) - when (targetSyncState /= actualSyncState) $ - error "failed to run genesis block" + let evalCtx = genesisEvaluationCtx serviceEnv + let blockCtx = blockCtxOfEvaluationCtx v cid evalCtx + + maybeErr <- runExceptT $ Checkpointer.restoreAndSave logger v cid (_psReadWriteSql serviceEnv) + $ NEL.singleton + $ (blockCtx, \blockEnv -> do + _ <- Pact.execExistingBlock logger serviceEnv blockEnv + (CheckablePayloadWithOutputs (_psGenesisPayload serviceEnv)) + return ((), (genesisBlockHash, genesisPayloadHash)) + ) + case maybeErr of + Left err -> error $ "genesis block invalid: " <> sshow err + Right () -> do + addNewPayload + (_payloadStoreTable $ _psPdb serviceEnv) + (genesisHeight v cid) + (_psGenesisPayload serviceEnv) + Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState + + where + v = _chainwebVersion serviceEnv + cid = _chainId serviceEnv + +getMiner :: HasCallStack => ServiceEnv tbl -> IO Miner +getMiner serviceEnv = case _psMiner serviceEnv of + Nothing -> error "Chainweb.Pact.PactService: Mining is disabled, but was invoked. This is a bug in chainweb." + Just miner -> return miner + +revertStateOnFailure :: Monad m => StateT s (ExceptT e m) a -> StateT s m (Either e a) +revertStateOnFailure s = do + StateT $ \old -> + runExceptT (runStateT s old) <&> f old + where + f old = \case + Left err -> (Left err, old) + Right (success, new) -> (Right success, new) makeEmptyBlock - :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) - => PactBlockM logger tbl BlockInProgress -makeEmptyBlock = do - miner <- view (psServiceEnv . psMiner) >>= \case - Nothing -> error "Chainweb.Pact.PactService.execNewBlock: Mining is disabled. Please provide a valid miner in the pact service configuration" - Just x -> return x - blockGasLimit <- view (psServiceEnv . psNewBlockGasLimit) - coinbaseOutput <- Pact.runCoinbase miner >>= \case + :: forall logger tbl. (Logger logger) + => logger + -> ServiceEnv tbl + -> BlockEnv + -> StateT BlockHandle IO BlockInProgress +makeEmptyBlock logger serviceEnv blockEnv = do + miner <- liftIO $ getMiner serviceEnv + let blockGasLimit = _psNewBlockGasLimit serviceEnv + coinbaseOutput <- revertStateOnFailure (Pact.runCoinbase logger blockEnv miner) >>= \case Left coinbaseError -> error $ "Error during coinbase: " <> sshow coinbaseError Right coinbaseOutput -> -- pretend that coinbase can throw an error, when we know it can't. -- perhaps we can make the Transactions express this, may not be worth it. return $ coinbaseOutput & Pact.crResult . Pact._PactResultErr %~ absurd - hndl <- use pbBlockHandle - blockCtx <- view psBlockCtx + hndl <- get return BlockInProgress { _blockInProgressHandle = hndl - , _blockInProgressBlockCtx = blockCtx + , _blockInProgressBlockCtx = view psBlockCtx blockEnv , _blockInProgressRemainingGasLimit = blockGasLimit , _blockInProgressTransactions = Transactions { _transactionCoinbase = coinbaseOutput , _transactionPairs = mempty } + , _blockInProgressNumber = 0 } --- -- | only for use in generating genesis blocks in tools. --- -- --- execNewGenesisBlock --- :: (Logger logger, CanReadablePayloadCas tbl) --- => Miner --- -> Vector Pact4.UnparsedTransaction --- -> PactServiceM logger tbl PayloadWithOutputs --- execNewGenesisBlock miner newTrans = pactLabel "execNewGenesisBlock" $ do --- historicalBlock <- Checkpointer.readFrom Nothing $ SomeBlockM $ Pair --- (do --- logger <- view (psServiceEnv . psLogger) --- v <- view chainwebVersion --- cid <- view chainId --- txs <- liftIO $ traverse (runExceptT . Pact4.checkParse logger v cid (genesisBlockHeight v cid)) newTrans --- parsedTxs <- case partitionEithers (V.toList txs) of --- ([], validTxs) -> return (V.fromList validTxs) --- (errs, _) -> internalError $ "Invalid genesis txs: " <> sshow errs --- -- NEW GENESIS COINBASE: Reject bad coinbase, use date rule for precompilation --- results <- --- Pact4.execTransactions miner parsedTxs --- (Pact4.EnforceCoinbaseFailure True) --- (Pact4.CoinbaseUsePrecompiled False) Nothing Nothing --- >>= throwCommandInvalidError --- return $! toPayloadWithOutputs Pact4T miner results --- ) --- (do --- v <- view chainwebVersion --- cid <- view chainId --- let mempoolAccess = mempty --- { mpaGetBlock = \bf pbc bh bhash _bheader -> do --- if _bfCount bf == 0 --- then do --- maybeInvalidTxs <- pbc bh bhash newTrans --- validTxs <- case partitionEithers (V.toList maybeInvalidTxs) of --- ([], validTxs) -> return validTxs --- (errs, _) -> throwM $ Pact5GenesisCommandsInvalid errs --- V.fromList validTxs `Strategies.usingIO` traverse Strategies.rseq --- else do --- return V.empty --- } --- startHandle <- use Pact.pbBlockHandle --- let bipStart = BlockInProgress --- { _blockInProgressHandle = startHandle --- , _blockInProgressMiner = miner --- , _blockInProgressModuleCache = Pact5NoModuleCache --- , _blockInProgressPactVersion = Pact5T --- , _blockInProgressParentHeader = Nothing --- , _blockInProgressChainwebVersion = v --- , _blockInProgressChainId = cid --- -- fake gas limit, gas is free for genesis --- , _blockInProgressRemainingGasLimit = GasLimit (Pact4.ParsedInteger 999_999_999) --- , _blockInProgressTransactions = Transactions --- { _transactionCoinbase = absurd <$> Pact.noCoinbase --- , _transactionPairs = mempty --- } --- } --- results <- Pact.continueBlock mempoolAccess bipStart --- return $! finalizeBlock results --- ) --- case historicalBlock of --- NoHistory -> internalError "PactService.execNewGenesisBlock: Impossible error, unable to rewind before genesis" --- Historical block -> return block +-- | only for use in generating genesis blocks in tools. +-- +execNewGenesisBlock + :: (Logger logger, CanReadablePayloadCas tbl) + => logger + -> ServiceEnv tbl + -> Vector Pact.Transaction + -> IO PayloadWithOutputs +execNewGenesisBlock logger serviceEnv newTrans = do + let v = _chainwebVersion serviceEnv + let cid = _chainId serviceEnv + fakeNewBlockCtx <- mkFakeNewBlockCtx + let genesisParent = Parent $ RankedBlockHash (genesisHeight v cid) (unwrapParent $ genesisParentBlockHash v cid) + historicalBlock <- Checkpointer.readFrom logger v cid (_psReadWriteSql serviceEnv) fakeNewBlockCtx genesisParent $ \blockEnv startHandle -> do + + let bipStart = BlockInProgress + { _blockInProgressHandle = startHandle + , _blockInProgressNumber = 0 + -- fake gas limit, gas is free for genesis + , _blockInProgressRemainingGasLimit = Pact.GasLimit (Pact.Gas 999_999_999) + , _blockInProgressTransactions = Transactions + { _transactionCoinbase = absurd <$> Pact.noCoinbase + , _transactionPairs = mempty + } + , _blockInProgressBlockCtx = _psBlockCtx blockEnv + } + + let fakeServiceEnv = serviceEnv + & psMempoolAccess .~ mempty + { mpaGetBlock = \bf pbc evalCtx -> do + if _bfCount bf == 0 + then do + maybeInvalidTxs <- pbc evalCtx newTrans + validTxs <- case partitionEithers (V.toList maybeInvalidTxs) of + ([], validTxs) -> return validTxs + (errs, _) -> error $ "Pact 5 genesis commands invalid: " <> sshow errs + V.fromList validTxs `Strategies.usingIO` traverse Strategies.rseq + else do + return V.empty + } + & psMiner .~ Just noMiner + + results <- Pact.continueBlock logger fakeServiceEnv (_psBlockDbEnv blockEnv) bipStart + let !pwo = toPayloadWithOutputs + noMiner + (_blockInProgressTransactions results) + return $! pwo + case historicalBlock of + NoHistory -> error "PactService.execNewGenesisBlock: Impossible error, unable to rewind before genesis" + Historical block -> return block execReadOnlyReplay :: forall logger tbl . (Logger logger, CanReadablePayloadCas tbl) - => [EvaluationCtx BlockPayloadHash] - -> PactServiceM logger tbl () -execReadOnlyReplay blocks = undefined -- pactLabel "execReadOnlyReplay" $ do - -- ParentHeader cur <- Checkpointer.findLatestValidBlockHeader - -- logger <- view psLogger - -- bhdb <- view psBlockHeaderDb - -- pdb <- view psPdb - -- v <- view chainwebVersion - -- cid <- view chainId - -- -- lower bound must be an ancestor of upper. - -- upperBound <- case maybeUpperBound of - -- Just upperBound -> do - -- liftIO (ancestorOf bhdb (view blockHash lowerBound) (view blockHash upperBound)) >>= - -- flip unless (internalError "lower bound is not an ancestor of upper bound") - - -- -- upper bound must be an ancestor of latest header. - -- liftIO (ancestorOf bhdb (view blockHash upperBound) (view blockHash cur)) >>= - -- flip unless (internalError "upper bound is not an ancestor of latest header") - - -- return upperBound - -- Nothing -> do - -- liftIO (ancestorOf bhdb (view blockHash lowerBound) (view blockHash cur)) >>= - -- flip unless (internalError "lower bound is not an ancestor of latest header") - - -- return cur - -- liftIO $ logFunctionText logger Info $ "pact db replaying between blocks " - -- <> sshow (view blockHeight lowerBound, view blockHash lowerBound) <> " and " - -- <> sshow (view blockHeight upperBound, view blockHash upperBound) - - -- let genHeight = genesisHeight v cid - -- -- we don't want to replay the genesis header in here. - -- let lowerHeight = max (succ genHeight) (view blockHeight lowerBound) - -- withPactState $ \runPact -> - -- liftIO $ getBranchIncreasing bhdb upperBound (int lowerHeight) $ \blocks -> do - -- heightRef <- newIORef lowerHeight - -- withAsync (heightProgress lowerHeight (view blockHeight upperBound) heightRef (logInfo_ logger)) $ \_ -> do - -- blocks - -- & Stream.hoist liftIO - -- & play bhdb pdb heightRef runPact - -- where - - -- play - -- :: CanReadablePayloadCas tbl - -- => BlockHeaderDb - -- -> PayloadDb tbl - -- -> IORef BlockHeight - -- -> (forall a. PactServiceM logger tbl a -> IO a) - -- -> Stream.Stream (Stream.Of BlockHeader) IO r - -- -> IO r - -- play bhdb pdb heightRef runPact blocks = do - -- logger <- runPact $ view psLogger - -- validationFailedRef <- newIORef False - -- r <- blocks & Stream.mapM_ (\bh -> do - -- bhParent <- liftIO $ lookupParentM GenesisParentThrow bhdb bh - -- let - -- printValidationError (BlockValidationFailure (BlockValidationFailureMsg m)) = do - -- writeIORef validationFailedRef True - -- logFunctionText logger Error m - -- printValidationError e = throwM e - -- handleMissingBlock NoHistory = throwM $ BlockHeaderLookupFailure $ - -- "execReadOnlyReplay: missing block: " <> sshow bh - -- handleMissingBlock (Historical ()) = return () - -- payload <- liftIO $ fromJuste <$> - -- lookupPayloadWithHeight pdb (Just $ view blockHeight bh) (view blockPayloadHash bh) - -- let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload) - -- let isUpgradeBlock = isJust $ _chainwebVersion bhdb ^? versionUpgrades . atChain (_chainId bhdb) . ix (view blockHeight bh) - -- liftIO $ writeIORef heightRef (view blockHeight bh) - -- unless (isPayloadEmpty && not isUpgradeBlock) - -- $ handle printValidationError - -- $ (handleMissingBlock =<<) - -- $ runPact - -- $ Checkpointer.readFrom (Just $ ParentHeader bhParent) $ - -- SomeBlockM $ Pair - -- (void $ Pact4.execBlock bh (CheckablePayloadWithOutputs payload)) - -- (void $ Pact.execExistingBlock bh (CheckablePayloadWithOutputs payload)) - -- ) - -- validationFailed <- readIORef validationFailedRef - -- when validationFailed $ - -- throwM $ BlockValidationFailure $ BlockValidationFailureMsg "Prior block validation errors" - -- return r - - -- heightProgress :: BlockHeight -> BlockHeight -> IORef BlockHeight -> (Text -> IO ()) -> IO () - -- heightProgress initialHeight endHeight ref logFun = do - -- heightAndRateRef <- newIORef (initialHeight, 0.0 :: Double) - -- let delayMicros = 20_000_000 - -- liftIO $ threadDelay (delayMicros `div` 2) - -- forever $ do - -- liftIO $ threadDelay delayMicros - -- (lastHeight, oldRate) <- readIORef heightAndRateRef - -- now' <- getCurrentTime - -- currentHeight <- readIORef ref - -- let blocksPerSecond - -- = 0.8 - -- * oldRate - -- + 0.2 - -- * fromIntegral (currentHeight - lastHeight) - -- / (fromIntegral delayMicros / 1_000_000) - -- writeIORef heightAndRateRef (currentHeight, blocksPerSecond) - -- let est = - -- flip addUTCTime now' - -- $ realToFrac @Double @NominalDiffTime - -- $ fromIntegral @BlockHeight @Double - -- (endHeight - initialHeight) - -- / blocksPerSecond - -- logFun - -- $ Text.pack $ printf "height: %d | rate: %.1f blocks/sec | est. %s" - -- (fromIntegral @BlockHeight @Int $ currentHeight - initialHeight) - -- blocksPerSecond - -- (formatShow iso8601Format est) - --- execLocal --- :: (Logger logger, CanReadablePayloadCas tbl) --- => Pact.Transaction --- -> Maybe LocalPreflightSimulation --- -- ^ preflight flag --- -> Maybe LocalSignatureVerification --- -- ^ turn off signature verification checks? --- -> Maybe RewindDepth --- -- ^ rewind depth --- -> PactServiceM logger tbl LocalResult --- execLocal cwtx preflight sigVerify rdepth = pactLabel "execLocal" $ do - --- e@PactServiceEnv{..} <- ask - --- let !cmd = Pact4.payloadObj <$> cwtx --- !pm = Pact4.publicMetaOf cmd --- !v = _chainwebVersion e --- !cid = _chainId e - --- bhdb <- view psBlockHeaderDb - --- -- when no depth is defined, treat --- -- withCheckpointerRewind as readFrom --- -- (i.e. setting rewind to 0). --- let rewindDepth = maybe 0 _rewindDepth rdepth - --- let timeoutLimit --- | _psEnableLocalTimeout = Just (2 * 1_000_000) --- | otherwise = Nothing - --- let doLocal = Checkpointer.readFromNthParent (fromIntegral rewindDepth) $ do --- ph <- view psParentHeader --- let pact5RequestKey = Pact.RequestKey (Pact.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) --- evalContT $ withEarlyReturn $ \earlyReturn -> do --- pact5Cmd <- case Pact.parsePact4Command cwtx of --- Left (fmap Pact.spanInfoToLineInfo -> parseError) -> --- earlyReturn $ Pact5LocalResultLegacy Pact.CommandResult --- { _crReqKey = pact5RequestKey --- , _crTxId = Nothing --- , _crResult = Pact.PactResultErr $ --- Pact.pactErrorToOnChainError parseError --- , _crGas = Pact.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit --- , _crLogs = Nothing --- , _crContinuation = Nothing --- , _crMetaData = Nothing --- , _crEvents = [] --- } --- Right pact5Cmd -> return pact5Cmd - --- -- this is just one of our metadata validation passes. --- -- in preflight, we do another one, which replicates some of this work; --- -- TODO: unify preflight, newblock, and validateblock tx metadata validation --- case (preflight, sigVerify) of --- (_, Just NoVerify) -> do --- let payloadBS = SB.fromShort (Pact4._cmdPayload $ Pact4.payloadBytes <$> cwtx) --- let validated = Pact.verifyHash (Pact._cmdHash pact5Cmd) payloadBS --- case validated of --- Left err -> earlyReturn $ --- review _MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err --- Right _ -> return () --- _ -> do --- let validated = Pact.assertCommand pact5Cmd --- case validated of --- Left err -> earlyReturn $ --- review _MetadataValidationFailure (pure $ displayAssertCommandError err) --- Right () -> return () - --- let txCtx = Pact.BlockCtx ph noMiner --- let spvSupport = Pact.pactSPV bhdb (_parentHeader ph) --- case preflight of --- Just PreflightSimulation -> do --- -- preflight needs to do additional checks on the metadata --- -- to match on-chain tx validation --- lift (Pact.liftPactServiceM (Pact.assertPreflightMetadata (view Pact.payloadObj <$> pact5Cmd) txCtx sigVerify)) >>= \case --- Left err -> earlyReturn $ review _MetadataValidationFailure err --- Right () -> return () --- let initialGas = Pact.initialGasOf $ Pact._cmdPayload pact5Cmd --- applyCmdResult <- lift $ Pact.pactTransaction Nothing (\dbEnv -> --- Pact.applyCmd --- _psLogger _psGasLogger dbEnv --- txCtx (TxBlockIdx 0) spvSupport initialGas (view Pact.payloadObj <$> pact5Cmd) --- ) --- commandResult <- case applyCmdResult of --- Left err -> --- earlyReturn $ Pact5LocalResultWithWarns Pact.CommandResult --- { _crReqKey = Pact.RequestKey (Pact.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) --- , _crTxId = Nothing --- , _crResult = Pact.PactResultErr $ --- Pact.PactOnChainError --- -- the only legal error type, once chainweaver is really gone, we --- -- can use a real error type --- (Pact.ErrorType "EvalError") --- (Pact.mkBoundedText $ prettyPact5GasPurchaseFailure err) --- (Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin Pact.noInfo) --- , _crGas = Pact.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit --- , _crLogs = Nothing --- , _crContinuation = Nothing --- , _crMetaData = Nothing --- , _crEvents = [] --- } --- [] --- Right commandResult -> return commandResult --- let pact5Pm = pact5Cmd ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta --- let metadata = J.toJsonViaEncode $ Pact.StableEncoding $ Pact.ctxToPublicData pact5Pm txCtx --- let commandResult' = hashPact5TxLogs $ set Pact.crMetaData (Just metadata) commandResult --- -- TODO: once Pact 5 has warnings, include them here. --- pure $ Pact5LocalResultWithWarns --- (Pact.pactErrorToOnChainError <$> commandResult') --- [] --- _ -> lift $ do --- -- default is legacy mode: use applyLocal, don't buy gas, don't do any --- -- metadata checks beyond signature and hash checking --- cr <- Pact.pactTransaction Nothing $ \dbEnv -> do --- fmap Pact.pactErrorToOnChainError <$> Pact.applyLocal _psLogger _psGasLogger dbEnv txCtx spvSupport (view Pact.payloadObj <$> pact5Cmd) --- pure $ Pact5LocalResultLegacy (hashPact5TxLogs cr) - --- case timeoutLimit of --- Nothing -> doLocal --- Just limit -> withPactState $ \run -> timeoutYield limit (run doLocal) >>= \case --- Just r -> pure r --- Nothing -> do --- logError_ _psLogger $ "Local action timed out for cwtx:\n" <> sshow cwtx --- pure $ review _LocalTimeout () - --- execSyncToBlock --- :: (CanReadablePayloadCas tbl, Logger logger) --- => BlockHeader --- -> PactServiceM logger tbl () --- execSyncToBlock targetHeader = pactLabel "execSyncToBlock" $ do --- latestHeader <- Checkpointer.findLatestValidBlockHeader' >>= maybe failNonGenesisOnEmptyDb return --- if latestHeader == targetHeader --- then do --- logInfoPact $ "checkpointer at checkpointer target" --- <> ". target height: " <> sshow (view blockHeight latestHeader) --- <> "; target hash: " <> blockHashToText (view blockHash latestHeader) --- else do --- logInfoPact $ "rewind to checkpointer target" --- <> ". current height: " <> sshow (view blockHeight latestHeader) --- <> "; current hash: " <> blockHashToText (view blockHash latestHeader) --- <> "; target height: " <> sshow targetHeight --- <> "; target hash: " <> blockHashToText targetHash --- Checkpointer.rewindToIncremental Nothing (ParentHeader targetHeader) --- where --- targetHeight = view blockHeight targetHeader --- targetHash = view blockHash targetHeader --- failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" + => logger + -> ServiceEnv tbl + -> [EvaluationCtx BlockPayloadHash] + -> IO [BlockInvalidError] +execReadOnlyReplay logger serviceEnv blocks = do + let readSqlPool = view psReadSqlPool serviceEnv + let v = view chainwebVersion serviceEnv + let cid = view chainId serviceEnv + let pdb = view psPdb serviceEnv + blocks + & mapM (\evalCtx -> do + payload <- liftIO $ fromJuste <$> + lookupPayloadWithHeight (_payloadStoreTable pdb) (Just $ _evaluationCtxCurrentHeight evalCtx) (_evaluationCtxPayload evalCtx) + let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload) + let isUpgradeBlock = isJust $ v ^? versionUpgrades . atChain cid . ix (_evaluationCtxCurrentHeight evalCtx) + newBlockCtx <- Checkpointer.mkFakeNewBlockCtx + if isPayloadEmpty && not isUpgradeBlock + then Pool.withResource readSqlPool $ \sql -> do + hist <- Checkpointer.readFrom + logger + v + cid + sql + newBlockCtx + (_evaluationCtxRankedParentHash evalCtx) + (\blockEnv blockHandle -> + runExceptT $ flip evalStateT blockHandle $ + void $ Pact.execExistingBlock logger serviceEnv blockEnv (CheckablePayloadWithOutputs payload)) + either Just (\_ -> Nothing) <$> throwIfNoHistory hist + else + return Nothing + ) + & fmap catMaybes + +execLocal + :: (Logger logger, CanReadablePayloadCas tbl) + => logger + -> ServiceEnv tbl + -> Pact.Transaction + -> Maybe LocalPreflightSimulation + -- ^ preflight flag + -> Maybe LocalSignatureVerification + -- ^ turn off signature verification checks? + -> Maybe RewindDepth + -- ^ rewind depth + -> IO (Historical LocalResult) +execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do + case timeoutLimit of + Nothing -> doLocal + Just limit -> timeoutYield limit doLocal >>= \case + Just r -> pure r + Nothing -> do + logError_ logger $ "Local action timed out for cwtx:\n" <> sshow cwtx + pure $ Historical LocalTimeout + where + + doLocal = Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> do + fakeNewBlockCtx <- liftIO $ Checkpointer.mkFakeNewBlockCtx + Checkpointer.readFromNthParent logger v cid sql fakeNewBlockCtx (fromIntegral rewindDepth) $ \blockEnv blockHandle -> do + let blockCtx = view psBlockCtx blockEnv + let requestKey = Pact.cmdToRequestKey cwtx + evalContT $ withEarlyReturn $ \earlyReturn -> do + -- this is just one of our metadata validation passes. + -- in preflight, we do another one, which replicates some of this work; + -- TODO: unify preflight, newblock, and validateblock tx metadata validation + case (preflight, sigVerify) of + (_, Just NoVerify) -> do + let payloadBS = SB.fromShort (Pact._cmdPayload $ view Pact.payloadBytes <$> cwtx) + case Pact.verifyHash (Pact._cmdHash cwtx) payloadBS of + Left err -> earlyReturn $ + MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err + Right _ -> return () + _ -> do + case Pact.assertCommand cwtx of + Left err -> earlyReturn $ + MetadataValidationFailure (pure $ displayAssertCommandError err) + Right () -> return () + + case preflight of + Just PreflightSimulation -> do + -- preflight needs to do additional checks on the metadata + -- to match on-chain tx validation + case (Pact.assertPreflightMetadata serviceEnv (view Pact.payloadObj <$> cwtx) blockCtx sigVerify) of + Left err -> earlyReturn $ MetadataValidationFailure err + Right () -> return () + let initialGas = Pact.initialGasOf $ Pact._cmdPayload cwtx + applyCmdResult <- flip evalStateT blockHandle $ pactTransaction blockEnv Nothing (\dbEnv spvSupport -> + Pact.applyCmd + logger gasLogger dbEnv noMiner + blockCtx (TxBlockIdx 0) spvSupport initialGas (view Pact.payloadObj <$> cwtx) + ) + commandResult <- case applyCmdResult of + Left _err -> + earlyReturn $ LocalResultWithWarns (J.encodeJsonText Pact.CommandResult + { _crReqKey = requestKey + , _crTxId = Nothing + , _crResult = Pact.PactResultErr $ + Pact.PactOnChainError + -- the only legal error type, once chainweaver is really gone, we + -- can use a real error type + (Pact.ErrorType "EvalError") + (Pact.mkBoundedText $ undefined) -- TODO: PP prettyPact5GasPurchaseFailure err) + (Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin (Pact.LineInfo 0)) + , _crGas = + cwtx ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmGasLimit . Pact._GasLimit + , _crLogs = Nothing :: Maybe Text + , _crContinuation = Nothing + , _crMetaData = Nothing + , _crEvents = [] + }) + [] + Right commandResult -> return commandResult + let pact5Pm = cwtx ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta + let metadata = J.toJsonViaEncode $ Pact.StableEncoding $ Pact.ctxToPublicData pact5Pm blockCtx + let commandResult' = hashPactTxLogs $ set Pact.crMetaData (Just metadata) commandResult + -- TODO: once Pact 5 has warnings, include them here. + pure $ LocalResultWithWarns + (J.encodeJsonText $ Pact.pactErrorToOnChainError <$> commandResult') + [] + _ -> lift $ do + -- default is legacy mode: use applyLocal, don't buy gas, don't do any + -- metadata checks beyond signature and hash checking + cr <- flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \dbEnv spvSupport -> do + -- TODO: PPgaslog + fmap Pact.pactErrorToOnChainError <$> Pact.applyLocal logger Nothing dbEnv blockCtx spvSupport (view Pact.payloadObj <$> cwtx) + pure $ LocalResultLegacy $ J.encodeJsonText (hashPactTxLogs cr) + + gasLogger = view psGasLogger serviceEnv + enableLocalTimeout = view psEnableLocalTimeout serviceEnv + v = _chainwebVersion serviceEnv + cid = _chainId serviceEnv + + -- when no depth is defined, treat + -- withCheckpointerRewind as readFrom + -- (i.e. setting rewind to 0). + rewindDepth = maybe 0 _rewindDepth rdepth + + timeoutLimit + | enableLocalTimeout = Just (2 * 1_000_000) + | otherwise = Nothing syncToFork :: forall tbl logger - . (CanReadablePayloadCas tbl, Logger logger) - => MemPoolAccess + . (CanPayloadCas tbl, Logger logger) + => logger + -> ServiceEnv tbl -> Maybe Hints -> ForkInfo - -> PactServiceM logger tbl ConsensusState -syncToFork memPoolAccess hints forkInfo = do - sql <- view psReadWriteSql - pdb <- view psPdb + -> IO ConsensusState +syncToFork logger serviceEnv hints forkInfo = do (rewoundTxs, validatedTxs, newConsensusState) <- withSavepoint sql ValidateBlockSavePoint $ do - let - findForkChain (tip:chain) = go (NEL.singleton tip) chain - findForkChain [] = return Nothing - go !acc (tip:chain) = do - -- note that if we see the eval ctx in the checkpointer, - -- that means that the block has been evaluated, thus we do - -- not include `tip` in the resulting list. - known <- Checkpointer.lookupBlockByEvalCtx tip - if known - then return $ Just acc - -- if we don't know this block, remember it for later as we'll - -- need to execute it - else go (tip `NEL.cons` acc) chain - go _acc [] = return Nothing - - pactConsensusState <- Checkpointer.getConsensusState + pactConsensusState <- fromJuste <$> Checkpointer.getConsensusState sql let atTarget = _syncStateBlockHash (_consensusStateLatest pactConsensusState) == _latestBlockHash (forkInfo._forkInfoTargetState) -- check if some past block had the target as its parent; if so, that -- means we can rewind to it latestBlockRewindable <- - Checkpointer.lookupParentBlockHash (Parent $ _syncStateBlockHash (_consensusStateLatest pactConsensusState)) + Checkpointer.lookupParentBlockHash sql (Parent $ _syncStateBlockHash (_consensusStateLatest pactConsensusState)) if atTarget - then do - -- no work to do at all except set consensus state - -- TODO PP: disallow rewinding final? - logDebugPact $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState - Checkpointer.setConsensusState forkInfo._forkInfoTargetState - return (mempty, mempty, forkInfo._forkInfoTargetState) - else if latestBlockRewindable - then do - -- we just have to rewind and set the final + safe blocks - -- TODO PP: disallow rewinding final? - logDebugPact $ "pure rewind to " <> sshow forkInfo._forkInfoTargetState - rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) - Checkpointer.rewindTo (_syncStateRankedBlockHash (_consensusStateLatest pactConsensusState)) - Checkpointer.setConsensusState forkInfo._forkInfoTargetState - return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) - else do - logDebugPact $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState - findForkChain forkInfo._forkInfoTrace >>= \case - Nothing -> do - logErrorPact $ "impossible to move to " <> sshow forkInfo._forkInfoTargetState - -- error: we have no way to get to the target block. just report - -- our current state and do nothing else. - return (mempty, mempty, pactConsensusState) - Just forkChainBottomToTop -> do - rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) - -- the happy case: we can find a way to get to the target block - -- look up all of the payloads to see if we've run them before - -- even then we still have to run them, because they aren't in the checkpointer - knownPayloads <- liftIO $ - tableLookupBatch' pdb (each . _2) ((\e -> (e, _evaluationCtxRankedPayloadHash e)) <$> forkChainBottomToTop) - - logDebugPact $ "unknown blocks in context: " <> sshow (length $ NEL.filter (isNothing . snd) knownPayloads) - - runnableBlocks <- forM knownPayloads $ \(evalCtx, maybePayload) -> do - payload <- case maybePayload of - -- fetch payload if missing - Nothing -> getPayloadForContext hints evalCtx - Just payload -> return payload - let runBlock = Pact.execExistingBlock (_consensusPayloadHash <$> evalCtx) (CheckablePayload payload) - return - ( DList.singleton <$> runBlock - , _consensusPayloadHash <$> evalCtx - ) - - runExceptT (Checkpointer.restoreAndSave runnableBlocks) >>= \case - Left err -> do - logErrorPact $ "Error in execValidateBlock: " <> sshow err - return (mempty, mempty, pactConsensusState) - Right blockResults -> do - let validatedTxHashes = V.concatMap - (fmap pactRequestKeyToTransactionHash . view _3) - (V.fromList $ DList.toList blockResults) - Checkpointer.setConsensusState forkInfo._forkInfoTargetState - return (rewoundTxs, validatedTxHashes, forkInfo._forkInfoTargetState) + then do + -- no work to do at all except set consensus state + -- TODO PP: disallow rewinding final? + logFunctionText logger Debug $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState + Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState + return (mempty, mempty, forkInfo._forkInfoTargetState) + else if latestBlockRewindable + then do + -- we just have to rewind and set the final + safe blocks + -- TODO PP: disallow rewinding final? + logFunctionText logger Debug $ "pure rewind to " <> sshow forkInfo._forkInfoTargetState + rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) + Checkpointer.rewindTo v cid sql (_syncStateRankedBlockHash (_consensusStateLatest pactConsensusState)) + Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState + return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) + else do + logFunctionText logger Debug $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState + let traceBlockHashes = + drop 1 (unwrapParent . _evaluationCtxRankedParentHash <$> forkInfo._forkInfoTrace) <> + [_syncStateRankedBlockHash pactConsensusState._consensusStateLatest] + findForkChain (zip forkInfo._forkInfoTrace traceBlockHashes) >>= \case + Nothing -> do + logFunctionText logger Error $ "impossible to move to " <> sshow forkInfo._forkInfoTargetState + -- error: we have no way to get to the target block. just report + -- our current state and do nothing else. + return (mempty, mempty, pactConsensusState) + Just forkChainBottomToTop -> do + rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) + -- the happy case: we can find a way to get to the target block + -- look up all of the payloads to see if we've run them before + -- even then we still have to run them, because they aren't in the checkpointer + knownPayloads <- liftIO $ + tableLookupBatch' pdb (each . _2) ((\e -> (e, _evaluationCtxRankedPayloadHash $ fst e)) <$> forkChainBottomToTop) + + logFunctionText logger Debug $ "unknown blocks in context: " <> sshow (length $ NEL.filter (isNothing . snd) knownPayloads) + + runnableBlocks <- forM knownPayloads $ \((evalCtx, rankedBHash), maybePayload) -> do + payload <- case maybePayload of + -- fetch payload if missing + Nothing -> getPayloadForContext logger serviceEnv hints evalCtx + Just payload -> return payload + let expectedPayloadHash = _consensusPayloadHash $ _evaluationCtxPayload evalCtx + return $ + (blockCtxOfEvaluationCtx v cid evalCtx, \blockEnv -> do + (_, pwo, validatedTxs) <- Pact.execExistingBlock logger serviceEnv blockEnv (CheckablePayload expectedPayloadHash payload) + -- add payload immediately after executing the block, because this is when we learn it's valid + liftIO $ addNewPayload (_payloadStoreTable $ _psPdb serviceEnv) (_evaluationCtxCurrentHeight evalCtx) pwo + return $ (DList.singleton validatedTxs, (_ranked rankedBHash, expectedPayloadHash)) + ) + + runExceptT (Checkpointer.restoreAndSave logger v cid sql runnableBlocks) >>= \case + Left err -> do + logFunctionText logger Error $ "Error in execValidateBlock: " <> sshow err + return (mempty, mempty, pactConsensusState) + Right (DList.toList -> blockResults) -> do + let validatedTxs = msum blockResults + Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState + return (rewoundTxs, validatedTxs, forkInfo._forkInfoTargetState) liftIO $ mpaProcessFork memPoolAccess (rewoundTxs, validatedTxs) case forkInfo._forkInfoNewBlockCtx of Just newBlockCtx @@ -721,30 +543,57 @@ syncToFork memPoolAccess hints forkInfo = do -- told to start mining, we produce an empty block -- immediately. then we set up a separate thread -- to add new transactions to the block. - emptyBlock <- Checkpointer.readFromLatest newBlockCtx makeEmptyBlock - payloadVar <- view psMiningPayloadVar + emptyBlock <- Checkpointer.readFromLatest logger v cid sql newBlockCtx $ \blockEnv blockHandle -> + flip evalStateT blockHandle $ makeEmptyBlock logger serviceEnv blockEnv + let payloadVar = view psMiningPayloadVar serviceEnv -- cancel payload refresher thread liftIO $ - atomically (fmap fst <$> tryTakeTMVar payloadVar) + atomically (fmap (view _1) <$> tryTakeTMVar payloadVar) >>= traverse_ cancel - e <- ask - - refresherThread <- liftIO $ async (runPactServiceM e refreshPayloads) + refresherThread <- liftIO $ async (refreshPayloads logger serviceEnv) liftIO $ - atomically $ writeTMVar payloadVar (refresherThread, emptyBlock) + atomically $ writeTMVar payloadVar (refresherThread, newBlockCtx, emptyBlock) _ -> return () return newConsensusState where + + memPoolAccess = view psMempoolAccess serviceEnv + sql = view psReadWriteSql serviceEnv + pdb = view psPdb serviceEnv + v = _chainwebVersion serviceEnv + cid = _chainId serviceEnv + + findForkChain + :: [(EvaluationCtx p, RankedBlockHash)] + -> IO (Maybe (NEL.NonEmpty (EvaluationCtx p, RankedBlockHash))) + findForkChain [] = return Nothing + findForkChain (tip:chain) = go (NEL.singleton tip) chain + where + go + :: NEL.NonEmpty (EvaluationCtx p, RankedBlockHash) + -> [(EvaluationCtx p, RankedBlockHash)] + -> IO (Maybe (NEL.NonEmpty (EvaluationCtx p, RankedBlockHash))) + go !acc (tip':chain') = do + -- note that if we see the eval ctx in the checkpointer, + -- that means that the block has been evaluated, thus we do + -- not include `tip` in the resulting list. + known <- Checkpointer.lookupRankedBlockHash sql (snd tip') + if known + then return $ Just acc + -- if we don't know this block, remember it for later as we'll + -- need to execute it on top + else go (tip' `NEL.cons` acc) chain' + go _acc [] = return Nothing + -- remember to call this *before* executing the actual rewind, -- and only alter the mempool *after* the db transaction is done. - getRewoundTxs :: Parent BlockHeight -> PactServiceM logger tbl (Vector Pact.Transaction) + getRewoundTxs :: Parent BlockHeight -> IO (Vector Pact.Transaction) getRewoundTxs rewindTargetHeight = do - pdb <- view psPdb - rewoundPayloadHashes <- Checkpointer.getPayloadsAfter rewindTargetHeight + rewoundPayloadHashes <- Checkpointer.getPayloadsAfter sql rewindTargetHeight rewoundPayloads <- liftIO $ fmap fromJuste <$> lookupPayloadDataWithHeightBatch (_payloadStoreTable pdb) @@ -753,122 +602,67 @@ syncToFork memPoolAccess hints forkInfo = do (fmap (fromRight (error "invalid payload in database")) . runExceptT . pact5TransactionsFromPayload) rewoundPayloads -refreshPayloads :: PactServiceM logger tbl () -refreshPayloads = undefined - - -- -- The parent block header must be available in the block header database. - -- parentOfHeaderToValidate <- getTarget - - - -- -- Add block-hash to the logs if presented - -- let logBlockHash = - -- localLabelPact ("block-hash", blockHashToText (view blockParent headerToValidate)) - - -- logBlockHash $ do - -- -- find the common ancestor of the new block and our current block - -- commonAncestor <- liftIO $ case (currHeader, parentOfHeaderToValidate) of - -- (Just currHeader', Just ph) -> - -- Just <$> forkEntry bhdb currHeader' (_parentHeader ph) - -- _ -> - -- return Nothing - -- -- check that we don't exceed the rewind limit. for the purpose - -- -- of this check, the genesis block and the genesis parent - -- -- have the same height. - -- let !currHeight = maybe (genesisHeight v cid) (view blockHeight) currHeader - -- let !ancestorHeight = maybe (genesisHeight v cid) (view blockHeight) commonAncestor - -- let !rewindLimitSatisfied = ancestorHeight + fromIntegral reorgLimit >= currHeight - -- unless rewindLimitSatisfied $ - -- throwM $ RewindLimitExceeded - -- (RewindLimit reorgLimit) - -- currHeader - -- commonAncestor - -- -- get all blocks on the fork we're going to play, up to the parent - -- -- of the block we're validating - -- let withForkBlockStream kont = case parentOfHeaderToValidate of - -- Nothing -> - -- -- we're validating a genesis block, so there are no fork blocks to speak of. - -- kont (pure ()) - -- Just (ParentHeader parentHeaderOfHeaderToValidate) -> - -- let forkStartHeight = maybe (genesisHeight v cid) (succ . view blockHeight) commonAncestor - -- in getBranchIncreasing bhdb parentHeaderOfHeaderToValidate (fromIntegral forkStartHeight) kont - - -- ((), results) <- - -- withPactState $ \runPact -> - -- withForkBlockStream $ \forkBlockHeaders -> do - - -- -- given a header for a block in the fork, fetch its payload - -- -- and run its transactions, validating its hashes - -- let runForkBlockHeaders = forkBlockHeaders & Stream.map (\forkBh -> do - -- payload <- liftIO $ lookupPayloadWithHeight payloadDb (Just $ view blockHeight forkBh) (view blockPayloadHash forkBh) >>= \case - -- Nothing -> internalError - -- $ "execValidateBlock: lookup of payload failed" - -- <> ". BlockPayloadHash: " <> encodeToText (view blockPayloadHash forkBh) - -- <> ". Block: " <> encodeToText (ObjectEncoded forkBh) - -- Just x -> return $ payloadWithOutputsToPayloadData x - -- SomeBlockM $ Pair - -- (void $ Pact4.execBlock forkBh (CheckablePayload payload)) - -- (void $ Pact.execExistingBlock forkBh (CheckablePayload payload)) - -- return ([], forkBh) - -- ) - - -- -- run the new block, the one we're validating, and - -- -- validate its hashes - -- let runThisBlock = Stream.yield $ SomeBlockM $ Pair - -- (do - -- !output <- Pact4.execBlock headerToValidate payloadToValidate - -- return ([output], headerToValidate) - -- ) - -- (do - -- !(gas, pwo) <- Pact.execExistingBlock headerToValidate payloadToValidate - -- return ([(fromIntegral (Pact._gas gas), pwo)], headerToValidate) - -- ) - - -- -- here we rewind to the common ancestor block, run the - -- -- transactions in all of its child blocks until the parent - -- -- of the block we're validating, then run the block we're - -- -- validating. - -- runPact $ Checkpointer.restoreAndSave - -- (ParentHeader <$> commonAncestor) - -- (runForkBlockHeaders >> runThisBlock) - -- let logRewind = - -- -- we consider a fork of height more than 3 to be notable. - -- if ancestorHeight + 3 < currHeight - -- then logWarnPact - -- else logDebugPact - -- logRewind $ - -- "execValidateBlock: rewound " <> sshow (currHeight - ancestorHeight) <> " blocks" - -- (totalGasUsed, result) <- case results of - -- [r] -> return r - -- _ -> internalError "execValidateBlock: wrong number of block results returned from _cpRestoreAndSave." - - -- -- update mempool - -- -- - -- -- Using the parent isn't optimal, since it doesn't delete the txs of - -- -- `currHeader` from the set of pending tx. The reason for this is that the - -- -- implementation 'mpaProcessFork' uses the chain database and at this point - -- -- 'currHeader' is generally not yet available in the database. It would be - -- -- possible to extract the txs from the result and remove them from the set - -- -- of pending txs. However, that would add extra complexity and at little - -- -- gain. - -- -- - -- case parentOfHeaderToValidate of - -- Nothing -> return () - -- Just (ParentHeader p) -> liftIO $ do - -- mpaProcessFork memPoolAccess p - -- mpaSetLastHeader memPoolAccess p - - -- return (result, totalGasUsed) +-- runBlock +-- :: (CanReadablePayloadCas tbl, Logger logger) +-- => logger +-- -> ServiceEnv tbl +-- -> BlockEnv +-- -> BlockPayloadHash +-- -> PayloadData +-- -> BlockHandle +-- -> StateT BlockHandle +-- (ExceptT BlockInvalidError IO) +-- (DList (Pact.Gas, PayloadWithOutputs, Vector Pact.Transaction)) +-- runBlock logger serviceEnv blockEnv payload = do +-- (outputs, finalBlockHandle) <- +-- Pact.execExistingBlock logger serviceEnv blockEnv expectedPayloadHash (CheckablePayload payload) +-- return (DList.singleton outputs, finalBlockHandle, blockHashes) +-- where +-- expectedPayloadHash = _consensusPayloadHash $ _evaluationCtxPayload evalCtx +-- v = _chainwebVersion serviceEnv +-- cid = _chainId serviceEnv + +refreshPayloads :: Logger logger => logger -> ServiceEnv tbl -> IO () +refreshPayloads logger serviceEnv = do + -- note that if this is empty, we wait; taking from it is the way to make us stop + let logOutraced = + liftIO $ logFunctionText logger Debug $ "Refresher outraced by new block" + (_, newBlockCtx, blockInProgress) <- liftIO $ atomically $ readTMVar payloadVar + maybeRefreshedBlockInProgress <- Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> + Checkpointer.readFrom logger v cid sql newBlockCtx (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) $ \blockEnv _bh -> do + let dbEnv = view psBlockDbEnv blockEnv + continueBlock logger serviceEnv dbEnv blockInProgress + case maybeRefreshedBlockInProgress of + -- the block's parent was removed + NoHistory -> logOutraced + Historical refreshedBlockInProgress -> do + outraced <- liftIO $ atomically $ do + (_, _, latestBlockInProgress) <- readTMVar payloadVar + -- the block has been replaced, this is a possible race + if _blockInProgressBlockCtx latestBlockInProgress /= _blockInProgressBlockCtx refreshedBlockInProgress + then return True + else do + writeTMVar payloadVar . (_3 .~ refreshedBlockInProgress) =<< readTMVar payloadVar + return False + if outraced + then logOutraced + else refreshPayloads logger serviceEnv + where + + payloadVar = _psMiningPayloadVar serviceEnv + cid = _chainId serviceEnv + v = _chainwebVersion serviceEnv getPayloadForContext :: CanReadablePayloadCas tbl => Logger logger - => Maybe Hints + => logger + -> ServiceEnv tbl + -> Maybe Hints -> EvaluationCtx ConsensusPayload - -> PactServiceM logger tbl PayloadData -getPayloadForContext h ctx = do - pdb <- view psPdb - candPdb <- view psCandidatePdb - mapM_ (insertPayloadData candPdb) (_consensusPayloadData $ _evaluationCtxPayload ctx) + -> IO PayloadData +getPayloadForContext logger serviceEnv h ctx = do + mapM_ insertPayloadData (_consensusPayloadData $ _evaluationCtxPayload ctx) pld <- liftIO $ getPayload pdb @@ -878,90 +672,86 @@ getPayloadForContext h ctx = do (_evaluationCtxRankedPayloadHash ctx) liftIO $ tableInsert candPdb rh pld return pld - where + where rh = _evaluationCtxRankedPayloadHash ctx + pdb = view psPdb serviceEnv + candPdb = view psCandidatePdb serviceEnv - insertPayloadData candPdb (EncodedPayloadData epld) = case decodePayloadData epld of + insertPayloadData (EncodedPayloadData epld) = case decodePayloadData epld of Right pld -> liftIO $ tableInsert candPdb rh pld Left e -> do - logWarnPact $ "failed to decode encoded payload from evaluation ctx: " <> sshow e + logFunctionText logger Warn $ "failed to decode encoded payload from evaluation ctx: " <> sshow e + +execPreInsertCheckReq + :: (Logger logger) + => logger + -> ServiceEnv tbl + -> Vector Pact.Transaction + -> IO (Vector (Maybe Mempool.InsertError)) +execPreInsertCheckReq logger serviceEnv txs = do + let requestKeys = V.map Pact.cmdToRequestKey txs + logFunctionText logger Info $ "(pre-insert check " <> sshow requestKeys <> ")" + newBlockCtx <- Checkpointer.mkFakeNewBlockCtx + let act sql = Checkpointer.readFromLatest logger v cid sql newBlockCtx $ \blockEnv bh -> do + forM txs $ \tx -> + fmap (either Just (\_ -> Nothing)) $ runExceptT $ do + -- it's safe to use initialBlockHandle here because it's + -- only used to check for duplicate pending txs in a block + () <- mapExceptT liftIO + $ Pact.validateParsedChainwebTx logger blockEnv tx + evalStateT (attemptBuyGas blockEnv tx) bh + Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> + timeoutYield timeoutLimit (act sql) >>= \case + Just r -> do + logDebug_ logger $ "Mempool pre-insert check result: " <> sshow r + pure r + Nothing -> do + logError_ logger $ "Mempool pre-insert check timed out for txs:\n" <> sshow txs + let result = V.map (const $ Just Mempool.InsertErrorTimedOut) txs + logDebug_ logger $ "Mempool pre-insert check result: " <> sshow result + pure result --- execPreInsertCheckReq --- :: (CanReadablePayloadCas tbl, Logger logger) --- => Vector Pact.Transaction --- -> PactServiceM logger tbl (Vector (Maybe Mempool.InsertError)) --- execPreInsertCheckReq txs = pactLabel "execPreInsertCheckReq" $ do --- let requestKeys = V.map Pact.cmdToRequestKey txs --- logInfoPact $ "(request keys = " <> sshow requestKeys <> ")" --- psEnv <- ask --- psState <- get --- logger <- view psLogger --- let timeoutLimit = fromIntegral $ (\(Micros n) -> n) $ _psPreInsertCheckTimeout psEnv --- let act = Checkpointer.readFromLatest $ do --- db <- view psBlockDbEnv --- ph <- view psParentHeader --- v <- view chainwebVersion --- cid <- view chainId --- initialBlockHandle <- use Pact.pbBlockHandle --- let --- parentTime = ParentCreationTime (view blockCreationTime $ _parentHeader ph) --- currHeight = succ $ view blockHeight $ _parentHeader ph --- isGenesis = False --- forM txs $ \tx -> --- fmap (either Just (\_ -> Nothing)) $ runExceptT $ do --- -- it's safe to use initialBlockHandle here because it's --- -- only used to check for duplicate pending txs in a block --- pact5Tx <- mapExceptT liftIO $ Pact.validateParsedChainwebTx --- logger v cid db initialBlockHandle parentTime currHeight isGenesis tx --- attemptBuyGasPact5 logger ph noMiner pact5Tx --- withPactState $ \run -> --- timeoutYield timeoutLimit (run act) >>= \case --- Just r -> do --- logDebug_ logger $ "Mempool pre-insert check result: " <> sshow r --- pure r --- Nothing -> do --- logError_ logger $ "Mempool pre-insert check timed out for txs:\n" <> sshow txs --- let result = V.map (const $ Just Mempool.InsertErrorTimedOut) txs --- logDebug_ logger $ "Mempool pre-insert check result: " <> sshow result --- pure result - --- where --- attemptBuyGas --- :: (Logger logger) --- => logger --- -> BlockCtx --- -> Miner --- -> Pact.Transaction --- -> ExceptT InsertError (Pact.PactBlockM logger tbl) () --- attemptBuyGas logger ph miner tx = do --- let logger' = addLabel ("transaction", "attemptBuyGas") logger --- result <- lift $ Pact.pactTransaction Nothing $ \pactDb -> do --- let txCtx = Pact.BlockCtx ph miner --- -- Note: `mempty` is fine here for the milligas limit. `buyGas` sets its own limit --- -- by necessity --- gasEnv <- Pact.mkTableGasEnv (Pact.MilliGasLimit mempty) Pact.GasLogsDisabled --- Pact.buyGas logger' gasEnv pactDb txCtx (view Pact.payloadObj <$> tx) --- case result of --- Left err -> do --- throwError $ InsertErrorBuyGas $ prettyPact5GasPurchaseFailure $ BuyGasError (Pact.cmdToRequestKey tx) err --- Right (_ :: Pact.EvalResult) -> return () + where + preInsertCheckTimeout = view psPreInsertCheckTimeout serviceEnv + v = _chainwebVersion serviceEnv + cid = _chainId serviceEnv + timeoutLimit = fromIntegral $ (\(Micros n) -> n) preInsertCheckTimeout + attemptBuyGas + :: BlockEnv + -> Pact.Transaction + -> StateT BlockHandle (ExceptT InsertError IO) () + attemptBuyGas blockEnv tx = do + let logger' = addLabel ("transaction", "attemptBuyGas") logger + let bctx = view psBlockCtx blockEnv + result <- pactTransaction blockEnv Nothing $ \pactDb _spv -> do + -- Note: `mempty` is fine here for the milligas limit. `buyGas` sets its own limit + -- by necessity + gasEnv <- Pact.mkTableGasEnv (Pact.MilliGasLimit mempty) Pact.GasLogsDisabled + Pact.buyGas logger' gasEnv pactDb noMiner bctx (view Pact.payloadObj <$> tx) + case result of + Left _err -> do + -- TODO: PP + throwError $ InsertErrorBuyGas $ undefined -- _prettyGasPurchaseFailure $ BuyGasError (Pact.cmdToRequestKey tx) err + Right (_ :: Pact.EvalResult) -> return () execLookupPactTxs :: (CanReadablePayloadCas tbl, Logger logger) - => Maybe ConfirmationDepth + => logger + -> ServiceEnv tbl + -> Maybe ConfirmationDepth -> Vector SB.ShortByteString - -> PactServiceM logger tbl (Historical (HM.HashMap SB.ShortByteString (T2 BlockHeight BlockHash))) -execLookupPactTxs confDepth txs = do -- pactLabel "execLookupPactTxs" $ do + -> IO (Historical (HM.HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash))) +execLookupPactTxs logger serviceEnv confDepth txs = do -- pactLabel "execLookupPactTxs" $ do if V.null txs then return (Historical mempty) else do - go =<< liftIO Checkpointer.fakeNewBlockCtx + go =<< liftIO Checkpointer.mkFakeNewBlockCtx where depth = maybe 0 (fromIntegral . _confirmationDepth) confDepth - go ctx = Checkpointer.readFromNthParent ctx depth $ do - dbenv <- view psBlockDbEnv - fmap (HM.mapKeys coerce) $ liftIO $ Pact.lookupPactTransactions dbenv (coerce txs) - --- pactLabel :: (Logger logger) => Text -> PactServiceM logger tbl x -> PactServiceM logger tbl x --- pactLabel lbl x = localLabelPact ("pact-request", lbl) x + v = _chainwebVersion serviceEnv + cid = _chainId serviceEnv + go ctx = Pool.withResource (_psReadSqlPool serviceEnv) $ \sql -> + Checkpointer.readFromNthParent logger v cid sql ctx depth $ \blockEnv _ -> do + let dbenv = view psBlockDbEnv blockEnv + fmap (HM.mapKeys coerce) $ liftIO $ Pact.lookupPactTransactions dbenv (coerce txs) diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index d08e48625a..34a6f061f8 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -21,6 +21,7 @@ {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Pact.PactService.Checkpointer @@ -32,20 +33,18 @@ -- Checkpointer interaction for Pact service. -- module Chainweb.Pact.PactService.Checkpointer - ( fakeNewBlockCtx + ( mkFakeNewBlockCtx , readFromLatest , readFromNthParent , readFrom , restoreAndSave , rewindTo - , getBlockCtx -- , findLatestValidBlockHeader' -- , findLatestValidBlockHeader -- , exitOnRewindLimitExceeded - , PactBlockM(..) , getEarliestBlock -- , lookupBlock - , lookupBlockByEvalCtx + , lookupRankedBlockHash , lookupParentBlockHash , lookupBlockWithHeight , getPayloadsAfter @@ -55,10 +54,7 @@ module Chainweb.Pact.PactService.Checkpointer import Control.Lens hiding ((:>), (:<)) import Control.Monad -import Control.Monad.Except import Control.Monad.Reader -import Data.List.NonEmpty (NonEmpty ((:|))) -import qualified Data.List.NonEmpty as NEL import Data.Maybe import Data.Monoid hiding (Product (..)) import GHC.Stack @@ -77,7 +73,6 @@ import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils import qualified Chainweb.Pact.Backend.Utils as PactDb import Chainweb.Pact.Types -import qualified Chainweb.Pact.Types as Pact import Chainweb.Parent import Chainweb.PayloadProvider import Chainweb.Ranked @@ -85,12 +80,17 @@ import Chainweb.Time import Chainweb.Utils hiding (check) import Chainweb.Version import Chainweb.Version.Guards (pact5) +import Control.Exception.Safe (MonadMask) +import Data.List.NonEmpty (NonEmpty) +import qualified Data.List.NonEmpty as NE +import Chainweb.Pact.Backend.ChainwebPactDb (lookupRankedBlockHash) +import Control.Monad.State.Strict -- readFrom demands to know this context in case it's mining a block. -- But it's not really used by /local, or polling, so we fabricate it -- for those users. -fakeNewBlockCtx :: IO NewBlockCtx -fakeNewBlockCtx = do +mkFakeNewBlockCtx :: IO NewBlockCtx +mkFakeNewBlockCtx = do fakeCreationTime <- Parent . BlockCreationTime <$> getCurrentTimeIntegral return NewBlockCtx -- fake @@ -106,53 +106,57 @@ fakeNewBlockCtx = do -- note: this function will never rewind before genesis. readFromLatest :: Logger logger - => NewBlockCtx - -> PactBlockM logger tbl a - -> PactServiceM logger tbl a -readFromLatest newBlockCtx doRead = readFromNthParent newBlockCtx 0 doRead >>= \case - NoHistory -> error "readFromLatest: failed to grab the latest header, this is a bug in chainweb" - Historical a -> return a + => logger + -> ChainwebVersion + -> ChainId + -> SQLiteEnv + -> NewBlockCtx + -> (BlockEnv -> BlockHandle -> IO a) + -> IO a +readFromLatest logger v cid sql newBlockCtx doRead = + readFromNthParent logger v cid sql newBlockCtx 0 doRead >>= \case + NoHistory -> error "readFromLatest: failed to grab the latest header, this is a bug in chainweb" + Historical a -> return a -- read-only rewind to the nth parent before the latest block. -- note: this function will never rewind before genesis. readFromNthParent - :: forall logger tbl a - . Logger logger - => NewBlockCtx + :: Logger logger + => logger + -> ChainwebVersion + -> ChainId + -> SQLiteEnv + -> NewBlockCtx -> Word - -> PactBlockM logger tbl a - -> PactServiceM logger tbl (Historical a) -readFromNthParent newBlockCtx n doRead = do - sql <- view psReadWriteSql - v <- view chainwebVersion - cid <- view chainId + -> (BlockEnv -> BlockHandle -> IO a) + -> IO (Historical a) +readFromNthParent logger v cid sql newBlockCtx n doRead = do withSavepoint sql ReadFromNSavepoint $ do - latest <- _consensusStateLatest <$> getConsensusState + latest <- _consensusStateLatest . fromJuste <$> getConsensusState sql if genesisHeight v cid + fromIntegral @Word @BlockHeight n < _syncStateHeight latest then return NoHistory else do - maybeNthBlock <- lookupBlockWithHeight - (_syncStateHeight latest - fromIntegral @Word @BlockHeight n) - case maybeNthBlock of - -- this case for shallow nodes without enough history - Nothing -> return NoHistory - Just nthBlock -> - readFrom newBlockCtx (parentBlockHeight v cid nthBlock) doRead + let targetHeight = _syncStateHeight latest - fromIntegral @Word @BlockHeight n + lookupBlockWithHeight sql targetHeight >>= \case + -- this case for shallow nodes without enough history + Nothing -> return NoHistory + Just nthBlock -> + readFrom logger v cid sql newBlockCtx (parentBlockHeight v cid nthBlock) doRead -- read-only rewind to a target block. -- if that target block is missing, return Nothing. readFrom :: Logger logger - => NewBlockCtx + => logger + -> ChainwebVersion + -> ChainId + -> SQLiteEnv + -> NewBlockCtx -- ^ you can fake this if you're not making a new block -> Parent RankedBlockHash - -> PactBlockM logger tbl a - -> PactServiceM logger tbl (Historical a) -readFrom newBlockCtx parent doRead = do - sql <- view psReadWriteSql - logger <- view psLogger - v <- view chainwebVersion - cid <- view chainId + -> (BlockEnv -> BlockHandle -> IO a) + -> IO (Historical a) +readFrom logger v cid sql newBlockCtx parent doRead = do let blockCtx = BlockCtx { _bctxParentCreationTime = _newBlockCtxParentCreationTime newBlockCtx , _bctxParentHash = _rankedBlockHashHash <$> parent @@ -161,15 +165,14 @@ readFrom newBlockCtx parent doRead = do , _bctxChainwebVersion = v , _bctxChainId = cid } - e <- ask liftIO $ withSavepoint sql ReadFromSavepoint $ do latestHeader <- _syncStateRankedBlockHash . _consensusStateLatest <$> - ChainwebPactDb.throwOnDbError (ChainwebPactDb.getConsensusState sql) + ChainwebPactDb.throwOnDbError (fromJuste <$> ChainwebPactDb.getConsensusState sql) -- is the parent the latest header, i.e., can we get away without rewinding? let parentIsLatestHeader = latestHeader == unwrapParent parent let currentHeight = _bctxCurrentBlockHeight blockCtx if pact5 v cid currentHeight - then PactDb.getEndTxId v cid "doReadFrom" sql parent >>= traverse \startTxId -> do + then PactDb.getEndTxId v cid sql parent >>= traverse \startTxId -> do let blockHandlerEnv = ChainwebPactDb.BlockHandlerEnv { ChainwebPactDb._blockHandlerDb = sql @@ -182,26 +185,18 @@ readFrom newBlockCtx parent doRead = do , ChainwebPactDb._blockHandlerAtTip = parentIsLatestHeader } let pactDb = ChainwebPactDb.chainwebPactBlockDb blockHandlerEnv - fmap fst $ - runPactServiceM e $ - Pact.runPactBlockM blockCtx pactDb (emptyBlockHandle startTxId) doRead + let blockEnv = BlockEnv blockCtx pactDb + doRead blockEnv (emptyBlockHandle startTxId) else error "Pact 4 blocks are not playable anymore" -- the special case where one doesn't want to extend the chain, just rewind it. -rewindTo :: Logger logger => RankedBlockHash -> PactServiceM logger tbl () -rewindTo ancestor = do - sql <- view psReadWriteSql - v <- view chainwebVersion - cid <- view chainId - liftIO $ withSavepoint sql RewindSavePoint $ +rewindTo + :: ChainwebVersion -> ChainId -> SQLiteEnv + -> RankedBlockHash -> IO () +rewindTo v cid sql ancestor = do + withSavepoint sql RewindSavePoint $ void $ PactDb.rewindDbTo v cid sql ancestor -getBlockCtx :: EvaluationCtx p -> PactServiceM logger tbl BlockCtx -getBlockCtx ec = do - cid <- view chainId - v <- view chainwebVersion - return $! blockCtxOfEvaluationCtx v cid ec - -- TODO: log more? -- | Given a list of blocks in ascending order, rewind to the first -- of them and play all of the subsequent blocks, saving the result persistently. @@ -214,50 +209,54 @@ getBlockCtx ec = do -- - the first block's parent must be an ancestor of the latest block in the database. -- - each subsequent block must be the direct child of the previous block. restoreAndSave - :: forall logger err q tbl. - (Logger logger, Monoid q, HasCallStack) - => NonEmpty (ExceptT err (PactBlockM logger tbl) q, EvaluationCtx BlockPayloadHash) - -> ExceptT err (PactServiceM logger tbl) q -restoreAndSave blocks@((_, forkPoint) :| _) = do - sql <- view psReadWriteSql - e <- ask + :: forall logger m q. + (Logger logger, MonadIO m, MonadMask m, Semigroup q, HasCallStack) + => logger + -> ChainwebVersion + -> ChainId + -> SQLiteEnv + -> NonEmpty (BlockCtx, BlockEnv -> StateT BlockHandle m (q, (BlockHash, BlockPayloadHash))) + -> m q +restoreAndSave logger v cid sql blocks = do withSavepoint sql RestoreAndSaveSavePoint $ do -- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above. - txid <- liftIO $ PactDb.rewindDbTo - (_chainwebVersion e) - (_chainId e) + startTxId <- liftIO $ PactDb.rewindDbTo + v + cid sql - (unwrapParent $ _evaluationCtxRankedParentHash forkPoint) - mapExceptT liftIO $ extend e (mempty, txid) (NEL.toList blocks) + (unwrapParent $ _bctxParentRankedBlockHash $ fst $ NE.head blocks) + + (mStart, startTxId') <- executeBlock startTxId (NE.head blocks) + extend mStart startTxId' (NE.tail blocks) + where - extend e (m, startTxId) = \case - (blockAction, evalCtx) : subsequentBlocks -> let - !bh = _evaluationCtxCurrentHeight evalCtx + + extend !acc !startTxId (blk:blks) = do + (acc', endTxId) <- executeBlock startTxId blk + -- compute the accumulator strictly + let !acc'' = acc <> acc' + extend acc'' endTxId blks + extend !acc !_ [] = return acc + + executeBlock startTxId (blockCtx, blockAction) = do + let + !bh = _bctxCurrentBlockHeight blockCtx blockEnv = ChainwebPactDb.BlockHandlerEnv - { ChainwebPactDb._blockHandlerDb = _psReadWriteSql e - , ChainwebPactDb._blockHandlerLogger = _psLogger e - , ChainwebPactDb._blockHandlerVersion = _chainwebVersion e + { ChainwebPactDb._blockHandlerDb = sql + , ChainwebPactDb._blockHandlerLogger = logger + , ChainwebPactDb._blockHandlerVersion = v , ChainwebPactDb._blockHandlerBlockHeight = bh - , ChainwebPactDb._blockHandlerChainId = _chainId e + , ChainwebPactDb._blockHandlerChainId = cid , ChainwebPactDb._blockHandlerMode = Pact.Transactional , ChainwebPactDb._blockHandlerUpperBoundTxId = startTxId , ChainwebPactDb._blockHandlerAtTip = True } pactDb = ChainwebPactDb.chainwebPactBlockDb blockEnv - in if pact5 (_chainwebVersion e) (_chainId e) bh then do - - let blockCtx = blockCtxOfEvaluationCtx (_chainwebVersion e) (_chainId e) evalCtx - - let runBlock = mapExceptT - (\r -> runPactServiceM e $ fmap - (\(eitherR, hndl) -> (,hndl) <$> eitherR) - (Pact.runPactBlockM blockCtx pactDb (emptyBlockHandle startTxId) r) - ) + in if pact5 v cid bh then do -- run the block - (m', blockHandle) <- runBlock blockAction - -- compute the accumulator early - let !m'' = m <> m' + ((m, blockInfo), blockHandle) <- + runStateT (blockAction (BlockEnv blockCtx pactDb)) (emptyBlockHandle startTxId) -- TODO PP: also check the child matches the parent height -- case maybeParent of -- Nothing @@ -269,156 +268,42 @@ restoreAndSave blocks@((_, forkPoint) :| _) = do -- <> sshow (_rankedBlockHashHeight ph) <> ", child height " <> sshow (_rankedBlockHashHeight nextBlock) -- _ -> return () liftIO $ ChainwebPactDb.commitBlockStateToDatabase - (_psReadWriteSql e) - evalCtx + sql + (Ranked bh blockInfo) blockHandle - extend e (m'', _blockHandleTxId blockHandle) subsequentBlocks + return (m, _blockHandleTxId blockHandle) else error $ "Pact 5 block executed on block height before Pact 5 fork, height: " <> sshow bh - [] -> return m --- --- -------------------------------------------------------------------------- -- --- Incremental rewindTo - --- | INTERNAL FUNCTION. DON'T USE UNLESS YOU KNOW WHAT YOU DO. --- --- Used for large incremental rewinds during pact history replay. Unlike --- @rewindTo@, this version is not transactional but instead incrementally --- commits the intermediate evaluation state to the pact database. --- --- Rewinds the pact state to the given parent header. --- --- If the rewind is deeper than the optionally provided rewind limit, an --- exception is raised. --- --- rewindToIncremental --- :: forall logger tbl --- . (HasCallStack, CanReadablePayloadCas tbl, Logger logger) --- => Maybe RewindLimit --- -- ^ if set, limit rewinds to this delta --- -> (Parent BlockHeader) --- -- ^ The parent header which is the rewind target --- -> PactServiceM logger tbl () --- rewindToIncremental rewindLimit (Parent parent) = do - --- latestHeader <- findLatestValidBlockHeader' >>= maybe failNonGenesisOnEmptyDb return - --- failOnTooLowRequestedHeight latestHeader --- playFork latestHeader - --- where --- parentHeight = view blockHeight parent - - --- -- TODO: PP --- -- failOnTooLowRequestedHeight lastHeader = case rewindLimit of --- -- Just limit --- -- | let limitHeight = BlockHeight $ _rewindLimit limit --- -- , parentHeight + 1 + limitHeight < lastHeight -> -- need to stick with addition because Word64 --- -- throwM $ RewindLimitExceeded limit (Just lastHeader) (Just parent) --- -- _ -> return () --- -- where --- -- lastHeight = view blockHeight lastHeader - --- failNonGenesisOnEmptyDb = error "impossible: playing non-genesis block to empty DB" - --- playFork :: BlockHeader -> PactServiceM logger tbl () --- playFork lastHeader = do --- bhdb <- asks _psBlockHeaderDb --- commonAncestor <- liftIO $ forkEntry bhdb lastHeader parent --- cp <- view psCheckpointer --- payloadDb <- view psPdb --- let ancestorHeight = view blockHeight commonAncestor - --- logger <- view psLogger - --- -- 'getBranchIncreasing' expects an 'IO' callback because it --- -- maintains an 'TreeDB' iterator. 'withPactState' allows us to call --- -- pact service actions from the callback. - --- (_ :> c) <- withPactState $ \runPact -> --- getBranchIncreasing bhdb parent (int ancestorHeight) $ \newBlocks -> do - --- -- fastforwards all blocks in a chunk in a single database --- -- transaction. --- -- invariant: when playing a chunk, the checkpointer must --- -- already be at `cur`. --- let playChunk :: IORef BlockHeight -> BlockHeader -> Stream (Of BlockHeader) IO r -> IO (Of BlockHeader r) --- playChunk heightRef cur blockChunk = runPact $ do --- (r, Last header) <- restoreAndSave --- (Just $ Parent cur) --- $ blockChunk & S.map --- (\blockHeader -> do --- payload <- liftIO $ lookupPayloadWithHeight payloadDb (Just $ view blockHeight blockHeader) (view blockPayloadHash blockHeader) >>= \case --- Nothing -> internalError --- $ "Checkpointer.rewindTo.fastForward: lookup of payload failed" --- <> ". BlockPayloadHash: " <> encodeToText (view blockPayloadHash blockHeader) --- <> ". Block: "<> encodeToText (ObjectEncoded blockHeader) --- Just x -> return $ payloadWithOutputsToPayloadData x --- liftIO $ writeIORef heightRef (view blockHeight blockHeader) --- PactBlockM $ Pair --- (void $ Pact4.execBlock blockHeader (CheckablePayload payload)) --- (void $ Pact.execExistingBlock blockHeader (CheckablePayload payload)) --- return (Last (Just blockHeader), blockHeader) --- ) - --- return $! fromJuste header :> r - --- -- This stream is guaranteed to at least contain @e@. --- (curHdr, remaining) <- fromJuste <$> S.uncons newBlocks - --- -- we have to rewind to the current header to start, for --- -- playChunk's invariant to be satisfied --- Internal.rewindTo cp --- (Just $ Parent curHdr) - --- heightRef <- newIORef (view blockHeight curHdr) --- withAsync (heightProgress (view blockHeight curHdr) heightRef (logInfo_ logger)) $ \_ -> --- remaining --- & S.copy --- & S.length_ --- & S.chunksOf 1000 --- & foldChunksM (playChunk heightRef) curHdr - --- when (c /= 0) $ --- logInfoPact $ "rewindTo.playFork: replayed " <> sshow c <> " blocks" -getEarliestBlock :: PactServiceM logger tbl (Maybe RankedBlockHash) -getEarliestBlock = do - sql <- view psReadWriteSql - liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getEarliestBlock sql +getEarliestBlock :: SQLiteEnv -> IO (Maybe RankedBlockHash) +getEarliestBlock sql = do + ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getEarliestBlock sql -getConsensusState :: PactServiceM logger tbl ConsensusState -getConsensusState = do - sql <- view psReadWriteSql - liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getConsensusState sql +getConsensusState :: SQLiteEnv -> IO (Maybe ConsensusState) +getConsensusState sql = do + ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getConsensusState sql -setConsensusState :: ConsensusState -> PactServiceM logger tbl () -setConsensusState cs = do - sql <- view psReadWriteSql - liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.setConsensusState sql cs +setConsensusState :: SQLiteEnv -> ConsensusState -> IO () +setConsensusState sql cs = do + ChainwebPactDb.throwOnDbError $ ChainwebPactDb.setConsensusState sql cs -lookupBlockWithHeight :: BlockHeight -> PactServiceM logger tbl (Maybe (Ranked (Parent BlockHash))) -lookupBlockWithHeight bh = do - sql <- view psReadWriteSql - liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockWithHeight sql bh +lookupBlockWithHeight :: SQLiteEnv -> BlockHeight -> IO (Maybe (Ranked (Parent BlockHash))) +lookupBlockWithHeight sql bh = do + ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockWithHeight sql bh -lookupBlockByEvalCtx :: EvaluationCtx p -> PactServiceM logger tbl Bool -lookupBlockByEvalCtx rpbh = do - sql <- view psReadWriteSql - liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockByEvalCtx sql rpbh +-- lookupBlockByHash :: SQLiteEnv -> EvaluationCtx p -> IO Bool +-- lookupBlockByHash sql rpbh = do +-- ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockByHash sql rpbh -lookupParentBlockHash :: Parent BlockHash -> PactServiceM logger tbl Bool -lookupParentBlockHash pbh = do - sql <- view psReadWriteSql - liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupParentBlockHash sql pbh +lookupParentBlockHash :: SQLiteEnv -> Parent BlockHash -> IO Bool +lookupParentBlockHash sql pbh = do + ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupParentBlockHash sql pbh -getPayloadsAfter :: Parent BlockHeight -> PactServiceM logger tbl [Ranked BlockPayloadHash] -getPayloadsAfter b = do - sql <- view psReadWriteSql - liftIO $ ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getPayloadsAfter sql b +getPayloadsAfter :: SQLiteEnv -> Parent BlockHeight -> IO [Ranked BlockPayloadHash] +getPayloadsAfter sql b = do + ChainwebPactDb.throwOnDbError $ ChainwebPactDb.getPayloadsAfter sql b -- -------------------------------------------------------------------------- -- -- Utils diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index 0b6a5b345f..d74fe0ccd0 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -23,17 +23,12 @@ module Chainweb.Pact.PactService.ExecBlock , BlockInvalidError(..) ) where -import Chainweb.BlockHeader -import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Mempool.Mempool(BlockFill (..), pactRequestKeyToTransactionHash, InsertError (..)) -import Chainweb.MinerReward import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.ChainwebPactDb (ChainwebPactDb(doChainwebPactDbTransaction)) import Chainweb.Pact.Types import Chainweb.Pact.Transaction import Chainweb.Pact.TransactionExec -import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Time @@ -51,11 +46,9 @@ import Control.Monad.Reader import Control.Monad.State.Strict import Data.ByteString (ByteString) import Data.Coerce -import Data.Decimal import Data.Either (partitionEithers) import Data.Foldable import Data.Maybe -import Data.Text (Text) import Data.Text qualified as T import Data.Vector (Vector) import Data.Vector qualified as V @@ -64,13 +57,11 @@ import Pact.Core.ChainData hiding (ChainId) import Pact.Core.Command.Types qualified as Pact import Pact.Core.Persistence qualified as Pact import Pact.Core.Hash -import Control.Exception.Safe import qualified Pact.Core.Gas as Pact import qualified Pact.JSON.Encode as J import System.Timeout import Utils.Logging.Trace import qualified Data.Set as S -import qualified Pact.Types.Gas as Pact4 import qualified Pact.Core.Gas as P import qualified Data.Text.Encoding as T import qualified Data.HashMap.Strict as HashMap @@ -79,9 +70,6 @@ import qualified Chainweb.Pact.Transaction as Pact import qualified Chainweb.Pact.Validations as Pact import Chainweb.Pact.NoCoinbase import Chainweb.Pact.Backend.Types -import Chainweb.Parent -import Chainweb.BlockCreationTime -import Pact.Core.Pretty qualified as Pact import qualified Data.ByteString.Short as SB import qualified Pact.Core.Hash as Pact import System.LogLevel @@ -90,79 +78,65 @@ import qualified Data.List.NonEmpty as NEL import qualified Pact.Core.Errors as Pact import qualified Pact.Core.Evaluate as Pact import qualified Pact.Core.ChainData as Pact -import qualified Pact.Core.Errors as Pact import qualified Chainweb.Payload as Chainweb import qualified Chainweb.Pact.Types as Pact -import qualified Chainweb.Pact.Backend.ChainwebPactDb as ChainwebPactDb -import Chainweb.PayloadProvider - --- | Calculate miner reward. We want this to error hard in the case where --- block times have finally exceeded the 120-year range. Rewards are calculated --- at regular blockheight intervals. --- --- See: 'rewards/miner_rewards.csv' --- -minerReward - :: ChainwebVersion - -> BlockHeight - -> Decimal -minerReward v = _kda . minerRewardKda . blockMinerReward v -{-# INLINE minerReward #-} runCoinbase :: (Logger logger) - => Miner - -> PactBlockM logger tbl (Either (Pact.PactError Pact.Info) (Pact.CommandResult [Pact.TxLog ByteString] Void)) -runCoinbase miner = do - isGenesis <- view (psBlockCtx . to _bctxIsGenesis) + => logger + -> BlockEnv + -> Miner + -> StateT BlockHandle (ExceptT (Pact.PactError Pact.Info) IO) (Pact.CommandResult [Pact.TxLog ByteString] Void) +runCoinbase logger blockEnv miner = do + let isGenesis = _bctxIsGenesis $ _psBlockCtx blockEnv if isGenesis - then return $ Right noCoinbase + then return noCoinbase else do - logger <- view (psServiceEnv . psLogger) - blockCtx <- view psBlockCtx + let blockCtx = _psBlockCtx blockEnv -- the coinbase request key is not passed here because TransactionIndex -- does not contain coinbase transactions - pactTransaction Nothing $ \db _spv -> - applyCoinbase logger db miner blockCtx + pactTransaction blockEnv Nothing + (\db _spv -> applyCoinbase logger db miner blockCtx) + >>= liftEither -- | Continue adding transactions to an existing block. continueBlock :: forall logger tbl - . (Logger logger, CanReadablePayloadCas tbl) - => ChainwebPactDb - -> MemPoolAccess + . (Logger logger) + => logger + -> ServiceEnv tbl + -> ChainwebPactDb -> BlockInProgress - -> PactServiceM logger tbl BlockInProgress -continueBlock dbEnv mpAccess blockInProgress = do - miner <- maybe (error "no miner but tried continuing block") return =<< view psMiner - let runBlock = runPactBlockM - (_blockInProgressBlockCtx blockInProgress) - dbEnv - (_blockInProgressHandle blockInProgress) - (blockInProgress', _newHandle) <- runBlock $ do + -> IO BlockInProgress +continueBlock logger serviceEnv dbEnv blockInProgress = do + let mpAccess = view psMempoolAccess serviceEnv + miner <- maybe (error "no miner but tried continuing block") return $ _psMiner serviceEnv + let blockCtx = _blockInProgressBlockCtx blockInProgress + let blockEnv = BlockEnv blockCtx dbEnv + (blockInProgress', _newHandle) <- flip runStateT (_blockInProgressHandle blockInProgress) $ do -- update the mempool, ensuring that we reintroduce any transactions that -- were removed due to being completed in a block on a different fork. - blockCtx <- view psBlockCtx - case _bctxIsGenesis blockCtx of + liftIO $ case _bctxIsGenesis blockCtx of True -> do - liftPactServiceM $ - logInfoPact $ T.unwords - [ "(parent height = " <> sshow (_bctxParentHash blockCtx) <> ")" - , "(parent hash = " <> sshow (_bctxParentHeight blockCtx) <> ")" - ] + logFunctionText logger Info $ + T.unwords + [ "(parent height = " <> sshow (_bctxParentHash blockCtx) <> ")" + , "(parent hash = " <> sshow (_bctxParentHeight blockCtx) <> ")" + ] False -> - liftPactServiceM $ logInfoPact "Continuing genesis block" + logFunctionText logger Info + "Continuing genesis block" - blockGasLimit <- view (psServiceEnv . psNewBlockGasLimit) - mTxTimeLimit <- view (psServiceEnv . psNewPayloadTxTimeLimit) + let blockGasLimit = view psNewBlockGasLimit serviceEnv + let mTxTimeLimit = view psNewPayloadTxTimeLimit serviceEnv let txTimeHeadroomFactor :: Double txTimeHeadroomFactor = 5 let txTimeLimit :: Micros -- 2.5 us per unit gas txTimeLimit = fromMaybe (round $ 2.5 * txTimeHeadroomFactor * fromIntegral (view (Pact._GasLimit . to Pact._gas) blockGasLimit)) mTxTimeLimit - liftPactServiceM $ do - logDebugPact $ T.unwords + liftIO $ + logFunctionText logger Debug $ T.unwords [ "Block gas limit:" , sshow blockGasLimit <> "," , "Transaction time limit:" @@ -181,14 +155,14 @@ continueBlock dbEnv mpAccess blockInProgress = do let fetchLimit = fromIntegral $ (view (Pact._GasLimit . to Pact._gas) blockGasLimit) `div` 1000 (BlockFill { _bfGasLimit = finalGasLimit }, valids, invalids) <- - refill miner fetchLimit txTimeLimit initState + refill blockEnv miner fetchLimit txTimeLimit initState - finalBlockHandle <- use pbBlockHandle + finalBlockHandle <- get liftIO $ mpaBadlistTx mpAccess (V.fromList $ fmap pactRequestKeyToTransactionHash $ concat invalids) - liftPactServiceM $ logDebugPact $ "Order of completed transactions: " <> sshow (map (Pact.unRequestKey . Pact._crReqKey . snd) $ concat $ reverse valids) + liftIO $ logFunctionText logger Debug $ "Order of completed transactions: " <> sshow (map (Pact.unRequestKey . Pact._crReqKey . snd) $ concat $ reverse valids) let !blockInProgress' = blockInProgress & blockInProgressHandle .~ finalBlockHandle @@ -197,40 +171,40 @@ continueBlock dbEnv mpAccess blockInProgress = do & blockInProgressRemainingGasLimit .~ finalGasLimit - liftPactServiceM $ logDebugPact $ "Final block transaction order: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . snd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) + liftIO $ logFunctionText logger Debug $ "Final block transaction order: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . snd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) return blockInProgress' return blockInProgress' where - refill miner fetchLimit txTimeLimit blockFillState = over _2 reverse <$> go [] [] blockFillState + refill blockEnv miner fetchLimit txTimeLimit blockFillState = over _2 reverse <$> go [] [] blockFillState where go :: [CompletedTransactions] -> [InvalidTransactions] -> BlockFill - -> PactBlockM logger tbl (BlockFill, [CompletedTransactions], [InvalidTransactions]) + -> StateT BlockHandle IO (BlockFill, [CompletedTransactions], [InvalidTransactions]) go completedTransactions invalidTransactions prevBlockFillState@BlockFill { _bfGasLimit = prevRemainingGas, _bfCount = prevFillCount, _bfTxHashes = prevTxHashes } - | prevFillCount > fetchLimit = liftPactServiceM $ do - logInfoPact $ "Refill fetch limit exceeded (" <> sshow fetchLimit <> ")" + | prevFillCount > fetchLimit = liftIO $ do + logFunctionText logger Info $ "Refill fetch limit exceeded (" <> sshow fetchLimit <> ")" pure stop | prevRemainingGas == Pact.GasLimit (Pact.Gas 0) = pure stop | otherwise = do - newTxs <- getBlockTxs prevBlockFillState - liftPactServiceM $ logDebugPact $ "Refill: fetched transaction: " <> sshow (V.length newTxs) + newTxs <- liftIO $ getBlockTxs blockEnv prevBlockFillState + liftIO $ logFunctionText logger Debug $ "Refill: fetched transaction: " <> sshow (V.length newTxs) if V.null newTxs then do - liftPactServiceM $ logDebugPact $ "Refill: no new transactions" + liftIO $ logFunctionText logger Debug "Refill: no new transactions" pure stop else do (newCompletedTransactions, newInvalidTransactions, newBlockGasLimit, timedOut) <- execNewTransactions prevRemainingGas txTimeLimit newTxs - liftPactServiceM $ do - logDebugPact $ "Refill: included request keys: " <> sshow @[Hash] (fmap (Pact.unRequestKey . Pact._crReqKey . snd) newCompletedTransactions) - logDebugPact $ "Refill: badlisted request keys: " <> sshow @[Hash] (fmap Pact.unRequestKey newInvalidTransactions) + liftIO $ do + logFunctionText logger Debug $ "Refill: included request keys: " <> sshow @[Hash] (fmap (Pact.unRequestKey . Pact._crReqKey . snd) newCompletedTransactions) + logFunctionText logger Debug $ "Refill: badlisted request keys: " <> sshow @[Hash] (fmap Pact.unRequestKey newInvalidTransactions) let newBlockFillState = BlockFill { _bfCount = succ prevFillCount @@ -260,17 +234,14 @@ continueBlock dbEnv mpAccess blockInProgress = do :: P.GasLimit -> Micros -> Vector Pact.Transaction - -> PactBlockM logger tbl (CompletedTransactions, InvalidTransactions, P.GasLimit, Bool) + -> StateT BlockHandle IO (CompletedTransactions, InvalidTransactions, P.GasLimit, Bool) execNewTransactions remainingGas timeLimit txs = do - env <- ask - startBlockHandle <- use pbBlockHandle - blockCtx <- view psBlockCtx - logger' <- view (psServiceEnv . psLogger) + startBlockHandle <- get + let blockCtx = view psBlockCtx blockEnv ((txResults, timedOut), (finalBlockHandle, Identity finalRemainingGas)) <- liftIO $ flip runStateT (startBlockHandle, Identity remainingGas) $ foldr (\(txIdxInBlock, tx) rest -> StateT $ \s -> do - let logger = addLabel ("transactionHash", sshow (Pact._cmdHash tx)) logger' - let env' = env & psServiceEnv . psLogger .~ logger + let logger' = addLabel ("transactionHash", sshow (Pact._cmdHash tx)) logger let timeoutFunc runTx = if _bctxIsGenesis blockCtx then do @@ -279,7 +250,7 @@ continueBlock dbEnv mpAccess blockInProgress = do else newTimeout (fromIntegral @Micros @Int timeLimit) runTx m <- liftIO $ timeoutFunc - $ runExceptT $ runStateT (applyPactCmd env' miner (TxBlockIdx txIdxInBlock) tx) s + $ runExceptT $ runStateT (applyCmdInBlock logger' serviceEnv blockEnv miner (TxBlockIdx txIdxInBlock) tx) s case m of Nothing -> do logFunctionJson logger Warn $ Aeson.object @@ -293,29 +264,29 @@ continueBlock dbEnv mpAccess blockInProgress = do Just (Left err) -> do logFunctionText logger Debug $ -- TODO PP: prettify - "applyCmd failed to buy gas: " <> sshow err + "applyCmdInBlock failed to buy gas: " <> sshow err ((as, timedOut), s') <- runStateT rest s return ((Left (Pact._cmdHash tx):as, timedOut), s') Just (Right (a, s')) -> do - logFunctionText logger Debug "applyCmd buy gas succeeded" + logFunctionText logger Debug "applyCmdInBlock buy gas succeeded" ((as, timedOut), s'') <- runStateT rest s' let !txBytes = commandToBytes tx return ((Right (txBytes, a):as, timedOut), s'') ) (return ([], False)) (zip [0..] (V.toList txs)) - pbBlockHandle .= finalBlockHandle + put finalBlockHandle let (invalidTxHashes, completedTxs) = partitionEithers txResults return (completedTxs, Pact.RequestKey <$> invalidTxHashes, finalRemainingGas, timedOut) - getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector Pact.Transaction) - getBlockTxs blockFillState = do - liftPactServiceM $ logDebugPact "Refill: fetching transactions" - blockCtx <- view psBlockCtx - logger <- view (psServiceEnv . psLogger) + getBlockTxs :: BlockEnv -> BlockFill -> IO (Vector Pact.Transaction) + getBlockTxs blockEnv blockFillState = do + liftIO $ logFunctionText logger Debug "Refill: fetching transactions" let validate _bha txs = do forM txs $ \tx -> - fmap (tx <$) $ runExceptT (validateParsedChainwebTx logger dbEnv blockCtx tx) + fmap (tx <$) $ runExceptT (validateParsedChainwebTx logger blockEnv tx) + let mpAccess = _psMempoolAccess serviceEnv + let blockCtx = _psBlockCtx blockEnv liftIO $ mpaGetBlock mpAccess blockFillState validate (evaluationCtxOfBlockCtx blockCtx) type CompletedTransactions = [(Chainweb.Transaction, Pact.OffChainCommandResult)] @@ -323,24 +294,25 @@ type InvalidTransactions = [Pact.RequestKey] -- Apply a Pact command in the current block. -- This function completely ignores timeouts! -applyPactCmd +applyCmdInBlock :: (Traversable t, Logger logger) - => PactBlockEnv logger tbl + => logger + -> ServiceEnv tbl + -> BlockEnv -> Miner -> TxIdxInBlock -> Pact.Transaction -> StateT (BlockHandle, t P.GasLimit) (ExceptT TxInvalidError IO) - (Pact.CommandResult [Pact.TxLog ByteString] (Pact.PactError Pact.Info)) -applyPactCmd env miner txIdxInBlock tx = StateT $ \(blockHandle, blockGasRemaining) -> do + OffChainCommandResult +applyCmdInBlock logger serviceEnv blockEnv miner txIdxInBlock tx = StateT $ \(blockHandle, blockGasRemaining) -> do -- we set the command gas limit to the minimum of its original value and the remaining gas in the block -- this way Pact never uses more gas than remains in the block, and the tx fails otherwise let alteredTx = (view payloadObj <$> tx) & Pact.cmdPayload . Pact.pMeta . pmGasLimit %~ maybe id min (blockGasRemaining ^? traversed) - resultOrGasError <- liftIO $ runReaderT - (unsafeApplyPactCmd blockHandle - (initialGasOf (tx ^. Pact.cmdPayload)) - alteredTx) - env + resultOrGasError <- liftIO $ unsafeApplyCmdInBlock + blockHandle + (initialGasOf (tx ^. Pact.cmdPayload)) + alteredTx case resultOrGasError of Left err -> throwError err Right (result, nextHandle) @@ -368,26 +340,22 @@ applyPactCmd env miner txIdxInBlock tx = StateT $ \(blockHandle, blockGasRemaini where -- | Apply a Pact command in the current block. -- This function completely ignores timeouts and the block gas limit! - unsafeApplyPactCmd - :: (Logger logger) - => BlockHandle + unsafeApplyCmdInBlock + :: BlockHandle -> Pact.Gas -> Pact.Command (Pact.Payload PublicMeta Pact.ParsedCode) - -> ReaderT - (PactBlockEnv logger tbl) - IO - (Either TxInvalidError - (Pact.CommandResult [Pact.TxLog ByteString] (Pact.PactError Pact.Info), BlockHandle)) - unsafeApplyPactCmd blockHandle initialGas cmd = do - _txFailuresCounter <- view (psServiceEnv . psTxFailuresCounter) - logger <- view (psServiceEnv . psLogger) - gasLogger <- view (psServiceEnv . psGasLogger) - dbEnv <- view psBlockDbEnv - blockCtx <- view psBlockCtx + -> IO + (Either TxInvalidError (OffChainCommandResult, BlockHandle)) + unsafeApplyCmdInBlock blockHandle initialGas cmd = do + -- TODO PP: use + let gasLogger = _psGasLogger serviceEnv + let _txFailuresCounter = _psTxFailuresCounter serviceEnv + let dbEnv = _psBlockDbEnv blockEnv + let blockCtx = _psBlockCtx blockEnv -- TODO: trace more info? let rk = Pact.RequestKey $ Pact._cmdHash cmd (resultOrError, blockHandle') <- - liftIO $ trace' (logFunction logger) "applyCmd" computeTrace (\_ -> 0) $ + liftIO $ trace' (logFunction logger) "applyCmdInBlock" computeTrace (\_ -> 0) $ doChainwebPactDbTransaction dbEnv blockHandle (Just rk) $ \pactDb spv -> if _bctxIsGenesis blockCtx then do @@ -447,12 +415,11 @@ applyPactCmd env miner txIdxInBlock tx = StateT $ \(blockHandle, blockGasRemaini validateParsedChainwebTx :: (Logger logger) => logger - -> ChainwebPactDb - -> BlockCtx + -> BlockEnv -- ^ reference time for tx validation. -> Pact.Transaction -> ExceptT InsertError IO () -validateParsedChainwebTx _logger db blockCtx tx +validateParsedChainwebTx _logger blockEnv tx | _bctxIsGenesis blockCtx = pure () | otherwise = do checkUnique tx @@ -462,6 +429,8 @@ validateParsedChainwebTx _logger db blockCtx tx checkTimes tx return () where + db = _psBlockDbEnv blockEnv + blockCtx = _psBlockCtx blockEnv cid = blockCtx ^. chainId v = blockCtx ^. chainwebVersion bh = _bctxCurrentBlockHeight blockCtx @@ -530,27 +499,31 @@ pact5TransactionsFromPayload plData = do return $! V.fromList theRights where toCWTransaction bs = - evaluate (force (codecDecode payloadCodec $ _transactionBytes bs)) + evaluate (force (codecDecode commandCodec $ _transactionBytes bs)) + +-- introduce a new state temporarily for an inner action +weaveStatesFst :: Monad m => StateT (s1, s2) m a -> s2 -> StateT s1 m (a, s2) +weaveStatesFst act s2 = StateT $ \s1 -> do + (a, (s1', s2')) <- runStateT act (s1, s2) + return ((a, s2'), s1') execExistingBlock :: (CanReadablePayloadCas tbl, Logger logger) - => EvaluationCtx BlockPayloadHash + => logger + -> ServiceEnv tbl + -> BlockEnv -> CheckablePayload - -> ExceptT BlockInvalidError (PactBlockM logger tbl) (P.Gas, PayloadWithOutputs, Vector Pact.RequestKey) -execExistingBlock evaluationCtx payload = do - blockCtx <- view psBlockCtx + -> StateT BlockHandle (ExceptT BlockInvalidError IO) (P.Gas, PayloadWithOutputs, Vector Pact.Transaction) +execExistingBlock logger serviceEnv blockEnv payload = do + let blockCtx = _psBlockCtx blockEnv let plData = checkablePayloadToPayloadData payload miner :: Miner <- decodeStrictOrThrow (_minerData $ view payloadDataMiner plData) - txs <- pact5TransactionsFromPayload plData - logger <- view (psServiceEnv . psLogger) - -- TODO: Pact5: ACTUALLY log gas - _gasLogger <- view (psServiceEnv . psGasLogger) - v <- view chainwebVersion - db <- view psBlockDbEnv + txs <- lift $ pact5TransactionsFromPayload plData + let v = view chainwebVersion serviceEnv let errors <- liftIO $ flip foldMap txs $ \tx -> do errorOrSuccess <- runExceptT $ - validateParsedChainwebTx logger db blockCtx tx + validateParsedChainwebTx logger blockEnv tx case errorOrSuccess of Right () -> return [] Left err -> return [(Pact.RequestKey (Pact._cmdHash tx), err)] @@ -558,57 +531,50 @@ execExistingBlock evaluationCtx payload = do Nothing -> return () Just errorsNel -> throwError $ BlockInvalidDueToInvalidTxs errorsNel - coinbaseResult <- lift (runCoinbase miner) >>= \case - Left err -> throwError $ BlockInvalidDueToCoinbaseFailure err - Right r -> return (absurd <$> r) - - -- TODO pact 5: make this less nasty? - postCoinbaseBlockHandle <- use pbBlockHandle + coinbaseResult <- fmap (fmap absurd) + $ mapStateT (withExceptT BlockInvalidDueToCoinbaseFailure) + $ runCoinbase logger blockEnv miner let blockGasLimit = Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit v (_bctxCurrentBlockHeight blockCtx) - env <- ask - (V.fromList -> results, (finalHandle, _finalBlockGasLimit)) <- - flip runStateT (postCoinbaseBlockHandle, blockGasLimit) $ + (V.fromList -> results, _finalBlockGasLimit) <- flip weaveStatesFst blockGasLimit $ + -- flip runStateT (postCoinbaseBlockHandle, blockGasLimit) $ forM (zip [0..] (V.toList txs)) $ \(txIdxInBlock, tx) -> (tx,) <$> (mapStateT (mapExceptT (liftIO . fmap (over _Left BlockInvalidDueToInvalidTxAtRuntime))) $ - applyPactCmd env miner (TxBlockIdx txIdxInBlock) tx) + applyCmdInBlock logger serviceEnv blockEnv miner (TxBlockIdx txIdxInBlock) tx) -- incorporate the final state of the transactions into the block state - pbBlockHandle .= finalHandle let !totalGasUsed = foldOf (folded . _2 . to Pact._crGas) results pwo <- liftEither . over _Left BlockInvalidDueToOutputMismatch $ - validateHashes evaluationCtx payload miner (Transactions results coinbaseResult) - let reqKeys = Pact._crReqKey . snd <$> results - return (totalGasUsed, pwo, reqKeys) + validateHashes blockCtx payload miner (Transactions results coinbaseResult) + return (totalGasUsed, pwo, txs) -- | Check that the two payloads agree. If we have access to the outputs, we -- check those too. validateHashes - :: EvaluationCtx BlockPayloadHash - -- ^ expected payload hash + :: BlockCtx -> CheckablePayload -> Miner -> Transactions Pact.Transaction Pact.OffChainCommandResult -> Either BlockOutputMismatchError PayloadWithOutputs -validateHashes evaluationCtx payload miner transactions = - if newHash == _evaluationCtxPayload evaluationCtx +validateHashes blockCtx payload miner transactions = + if newHash == expectedPayloadHash then Right actualPwo else Left $ BlockOutputMismatchError - { blockOutputMismatchCtx = evaluationCtx + { blockOutputMismatchCtx = blockCtx , blockOutputMismatchActualPayload = actualPwo - , blockOutputMismatchExpectedPayload = case payload of - CheckablePayload _ -> Nothing - CheckablePayloadWithOutputs pwo -> Just pwo + , blockOutputMismatchExpectedPayload = payload } where + expectedPayloadHash = checkablePayloadExpectedHash payload - actualPwo = toPayloadWithOutputs miner transactions + actualPwo = toPayloadWithOutputs miner + (over (transactionPairs . mapped . _1) commandToBytes transactions) newHash = _payloadWithOutputsPayloadHash actualPwo diff --git a/src/Chainweb/Pact/RestAPI.hs b/src/Chainweb/Pact/RestAPI.hs index ea211737a1..8737c3a00d 100644 --- a/src/Chainweb/Pact/RestAPI.hs +++ b/src/Chainweb/Pact/RestAPI.hs @@ -202,9 +202,9 @@ ethSpvApi = Proxy type PactServiceApi v c = PactApi v c - :<|> PactSpvApi v c - :<|> EthSpvApi v c - :<|> PactSpv2Api v c + -- :<|> PactSpvApi v c + -- :<|> EthSpvApi v c + -- :<|> PactSpv2Api v c pactServiceApi :: forall (v :: ChainwebVersionT) (c :: ChainIdT) diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index cfb552822b..e0bfd9058c 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -14,6 +14,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE NumericUnderscores #-} module Chainweb.Pact.RestAPI.Server ( PactServerData(..) @@ -25,11 +26,10 @@ module Chainweb.Pact.RestAPI.Server , pollHandler , listenHandler , localHandler -, spvHandler +-- , spvHandler , somePactServer , somePactServers , validateCommand -, validatePact5Command ) where import Control.Applicative @@ -43,7 +43,7 @@ import Control.Monad.Reader import Control.Monad.State.Strict import Control.Monad.Trans.Except (ExceptT, runExceptT, except) -import Data.Aeson as Aeson +import Data.Aeson as Aeson hiding (Success) import Data.Bifunctor (second) import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as BSL @@ -62,6 +62,7 @@ import Data.Singletons import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding +import Data.Validation import Data.Vector (Vector) import qualified Data.Vector as V @@ -134,15 +135,20 @@ import qualified Pact.Core.Hash as Pact import qualified Pact.Core.Gas as Pact import qualified Pact.Core.Command.Client as Pact import qualified Pact.Core.ChainData as Pact +import Chainweb.PayloadProvider.Pact (PactPayloadProvider (..)) +import Chainweb.PayloadProvider.P2P +import Chainweb.Pact.PactService +import Chainweb.Pact.Backend.Types (Historical(..), throwIfNoHistory) +import qualified Pact.Core.StableEncoding as Pact +import qualified Pact.Core.Info as Pact +import Control.Concurrent (threadDelay) -- -------------------------------------------------------------------------- -- data PactServerData logger tbl = PactServerData - { _pactServerDataCutDb :: !CutDB.CutDb - , _pactServerDataMempool :: !(MempoolBackend Pact.Transaction) + { _pactServerDataMempool :: !(MempoolBackend Pact.Transaction) , _pactServerDataLogger :: !logger - -- , _pactServerDataPact :: !PactExecutionService - , _pactServerDataPayloadDb :: !(PayloadDb tbl) + , _pactServerDataPact :: !(PactPayloadProvider logger tbl) } newtype PactServerData_ (v :: ChainwebVersionT) (c :: ChainIdT) logger tbl @@ -155,7 +161,6 @@ data SomePactServerData = forall v c logger tbl Logger logger) => SomePactServerData (PactServerData_ v c logger tbl) - somePactServerData :: CanReadablePayloadCas tbl => Logger logger @@ -170,7 +175,6 @@ somePactServerData v cid db = (SomeChainIdT (Proxy :: Proxy cidt)) -> SomePactServerData (PactServerData_ @vt @cidt db) - pactServer :: forall v c tbl logger . KnownChainwebVersionSymbol v @@ -181,25 +185,22 @@ pactServer -> Server (PactServiceApi v c) pactServer d = pactApiHandlers - :<|> pactSpvHandler - :<|> ethSpvHandler - :<|> pactSpv2Handler + -- :<|> pactSpvHandler + -- :<|> ethSpvHandler + -- :<|> pactSpv2Handler where - cid = FromSing (SChainId :: Sing c) mempool = _pactServerDataMempool d logger = _pactServerDataLogger d - pact = _pactServerDataPact d - cdb = _pactServerDataCutDb d - pdb = _pactServerDataPayloadDb d + PactPayloadProvider _ pact = _pactServerDataPact d pactApiHandlers = sendHandler logger mempool - :<|> pollHandler logger cdb pdb cid pact mempool - :<|> listenHandler logger cdb pdb cid pact mempool + :<|> pollHandler logger pact mempool + :<|> listenHandler logger pact mempool :<|> localHandler logger pact - pactSpvHandler = spvHandler logger cdb pdb pact cid - pactSpv2Handler = spv2Handler logger cdb pdb pact cid + -- pactSpvHandler = spvHandler logger cdb pdb pact cid + -- pactSpv2Handler = spv2Handler logger cdb pdb pact cid somePactServer :: SomePactServerData -> SomeServer somePactServer (SomePactServerData (db :: PactServerData_ v c logger tbl)) @@ -254,19 +255,19 @@ sendHandler => logger -> MempoolBackend Pact.Transaction -> Pact.SendRequest - -> Handler Pact.RequestKeys + -> Handler Pact.SendResponse sendHandler logger mempool (Pact.SendRequest (Pact.SubmitBatch cmds)) = Handler $ do liftIO $ logg Info (PactCmdLogSend cmds) - undefined - -- case cmds of - -- Right (fmap Pact.mkPayloadWithText -> cmdsWithParsedPayloads) -> do - -- let cmdsWithParsedPayloadsV = V.fromList $ NEL.toList cmdsWithParsedPayloads - -- -- If any of the txs in the batch fail validation, we reject them all. - -- liftIO (mempoolInsertCheckVerbose mempool cmdsWithParsedPayloadsV) >>= checkResult - -- liftIO (mempoolInsert mempool UncheckedInsert cmdsWithParsedPayloadsV) - -- return $! Pact.RequestKeys $ NEL.map Pact.cmdToRequestKey cmdsWithParsedPayloads - -- Left err -> failWith $ "reading JSON for transaction failed: " <> T.pack err + case traverse (liftError NEL.singleton . Pact.parseCommand) cmds of + Success cmdsWithParsedPayloads -> do + let cmdsWithParsedPayloadsV = V.fromList $ NEL.toList cmdsWithParsedPayloads + -- If any of the txs in the batch fail validation, we reject them all. + liftIO (mempoolInsertCheckVerbose mempool cmdsWithParsedPayloadsV) >>= checkResult + liftIO (mempoolInsert mempool UncheckedInsert cmdsWithParsedPayloadsV) + return $! Pact.SendResponse $ Pact.RequestKeys $ NEL.map Pact.cmdToRequestKey cmdsWithParsedPayloads + Failure errs -> throwError $ setErrJSONPact (J.array $ fmap convertError errs) err400 where + convertError = Pact.pactErrorToOnChainError . fmap Pact.spanInfoToLineInfo failWith :: Text -> ExceptT ServerError IO a failWith err = do liftIO $ logFunctionText logger Info err @@ -295,19 +296,15 @@ sendHandler logger mempool (Pact.SendRequest (Pact.SubmitBatch cmds)) = Handler pollHandler :: (HasCallStack, CanReadablePayloadCas tbl, Logger logger) => logger - -> CutDB.CutDb - -> PayloadDb tbl - -> ChainId - -> PactLookup + -> ServiceEnv tbl -> MempoolBackend Pact.Transaction -> Maybe ConfirmationDepth -> Pact.PollRequest -> Handler Pact.PollResponse -pollHandler logger cdb pdb cid pact mem confDepth (Pact.PollRequest request) = do +pollHandler logger pact mem confDepth (Pact.PollRequest request) = do liftIO $! logg Info $ PactCmdLogPoll $ fmap Pact.requestKeyToB64Text request - Pact.PollResponse <$!> liftIO (internalPoll logger pdb bdb mem pact confDepth request) + Pact.PollResponse <$!> liftIO (internalPoll logger mem pact confDepth request) where - bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb logg = logFunctionJson (setComponent "poll-handler" logger) -- -------------------------------------------------------------------------- -- @@ -317,67 +314,44 @@ pollHandler logger cdb pdb cid pact mem confDepth (Pact.PollRequest request) = d listenHandler :: (CanReadablePayloadCas tbl, Logger logger) => logger - -> CutDB.CutDb - -> PayloadDb tbl - -> ChainId - -> PactLookup + -> ServiceEnv tbl -> MempoolBackend Pact.Transaction -> Pact.ListenRequest -> Handler Pact.ListenResponse -listenHandler logger cdb pdb cid pact mem (Pact.ListenRequest key) = do +listenHandler logger pact mem (Pact.ListenRequest key) = do liftIO $ logg Info $ PactCmdLogListen $ Pact.requestKeyToB64Text key liftIO (registerDelay defaultTimeout) >>= runListen where - bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb logg = logFunctionJson (setComponent "listen-handler" logger) runListen :: TVar Bool -> Handler Pact.ListenResponse - runListen timedOut = do - startCut <- liftIO $ CutDB._cut cdb - case HM.lookup cid (_cutMap startCut) of - Nothing -> throwError err504 - Just bh -> poll bh + runListen timedOut = poll where - go :: BlockHeader -> Handler Pact.ListenResponse - go !prevBlock = do - m <- liftIO $ waitForNewBlock prevBlock - case m of - Nothing -> throwError err504 - Just block -> poll block - - poll :: BlockHeader -> Handler Pact.ListenResponse - poll bh = do - hm <- liftIO $ internalPoll logger pdb bdb mem pact Nothing (pure key) + onMissing :: Handler Pact.ListenResponse + onMissing = do + isTimedOut <- liftIO $ readTVarIO timedOut + if isTimedOut + then throwError err504 + else liftIO (threadDelay 1_000_000) >> poll + + poll :: Handler Pact.ListenResponse + poll = do + hm <- liftIO $ internalPoll logger mem pact Nothing (pure key) if HM.null hm - then go bh + then onMissing else pure $! Pact.ListenResponse $ snd $ unsafeHead "Chainweb.Pact.RestAPI.Server.listenHandler.poll" $ HM.toList hm - waitForNewBlock :: BlockHeader -> IO (Maybe BlockHeader) - waitForNewBlock lastBlockHeader = atomically $ do - isTimedOut <- readTVar timedOut - if isTimedOut - then do - pure Nothing - else do - Just <$!> CutDB.awaitNewBlockStm cdb cid (view blockHash lastBlockHeader) - -- TODO: make configurable defaultTimeout = 180 * 1000000 -- two minutes -- -------------------------------------------------------------------------- -- -- Local Handler -type PactLocal = - Maybe LocalPreflightSimulation - -> Maybe LocalSignatureVerification - -> Maybe RewindDepth - -> Transaction - -> IO LocalResult - -- TODO: convert to Pact 5? localHandler :: Logger logger + => CanReadablePayloadCas tbl => logger - -> PactLocal + -> ServiceEnv tbl -> Maybe LocalPreflightSimulation -- ^ Preflight flag -> Maybe LocalSignatureVerification @@ -388,20 +362,21 @@ localHandler -> Handler LocalResult localHandler logger pact preflight sigVerify rewindDepth cmd = do liftIO $ logg Info $ PactCmdLogLocal cmd - cmd' <- case validatedCommand of - Right c -> return c - Left err -> - throwError $ setErrText ("Validation failed: " <> T.pack err) err400 + cmd' <- validatedCommand - r <- liftIO $ pact preflight sigVerify rewindDepth cmd' + r <- liftIO $ execLocal logger pact cmd' preflight sigVerify rewindDepth case r of - -- (preview _MetadataValidationFailure -> Just e) -> do - -- throwError $ setErrText - -- ("Metadata validation failed: " <> decodeUtf8 (BSL.toStrict (Aeson.encode e))) err400 - lr -> return $! lr + Historical (MetadataValidationFailure e) -> do + throwError $ setErrText + ("Metadata validation failed: " <> decodeUtf8 (BSL.toStrict (Aeson.encode e))) err400 + Historical lr -> return $! lr + NoHistory -> throwError $ setErrText + "No block exists at the given rewind depth" err500 where logg = logFunctionJson (setComponent "local-handler" logger) + mkParseError err = setErrJSONPact (Pact.pactErrorToOnChainError (Pact.spanInfoToLineInfo <$> err)) err400 + validatedCommand | Just NoVerify <- sigVerify = do -- @@ -411,220 +386,211 @@ localHandler logger pact preflight sigVerify rewindDepth cmd = do -- down in the 'execLocal' code, 'noSigVerify' triggers a nop on -- checking again if 'preflight' is set. -- - let payloadBS = encodeUtf8 (Pact._cmdPayload cmd) - - void $ Pact.verifyHash (Pact._cmdHash cmd) payloadBS - decoded <- eitherDecodeStrict' payloadBS - - let cmd' = cmd { Pact._cmdPayload = (payloadBS, decoded) } - undefined - -- pure $ Pact.mkPayloadWithText cmd' - | otherwise = undefined -- Pact.mkPayloadWithText <$> - -- traverse (\bs -> (encodeUtf8 bs,) <$> eitherDecodeStrictText bs) cmd + let command' = Pact.parseCommand cmd + case command' of + Left err -> throwError $ mkParseError err + Right cmd' -> pure cmd' + | otherwise = + either (throwError . mkParseError) return + $ Pact.parseCommand cmd -- -------------------------------------------------------------------------- -- -- Cross Chain SPV Handler -spvHandler - :: forall tbl l - . Logger l - => CanReadablePayloadCas tbl - => l - -> CutDB.CutDb - -- ^ cut db - -> PayloadDb tbl - -> PactLookup - -> ChainId - -- ^ the chain id of the source chain id used in the - -- execution of a cross-chain-transfer. - -> SpvRequest - -- ^ Contains the (pact) chain id of the target chain id used in the - -- 'target-chain' field of a cross-chain-transfer. - -- Also contains the request key of of the cross-chain transfer - -- tx request. - -> Handler TransactionOutputProofB64 -spvHandler l cdb pdb pe cid (SpvRequest rk (Pact.ChainId ptid)) = do - - liftIO $! logg (sshow ph) - - T2 bhe _bha <- liftIO (pe cid Nothing (pure $ Pact.unHash ph)) >>= \v -> - -- Left (e :: PactException) -> - -- toErr $ "Internal error: transaction hash lookup failed: " <> sshow e - case HM.lookup (Pact.unHash ph) v of - Nothing -> toErr $ "Transaction hash not found: " <> sshow ph - Just t -> return t - - idx <- undefined >>= \case -- liftIO (Pact.getTxIdx bdb pdb bhe ph) >>= \case - Left e -> toErr - $ "Internal error: Index lookup for hash failed: " - <> sshow e - Right i -> return i - - tid <- chainIdFromText ptid - p <- liftIO (try $ createTransactionOutputProof cdb tid cid bhe idx) >>= \case - Left e@SpvExceptionTargetNotReachable{} -> - toErr $ "SPV target not reachable: " <> _spvExceptionMsg e - Left e@SpvExceptionVerificationFailed{} -> - toErr $ "SPV verification failed: " <> _spvExceptionMsg e - Left e -> - toErr $ "Internal error: SPV verification failed: " <> _spvExceptionMsg e - Right q -> return q - - return $! b64 p - where - ph = Pact.unRequestKey rk - bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb - b64 = TransactionOutputProofB64 - . encodeB64UrlNoPaddingText - . BSL8.toStrict - . Aeson.encode - - logg = logFunctionJson (setComponent "spv-handler" l) Info - . PactCmdLogSpv - - toErr e = throwError $ setErrText e err400 +-- spvHandler +-- :: forall tbl l +-- . Logger l +-- => CanReadablePayloadCas tbl +-- => l +-- -> CutDB.CutDb +-- -- ^ cut db +-- -> PayloadDb tbl +-- -> PactLookup +-- -> ChainId +-- -- ^ the chain id of the source chain id used in the +-- -- execution of a cross-chain-transfer. +-- -> SpvRequest +-- -- ^ Contains the (pact) chain id of the target chain id used in the +-- -- 'target-chain' field of a cross-chain-transfer. +-- -- Also contains the request key of of the cross-chain transfer +-- -- tx request. +-- -> Handler TransactionOutputProofB64 +-- spvHandler l cdb pdb pe cid (SpvRequest rk (Pact.ChainId ptid)) = do + +-- liftIO $! logg (sshow ph) + +-- T2 bhe _bha <- liftIO (pe cid Nothing (pure $ Pact.unHash ph)) >>= \v -> +-- -- Left (e :: PactException) -> +-- -- toErr $ "Internal error: transaction hash lookup failed: " <> sshow e +-- case HM.lookup (Pact.unHash ph) v of +-- Nothing -> toErr $ "Transaction hash not found: " <> sshow ph +-- Just t -> return t + +-- idx <- undefined >>= \case -- liftIO (Pact.getTxIdx bdb pdb bhe ph) >>= \case +-- Left e -> toErr +-- $ "Internal error: Index lookup for hash failed: " +-- <> sshow e +-- Right i -> return i + +-- tid <- chainIdFromText ptid +-- p <- liftIO (try $ createTransactionOutputProof cdb tid cid bhe idx) >>= \case +-- Left e@SpvExceptionTargetNotReachable{} -> +-- toErr $ "SPV target not reachable: " <> _spvExceptionMsg e +-- Left e@SpvExceptionVerificationFailed{} -> +-- toErr $ "SPV verification failed: " <> _spvExceptionMsg e +-- Left e -> +-- toErr $ "Internal error: SPV verification failed: " <> _spvExceptionMsg e +-- Right q -> return q + +-- return $! b64 p +-- where +-- ph = Pact.unRequestKey rk +-- bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb +-- b64 = TransactionOutputProofB64 +-- . encodeB64UrlNoPaddingText +-- . BSL8.toStrict +-- . Aeson.encode + +-- logg = logFunctionJson (setComponent "spv-handler" l) Info +-- . PactCmdLogSpv + +-- toErr e = throwError $ setErrText e err400 -- -------------------------------------------------------------------------- -- -- SPV2 Handler -spv2Handler - :: forall tbl l - . Logger l - => CanReadablePayloadCas tbl - => l - -> CutDB.CutDb - -- ^ CutDb contains the cut, payload, and block db - -> PayloadDb tbl - -> PactLookup - -> ChainId - -- ^ ChainId of the target - -> Spv2Request - -- ^ Contains the (pact) chain id of the target chain id used in the - -- 'target-chain' field of a cross-chain-transfer. - -- Also contains the request key of of the cross-chain transfer - -- tx request. - -> Handler SomePayloadProof -spv2Handler l cdb pdb pactLookup cid r = case _spvSubjectIdType sid of - SpvSubjectResult - | _spv2ReqAlgorithm r /= SpvSHA512t_256 -> - toErr $ "Algorithm " <> sshow r <> " is not supported with SPV result proofs." - | otherwise -> proof createOutputProofDb - SpvSubjectEvents - | cid /= _spvSubjectIdChain sid -> - toErr "Cross chain SPV proofs for are not supported for Pact events" - | otherwise -> case _spv2ReqAlgorithm r of - SpvSHA512t_256 -> proof createEventsProofDb - SpvKeccak_256 -> proof createEventsProofDbKeccak256 - where - proof - :: forall a - . MerkleHashAlgorithm a - => MerkleHashAlgorithmName a - => (BlockHeaderDb -> PayloadDb tbl -> Natural -> BlockHash -> Pact.RequestKey -> IO (PayloadProof a)) - -> Handler SomePayloadProof - proof f = SomePayloadProof <$> do - liftIO $! logg (sshow ph) - T2 bhe bha <- liftIO (pactLookup Nothing (pure $ coerce ph)) >>= - -- Left (e :: PactException) -> - -- toErr $ "Internal error: transaction hash lookup failed: " <> sshow e - \v -> case HM.lookup (coerce ph) v of - Nothing -> toErr $ "Transaction hash not found: " <> sshow ph - Just t -> return t - - let confDepth = fromMaybe (diameter (chainGraphAt cdb bhe)) $ _spv2ReqMinimalProofDepth r - - liftIO (tryAllSynchronous $ f bdb pdb confDepth bha rk) >>= \case - Left e -> toErr $ "SPV proof creation failed:" <> sshow e - Right q -> return q - - sid = _spv2ReqSubjectIdentifier r - - rk = _spvSubjectIdReqKey sid - ph = Pact.unRequestKey rk - bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb - - logg = logFunctionJson (setComponent "spv-handler" l) Info - . PactCmdLogSpv - - toErr e = throwError $ err400 { errBody = e } - --- -------------------------------------------------------------------------- -- --- Eth SPV Handler - -ethSpvHandler - :: EthSpvRequest - -> Handler EthSpvResponse -ethSpvHandler req = do - - -- find block with transaction - (block, rest) <- case evalState start Nothing of - Left () -> toErr $ "the transaction " <> sshow tx <> " is not contained in any of the provided blocks" - Right x -> return x - - -- select and order set of receipts in the block - -- - -- How big can blocks be? Should we create an index instead? - -- - rcs <- forM (_rpcBlockTransactions block) $ \t -> do - case L.find (\r -> _rpcReceiptTransactionHash r == t) receipts of - Nothing -> toErr $ "missing receipt for tx " <> sshow t - Just x -> return x - - -- select and order set of extra headers and create proof - case rpcReceiptProof (_rpcBlockHeader block) (hdrs block rest) rcs (TransactionIndex 28) of - Left e -> toErr $ "failed to create proof: " <> sshow e - Right proof -> return $ EthSpvResponse $ - encodeB64UrlNoPaddingText (putRlpByteString proof) - - where - receipts = _ethSpvReqReceipts req - blocks = _ethSpvReqBlocks req - orderedBlocks = L.sortOn (_hdrNumber . _rpcBlockHeader) blocks - tx = _ethSpvReqTransactionHash req - - start = S.each orderedBlocks - & S.dropWhile (notElem tx . _rpcBlockTransactions) - & S.next - - -- filter sequence consecutive headers - hdrs block rest = flip evalState (Just $ _rpcBlockHash block) $ rest - & S.filterM (\b -> do - c <- get - if Just (bytes $ _hdrParentHash $ _rpcBlockHeader b) == (bytes <$> c) - then True <$ put (Just $ _rpcBlockHash b) - else return False - ) - & S.map _rpcBlockHeader - & S.toList_ - - toErr e = throwError $ err400 { errBody = e } +-- spv2Handler +-- :: forall tbl l +-- . Logger l +-- => CanReadablePayloadCas tbl +-- => l +-- -> CutDB.CutDb +-- -- ^ CutDb contains the cut, payload, and block db +-- -> PayloadDb tbl +-- -> PactLookup +-- -> ChainId +-- -- ^ ChainId of the target +-- -> Spv2Request +-- -- ^ Contains the (pact) chain id of the target chain id used in the +-- -- 'target-chain' field of a cross-chain-transfer. +-- -- Also contains the request key of of the cross-chain transfer +-- -- tx request. +-- -> Handler SomePayloadProof +-- spv2Handler l cdb pdb pactLookup cid r = case _spvSubjectIdType sid of +-- SpvSubjectResult +-- | _spv2ReqAlgorithm r /= SpvSHA512t_256 -> +-- toErr $ "Algorithm " <> sshow r <> " is not supported with SPV result proofs." +-- | otherwise -> proof createOutputProofDb +-- SpvSubjectEvents +-- | cid /= _spvSubjectIdChain sid -> +-- toErr "Cross chain SPV proofs for are not supported for Pact events" +-- | otherwise -> case _spv2ReqAlgorithm r of +-- SpvSHA512t_256 -> proof createEventsProofDb +-- SpvKeccak_256 -> proof createEventsProofDbKeccak256 +-- where +-- proof +-- :: forall a +-- . MerkleHashAlgorithm a +-- => MerkleHashAlgorithmName a +-- => (BlockHeaderDb -> PayloadDb tbl -> Natural -> BlockHash -> Pact.RequestKey -> IO (PayloadProof a)) +-- -> Handler SomePayloadProof +-- proof f = SomePayloadProof <$> do +-- liftIO $! logg (sshow ph) +-- T2 bhe bha <- liftIO (pactLookup Nothing (pure $ coerce ph)) >>= +-- -- Left (e :: PactException) -> +-- -- toErr $ "Internal error: transaction hash lookup failed: " <> sshow e +-- \v -> case HM.lookup (coerce ph) v of +-- Nothing -> toErr $ "Transaction hash not found: " <> sshow ph +-- Just t -> return t + +-- let confDepth = fromMaybe (diameter (chainGraphAt cdb bhe)) $ _spv2ReqMinimalProofDepth r + +-- liftIO (tryAllSynchronous $ f bdb pdb confDepth bha rk) >>= \case +-- Left e -> toErr $ "SPV proof creation failed:" <> sshow e +-- Right q -> return q + +-- sid = _spv2ReqSubjectIdentifier r + +-- rk = _spvSubjectIdReqKey sid +-- ph = Pact.unRequestKey rk +-- bdb = fromJuste $ preview (CutDB.cutDbBlockHeaderDb cid) cdb + +-- logg = logFunctionJson (setComponent "spv-handler" l) Info +-- . PactCmdLogSpv + +-- toErr e = throwError $ err400 { errBody = e } + +-- -- -------------------------------------------------------------------------- -- +-- -- Eth SPV Handler + +-- ethSpvHandler +-- :: EthSpvRequest +-- -> Handler EthSpvResponse +-- ethSpvHandler req = do + +-- -- find block with transaction +-- (block, rest) <- case evalState start Nothing of +-- Left () -> toErr $ "the transaction " <> sshow tx <> " is not contained in any of the provided blocks" +-- Right x -> return x + +-- -- select and order set of receipts in the block +-- -- +-- -- How big can blocks be? Should we create an index instead? +-- -- +-- rcs <- forM (_rpcBlockTransactions block) $ \t -> do +-- case L.find (\r -> _rpcReceiptTransactionHash r == t) receipts of +-- Nothing -> toErr $ "missing receipt for tx " <> sshow t +-- Just x -> return x + +-- -- select and order set of extra headers and create proof +-- case rpcReceiptProof (_rpcBlockHeader block) (hdrs block rest) rcs (TransactionIndex 28) of +-- Left e -> toErr $ "failed to create proof: " <> sshow e +-- Right proof -> return $ EthSpvResponse $ +-- encodeB64UrlNoPaddingText (putRlpByteString proof) + +-- where +-- receipts = _ethSpvReqReceipts req +-- blocks = _ethSpvReqBlocks req +-- orderedBlocks = L.sortOn (_hdrNumber . _rpcBlockHeader) blocks +-- tx = _ethSpvReqTransactionHash req + +-- start = S.each orderedBlocks +-- & S.dropWhile (notElem tx . _rpcBlockTransactions) +-- & S.next + +-- -- filter sequence consecutive headers +-- hdrs block rest = flip evalState (Just $ _rpcBlockHash block) $ rest +-- & S.filterM (\b -> do +-- c <- get +-- if Just (bytes $ _hdrParentHash $ _rpcBlockHeader b) == (bytes <$> c) +-- then True <$ put (Just $ _rpcBlockHash b) +-- else return False +-- ) +-- & S.map _rpcBlockHeader +-- & S.toList_ + +-- toErr e = throwError $ err400 { errBody = e } -- --------------------------------------------------------------------------- -- -- Poll Helper -type PactLookup = - Maybe ConfirmationDepth - -> Vector SB.ShortByteString - -> IO (HashMap SB.ShortByteString (T2 BlockHeight BlockHash)) - internalPoll :: (CanReadablePayloadCas tbl, Logger logger) => logger - -> PayloadDb tbl - -> BlockHeaderDb -> MempoolBackend Pact.Transaction - -> PactLookup + -> ServiceEnv tbl -> Maybe ConfirmationDepth -> NonEmpty Pact.RequestKey -> IO (HashMap Pact.RequestKey (Pact.CommandResult Pact.Hash Pact.PactOnChainError)) -internalPoll logger pdb bhdb mempool pactLookup confDepth requestKeys0 = do +internalPoll logger mempool pact confDepth requestKeys0 = do let dbg txt = logFunctionText logger Debug txt -- get leaf block header for our chain from current best cut - results0 <- pactLookup confDepth (coerce requestKeys) + results0 <- throwIfNoHistory =<< execLookupPactTxs logger pact confDepth (coerce requestKeys) + dbg $ "internalPoll.results0: " <> sshow results0 -- TODO: are we sure that all of these are raised locally. This will cause the -- server to shut down the connection without returning a result to the user. - let results1 = V.map (\rk -> (rk, HM.lookup (coerce $ Pact.unRequestKey rk) results0)) requestKeysV + let results1 = V.map (\rk -> (rk, HM.lookup (coerce rk) results0)) requestKeysV let (present0, missing) = V.unstablePartition (isJust . snd) results1 let present = V.map (second fromJuste) present0 badlisted <- V.toList <$> checkBadList (V.map fst missing) @@ -635,33 +601,30 @@ internalPoll logger pdb bhdb mempool pactLookup confDepth requestKeys0 = do logFunctionJson logger Warn errs return $! HM.fromList (good ++ badlisted) where - cid = _chainId bhdb !requestKeysV = V.fromList $ NEL.toList requestKeys0 !requestKeys = V.map Pact.unRequestKey requestKeysV lookup - :: (Pact.RequestKey, T2 BlockHeight BlockHash) + :: (Pact.RequestKey, T3 BlockHeight BlockPayloadHash BlockHash) -> IO (Either String (Maybe (Pact.RequestKey, Pact.CommandResult Pact.Hash Pact.PactOnChainError))) - lookup (key, T2 _ ha) = (fmap . fmap . fmap) (key,) $ lookupRequestKey key ha + lookup (key, T3 bh bph bhsh) = (fmap . fmap . fmap) (key,) $ lookupRequestKey key bph bhsh bh -- TODO: group by block for performance (not very important right now) lookupRequestKey :: Pact.RequestKey + -> BlockPayloadHash -> BlockHash + -> BlockHeight -> IO (Either String (Maybe (Pact.CommandResult Pact.Hash Pact.PactOnChainError))) - lookupRequestKey key bHash = runExceptT $ do + lookupRequestKey key payloadHash bHash height = runExceptT $ do let pactHash = Pact.unRequestKey key let matchingHash = (== pactHash) . Pact._cmdHash . fst - blockHeader <- liftIO (TreeDB.lookup bhdb bHash) >>= \case - Nothing -> throwError $ "missing block header: " <> sshow key - Just x -> return x + let pdb = _payloadStoreTable (_psPdb pact) - let payloadHash = view blockPayloadHash blockHeader (_payloadWithOutputsTransactions -> txsBs) <- barf - ("payload lookup failed: " <> T.unpack (blockHeaderShortDescription blockHeader) - <> " w/ payload hash " <> sshow (view blockPayloadHash blockHeader)) - =<< liftIO (lookupPayloadWithHeight pdb (Just $ view blockHeight blockHeader) payloadHash) + ("payload lookup failed w/ payload hash: " <> sshow payloadHash) + =<< liftIO (lookupPayloadWithHeight pdb (Just height) payloadHash) !txs <- mapM fromTx txsBs case find matchingHash txs of Just (_cmd, TransactionOutput output) -> do @@ -671,7 +634,7 @@ internalPoll logger pdb bhdb mempool pactLookup confDepth requestKeys0 = do Right decodedOutput -> return decodedOutput when (Pact._crReqKey out /= key) $ throwError "internal error: Transaction output doesn't match its hash!" - return $ Just $ enrichCR blockHeader out + return $ Just $ enrichCR payloadHash bHash height out Nothing -> return Nothing fromTx :: (Transaction, TransactionOutput) -> ExceptT String IO (Pact.Command Text, TransactionOutput) @@ -700,13 +663,12 @@ internalPoll logger pdb bhdb mempool pactLookup confDepth requestKeys0 = do !cr = Pact.CommandResult rk Nothing res (mempty :: Pact.Gas) Nothing Nothing Nothing [] in (rk, cr) - enrichCR :: BlockHeader -> Pact.CommandResult i e -> Pact.CommandResult i e - enrichCR bh = set Pact.crMetaData + enrichCR :: BlockPayloadHash -> BlockHash -> BlockHeight -> Pact.CommandResult i e -> Pact.CommandResult i e + enrichCR bph bhsh bh = set Pact.crMetaData (Just $ object - [ "blockHeight" .= view blockHeight bh - , "blockTime" .= view blockCreationTime bh - , "blockHash" .= view blockHash bh - , "prevBlockHash" .= view blockParent bh + [ "blockHeight" .= bh + , "blockHash" .= bhsh + , "blockPayloadHash" .= bph ]) -- -------------------------------------------------------------------------- -- @@ -717,24 +679,8 @@ barf e = maybe (throwError e) return -- TODO: all of the functions in this module can instead grab the current block height from consensus -- and pass it here to get a better estimate of what behavior is correct. -validateCommand :: ChainwebVersion -> ChainId -> Pact.Command Text -> Either Text Pact.Transaction -validateCommand v cid (fmap encodeUtf8 -> cmdBs) = case parsedCmd of - Right (commandParsed :: Pact.Transaction) -> - case Pact.assertCommand commandParsed of - Left err -> Left $ "Command failed validation: " <> Pact.displayAssertCommandError err - Right () -> Right commandParsed - Left e -> Left $ "Pact parsing error: " <> T.pack e - where - bh = maxBound :: BlockHeight - decodeAndParse bs = - traverse (Pact.parsePact) =<< Aeson.eitherDecodeStrict' bs - parsedCmd = Pact.mkPayloadWithText <$> - Pact.cmdPayload (\bs -> (bs,) <$> decodeAndParse bs) cmdBs - --- TODO: all of the functions in this module can instead grab the current block height from consensus --- and pass it here to get a better estimate of what behavior is correct. -validatePact5Command :: ChainwebVersion -> Pact.Command Text -> Either String Pact.Transaction -validatePact5Command _v cmdText = case parsedCmd of +validateCommand :: ChainwebVersion -> Pact.Command Text -> Either String Pact.Transaction +validateCommand _v cmdText = case parsedCmd of Right (commandParsed :: Pact.Transaction) -> if isRight (Pact.assertCommand commandParsed) then Right commandParsed diff --git a/src/Chainweb/Pact/SPV.hs b/src/Chainweb/Pact/SPV.hs index 70f45d390f..ee5038b40c 100644 --- a/src/Chainweb/Pact/SPV.hs +++ b/src/Chainweb/Pact/SPV.hs @@ -10,11 +10,6 @@ module Chainweb.Pact.SPV (pactSPV) where -import Chainweb.Payload (TransactionOutput(..)) -import Chainweb.SPV (TransactionOutputProof(..), outputProofChainId) -import Chainweb.SPV.VerifyProof (runTransactionOutputProof) -import Chainweb.Utils (decodeB64UrlNoPaddingText, unlessM) -import Chainweb.Version qualified as CW import Control.Lens import Control.Monad (when) import Control.Monad.Except (ExceptT, runExceptT, throwError) @@ -29,8 +24,15 @@ import Pact.Core.Hash (Hash(..)) import Pact.Core.PactValue (ObjectData(..), PactValue(..)) import Pact.Core.SPV (SPVSupport(..), ContProof (..)) import Pact.Core.StableEncoding (encodeStable) + import Chainweb.Crypto.MerkleLog import Chainweb.Pact.Backend.Types +import Chainweb.Parent +import Chainweb.Payload (TransactionOutput(..)) +import Chainweb.SPV (TransactionOutputProof(..), outputProofChainId) +import Chainweb.SPV.VerifyProof (runTransactionOutputProof) +import Chainweb.Utils (decodeB64UrlNoPaddingText, unlessM) +import Chainweb.Version qualified as CW pactSPV :: HeaderOracle -> SPVSupport pactSPV oracle = SPVSupport @@ -41,7 +43,7 @@ pactSPV oracle = SPVSupport checkProofAndExtractOutput :: HeaderOracle -> TransactionOutputProof SHA512t_256 -> ExceptT Text IO TransactionOutput checkProofAndExtractOutput oracle proof@(TransactionOutputProof _cid p) = do let h = runTransactionOutputProof proof - unlessM (liftIO $ oracle.consult h) $ throwError + unlessM (liftIO $ oracle.consult (Parent h)) $ throwError "spv verification failed: target header is not in the chain" proofSubject p diff --git a/src/Chainweb/Pact/Templates.hs b/src/Chainweb/Pact/Templates.hs index 8e588671ba..6aaa38c36f 100644 --- a/src/Chainweb/Pact/Templates.hs +++ b/src/Chainweb/Pact/Templates.hs @@ -4,6 +4,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module : Chainweb.Pact.Templates @@ -26,120 +27,112 @@ import Data.Text (Text) -- internal modules -import qualified Pact.JSON.Encode as J - import Chainweb.Miner.Pact -import Pact.Core.Literal -import Pact.Core.Names -import Pact.Core.Syntax.ParseTree -import Pact.Core.PactValue -import qualified Data.Map as Map -import Chainweb.Utils (decodeOrThrow) -import Pact.Core.StableEncoding (StableEncoding(_stableEncoding)) -import Control.Exception.Safe (impureThrow) -import qualified Pact.Types.KeySet as Pact4 +import Pact.Core.Literal qualified as Pact +import Pact.Core.Names qualified as Pact +import Pact.Core.Syntax.ParseTree qualified as Pact +import Pact.Core.PactValue qualified as Pact +import Data.Map as Map import Chainweb.Pact.Types -fundTxTemplate :: Text -> Text -> Expr () +fundTxTemplate :: Text -> Text -> Pact.Expr () fundTxTemplate sender mid = let senderTerm = strLit sender midTerm = strLit mid varApp = qn "fund-tx" "coin" + -- TODO PP: fork this so that guards are supported, by using read-msg instead. + -- instead of forking here, in theory we could detect the type of guard + -- and use different code if it's a keyset. rks = app (bn "read-keyset") [strLit "miner-keyset"] rds = app (bn "read-decimal") [strLit "total"] in app varApp [senderTerm, midTerm, rks, rds] -buyGasTemplate :: Text -> Expr () +buyGasTemplate :: Text -> Pact.Expr () buyGasTemplate sender = let senderTerm = strLit sender varApp = qn "buy-gas" "coin" rds = app (bn "read-decimal") [strLit "total"] in app varApp [senderTerm, rds] -redeemGasTemplate :: Text -> Text -> Expr () +redeemGasTemplate :: Text -> Text -> Pact.Expr () redeemGasTemplate mid sender = let midTerm = strLit mid senderTerm = strLit sender varApp = qn "redeem-gas" "coin" + -- TODO PP: fork this so that guards are supported, by using read-msg instead rks = app (bn "read-keyset") [strLit "miner-keyset"] rds = app (bn "read-decimal") [strLit "total"] in app varApp [midTerm, rks, senderTerm, rds] -app :: Expr () -> [Expr ()] -> Expr () -app arg args = App arg args () +app :: Pact.Expr () -> [Pact.Expr ()] -> Pact.Expr () +app arg args = Pact.App arg args () -strLit :: Text -> Expr () -strLit txt = Constant (LString txt) () +strLit :: Text -> Pact.Expr () +strLit txt = Pact.Constant (Pact.LString txt) () -qn :: Text -> Text -> Expr () -qn name modname = Var (QN (QualifiedName name (ModuleName modname Nothing))) () +qn :: Text -> Text -> Pact.Expr () +qn name modname = Pact.Var (Pact.QN (Pact.QualifiedName name (Pact.ModuleName modname Nothing))) () -bn :: Text -> Expr () -bn name = Var (BN (BareName name)) () +bn :: Text -> Pact.Expr () +bn name = Pact.Var (Pact.BN (Pact.BareName name)) () mkFundTxTerm :: MinerId -- ^ Id of the miner to fund - -> MinerKeys + -> MinerGuard -> Text -- ^ Address of the sender from the command -> GasSupply - -> (Expr (), Map.Map Field PactValue) -mkFundTxTerm (MinerId mid) (MinerKeys ks) sender total = + -> (Pact.Expr (), Map.Map Pact.Field Pact.PactValue) +mkFundTxTerm (MinerId mid) (MinerGuard ks) sender total = let term = fundTxTemplate sender mid buyGasData = Map.fromList - [ ("miner-keyset", convertKeySet ks) - , ("total", PDecimal $ _pact5GasSupply total) + [ ("miner-keyset", Pact.PGuard ks) + , ("total", Pact.PDecimal $ _pact5GasSupply total) ] in (term, buyGasData) --- we configure the miner keyset as a Pact4 keyset --- TODO: change this? -convertKeySet :: Pact4.KeySet -> PactValue -convertKeySet = - either impureThrow _stableEncoding . decodeOrThrow . J.encode -{-# INLINABLE mkFundTxTerm #-} - mkBuyGasTerm :: Text -- ^ Address of the sender from the command -> GasSupply - -> (Expr (), Map.Map Field PactValue) + -> (Pact.Expr (), Map.Map Pact.Field Pact.PactValue) mkBuyGasTerm sender total = (buyGasTemplate sender, buyGasData) where buyGasData = Map.fromList - [ ("total", PDecimal $ _pact5GasSupply total) ] + [ ("total", Pact.PDecimal $ _pact5GasSupply total) ] {-# INLINABLE mkBuyGasTerm #-} mkRedeemGasTerm - :: MinerId -- ^ Id of the miner to fund - -> MinerKeys -- ^ Miner keyset - -> Text -- ^ Address of the sender from the command - -> GasSupply -- ^ The gas limit total * price - -> GasSupply -- ^ The gas used * price - -> (Expr (), PactValue) -mkRedeemGasTerm (MinerId mid) (MinerKeys ks) sender total fee = + :: MinerId -- ^ Id of the miner to fund + -> MinerGuard -- ^ Miner guard + -> Text -- ^ Address of the sender from the command + -> GasSupply -- ^ The gas limit total * price + -> GasSupply -- ^ The gas used * price + -> (Pact.Expr (), Pact.PactValue) +mkRedeemGasTerm (MinerId mid) (MinerGuard g) sender total fee = (redeemGasTemplate mid sender, redeemGasData) where - redeemGasData = PObject $ Map.fromList - [ ("total", PDecimal $ _pact5GasSupply total) - , ("fee", PDecimal $ _pact5GasSupply fee) - , ("miner-keyset", convertKeySet ks) + redeemGasData = Pact.PObject $ Map.fromList + [ ("total", Pact.PDecimal $ _pact5GasSupply total) + , ("fee", Pact.PDecimal $ _pact5GasSupply fee) + , ("miner-keyset", Pact.PGuard g) ] {-# INLINABLE mkRedeemGasTerm #-} -coinbaseTemplate :: Text -> Expr () +coinbaseTemplate :: Text -> Pact.Expr () coinbaseTemplate mid = let midTerm = strLit mid varApp = qn "coinbase" "coin" + -- TODO PP: fork this so that guards are supported, by using read-msg instead rks = app (bn "read-keyset") [strLit "miner-keyset"] rds = app (bn "read-decimal") [strLit "reward"] in app varApp [midTerm, rks, rds] -mkCoinbaseTerm :: MinerId -> MinerKeys -> Decimal -> (Expr (), PactValue) -mkCoinbaseTerm (MinerId mid) (MinerKeys ks) reward = (coinbaseTemplate mid, coinbaseData) +mkCoinbaseTerm :: MinerId -> MinerGuard -> Decimal -> (Pact.Expr (), Pact.PactValue) +mkCoinbaseTerm (MinerId mid) (MinerGuard g) reward = (coinbaseTemplate mid, coinbaseData) where - coinbaseData = PObject $ Map.fromList - [ ("miner-keyset", convertKeySet ks) - , ("reward", PDecimal reward) + coinbaseData = Pact.PObject $ Map.fromList + [ ("miner-keyset", Pact.PGuard g) + , ("reward", Pact.PDecimal reward) ] {-# INLINABLE mkCoinbaseTerm #-} diff --git a/src/Chainweb/Pact/Transaction.hs b/src/Chainweb/Pact/Transaction.hs index f376deeb30..dabcf3bafb 100644 --- a/src/Chainweb/Pact/Transaction.hs +++ b/src/Chainweb/Pact/Transaction.hs @@ -11,14 +11,15 @@ {-# language TypeApplications #-} module Chainweb.Pact.Transaction - ( Transaction - , PayloadWithText - , payloadBytes - , payloadObj - , payloadCodec - , parseCommand - , HashableTransaction(..) - ) where + ( Transaction + , PayloadWithText + , unsafeMkPayloadWithText + , payloadBytes + , payloadObj + , commandCodec + , parseCommand + , HashableTransaction(..) + ) where import "aeson" Data.Aeson qualified as Aeson import "base" Data.Function @@ -59,6 +60,12 @@ instance (J.Encode meta, J.Encode code) => J.Encode (PayloadWithText meta code) , "payloadObject" J..= _payloadObj p ] +unsafeMkPayloadWithText :: Payload meta code -> ByteString -> PayloadWithText meta code +unsafeMkPayloadWithText payload bytes = UnsafePayloadWithText + { _payloadBytes = SB.toShort bytes + , _payloadObj = payload + } + payloadBytes :: Getter (PayloadWithText meta code) SB.ShortByteString payloadBytes = to _payloadBytes {-# inline conlike payloadBytes #-} @@ -69,9 +76,9 @@ payloadObj = to _payloadObj -- | A codec for Pact5's (Command PayloadWithText) transactions. -- -payloadCodec +commandCodec :: Codec (Command (PayloadWithText PublicMeta ParsedCode)) -payloadCodec = Codec enc dec +commandCodec = Codec enc dec where enc c = J.encodeStrict $ fmap (decodeUtf8 . encodePayload) c dec bs = case Aeson.decodeStrict' bs of diff --git a/src/Chainweb/Pact/TransactionExec.hs b/src/Chainweb/Pact/TransactionExec.hs index 55f50c0995..621b1ef409 100644 --- a/src/Chainweb/Pact/TransactionExec.hs +++ b/src/Chainweb/Pact/TransactionExec.hs @@ -283,7 +283,7 @@ applyCmd :: forall logger. (Logger logger) => logger -- ^ Pact logger - -> Maybe logger + -> Maybe GasLogger -- ^ Pact gas logger -> PactDb CoreBuiltin Info -- ^ Pact db environment @@ -321,9 +321,10 @@ applyCmd logger maybeGasLogger db miner txCtx txIdxInBlock spv initialGas cmd = runVerifiers txCtx cmd - liftIO $ dumpGasLogs "applyCmd.paidFor.beforeRunPayload" (_cmdHash cmd) maybeGasLogger gasEnv + -- TODO: PP + -- liftIO $ dumpGasLogs "applyCmd.paidFor.beforeRunPayload" (_cmdHash cmd) maybeGasLogger gasEnv evalResult <- runPayload Transactional flags db spv [] managedNamespacePolicy gasEnv txCtx txIdxInBlock cmd - liftIO $ dumpGasLogs "applyCmd.paidFor.afterRunPayload" (_cmdHash cmd) maybeGasLogger gasEnv + -- liftIO $ dumpGasLogs "applyCmd.paidFor.afterRunPayload" (_cmdHash cmd) maybeGasLogger gasEnv return evalResult eBuyGasResult <- do diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 1db3936032..7604699da3 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -16,14 +16,16 @@ {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Chainweb.Pact.Types - ( PactServiceEnv(..) + ( ServiceEnv(..) , psVersion , psChainId - , psLogger , psGasLogger , psReadWriteSql + , psReadSqlPool , psPdb , psCandidatePdb , psMempoolAccess @@ -35,6 +37,7 @@ module Chainweb.Pact.Types , psMiner , psMiningPayloadVar , psNewBlockGasLimit + , psGenesisPayload , BlockCtx(..) , blockCtxOfEvaluationCtx @@ -43,17 +46,13 @@ module Chainweb.Pact.Types , _bctxParentRankedBlockHash , _bctxIsGenesis , _bctxCurrentBlockHeight + , genesisEvaluationCtx , PactServiceConfig(..) - , PactServiceM(..) - , runPactServiceM - , withPactState - , PactBlockEnv(..) + , testPactServiceConfig + , BlockEnv(..) , psBlockDbEnv , psBlockCtx - , psServiceEnv - , PactBlockState(..) - , PactBlockM(..) , Transactions(..) , transactionPairs @@ -65,23 +64,20 @@ module Chainweb.Pact.Types , blockInProgressBlockCtx , blockInProgressRemainingGasLimit , blockInProgressTransactions + , blockInProgressNumber , toPayloadWithOutputs , commandToBytes + , hashPactTxLogs , MemPoolAccess(..) + , GasLogger , GasSupply(..) , RewindLimit(..) , defaultReorgLimit , defaultPreInsertCheckTimeout - , pbBlockHandle - , runPactBlockM - , tracePactBlockM - , tracePactBlockM' - , liftPactServiceM , pactTransaction - , localLabelBlock -- * default values , noInfo , noPublicMeta @@ -98,6 +94,10 @@ module Chainweb.Pact.Types , RewindDepth(..) , ConfirmationDepth(..) , LocalResult(..) + , _MetadataValidationFailure + , _LocalResultLegacy + , _LocalResultWithWarns + , _LocalTimeout , SpvRequest(..) , TransactionOutputProofB64(..) @@ -114,11 +114,6 @@ module Chainweb.Pact.Types , logWarn_ , logError_ - , logInfoPact - , logWarnPact - , logErrorPact - , logDebugPact - , PactTxFailureLog(..) ) where @@ -128,7 +123,6 @@ import Control.DeepSeq import Control.Exception.Safe import Control.Lens import Control.Monad.IO.Class -import Control.Monad.Reader import Control.Monad.State.Strict import Data.Aeson hiding (Error, (.=)) import Data.Bool @@ -137,6 +131,7 @@ import Data.ByteString.Short qualified as SB import Data.Decimal import Data.List.NonEmpty qualified as NE import Data.LogMessage +import Data.Pool(Pool) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T @@ -158,7 +153,6 @@ import Pact.Core.Persistence import Pact.Core.StableEncoding qualified as Pact import Pact.JSON.Encode qualified as J import System.LogLevel -import Utils.Logging.Trace import Chainweb.BlockHeader import Chainweb.BlockPayloadHash @@ -190,6 +184,7 @@ import Chainweb.Parent import Chainweb.MinerReward import Chainweb.BlockCreationTime import Control.Concurrent.Async +import qualified Data.Aeson as A data Transactions t r = Transactions { _transactionPairs :: !(Vector (t, r)) @@ -305,6 +300,8 @@ data LocalResult | LocalTimeout deriving stock (Generic, Show) +makePrisms ''LocalResult + instance J.Encode LocalResult where build (MetadataValidationFailure e) = J.object [ "preflightValidationFailures" J..= J.Array (J.text <$> e) @@ -354,7 +351,17 @@ data BlockCtx = BlockCtx , _bctxChainId :: !ChainId , _bctxChainwebVersion :: !ChainwebVersion , _bctxMinerReward :: !MinerReward - } deriving (Eq, Show) + } deriving stock (Eq, Generic, Show) + +instance ToJSON BlockCtx where + toJSON BlockCtx{..} = object + [ "parentCreationTime" A..= _bctxParentCreationTime + , "parentHash" A..= _bctxParentHash + , "parentHeight" A..= _bctxParentHeight + , "chainId" A..= _bctxChainId + , "chainwebVersion" A..= _versionName _bctxChainwebVersion + , "minerReward" A..= _bctxMinerReward + ] blockCtxOfEvaluationCtx :: ChainwebVersion -> ChainId -> EvaluationCtx p -> BlockCtx blockCtxOfEvaluationCtx v cid ec = BlockCtx @@ -397,8 +404,7 @@ _bctxCurrentBlockHeight bc = childBlockHeight (_chainwebVersion bc) (_chainId bc) (_bctxParentRankedBlockHash bc) - --- | Externally-injected PactService properties. +-- | Externally-injected PactService properties. -- data PactServiceConfig = PactServiceConfig { _pactReorgLimit :: !RewindLimit @@ -421,17 +427,38 @@ data PactServiceConfig = PactServiceConfig -- available. Compaction can remove history. , _pactEnableLocalTimeout :: !Bool -- ^ Whether to enable the local timeout to prevent long-running transactions - , _pactPersistIntraBlockWrites :: !IntraBlockPersistence - -- ^ Whether or not the node requires that all writes made in a block - -- are persisted. Useful if you want to use PactService BlockTxHistory. , _pactTxTimeLimit :: !(Maybe Micros) -- ^ *Only affects Pact5* -- Maximum allowed execution time for a single transaction. -- If 'Nothing', it's a function of the BlockGasLimit. - -- , _pactMiner :: !(Maybe Miner) + -- ^ The miner used to make new blocks. + , _pactGenesisPayload :: !Chainweb.PayloadWithOutputs + -- ^ The genesis payload for this chain. } deriving (Eq,Show) +testPactServiceConfig :: Chainweb.PayloadWithOutputs -> PactServiceConfig +testPactServiceConfig genesisPayload = PactServiceConfig + { _pactReorgLimit = defaultReorgLimit + , _pactPreInsertCheckTimeout = defaultPreInsertCheckTimeout + , _pactQueueSize = 1000 + , _pactAllowReadsInLocal = False + , _pactUnlimitedInitialRewind = False + , _pactNewBlockGasLimit = testBlockGasLimit + , _pactLogGas = False + , _pactFullHistoryRequired = False + , _pactEnableLocalTimeout = False + , _pactTxTimeLimit = Nothing + , _pactMiner = Nothing + , _pactGenesisPayload = genesisPayload + } + +-- | This default value is only relevant for testing. In a chainweb-node the @GasLimit@ +-- is initialized from the @_configBlockGasLimit@ value of @ChainwebConfiguration@. +-- +testBlockGasLimit :: Pact.GasLimit +testBlockGasLimit = Pact.GasLimit $ Pact.Gas 100000 + -- TODO: get rid of this shim, it's probably not necessary data MemPoolAccess = MemPoolAccess { mpaGetBlock @@ -441,7 +468,7 @@ data MemPoolAccess = MemPoolAccess -> EvaluationCtx () -> IO (Vector to) ) - , mpaProcessFork :: !((Vector Pact.Transaction, Vector TransactionHash) -> IO ()) + , mpaProcessFork :: !((Vector Pact.Transaction, Vector Pact.Transaction) -> IO ()) , mpaBadlistTx :: !(Vector TransactionHash -> IO ()) } @@ -452,144 +479,88 @@ instance Semigroup MemPoolAccess where instance Monoid MemPoolAccess where mempty = MemPoolAccess mempty mempty mempty -data PactServiceEnv logger tbl = PactServiceEnv - { _psVersion :: !ChainwebVersion - , _psChainId :: !ChainId - , _psLogger :: !logger +-- TODO PP: this is really a cop-out. PactService is responsible for this, +-- there's no need to make it so open. Just find a good solution and stick with +-- it, like logging to a file, or returning to /local, or both. +type GasLogger = Pact.RequestKey -> [Pact.GasLogEntry Pact.CoreBuiltin Pact.Info] -> IO () + +data ServiceEnv tbl = ServiceEnv + { _psVersion :: ChainwebVersion + , _psChainId :: ChainId - , _psGasLogger :: !(Maybe logger) + , _psGasLogger :: Maybe GasLogger -- ^ Used to emit gas logs of Pact code; gas logs are disabled if this is set to `Nothing`. -- Gas logs have a non-zero cost, so usually this is disabled. - , _psReadWriteSql :: !SQLiteEnv + , _psReadWriteSql :: SQLiteEnv -- ^ Database connection used to mutate the Pact state. - , _psPdb :: !(PayloadStore (PayloadDb tbl) Chainweb.PayloadData) + , _psReadSqlPool :: Pool SQLiteEnv + , _psPdb :: PayloadStore (PayloadDb tbl) Chainweb.PayloadData -- ^ Used to store payloads of validated blocks. -- Contains outputs too. - , _psCandidatePdb :: !(MapTable RankedBlockPayloadHash Chainweb.PayloadData) + , _psCandidatePdb :: MapTable RankedBlockPayloadHash Chainweb.PayloadData -- ^ Used to store payloads of blocks that have not yet been validated. - , _psMempoolAccess :: !MemPoolAccess + , _psMempoolAccess :: MemPoolAccess -- ^ The mempool's limited interface as used by Pact. - , _psPreInsertCheckTimeout :: !Micros + , _psPreInsertCheckTimeout :: Micros -- ^ Maximum allowed execution time for mempool transaction validation. - , _psAllowReadsInLocal :: !Bool + , _psAllowReadsInLocal :: Bool -- ^ Whether to allow reads of arbitrary tables in /local. - , _psEnableLocalTimeout :: !Bool + , _psEnableLocalTimeout :: Bool -- ^ Whether to have a timeout for /local calls. - , _psTxFailuresCounter :: !(Maybe (Counter "txFailures")) + , _psTxFailuresCounter :: Maybe (Counter "txFailures") -- ^ Counter of the number of failed transactions. - , _psNewPayloadTxTimeLimit :: !(Maybe Micros) + , _psNewPayloadTxTimeLimit :: Maybe Micros -- ^ Maximum amount of time to validate transactions while constructing payload. -- If unset, defaults to a reasonable value based on the transaction's gas limit. - , _psMiner :: !(Maybe Miner) + , _psMiner :: Maybe Miner -- ^ Miner identity for use in newly mined blocks. - , _psMiningPayloadVar :: !(TMVar (Async (), BlockInProgress)) + , _psMiningPayloadVar :: TMVar (Async (), NewBlockCtx, BlockInProgress) -- ^ Latest mining payload produced, and block continuation thread. - , _psNewBlockGasLimit :: !Pact.GasLimit + , _psNewBlockGasLimit :: Pact.GasLimit -- ^ Block gas limit in newly produced blocks. + , _psGenesisPayload :: !Chainweb.PayloadWithOutputs + -- ^ The genesis payload for this chain. } -instance HasChainwebVersion (PactServiceEnv logger c) where +instance HasChainwebVersion (ServiceEnv tbl) where _chainwebVersion = _psVersion {-# INLINE _chainwebVersion #-} -instance HasChainId (PactServiceEnv logger c) where +instance HasChainId (ServiceEnv tbl) where _chainId = _psChainId {-# INLINE _chainId #-} --- | The top level monad of PactService, notably allowing access to a --- checkpointer and module init cache and some configuration parameters. -newtype PactServiceM logger tbl a = PactServiceM - { unPactServiceM :: - ReaderT (PactServiceEnv logger tbl) IO a - } deriving newtype - ( Functor, Applicative, Monad - , MonadReader (PactServiceEnv logger tbl) - , MonadThrow, MonadCatch, MonadMask - , MonadIO - ) - -runPactServiceM :: PactServiceEnv logger tbl -> PactServiceM logger tbl a -> IO a -runPactServiceM e a = runReaderT (unPactServiceM a) e - -withPactState - :: forall logger tbl b - . Logger logger - => ((forall a. PactServiceM logger tbl a -> IO a) -> IO b) - -> PactServiceM logger tbl b -withPactState inner = do - e <- ask - liftIO $ inner $ - runPactServiceM e - -data PactBlockEnv logger tbl = PactBlockEnv - { _psServiceEnv :: !(PactServiceEnv logger tbl) - , _psBlockCtx :: !BlockCtx +data BlockEnv = BlockEnv + { _psBlockCtx :: !BlockCtx , _psBlockDbEnv :: !ChainwebPactDb } -instance HasChainwebVersion (PactBlockEnv logger tbl) where - _chainwebVersion = _chainwebVersion . _psServiceEnv -instance HasChainId (PactBlockEnv logger tbl) where - _chainId = _chainId . _psServiceEnv - -data PactBlockState = PactBlockState - { _pbBlockHandle :: !BlockHandle - } - --- | A sub-monad of PactServiceM, for actions taking place at a particular block. -newtype PactBlockM logger tbl a = PactBlockM - { _unPactBlockM :: - ReaderT (PactBlockEnv logger tbl) (StateT PactBlockState IO) a - } deriving newtype - ( Functor, Applicative, Monad - , MonadReader (PactBlockEnv logger tbl) - , MonadState PactBlockState - , MonadThrow, MonadCatch, MonadMask - , MonadIO - ) - --- | Lifts PactServiceM to PactBlockM by forgetting about the current block. --- It is unsafe to use `runPactBlockM` inside the argument to this function. -liftPactServiceM :: PactServiceM logger tbl a -> PactBlockM logger tbl a -liftPactServiceM (PactServiceM a) = - PactBlockM $ ReaderT $ \e -> - liftIO $ runReaderT a (_psServiceEnv e) - --- | Run 'PactBlockM' by providing the block context, in the form of --- a database snapshot at that block and information about the parent header. --- It is unsafe to use this function in an argument to `liftPactServiceM`. -runPactBlockM - :: BlockCtx -> ChainwebPactDb -> BlockHandle - -> PactBlockM logger tbl a -> PactServiceM logger tbl (a, BlockHandle) -runPactBlockM pctx dbEnv startBlockHandle (PactBlockM act) = PactServiceM $ ReaderT $ \e -> do - let blockEnv = PactBlockEnv - { _psServiceEnv = e - , _psBlockCtx = pctx - , _psBlockDbEnv = dbEnv +instance HasChainwebVersion BlockEnv where + _chainwebVersion = _chainwebVersion . _psBlockCtx +instance HasChainId BlockEnv where + _chainId = _chainId . _psBlockCtx + +-- the evaluation context for the genesis block; note that the payload is filled in +genesisEvaluationCtx :: ServiceEnv tbl -> EvaluationCtx ConsensusPayload +genesisEvaluationCtx serviceEnv = EvaluationCtx + { _evaluationCtxParentCreationTime = Parent $ v ^?! versionGenesis . genesisTime . atChain cid + , _evaluationCtxParentHash = genesisParentBlockHash v cid + , _evaluationCtxParentHeight = Parent $ genesisHeight v cid + -- should not be used + , _evaluationCtxMinerReward = MinerReward 0 + , _evaluationCtxPayload = ConsensusPayload + { _consensusPayloadHash = genesisBlockPayloadHash v cid + , _consensusPayloadData = Just $ EncodedPayloadData $ Chainweb.encodePayloadData $ + Chainweb.payloadWithOutputsToPayloadData (_psGenesisPayload serviceEnv) } - (a, s') <- runStateT - (runReaderT act blockEnv) - (PactBlockState startBlockHandle) - return ((a, _pbBlockHandle s')) - -tracePactBlockM :: (Logger logger, ToJSON param) => Text -> param -> Int -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM label param weight a = tracePactBlockM' label (const param) (const weight) a - -tracePactBlockM' :: (Logger logger, ToJSON param) => Text -> (a -> param) -> (a -> Int) -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM' label calcParam calcWeight a = do - e <- ask - s <- get - (r, s') <- liftIO $ trace' (logJsonTrace_ (_psLogger $ _psServiceEnv e)) label (calcParam . fst) (calcWeight . fst) - $ runStateT (runReaderT (_unPactBlockM a) e) s - put s' - return r - -logJsonTrace_ :: (MonadIO m, ToJSON a, Typeable a, NFData a, Logger logger) => logger -> LogLevel -> JsonLog a -> m () -logJsonTrace_ logger level msg = liftIO $ logFunction logger level msg + } + where + v = _chainwebVersion serviceEnv + cid = _chainId serviceEnv -- State from a block in progress, which is used to extend blocks after -- running their payloads. @@ -598,9 +569,12 @@ data BlockInProgress = BlockInProgress , _blockInProgressBlockCtx :: !BlockCtx , _blockInProgressRemainingGasLimit :: !Pact.GasLimit , _blockInProgressTransactions :: !(Transactions Chainweb.Transaction OffChainCommandResult) + , _blockInProgressNumber :: !Int + -- ^ identifier sequentially increasing for the same given BlockCtx, to + -- indicate "freshness" to the miner } -makeLenses ''PactServiceEnv +makeLenses ''ServiceEnv makeLenses ''BlockInProgress instance Eq BlockInProgress where @@ -618,9 +592,7 @@ instance HasChainId BlockInProgress where _chainId = _chainId . _blockInProgressBlockCtx {-# INLINE _chainId #-} -makeLenses ''PactBlockState - -makeLenses ''PactBlockEnv +makeLenses ''BlockEnv -- | Indicates a computed gas charge (gas amount * gas price) newtype GasSupply = GasSupply { _pact5GasSupply :: Decimal } @@ -631,18 +603,18 @@ instance J.Encode GasSupply where build = J.build . Pact.StableEncoding . Pact.LDecimal . _pact5GasSupply instance Show GasSupply where show (GasSupply g) = show g -pactTransaction :: Maybe RequestKey -> (PactDb Pact.CoreBuiltin Pact.Info -> Pact.SPVSupport -> IO a) -> PactBlockM logger tbl a -pactTransaction rk k = do - e <- view psBlockDbEnv - h <- use pbBlockHandle - (r, h') <- liftIO $ doChainwebPactDbTransaction e h rk k - pbBlockHandle .= h' +pactTransaction + :: (MonadIO m, MonadState BlockHandle m) + => BlockEnv + -> Maybe RequestKey + -> (PactDb Pact.CoreBuiltin Pact.Info -> Pact.SPVSupport -> IO a) + -> m a +pactTransaction env rk k = do + h <- get + (r, h') <- liftIO $ doChainwebPactDbTransaction (_psBlockDbEnv env) h rk k + put h' return r -localLabelBlock :: (Logger logger) => (Text, Text) -> PactBlockM logger tbl x -> PactBlockM logger tbl x -localLabelBlock lbl x = do - locally (psServiceEnv . psLogger) (addLabel lbl) x - -- -------------------------------------------------------------------------- -- -- Default Values -- @@ -690,12 +662,12 @@ data BlockInvalidError | BlockInvalidDueToInvalidTxAtRuntime TxInvalidError | BlockInvalidDueToTxDecodeFailure [Text] | BlockInvalidDueToCoinbaseFailure (Pact.PactError Pact.Info) - deriving Show + deriving stock (Show, Generic) data BlockOutputMismatchError = BlockOutputMismatchError - { blockOutputMismatchCtx :: !(EvaluationCtx BlockPayloadHash) + { blockOutputMismatchCtx :: !BlockCtx , blockOutputMismatchActualPayload :: !Chainweb.PayloadWithOutputs - , blockOutputMismatchExpectedPayload :: !(Maybe Chainweb.PayloadWithOutputs) + , blockOutputMismatchExpectedPayload :: !Chainweb.CheckablePayload } deriving Show @@ -703,7 +675,7 @@ instance J.Encode BlockOutputMismatchError where build bvf = J.object [ "ctx" J..= J.encodeWithAeson (blockOutputMismatchCtx bvf) , "actual" J..= J.encodeWithAeson (blockOutputMismatchActualPayload bvf) - , "expected" J..?= fmap J.encodeWithAeson (blockOutputMismatchExpectedPayload bvf) + , "expected" J..= J.encodeWithAeson (blockOutputMismatchExpectedPayload bvf) ] -- | Write log message @@ -725,18 +697,6 @@ logError_ l = logg_ l Error logDebug_ :: (MonadIO m, Logger logger) => logger -> Text -> m () logDebug_ l = logg_ l Debug -logInfoPact :: (Logger logger) => Text -> PactServiceM logger tbl () -logInfoPact msg = view psLogger >>= \l -> logInfo_ l msg - -logWarnPact :: (Logger logger) => Text -> PactServiceM logger tbl () -logWarnPact msg = view psLogger >>= \l -> logWarn_ l msg - -logErrorPact :: (Logger logger) => Text -> PactServiceM logger tbl () -logErrorPact msg = view psLogger >>= \l -> logError_ l msg - -logDebugPact :: (Logger logger) => Text -> PactServiceM logger tbl () -logDebugPact msg = view psLogger >>= \l -> logDebug_ l msg - -- | This function converts CommandResults into bytes in a stable way that can -- be stored on-chain. pactCommandResultToBytes :: Pact.CommandResult Pact.Hash (Pact.PactError Pact.Info) -> ByteString @@ -753,14 +713,14 @@ commandToBytes = Chainweb.Transaction . J.encodeStrict toPayloadWithOutputs :: Miner - -> Transactions Pact.Transaction OffChainCommandResult + -> Transactions Chainweb.Transaction OffChainCommandResult -> Chainweb.PayloadWithOutputs toPayloadWithOutputs mi ts = let - oldSeq :: Vector (Pact.Transaction, OffChainCommandResult) + oldSeq :: Vector (Chainweb.Transaction, OffChainCommandResult) oldSeq = _transactionPairs ts trans :: Vector Chainweb.Transaction - trans = commandToBytes . fst <$> oldSeq + trans = fst <$> oldSeq transOuts :: Vector Chainweb.TransactionOutput transOuts = Chainweb.TransactionOutput . pactCommandResultToBytes . hashPactTxLogs . snd <$> oldSeq diff --git a/src/Chainweb/Pact/Utils.hs b/src/Chainweb/Pact/Utils.hs index c42ad1e1e7..fb7cf348a7 100644 --- a/src/Chainweb/Pact/Utils.hs +++ b/src/Chainweb/Pact/Utils.hs @@ -33,11 +33,11 @@ import qualified Data.Text as T import Control.Monad.Catch -import Pact.Parse -import qualified Pact.Types.ChainId as P -import qualified Pact.Types.Term as P -import Pact.Types.ChainMeta -import Pact.Types.KeySet (ed25519HexFormat) +-- import Pact.Parse +-- import qualified Pact.Types.ChainId as P +-- import qualified Pact.Types.Term as P +-- import Pact.Types.ChainMeta +-- import Pact.Types.KeySet (ed25519HexFormat) import qualified Pact.JSON.Encode as J @@ -47,6 +47,10 @@ import Chainweb.ChainId import Chainweb.Miner.Pact import Chainweb.Payload import Chainweb.Time +import qualified Pact.Core.ChainData as P +import qualified Pact.Core.Guards as P +import Pact.Core.Guards (ed25519HexFormat) +import qualified Data.Set as Set fromPactChainId :: MonadThrow m => P.ChainId -> m ChainId fromPactChainId (P.ChainId t) = chainIdFromText t @@ -58,9 +62,9 @@ aeson :: (String -> b) -> (a -> b) -> Result a -> b aeson f _ (Error a) = f a aeson _ g (Success a) = g a -toTxCreationTime :: Time Micros -> TxCreationTime +toTxCreationTime :: Time Micros -> P.TxCreationTime toTxCreationTime (Time timespan) = - TxCreationTime $ ParsedInteger $ fromIntegral $ timeSpanToSeconds timespan + P.TxCreationTime $ fromIntegral $ timeSpanToSeconds timespan @@ -90,7 +94,7 @@ generateKAccountFromPubKey pubKey -- is valid. -- Note: We are assuming the k: account is ED25519. pubKeyToKAccountKeySet :: P.PublicKeyText -> P.KeySet -pubKeyToKAccountKeySet pubKey = P.mkKeySet [pubKey] "keys-all" +pubKeyToKAccountKeySet pubKey = P.KeySet (Set.singleton pubKey) P.KeysAll generateKeySetFromKAccount :: T.Text -> Maybe P.KeySet generateKeySetFromKAccount kacct = do diff --git a/src/Chainweb/Pact/Validations.hs b/src/Chainweb/Pact/Validations.hs index 06f8930bbb..c9cfd7db14 100644 --- a/src/Chainweb/Pact/Validations.hs +++ b/src/Chainweb/Pact/Validations.hs @@ -57,20 +57,25 @@ import qualified Pact.Core.ChainData as Pact import qualified Pact.Core.Gas.Types as Pact import qualified Pact.Core.Hash as Pact import qualified Chainweb.Pact.Transaction as Pact -import Chainweb.Utils (ebool_) +import Chainweb.Utils (ebool_, int) +import Chainweb.Version.Guards (maxBlockGasLimit) +import Control.Monad (forM) +import Numeric.Natural -- | Check whether a local Api request has valid metadata -- assertPreflightMetadata - :: Pact.Command (Pact.Payload Pact.PublicMeta c) + :: ServiceEnv tbl + -> Pact.Command (Pact.Payload Pact.PublicMeta c) -> BlockCtx -> Maybe LocalSignatureVerification - -> PactServiceM logger tbl (Either (NonEmpty Text) ()) -assertPreflightMetadata cmd@(Pact.Command pay sigs hsh) txCtx sigVerify = do - v <- view chainwebVersion - cid <- view chainId - bgl <- view psNewBlockGasLimit + -> Either (NonEmpty Text) () +assertPreflightMetadata env cmd@(Pact.Command pay sigs hsh) blockCtx sigVerify = do + let v = view chainwebVersion env + let cid = view chainId env + -- TODO PP: fix this in master too; master uses the wrong block gas limit. + let mbgl = maxBlockGasLimit v (_bctxCurrentBlockHeight blockCtx) let Pact.PublicMeta pcid _ gl gp _ _ = Pact._pMeta pay nid = Pact._pNetworkId pay @@ -79,8 +84,9 @@ assertPreflightMetadata cmd@(Pact.Command pay sigs hsh) txCtx sigVerify = do let errs = catMaybes [ eUnless "Chain id mismatch" $ assertChainId cid pcid -- TODO: use failing conversion - , eUnless "Transaction Gas limit exceeds block gas limit" - $ assertBlockGasLimit bgl gl + , mbgl >>= \bgl -> + eUnless "Transaction Gas limit exceeds block gas limit" + $ assertBlockGasLimit (Pact.GasLimit $ Pact.Gas $ int @Natural @Pact.SatWord bgl) gl , eUnless "Gas price decimal precision too high" $ assertGasPrice gp , eUnless "Network id mismatch" $ assertNetworkId v nid , eUnless "Signature list size too big" $ assertSigSize sigs @@ -88,7 +94,7 @@ assertPreflightMetadata cmd@(Pact.Command pay sigs hsh) txCtx sigVerify = do , eUnless "Tx time outside of valid range" $ assertTxTimeRelativeToParent pct cmd ] - pure $ case nonEmpty errs of + case nonEmpty errs of Nothing -> Right () Just vs -> Left vs where @@ -96,7 +102,7 @@ assertPreflightMetadata cmd@(Pact.Command pay sigs hsh) txCtx sigVerify = do | Just NoVerify <- sigVerify = True | otherwise = isRight $ assertValidateSigs hsh signers sigs - pct = _bctxParentCreationTime txCtx + pct = _bctxParentCreationTime blockCtx eUnless t assertion | assertion = Nothing diff --git a/src/Chainweb/Payload.hs b/src/Chainweb/Payload.hs index 0bb33152f8..fde277834b 100644 --- a/src/Chainweb/Payload.hs +++ b/src/Chainweb/Payload.hs @@ -139,6 +139,7 @@ module Chainweb.Payload , CheckablePayload(..) , checkablePayloadToPayloadData +, checkablePayloadExpectedHash ) where import Control.DeepSeq @@ -1425,11 +1426,16 @@ verifyPayloadWithOutputs p -- to run the payload to check if it's a valid block - but they are great -- for comparative error messages if the block is invalid. data CheckablePayload - = CheckablePayloadWithOutputs !PayloadWithOutputs - | CheckablePayload !PayloadData - deriving (Show) + = CheckablePayloadWithOutputs !PayloadWithOutputs + | CheckablePayload !BlockPayloadHash !PayloadData + deriving (Show, Generic, ToJSON) checkablePayloadToPayloadData :: CheckablePayload -> PayloadData checkablePayloadToPayloadData = \case - CheckablePayload pd -> pd - CheckablePayloadWithOutputs pwo -> payloadWithOutputsToPayloadData pwo + CheckablePayload _ pd -> pd + CheckablePayloadWithOutputs pwo -> payloadWithOutputsToPayloadData pwo + +checkablePayloadExpectedHash :: CheckablePayload -> BlockPayloadHash +checkablePayloadExpectedHash = \case + CheckablePayload h _ -> h + CheckablePayloadWithOutputs pwo -> _payloadWithOutputsPayloadHash pwo diff --git a/src/Chainweb/Payload/PayloadStore.hs b/src/Chainweb/Payload/PayloadStore.hs index c1b11798d9..4f782ef758 100644 --- a/src/Chainweb/Payload/PayloadStore.hs +++ b/src/Chainweb/Payload/PayloadStore.hs @@ -65,7 +65,7 @@ module Chainweb.Payload.PayloadStore , lookupPayloadDataWithHeightBatch -- ** Initialize Payload Database with Genesis Payloads -, initializePayloadDb +-- , initializePayloadDb -- ** insert new payload , addPayload @@ -339,20 +339,21 @@ instance (pk ~ CasKeyType (PayloadData_ a), CanReadableTransactionDbCas_ a tbl) -- | Initialize a PayloadDb with genesis payloads for the given chainweb -- version. -- -initializePayloadDb - :: CanPayloadCas tbl - => ChainwebVersion - -> PayloadDb tbl - -> IO () -initializePayloadDb v db = traverse_ initForChain $ chainIds v - where - initForChain cid - | provider /= PactProvider = - error "Chainweb.Payload.PayloadStore.initializePayloadDb: this module must only be used by Pact" - | otherwise = - addNewPayload db (genesisBlockHeight v cid) $ genesisPayload v ^?! atChain cid - where - provider = payloadProviderTypeForChain v cid +-- initializePayloadDb +-- :: CanPayloadCas tbl +-- => ChainwebVersion +-- -> +-- -> PayloadDb tbl +-- -> IO () +-- initializePayloadDb v db = traverse_ initForChain $ chainIds v +-- where +-- initForChain cid +-- | provider /= PactProvider = +-- error "Chainweb.Payload.PayloadStore.initializePayloadDb: this module must only be used by Pact" +-- | otherwise = +-- addNewPayload db (genesisBlockHeight v cid) $ genesisPayload v ^?! atChain cid +-- where +-- provider = payloadProviderTypeForChain v cid -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index ee57c862b2..5d8cc8b29a 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -54,8 +54,6 @@ module Chainweb.PayloadProvider , EncodedPayloadOutputs(..) , assertForkInfoInvariants , _forkInfoBaseHeight -, _forkInfoBaseBlockHash -, _forkInfoBaseRankedBlockHash , _forkInfoBaseRankedPayloadHash -- * New Payload @@ -521,16 +519,6 @@ _forkInfoBaseHeight fi = case _forkInfoTrace fi of [] -> _latestHeight (_forkInfoTargetState fi) (h:_) -> unwrapParent $ _evaluationCtxParentHeight h -_forkInfoBaseBlockHash :: ForkInfo -> BlockHash -_forkInfoBaseBlockHash fi = case _forkInfoTrace fi of - [] -> _latestBlockHash (_forkInfoTargetState fi) - (h:_) -> unwrapParent $ _evaluationCtxParentHash h - -_forkInfoBaseRankedBlockHash :: ForkInfo -> RankedBlockHash -_forkInfoBaseRankedBlockHash fi = case _forkInfoTrace fi of - [] -> _latestRankedBlockHash (_forkInfoTargetState fi) - (h:_) -> unwrapParent $ _evaluationCtxRankedParentHash h - _forkInfoBaseRankedPayloadHash :: ForkInfo -> RankedBlockPayloadHash _forkInfoBaseRankedPayloadHash fi = RankedBlockPayloadHash (_forkInfoBaseHeight fi) @@ -631,8 +619,8 @@ instance FromJSON EncodedPayloadOutputs where data NewPayload = NewPayload { _newPayloadChainwebVersion :: !ChainwebVersion , _newPayloadChainId :: !ChainId - , _newPayloadParentHeight :: !BlockHeight - , _newPayloadParentHash :: !BlockHash + , _newPayloadParentHeight :: !(Parent BlockHeight) + , _newPayloadParentHash :: !(Parent BlockHash) , _newPayloadBlockPayloadHash :: !BlockPayloadHash , _newPayloadEncodedPayloadData :: !(Maybe EncodedPayloadData) , _newPayloadEncodedPayloadOutputs :: !(Maybe EncodedPayloadOutputs) @@ -700,8 +688,8 @@ instance Hashable NewPayload where _newPayloadRankedParentHash :: NewPayload -> Parent RankedBlockHash _newPayloadRankedParentHash np = Parent $ RankedBlockHash - (_newPayloadParentHeight np) - (_newPayloadParentHash np) + (unwrapParent $ _newPayloadParentHeight np) + (unwrapParent $ _newPayloadParentHash np) instance HasChainwebVersion NewPayload where _chainwebVersion = _newPayloadChainwebVersion diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index a7f86c6cc9..1a0862588f 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -57,6 +57,7 @@ import Chainweb.ChainId import Chainweb.Core.Brief import Chainweb.Logger import Chainweb.MinerReward +import Chainweb.Parent import Chainweb.PayloadProvider hiding (TransactionIndex) import Chainweb.PayloadProvider.EVM.EngineAPI import Chainweb.PayloadProvider.EVM.EthRpcAPI @@ -480,7 +481,7 @@ withEvmPayloadProvider -> v -> c -> RocksDb - -> HTTP.Manager + -> Maybe HTTP.Manager -- ^ P2P Network manager. This is supposed to be shared among all P2P -- network clients. -- @@ -804,7 +805,7 @@ mkPayloadAttributes ph parentTimestamp addr nctx = PayloadAttributesV3 -- I guess, for now this fine -- better than something that looks random. randao = Utils.Randao (Ethereum.encodeLeN 0) - et = EVM.timestamp parentTimestamp (_newBlockCtxParentCreationTime nctx) + et = EVM.timestamp parentTimestamp (unwrapParent $ _newBlockCtxParentCreationTime nctx) -- -------------------------------------------------------------------------- -- -- Await New Payload @@ -949,8 +950,8 @@ awaitNewPayload p = do { _newPayloadTxCount = int $ length (_executionPayloadV1Transactions v1) , _newPayloadSize = int $ sum $ (BS.length . _transactionBytes) <$> (_executionPayloadV1Transactions v1) - , _newPayloadParentHeight = _syncStateHeight sstate - , _newPayloadParentHash = _syncStateBlockHash sstate + , _newPayloadParentHeight = Parent $ _syncStateHeight sstate + , _newPayloadParentHash = Parent $ _syncStateBlockHash sstate , _newPayloadBlockPayloadHash = EVM._hdrPayloadHash pld , _newPayloadOutputSize = 0 , _newPayloadNumber = n diff --git a/src/Chainweb/PayloadProvider/EVM/Header.hs b/src/Chainweb/PayloadProvider/EVM/Header.hs index e409fb0d34..5f1253f2ff 100644 --- a/src/Chainweb/PayloadProvider/EVM/Header.hs +++ b/src/Chainweb/PayloadProvider/EVM/Header.hs @@ -818,4 +818,3 @@ hdrHash = to _hdrHash hdrPayloadHash :: Getter Header BlockPayloadHash hdrPayloadHash = to _hdrPayloadHash - diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index 480cb0455b..f383bf91f2 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -216,7 +216,7 @@ newMinimalPayloadProvider -> v -> c -> RocksDb - -> HTTP.Manager + -> Maybe HTTP.Manager -> MinimalProviderConfig -> IO MinimalPayloadProvider newMinimalPayloadProvider logger v c rdb mgr conf @@ -451,8 +451,8 @@ makeNewPayload makeNewPayload p latest ctx = NewPayload { _newPayloadChainwebVersion = _chainwebVersion p , _newPayloadChainId = _chainId p - , _newPayloadParentHeight = _syncStateHeight latest - , _newPayloadParentHash = _syncStateBlockHash latest + , _newPayloadParentHeight = Parent $ _syncStateHeight latest + , _newPayloadParentHash = Parent $ _syncStateBlockHash latest , _newPayloadBlockPayloadHash = view payloadHash pld , _newPayloadEncodedPayloadData = Just epld , _newPayloadEncodedPayloadOutputs = Nothing diff --git a/src/Chainweb/PayloadProvider/P2P.hs b/src/Chainweb/PayloadProvider/P2P.hs index 54211fbb96..7adeea5908 100644 --- a/src/Chainweb/PayloadProvider/P2P.hs +++ b/src/Chainweb/PayloadProvider/P2P.hs @@ -100,7 +100,7 @@ data PayloadStore tbl a = PayloadStore -- payload type parameter, both here as well as in the HTTP client) , _payloadStoreLogFunction :: !LogFunction -- ^ LogFunction - , _payloadStoreMgr :: !HTTP.Manager + , _payloadStoreMgr :: !(Maybe HTTP.Manager) -- ^ Manager object for making HTTP requests , _payloadStoreGetPayloadClient :: !(RankedBlockPayloadHash -> ClientM a) -- ^ HTTP client for querying payloads (provided by the PayloadProvider) @@ -163,7 +163,7 @@ instance -- newPayloadStore :: ReadableTable tbl RankedBlockPayloadHash a - => HTTP.Manager + => Maybe HTTP.Manager -- ^ Manager for P2P networking. This manager should be shared by all -- P2P networks. -> LogFunction @@ -236,7 +236,7 @@ getPayload s candidateStore priority maybeOrigin payloadHash = do awaitTask t Just !x -> return x where - mgr = _payloadStoreMgr s + maybeMgr = _payloadStoreMgr s tbl = _payloadStoreTable s memoMap = _payloadStoreMemo s queue = _payloadStoreQueue s @@ -264,18 +264,22 @@ getPayload s candidateStore priority maybeOrigin payloadHash = do pullOrigin k Nothing = do logfun Debug $ taskMsg k "no origin" return Nothing - pullOrigin k (Just origin) = do - let originEnv = setResponseTimeout pullOriginResponseTimeout $ peerInfoClientEnv mgr origin - logfun Debug $ taskMsg k "lookup origin" - !r <- trace traceLogfun (traceLabel "pullOrigin") (rankedHashJson k) 0 - $ runClientM (_payloadStoreGetPayloadClient s k) originEnv - case r of - (Right !x) -> do - logfun Debug $ taskMsg k "received from origin" - return $ Just x - Left (e :: ClientError) -> do - logfun Debug $ taskMsg k $ "failed to receive from origin: " <> sshow e - return Nothing + pullOrigin k (Just origin) + | Nothing <- maybeMgr = do + logfun Debug $ taskMsg k "no origin" + return Nothing + | Just mgr <- maybeMgr = do + let originEnv = setResponseTimeout pullOriginResponseTimeout $ peerInfoClientEnv mgr origin + logfun Debug $ taskMsg k "lookup origin" + !r <- trace traceLogfun (traceLabel "pullOrigin") (rankedHashJson k) 0 + $ runClientM (_payloadStoreGetPayloadClient s k) originEnv + case r of + (Right !x) -> do + logfun Debug $ taskMsg k "received from origin" + return $ Just x + Left (e :: ClientError) -> do + logfun Debug $ taskMsg k $ "failed to receive from origin: " <> sshow e + return Nothing -- | Query a block payload via the task queue -- diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index c6cc5c5dde..dec440850f 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -6,60 +6,86 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Chainweb.PayloadProvider.Pact ( PactPayloadProvider(..) + , withPactPayloadProvider ) where -import Chainweb.BlockCreationTime -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB -import Chainweb.BlockHeight -import Chainweb.BlockPayloadHash +import Control.Concurrent.STM +import Data.LogMessage +import Data.Vector (Vector) +import System.LogLevel +import Control.Lens +import qualified Network.HTTP.Client as HTTP +import qualified Data.Vector as V +import Control.Monad.Reader + import Chainweb.ChainId import Chainweb.Counter -import Chainweb.Counter (Counter) import Chainweb.Logger import Chainweb.Mempool.Mempool -import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Types +import qualified Chainweb.MinerReward as MinerReward import Chainweb.Pact.Backend.Utils -import qualified Chainweb.Pact.PactService as PS +import qualified Chainweb.Pact.PactService as PactService import qualified Chainweb.Pact.Transaction as Pact import Chainweb.Pact.Types +import Chainweb.Payload import Chainweb.Payload.PayloadStore -import qualified Chainweb.Payload.PayloadStore as PDB import Chainweb.PayloadProvider -import Chainweb.PayloadProvider.P2P -import Chainweb.Storage.Table.Map -import Chainweb.Storage.Table.RocksDB -import Chainweb.Time import Chainweb.Utils import Chainweb.Version -import Control.Concurrent.Async -import Control.Concurrent.STM -import Data.IORef -import Data.LogMessage -import Data.Text (Text) -import Data.Vector (Vector) -import GHC.Stack (HasCallStack) -import System.LogLevel -import Chainweb.Payload (PayloadData) -import Data.Coerce -import Control.Lens -import qualified Network.HTTP.Client as HTTP +import qualified Data.Pool as Pool + +data PactPayloadProvider logger tbl = PactPayloadProvider logger (ServiceEnv tbl) -newtype PactPayloadProvider logger tbl = PactPayloadProvider (PactServiceEnv logger tbl) makePrisms ''PactPayloadProvider +instance HasChainId (PactPayloadProvider logger tbl) where + chainId = _PactPayloadProvider . _2 . chainId + +instance HasChainwebVersion (PactPayloadProvider logger tbl) where + chainwebVersion = _PactPayloadProvider . _2 . chainwebVersion + instance (Logger logger, CanPayloadCas tbl) => PayloadProvider (PactPayloadProvider logger tbl) where prefetchPayloads :: Logger logger => PactPayloadProvider logger tbl -> Maybe Hints -> ForkInfo -> IO () - prefetchPayloads pp hints forkInfo = undefined + prefetchPayloads _pp _hints _forkInfo = return () + syncToBlock :: Logger logger => PactPayloadProvider logger tbl -> Maybe Hints -> ForkInfo -> IO ConsensusState - syncToBlock pp hints forkInfo = undefined + syncToBlock (PactPayloadProvider logger e) hints forkInfo = + PactService.syncToFork logger e hints forkInfo + latestPayloadSTM :: Logger logger => PactPayloadProvider logger tbl -> STM NewPayload - latestPayloadSTM = readTMVar . _psMiningPayloadVar . view _PactPayloadProvider + latestPayloadSTM (PactPayloadProvider _logger e) = do + (_, _, bip) <- readTMVar (_psMiningPayloadVar e) + let pwo = toPayloadWithOutputs + (fromJuste $ _psMiner e) + (_blockInProgressTransactions bip) + return NewPayload + { _newPayloadChainwebVersion = _chainwebVersion e + , _newPayloadChainId = _chainId e + , _newPayloadParentHeight = _bctxParentHeight $ _blockInProgressBlockCtx bip + , _newPayloadParentHash = _bctxParentHash $ _blockInProgressBlockCtx bip + , _newPayloadBlockPayloadHash = _payloadWithOutputsPayloadHash pwo + , _newPayloadEncodedPayloadData = Just $ EncodedPayloadData $ encodePayloadData $ payloadWithOutputsToPayloadData pwo + -- this doesn't make it anywhere in storage, right? + , _newPayloadEncodedPayloadOutputs = Just $ EncodedPayloadOutputs $ encodeBlockOutputs $ + BlockOutputs (_payloadWithOutputsOutputsHash pwo) (snd <$> _payloadWithOutputsTransactions pwo) (_payloadWithOutputsCoinbase pwo) + , _newPayloadNumber = _blockInProgressNumber bip + -- Informative: + , _newPayloadTxCount = int $ V.length $ _payloadWithOutputsTransactions pwo + -- ^ The number of user transactions in the block. The exact way how + -- transactions are counted is provider specific. + , _newPayloadSize = 0 + -- ^ what do we do here? why do we care? + , _newPayloadOutputSize = 0 + -- ^ what do we do here? why do we care? + , _newPayloadFees = MinerReward.Stu 0 + -- I suppose this is the sum of the gas in the command results? + } + eventProof :: Logger logger => PactPayloadProvider logger tbl -> XEventId -> IO SpvProof eventProof = error "not figured out yet" @@ -67,9 +93,9 @@ instance (Logger logger, CanPayloadCas tbl) => PayloadProvider (PactPayloadProvi withPactPayloadProvider :: CanReadablePayloadCas tbl => Logger logger - => HTTP.Manager - -> ChainwebVersion + => ChainwebVersion -> ChainId + -> Maybe HTTP.Manager -> logger -> Maybe (Counter "txFailures") -> MempoolBackend Pact.Transaction @@ -78,9 +104,16 @@ withPactPayloadProvider -> PactServiceConfig -> (PactPayloadProvider logger tbl -> IO a) -> IO a -withPactPayloadProvider http ver cid logger txFailuresCounter mp pdb pactDbDir config action = - withSqliteDb cid logger pactDbDir False $ \sqlenv -> - PS.withPactService http ver cid mp logger txFailuresCounter mpa pdb sqlenv config action +withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir config action = + withSqliteDb cid logger pactDbDir False $ \readWriteSqlenv -> do + readOnlySqlPool <- Pool.newPool $ Pool.defaultPoolConfig + (startReadSqliteDb cid logger pactDbDir) + stopSqliteDb + 10 -- seconds to keep them around unused + 2 -- connections at most + & Pool.setNumStripes (Just 2) -- two stripes, one connection per stripe + PactService.withPactService ver cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config + (action . PactPayloadProvider logger) where mpa = pactMemPoolAccess mp $ addLabel ("sub-component", "MempoolAccess") logger @@ -101,14 +134,12 @@ pactMemPoolGetBlock -> logger -> BlockFill -> (MempoolPreBlockCheck Pact.Transaction to - -> BlockHeight - -> BlockHash - -> BlockCreationTime + -> EvaluationCtx () -> IO (Vector to)) -pactMemPoolGetBlock mp theLogger bf validate height hash _btime = do +pactMemPoolGetBlock mp theLogger bf validate ctx = do logFn theLogger Debug $! "pactMemPoolAccess - getting new block of transactions for " - <> "height = " <> sshow height <> ", hash = " <> sshow hash - mempoolGetBlock mp bf validate height hash + <> "height = " <> sshow (_evaluationCtxCurrentHeight ctx) <> ", hash = " <> sshow (_evaluationCtxParentHash ctx) + mempoolGetBlock mp bf validate ctx where logFn :: Logger l => l -> LogFunctionText -- just for giving GHC some type hints logFn l = logFunction l @@ -117,7 +148,7 @@ pactProcessFork :: Logger logger => MempoolBackend Pact.Transaction -> logger - -> ((Vector Pact.Transaction, Vector TransactionHash) -> IO ()) + -> ((Vector Pact.Transaction, Vector Pact.Transaction) -> IO ()) pactProcessFork mp theLogger (reintroTxs, validatedTxs) = do logFn theLogger Debug $! "pactMemPoolAccess - " <> sshow (length reintroTxs) <> " transactions to reintroduce" diff --git a/src/Chainweb/RestAPI/Utils.hs b/src/Chainweb/RestAPI/Utils.hs index 8fb6905228..f239006db8 100644 --- a/src/Chainweb/RestAPI/Utils.hs +++ b/src/Chainweb/RestAPI/Utils.hs @@ -43,6 +43,7 @@ module Chainweb.RestAPI.Utils Reassoc , setErrText , setErrJSON +, setErrJSONPact -- * API Version , Version @@ -139,6 +140,7 @@ import Chainweb.RestAPI.Orphans () import Chainweb.Utils import Chainweb.Utils.Paging import Chainweb.Version +import qualified Pact.JSON.Encode as J -- -------------------------------------------------------------------------- -- -- Servant Utils @@ -174,6 +176,12 @@ setErrJSON m e = e , errHeaders = addHeader ("Content-Type", "application/json;charset=utf-8") (errHeaders e) } +setErrJSONPact :: J.Encode a => a -> ServerError -> ServerError +setErrJSONPact m e = e + { errBody = J.encode m + , errHeaders = addHeader ("Content-Type", "application/json;charset=utf-8") (errHeaders e) + } + -- -------------------------------------------------------------------------- -- -- API Version @@ -569,4 +577,3 @@ instance HasClient m api => HasClient m (Traced s api) where type Client m (Traced s api) = Client m api clientWithRoute pm _ r = clientWithRoute pm (Proxy @api) r hoistClientMonad pm _ f c = hoistClientMonad pm (Proxy @api) f c - diff --git a/src/Chainweb/SPV/EventProof.hs b/src/Chainweb/SPV/EventProof.hs index 8138fc2f57..ef1f380e4d 100644 --- a/src/Chainweb/SPV/EventProof.hs +++ b/src/Chainweb/SPV/EventProof.hs @@ -148,6 +148,7 @@ import Pact.Core.Capabilities import Pact.Core.Hash import Pact.Core.ModRefs import Pact.Core.Errors +import Pact.Core.Pretty (renderCompactText) -- -------------------------------------------------------------------------- -- -- Pact Encoding Exceptions @@ -271,13 +272,13 @@ int256Hex x@(Int256 i) encodePactEvent :: PactEvent PactValue -> Put encodePactEvent e = do - encodeString $ _eventName e - encodeModuleName $ _eventModule e - encodeHash $ _mhHash $ _eventModuleHash e - encodeArray (_eventParams e) encodeParam + encodeString $ _peName e + encodeModuleName $ _peModule e + encodeHash $ _mhHash $ _peModuleHash e + encodeArray (_peArgs e) encodeParam encodeModuleName :: ModuleName -> Put -encodeModuleName = encodeString . asString +encodeModuleName = encodeString . renderModuleName -- | This throws a pure exception of type 'PactEventEncodingException', if the -- input bytestring is too long. @@ -308,7 +309,7 @@ encodeHash :: Hash -> Put encodeHash = encodeBytes . BS.fromShort . unHash encodeModRef :: ModRef -> Put -encodeModRef n@(ModRef (Just _) _) = throw $ UnsupportedModRefWithSpec (renderCompactText n) +encodeModRef n@(ModRef _ s) | not (null s) = throw $ UnsupportedModRefWithSpec (renderCompactText n) encodeModRef n = encodeString $ renderCompactText n -- | This throws a pure exception of type 'PactEventEncodingException', if the @@ -347,9 +348,9 @@ decodePactEvent = label "decodeEvent" $ do params <- decodeArray decodeParam return $ PactEvent name + params m mh - params decodeArray :: Get a -> Get [a] decodeArray f = label "decodeArray" $ do @@ -390,8 +391,8 @@ decodeParam = label "decodeParam" $ getWord8 >>= \case decodeModuleName :: Get ModuleName decodeModuleName = label "decodeModuleName" $ decodeString >>= \t -> case parseModuleName t of - Left e -> fail e - Right m -> return m + Nothing -> fail "invalid module name" + Just m -> return m decodeModRef :: Get ModRef decodeModRef = label "ModRef" $ ModRef diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index c56a8064ca..efe5312388 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -35,7 +35,7 @@ module Chainweb.Sync.WebBlockHeaderStore -- * , WebBlockPayloadStore(..) -, newEmptyWebPayloadStore +-- , newEmptyWebPayloadStore , newWebPayloadStore -- * Utils @@ -358,7 +358,7 @@ forkInfoForHeader wdb hdr pldData nbctx = NewBlockCtx { _newBlockCtxMinerReward = blockMinerReward v (height + 1) - , _newBlockCtxParentCreationTime = view blockCreationTime hdr + , _newBlockCtxParentCreationTime = Parent $ view blockCreationTime hdr } height = view blockHeight hdr v = _chainwebVersion hdr @@ -666,17 +666,6 @@ newWebBlockHeaderStore mgr wdb logfun = do queue <- newEmptyPQueue return $! WebBlockHeaderStore wdb m queue logfun mgr -newEmptyWebPayloadStore - :: CanPayloadCas tbl - => ChainwebVersion - -> HTTP.Manager - -> LogFunction - -> PayloadDb tbl - -> IO (WebBlockPayloadStore tbl) -newEmptyWebPayloadStore v mgr logfun payloadDb = do - initializePayloadDb v payloadDb - newWebPayloadStore mgr payloadDb logfun - newWebPayloadStore :: HTTP.Manager -> PayloadDb tbl diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index 58b962510c..4b12ca1392 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -245,7 +245,7 @@ createNewCut createNewCut hdb n t pay i c = do extension <- fromMaybeM BadAdjacents $ getCutExtension c i work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay - (h, mc') <- extendCut c pay (solveWork work n t) + (h, mc') <- extendCut c (solveWork work n t) `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg c' <- fromMaybeM BadAdjacents mc' return $ T2 h c' diff --git a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs index f7689e452c..02c4f73d4e 100644 --- a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs +++ b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs @@ -63,7 +63,7 @@ mkTestBlockDb cv rdb = do liftIO $ do wdb <- initWebBlockHeaderDb testRdb cv let pdb = newPayloadDb testRdb - initializePayloadDb cv pdb + -- initializePayloadDb cv pdb initCut <- newMVar $ genesisCut cv return $! TestBlockDb wdb pdb initCut @@ -77,7 +77,7 @@ mkTestBlockDbIO v rdb = do testRdb <- testRocksDb "mkTestBlockDbIO" rdb wdb <- initWebBlockHeaderDb testRdb v let pdb = newPayloadDb testRdb - initializePayloadDb v pdb + -- initializePayloadDb v pdb initCut <- newMVar $ genesisCut v return $! T2 (TestBlockDb wdb pdb initCut) testRdb diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index d825d3f974..18cf5d7cf5 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -38,9 +38,11 @@ module Chainweb.Test.MultiNode ( test , replayTest - , compactAndResumeTest +-- TODO: PP +-- , compactAndResumeTest , pactImportTest - , compactLiveNodeTest +-- TODO: PP +-- , compactLiveNodeTest ) where import Control.Concurrent @@ -49,7 +51,7 @@ import Control.DeepSeq import Control.Exception import Control.Lens (set, view, (^?!), ix) import Control.Monad -import Chronos qualified +-- import Chronos qualified import Patience.Map qualified as P import Data.ByteString.Base16 qualified as Base16 @@ -74,7 +76,7 @@ import Numeric.Natural import qualified Streaming.Prelude as S import Prelude hiding (log) -import System.Directory (createDirectoryIfMissing) +-- import System.Directory (createDirectoryIfMissing) import System.FilePath import System.IO.Temp import System.LogLevel @@ -91,21 +93,20 @@ import Chainweb.Chainweb import Chainweb.Chainweb.Configuration import Chainweb.Chainweb.CutResources import Chainweb.Chainweb.PeerResources -import Chainweb.Pact.Backend.Compaction qualified as Sigma -import Chainweb.Pact.Backend.Utils (withSqliteDb) +-- import Chainweb.Pact.Backend.Compaction qualified as Sigma +-- import Chainweb.Pact.Backend.Utils (withSqliteDb) import Chainweb.Cut import Chainweb.CutDB import Chainweb.Graph import Chainweb.Logger import Chainweb.Miner.Config -import Chainweb.Miner.Pact +-- import Chainweb.Miner.Pact import Chainweb.Pact.Backend.PactState (allChains, getLatestBlockHeight, getLatestPactStateAtDiffable, TableDiffable(..), addChainIdLabel) import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (ChainGrandHash(..)) import Chainweb.Pact.Backend.PactState.GrandHash.Calc qualified as GrandHash.Calc import Chainweb.Pact.Backend.PactState.GrandHash.Import qualified as GrandHash.Import import Chainweb.Pact.Backend.PactState.GrandHash.Utils qualified as GrandHash.Utils import Chainweb.Test.P2P.Peer.BootstrapConfig -import Chainweb.Test.Pact4.Utils (sigmaCompact) import Chainweb.Test.Utils import Chainweb.Time (Seconds(..)) import Chainweb.Utils @@ -185,7 +186,6 @@ multiConfig v n = defaultChainwebConfiguration v where miner = NodeMiningConfig { _nodeMiningEnabled = True - , _nodeMiner = noMiner , _nodeTestMiners = MinerCount n } @@ -229,8 +229,8 @@ harvestConsensusState logger stateVar nid (StartedChainweb cw) = do modifyMVar_ stateVar $ sampleConsensusState nid - (view (chainwebCutResources . cutsCutDb . cutDbWebBlockHeaderDb) cw) - (view (chainwebCutResources . cutsCutDb) cw) + (view (chainwebCutResources . cutResCutDb . cutDbWebBlockHeaderDb) cw) + (view (chainwebCutResources . cutResCutDb) cw) logFunctionText logger Info "shutdown node" multiNode @@ -246,7 +246,7 @@ multiNode -> IO () multiNode loglevel write bootstrapPeerInfoVar conf rdb pactDbDir nid inner = do withSystemTempDirectory "multiNode-backup-dir" $ \backupTmpDir -> - withChainweb conf logger namespacedNodeRocksDb (pactDbDir show nid) backupTmpDir False $ \cw -> do + withChainweb conf logger namespacedNodeRocksDb (pactDbDir show nid) backupTmpDir $ \cw -> do case cw of StartedChainweb cw' -> when (nid == 0) $ putMVar bootstrapPeerInfoVar @@ -314,70 +314,71 @@ runNodesForSeconds loglevel write baseConf n (Seconds seconds) rdb pactDbDir inn void $ timeout (int seconds * 1_000_000) $ runNodes loglevel write baseConf n rdb pactDbDir inner --- | Ensure that we can compact a live node(s). --- --- This test works like so: --- 1. Run a number of chainweb nodes in a network for 10 seconds. --- 2. Ensure that the nodes made sufficient progress. --- 3. Run two threads: --- - The first runs all of the nodes for 60 seconds. --- - The second compacts all of the nodes, after sleeping for 5 seconds. --- 4. At the end we compare how long each thread took. The node thread should --- outlive the compaction thread. -compactLiveNodeTest :: () - => LogLevel - -> ChainwebVersion - -> Natural - -> RocksDb - -> FilePath - -> FilePath - -> (String -> IO ()) - -> IO () -compactLiveNodeTest logLevel v n rocksDb srcPactDir targetPactDir step = do - let logFun = step . T.unpack - let logger = genericLogger logLevel logFun - - logFun "Phase 1... creating blocks" - - -- N.B: This consensus state stuff counts the number of blocks - -- in RocksDB, rather than the number of blocks in all chains - -- on the current cut. This is fine because we ultimately just want - -- to make sure that we are making progress (i.e, new blocks). - stateVar <- newMVar (emptyConsensusState v) - let ct :: Int -> StartedChainweb logger -> IO () - ct = harvestConsensusState logger stateVar - do - runNodesForSeconds logLevel logFun (multiConfig v n) n 10 rocksDb srcPactDir ct - Just stats1 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) - assertGe "average block count before proceeding" (Actual $ _statBlockCount stats1) (Expected 50) - logFun $ sshow stats1 - - let compactAll = Chronos.stopwatch_ $ do - threadDelay 5_000_000 - Sigma.withDefaultLogger logLevel $ \lgr -> do - forM_ [0 .. int @_ @Word n - 1] $ \nid -> do - -- We haven't gotten to run the node against the target yet, - -- and nothing has created its db directories, so we do that here. - createDirectoryIfMissing False (targetPactDir show nid) - forM_ (allChains v) $ \cid -> do - let logger' = addLabel ("nodeId", sshow nid) $ addLabel ("chainId", chainIdToText cid) lgr - withSqliteDb cid logger' (srcPactDir show nid) False $ \srcDb -> do - withSqliteDb cid logger' (targetPactDir show nid) False $ \targetDb -> do - sigmaCompact srcDb targetDb (BlockHeight 25) - - let run = Chronos.stopwatch_ $ do - -- It may seem a bit strange that we never run the node against the - -- compacted state (see that we are using 'srcPactDir' here). This is - -- because we are only trying to see that compacting a node as it is, - -- does not disrupt it. - runNodesForSeconds logLevel logFun (multiConfig v n) n 60 rocksDb srcPactDir ct - Just stats2 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) - assertGe "average block count before proceeding" (Actual $ _statBlockCount stats2) (Expected 100) - logFun $ sshow stats2 - - (compactTime, nodeTime) <- concurrently compactAll run - - assertGe "node runs beyond compaction" (Actual nodeTime) (Expected compactTime) +-- TODO: PP +-- -- | Ensure that we can compact a live node(s). +-- -- +-- -- This test works like so: +-- -- 1. Run a number of chainweb nodes in a network for 10 seconds. +-- -- 2. Ensure that the nodes made sufficient progress. +-- -- 3. Run two threads: +-- -- - The first runs all of the nodes for 60 seconds. +-- -- - The second compacts all of the nodes, after sleeping for 5 seconds. +-- -- 4. At the end we compare how long each thread took. The node thread should +-- -- outlive the compaction thread. +-- compactLiveNodeTest :: () +-- => LogLevel +-- -> ChainwebVersion +-- -> Natural +-- -> RocksDb +-- -> FilePath +-- -> FilePath +-- -> (String -> IO ()) +-- -> IO () +-- compactLiveNodeTest logLevel v n rocksDb srcPactDir targetPactDir step = do +-- let logFun = step . T.unpack +-- let logger = genericLogger logLevel logFun + +-- logFun "Phase 1... creating blocks" + +-- -- N.B: This consensus state stuff counts the number of blocks +-- -- in RocksDB, rather than the number of blocks in all chains +-- -- on the current cut. This is fine because we ultimately just want +-- -- to make sure that we are making progress (i.e, new blocks). +-- stateVar <- newMVar (emptyConsensusState v) +-- let ct :: Int -> StartedChainweb logger -> IO () +-- ct = harvestConsensusState logger stateVar +-- do +-- runNodesForSeconds logLevel logFun (multiConfig v n) n 10 rocksDb srcPactDir ct +-- Just stats1 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) +-- assertGe "average block count before proceeding" (Actual $ _statBlockCount stats1) (Expected 50) +-- logFun $ sshow stats1 + +-- let compactAll = Chronos.stopwatch_ $ do +-- threadDelay 5_000_000 +-- Sigma.withDefaultLogger logLevel $ \lgr -> do +-- forM_ [0 .. int @_ @Word n - 1] $ \nid -> do +-- -- We haven't gotten to run the node against the target yet, +-- -- and nothing has created its db directories, so we do that here. +-- createDirectoryIfMissing False (targetPactDir show nid) +-- forM_ (allChains v) $ \cid -> do +-- let logger' = addLabel ("nodeId", sshow nid) $ addLabel ("chainId", chainIdToText cid) lgr +-- withSqliteDb cid logger' (srcPactDir show nid) False $ \srcDb -> do +-- withSqliteDb cid logger' (targetPactDir show nid) False $ \targetDb -> do +-- sigmaCompact srcDb targetDb (BlockHeight 25) + +-- let run = Chronos.stopwatch_ $ do +-- -- It may seem a bit strange that we never run the node against the +-- -- compacted state (see that we are using 'srcPactDir' here). This is +-- -- because we are only trying to see that compacting a node as it is, +-- -- does not disrupt it. +-- runNodesForSeconds logLevel logFun (multiConfig v n) n 60 rocksDb srcPactDir ct +-- Just stats2 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) +-- assertGe "average block count before proceeding" (Actual $ _statBlockCount stats2) (Expected 100) +-- logFun $ sshow stats2 + +-- (compactTime, nodeTime) <- concurrently compactAll run + +-- assertGe "node runs beyond compaction" (Actual nodeTime) (Expected compactTime) -- | This test is essentially just calling pact-calc followed by pact-import, -- and making sure that works end to end. @@ -505,62 +506,63 @@ pactImportTest logLevel v n rocksDb pactDir step = do log Error $ "In table " <> tbl <> ", there was a difference in rowdata at rowkey " <> T.decodeUtf8 x <> "." assertFailure "Diff failed" --- | Run nodes --- Each node creates blocks --- We wait until they've made a sufficient amount of blocks --- We stop the nodes --- We compact the databases of each node --- We restart all nodes with the same database dirs --- We observe that they can make progress -compactAndResumeTest :: () - => LogLevel - -> ChainwebVersion - -> Natural - -> RocksDb - -> RocksDb - -> FilePath - -> FilePath - -> (String -> IO ()) - -> IO () -compactAndResumeTest logLevel v n srcRocksDb targetRocksDb srcPactDir targetPactDir step = do - let logFun = step . T.unpack - let logger = genericLogger logLevel logFun - - logFun "phase 1... creating blocks" - -- N.B: This consensus state stuff counts the number of blocks - -- in RocksDB, rather than the number of blocks in all chains - -- on the current cut. This is fine because we ultimately just want - -- to make sure that we are making progress (i.e, new blocks). - stateVar <- newMVar (emptyConsensusState v) - let ct :: Int -> StartedChainweb logger -> IO () - ct = harvestConsensusState logger stateVar - runNodesForSeconds logLevel logFun (multiConfig v n) n 10 srcRocksDb srcPactDir ct - Just stats1 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) - assertGe "average block count before compaction" (Actual $ _statBlockCount stats1) (Expected 50) - logFun $ sshow stats1 - - logFun "phase 2... compacting" - - logFun "phase 2.1...compacting pact state" - forM_ [0 .. int @_ @Word n - 1] $ \nid -> do - forM_ (allChains v) $ \cid -> do - let logger' = addLabel ("nodeId", sshow nid) $ addLabel ("chainId", chainIdToText cid) logger - withSqliteDb cid logger' (srcPactDir show nid) False $ \srcDb -> do - withSqliteDb cid logger' (targetPactDir show nid) False $ \targetDb -> do - sigmaCompact srcDb targetDb (BlockHeight 25) - - logFun "phase 2.2...compacting RocksDB" - forM_ [0 .. int @_ @Word n - 1] $ \nid -> do - let srcRdb = srcRocksDb { _rocksDbNamespace = T.encodeUtf8 (toText nid) } - let tgtRdb = targetRocksDb { _rocksDbNamespace = T.encodeUtf8 (toText nid) } - Sigma.compactRocksDb (addLabel ("nodeId", sshow nid) logger) v (allChains v) 20 srcRdb tgtRdb - - logFun "phase 3... restarting nodes and ensuring progress" - runNodesForSeconds logLevel logFun (multiConfig v n) { _configFullHistoricPactState = False } n 10 targetRocksDb targetPactDir ct - Just stats2 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) - -- We ensure that we've gotten to at least 1.5x the previous block count - assertGe "average block count post-compaction" (Actual $ _statBlockCount stats2) (Expected (3 * _statBlockCount stats1 `div` 2)) - logFun $ sshow stats2 +-- TODO: PP +-- -- | Run nodes +-- -- Each node creates blocks +-- -- We wait until they've made a sufficient amount of blocks +-- -- We stop the nodes +-- -- We compact the databases of each node +-- -- We restart all nodes with the same database dirs +-- -- We observe that they can make progress +-- compactAndResumeTest :: () +-- => LogLevel +-- -> ChainwebVersion +-- -> Natural +-- -> RocksDb +-- -> RocksDb +-- -> FilePath +-- -> FilePath +-- -> (String -> IO ()) +-- -> IO () +-- compactAndResumeTest logLevel v n srcRocksDb targetRocksDb srcPactDir targetPactDir step = do +-- let logFun = step . T.unpack +-- let logger = genericLogger logLevel logFun + +-- logFun "phase 1... creating blocks" +-- -- N.B: This consensus state stuff counts the number of blocks +-- -- in RocksDB, rather than the number of blocks in all chains +-- -- on the current cut. This is fine because we ultimately just want +-- -- to make sure that we are making progress (i.e, new blocks). +-- stateVar <- newMVar (emptyConsensusState v) +-- let ct :: Int -> StartedChainweb logger -> IO () +-- ct = harvestConsensusState logger stateVar +-- runNodesForSeconds logLevel logFun (multiConfig v n) n 10 srcRocksDb srcPactDir ct +-- Just stats1 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) +-- assertGe "average block count before compaction" (Actual $ _statBlockCount stats1) (Expected 50) +-- logFun $ sshow stats1 + +-- logFun "phase 2... compacting" + +-- logFun "phase 2.1...compacting pact state" +-- forM_ [0 .. int @_ @Word n - 1] $ \nid -> do +-- forM_ (allChains v) $ \cid -> do +-- let logger' = addLabel ("nodeId", sshow nid) $ addLabel ("chainId", chainIdToText cid) logger +-- withSqliteDb cid logger' (srcPactDir show nid) False $ \srcDb -> do +-- withSqliteDb cid logger' (targetPactDir show nid) False $ \targetDb -> do +-- sigmaCompact srcDb targetDb (BlockHeight 25) + +-- logFun "phase 2.2...compacting RocksDB" +-- forM_ [0 .. int @_ @Word n - 1] $ \nid -> do +-- let srcRdb = srcRocksDb { _rocksDbNamespace = T.encodeUtf8 (toText nid) } +-- let tgtRdb = targetRocksDb { _rocksDbNamespace = T.encodeUtf8 (toText nid) } +-- Sigma.compactRocksDb (addLabel ("nodeId", sshow nid) logger) v (allChains v) 20 srcRdb tgtRdb + +-- logFun "phase 3... restarting nodes and ensuring progress" +-- runNodesForSeconds logLevel logFun (multiConfig v n) { _configFullHistoricPactState = False } n 10 targetRocksDb targetPactDir ct +-- Just stats2 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) +-- -- We ensure that we've gotten to at least 1.5x the previous block count +-- assertGe "average block count post-compaction" (Actual $ _statBlockCount stats2) (Expected (3 * _statBlockCount stats1 `div` 2)) +-- logFun $ sshow stats2 replayTest :: LogLevel @@ -707,7 +709,7 @@ sampleConsensusState :: Int -- ^ node Id -> WebBlockHeaderDb - -> CutDb tbl + -> CutDb -> ConsensusState -> IO ConsensusState sampleConsensusState nid bhdb cutdb s = do diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index e223ee6f03..e3572d1f09 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -87,9 +87,10 @@ import GHC.Stack import Numeric.Natural import qualified Pact.JSON.Encode as J -import Pact.Types.Command -import Pact.Types.PactValue -import Pact.Types.Runtime (PactEvent(..), Literal(..)) +import Pact.Core.Command.Types +import Pact.Core.Capabilities +import Pact.Core.PactValue +import Pact.Core.Literal import Prelude hiding (Applicative(..)) @@ -166,6 +167,11 @@ import P2P.Test.Orphans () import System.Logger.Types import Utils.Logging +import Pact.Core.StableEncoding +import Pact.Core.Errors (pactErrorToOnChainError) +import Pact.Core.Info (spanInfoToLineInfo) +import Chainweb.PayloadProvider +import Chainweb.Parent -- -------------------------------------------------------------------------- -- -- Utils @@ -342,7 +348,7 @@ arbitraryBlockHashRecordVersionHeightChain v h cid | isWebChain graph cid = BlockHashRecord . HM.fromList . zip (toList $ adjacentChainIds graph cid) - <$> infiniteListOf arbitrary + <$> infiniteListOf (Parent <$> arbitrary) | otherwise = discard where graph @@ -376,7 +382,7 @@ arbitraryBlockHeaderVersionHeightChain v h cid entries t = liftA2 (:+:) arbitrary -- feature flags $ liftA2 (:+:) (pure $ BlockCreationTime t) -- time - $ liftA2 (:+:) arbitrary -- parent hash + $ liftA2 (:+:) (Parent <$> arbitrary) -- parent hash $ liftA2 (:+:) arbitrary -- target $ liftA2 (:+:) arbitrary -- payload hash $ liftA2 (:+:) (pure cid) -- chain id @@ -393,8 +399,6 @@ instance Arbitrary HeaderUpdate where <$> (ObjectEncoded <$> arbitrary) <*> arbitrary <*> arbitrary - <*> arbitrary - <*> arbitrary instance Arbitrary BlockHashWithHeight where arbitrary = BlockHashWithHeight <$> arbitrary <*> arbitrary @@ -412,7 +416,7 @@ instance Arbitrary CutId where instance Arbitrary CutHashes where arbitrary = CutHashes <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary - <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary + <*> arbitrary <*> arbitrary <*> arbitrary <*> (fmap EncodedPayloadData <$> arbitrary) <*> return Nothing instance Arbitrary CutHeight where @@ -688,7 +692,8 @@ arbitraryPayloadWithStructuredOutputs = resize 10 $ do payloads <- newPayloadWithOutputs <$> arbitrary <*> arbitrary - <*> pure (fmap (TransactionOutput . J.encodeStrict) <$> txs) + -- TODO: PP + <*> pure (fmap (TransactionOutput . J.encodeStrict . fmap (pactErrorToOnChainError . fmap spanInfoToLineInfo)) <$> txs) return (_crReqKey . snd <$> txs, payloads) where genResult = arbitraryCommandResultWithEvents arbitraryProofPactEvent @@ -830,16 +835,13 @@ instance Arbitrary CoordinationConfig where <$> arbitrary <*> arbitrary <*> arbitrary - <*> arbitrary - <*> arbitrary - <*> arbitrary instance Arbitrary NodeMiningConfig where arbitrary = NodeMiningConfig - <$> arbitrary <*> arbitrary <*> pure (MinerCount 10) + <$> arbitrary <*> pure (MinerCount 10) instance Arbitrary MiningConfig where - arbitrary = MiningConfig <$> arbitrary <*> arbitrary + arbitrary = MiningConfig <$> arbitrary <*> arbitrary <*> arbitrary -- -------------------------------------------------------------------------- -- -- Chainweb.SPV.EventProof @@ -854,7 +856,7 @@ arbitraryEventPactValue = oneof -- | Arbitrary Pact events that are supported in events proofs -- -arbitraryProofPactEvent :: Gen PactEvent +arbitraryProofPactEvent :: Gen (PactEvent PactValue) arbitraryProofPactEvent = PactEvent <$> arbitrary <*> listOf arbitraryEventPactValue @@ -871,7 +873,9 @@ instance Arbitrary OutputEvents where -- | Events that are supported in proofs -- -newtype ProofPactEvent = ProofPactEvent { getProofPactEvent :: PactEvent } +newtype ProofPactEvent = ProofPactEvent + { getProofPactEvent :: StableEncoding (PactEvent PactValue) + } deriving (Show) deriving newtype (Eq, FromJSON) @@ -880,7 +884,7 @@ instance ToJSON ProofPactEvent where {-# INLINEABLE toJSON #-} instance Arbitrary ProofPactEvent where - arbitrary = ProofPactEvent <$> arbitraryProofPactEvent + arbitrary = ProofPactEvent . StableEncoding <$> arbitraryProofPactEvent instance MerkleHashAlgorithm a => Arbitrary (BlockEventsHash_ a) where arbitrary = BlockEventsHash <$> arbitrary @@ -891,7 +895,9 @@ instance Arbitrary Int256 where -- | PactValues that are supported in Proofs -- -newtype EventPactValue = EventPactValue { getEventPactValue :: PactValue } +newtype EventPactValue = EventPactValue + { getEventPactValue :: PactValue + } deriving (Show, Eq, Ord) instance Arbitrary EventPactValue where @@ -939,8 +945,8 @@ instance Eq SomePayloadProof where instance Arbitrary MinerId where arbitrary = MinerId <$> arbitrary -instance Arbitrary MinerKeys where - arbitrary = MinerKeys <$> arbitrary +instance Arbitrary MinerGuard where + arbitrary = MinerGuard <$> arbitrary instance Arbitrary Miner where arbitrary = Miner <$> arbitrary <*> arbitrary diff --git a/test/lib/Chainweb/Test/Orphans/Pact.hs b/test/lib/Chainweb/Test/Orphans/Pact.hs index 5f57055c06..07c438461d 100644 --- a/test/lib/Chainweb/Test/Orphans/Pact.hs +++ b/test/lib/Chainweb/Test/Orphans/Pact.hs @@ -20,51 +20,86 @@ -- Orphand Arbitrary Instances for Pact Types -- module Chainweb.Test.Orphans.Pact -( arbitraryJsonValue -, arbitraryCommandResultWithEvents +-- ( arbitraryJsonValue +( arbitraryCommandResultWithEvents , arbitraryMaybe ) where -import qualified Data.Aeson as A -import qualified Data.Vector as V +-- import qualified Data.Aeson as A +-- import qualified Data.Vector as V -import Pact.Types.Command -import Pact.Types.Runtime +import Pact.Core.Capabilities +import Pact.Core.Command.Types +import Pact.Core.Hash +import Pact.Core.PactValue import Test.QuickCheck +import Pact.Core.Errors +-- import Pact.Core.Gas +import Pact.Core.Persistence +import Pact.Core.Names +import qualified Pact.Core.ChainData as Pact +import qualified Pact.Core.Guards as Pact -- -------------------------------------------------------------------------- -- -- -arbitraryJsonValue :: Gen A.Value -arbitraryJsonValue = go (3 :: Int) - where - go 0 = oneof - [ pure A.Null - , A.String <$> arbitrary - , A.Number <$> arbitrary - ] - go i = oneof - [ A.object <$> listOf ((,) <$> arbitrary <*> go (i - 1)) - , A.Array . V.fromList <$> listOf (go (i - 1)) - , pure A.Null - , A.String <$> arbitrary - , A.Number <$> arbitrary - ] +-- arbitraryJsonValue :: Gen A.Value +-- arbitraryJsonValue = go (3 :: Int) +-- where +-- go 0 = oneof +-- [ pure A.Null +-- , A.String <$> arbitrary +-- , A.Number <$> arbitrary +-- ] +-- go i = oneof +-- [ A.object <$> listOf ((,) <$> arbitrary <*> go (i - 1)) +-- , A.Array . V.fromList <$> listOf (go (i - 1)) +-- , pure A.Null +-- , A.String <$> arbitrary +-- , A.Number <$> arbitrary +-- ] -- | Generates only successful results without continuations. -- -arbitraryCommandResultWithEvents :: Gen PactEvent -> Gen (CommandResult Hash) +arbitraryCommandResultWithEvents :: Gen (PactEvent PactValue) -> Gen (CommandResult Hash PactErrorI) arbitraryCommandResultWithEvents genEvent = CommandResult - <$> arbitrary -- _crReqKey - <*> arbitrary -- _crTxId - <*> arbitrary -- _crResult - <*> arbitrary -- _crGas - <*> arbitrary -- _crLogs + <$> undefined -- (RequestKey <$> arbitrary) -- _crReqKey + <*> (fmap TxId <$> arbitrary) -- _crTxId + <*> undefined -- arbitrary -- _crResult -- TODO: PP + <*> undefined -- (Gas <$> arbitrary) -- _crGas + <*> undefined -- arbitrary -- _crLogs <*> pure Nothing -- _crContinuation - <*> arbitraryMaybe (resize 5 arbitraryJsonValue) -- _crMetaData + <*> undefined -- arbitraryMaybe (resize 5 arbitraryJsonValue) -- _crMetaData <*> (resize 10 (listOf genEvent)) -- _crEvents +instance Arbitrary ModuleName where + -- TODO: PP + arbitrary = undefined + +instance Arbitrary ModuleHash where + -- TODO: PP + arbitrary = undefined + +instance Arbitrary RequestKey where + -- TODO: PP + arbitrary = undefined + +instance Arbitrary Pact.ChainId where + -- TODO: PP + arbitrary = undefined + +instance Arbitrary QualifiedName where + -- TODO: PP + arbitrary = undefined + +instance Arbitrary PactValue where + -- TODO: PP + arbitrary = undefined + +instance (Arbitrary n, Arbitrary v) => Arbitrary (Pact.Guard n v) where + -- TODO: PP + arbitrary = undefined + arbitraryMaybe :: Gen a -> Gen (Maybe a) arbitraryMaybe gen = arbitrary >>= mapM (const @_ @() gen) - diff --git a/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs b/test/lib/Chainweb/Test/Pact/CmdBuilder.hs similarity index 85% rename from test/lib/Chainweb/Test/Pact5/CmdBuilder.hs rename to test/lib/Chainweb/Test/Pact/CmdBuilder.hs index 879018055b..9ee268e427 100644 --- a/test/lib/Chainweb/Test/Pact5/CmdBuilder.hs +++ b/test/lib/Chainweb/Test/Pact/CmdBuilder.hs @@ -16,12 +16,9 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} -module Chainweb.Test.Pact5.CmdBuilder where +module Chainweb.Test.Pact.CmdBuilder where -import Pact.Types.ChainMeta qualified as Pact4 -import Pact.Types.Command qualified as Pact4 -import Pact.JSON.Legacy.Value qualified as J -import Chainweb.Pact4.Transaction qualified as Pact4 +-- import Pact.JSON.Legacy.Value qualified as J import Control.Lens hiding ((.=)) import Pact.Core.Command.Types import Data.Text (Text) @@ -38,7 +35,7 @@ import Chainweb.Time import Chainweb.Version import qualified Chainweb.ChainId as Chainweb import Data.ByteString (ByteString) -import qualified Chainweb.Pact5.Transaction as Pact5 +import qualified Chainweb.Pact.Transaction as Pact import qualified Data.Text.Encoding as T import Chainweb.Utils import Data.Maybe @@ -50,13 +47,14 @@ import Pact.Core.PactValue import Pact.Core.Signer import qualified Data.Set as Set import Pact.Core.StableEncoding -import Chainweb.Pact.RestAPI.Server (validatePact5Command) +import Chainweb.Pact.RestAPI.Server (validateCommand) import Pact.Core.Command.Client (ApiKeyPair (..), mkCommandWithDynKeys) +import Pact.Core.ChainData qualified as Pact import System.Random import Control.Monad import Data.Vector qualified as Vector import Data.Map.Strict qualified as Map -import Data.Aeson qualified as Aeson +-- import Data.Aeson qualified as Aeson type TextKeyPair = (Text,Text) @@ -188,28 +186,28 @@ defaultCmd cid = CmdBuilder -- | Build parsed + verified Pact command -- TODO: Use the new `assertPact4Command` function. -buildCwCmd :: (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact5.Transaction +buildCwCmd :: (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact.Transaction buildCwCmd v cmd = buildTextCmd v cmd >>= \(c :: Command Text) -> - case validatePact5Command v c of + case validateCommand v c of Left err -> throwM $ userError $ "buildCwCmd failed: " ++ err Right cmd' -> return cmd' --- | Build a Pact4 command without parsing it. This can be useful for inserting txs directly into the mempool for testing. -buildCwCmdNoParse :: forall m. (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact4.UnparsedTransaction -buildCwCmdNoParse v cmd = do - cmd5 <- buildTextCmd v cmd - cmd4 <- case Aeson.fromJSON @(Pact4.Command Text) $ J._getLegacyValue $ J.toLegacyJsonViaEncode cmd5 of - Aeson.Error e -> throwM $ userError $ "buildCwCmdNoParse failed: " ++ e - Aeson.Success c -> return c +-- -- | Build a Pact4 command without parsing it. This can be useful for inserting txs directly into the mempool for testing. +-- buildCwCmdNoParse :: forall m. (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact4.UnparsedTransaction +-- buildCwCmdNoParse v cmd = do +-- cmd5 <- buildTextCmd v cmd +-- cmd4 <- case Aeson.fromJSON @(Pact4.Command Text) $ J._getLegacyValue $ J.toLegacyJsonViaEncode cmd5 of +-- Aeson.Error e -> throwM $ userError $ "buildCwCmdNoParse failed: " ++ e +-- Aeson.Success c -> return c - let decodePayload :: ByteString -> m (Pact4.Payload Pact4.PublicMeta Text) - decodePayload bs = case Aeson.eitherDecodeStrict' bs of - Left err -> throwM $ userError $ "buildCwCmdNoParse failed to decode json payload: " ++ err - Right payload -> return payload +-- let decodePayload :: ByteString -> m (Pact4.Payload Pact4.PublicMeta Text) +-- decodePayload bs = case Aeson.eitherDecodeStrict' bs of +-- Left err -> throwM $ userError $ "buildCwCmdNoParse failed to decode json payload: " ++ err +-- Right payload -> return payload - let payloadBytes = T.encodeUtf8 $ Pact4._cmdPayload cmd4 - payload <- decodePayload payloadBytes - return $ Pact4.mkPayloadWithText $ fmap (\_ -> (payloadBytes, payload)) cmd4 +-- let payloadBytes = T.encodeUtf8 $ Pact4._cmdPayload cmd4 +-- payload <- decodePayload payloadBytes +-- return $ Pact4.mkPayloadWithText $ fmap (\_ -> (payloadBytes, payload)) cmd4 -- | Build unparsed, unverified command -- @@ -241,7 +239,7 @@ buildRawCmd v CmdBuilder{..} = do pure cmd where nid = NetworkId (sshow v) - cid = ChainId _cbChainId + cid = Pact.ChainId _cbChainId dieL :: MonadThrow m => [Char] -> Either [Char] a -> m a dieL msg = either (\s -> throwM $ userError $ msg ++ ": " ++ s) return diff --git a/test/lib/Chainweb/Test/Pact/Utils.hs b/test/lib/Chainweb/Test/Pact/Utils.hs new file mode 100644 index 0000000000..e0faccf445 --- /dev/null +++ b/test/lib/Chainweb/Test/Pact/Utils.hs @@ -0,0 +1,182 @@ +{-# language + FlexibleContexts + , ImportQualifiedPost + , LambdaCase + , NumericUnderscores + , OverloadedStrings + , PackageImports + , TypeApplications +#-} + +module Chainweb.Test.Pact.Utils + -- ( initCheckpointer + -- , pactTxFrom4To5 + + -- * Logging + ( getTestLogLevel + , testLogFn + , getTestLogger + + -- * Mempool + -- , mempoolInsertPact5 + -- , mempoolLookupPact5 + + -- * Resources + , withTempSQLiteResource + , withInMemSQLiteResource + -- , withPactQueue + , withMempool + -- , withRunPactService + , withBlockDbs + + -- * Properties + , event + , successfulTx + -- * Utilities + , coinModuleName + ) + where + +import Chainweb.Chainweb (validatingMempoolConfig) +import Chainweb.ChainId +import Chainweb.Logger +import Chainweb.Mempool.InMem +import Chainweb.Mempool.Mempool (MempoolBackend (..)) +--import Chainweb.Pact.Backend.RelationalCheckpointer +import Chainweb.Pact.Backend.Types (SQLiteEnv) +import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) +import Chainweb.Pact.PactService +import Chainweb.Pact.Types +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Payload.PayloadStore +import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.Storage.Table.RocksDB +-- import Chainweb.Utils +import Chainweb.Version +import Chainweb.WebBlockHeaderDB +-- import Control.Concurrent hiding (throwTo) +-- import Control.Exception (AsyncException (..), throwTo) +-- import Control.Lens hiding (elements, only) +-- import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource (ResourceT, allocate) +-- import Control.Monad.Trans.Resource qualified as Resource +-- import Data.Aeson qualified as Aeson +-- import Data.ByteString.Short qualified as SBS +-- import Data.HashSet (HashSet) +-- import Data.HashSet qualified as HashSet +import Data.Maybe (fromMaybe) +import Data.Text (Text) +import Data.Text qualified as Text +-- import Data.Text.Encoding qualified as Text +import Data.Text.IO qualified as Text +-- import Data.Vector (Vector) +-- import Data.Vector qualified as Vector +import Pact.Core.Command.Types qualified as Pact +-- import Pact.Core.Hash qualified as Pact +-- import Pact.Core.Pretty qualified as Pact +import Pact.Core.Gas qualified as Pact +-- import Pact.JSON.Encode qualified as J +import System.Environment (lookupEnv) +import System.LogLevel +import PropertyMatchers ((?)) +import PropertyMatchers qualified as P +import Pact.Core.PactValue +import Pact.Core.Capabilities +import Pact.Core.Names + +withBlockDbs :: ChainwebVersion -> RocksDb -> ResourceT IO (PayloadDb RocksDbTable, WebBlockHeaderDb) +withBlockDbs v rdb = do + webBHDb <- liftIO $ initWebBlockHeaderDb rdb v + let payloadDb = newPayloadDb rdb + -- liftIO $ initializePayloadDb v payloadDb + return (payloadDb, webBHDb) + +-- | Internal. See https://www.sqlite.org/c3ref/open.html +withSQLiteResource + :: String + -> ResourceT IO SQLiteEnv +withSQLiteResource file = snd <$> allocate + (openSQLiteConnection file chainwebPragmas) + closeSQLiteConnection + +-- | Open a temporary file-backed SQLite database. +withTempSQLiteResource :: ResourceT IO SQLiteEnv +withTempSQLiteResource = withSQLiteResource "" + +-- | Open a temporary in-memory SQLite database. +withInMemSQLiteResource :: ResourceT IO SQLiteEnv +withInMemSQLiteResource = withSQLiteResource ":memory:" + +withMempool + :: (Logger logger) + => logger + -> ServiceEnv tbl + -> ResourceT IO (MempoolBackend Pact.Transaction) +withMempool logger pact = do + let mempoolCfg = validatingMempoolConfig + (_chainId pact) (_chainwebVersion pact) + (Pact.GasLimit $ Pact.Gas 150_000) (Pact.GasPrice 1e-8) + (execPreInsertCheckReq logger pact) + liftIO $ startInMemoryMempoolTest mempoolCfg + +-- withRunPactService :: (Logger logger) +-- => logger +-- -> ChainwebVersion +-- -> ChainId +-- -> MempoolBackend Pact.Transaction +-- -> WebBlockHeaderDb +-- -> PayloadDb RocksDbTable +-- -> PactServiceConfig +-- -> ResourceT IO () +-- withRunPactService logger v cid mempool webBHDb payloadDb pactServiceConfig = do +-- sqlite <- withTempSQLiteResource +-- blockHeaderDb <- liftIO $ getWebBlockHeaderDb webBHDb cid +-- mempoolConsensus <- liftIO $ mkMempoolConsensus mempool blockHeaderDb (Just payloadDb) +-- let mempoolAccess = pactMemPoolAccess mempoolConsensus logger + +-- void $ Resource.allocate +-- (forkIO $ runPactService v cid logger Nothing pactQueue mempoolAccess blockHeaderDb payloadDb sqlite pactServiceConfig) --bhdb (_bdbPayloadDb tdb) sqlite pactServiceConfig) +-- (\tid -> throwTo tid ThreadKilled) + +getTestLogLevel :: IO LogLevel +getTestLogLevel = do + let parseLogLevel txt = case Text.toUpper txt of + "DEBUG" -> Debug + "INFO" -> Info + "WARN" -> Warn + "ERROR" -> Error + _ -> Error + fromMaybe Error . fmap (parseLogLevel . Text.pack) <$> lookupEnv "CHAINWEB_TEST_LOG_LEVEL" + +-- | Generally, we want tests to throw an exception on an Error log, but we don't want +-- to throw an exception on any other level of log. +testLogFn :: LogLevel -> Text -> IO () +testLogFn ll msg = case ll of + Error -> do + error (Text.unpack msg) + _ -> do + Text.putStrLn msg + +getTestLogger :: IO GenericLogger +getTestLogger = do + logLevel <- getTestLogLevel + return $ genericLogger logLevel (testLogFn logLevel) + +-- usually we don't want to check the module hash +event + :: P.Prop Text + -> P.Prop [PactValue] + -> P.Prop ModuleName + -> P.Prop (PactEvent PactValue) +event n args modName = P.checkAll + [ P.fun _peName n + , P.fun _peArgs args + , P.fun _peModule modName + ] + +coinModuleName :: ModuleName +coinModuleName = ModuleName "coin" Nothing + +successfulTx :: P.Prop (Pact.CommandResult log err) +successfulTx = P.fun Pact._crResult ? P.match Pact._PactResultOk P.succeed diff --git a/test/lib/Chainweb/Test/Pact4/Utils.hs b/test/lib/Chainweb/Test/Pact4/Utils.hs deleted file mode 100644 index bf51e08b4a..0000000000 --- a/test/lib/Chainweb/Test/Pact4/Utils.hs +++ /dev/null @@ -1,1118 +0,0 @@ -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE DataKinds #-} - -{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} --- | --- Module: Chainweb.Test.Pact4.Utils --- Copyright: Copyright © 2018 - 2020 Kadena LLC. --- License: See LICENSE file --- Maintainer: Emily Pillmore --- Stability: experimental --- --- Unit test for Pact execution via (inprocess) API in Chainweb -module Chainweb.Test.Pact4.Utils -( -- * Test key data - SimpleKeyPair -, sender00 -, sender01 -, sender00Ks -, sender02WebAuthn -, sender02WebAuthnPrefixed -, sender03WebAuthn -, allocation00KeyPair -, testKeyPairs -, mkKeySetData --- * 'PactValue' helpers -, pInteger -, pString -, pDecimal -, pBool -, pList -, pKeySet -, pObject --- * event helpers -, mkEvent -, mkTransferEvent -, mkTransferXChainEvent -, mkTransferXChainRecdEvent -, mkXYieldEvent -, mkXResumeEvent --- * Capability helpers -, mkCapability -, mkTransferCap -, mkGasCap -, mkCoinCap -, mkXChainTransferCap --- * Command builder -, defaultCmd -, buildCwCmd -, buildTextCmd -, mkExec' -, mkExec -, mkCont -, mkContMsg -, ContMsg (..) -, mkEd25519Signer -, mkEd25519Signer' -, mkWebAuthnSigner -, mkWebAuthnSigner' -, CmdBuilder(..) -, cbSigners -, cbVerifiers -, cbRPC -, cbNonce -, cbChainId -, cbSender -, cbGasLimit -, cbGasPrice -, cbTTL -, cbCreationTime -, CmdSigner -, csSigner -, csPrivKey --- * Pact Service creation -, withPactTestBlockDb -, withPactTestBlockDb' -, withWebPactExecutionService -, withWebPactExecutionServiceCompaction -, withPactCtxSQLite -, WithPactCtxSQLite --- * Other service creation -, initializeSQLite -, freeSQLiteResource -, freeGasModel -, testPactServiceConfig -, withBlockHeaderDb -, withSqliteDb --- * Mempool utils -, delegateMemPoolAccess -, withDelegateMempool -, setMempool -, setOneShotMempool --- * Block formation -, runCut -, Noncer -, zeroNoncer --- * Pact State -, sigmaCompact -, PactRow(..) -, getLatestPactState -, getPactUserTables --- * miscellaneous -, toTxCreationTime -, dummyLogger -, stdoutDummyLogger -, hunitDummyLogger -, pactTestLogger -, someTestVersionHeader -, someBlockHeader -, testPactFilesDir -, getPWOByHeader -, throwIfNotPact4 - -) where - -import Control.Arrow ((&&&)) -import Control.Concurrent.Async -import Control.Concurrent.MVar -import Control.Lens hiding ((.=)) -import Control.Monad -import Control.Monad.Catch -import Control.Monad.IO.Class - -import Data.Aeson (Value(..), object, (.=), Key) -import Data.ByteString (ByteString) -import qualified Data.ByteString.Short as BS -import Data.Decimal -import Data.Foldable -import qualified Data.HashMap.Strict as HM -import Data.IORef -import Data.List qualified as List -import Data.Map (Map) -import qualified Data.Map.Strict as M -import Data.Maybe -import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import Data.String -import qualified Data.Vector as V - -import Database.SQLite3.Direct (Database) - -import GHC.Generics - -import Streaming.Prelude qualified as S -import System.LogLevel - -import Test.Tasty -import Test.Tasty.HUnit(assertFailure) - --- internal pact modules - -import Pact.ApiReq (ApiKeyPair(..), mkKeyPairs) -import Pact.Gas -import Pact.JSON.Legacy.Value -import Pact.Types.Capability -import qualified Pact.Types.ChainId as P -import Pact.Types.ChainMeta -import Pact.Types.Command -import Pact.Types.Crypto -import Pact.Types.Exp -import Pact.Types.Gas -import Pact.Types.Hash -import Pact.Types.Info (noInfo) -import Pact.Types.KeySet -import qualified Pact.Types.Logger as P -import Pact.Types.Names -import Pact.Types.PactValue -import Pact.Types.RPC -import Pact.Types.Runtime (PactEvent(..)) -import Pact.Types.Term -import Pact.Types.Util (parseB16TextOnly) -import Pact.Types.Verifier - --- internal modules - -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB hiding (withBlockHeaderDb) -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Graph -import Chainweb.Logger -import Chainweb.Miner.Pact -import Chainweb.Pact.Backend.Compaction qualified as Sigma -import Chainweb.Pact.Backend.PactState qualified as PactState -import Chainweb.Pact.Backend.PactState (TableDiffable(..), Table(..), PactRow(..)) -import Chainweb.Pact.PactService.Checkpointer.Internal (initCheckpointerResources) -import Chainweb.Pact.Backend.SQLite.DirectV2 - -import Chainweb.Pact.Backend.Utils hiding (tbl, withSqliteDb) -import Chainweb.Pact.PactService -import Chainweb.Pact.RestAPI.Server (validateCommand) -import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Types -import Chainweb.Pact4.Types -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Test.Cut -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Utils -import Chainweb.Test.Utils.BlockHeader -import Chainweb.Test.TestVersions -import Chainweb.Time -import qualified Chainweb.Pact4.Transaction as Pact4 -import Chainweb.Utils -import Chainweb.Version (ChainwebVersion(..), chainIds) -import qualified Chainweb.Version as Version -import Chainweb.Version.Utils (someChainId) -import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService - -import Chainweb.Storage.Table.RocksDB -import Chainweb.Pact.Backend.Types - --- ----------------------------------------------------------------------- -- --- Keys - -testPactFilesDir :: FilePath -testPactFilesDir = "test/pact/" - -type SimpleKeyPair = (Text,Text) - -sender00 :: SimpleKeyPair -sender00 = ("368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca" - ,"251a920c403ae8c8f65f59142316af3c82b631fba46ddea92ee8c95035bd2898") - -sender01 :: SimpleKeyPair -sender01 = ("6be2f485a7af75fedb4b7f153a903f7e6000ca4aa501179c91a2450b777bd2a7" - ,"2beae45b29e850e6b1882ae245b0bab7d0689ebdd0cd777d4314d24d7024b4f7") - -sender02WebAuthnPrefixed :: SimpleKeyPair -sender02WebAuthnPrefixed = - ("WEBAUTHN-a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf" - ,"fecd4feb1243d715d095e24713875ca76c476f8672ec487be8e3bc110dd329ab") - -sender02WebAuthn :: SimpleKeyPair -sender02WebAuthn = - ("a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf" - ,"fecd4feb1243d715d095e24713875ca76c476f8672ec487be8e3bc110dd329ab") - -sender03WebAuthn :: SimpleKeyPair -sender03WebAuthn = - ("a4010103272006215820ad72392508272b4c45536976474cdd434e772bfd630738ee9aac7343e7222eb6" - ,"ebe7d1119a53863fa64be7347d82d9fcc9ebeb8cbbe480f5e8642c5c36831434") - -allocation00KeyPair :: SimpleKeyPair -allocation00KeyPair = - ( "d82d0dcde9825505d86afb6dcc10411d6b67a429a79e21bda4bb119bf28ab871" - , "c63cd081b64ae9a7f8296f11c34ae08ba8e1f8c84df6209e5dee44fa04bcb9f5" - ) - - --- | Make trivial keyset data -mkKeySetData :: Key -> [SimpleKeyPair] -> Value -mkKeySetData name keys = object [ name .= map fst keys ] - -sender00Ks :: KeySet -sender00Ks = mkKeySet - ["368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca"] - "keys-all" - --- ----------------------------------------------------------------------- -- --- PactValue helpers - --- | Make PactValue from 'Integral' -pInteger :: Integer -> PactValue -pInteger = PLiteral . LInteger - --- | Make PactValue from text -pString :: Text -> PactValue -pString = PLiteral . LString - --- | Make PactValue from decimal -pDecimal :: Decimal -> PactValue -pDecimal = PLiteral . LDecimal - --- | Make PactValue from boolean -pBool :: Bool -> PactValue -pBool = PLiteral . LBool - -pList :: [PactValue] -> PactValue -pList = PList . V.fromList - -pKeySet :: KeySet -> PactValue -pKeySet = PGuard . GKeySet - -pObject :: [(FieldKey,PactValue)] -> PactValue -pObject = PObject . ObjectMap . M.fromList - -mkEvent - :: MonadThrow m - => Text - -- ^ name - -> [PactValue] - -- ^ params - -> ModuleName - -> Text - -- ^ module hash - -> m PactEvent -mkEvent n params m mh = do - mh' <- decodeB64UrlNoPaddingText mh - return $ PactEvent n params m (ModuleHash (Hash $ BS.toShort mh')) - -mkTransferEvent - :: MonadThrow m - => Text - -- ^ sender - -> Text - -- ^ receiver - -> Decimal - -- ^ amount - -> ModuleName - -> Text - -- ^ module hash - -> m PactEvent -mkTransferEvent sender receiver amount m mh = - mkEvent "TRANSFER" [pString sender,pString receiver,pDecimal amount] m mh - -mkTransferXChainEvent - :: MonadThrow m - => Text - -- ^ sender - -> Text - -- ^ receiver - -> Decimal - -- ^ amount - -> ModuleName - -> Text - -- ^ module hash - -> Text - -- ^ target chain id - -> m PactEvent -mkTransferXChainEvent sender receiver amount m mh tid - = mkEvent "TRANSFER_XCHAIN" args m mh - where - args = - [ pString sender - , pString receiver - , pDecimal amount - , pString tid - ] - -mkTransferXChainRecdEvent - :: MonadThrow m - => Text - -- ^ sender - -> Text - -- ^ receiver - -> Decimal - -- ^ amount - -> ModuleName - -> Text - -- ^ module hash - -> Text - -- ^ source chain id - -> m PactEvent -mkTransferXChainRecdEvent sender receiver amount m mh sid - = mkEvent "TRANSFER_XCHAIN_RECD" args m mh - where - args = - [ pString sender - , pString receiver - , pDecimal amount - , pString sid - ] - -mkXYieldEvent - :: MonadThrow m - => Text - -- ^ sender - -> Text - -- ^ receiver - -> Decimal - -- ^ amount - -> KeySet - -- ^ receiver guard - -> ModuleName - -> Text - -- ^ module hash - -> Text - -- ^ target chain id - -> Text - -- ^ source chain id - -> m PactEvent -mkXYieldEvent sender receiver amount ks mn mh tid sid - = mkEvent "X_YIELD" args mn mh - where - args = - [ pString tid - , pString "coin.transfer-crosschain" - , pList - [ pString sender - , pString receiver - , pKeySet ks - , pString sid - , pDecimal amount - ] - ] - -mkXResumeEvent - :: MonadThrow m - => Text - -- ^ sender - -> Text - -- ^ receiver - -> Decimal - -- ^ amount - -> KeySet - -- ^ receiver guard - -> ModuleName - -> Text - -- ^ module hash - -> Text - -- ^ target chain id - -> Text - -- ^ source chain id - -> m PactEvent -mkXResumeEvent sender receiver amount ks mn mh tid sid - = mkEvent "X_RESUME" args mn mh - where - args = - [ pString tid - , pString "coin.transfer-crosschain" - , pList - [ pString sender - , pString receiver - , pKeySet ks - , pString sid - , pDecimal amount - ] - ] - --- ----------------------------------------------------------------------- -- --- Capability helpers - --- | Cap smart constructor. -mkCapability :: ModuleName -> Text -> [PactValue] -> SigCapability -mkCapability mn cap args = SigCapability (QualifiedName mn cap noInfo) args - --- | Convenience to make caps like TRANSFER, GAS etc. -mkCoinCap :: Text -> [PactValue] -> SigCapability -mkCoinCap n = mkCapability "coin" n - -mkTransferCap :: Text -> Text -> Decimal -> SigCapability -mkTransferCap sender receiver amount = mkCoinCap "TRANSFER" - [ pString sender, pString receiver, pDecimal amount ] - -mkXChainTransferCap :: Text -> Text -> Decimal -> Text -> SigCapability -mkXChainTransferCap sender receiver amount cid = mkCoinCap "TRANSFER_XCHAIN" - [ pString sender - , pString receiver - , pDecimal amount - , pString cid - ] - -mkGasCap :: SigCapability -mkGasCap = mkCoinCap "GAS" [] - - - --- ----------------------------------------------------------------------- -- --- CmdBuilder and friends - - --- | Pair a 'Signer' with private key. -data CmdSigner = CmdSigner - { _csSigner :: !Signer - , _csPrivKey :: !Text - } deriving (Eq,Show,Ord,Generic) -makeLenses ''CmdSigner - --- | Make ED25519 signer. -mkEd25519Signer :: Text -> Text -> [SigCapability] -> CmdSigner -mkEd25519Signer pubKey privKey caps = CmdSigner - { _csSigner = signer - , _csPrivKey = privKey - } - where - signer = Signer - { _siScheme = Nothing - , _siPubKey = pubKey - , _siAddress = Nothing - , _siCapList = caps - } - -mkEd25519Signer' :: SimpleKeyPair -> [SigCapability] -> CmdSigner -mkEd25519Signer' (pub,priv) = mkEd25519Signer pub priv - -mkWebAuthnSigner :: Text -> Text -> [SigCapability] -> CmdSigner -mkWebAuthnSigner pubKey privKey caps = CmdSigner - { _csSigner = signer - , _csPrivKey = privKey - } - where - signer = Signer - { _siScheme = Just WebAuthn - , _siPubKey = pubKey - , _siAddress = Nothing - , _siCapList = caps } - -mkWebAuthnSigner' :: SimpleKeyPair -> [SigCapability] -> CmdSigner -mkWebAuthnSigner' (pub, priv) caps = mkWebAuthnSigner pub priv caps - --- | Chainweb-oriented command builder. -data CmdBuilder = CmdBuilder - { _cbSigners :: ![CmdSigner] - , _cbVerifiers :: ![Verifier ParsedVerifierProof] - , _cbRPC :: !(PactRPC Text) - , _cbNonce :: !Text - , _cbChainId :: !ChainId - , _cbSender :: !Text - , _cbGasLimit :: !GasLimit - , _cbGasPrice :: !GasPrice - , _cbTTL :: !TTLSeconds - , _cbCreationTime :: !TxCreationTime - } deriving (Eq,Show,Generic) -makeLenses ''CmdBuilder - --- | Make code-only Exec PactRPC -mkExec' :: Text -> PactRPC Text -mkExec' ecode = mkExec ecode Null - --- | Make Exec PactRPC -mkExec :: Text -> Value -> PactRPC Text -mkExec ecode edata = Exec $ ExecMsg ecode (toLegacyJson edata) - -mkCont :: ContMsg -> PactRPC Text -mkCont = Continuation - -mkContMsg :: PactId -> Int -> ContMsg -mkContMsg pid step = ContMsg - { _cmPactId = pid - , _cmStep = step - , _cmRollback = False - , _cmData = toLegacyJson Null - , _cmProof = Nothing } - --- | Default builder. -defaultCmd :: CmdBuilder -defaultCmd = CmdBuilder - { _cbSigners = [] - , _cbVerifiers = [] - , _cbRPC = mkExec' "1" - , _cbNonce = "nonce" - , _cbChainId = unsafeChainId 0 - , _cbSender = "sender00" - , _cbGasLimit = 10_000 - , _cbGasPrice = 0.000_1 - , _cbTTL = 300 -- 5 minutes - , _cbCreationTime = 0 -- epoch - } - --- | Build parsed + verified Pact command --- --- TODO: Use the new `assertPact4Command` function. -buildCwCmd :: (MonadThrow m, MonadIO m) => Text -> ChainwebVersion -> CmdBuilder -> m Pact4.Transaction -buildCwCmd nonce v cmd = buildRawCmd nonce v cmd >>= \(c :: Command ByteString) -> - case validateCommand v (_cbChainId cmd) (T.decodeUtf8 <$> c) of - Left err -> throwM $ userError $ "buildCmd failed: " ++ T.unpack err - Right cmd' -> return cmd' - --- | Build unparsed, unverified command --- -buildTextCmd :: Text -> ChainwebVersion -> CmdBuilder -> IO (Command Text) -buildTextCmd nonce v = fmap (fmap T.decodeUtf8) . buildRawCmd nonce v - --- | Build a raw bytestring command --- -buildRawCmd :: (MonadThrow m, MonadIO m) => Text -> ChainwebVersion -> CmdBuilder -> m (Command ByteString) -buildRawCmd nonce v (set cbNonce nonce -> CmdBuilder{..}) = do - kps <- liftIO $ traverse mkDynKeyPairs _cbSigners - cmd <- liftIO $ mkCommandWithDynKeys kps _cbVerifiers pm _cbNonce (Just nid) _cbRPC - pure cmd - where - nid = P.NetworkId (sshow v) - cid = fromString $ show (chainIdInt _cbChainId :: Int) - pm = PublicMeta cid _cbSender _cbGasLimit _cbGasPrice _cbTTL _cbCreationTime - -dieL :: MonadThrow m => [Char] -> Either [Char] a -> m a -dieL msg = either (\s -> throwM $ userError $ msg ++ ": " ++ s) return - -mkDynKeyPairs :: MonadThrow m => CmdSigner -> m (DynKeyPair, [SigCapability]) -mkDynKeyPairs (CmdSigner Signer{..} privKey) = - case (fromMaybe ED25519 _siScheme, _siPubKey, privKey) of - (ED25519, pub, priv) -> do - pub' <- either diePubKey return $ parseEd25519PubKey =<< parseB16TextOnly pub - priv' <- either diePrivKey return $ parseEd25519SecretKey =<< parseB16TextOnly priv - return (DynEd25519KeyPair (pub', priv'), _siCapList) - - (WebAuthn, pub, priv) -> do - let (pubKeyStripped, wasPrefixed) = fromMaybe - (pub, WebAuthnPubKeyBare) - ((,WebAuthnPubKeyPrefixed) <$> T.stripPrefix webAuthnPrefix pub) - pubWebAuthn <- - either diePubKey return (parseWebAuthnPublicKey =<< parseB16TextOnly pubKeyStripped) - privWebAuthn <- - either diePrivKey return (parseWebAuthnPrivateKey =<< parseB16TextOnly priv) - return (DynWebAuthnKeyPair wasPrefixed pubWebAuthn privWebAuthn, _siCapList) - where - diePubKey str = error $ "pubkey: " <> str - diePrivKey str = error $ "privkey: " <> str - -toApiKp :: MonadThrow m => CmdSigner -> m ApiKeyPair -toApiKp (CmdSigner Signer{..} privKey) = do - sk <- dieL "private key" $ parseB16TextOnly privKey - pk <- dieL "public key" $ parseB16TextOnly _siPubKey - let keyPair = ApiKeyPair (PrivBS sk) (Just (PubBS pk)) _siAddress _siScheme (Just _siCapList) - return $! keyPair - --- | Legacy; better to use 'CmdSigner'/'CmdBuilder'. --- if caps are empty, gas cap is implicit. otherwise it must be included -testKeyPairs :: SimpleKeyPair -> Maybe [SigCapability] -> IO [(DynKeyPair, [SigCapability])] -testKeyPairs skp capsm = do - kp <- toApiKp $ mkEd25519Signer' skp (fromMaybe [] capsm) - mkKeyPairs [kp] - --- ----------------------------------------------------------------------- -- --- Service creation utilities - -pactTestLogger :: (String -> IO ()) -> Bool -> P.Loggers -pactTestLogger backend showAll = P.initLoggers backend f (P.LogRules mempty) - where - f :: (String -> IO ()) -> P.LogName -> String -> String -> IO () - f _ b "ERROR" d = P.doLog (\_ -> return ()) b "ERROR" d - f _ b "DEBUG" d | not showAll = P.doLog (\_ -> return ()) b "DEBUG" d - f _ b "INFO" d | not showAll = P.doLog (\_ -> return ()) b "INFO" d - f _ b "DDL" d | not showAll = P.doLog (\_ -> return ()) b "DDL" d - f a b c d = P.doLog a b c d - --- | Test Pact Execution Context for running inside 'PactServiceM'. --- Only used internally. -data TestPactCtx logger tbl = TestPactCtx - { _testPactCtxState :: !(MVar PactServiceState) - , _testPactCtxEnv :: !(PactServiceEnv logger tbl) - } - -evalPactServiceM_ :: TestPactCtx logger tbl -> PactServiceM logger tbl a -> IO a -evalPactServiceM_ ctx pact = modifyMVar (_testPactCtxState ctx) $ \s -> do - T2 a s' <- runPactServiceM s (_testPactCtxEnv ctx) pact - return (s',a) - -destroyTestPactCtx :: TestPactCtx logger tbl -> IO () -destroyTestPactCtx = void . takeMVar . _testPactCtxState - --- | setup TestPactCtx, internal function. --- Use 'withPactCtxSQLite' in tests. -testPactCtxSQLite - :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) - => logger - -> ChainwebVersion - -> Version.ChainId - -> BlockHeaderDb - -> PayloadDb tbl - -> SQLiteEnv - -> PactServiceConfig - -> IO (TestPactCtx logger tbl) -testPactCtxSQLite logger v cid bhdb pdb sqlenv conf = do - cp <- initCheckpointerResources defaultModuleCacheLimit sqlenv DoNotPersistIntraBlockWrites cpLogger v cid - let rs = readRewards - !ctx <- TestPactCtx - <$!> newMVar (PactServiceState mempty) - <*> pure (mkPactServiceEnv cp rs) - evalPactServiceM_ ctx (initialPayloadState v cid) - return ctx - where - cpLogger = addLabel ("chain-id", chainIdToText cid) $ addLabel ("sub-component", "checkpointer") $ logger - mkPactServiceEnv :: Checkpointer logger -> MinerRewards -> PactServiceEnv logger tbl - mkPactServiceEnv cp rs = PactServiceEnv - { _psMempoolAccess = Nothing - , _psCheckpointer = cp - , _psPdb = pdb - , _psBlockHeaderDb = bhdb - , _psMinerRewards = rs - , _psReorgLimit = _pactReorgLimit conf - , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout conf - , _psOnFatalError = defaultOnFatalError mempty - , _psVersion = v - , _psAllowReadsInLocal = _pactAllowReadsInLocal conf - , _psLogger = addLabel ("chain-id", chainIdToText cid) $ addLabel ("component", "pact") $ logger - , _psGasLogger = do - guard (_pactLogGas conf) - return - $ addLabel ("chain-id", chainIdToText cid) - $ addLabel ("component", "pact") - $ addLabel ("sub-component", "gas") - $ logger - - , _psBlockGasLimit = _pactNewBlockGasLimit conf - , _psEnableLocalTimeout = False - , _psTxFailuresCounter = Nothing - , _psTxTimeLimit = _pactTxTimeLimit conf - } - -freeGasModel :: TxContext -> GasModel -freeGasModel = const $ constGasModel 0 - ---- | A queue-less WebPactExecutionService (for all chains) ---- with direct chain access map for local. -withWebPactExecutionServiceCompaction - :: (Logger logger) - => logger - -> ChainwebVersion - -> PactServiceConfig - -> TestBlockDb - -> ChainMap MemPoolAccess - -> ((WebPactExecutionService, HM.HashMap ChainId (SQLiteEnv, PactExecutionService), WebPactExecutionService, HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) -> IO a) -- TODO: second 'WebPactExecutionService' seems unnecessary? - -> IO a -withWebPactExecutionServiceCompaction logger v pactConfig bdb mempools act = - withDbs $ \srcSqlEnvs -> - withDbs $ \targetSqlEnvs -> do - srcPacts <- mkPacts srcSqlEnvs - let srcWeb = mkWebPactExecutionService (snd <$> srcPacts) - - targetPacts <- mkPacts targetSqlEnvs - let targetWeb = mkWebPactExecutionService (snd <$> targetPacts) - - act (srcWeb, srcPacts, targetWeb, targetPacts) - where - mkPacts :: [SQLiteEnv] -> IO (HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) - mkPacts sqlEnvs = fmap HM.fromList - $ traverse (\(dbEnv, cid) -> (cid,) . (dbEnv,) <$> mkTestPactExecutionService dbEnv cid) - $ zip sqlEnvs - $ toList - $ chainIds v - - withDbs :: ([SQLiteEnv] -> IO x) -> IO x - withDbs f = foldl' (\soFar _ -> withDb soFar) f (chainIds v) [] - - withDb :: ([SQLiteEnv] -> IO x) -> [SQLiteEnv] -> IO x - withDb g envs = withTempSQLiteConnection chainwebPragmas $ \s -> g (s : envs) - - mkTestPactExecutionService :: () - => SQLiteEnv - -> ChainId - -> IO PactExecutionService - mkTestPactExecutionService sqlenv c = do - bhdb <- getBlockHeaderDb c bdb - ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig - return $ PactExecutionService - { _pactNewBlock = \_ m fill ph -> - evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph - , _pactContinueBlock = \_ bip -> - evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip - , _pactValidateBlock = \h d -> - evalPactServiceM_ ctx $ fst <$> execValidateBlock (mempools ^?! atChain c) h d - , _pactLocal = \pf sv rd cmd -> - evalPactServiceM_ ctx $ execLocal cmd pf sv rd - , _pactLookup = \_cid cd hashes -> - evalPactServiceM_ ctx $ execLookupPactTxs cd hashes - , _pactPreInsertCheck = \_ txs -> - evalPactServiceM_ ctx $ V.map (\_ -> Nothing) <$> execPreInsertCheckReq txs - , _pactBlockTxHistory = \h d -> - evalPactServiceM_ ctx $ execBlockTxHistory h d - , _pactHistoricalLookup = \h d k -> - evalPactServiceM_ ctx $ execHistoricalLookup h d k - , _pactSyncToBlock = \h -> - evalPactServiceM_ ctx $ execSyncToBlock h - , _pactReadOnlyReplay = \l u -> - evalPactServiceM_ ctx $ execReadOnlyReplay l u - } - --- | A queue-less WebPactExecutionService (for all chains) --- with direct chain access map for local. -withWebPactExecutionService - :: (Logger logger) - => logger - -> ChainwebVersion - -> PactServiceConfig - -> TestBlockDb - -> ChainMap MemPoolAccess - -> ((WebPactExecutionService,HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) -> IO a) - -> IO a -withWebPactExecutionService logger v pactConfig bdb mempools act = - withDbs $ \sqlenvs -> do - pacts <- fmap HM.fromList - $ traverse (\(dbEnv, cid) -> (cid,) . (dbEnv,) <$> mkPact dbEnv cid) - $ zip sqlenvs - $ toList - $ chainIds v - act (mkWebPactExecutionService (snd <$> pacts), pacts) - where - withDbs f = foldl' (\soFar _ -> withDb soFar) f (chainIds v) [] - withDb g envs = withTempSQLiteConnection chainwebPragmas $ \s -> g (s : envs) - - mkPact :: SQLiteEnv -> ChainId -> IO PactExecutionService - mkPact sqlenv c = do - bhdb <- getBlockHeaderDb c bdb - ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig - return $ PactExecutionService - { _pactNewBlock = \_ m fill ph -> - evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph - , _pactContinueBlock = \_ bip -> - evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip - , _pactValidateBlock = \h d -> - evalPactServiceM_ ctx $ fst <$> execValidateBlock (mempools ^?! atChain c) h d - , _pactLocal = \pf sv rd cmd -> - evalPactServiceM_ ctx $ execLocal cmd pf sv rd - , _pactLookup = \_cid cd hashes -> - evalPactServiceM_ ctx $ execLookupPactTxs cd hashes - , _pactPreInsertCheck = \_ txs -> - evalPactServiceM_ ctx $ V.map (\_ -> Nothing) <$> execPreInsertCheckReq txs - , _pactBlockTxHistory = \h d -> - evalPactServiceM_ ctx $ execBlockTxHistory h d - , _pactHistoricalLookup = \h d k -> - evalPactServiceM_ ctx $ execHistoricalLookup h d k - , _pactSyncToBlock = \h -> - evalPactServiceM_ ctx $ execSyncToBlock h - , _pactReadOnlyReplay = \l u -> - evalPactServiceM_ ctx $ execReadOnlyReplay l u - } - --- | Noncer for 'runCut' -type Noncer = ChainId -> IO Nonce - -zeroNoncer :: Noncer -zeroNoncer = const (return $ Nonce 0) - --- | Populate blocks for every chain of the current cut. Uses provided pact --- service to produce a new block, add it to dbs, etc. -runCut - :: ChainwebVersion - -> TestBlockDb - -> WebPactExecutionService - -> GenBlockTime - -> Noncer - -> Miner - -> IO () -runCut v bdb pact genTime noncer miner = - forM_ (chainIds v) $ \cid -> do - ph <- ParentHeader <$> getParentTestBlockDb bdb cid - !newBlock <- throwIfNoHistory =<< _webPactNewBlock pact cid miner NewBlockFill ph - let pout = newBlockToPayloadWithOutputs newBlock - n <- noncer cid - - -- skip this chain if mining fails and retry with the next chain. - whenM (addTestBlockDb bdb (succ $ view blockHeight $ unwrapParent ph) n genTime cid pout) $ do - h <- getParentTestBlockDb bdb cid - void $ _webPactValidateBlock pact h (CheckablePayloadWithOutputs pout) - -initializeSQLite :: IO SQLiteEnv -initializeSQLite = open2 file >>= \case - Left (_err, _msg) -> - internalError "initializeSQLite: A connection could not be opened." - Right r -> return r - where - file = "" {- temporary sqlitedb -} - -freeSQLiteResource :: SQLiteEnv -> IO () -freeSQLiteResource sqlenv = void $ close_v2 sqlenv - -type WithPactCtxSQLite logger tbl = forall a . PactServiceM logger tbl a -> IO a - --- | Used to run 'PactServiceM' functions directly on a database (ie not use checkpointer). -withPactCtxSQLite - :: (Logger logger, CanReadablePayloadCas tbl) - => logger - -> ChainwebVersion - -> IO BlockHeaderDb - -> IO (PayloadDb tbl) - -> PactServiceConfig - -> (WithPactCtxSQLite logger tbl -> TestTree) - -> TestTree -withPactCtxSQLite logger v bhdbIO pdbIO conf f = - withResource - initializeSQLite - freeSQLiteResource $ \io -> - withResource (start io) destroy $ \ctxIO -> f $ \toPact -> do - ctx <- ctxIO - evalPactServiceM_ ctx toPact - where - destroy = destroyTestPactCtx - cid = someChainId v - start ios = do - bhdb <- bhdbIO - pdb <- pdbIO - s <- ios - testPactCtxSQLite logger v cid bhdb pdb s conf - -toTxCreationTime :: Integral a => Time a -> TxCreationTime -toTxCreationTime (Time timespan) = TxCreationTime $ fromIntegral $ timeSpanToSeconds timespan - --- | 'MemPoolAccess' that delegates all calls to the contents of provided `IORef`. -delegateMemPoolAccess :: IORef MemPoolAccess -> MemPoolAccess -delegateMemPoolAccess r = MemPoolAccess - { mpaGetBlock = \a b c d e -> call mpaGetBlock $ \f -> f a b c d e - , mpaSetLastHeader = \a -> call mpaSetLastHeader ($ a) - , mpaProcessFork = \a -> call mpaProcessFork ($ a) - , mpaBadlistTx = \a -> call mpaBadlistTx ($ a) - } - where - call :: (MemPoolAccess -> f) -> (f -> IO a) -> IO a - call f g = readIORef r >>= g . f - --- | use a "delegate" which you can dynamically reset/modify. --- Returns the updateable 'IORef MemPoolAccess` for use --- in tests, and the plain 'MemPoolAccess' for initializing --- PactService etc. -withDelegateMempool - :: (IO (IORef MemPoolAccess, MemPoolAccess) -> TestTree) - -> TestTree -withDelegateMempool = withResource' start - where - start = (id &&& delegateMemPoolAccess) <$> newIORef mempty - --- | Set test mempool using IORef. -setMempool :: MonadIO m => IO (IORef MemPoolAccess) -> MemPoolAccess -> m () -setMempool refIO mp = liftIO (refIO >>= flip writeIORef mp) - --- | Set test mempool wrapped with a "one shot" 'mpaGetBlock' adapter. -setOneShotMempool :: MonadIO m => IO (IORef MemPoolAccess) -> MemPoolAccess -> m () -setOneShotMempool mpRefIO mp = do - oneShot <- liftIO $ newIORef False - setMempool mpRefIO $ mp - { mpaGetBlock = \g v i a e -> readIORef oneShot >>= \case - False -> writeIORef oneShot True >> mpaGetBlock mp g v i a e - True -> mempty - } - -withBlockHeaderDb - :: IO RocksDb - -> BlockHeader - -> (IO BlockHeaderDb -> TestTree) - -> TestTree -withBlockHeaderDb iordb b = withResource start stop - where - start = do - rdb <- testRocksDb "withBlockHeaderDb" =<< iordb - testBlockHeaderDb rdb b - stop = closeBlockHeaderDb - --- | Single-chain Pact via service queue. --- --- The difference between this and 'withPactTestBlockDb' is that, --- this function takes a `SQLiteEnv` resource which it then exposes --- to the test function. --- --- TODO: Consolidate these two functions. -withPactTestBlockDb' - :: ChainwebVersion - -> ChainId - -> RocksDb - -> IO SQLiteEnv - -> IO MemPoolAccess - -> PactServiceConfig - -> (IO (SQLiteEnv,PactQueue,TestBlockDb) -> TestTree) - -> TestTree -withPactTestBlockDb' version cid rdb sqlEnvIO mempoolIO pactConfig f = - withResourceT (mkTestBlockDb version rdb) $ \bdbio -> - withResource (startPact bdbio) stopPact $ f . fmap (view _2) - where - startPact bdbio = do - reqQ <- newPactQueue 2000 - bdb <- bdbio - sqlEnv <- sqlEnvIO - mempool <- mempoolIO - bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) cid - let pdb = _bdbPayloadDb bdb - a <- async $ runForever (\_ _ -> return ()) "Chainweb.Test.Pact4.Utils.withPactTestBlockDb" $ - runPactService version cid logger Nothing reqQ mempool bhdb pdb sqlEnv pactConfig - return (a, (sqlEnv,reqQ,bdb)) - - stopPact (a, _) = cancel a - - -- Ideally, we should throw 'error' when the logger is invoked, because - -- error logs should not happen in production and should always be resolved. - -- Unfortunately, that's not yet always the case. So we just drop the - -- message. - -- - logger = genericLogger Error (\_ -> return ()) - -withSqliteDb :: () - => ChainId - -> IO FilePath - -> (IO SQLiteEnv -> TestTree) - -> TestTree -withSqliteDb cid iodir s = withResource start stop s - where - start = do - dir <- iodir - startSqliteDb cid logger dir False - - stop env = do - stopSqliteDb env - - -- Ideally, we should throw 'error' when the logger is invoked, because - -- error logs should not happen in production and should always be resolved. - -- Unfortunately, that's not yet always the case. So we just drop the - -- message. - -- - logger = genericLogger Error (\_ -> return ()) - --- | Single-chain Pact via service queue. -withPactTestBlockDb - :: ChainwebVersion - -> ChainId - -> RocksDb - -> IO MemPoolAccess - -> PactServiceConfig - -> (IO (SQLiteEnv,PactQueue,TestBlockDb) -> TestTree) - -> TestTree -withPactTestBlockDb version cid rdb mempoolIO pactConfig f = - withResourceT (withTempDir "pact-dir") $ \iodir -> - withResourceT (mkTestBlockDb version rdb) $ \bdbio -> - withResource (startPact bdbio iodir) stopPact $ f . fmap (view _2) - where - startPact bdbio iodir = do - reqQ <- newPactQueue 2000 - dir <- iodir - bdb <- bdbio - mempool <- mempoolIO - bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) cid - let pdb = _bdbPayloadDb bdb - sqlEnv <- startSqliteDb cid logger dir False - a <- async $ runForever (\_ _ -> return ()) "Chainweb.Test.Pact4.Utils.withPactTestBlockDb" $ - runPactService version cid logger Nothing reqQ mempool bhdb pdb sqlEnv pactConfig - return (a, (sqlEnv,reqQ,bdb)) - - stopPact (a, (sqlEnv, _, _)) = cancel a >> stopSqliteDb sqlEnv - - -- Ideally, we should throw 'error' when the logger is invoked, because - -- error logs should not happen in production and should always be resolved. - -- Unfortunately, that's not yet always the case. So we just drop the - -- message. - -- - logger = genericLogger Error (\_ -> return ()) - -dummyLogger :: GenericLogger -dummyLogger = genericLogger Error (error . T.unpack) - -stdoutDummyLogger :: GenericLogger -stdoutDummyLogger = genericLogger Error (putStrLn . T.unpack) - -hunitDummyLogger :: (String -> IO ()) -> GenericLogger -hunitDummyLogger f = genericLogger Error (f . T.unpack) - -someTestVersion :: ChainwebVersion -someTestVersion = instantCpmTestVersion petersonChainGraph - -someTestVersionHeader :: BlockHeader -someTestVersionHeader = someBlockHeader someTestVersion 10 - --- | The runtime is linear in the requested height. This can be slow if a large --- block height is requested for a chainweb version that simulates realtime --- mining. It is fast enough for testing purposes with "fast" mining chainweb --- versions like 'someTestVersion' for block heights up to, say, 1000. --- -someBlockHeader :: ChainwebVersion -> BlockHeight -> BlockHeader -someBlockHeader v 0 = genesisBlockHeader v (unsafeChainId 0) -someBlockHeader v h = (!! (int h - 1)) - $ testBlockHeaders - $ ParentHeader - $ genesisBlockHeader v (unsafeChainId 0) - --- | Get all pact user tables. --- --- Note: This consumes a stream. If you are writing a test --- with very large pact states (think: Gigabytes), use --- the streaming version of this function from --- 'Chainweb.Pact.Backend.PactState'. -getPactUserTables :: Database -> IO (Map Text [PactRow]) -getPactUserTables db = fmap (M.map (List.sortOn (\pr -> (pr.rowKey, pr.txId)))) $ do - S.foldM_ - (\m tbl -> pure (M.insert tbl.name tbl.rows m)) - (pure M.empty) - pure - (PactState.getPactTables db) - --- | Get active/latest pact state. --- --- Note: This consumes a stream. If you are writing a test --- with very large pact states (think: Gigabytes), use --- the streaming version of this function from --- 'Chainweb.Pact.Backend.PactState'. -getLatestPactState :: Database -> IO (Map Text (Map ByteString ByteString)) -getLatestPactState db = do - S.foldM_ - (\m td -> pure (M.insert td.name td.rows m)) - (pure M.empty) - pure - (PactState.getLatestPactStateDiffable db) - -sigmaCompact :: () - => SQLiteEnv - -> SQLiteEnv - -> BlockHeight - -> IO () -sigmaCompact srcDb targetDb targetBlockHeight = do - Sigma.withDefaultLogger Warn $ \logger -> do - Sigma.compactPactState logger Sigma.defaultRetainment targetBlockHeight srcDb targetDb - -getPWOByHeader :: BlockHeader -> TestBlockDb -> IO PayloadWithOutputs -getPWOByHeader h (TestBlockDb _ pdb _) = - lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) >>= \case - Nothing -> throwM $ userError "getPWOByHeader: payload not found" - Just pwo -> return pwo - -throwIfNotPact4 :: Version.ForSomePactVersion f -> IO (f Version.Pact4) -throwIfNotPact4 h = case h of - Version.ForSomePactVersion Version.Pact4T a -> do - pure a - Version.ForSomePactVersion Version.Pact5T _ -> do - assertFailure "throwIfNotPact4: should be pact4" diff --git a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction.hs deleted file mode 100644 index 912aa762ff..0000000000 --- a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction.hs +++ /dev/null @@ -1,279 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} - -module Chainweb.Test.Pact4.VerifierPluginTest.Transaction -( tests -) where - -import Control.Lens hiding ((.=)) -import Control.Monad.Reader -import Data.IORef -import qualified Data.Vector as V -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Pact.Types.Capability -import Pact.Types.Command -import Pact.Types.Info (noInfo) -import qualified Pact.JSON.Encode as PactJSON -import Pact.Types.PactValue -import Pact.Types.Term -import Pact.Types.Verifier hiding (verifierName) - -import Chainweb.Miner.Pact -import Chainweb.Pact.Types -import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Version - -import qualified Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Message.After225 as After225 -import qualified Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Message.Before225 as Before225 -import Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils - - -tests :: RocksDb -> TestTree -tests rdb = testGroup testName - [ test generousConfig "verifierTest" verifierTest - - , test generousConfig "recoverValidatorAnnouncementSuccess" hyperlaneRecoverValidatorAnnouncementSuccess - , test generousConfig "recoverValidatorAnnouncementIncorrectSignatureFailure" - hyperlaneRecoverValidatorAnnouncementIncorrectSignatureFailure - , test generousConfig "recoverValidatorAnnouncementDifferentSignerFailure" - hyperlaneRecoverValidatorAnnouncementDifferentSignerFailure - - , testGroup "Message" - [ Before225.tests rdb - , After225.tests rdb - ] - ] - where - testName = "Chainweb.Test.Pact4.VerifierPluginTest.Transaction" - -- This is way more than what is used in production, but during testing - -- we can be generous. - generousConfig = testPactServiceConfig { _pactNewBlockGasLimit = 300_000 } - - test pactConfig tname f = - withResourceT (mkTestBlockDb testVersion rdb) $ \bdbIO -> do - testCaseSteps tname $ \step -> do - bdb <- bdbIO - let logger = hunitDummyLogger step - mempools <- onAllChains testVersion $ \_ -> do - mempoolRef <- newIORef mempty - return (mempoolRef, delegateMemPoolAccess mempoolRef) - withWebPactExecutionService logger testVersion pactConfig bdb (snd <$> mempools) $ \(pact,_) -> - runReaderT f $ - SingleEnv bdb pact (mempools ^?! atChain cid . _1) noMiner cid - -verifierTest :: PactTestM () -verifierTest = do - runToHeight 118 - - let cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "G" noInfo) [] - - runBlockTest - [ PactTxTest - (buildBasic (mkExec' "(enforce-verifier 'allow)")) - (assertTxFailure "Should not resolve enforce-verifier" "Cannot resolve enforce-verifier") - , PactTxTest - (buildBasic' - (set cbVerifiers - [Verifier - (VerifierName "missing") - (ParsedVerifierProof $ pString "") - [cap]]) - (mkExec' "1")) - (assertTxSuccess - "Should not run verifiers before they're enabled" (pDecimal 1)) - ] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () (enforce-verifier 'allow))" - , "(defun x () (with-capability (G) 1)))" - ] - ) - (assertTxSuccess - "Should allow enforce-verifier in a capability" - (pString "Loaded module free.m, hash QNTlTCp-KMPkT52CEo_0zGaLJ_PnAxsenyhUck1njcc") - ) - , PactTxTest - (buildBasicGas 10000 (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - assertTxFailure - "verifier not present" - "Verifier failure allow: not in transaction" - cr - assertTxGas "verifier errors charge all gas" 10000 cr - ) - , PactTxTest - (buildBasic' - (set cbVerifiers - [Verifier - (VerifierName "allow") - (ParsedVerifierProof $ pString (PactJSON.encodeText cap)) - [cap] - ] - ) - (mkExec' "(free.m.x)") - ) - (\cr -> liftIO $ do - assertTxSuccess "should have succeeded" (pDecimal 1) cr - -- The **Allow** verifier costs 100 gas flat - assertEqual "gas should have been charged" 335 (_crGas cr) - ) - , PactTxTest - (buildBasic' - (set cbVerifiers - [Verifier - (VerifierName "missing") - (ParsedVerifierProof $ pString (PactJSON.encodeText cap)) - [cap] - ] - ) - (mkExec' "(free.m.x)") - ) - (assertTxFailure - "should have failed, missing verifier" - "Tx verifier error: verifier does not exist: missing") - ] - --- hyperlane validator announcement tests - -hyperlaneRecoverValidatorAnnouncementSuccess :: PactTestM () -hyperlaneRecoverValidatorAnnouncementSuccess = do - runToHeight 119 - let verifierName = "hyperlane_v3_announcement" - - let cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [pString "storagelocation", pString "0x6c414e7a15088023e28af44ad0e1d593671e4b15", pString "kb-mailbox"] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (location:string signer:string mailbox:string) (enforce-verifier '" <> verifierName <> "))" - , "(defun x () (with-capability (K \"storagelocation\" \"0x6c414e7a15088023e28af44ad0e1d593671e4b15\" \"kb-mailbox\") 1)))"]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash JbKuarvFOs3FGnpQ3R2exafA2gKonqa5Ls1-l3rIh8I")) - , checkVerifierNotInTx verifierName - , PactTxTest - (buildBasic' - (set cbGasLimit 20000 . set cbVerifiers - [Verifier - (VerifierName verifierName) - (ParsedVerifierProof $ - PList $ V.fromList - [ pString "storagelocation" - -- TODO: generate instead of using the precomputed value - , pString "U7oftiGhn7rpWJydP6t0FKStdcRd223a8uSTqKjs8K8nJW7U84tzBOgPZTtGKnncwiu8l1185vB38c7-Ov7avBw" - , pString "kb-mailbox" - ] - ) - [cap]]) - (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - assertTxSuccess "should have succeeded" (pDecimal 1) cr - assertEqual "gas should have been charged" 16492 (_crGas cr)) - ] - -hyperlaneRecoverValidatorAnnouncementIncorrectSignatureFailure :: PactTestM () -hyperlaneRecoverValidatorAnnouncementIncorrectSignatureFailure = do - runToHeight 119 - let verifierName = "hyperlane_v3_announcement" - - let cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [pString "storagelocation", pString "0x6c414e7a15088023e28af44ad0e1d593671e4b15", pString "kb-mailbox"] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (location:string signer:string mailbox:string) (enforce-verifier '" <> verifierName <> "))" - , "(defun x () (with-capability (K \"storagelocation\" \"0x6c414e7a15088023e28af44ad0e1d593671e4b15\" \"kb-mailbox\") 1)))"]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash JbKuarvFOs3FGnpQ3R2exafA2gKonqa5Ls1-l3rIh8I")) - , checkVerifierNotInTx verifierName - , PactTxTest - (buildBasic' - (set cbGasLimit 20000 . set cbVerifiers - [Verifier - (VerifierName verifierName) - (ParsedVerifierProof $ - PList $ V.fromList - [ pString "storagelocation" - - -- bad signature (same as from the previous test but the different first symbol) - , pString "Q7oftiGhn7rpWJydP6t0FKStdcRd223a8uSTqKjs8K8nJW7U84tzBOgPZTtGKnncwiu8l1185vB38c7-Ov7avBw" - - , pString "kb-mailbox" - ] - ) - [cap]]) - (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - assertTxFailure "should have failed with incorrect signature" "Tx verifier error: Failed to recover the address from the signature" cr - assertTxGas "verifier errors charge all gas" 20000 cr) - ] - -hyperlaneRecoverValidatorAnnouncementDifferentSignerFailure :: PactTestM () -hyperlaneRecoverValidatorAnnouncementDifferentSignerFailure = do - runToHeight 119 - let verifierName = "hyperlane_v3_announcement" - - let cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - -- bad signer (same as from the previous test but the different first symbol) - [pString "storagelocation", pString "0x5c414e7a15088023e28af44ad0e1d593671e4b15", pString "kb-mailbox"] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (location:string signer:string mailbox:string) (enforce-verifier '" <> verifierName <> "))" - , "(defun x () (with-capability (K \"storagelocation\" \"0x5c414e7a15088023e28af44ad0e1d593671e4b15\" \"kb-mailbox\") 1)))"]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash jZR2sS2FjdR-3udU9-DNL1RfsjKISk4AuhNm43yueOA")) - , checkVerifierNotInTx verifierName - , PactTxTest - (buildBasic' - (set cbGasLimit 20000 . set cbVerifiers - [Verifier - (VerifierName verifierName) - (ParsedVerifierProof $ - PList $ V.fromList - [ pString "storagelocation" - , pString "U7oftiGhn7rpWJydP6t0FKStdcRd223a8uSTqKjs8K8nJW7U84tzBOgPZTtGKnncwiu8l1185vB38c7-Ov7avBw" - , pString "kb-mailbox" - ] - ) - [cap]]) - (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - let errMsg = "Tx verifier error: Incorrect signer. Expected: PLiteral (LString {_lString = \"0x6c414e7a15088023e28af44ad0e1d593671e4b15\"}) but got PLiteral (LString {_lString = \"0x5c414e7a15088023e28af44ad0e1d593671e4b15\"})" - assertTxFailure "should have failed with incorrect signer" errMsg cr - assertTxGas "verifier errors charge all gas" 20000 cr) - ] diff --git a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/After225.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/After225.hs deleted file mode 100644 index 73b6ee237c..0000000000 --- a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/After225.hs +++ /dev/null @@ -1,358 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} - -module Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Message.After225 (tests) where - -import Control.Lens hiding ((.=)) -import Control.Monad.Reader -import qualified Data.ByteString as B -import qualified Data.Text as T -import qualified Data.Vector as V -import qualified Data.Map.Strict as M -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Pact.Types.Capability -import Pact.Types.Command -import Pact.Types.PactValue -import Pact.Types.Term -import Pact.Types.Runtime -import Pact.Types.Verifier hiding (verifierName) - -import Chainweb.Miner.Pact -import Chainweb.Pact.Types -import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Utils -import Chainweb.Utils.Serialization -import Chainweb.VerifierPlugin.Hyperlane.Binary -import Chainweb.VerifierPlugin.Hyperlane.Utils - -import Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils -import Chainweb.Version -import Data.IORef - -tests :: RocksDb -> TestTree -tests rdb = testGroup "After225" - [ test generousConfig "verifySuccess" hyperlaneVerifySuccess - , test generousConfig "verifyMoreValidatorsSuccess" hyperlaneVerifyMoreValidatorsSuccess - , test generousConfig "verifyThresholdZeroError" hyperlaneVerifyThresholdZeroError - , test generousConfig "verifyWrongSignersFailure" hyperlaneVerifyWrongSignersFailure - , test generousConfig "verifyNotEnoughRecoveredSignaturesFailure" hyperlaneVerifyNotEnoughRecoveredSignaturesFailure - , test generousConfig "verifyNotEnoughCapabilitySignaturesFailure" hyperlaneVerifyNotEnoughCapabilitySignaturesFailure - , test generousConfig "verifyIncorrectProofFailure" hyperlaneVerifyMerkleIncorrectProofFailure - , test generousConfig "verifyFailureNotEnoughSignaturesToPassThreshold" hyperlaneVerifyFailureNotEnoughSignaturesToPassThreshold - ] - where - -- This is way more than what is used in production, but during testing - -- we can be generous. - generousConfig = testPactServiceConfig { _pactNewBlockGasLimit = 300_000 } - - test pactConfig tname f = withResourceT (mkTestBlockDb testVersion rdb) $ \bdbIO -> - testCaseSteps tname $ \step -> do - bdb <- bdbIO - let logger = hunitDummyLogger step - mempools <- onAllChains testVersion $ \_ -> do - mempoolRef <- newIORef mempty - return (mempoolRef, delegateMemPoolAccess mempoolRef) - withWebPactExecutionService logger testVersion pactConfig bdb (snd <$> mempools) $ \(pact,_) -> - runReaderT f $ - SingleEnv bdb pact (mempools ^?! atChain cid . _1) noMiner cid - --- hyperlane message tests - --- | Hyperlane test message TokenMessageERC20 encoded in base64: --- --- { "amount": 0.000000000000000123 --- , "chainId": "4" --- , "recipient": { "test-keys" : {"pred": "keys-all", "keys": ["da1a339bd82d2c2e9180626a00dc043275deb3ababb27b5738abf6b9dcee8db6"]} } --- } --- -hyperlaneTokenMessageBase64 :: T.Text -hyperlaneTokenMessageBase64 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHsABHsicHJlZCI6ICJrZXlzLWFsbCIsICJrZXlzIjpbImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiXX0" - --- | Hyperlane test message encoded in base64 -hyperlaneMessageBase64 :: T.Text -hyperlaneMessageBase64 = encodeB64UrlNoPaddingText $ runPutS $ putHyperlaneMessage $ - HyperlaneMessage - { hmVersion = 3 - , hmNonce = 0 - , hmOriginDomain = 31337 - , hmSender = either (error . show) id $ decodeB64UrlNoPaddingText "AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY" - , hmDestinationDomain = 626 - , hmRecipient = either (error . show) id $ decodeB64UrlNoPaddingText "AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU" - , hmMessageBody = either (error . show) id $ decodeB64UrlNoPaddingText hyperlaneTokenMessageBase64 - } - -hyperlaneMessageId :: T.Text -hyperlaneMessageId = encodeB64UrlNoPaddingText $ keccak256ByteString $ either (error . show) id $ decodeB64UrlNoPaddingText $ hyperlaneMessageBase64 - --- | Hyperlane test MerkleTree Metadata encoded in base64 --- Test data was generated using the following test in the hyperlane codebase --- https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/b14f997810ebd7dbdff2ac6622a149ae77010ae3/solidity/test/isms/MultisigIsm.t.sol#L35 -mkHyperlaneMerkleTreeMetadataBase64 :: B.ByteString -> [B.ByteString] -> T.Text -mkHyperlaneMerkleTreeMetadataBase64 proof signatures = encodeB64UrlNoPaddingText $ runPutS $ putMerkleRootMultisigIsmMetadata $ - MerkleRootMultisigIsmMetadata - { mrmimOriginMerkleTreeAddress = decodeHexUnsafe "0x2e234dae75c793f67a35089c9d99245e1c58470b" - , mrmimMessageIdIndex = 0 - , mrmimSignedCheckpointMessageId = either (error . show) id $ decodeB64UrlNoPaddingText hyperlaneMessageId - , mrmimMerkleProof = proof - , mrmimSignedCheckpointIndex = 0 - , mrmimSignatures = signatures - } - -hyperlaneMerkleTreeCorrectProof :: B.ByteString -hyperlaneMerkleTreeCorrectProof = decodeHexUnsafe "0x0000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea32293237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7358448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" - -hyperlaneMerkleTreeIncorrectProof :: B.ByteString -hyperlaneMerkleTreeIncorrectProof - = B.take 31 hyperlaneMerkleTreeCorrectProof - <> "\x19" - <> B.drop 32 hyperlaneMerkleTreeCorrectProof - -validSigner :: T.Text -validSigner = "0x2bd2e3ba4861fae19d87cd77f8557bbad8e92d23" - -validSignature :: T.Text -validSignature = "0xfabe80dd5bf4440e5e7fbc3cdf12325df9c00beb1281c5ddf12e77177046790c49f531ccebb29ba9c9664a581ed1870873850e0cf0c231b779e21f48a1d0dcea1b" - --- | Deploys a contract with a valid signer -deployContractWith :: [T.Text] -> Integer -> T.Text -> PactTxTest -deployContractWith signers threshold moduleHash = - PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defschema hyperlane_message" - , " version:integer" - , " nonce:integer" - , " originDomain:integer" - , " destinationDomain:integer" - , " sender:string" - , " recipient:string" - , " messageBody:string" - , ")" - , "(defcap G () true)" - , "(defcap K (messageId:string message:object{hyperlane_message} signers:[string] threshold:integer)" - , " (enforce-verifier 'hyperlane_v3_message)" - , " (enforce (= messageId \"" <> hyperlaneMessageId <> "\") \"invalid messageId\")" - , " (enforce (= messageId (hyperlane-message-id message)) \"invalid calculated messageId\")" - , " (enforce (= signers [" <> (T.intercalate "," $ map (\s -> "\"" <> s <> "\"") signers) <> "]) \"invalid signers\")" - , " (enforce (= (at \"sender\" message) \"AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY\") \"invalid sender\")" - , " (enforce (= (at \"recipient\" message) \"AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU\") \"invalid recipient\")" - , " (bind (hyperlane-decode-token-message (at \"messageBody\" message)) " - , " { \"amount\" := amount, " - , " \"chainId\" := chain-id, " - , " \"recipient\" := recipient-guard }" - , " (enforce (= amount 0.000000000000000123) \"invalid amount\")" - , " (enforce (= (create-principal recipient-guard) \"k:da1a339bd82d2c2e9180626a00dc043275deb3ababb27b5738abf6b9dcee8db6\") \"invalid recipient guard\")" - , " (enforce (= (hyperlane-encode-token-message {\"amount\": 123.0, \"chainId\": chain-id, \"recipient\": \"eyJwcmVkIjogImtleXMtYWxsIiwgImtleXMiOlsiZGExYTMzOWJkODJkMmMyZTkxODA2MjZhMDBkYzA0MzI3NWRlYjNhYmFiYjI3YjU3MzhhYmY2YjlkY2VlOGRiNiJdfQ\"" - , "}) (at \"messageBody\" message)) \"invalid encoded message\")" - , " )" - , ")" - , "(defun x () (with-capability (K " - , "\"" <> hyperlaneMessageId <> "\"" - , " {" - , " \"version\": 3," - , " \"nonce\": 0," - , " \"originDomain\": 31337," - , " \"sender\": \"AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY\"," - , " \"destinationDomain\": 626," - , " \"recipient\": \"AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU\"," - , " \"messageBody\": \"" <> hyperlaneTokenMessageBase64 <> "\"" - , "}" - , " [" <> (T.intercalate "," $ map (\s -> "\"" <> s <> "\"") signers) <> "]" - , " " <> sshow threshold - , ")" - , " \"succeeded\")))" - ]) - (assertTxSuccess - "Should deploy module" - (pString $ "Loaded module free.m, hash " <> moduleHash)) - --- | Calls '(free.m.x)' from 'deployContractWithValidSigner' -mkMerkleMetadataCallWithGas :: GasLimit -> B.ByteString -> [T.Text] -> [T.Text] -> Integer -> MempoolCmdBuilder -mkMerkleMetadataCallWithGas gas merkleProof signatures signersText threshold = buildBasic' - (set cbGasLimit gas . set cbVerifiers - [Verifier - (VerifierName "hyperlane_v3_message") - (ParsedVerifierProof $ - PList $ V.fromList - [ pString hyperlaneMessageBase64 - - , pString $ mkHyperlaneMerkleTreeMetadataBase64 merkleProof $ map decodeHexUnsafe signatures - ] - ) - [cap]]) - (mkExec' "(free.m.x)") - where - messageId = pString hyperlaneMessageId - message = PObject . ObjectMap . M.fromList $ - [ ("version", PLiteral $ LInteger 3) - , ("nonce", PLiteral $ LInteger 0) - , ("originDomain", PLiteral $ LInteger 31337) - , ("destinationDomain", PLiteral $ LInteger 626) - , ("sender", pString "AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY") - , ("recipient", pString "AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU") - , ("messageBody", pString hyperlaneTokenMessageBase64) - ] - - signers = PList $ V.fromList $ map pString signersText - cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [messageId, message, signers, pInteger threshold] - -mkMerkleMetadataCall :: B.ByteString -> [T.Text] -> [T.Text] -> Integer -> MempoolCmdBuilder -mkMerkleMetadataCall = mkMerkleMetadataCallWithGas 20000 - -hyperlaneVerifySuccess :: PactTestM () -hyperlaneVerifySuccess = do - runToHeight 131 - - let threshold = 1 - runBlockTest - [ deployContractWith [validSigner] threshold "QpXqxeT4QKDK98PZaxQCexpxg8bRWDkF4fmB0IhoLR0" - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [validSignature] [validSigner] threshold) - (\cr -> liftIO $ do - assertTxSuccess "should have succeeded" (pString "succeeded") cr - assertEqual "gas should have been charged" 16582 (_crGas cr)) - ] - -hyperlaneVerifyMoreValidatorsSuccess :: PactTestM () -hyperlaneVerifyMoreValidatorsSuccess = do - runToHeight 131 - - let threshold = 1 - let signers = ["wrongSigner", validSigner] - runBlockTest - [ deployContractWith signers threshold "8hkRC85XbTyMUBEbpwLNXP-KH7mVezY51uKnuznGAAo" - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [validSignature] signers threshold) - (\cr -> liftIO $ do - assertTxSuccess "should have succeeded" (pString "succeeded") cr - assertEqual "gas should have been charged" 16583 (_crGas cr)) - ] - -hyperlaneVerifyThresholdZeroError :: PactTestM () -hyperlaneVerifyThresholdZeroError = do - runToHeight 131 - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (messageId:string message signers:[string] threshold:integer)" - , " (enforce-verifier 'hyperlane_v3_message)" - , " (enforce (= signers []) \"invalid signers\")" - , ")" - , "(defun x () (with-capability (K " - , "\"" <> hyperlaneMessageId <> "\"" - , " {" - , " \"version\": 3," - , " \"nonce\": 0," - , " \"originDomain\": 31337," - , " \"sender\": \"AAAAAAAAAAAAAAAAf6k4W-ECrD6sKXSD3WIz1is-FJY\"," - , " \"destinationDomain\": 626," - , " \"recipient\": \"AAAAAAAAAADpgrOqkM0BOY-FQnNzkDXuYlsVcf50GRU\"," - , " \"messageBody\": \"" <> hyperlaneTokenMessageBase64 <> "\"" - , "}" - , " [] 0" - , ")" - , " \"succeeded\")))" - ]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash dSzwI4Kg9RYSrEqzdqQ9-bhsqn3NrefVXvtPQF7RF80")) - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [] [] 0) - (\cr -> liftIO $ do - assertTxFailure "Verification should fail" "Tx verifier error: Threshold should be greater than 0" cr - assertEqual "gas should have been charged" 20000 (_crGas cr)) - ] - -hyperlaneVerifyWrongSignersFailure :: PactTestM () -hyperlaneVerifyWrongSignersFailure = do - runToHeight 131 - - let threshold = 1 - runBlockTest - [ deployContractWith ["wrongSigner"] threshold "LqQ1Y2N2UXxOnWRBaXQ9lNcit5dESFtetuB3oxQyNEg" - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [validSignature] ["wrongSigner"] threshold) - (\cr -> liftIO $ do - assertTxFailure "Verification should fail" "Tx verifier error: Verification failed" cr - assertEqual "gas should have been charged" 20000 (_crGas cr)) - ] - -hyperlaneVerifyNotEnoughRecoveredSignaturesFailure :: PactTestM () -hyperlaneVerifyNotEnoughRecoveredSignaturesFailure = do - runToHeight 131 - - let threshold = 1 - runBlockTest - [ deployContractWith ["wrongSigner"] threshold "LqQ1Y2N2UXxOnWRBaXQ9lNcit5dESFtetuB3oxQyNEg" - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadataCall - hyperlaneMerkleTreeCorrectProof [] ["wrongSigner"] threshold) - (\cr -> liftIO $ do - assertTxFailure "Verification should fail with not enough recovered addresses" "Tx verifier error: The number of signatures can't be less than threshold" cr - assertEqual "gas should have been charged" 20000 (_crGas cr)) - ] - -hyperlaneVerifyNotEnoughCapabilitySignaturesFailure :: PactTestM () -hyperlaneVerifyNotEnoughCapabilitySignaturesFailure = do - runToHeight 131 - - let threshold = 2 - runBlockTest - [ deployContractWith [validSigner] threshold "7z1p9sgWQ_RC-iaNSwQU8I7K1pFL2AOFtI-ZyxRdzkg" - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadataCallWithGas 40000 hyperlaneMerkleTreeCorrectProof [validSignature, validSignature] [validSigner] threshold) - (\cr -> liftIO $ do - assertTxFailure "Verification should fail" "Tx verifier error: Verification failed" cr - assertEqual "gas should have been charged" 40000 (_crGas cr)) - ] - -hyperlaneVerifyMerkleIncorrectProofFailure :: PactTestM () -hyperlaneVerifyMerkleIncorrectProofFailure = do - runToHeight 131 - - let threshold = 1 - runBlockTest - [ deployContractWith [validSigner] threshold "QpXqxeT4QKDK98PZaxQCexpxg8bRWDkF4fmB0IhoLR0" - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadataCall hyperlaneMerkleTreeIncorrectProof [validSignature] [validSigner] threshold) - (\cr -> liftIO $ do - assertTxFailure "Verification should fail" "Tx verifier error: Verification failed" cr - assertEqual "gas should have been charged" 20000 (_crGas cr)) - ] - --- | We pass 2 signatures, 1st one matches to the correct validator, --- but there is no second valid validator for the 2nd signature, and the verification fails. -hyperlaneVerifyFailureNotEnoughSignaturesToPassThreshold :: PactTestM () -hyperlaneVerifyFailureNotEnoughSignaturesToPassThreshold = do - runToHeight 131 - - let threshold = 2 - let signers = ["wrongSigner", validSigner, "wrongSigner"] - runBlockTest - [ deployContractWith [validSigner] threshold "7z1p9sgWQ_RC-iaNSwQU8I7K1pFL2AOFtI-ZyxRdzkg" - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadataCallWithGas 40000 hyperlaneMerkleTreeCorrectProof [validSignature, validSignature] signers threshold) - (\cr -> liftIO $ do - assertTxFailure "Verification should fail" "Tx verifier error: Verification failed" cr - assertEqual "gas should have been charged" 40000 (_crGas cr)) - ] diff --git a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/Before225.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/Before225.hs deleted file mode 100644 index c2d8a1ac63..0000000000 --- a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Message/Before225.hs +++ /dev/null @@ -1,463 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} - -module Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Message.Before225 (tests) where - -import Control.Lens hiding ((.=)) -import Control.Monad.Reader -import qualified Data.ByteString as B -import qualified Data.Text as T -import qualified Data.Vector as V -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Pact.Types.Capability -import Pact.Types.Command -import Pact.Types.Info (noInfo) -import Pact.Types.PactValue -import Pact.Types.Term -import Pact.Types.Verifier hiding (verifierName) - -import Chainweb.Miner.Pact -import Chainweb.Pact.Types -import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Utils -import Chainweb.Utils.Serialization -import Chainweb.VerifierPlugin.Hyperlane.Binary -import Chainweb.VerifierPlugin.Hyperlane.Utils - -import Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils -import Data.IORef -import Chainweb.Version - -tests :: RocksDb -> TestTree -tests rdb = testGroup "Before225" - [ testGroup "MessageId metadata tests" - [ test generousConfig "verifySuccess" hyperlaneVerifyMessageIdSuccess - , test generousConfig "verifyEmptyRecoveredSignaturesSuccess" hyperlaneVerifyMessageIdEmptyRecoveredSignaturesSuccess - , test generousConfig "verifyWrongSignersFailure" hyperlaneVerifyMessageIdWrongSignersFailure - , test generousConfig "verifyNotEnoughRecoveredSignaturesFailure" hyperlaneVerifyMessageIdNotEnoughRecoveredSignaturesFailure - , test generousConfig "verifyNotEnoughCapabilitySignaturesFailure" hyperlaneVerifyMessageIdNotEnoughCapabilitySignaturesFailure - ] - - , testGroup "MerkleTree metadata tests" - [ test generousConfig "verifyNotEnabledFailure" hyperlaneVerifyMerkleNotEnabledFailure - ] - ] - where - -- This is way more than what is used in production, but during testing - -- we can be generous. - generousConfig = testPactServiceConfig { _pactNewBlockGasLimit = 300_000 } - - test pactConfig tname f = - withResourceT (mkTestBlockDb testVersion rdb) $ \bdbIO -> - testCaseSteps tname $ \step -> do - bdb <- bdbIO - let logger = hunitDummyLogger step - mempools <- onAllChains testVersion $ \_ -> do - mempoolRef <- newIORef mempty - return (mempoolRef, delegateMemPoolAccess mempoolRef) - withWebPactExecutionService logger testVersion pactConfig bdb (snd <$> mempools) $ \(pact,_) -> - runReaderT f $ - SingleEnv bdb pact (mempools ^?! atChain cid . _1) noMiner cid - --- hyperlane message tests - --- | Hyperlane test message TokenMessageERC20 encoded in base64: --- --- { "amount": 0.000000000000000123 --- , "chainId": "4" --- , "recipient": { "test-keys" : {"pred": "keys-all", "keys": ["da1a339bd82d2c2e9180626a00dc043275deb3ababb27b5738abf6b9dcee8db6"]} } --- } --- -hyperlaneTokenMessageBase64 :: T.Text -hyperlaneTokenMessageBase64 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHsABHsicHJlZCI6ICJrZXlzLWFsbCIsICJrZXlzIjpbImRhMWEzMzliZDgyZDJjMmU5MTgwNjI2YTAwZGMwNDMyNzVkZWIzYWJhYmIyN2I1NzM4YWJmNmI5ZGNlZThkYjYiXX0" - --- | Hyperlane test message encoded in base64 -hyperlaneMessageBase64 :: T.Text -hyperlaneMessageBase64 = encodeB64UrlNoPaddingText $ runPutS $ putHyperlaneMessage $ - HyperlaneMessage - { hmVersion = 3 - , hmNonce = 0 - , hmOriginDomain = 31337 - , hmSender = padLeft $ decodeHexUnsafe "0x7fa9385be102ac3eac297483dd6233d62b3e1496" - , hmDestinationDomain = 626 - , hmRecipient = padLeft $ "6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV" - , hmMessageBody = either (error . show) id $ decodeB64UrlNoPaddingText hyperlaneTokenMessageBase64 - } - - --- ========================================================= --- --- MessageId metadata tests --- --- ========================================================= - --- | Hyperlane test MessageId Metadata encoded in base64 -mkHyperlaneMessageIdMetadataBase64 :: [B.ByteString] -> T.Text -mkHyperlaneMessageIdMetadataBase64 signatures = encodeB64UrlNoPaddingText $ runPutS $ putMessageIdMultisigIsmMetadata $ - MessageIdMultisigIsmMetadata - { mmimOriginMerkleTreeAddress = decodeHexUnsafe "0x2e234dae75c793f67a35089c9d99245e1c58470b" - , mmimSignedCheckpointRoot = decodeHexUnsafe "0x6d1257af3b899a1ffd71849d9f5534753accbe25f85983aac343807a9184bd10" - , mmimSignedCheckpointIndex = 0 - , mmimSignatures = signatures - } - -hyperlaneVerifyMessageIdSuccess :: PactTestM () -hyperlaneVerifyMessageIdSuccess = do - runToHeight 127 - let verifierName = "hyperlane_v3_message" - - let - recipient = pString "6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV" - signers = PList $ V.fromList [ pString "0x909de0a3579891470db562c178905bf17a0e2cc1" ] - cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [pString hyperlaneTokenMessageBase64, recipient, signers] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (messageBody:string recipient:string signers:[string])" - , " (enforce-verifier '" <> verifierName <> ")" - , " (enforce (= signers [\"0x909de0a3579891470db562c178905bf17a0e2cc1\"]) \"invalid signers\")" - , " (enforce (= recipient \"6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV\") \"invalid recipient\")" - , " (bind (hyperlane-decode-token-message \"" <> hyperlaneTokenMessageBase64 <> "\") " - , " { \"amount\" := amount, " - , " \"chainId\" := chain-id, " - , " \"recipient\" := recipient-guard }" - , " (enforce (= amount 0.000000000000000123) \"invalid amount\")" - , " (enforce (= (create-principal recipient-guard) \"k:da1a339bd82d2c2e9180626a00dc043275deb3ababb27b5738abf6b9dcee8db6\") \"invalid recipient guard\")" - , " )" - , ")" - , "(defun x () (with-capability (K " - , "\"" <> hyperlaneTokenMessageBase64 <> "\"" - , " \"6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV\"" - , " [\"0x909de0a3579891470db562c178905bf17a0e2cc1\"]" - , ")" - , " \"succeeded\")))" - ]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash CbPVttnlGVDHCi2MFqsbkVjNgscz5SwMInZUA5lmJCk")) - , checkVerifierNotInTx verifierName - , PactTxTest - (buildBasic' - (set cbGasLimit 20000 . set cbVerifiers - [Verifier - (VerifierName verifierName) - (ParsedVerifierProof $ - PList $ V.fromList - [ pString hyperlaneMessageBase64 - - -- metadata with one valid signature - , pString $ mkHyperlaneMessageIdMetadataBase64 - [ decodeHexUnsafe "0x60ab9a1a8c880698ad56cc32210ba75f3f73599afca28e85e3935d9c3252c7f353fec4452218367116ae5cb0df978a21b39a4701887651fff1d6058d629521641c"] - ] - ) - [cap]]) - (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - assertTxSuccess "should have succeeded" (pString "succeeded") cr - assertEqual "gas should have been charged" 16524 (_crGas cr)) - ] - - -hyperlaneVerifyMessageIdEmptyRecoveredSignaturesSuccess :: PactTestM () -hyperlaneVerifyMessageIdEmptyRecoveredSignaturesSuccess = do - runToHeight 127 - let verifierName = "hyperlane_v3_message" - - let - recipient = pString "6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV" - signers = PList $ V.fromList [] - cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [pString hyperlaneTokenMessageBase64, recipient, signers] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (messageBody:string recipient:string signers:[string]) (enforce-verifier '" <> verifierName <> "))" - , "(defun x () (with-capability (K" - , " \"" <> hyperlaneTokenMessageBase64 <> "\"" - , " \"6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV\" []) 1)))" - ]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash qimic9pNaQ9A-hdJEQz8XQx91sPXlrGrn22Jg7GrkGg")) - , checkVerifierNotInTx verifierName - , PactTxTest - (buildBasic' - (set cbGasLimit 20000 . set cbVerifiers - [Verifier - (VerifierName verifierName) - (ParsedVerifierProof $ - PList $ V.fromList - [ pString hyperlaneMessageBase64 - - -- metadata without signatures - , pString $ mkHyperlaneMessageIdMetadataBase64 [] - ] - ) - [cap]]) - (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - assertTxSuccess "should have succeeded" (pDecimal 1) cr - assertEqual "gas should have been charged" 249 (_crGas cr)) - ] - - -hyperlaneVerifyMessageIdWrongSignersFailure :: PactTestM () -hyperlaneVerifyMessageIdWrongSignersFailure = do - runToHeight 127 - let verifierName = "hyperlane_v3_message" - - let - recipient = pString "6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV" - -- incorrect validator - signers = PList $ V.fromList [ pString "wrongValidator" ] - cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [pString hyperlaneTokenMessageBase64, recipient, signers] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (messageBody:string recipient:string signers:[string]) (enforce-verifier '" <> verifierName <> "))" - , "(defun x () (with-capability (K" - , " \"" <> hyperlaneTokenMessageBase64 <> "\"" - , " \"6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV\" [\"wrongValidator\"]) 1)))" - ]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash 8-eX_a7n5D_ETZ7FAW6p_WGTHjDK4DC_RD_gbQl7AVY")) - , checkVerifierNotInTx verifierName - , PactTxTest - (buildBasic' - (set cbGasLimit 20000 . set cbVerifiers - [Verifier - (VerifierName verifierName) - (ParsedVerifierProof $ - PList $ V.fromList - [ pString hyperlaneMessageBase64 - - -- metadata with one valid signature - , pString $ mkHyperlaneMessageIdMetadataBase64 - [ decodeHexUnsafe "0x60ab9a1a8c880698ad56cc32210ba75f3f73599afca28e85e3935d9c3252c7f353fec4452218367116ae5cb0df978a21b39a4701887651fff1d6058d629521641c"] - ] - ) - [cap]]) - (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - let errMsg = "Tx verifier error: Signers don't match. Expected: PList [PLiteral (LString {_lString = \"0x909de0a3579891470db562c178905bf17a0e2cc1\"})] but got PList [PLiteral (LString {_lString = \"wrongValidator\"})]" - assertTxFailure "should have failed with signers don't match" errMsg cr - assertEqual "gas should have been charged" 20000 (_crGas cr)) - ] - -hyperlaneVerifyMessageIdNotEnoughRecoveredSignaturesFailure :: PactTestM () -hyperlaneVerifyMessageIdNotEnoughRecoveredSignaturesFailure = do - runToHeight 127 - let verifierName = "hyperlane_v3_message" - - let - recipient = pString "6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV" - signers = PList $ V.fromList [ pString "wrongValidator" ] - cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [pString hyperlaneTokenMessageBase64, recipient, signers] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (messageBody:string recipient:string signers:[string]) (enforce-verifier '" <> verifierName <> "))" - , "(defun x () (with-capability (K" - , " \"" <> hyperlaneTokenMessageBase64 <> "\"" - , " \"6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV\" [\"wrongValidator\"]) 1)))" - ]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash 8-eX_a7n5D_ETZ7FAW6p_WGTHjDK4DC_RD_gbQl7AVY")) - , checkVerifierNotInTx verifierName - , PactTxTest - (buildBasic' - (set cbGasLimit 20000 . set cbVerifiers - [Verifier - (VerifierName verifierName) - (ParsedVerifierProof $ - PList $ V.fromList - [ pString hyperlaneMessageBase64 - - -- metadata without signatures - , pString $ mkHyperlaneMessageIdMetadataBase64 [] - ] - ) - [cap]]) - (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - let errMsg = "Tx verifier error: Signers don't match. Expected: PList [] but got PList [PLiteral (LString {_lString = \"wrongValidator\"})]" - assertTxFailure "should have failed with signers don't match" errMsg cr - assertEqual "gas should have been charged" 20000 (_crGas cr)) - ] - -hyperlaneVerifyMessageIdNotEnoughCapabilitySignaturesFailure :: PactTestM () -hyperlaneVerifyMessageIdNotEnoughCapabilitySignaturesFailure = do - runToHeight 127 - let verifierName = "hyperlane_v3_message" - - let - recipient = pString "6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV" - signers = PList $ V.fromList [ pString "0x909de0a3579891470db562c178905bf17a0e2cc1" ] - cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [pString hyperlaneTokenMessageBase64, recipient, signers] - - runBlockTest - [ PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (messageBody:string recipient:string signers:[string]) (enforce-verifier '" <> verifierName <> "))" - , "(defun x () (with-capability (K" - , " \"" <> hyperlaneTokenMessageBase64 <> "\"" - , " \"6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV\" [\"0x909de0a3579891470db562c178905bf17a0e2cc1\"]) 1)))" - ]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash fpbofdp-l4YpUMhAcJGrGzu1H-l1Y1mA6JTjJi41ADk")) - , checkVerifierNotInTx verifierName - , PactTxTest - (buildBasic' - (set cbGasLimit 40000 . set cbVerifiers - [Verifier - (VerifierName verifierName) - (ParsedVerifierProof $ - PList $ V.fromList - [ pString hyperlaneMessageBase64 - - -- metadata with one valid signature repeated twice - , pString $ mkHyperlaneMessageIdMetadataBase64 - [ decodeHexUnsafe "0x60ab9a1a8c880698ad56cc32210ba75f3f73599afca28e85e3935d9c3252c7f353fec4452218367116ae5cb0df978a21b39a4701887651fff1d6058d629521641c" - , decodeHexUnsafe "0x60ab9a1a8c880698ad56cc32210ba75f3f73599afca28e85e3935d9c3252c7f353fec4452218367116ae5cb0df978a21b39a4701887651fff1d6058d629521641c" - ] - ] - ) - [cap]]) - (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - let errMsg = "Tx verifier error: Signers don't match. Expected: PList [PLiteral (LString {_lString = \"0x909de0a3579891470db562c178905bf17a0e2cc1\"}),PLiteral (LString {_lString = \"0x909de0a3579891470db562c178905bf17a0e2cc1\"})] but got PList [PLiteral (LString {_lString = \"0x909de0a3579891470db562c178905bf17a0e2cc1\"})]" - assertTxFailure "should have failed with signers don't match" errMsg cr - assertEqual "gas should have been charged" 40000 (_crGas cr)) - ] - --- ========================================================= --- --- MerkleTree metadata tests --- --- ========================================================= - --- | Hyperlane test MerkleTree Metadata encoded in base64 --- Test data was generated using the following test in the hyperlane codebase --- https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/b14f997810ebd7dbdff2ac6622a149ae77010ae3/solidity/test/isms/MultisigIsm.t.sol#L35 -mkHyperlaneMerkleTreeMetadataBase64 :: B.ByteString -> [B.ByteString] -> T.Text -mkHyperlaneMerkleTreeMetadataBase64 proof signatures = encodeB64UrlNoPaddingText $ runPutS $ putMerkleRootMultisigIsmMetadata $ - MerkleRootMultisigIsmMetadata - { mrmimOriginMerkleTreeAddress = decodeHexUnsafe "0x2e234dae75c793f67a35089c9d99245e1c58470b" - , mrmimMessageIdIndex = 0 - , mrmimSignedCheckpointMessageId = decodeHexUnsafe "0x6f370c453c86ad681e936741683cceca8f13c46f2a49b1c9f8c6a23b5bb97aae" - , mrmimMerkleProof = proof - , mrmimSignedCheckpointIndex = 0 - , mrmimSignatures = signatures - } - -hyperlaneMerkleTreeCorrectProof :: B.ByteString -hyperlaneMerkleTreeCorrectProof = decodeHexUnsafe "0x0000000000000000000000000000000000000000000000000000000000000000ad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5b4c11951957c6f8f642c4af61cd6b24640fec6dc7fc607ee8206a99e92410d3021ddb9a356815c3fac1026b6dec5df3124afbadb485c9ba5a3e3398a04b7ba85e58769b32a1beaf1ea27375a44095a0d1fb664ce2dd358e7fcbfb78c26a193440eb01ebfc9ed27500cd4dfc979272d1f0913cc9f66540d7e8005811109e1cf2d887c22bd8750d34016ac3c66b5ff102dacdd73f6b014e710b51e8022af9a1968ffd70157e48063fc33c97a050f7f640233bf646cc98d9524c6b92bcf3ab56f839867cc5f7f196b93bae1e27e6320742445d290f2263827498b54fec539f756afcefad4e508c098b9a7e1d8feb19955fb02ba9675585078710969d3440f5054e0f9dc3e7fe016e050eff260334f18a5d4fe391d82092319f5964f2e2eb7c1c3a5f8b13a49e282f609c317a833fb8d976d11517c571d1221a265d25af778ecf8923490c6ceeb450aecdc82e28293031d10c7d73bf85e57bf041a97360aa2c5d99cc1df82d9c4b87413eae2ef048f94b4d3554cea73d92b0f7af96e0271c691e2bb5c67add7c6caf302256adedf7ab114da0acfe870d449a3a489f781d659e8beccda7bce9f4e8618b6bd2f4132ce798cdc7a60e7e1460a7299e3c6342a579626d22733e50f526ec2fa19a22b31e8ed50f23cd1fdf94c9154ed3a7609a2f1ff981fe1d3b5c807b281e4683cc6d6315cf95b9ade8641defcb32372f1c126e398ef7a5a2dce0a8a7f68bb74560f8f71837c2c2ebbcbf7fffb42ae1896f13f7c7479a0b46a28b6f55540f89444f63de0378e3d121be09e06cc9ded1c20e65876d36aa0c65e9645644786b620e2dd2ad648ddfcbf4a7e5b1a3a4ecfe7f64667a3f0b7e2f4418588ed35a2458cffeb39b93d26f18d2ab13bdce6aee58e7b99359ec2dfd95a9c16dc00d6ef18b7933a6f8dc65ccb55667138776f7dea101070dc8796e3774df84f40ae0c8229d0d6069e5c8f39a7c299677a09d367fc7b05e3bc380ee652cdc72595f74c7b1043d0e1ffbab734648c838dfb0527d971b602bc216c9619ef0abf5ac974a1ed57f4050aa510dd9c74f508277b39d7973bb2dfccc5eeb0618db8cd74046ff337f0a7bf2c8e03e10f642c1886798d71806ab1e888d9e5ee87d0838c5655cb21c6cb83313b5a631175dff4963772cce9108188b34ac87c81c41e662ee4dd2dd7b2bc707961b1e646c4047669dcb6584f0d8d770daf5d7e7deb2e388ab20e2573d171a88108e79d820e98f26c0b84aa8b2f4aa4968dbb818ea32293237c50ba75ee485f4c22adf2f741400bdf8d6a9cc7df7ecae576221665d7358448818bb4ae4562849e949e17ac16e0be16688e156b5cf15e098c627c0056a9" - -deployContractForMerkleTests :: PactTxTest -deployContractForMerkleTests = - PactTxTest - (buildBasicGas 70000 - $ mkExec' $ mconcat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defcap K (messageBody:string recipient:string signers:[string])" - , " (enforce-verifier 'hyperlane_v3_message)" - , " (enforce (= signers [\"0x4bd34992e0994e9d3c53c1ccfe5c2e38d907338e\"]) \"invalid signers\")" - , " (enforce (= recipient \"6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV\") \"invalid recipient\")" - , " (bind (hyperlane-decode-token-message \"" <> hyperlaneTokenMessageBase64 <> "\") " - , " { \"amount\" := amount, " - , " \"chainId\" := chain-id, " - , " \"recipient\" := recipient-guard }" - , " (enforce (= amount 0.000000000000000123) \"invalid amount\")" - , " (enforce (= (create-principal recipient-guard) \"k:da1a339bd82d2c2e9180626a00dc043275deb3ababb27b5738abf6b9dcee8db6\") \"invalid recipient guard\")" - , " )" - , ")" - , "(defun x () (with-capability (K " - , "\"" <> hyperlaneTokenMessageBase64 <> "\"" - , " \"6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV\"" - , " [\"0x4bd34992e0994e9d3c53c1ccfe5c2e38d907338e\"]" - , ")" - , " \"succeeded\")))" - ]) - (assertTxSuccess - "Should deploy module" - (pString "Loaded module free.m, hash kU7y3IbldFn1zeYRrrkT0BvLwjtBltyMsMR_E_SL5wU")) - --- | Calls '(free.m.x)' from 'deployContractForMerkleTests' -mkMerkleMetadatWithOneSignatureCall :: B.ByteString -> MempoolCmdBuilder -mkMerkleMetadatWithOneSignatureCall merkleProof = buildBasic' - (set cbGasLimit 20000 . set cbVerifiers - [Verifier - (VerifierName "hyperlane_v3_message") - (ParsedVerifierProof $ - PList $ V.fromList - [ pString hyperlaneMessageBase64 - - , pString $ mkHyperlaneMerkleTreeMetadataBase64 merkleProof - [ decodeHexUnsafe "0xb3df841e9e3036f0858b0376280ef6692be293b53da3fba3384c82d6ca86704619cd7700147e3439fb66a985bdb310a4273204a0b5e5337deec0f00dfc8a5a171c" ] - ] - ) - [cap]]) - (mkExec' "(free.m.x)") - where - recipient = pString "6YKzqpDNATmPhUJzc5A17mJbFXH-dBkV" - signers = PList $ V.fromList [ pString "0x4bd34992e0994e9d3c53c1ccfe5c2e38d907338e" ] - cap = SigCapability (QualifiedName (ModuleName "m" (Just (NamespaceName "free"))) "K" noInfo) - [pString hyperlaneTokenMessageBase64, recipient, signers] - -hyperlaneVerifyMerkleNotEnabledFailure :: PactTestM () -hyperlaneVerifyMerkleNotEnabledFailure = do - runToHeight 127 - - runBlockTest - [ deployContractForMerkleTests - , checkVerifierNotInTx "hyperlane_v3_message" - , PactTxTest (mkMerkleMetadatWithOneSignatureCall hyperlaneMerkleTreeCorrectProof) - (\cr -> liftIO $ do - assertTxFailure "should have failed with uncaught exception" "Tx verifier error: Uncaught exception in verifier" cr - assertEqual "gas should have been charged" 20000 (_crGas cr)) - ] diff --git a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs deleted file mode 100644 index 0b01b8e653..0000000000 --- a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs +++ /dev/null @@ -1,248 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} - -module Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils where - -import Control.Concurrent.MVar -import Control.Lens hiding ((.=)) -import Control.Monad -import Control.Monad.Reader -import qualified Data.HashMap.Strict as HM -import Data.IORef -import qualified Data.Text as T -import qualified Data.Vector as V -import Test.Tasty.HUnit - --- internal modules - -import Pact.Types.Command -import Pact.Types.Hash -import Pact.Types.PactError -import Pact.Types.PactValue -import Pact.Types.Pretty -import Pact.Types.RPC -import Pact.Types.Term - -import Chainweb.BlockCreationTime -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Cut -import Chainweb.Mempool.Mempool -import Chainweb.Miner.Pact - -import Chainweb.Payload -import Chainweb.Test.Cut -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import Chainweb.Utils -import Chainweb.Version -import Chainweb.WebPactExecutionService -import Chainweb.Payload.PayloadStore (lookupPayloadWithHeight) -import Chainweb.Pact.Types (MemPoolAccess, mpaGetBlock) - -testVersion :: ChainwebVersion -testVersion = slowForkingCpmTestVersion peterson - -cid :: ChainId -cid = unsafeChainId 9 - -- several tests in this file expect chain 9 - -data SingleEnv = SingleEnv - { _menvBdb :: !TestBlockDb - , _menvPact :: !WebPactExecutionService - , _menvMpa :: !(IORef MemPoolAccess) - , _menvMiner :: !Miner - , _menvChainId :: !ChainId - } - -makeLenses ''SingleEnv - -type PactTestM = ReaderT SingleEnv IO - -newtype MempoolCmdBuilder = MempoolCmdBuilder - { _mempoolCmdBuilder :: ChainId -> BlockCreationTime -> CmdBuilder - } - --- | Block filler. A 'Nothing' result means "skip this filler". -newtype MempoolBlock = MempoolBlock - { _mempoolBlock :: ChainId -> BlockCreationTime -> Maybe [MempoolCmdBuilder] - } - --- | Mempool with an ordered list of fillers. -newtype PactMempool = PactMempool - { _pactMempool :: [MempoolBlock] - } - deriving (Semigroup,Monoid) - - --- | Pair a builder with a test -data PactTxTest = PactTxTest - { _pttBuilder :: MempoolCmdBuilder - , _pttTest :: CommandResult Hash -> Assertion - } - -checkVerifierNotInTx :: T.Text -> PactTxTest -checkVerifierNotInTx v = PactTxTest - (buildBasic (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - assertTxFailure - "verifier not present" - ("Verifier failure " <> pretty v <> ": not in transaction") - cr - assertTxGas "verifier errors charge all gas" 10000 cr) - --- | Sets mempool with block fillers. A matched filler --- (returning a 'Just' result) is executed and removed from the list. --- Fillers are tested in order. -setPactMempool :: PactMempool -> PactTestM () -setPactMempool (PactMempool fs) = do - mpa <- view menvMpa - mpsRef <- liftIO $ newIORef fs - liftIO $ writeIORef mpa $ mempty { - mpaGetBlock = \_ -> go mpsRef - } - where - go ref mempoolPreBlockCheck bHeight bHash bct = do - mps <- readIORef ref - let runMps i = \case - [] -> return mempty - (mp:r) -> case _mempoolBlock mp cid bct of - Just bs -> do - writeIORef ref (take i mps ++ r) - cmds <- fmap V.fromList $ forM bs $ \b -> - buildCwCmd (sshow bct) testVersion $ _mempoolCmdBuilder b cid bct - tos <- mempoolPreBlockCheck bHeight bHash ((fmap . fmap . fmap) _pcCode cmds) - return $ V.fromList - [ t - | Right t <- V.toList tos - ] - -- return $ fmap fst $ V.filter snd (V.zip cmds validationResults) - Nothing -> runMps (succ i) r - runMps 0 mps - -filterBlock :: (ChainId -> BlockCreationTime -> Bool) -> MempoolBlock -> MempoolBlock -filterBlock f (MempoolBlock b) = MempoolBlock $ \chain bct -> - if f chain bct then b chain bct else Nothing - -blockForChain :: ChainId -> MempoolBlock -> MempoolBlock -blockForChain chid = filterBlock $ \chain _ -> - chain == chid - -runCut' :: PactTestM () -runCut' = do - pact <- view menvPact - bdb <- view menvBdb - miner <- view menvMiner - liftIO $ runCut testVersion bdb pact (offsetBlockTime second) zeroNoncer miner - -assertTxGas :: (HasCallStack, MonadIO m) => String -> Gas -> CommandResult Hash -> m () -assertTxGas msg g = liftIO . assertEqual msg g . _crGas - -assertTxSuccess - :: HasCallStack - => MonadIO m - => String - -> PactValue - -> CommandResult Hash - -> m () -assertTxSuccess msg r tx = do - liftIO $ assertEqual msg (Just r) - (tx ^? crResult . to _pactResult . _Right) - --- | Exact match on error doc -assertTxFailure :: (HasCallStack, MonadIO m) => String -> Doc -> CommandResult Hash -> m () -assertTxFailure msg d tx = - liftIO $ assertEqual msg (Just d) - (tx ^? crResult . to _pactResult . _Left . to peDoc) - --- | Run a single mempool block on current chain with tests for each tx. --- Limitations: can only run a single-chain, single-refill test for --- a given cut height. -runBlockTest :: HasCallStack => [PactTxTest] -> PactTestM () -runBlockTest pts = do - chid <- view menvChainId - setPactMempool $ PactMempool [testsToBlock chid pts] - runCut' - runBlockTests pts - --- | Convert tests to block for specified chain. -testsToBlock :: ChainId -> [PactTxTest] -> MempoolBlock -testsToBlock chid pts = blockForChain chid $ MempoolBlock $ \_ _ -> - pure $ map _pttBuilder pts - --- | Run tests on current cut and chain. -runBlockTests :: HasCallStack => [PactTxTest] -> PactTestM () -runBlockTests pts = do - rs <- txResults - liftIO $ assertEqual "Result length should equal transaction length" (length pts) (length rs) - zipWithM_ go pts (V.toList rs) - where - go :: PactTxTest -> CommandResult Hash -> PactTestM () - go (PactTxTest _ t) cr = liftIO $ t cr - --- | Run cuts to block height. -runToHeight :: BlockHeight -> PactTestM () -runToHeight bhi = do - chid <- view menvChainId - bh <- getHeader chid - when (view blockHeight bh < bhi) $ do - runCut' - runToHeight bhi - -signSender00 :: CmdBuilder -> CmdBuilder -signSender00 = set cbSigners [mkEd25519Signer' sender00 []] - -setFromHeader :: BlockHeader -> CmdBuilder -> CmdBuilder -setFromHeader bh = - set cbChainId (_chainId bh) - . set cbCreationTime (toTxCreationTime $ _bct $ view blockCreationTime bh) - -buildBasic - :: PactRPC T.Text - -> MempoolCmdBuilder -buildBasic = buildBasic' id - -buildBasicGas :: GasLimit -> PactRPC T.Text -> MempoolCmdBuilder -buildBasicGas g = buildBasic' (set cbGasLimit g) - --- | Build with specified setter to mutate defaults. -buildBasic' - :: (CmdBuilder -> CmdBuilder) - -> PactRPC T.Text - -> MempoolCmdBuilder -buildBasic' f r = MempoolCmdBuilder $ \chain bct -> - f $ signSender00 - $ set cbChainId chain - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC r - $ defaultCmd - --- | Get output on latest cut for chain -getPWO :: ChainId -> PactTestM (PayloadWithOutputs,BlockHeader) -getPWO chid = do - (TestBlockDb _ pdb _) <- view menvBdb - h <- getHeader chid - Just pwo <- liftIO $ lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) - return (pwo,h) - -getHeader :: ChainId -> PactTestM BlockHeader -getHeader chid = do - (TestBlockDb _ _ cmv) <- view menvBdb - c <- liftIO $ readMVar cmv - fromMaybeM (userError $ "chain lookup failed for " ++ show chid) $ HM.lookup chid (_cutMap c) - -txResults :: HasCallStack => PactTestM (V.Vector (CommandResult Hash)) -txResults = do - chid <- view menvChainId - (o,_h) <- getPWO chid - forM (_payloadWithOutputsTransactions o) $ \(_,txo) -> - decodeStrictOrThrow @_ @(CommandResult Hash) (_transactionOutputBytes txo) diff --git a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs deleted file mode 100644 index 29ed9eeea2..0000000000 --- a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs +++ /dev/null @@ -1,56 +0,0 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeFamilies #-} - -{-# OPTIONS_GHC -fno-warn-orphans #-} - -module Chainweb.Test.Pact4.VerifierPluginTest.Unit -( -- * test suite - tests -) where - -import Data.DoubleWord (Word256) - -import Test.Tasty -import Test.Tasty.HUnit -import Test.Tasty.QuickCheck - -import Test.QuickCheck.Instances () - -import Chainweb.Test.Utils (prop_iso) - --- internal chainweb modules - -import Chainweb.VerifierPlugin.Hyperlane.Utils - -instance Arbitrary Word256 where - arbitrary = fromInteger . getNonNegative <$> arbitrary - -tests :: TestTree -tests = testGroup "Chainweb.Test.Pact4.VerifierPluginTest.Unit" - [ testCase "decimalToWord" hyperlaneDecimalToWord - , testCase "decimalToWord2" hyperlaneDecimalToWord2 - , testCase "wordToDecimal" hyperlaneWordToDecimal - , testCase "wordToDecimal2" hyperlaneWordToDecimal2 - , testProperty "wordDecimalRoundTrip" $ prop_iso @Word256 @_ decimalToWord wordToDecimal - ] - -hyperlaneDecimalToWord :: Assertion -hyperlaneDecimalToWord = assertEqual "" 10000000000000000000000000000000000000 (decimalToWord 10000000000000000000) - -hyperlaneDecimalToWord2 :: Assertion -hyperlaneDecimalToWord2 = assertEqual "" 10333333333333333000 (decimalToWord 10.333333333333333) - -hyperlaneWordToDecimal :: Assertion -hyperlaneWordToDecimal = assertEqual "" 3333333333333333333.333333333333333333 (wordToDecimal 3333333333333333333333333333333333333) - -hyperlaneWordToDecimal2 :: Assertion -hyperlaneWordToDecimal2 = assertEqual "" 10.333333333333333 (wordToDecimal 10333333333333333000) diff --git a/test/lib/Chainweb/Test/Pact5/Utils.hs b/test/lib/Chainweb/Test/Pact5/Utils.hs deleted file mode 100644 index 23f0d6d1e9..0000000000 --- a/test/lib/Chainweb/Test/Pact5/Utils.hs +++ /dev/null @@ -1,231 +0,0 @@ -{-# language - FlexibleContexts - , ImportQualifiedPost - , LambdaCase - , NumericUnderscores - , OverloadedStrings - , PackageImports - , TypeApplications -#-} - -module Chainweb.Test.Pact5.Utils - ( initCheckpointer - , pactTxFrom4To5 - - -- * Logging - , getTestLogLevel - , testLogFn - , getTestLogger - - -- * Mempool - , mempoolInsertPact5 - , mempoolLookupPact5 - - -- * Resources - , withTempSQLiteResource - , withInMemSQLiteResource - , withPactQueue - , withMempool - , withRunPactService - , withBlockDbs - - -- * Properties - , event - , successfulTx - -- * Utilities - , coinModuleName - ) - where - -import Chainweb.Chainweb (validatingMempoolConfig) -import "pact" Pact.Types.Command qualified as Pact4 -import "pact" Pact.Types.Hash qualified as Pact4 -import Chainweb.ChainId -import Chainweb.Logger -import Chainweb.Mempool.Consensus -import Chainweb.Mempool.InMem -import Chainweb.Mempool.Mempool (InsertType (..), LookupResult(..), MempoolBackend (..), TransactionHash(..)) ---import Chainweb.Pact.Backend.RelationalCheckpointer -import Chainweb.Pact.PactService.Checkpointer.Internal (initCheckpointerResources) -import Chainweb.Pact.Backend.Types (Checkpointer, IntraBlockPersistence(..), SQLiteEnv) -import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) -import Chainweb.Pact.PactService -import Chainweb.Pact.PactService.Pact4.ExecBlock () -import Chainweb.Pact.Service.PactInProcApi -import Chainweb.Pact.Service.PactQueue -import Chainweb.Pact.Types -import Chainweb.Pact4.Transaction qualified as Pact4 -import Chainweb.Pact5.Transaction qualified as Pact5 -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB -import Chainweb.Storage.Table.RocksDB -import Chainweb.Utils -import Chainweb.Version -import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService -import Control.Concurrent hiding (throwTo) -import Control.Exception (AsyncException (..), throwTo) -import Control.Lens hiding (elements, only) -import Control.Monad -import Control.Monad.IO.Class -import Control.Monad.Trans.Resource (ResourceT, allocate) -import Control.Monad.Trans.Resource qualified as Resource -import Data.Aeson qualified as Aeson -import Data.ByteString.Short qualified as SBS -import Data.HashSet (HashSet) -import Data.HashSet qualified as HashSet -import Data.Maybe (fromMaybe) -import Data.Text (Text) -import Data.Text qualified as Text -import Data.Text.Encoding qualified as Text -import Data.Text.IO qualified as Text -import Data.Vector (Vector) -import Data.Vector qualified as Vector -import Pact.Core.Command.Types qualified as Pact5 -import Pact.Core.Hash qualified as Pact5 -import Pact.Core.Pretty qualified as Pact5 -import Pact.JSON.Encode qualified as J -import Pact.Types.Gas qualified as Pact4 -import System.Environment (lookupEnv) -import System.LogLevel -import PropertyMatchers ((?)) -import PropertyMatchers qualified as P -import Pact.Core.PactValue -import Pact.Core.Capabilities -import Pact.Core.Names - -withBlockDbs :: ChainwebVersion -> RocksDb -> ResourceT IO (PayloadDb RocksDbTable, WebBlockHeaderDb) -withBlockDbs v rdb = do - webBHDb <- liftIO $ initWebBlockHeaderDb rdb v - let payloadDb = newPayloadDb rdb - liftIO $ initializePayloadDb v payloadDb - return (payloadDb, webBHDb) - --- | Internal. See https://www.sqlite.org/c3ref/open.html -withSQLiteResource - :: String - -> ResourceT IO SQLiteEnv -withSQLiteResource file = snd <$> allocate - (openSQLiteConnection file chainwebPragmas) - closeSQLiteConnection - --- | Open a temporary file-backed SQLite database. -withTempSQLiteResource :: ResourceT IO SQLiteEnv -withTempSQLiteResource = withSQLiteResource "" - --- | Open a temporary in-memory SQLite database. -withInMemSQLiteResource :: ResourceT IO SQLiteEnv -withInMemSQLiteResource = withSQLiteResource ":memory:" - -withPactQueue :: ResourceT IO PactQueue -withPactQueue = do - liftIO (newPactQueue 2_000) - -withMempool :: () - => ChainwebVersion - -> ChainId - -> PactQueue - -> ResourceT IO (MempoolBackend Pact4.UnparsedTransaction) -withMempool v cid pactQueue = do - pactExecutionServiceVar <- liftIO $ newMVar (mkPactExecutionService pactQueue) - let mempoolCfg = validatingMempoolConfig cid v (Pact4.GasLimit 150_000) (Pact4.GasPrice 1e-8) pactExecutionServiceVar - liftIO $ startInMemoryMempoolTest mempoolCfg - -withRunPactService :: (Logger logger) - => logger - -> ChainwebVersion - -> ChainId - -> PactQueue - -> MempoolBackend Pact4.UnparsedTransaction - -> WebBlockHeaderDb - -> PayloadDb RocksDbTable - -> PactServiceConfig - -> ResourceT IO () -withRunPactService logger v cid pactQueue mempool webBHDb payloadDb pactServiceConfig = do - sqlite <- withTempSQLiteResource - blockHeaderDb <- liftIO $ getWebBlockHeaderDb webBHDb cid - mempoolConsensus <- liftIO $ mkMempoolConsensus mempool blockHeaderDb (Just payloadDb) - let mempoolAccess = pactMemPoolAccess mempoolConsensus logger - - void $ Resource.allocate - (forkIO $ runPactService v cid logger Nothing pactQueue mempoolAccess blockHeaderDb payloadDb sqlite pactServiceConfig) --bhdb (_bdbPayloadDb tdb) sqlite pactServiceConfig) - (\tid -> throwTo tid ThreadKilled) - --- | Insert a 'Pact5.Transaction' into the mempool. The mempool currently operates by default on --- 'Pact4.UnparsedTransaction's, so the txs have to be converted. -mempoolInsertPact5 :: MempoolBackend Pact4.UnparsedTransaction -> InsertType -> [Pact5.Transaction] -> IO () -mempoolInsertPact5 mp insertType txs = do - let unparsedTxs :: [Pact4.UnparsedTransaction] - unparsedTxs = flip map txs $ \tx -> - case codecDecode Pact4.rawCommandCodec (codecEncode Pact5.payloadCodec tx) of - Left err -> error err - Right a -> a - mempoolInsert mp insertType $ Vector.fromList unparsedTxs - --- | Looks up transactions in the mempool. Returns a set which indicates pending membership of the mempool. -mempoolLookupPact5 :: MempoolBackend Pact4.UnparsedTransaction -> Vector Pact5.Hash -> IO (HashSet Pact5.Hash) -mempoolLookupPact5 mp hashes = do - results <- mempoolLookup mp $ Vector.map (TransactionHash . Pact5.unHash) hashes - return $ HashSet.fromList $ Vector.toList $ flip Vector.mapMaybe results $ \case - Missing -> Nothing - Pending tx -> Just $ Pact5.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash tx - --- | Initializes a checkpointer for a given chain. -initCheckpointer :: ChainwebVersion -> ChainId -> SQLiteEnv -> IO (Checkpointer GenericLogger) -initCheckpointer v cid sql = do - logLevel <- getTestLogLevel - initCheckpointerResources defaultModuleCacheLimit sql DoNotPersistIntraBlockWrites (genericLogger logLevel (testLogFn logLevel)) v cid - -pactTxFrom4To5 :: Pact4.Transaction -> Pact5.Transaction -pactTxFrom4To5 tx = - let - e = do - let json = J.encode (fmap (Text.decodeUtf8 . SBS.fromShort . Pact4.payloadBytes) tx) - cmdWithPayload <- Aeson.eitherDecode @(Pact5.Command Text) json - over _Left Pact5.renderCompactString $ Pact5.parseCommand cmdWithPayload - in - case e of - Left err -> error err - Right cmds -> cmds - -getTestLogLevel :: IO LogLevel -getTestLogLevel = do - let parseLogLevel txt = case Text.toUpper txt of - "DEBUG" -> Debug - "INFO" -> Info - "WARN" -> Warn - "ERROR" -> Error - _ -> Error - fromMaybe Error . fmap (parseLogLevel . Text.pack) <$> lookupEnv "CHAINWEB_TEST_LOG_LEVEL" - --- | Generally, we want tests to throw an exception on an Error log, but we don't want --- to throw an exception on any other level of log. -testLogFn :: LogLevel -> Text -> IO () -testLogFn ll msg = case ll of - Error -> do - error (Text.unpack msg) - _ -> do - Text.putStrLn msg - -getTestLogger :: IO GenericLogger -getTestLogger = do - logLevel <- getTestLogLevel - return $ genericLogger logLevel (testLogFn logLevel) - --- usually we don't want to check the module hash -event - :: P.Prop Text - -> P.Prop [PactValue] - -> P.Prop ModuleName - -> P.Prop (PactEvent PactValue) -event n args modName = P.checkAll - [ P.fun _peName n - , P.fun _peArgs args - , P.fun _peModule modName - ] - -coinModuleName :: ModuleName -coinModuleName = ModuleName "coin" Nothing - -successfulTx :: P.Prop (Pact5.CommandResult log err) -successfulTx = P.fun Pact5._crResult ? P.match Pact5._PactResultOk P.succeed diff --git a/test/lib/Chainweb/Test/RestAPI/Client_.hs b/test/lib/Chainweb/Test/RestAPI/Client_.hs index 9260da9d2c..34f49a2739 100644 --- a/test/lib/Chainweb/Test/RestAPI/Client_.hs +++ b/test/lib/Chainweb/Test/RestAPI/Client_.hs @@ -33,10 +33,12 @@ module Chainweb.Test.RestAPI.Client_ , headerClient' , hashesClient' , headersClient' -, blocksClient' +-- TODO: PP +-- , blocksClient' , branchHashesClient' , branchHeadersClient' -, branchBlocksClient' +-- TODO: PP +-- , branchBlocksClient' ) where import Data.Functor.Identity @@ -45,7 +47,7 @@ import Servant.API.ContentTypes -- internal modules -import Chainweb.Block +-- import Chainweb.Block import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeaderDB @@ -142,18 +144,19 @@ headersClient' v c = runIdentity $ do (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(HeadersApi v c) -blocksClient' - :: ChainwebVersion - -> ChainId - -> Maybe Limit - -> Maybe (NextItem BlockHash) - -> Maybe MinRank - -> Maybe MaxRank - -> ClientM_ BlockPage -blocksClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) - (SomeSing (SChainId :: Sing c)) <- return $ toSing c - return $ client_ @(BlocksApi v c) +-- TODO: PP +-- blocksClient' +-- :: ChainwebVersion +-- -> ChainId +-- -> Maybe Limit +-- -> Maybe (NextItem BlockHash) +-- -> Maybe MinRank +-- -> Maybe MaxRank +-- -> ClientM_ BlockPage +-- blocksClient' v c = runIdentity $ do +-- (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +-- (SomeSing (SChainId :: Sing c)) <- return $ toSing c +-- return $ client_ @(BlocksApi v c) branchHashesClient' :: ChainwebVersion @@ -183,16 +186,17 @@ branchHeadersClient' v c = runIdentity $ do (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(BranchHeadersApi v c) -branchBlocksClient' - :: ChainwebVersion - -> ChainId - -> Maybe Limit - -> Maybe (NextItem BlockHash) - -> Maybe MinRank - -> Maybe MaxRank - -> BranchBounds BlockHeaderDb - -> ClientM_ (Page (NextItem BlockHash) Block) -branchBlocksClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) - (SomeSing (SChainId :: Sing c)) <- return $ toSing c - return $ client_ @(BranchBlocksApi v c) +-- TODO: PP +-- branchBlocksClient' +-- :: ChainwebVersion +-- -> ChainId +-- -> Maybe Limit +-- -> Maybe (NextItem BlockHash) +-- -> Maybe MinRank +-- -> Maybe MaxRank +-- -> BranchBounds BlockHeaderDb +-- -> ClientM_ (Page (NextItem BlockHash) Block) +-- branchBlocksClient' v c = runIdentity $ do +-- (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +-- (SomeSing (SChainId :: Sing c)) <- return $ toSing c +-- return $ client_ @(BranchBlocksApi v c) diff --git a/test/lib/Chainweb/Test/RestAPI/Utils.hs b/test/lib/Chainweb/Test/RestAPI/Utils.hs index 0134317906..f5acd235bf 100644 --- a/test/lib/Chainweb/Test/RestAPI/Utils.hs +++ b/test/lib/Chainweb/Test/RestAPI/Utils.hs @@ -3,6 +3,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE BangPatterns #-} module Chainweb.Test.RestAPI.Utils -- * Debugging ( debug @@ -17,7 +18,6 @@ module Chainweb.Test.RestAPI.Utils , PactTestFailure(..) , PollingExpectation(..) , local -, localTestToRetry , spv , ethSpv , sending @@ -56,15 +56,15 @@ import Chainweb.Test.Utils -- internal pact modules import qualified Pact.JSON.Encode as J -import Pact.Types.API -import Pact.Types.Command -import Pact.Types.Hash -import qualified Pact.Core.Command.Server as Pact5 -import qualified Pact.Core.Command.Types as Pact5 -import qualified Pact.Types.API as Pact4 +import qualified Pact.Core.Command.Server as Pact +import qualified Pact.Core.Command.Types as Pact import qualified Data.Aeson as Aeson import qualified Data.Text.Lazy.Encoding as TL import qualified Data.Text.Lazy as TL +import qualified Pact.Core.Hash as Pact +import qualified Pact.Core.Command.Client as Pact +import qualified Pact.Core.Errors as Pact +import Chainweb.Utils -- ------------------------------------------------------------------ -- -- Defaults @@ -107,23 +107,12 @@ localWithQueryParams -> Maybe LocalPreflightSimulation -> Maybe LocalSignatureVerification -> Maybe RewindDepth - -> Command Text + -> Pact.Command Text -> IO LocalResult localWithQueryParams v sid cenv pf sv rd cmd = - recovering testRetryPolicy [h] $ \s -> do - debug - $ "requesting local cmd for " <> take 19 (show cmd) - <> " [" <> show (view rsIterNumberL s) <> "]" - - -- send a single local request and return the result - -- - runClientM (pactLocalWithQueryApiClient v sid pf sv rd cmd) cenv >>= \case - Left e -> throwM $ LocalFailure (show e) - Right t -> return t - where - h _ = Handler $ \case - LocalFailure _ -> pure True - _ -> pure False + runClientM (pactLocalApiClient v sid pf sv rd cmd) cenv >>= \case + Left e -> throwM $ LocalFailure (show e) + Right t -> return t -- | Calls /local via the pact local api client with preflight -- turned off. Retries. @@ -132,24 +121,15 @@ local :: ChainwebVersion -> ChainId -> ClientEnv - -> Command Text - -> IO (CommandResult Hash) + -> Pact.Command Text + -- TODO: PP. This needs to become a full PactError eventually + -> IO (Pact.CommandResult Pact.Hash Pact.PactOnChainError) local v sid cenv cmd = do Just cr <- preview _LocalResultLegacy <$> localWithQueryParams v sid cenv Nothing Nothing Nothing cmd - Just pact4Cr <- return $ - Aeson.decode (TL.encodeUtf8 $ TL.fromStrict $ J.getJsonText cr) - pure pact4Cr - -localTestToRetry - :: ChainwebVersion - -> ChainId - -> ClientEnv - -> Command Text - -> (CommandResult Hash -> Bool) - -> IO (CommandResult Hash) -localTestToRetry v sid cenv cmd test = - repeatUntil (return . test) (local v sid cenv cmd) + let !result = fromJuste $ + Aeson.decode (TL.encodeUtf8 $ TL.fromStrict $ J.getJsonText cr) + pure result -- | Request an SPV proof using exponential retry logic -- @@ -204,22 +184,22 @@ sending :: ChainwebVersion -> ChainId -> ClientEnv - -> SubmitBatch - -> IO RequestKeys + -> Pact.SubmitBatch + -> IO Pact.RequestKeys sending v sid cenv batch = recovering testRetryPolicy [h] $ \s -> do debug - $ "sending requestkeys " <> show (_cmdHash <$> toList ss) + $ "sending requestkeys " <> show (Pact._cmdHash <$> toList ss) <> " [" <> show (view rsIterNumberL s) <> "]" -- Send and return naively -- - runClientM (pactSendApiClient v sid batch) cenv >>= \case + runClientM (pactSendApiClient v sid (Pact.SendRequest batch)) cenv >>= \case Left e -> throwM $ SendFailure (show e) - Right rs -> return rs + Right (Pact.SendResponse rs) -> return rs where - ss = _sbCmds batch + ss = Pact._sbCmds batch h _ = Handler $ \case SendFailure _ -> return True @@ -234,9 +214,9 @@ polling :: ChainwebVersion -> ChainId -> ClientEnv - -> RequestKeys + -> Pact.RequestKeys -> PollingExpectation - -> IO Pact4.PollResponses + -> IO Pact.PollResponse polling v sid cenv rks pollingExpectation = pollingWithDepth v sid cenv rks Nothing pollingExpectation @@ -244,10 +224,10 @@ pollingWithDepth :: ChainwebVersion -> ChainId -> ClientEnv - -> RequestKeys + -> Pact.RequestKeys -> Maybe ConfirmationDepth -> PollingExpectation - -> IO Pact4.PollResponses + -> IO Pact.PollResponse pollingWithDepth v sid cenv rks confirmationDepth pollingExpectation = recovering testRetryPolicy [h] $ \s -> do debug @@ -258,27 +238,25 @@ pollingWithDepth v sid cenv rks confirmationDepth pollingExpectation = -- by making sure results are successful and request keys -- are sane - runClientM (pactPollWithQueryApiClient v sid confirmationDepth $ Pact5.PollRequest rs) cenv >>= \case + runClientM (pactPollApiClient v sid confirmationDepth $ Pact.PollRequest rs) cenv >>= \case Left e -> throwM $ PollingFailure (show e) - Right r@(Pact5.PollResponse mp) -> + Right r@(Pact.PollResponse mp) -> if all (go mp) (toList rs) then do - let pact4Resps = HM.fromList $ - [ (toPact4RequestKey rk, toPact4CommandResult cr) | (rk, cr) <- HM.toList mp ] - return $ Pact4.PollResponses pact4Resps + return $ Pact.PollResponse mp else throwM $ PollingFailure $ T.unpack $ "polling check failed: " <> J.encodeText r where h _ = Handler $ \case PollingFailure _ -> return True _ -> return False - rs = toPact5RequestKey <$> _rkRequestKeys rks + rs = Pact._rkRequestKeys rks - validate Pact5.PactResultOk{} = pollingExpectation == ExpectPactResult - validate Pact5.PactResultErr{} = pollingExpectation == ExpectPactError + validate Pact.PactResultOk{} = pollingExpectation == ExpectPactResult + validate Pact.PactResultErr{} = pollingExpectation == ExpectPactError go m rk = case m ^. at rk of - Just cr -> Pact5._crReqKey cr == rk && validate (Pact5._crResult cr) + Just cr -> Pact._crReqKey cr == rk && validate (Pact._crResult cr) Nothing -> False getCurrentBlockHeight :: ChainwebVersion -> ClientEnv -> ChainId -> IO BlockHeight diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index c22a30998b..7255349a3b 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -9,17 +9,14 @@ module Chainweb.Test.TestVersions ( barebonesTestVersion - , fastForkingCpmTestVersion - , noBridgeCpmTestVersion - , slowForkingCpmTestVersion + -- , fastForkingCpmTestVersion + -- , noBridgeCpmTestVersion + -- , slowForkingCpmTestVersion , quirkedGasInstantCpmTestVersion , quirkedGasPact5InstantCpmTestVersion , timedConsensusVersion , instantCpmTestVersion - , pact5InstantCpmTestVersion - , pact5CheckpointerTestVersion - , pact5SlowCpmTestVersion - , instantCpmTransitionTestVersion + , checkpointerTestVersion ) where import Control.Lens hiding (elements) @@ -28,14 +25,10 @@ import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS import qualified Data.List as List import qualified Data.Set as Set -import qualified Chainweb.BlockHeader.Genesis.FastTimedCPM0Payload as TN0 -import qualified Chainweb.BlockHeader.Genesis.FastTimedCPM1to9Payload as TNN import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN -import qualified Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM0Payload as PIN0 -import qualified Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM1to9Payload as PINN -import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM0Payload as QPIN0 -import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM1to9Payload as QPINN +-- import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM0Payload as QPIN0 +-- import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM1to9Payload as QPINN import System.IO.Unsafe @@ -54,17 +47,9 @@ import Chainweb.Version import Chainweb.Version.Registry import P2P.Peer -import qualified Pact.Types.Gas as P -import Chainweb.Test.Pact5.Utils (pactTxFrom4To5) - -import Pact.Types.Verifier - -import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 -import qualified Chainweb.Pact.Transactions.CoinV4Transactions as CoinV4 -import qualified Chainweb.Pact.Transactions.CoinV5Transactions as CoinV5 -import qualified Chainweb.Pact.Transactions.CoinV6Transactions as CoinV6 -import qualified Chainweb.Pact.Transactions.MainnetKADTransactions as MNKAD -import qualified Chainweb.Pact.Transactions.OtherTransactions as Other +import Chainweb.Payload (PayloadWithOutputs_(_payloadWithOutputsPayloadHash)) +import qualified Pact.Core.Names as Pact +import qualified Pact.Core.Gas as Pact testBootstrapPeerInfos :: PeerInfo testBootstrapPeerInfos = @@ -103,18 +88,18 @@ buildTestVersion f = -- result in runtime errors from `Chainweb.Version.Registry`. testVersions :: [ChainwebVersionName] testVersions = _versionName <$> concat - [ [ fastForkingCpmTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - , [ slowForkingCpmTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - , [ barebonesTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - , [ noBridgeCpmTestVersion (knownChainGraph g) + -- [ [ fastForkingCpmTestVersion (knownChainGraph g) + -- | g :: KnownGraph <- [minBound..maxBound] + -- ] + -- , [ slowForkingCpmTestVersion (knownChainGraph g) + -- | g :: KnownGraph <- [minBound..maxBound] + -- ] + [ [ barebonesTestVersion (knownChainGraph g) | g :: KnownGraph <- [minBound..maxBound] ] + -- , [ noBridgeCpmTestVersion (knownChainGraph g) + -- | g :: KnownGraph <- [minBound..maxBound] + -- ] , [ timedConsensusVersion (knownChainGraph g1) (knownChainGraph g2) | g1 :: KnownGraph <- [minBound..maxBound] , g2 :: KnownGraph <- [minBound..maxBound] @@ -128,16 +113,10 @@ testVersions = _versionName <$> concat , [ instantCpmTestVersion (knownChainGraph g) | g :: KnownGraph <- [minBound..maxBound] ] - , [ pact5InstantCpmTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - , [ pact5CheckpointerTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - , [ pact5SlowCpmTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - , [ instantCpmTransitionTestVersion (knownChainGraph g) + -- , [ pact5InstantCpmTestVersion (knownChainGraph g) + -- | g :: KnownGraph <- [minBound..maxBound] + -- ] + , [ checkpointerTestVersion (knownChainGraph g) | g :: KnownGraph <- [minBound..maxBound] ] ] @@ -174,7 +153,7 @@ barebonesTestVersion g = buildTestVersion $ \v -> , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = AllChains emptyPayload + { _genesisBlockPayload = AllChains $ _payloadWithOutputsPayloadHash emptyPayload , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -207,16 +186,16 @@ timedConsensusVersion g1 g2 = buildTestVersion $ \v -> v , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, TN0.payloadBlock) : - [(n, TNN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] + { _genesisBlockPayload = onChains $ [] -- TODO: PP + -- (unsafeChainId 0, TN0.payloadBlock) : + -- [(n, TNN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } -- | A test version without Pact or PoW. -pact5CheckpointerTestVersion :: ChainGraph -> ChainwebVersion -pact5CheckpointerTestVersion g1 = buildTestVersion $ \v -> v +checkpointerTestVersion :: ChainGraph -> ChainwebVersion +checkpointerTestVersion g1 = buildTestVersion $ \v -> v & testVersionTemplate & versionName .~ ChainwebVersionName ("pact5-checkpointertest-" <> toText g1) & versionBlockDelay .~ BlockDelay 1_000_000 @@ -239,10 +218,11 @@ pact5CheckpointerTestVersion g1 = buildTestVersion $ \v -> v , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains [ (n, emptyPayload) | n <- HS.toList (chainIds v) ] + { _genesisBlockPayload = onChains [ (n, _payloadWithOutputsPayloadHash emptyPayload) | n <- HS.toList (chainIds v) ] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } + & versionPayloadProviderTypes .~ AllChains PactProvider -- | A family of versions each with Pact enabled and PoW disabled. cpmTestVersion :: ChainGraph -> VersionBuilder @@ -260,107 +240,8 @@ cpmTestVersion g v = v { _disableMempoolSync = False , _disablePeerValidation = True } - & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, TN0.payloadBlock) : - [(n, TNN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch - } - & versionUpgrades .~ chainZip HM.union - (indexByForkHeights v - [ (CoinV2, AllChains (pact4Upgrade Other.transactions)) - , (Pact4Coin3, AllChains (Pact4Upgrade CoinV3.transactions True)) - , (Chainweb214Pact, AllChains (Pact4Upgrade CoinV4.transactions True)) - , (Chainweb215Pact, AllChains (Pact4Upgrade CoinV5.transactions True)) - , (Chainweb223Pact, AllChains (pact4Upgrade CoinV6.transactions)) - ]) - (onChains [(unsafeChainId 3, HM.singleton (BlockHeight 2) (Pact4Upgrade MNKAD.transactions False))]) - -slowForks :: HashMap Fork (ChainMap ForkHeight) -slowForks = tabulateHashMap \case - SlowEpoch -> AllChains ForkAtGenesis - OldTargetGuard -> AllChains ForkAtGenesis - SkipFeatureFlagValidation -> AllChains ForkAtGenesis - OldDAGuard -> AllChains ForkAtGenesis - Vuln797Fix -> AllChains ForkAtGenesis - PactBackCompat_v16 -> AllChains ForkAtGenesis - SPVBridge -> AllChains ForkAtGenesis - Pact44NewTrans -> AllChains ForkAtGenesis - CoinV2 -> AllChains $ ForkAtBlockHeight (BlockHeight 1) - SkipTxTimingValidation -> AllChains $ ForkAtBlockHeight (BlockHeight 2) - ModuleNameFix -> AllChains $ ForkAtBlockHeight (BlockHeight 2) - ModuleNameFix2 -> AllChains $ ForkAtBlockHeight (BlockHeight 2) - Pact42 -> AllChains $ ForkAtBlockHeight (BlockHeight 5) - CheckTxHash -> AllChains $ ForkAtBlockHeight (BlockHeight 7) - EnforceKeysetFormats -> AllChains $ ForkAtBlockHeight (BlockHeight 10) - PactEvents -> AllChains $ ForkAtBlockHeight (BlockHeight 10) - Pact4Coin3 -> AllChains $ ForkAtBlockHeight (BlockHeight 20) - Chainweb213Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 26) - Chainweb214Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 30) - Chainweb215Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 35) - Chainweb216Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 53) - Chainweb217Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 55) - Chainweb218Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 60) - Chainweb219Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 71) - Chainweb220Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 85) - Chainweb221Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 100) - Chainweb222Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 115) - Chainweb223Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 120) - Chainweb224Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 125) - Chainweb225Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 130) - Chainweb226Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 135) - Pact5Fork -> AllChains ForkNever - Chainweb228Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 145) - Chainweb229Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 150) - --- | A set of fork heights which are relatively fast, but not fast enough to break anything. -fastForks :: HashMap Fork (ChainMap ForkHeight) -fastForks = tabulateHashMap $ \case - SlowEpoch -> AllChains ForkAtGenesis - OldTargetGuard -> AllChains ForkAtGenesis - SkipFeatureFlagValidation -> AllChains ForkAtGenesis - OldDAGuard -> AllChains ForkAtGenesis - Vuln797Fix -> AllChains ForkAtGenesis - PactBackCompat_v16 -> AllChains ForkAtGenesis - SPVBridge -> AllChains ForkAtGenesis - EnforceKeysetFormats -> AllChains ForkAtGenesis - CheckTxHash -> AllChains ForkAtGenesis - Pact44NewTrans -> AllChains ForkAtGenesis - Chainweb213Pact -> AllChains ForkAtGenesis - PactEvents -> AllChains ForkAtGenesis - CoinV2 -> AllChains $ ForkAtBlockHeight $ BlockHeight 1 - Pact42 -> AllChains $ ForkAtBlockHeight $ BlockHeight 1 - SkipTxTimingValidation -> AllChains $ ForkAtBlockHeight $ BlockHeight 2 - ModuleNameFix -> AllChains $ ForkAtBlockHeight $ BlockHeight 2 - ModuleNameFix2 -> AllChains $ ForkAtBlockHeight $ BlockHeight 2 - Pact4Coin3 -> AllChains $ ForkAtBlockHeight $ BlockHeight 4 - Chainweb214Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 5 - Chainweb215Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 10 - Chainweb216Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 11 - Chainweb217Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 - Chainweb218Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 - Chainweb219Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 27 - Chainweb220Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 30 - Chainweb221Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 33 - Chainweb222Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 36 - Chainweb223Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 38 - Chainweb224Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 40 - Chainweb225Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 42 - Chainweb226Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 44 - Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 46 - Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 48 - Chainweb229Pact -> AllChains ForkNever - --- | CPM version (see `cpmTestVersion`) with forks and upgrades slowly enabled. -slowForkingCpmTestVersion :: ChainGraph -> ChainwebVersion -slowForkingCpmTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g - & versionName .~ ChainwebVersionName ("slowfork-CPM-" <> toText g) - & versionForks .~ slowForks - & versionVerifierPluginNames .~ AllChains - (Bottom (minBound, Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"])) - & versionQuirks .~ noQuirks + & versionUpgrades .~ AllChains mempty + & versionPayloadProviderTypes .~ AllChains PactProvider -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled, -- and with a gas fee quirk. @@ -369,16 +250,15 @@ quirkedGasInstantCpmTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("quirked-instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - Pact5Fork -> AllChains ForkNever _ -> AllChains ForkAtGenesis) & versionQuirks .~ VersionQuirks { _quirkGasFees = onChain (unsafeChainId 0) - $ HM.singleton (BlockHeight 2, TxBlockIdx 0) (P.Gas 1) + $ HM.singleton (BlockHeight 2, TxBlockIdx 0) (Pact.Gas 1) } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + { _genesisBlockPayload = onChains $ [] -- TODO: PP + -- (unsafeChainId 0, IN0.payloadBlock) : + -- [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -395,35 +275,18 @@ quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ \v -> v _ -> AllChains ForkAtGenesis) & versionQuirks .~ VersionQuirks { _quirkGasFees = onChain (unsafeChainId 0) - $ HM.singleton (BlockHeight 1, TxBlockIdx 0) (P.Gas 1) + $ HM.singleton (BlockHeight 1, TxBlockIdx 0) (Pact.Gas 1) } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, QPIN0.payloadBlock) : - [(n, QPINN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + { _genesisBlockPayload = onChains $ [] -- TODO: PP + -- (unsafeChainId 0, QPIN0.payloadBlock) : + -- [(n, QPINN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } & versionUpgrades .~ AllChains mempty & versionVerifierPluginNames .~ AllChains (Bottom (minBound, mempty)) --- | CPM version (see `cpmTestVersion`) with forks and upgrades quickly enabled. -fastForkingCpmTestVersion :: ChainGraph -> ChainwebVersion -fastForkingCpmTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g - & versionName .~ ChainwebVersionName ("fastfork-CPM-" <> toText g) - & versionForks .~ fastForks - & versionQuirks .~ noQuirks - --- | CPM version (see `cpmTestVersion`) with forks and upgrades quickly enabled --- but with no SPV bridge. -noBridgeCpmTestVersion :: ChainGraph -> ChainwebVersion -noBridgeCpmTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g - & versionName .~ ChainwebVersionName ("nobridge-CPM-" <> toText g) - & versionForks .~ (fastForks & at SPVBridge ?~ AllChains ForkNever) - & versionQuirks .~ noQuirks - -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled -- at genesis EXCEPT Pact 5. instantCpmTestVersion :: ChainGraph -> ChainwebVersion @@ -431,40 +294,13 @@ instantCpmTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - -- pact 5 is off - Pact5Fork -> AllChains ForkNever - _ -> AllChains ForkAtGenesis - ) - & versionQuirks .~ noQuirks - & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch - } - & versionUpgrades .~ AllChains mempty - & versionVerifierPluginNames .~ AllChains - (Bottom - ( minBound - , Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] - ) - ) - -pact5InstantCpmTestVersion :: ChainGraph -> ChainwebVersion -pact5InstantCpmTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g - & versionName .~ ChainwebVersionName ("instant-pact5-CPM-" <> toText g) - & versionForks .~ tabulateHashMap (\case - -- SPV Bridge is not in effect for Pact 5 yet. - SPVBridge -> AllChains ForkNever _ -> AllChains ForkAtGenesis ) & versionQuirks .~ noQuirks & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, PIN0.payloadBlock) : - [(n, PINN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : + [(n, _payloadWithOutputsPayloadHash INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -472,64 +308,40 @@ pact5InstantCpmTestVersion g = buildTestVersion $ \v -> v & versionVerifierPluginNames .~ AllChains (Bottom ( minBound - , Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] + , Set.fromList $ map Pact.VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] ) ) --- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled --- at genesis. We also have an upgrade after genesis that redeploys Coin v5 as --- a Pact 5 module. -pact5SlowCpmTestVersion :: ChainGraph -> ChainwebVersion -pact5SlowCpmTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g - & versionName .~ ChainwebVersionName ("pact5-slow-CPM-" <> toText g) - & versionForks .~ tabulateHashMap (\case - -- genesis blocks are not ever run with Pact 5 - Pact5Fork -> onChains [ (cid, ForkAtBlockHeight (succ $ genesisBlockHeight v cid)) | cid <- HS.toList $ graphChainIds g ] - -- SPV Bridge is not in effect for Pact 5 yet. - SPVBridge -> AllChains ForkNever - _ -> AllChains ForkAtGenesis - ) - & versionQuirks .~ noQuirks - & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch - } - & versionUpgrades .~ indexByForkHeights v - [ (Pact5Fork, AllChains (Pact5Upgrade (List.map pactTxFrom4To5 CoinV6.transactions))) - ] - & versionVerifierPluginNames .~ AllChains - (Bottom - ( minBound - , Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] - ) - ) - --- | ChainwebVersion that transitions between Pact4 and Pact5 at block height 20. -instantCpmTransitionTestVersion :: ChainGraph -> ChainwebVersion -instantCpmTransitionTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g - & versionName .~ ChainwebVersionName ("instant-CPM-transition-" <> toText g) - & versionForks .~ tabulateHashMap (\case - -- pact 5 is off - Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 - _ -> AllChains ForkAtGenesis - ) - & versionQuirks .~ noQuirks - & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch - } - & versionUpgrades .~ AllChains mempty - & versionVerifierPluginNames .~ AllChains - (Bottom - ( minBound - , Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] - ) - ) +-- -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled +-- -- at genesis. We also have an upgrade after genesis that redeploys Coin v5 as +-- -- a Pact 5 module. +-- pact5SlowCpmTestVersion :: ChainGraph -> ChainwebVersion +-- pact5SlowCpmTestVersion g = buildTestVersion $ \v -> v +-- & cpmTestVersion g +-- & versionName .~ ChainwebVersionName ("pact5-slow-CPM-" <> toText g) +-- & versionForks .~ tabulateHashMap (\case +-- -- genesis blocks are not ever run with Pact 5 +-- Pact5Fork -> onChains [ (cid, ForkAtBlockHeight (succ $ genesisBlockHeight v cid)) | cid <- HS.toList $ graphChainIds g ] +-- -- SPV Bridge is not in effect for Pact 5 yet. +-- SPVBridge -> AllChains ForkNever +-- _ -> AllChains ForkAtGenesis +-- ) +-- & versionQuirks .~ noQuirks +-- & versionGenesis .~ VersionGenesis +-- { _genesisBlockPayload = onChains $ [] -- TODO: PP +-- -- (unsafeChainId 0, IN0.payloadBlock) : +-- -- [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] +-- , _genesisBlockTarget = AllChains maxTarget +-- , _genesisTime = AllChains $ BlockCreationTime epoch +-- } +-- & versionUpgrades .~ indexByForkHeights v +-- -- TODO: PP +-- -- [ (Pact5Fork, AllChains (Pact5Upgrade (List.map pactTxFrom4To5 CoinV6.transactions))) +-- [ +-- ] +-- & versionVerifierPluginNames .~ AllChains +-- (Bottom +-- ( minBound +-- , Set.fromList $ map Pact.VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] +-- ) +-- ) diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index a49f4a2951..ed7e43fd0c 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -31,12 +31,6 @@ module Chainweb.Test.Utils , unsafeHeadOf , TestPact5CommandResult -, toPact4RequestKey -, toPact5RequestKey -, toPact4Command -, toPact4CommandResult -, toPact5CommandResult -, pact4Poll -- * Test RocksDb , testRocksDb @@ -151,14 +145,12 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL import Data.Coerce (coerce) import Data.Foldable -import qualified Data.HashMap.Strict as HashMap import Data.IORef import Data.List (sortOn, isInfixOf) import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Tree import qualified Data.Tree.Lens as LT -import qualified Data.Vector as V import Data.Word import qualified Network.Connection as HTTP @@ -205,7 +197,6 @@ import Chainweb.BlockHeight import Chainweb.BlockWeight import Chainweb.ChainId import Chainweb.Chainweb -import Chainweb.Chainweb.ChainResources import Chainweb.Chainweb.Configuration import Chainweb.Chainweb.PeerResources import Chainweb.Crypto.MerkleLog hiding (header) @@ -219,13 +210,12 @@ import Chainweb.Logger import Chainweb.Mempool.Mempool (MempoolBackend(..), TransactionHash(..), BlockFill(..), mockBlockGasLimit) import Chainweb.MerkleUniverse import Chainweb.Miner.Config -import Chainweb.Miner.Pact import Chainweb.Pact.Backend.Types(SQLiteEnv) import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) -import Chainweb.Payload.PayloadStore +import Chainweb.Parent import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID -import Chainweb.Test.Pact5.Utils (getTestLogLevel) +import Chainweb.Test.Pact.Utils (getTestLogLevel) import Chainweb.Test.P2P.Peer.BootstrapConfig (testBootstrapCertificate, testBootstrapKey, testBootstrapPeerConfig) import Chainweb.Test.Utils.BlockHeader @@ -251,14 +241,9 @@ import P2P.Peer import Chainweb.Test.Utils.APIValidation import Data.Semigroup import qualified Pact.Core.Command.Types as Pact5 -import qualified Data.Aeson as Aeson import qualified Pact.Core.Errors as Pact5 -import qualified Pact.Types.Command as Pact4 import qualified Pact.Core.Hash as Pact5 -import qualified Pact.Types.Hash as Pact4 -import qualified Pact.JSON.Encode as J -import qualified Pact.Types.API as Pact4 -import qualified Pact.Core.Command.Server as Pact5 +import qualified Pact.Core.Gas as Pact -- -------------------------------------------------------------------------- -- @@ -411,7 +396,7 @@ genesisBlockHeaderForChain v i insertN :: Int -> BlockHeader -> BlockHeaderDb -> IO () insertN n g db = traverse_ (unsafeInsertBlockHeaderDb db) bhs where - bhs = take n $ testBlockHeaders $ ParentHeader g + bhs = take n $ testBlockHeaders $ Parent g -- | Payload hashes are generated using 'testBlockPayloadFromParent_', which -- includes the nonce. They payloads can be recovered using @@ -422,7 +407,7 @@ insertN_ s n g db = do traverse_ (unsafeInsertBlockHeaderDb db) bhs return bhs where - bhs = take (int n) $ testBlockHeadersWithNonce s $ ParentHeader g + bhs = take (int n) $ testBlockHeadersWithNonce s $ Parent g -- | Useful for terminal-based debugging. A @Tree BlockHeader@ can be obtained -- from any `TreeDb` via `toTree`. @@ -507,19 +492,19 @@ header p = do . newMerkleLog $ mkFeatureFlags :+: t' - :+: view blockHash p + :+: Parent (view blockHash p) :+: target - :+: casKey (testBlockPayloadFromParent (ParentHeader p)) + :+: casKey (testBlockPayloadFromParent (Parent p)) :+: _chainId p :+: BlockWeight (targetToDifficulty target) + view blockWeight p :+: succ (view blockHeight p) :+: _versionCode v - :+: epochStart (ParentHeader p) mempty t' + :+: epochStart (Parent p) mempty t' :+: nonce :+: MerkleLogBody mempty where BlockCreationTime t = view blockCreationTime p - target = powTarget (ParentHeader p) mempty t' + target = powTarget (Parent p) mempty t' v = _chainwebVersion p t' = BlockCreationTime (scaleTimeSpan (10 :: Int) second `add` t) @@ -554,7 +539,7 @@ linearBlockHeaderDbs n dbs = do where populateDb (_, db) = do gbh0 <- root db - traverse_ (unsafeInsertBlockHeaderDb db) . take (int n) . testBlockHeaders $ ParentHeader gbh0 + traverse_ (unsafeInsertBlockHeaderDb db) . take (int n) . testBlockHeaders $ Parent gbh0 starBlockHeaderDbs :: Natural @@ -566,7 +551,7 @@ starBlockHeaderDbs n dbs = do where populateDb (_, db) = do gbh0 <- root db - traverse_ (\i -> unsafeInsertBlockHeaderDb db . newEntry i $ ParentHeader gbh0) [0 .. (int n-1)] + traverse_ (\i -> unsafeInsertBlockHeaderDb db . newEntry i $ Parent gbh0) [0 .. (int n-1)] newEntry i h = head $ testBlockHeadersWithNonce (Nonce i) h @@ -1048,7 +1033,7 @@ node -- ^ Unique Node Id. The node id 0 is used for the bootstrap node -> IO () node rdb rawLogger nowServingRef peerInfoVar conf pactDbDir backupDir nid = do - withChainweb conf logger rdb pactDbDir backupDir False $ \case + withChainweb conf logger rdb pactDbDir backupDir $ \case StartedChainweb cw -> do -- If this is the bootstrap node we extract the port number and publish via an MVar. when (nid == 0) $ do @@ -1115,7 +1100,7 @@ config ver n = defaultChainwebConfiguration ver & set (configP2p . p2pConfigSessionTimeout) 60 & set (configMining . miningInNode) miner & set configReintroTxs True - & set configBlockGasLimit 1_000_000 + & set configBlockGasLimit (Pact.GasLimit $ Pact.Gas 1_000_000) & set (configMining . miningCoordination . coordinationEnabled) True & set (configServiceApi . serviceApiConfigPort) 0 & set (configServiceApi . serviceApiConfigInterface) interface @@ -1179,34 +1164,3 @@ unsafeHeadOf :: HasCallStack => Getting (Endo a) s a -> s -> a unsafeHeadOf l s = s ^?! l type TestPact5CommandResult = Pact5.CommandResult Pact5.Hash Pact5.PactOnChainError - -toPact4RequestKey :: Pact5.RequestKey -> Pact4.RequestKey -toPact4RequestKey = \case - Pact5.RequestKey (Pact5.Hash bytes) -> Pact4.RequestKey (Pact4.Hash bytes) - -toPact5RequestKey :: Pact4.RequestKey -> Pact5.RequestKey -toPact5RequestKey = \case - Pact4.RequestKey (Pact4.Hash bytes) -> Pact5.RequestKey (Pact5.Hash bytes) - -toPact4Command :: Pact5.Command T.Text -> Pact4.Command T.Text -toPact4Command cmd4 = case Aeson.eitherDecodeStrictText (J.encodeText cmd4) of - Left err -> error $ "toPact4Command: decode failed: " ++ err - Right cmd5 -> cmd5 - -toPact4CommandResult :: () - => TestPact5CommandResult - -> Pact4.CommandResult Pact4.Hash -toPact4CommandResult cr5 = - case Aeson.eitherDecodeStrictText (J.encodeText cr5) of - Left err -> error $ "toPact5CommandResult: decode failed: " ++ err - Right cr4 -> cr4 - -toPact5CommandResult :: () - => Pact4.CommandResult Pact4.Hash - -> TestPact5CommandResult -toPact5CommandResult cr4 = case Aeson.eitherDecodeStrictText (J.encodeText cr4) of - Left err -> error $ "toPact5CommandResult: decode failed: " ++ err - Right cr5 -> cr5 - -pact4Poll :: Pact4.Poll -> Pact5.PollRequest -pact4Poll (Pact4.Poll rks) = Pact5.PollRequest $ toPact5RequestKey <$> rks diff --git a/test/lib/Chainweb/Test/Utils/BlockHeader.hs b/test/lib/Chainweb/Test/Utils/BlockHeader.hs index d9fcfc550b..f9586bb84f 100644 --- a/test/lib/Chainweb/Test/Utils/BlockHeader.hs +++ b/test/lib/Chainweb/Test/Utils/BlockHeader.hs @@ -40,6 +40,7 @@ import Chainweb.BlockCreationTime import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.ChainValue +import Chainweb.Parent import Chainweb.Payload import Chainweb.Time import Chainweb.Utils @@ -120,12 +121,12 @@ testGetNewAdjacentParentHeaders => ChainwebVersion -> (ChainValue BlockHash -> m BlockHeader) -> BlockHashRecord - -> m (HM.HashMap ChainId (Either BlockHash (Parent BlockHeader))) + -> m (HM.HashMap ChainId (Either (Parent BlockHash) (Parent BlockHeader))) testGetNewAdjacentParentHeaders v hdb = itraverse select . _getBlockHashRecord where select cid h - | h == genesisParentBlockHash v cid = pure $ Left h - | otherwise = Right . Parent <$> hdb (ChainValue cid h) + | h == genesisParentBlockHash v cid = pure $ Left $ h + | otherwise = Right . Parent <$> hdb (ChainValue cid (unwrapParent h)) testBlockHeader :: HM.HashMap ChainId (Parent BlockHeader) diff --git a/test/lib/Chainweb/Test/Utils/TestHeader.hs b/test/lib/Chainweb/Test/Utils/TestHeader.hs index 4d6e8d703f..47f033c5ff 100644 --- a/test/lib/Chainweb/Test/Utils/TestHeader.hs +++ b/test/lib/Chainweb/Test/Utils/TestHeader.hs @@ -57,6 +57,7 @@ import Chainweb.BlockCreationTime import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainValue +import Chainweb.Parent import Chainweb.Test.Orphans.Internal import Chainweb.Version @@ -67,8 +68,8 @@ import Chainweb.Storage.Table data TestHeader = TestHeader { _testHeaderHdr :: !BlockHeader - , _testHeaderParent :: !ParentHeader - , _testHeaderAdjs :: ![ParentHeader] + , _testHeaderParent :: !(Parent BlockHeader) + , _testHeaderAdjs :: ![Parent BlockHeader] } deriving (Show, Eq, Ord, Generic) @@ -95,13 +96,13 @@ testHeaderLookup testHdr x = lookup x tbl tbl = (view blockHash h, h) : (view blockHash p, p) - : fmap (\(ParentHeader b) -> (view blockHash b, b)) a + : fmap (\(Parent b) -> (view blockHash b, b)) a instance FromJSON TestHeader where parseJSON = withObject "TestHeader" $ \o -> TestHeader <$> o .: "header" - <*> (ParentHeader <$> o .: "parent") - <*> (fmap ParentHeader <$> o .: "adjacents") + <*> (Parent <$> o .: "parent") + <*> (fmap Parent <$> o .: "adjacents") instance ToJSON TestHeader where toJSON o = object @@ -139,7 +140,7 @@ arbitraryTestHeaderHeight -> BlockHeight -> Gen TestHeader arbitraryTestHeaderHeight v cid h = do - parent <- ParentHeader <$> arbitraryBlockHeaderVersionHeightChain v h cid + parent <- Parent <$> arbitraryBlockHeaderVersionHeightChain v h cid trace "a" $ return () -- TODO: support graph changes in arbitary? @@ -153,9 +154,9 @@ arbitraryTestHeaderHeight v cid h = do <$> HM.insert cid (unwrapParent parent) as t <- BlockCreationTime <$> chooseEnum (pt, maxBound) return $ TestHeader - { _testHeaderHdr = newBlockHeader (ParentHeader <$> as) payloadHash nonce t parent + { _testHeaderHdr = newBlockHeader (Parent <$> as) payloadHash nonce t parent , _testHeaderParent = parent - , _testHeaderAdjs = toList $ ParentHeader <$> as + , _testHeaderAdjs = toList $ Parent <$> as } -- -------------------------------------------------------------------------- -- @@ -186,8 +187,8 @@ genesisTestHeader -> TestHeader genesisTestHeader v cid = TestHeader { _testHeaderHdr = gen - , _testHeaderParent = ParentHeader gen - , _testHeaderAdjs = ParentHeader . genesisBlockHeader (_chainwebVersion v) + , _testHeaderParent = Parent gen + , _testHeaderAdjs = Parent . genesisBlockHeader (_chainwebVersion v) <$> toList (adjacentChainIds (_chainGraph gen) cid) } where diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index d1fa8aa936..dd8741f26d 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -82,7 +82,7 @@ import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService +-- import Chainweb.WebPactExecutionService import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB @@ -131,7 +131,6 @@ withTestCutDb rdb v conf n pactIO logfun f = do rocksDb <- testRocksDb "withTestCutDb" rdb let payloadDb = newPayloadDb rocksDb cutHashesDb = cutHashesTable rocksDb - initializePayloadDb v payloadDb webDb <- initWebBlockHeaderDb rocksDb v mgr <- HTTP.newManager HTTP.defaultManagerSettings pact <- pactIO webDb payloadDb diff --git a/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs similarity index 53% rename from test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs rename to test/unit/Chainweb/Test/Pact/CheckpointerTest.hs index c104cbcafd..b04f572a09 100644 --- a/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs +++ b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs @@ -11,54 +11,54 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE ImportQualifiedPost #-} -module Chainweb.Test.Pact5.CheckpointerTest (tests) where +module Chainweb.Test.Pact.CheckpointerTest (tests) where -import Chainweb.BlockHeader -import Chainweb.Graph (singletonChainGraph) -import Chainweb.Logger -import Chainweb.MerkleLogHash -import Chainweb.MerkleUniverse (ChainwebMerkleHashAlgorithm) -import Chainweb.Pact.Types -import Chainweb.Test.TestVersions -import Chainweb.Test.Utils hiding (withTempSQLiteResource) -import Chainweb.Time -import Chainweb.Utils -import Chainweb.Utils.Serialization (runGetS, runPutS) -import Chainweb.Version import Control.Exception (evaluate) import Control.Exception.Safe import Control.Lens import Control.Monad -import Control.Monad.IO.Class import Data.ByteString (ByteString) -import Data.Foldable import Data.Functor.Product -import qualified Data.Map as Map -import Data.MerkleLog (MerkleNodeType(..), merkleRoot, merkleTree) +import Data.List.NonEmpty qualified as NE +import Data.Map qualified as Map +import Data.MerkleLog (MerkleNodeType (..), merkleRoot, merkleTree) import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Encoding as T +import Data.Text qualified as T +import Data.Text.Encoding qualified as T import Hedgehog hiding (Update) -import qualified Hedgehog.Gen as Gen -import qualified Hedgehog.Range as Range -import Numeric.AffineSpace +import Hedgehog.Gen qualified as Gen +import Hedgehog.Range qualified as Range import Pact.Core.Builtin import Pact.Core.Evaluate (Info) import Pact.Core.Literal import Pact.Core.Names -import qualified Pact.Core.PactDbRegression as Pact.Core +import Pact.Core.PactDbRegression qualified as Pact.Core import Pact.Core.PactValue import Pact.Core.Persistence -import qualified Streaming.Prelude as Stream +import PropertyMatchers qualified as P import Test.Tasty import Test.Tasty.HUnit (assertEqual, testCase) import Test.Tasty.Hedgehog -import Chainweb.Test.Pact5.Utils -import Chainweb.Pact5.Backend.ChainwebPactDb (Pact5Db(doPact5DbTransaction)) -import Chainweb.Pact5.Types (noInfo) + +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.Graph (singletonChainGraph) +import Chainweb.MerkleLogHash +import Chainweb.MerkleUniverse (ChainwebMerkleHashAlgorithm) +import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb import Chainweb.Pact.Backend.Types -import qualified Chainweb.Pact.PactService.Checkpointer.Internal as Checkpointer +import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer +import Chainweb.Pact.Types +import Chainweb.Parent +import Chainweb.PayloadProvider (NewBlockCtx (_newBlockCtxMinerReward, _newBlockCtxParentCreationTime), genesisConsensusState) +import Chainweb.Test.Pact.Utils +import Chainweb.Test.TestVersions +import Chainweb.Test.Utils hiding (withTempSQLiteResource) +import Chainweb.Utils +import Chainweb.Utils.Serialization (runGetS, runPutS) +import Chainweb.Version -- | A @DbAction f@ is a description of some action on the database together with an f-full of results for it. type DbValue = Integer @@ -120,8 +120,7 @@ hoistDbAction f (DbCreateTable tn es) = DbCreateTable tn (f es) tryShow :: IO a -> IO (Either Text a) tryShow = handleAny (fmap Left . \case - (fromException -> Just (PactInternalError _ text)) -> return text - e -> return $ sshow e + e -> return (sshow e) ) . fmap Right -- Run an empty DbAction, annotating it with its result @@ -158,54 +157,92 @@ runDbAction' pactDB = \case -- craft a fake block header from txlogs, i.e. some set of writes. -- that way, the block header changes if the write set stops agreeing. -blockHeaderFromTxLogs :: ParentHeader -> [TxLog ByteString] -> IO BlockHeader -blockHeaderFromTxLogs ph txLogs = do - let - logMerkleTree = merkleTree @ChainwebMerkleHashAlgorithm @ByteString - [ TreeNode $ merkleRoot $ merkleTree - [ InputNode (T.encodeUtf8 (_txDomain txLog)) - , InputNode (T.encodeUtf8 (_txKey txLog)) - , InputNode (_txValue txLog) +blockHeaderFromTxLogs :: Parent RankedBlockHash -> [TxLog ByteString] -> IO (BlockHash, BlockPayloadHash) +blockHeaderFromTxLogs parent txLogs = do + fakePayloadHash <- runGetS decodeBlockPayloadHash $ + let + payloadLogMerkleTree = merkleTree @ChainwebMerkleHashAlgorithm @ByteString + [ TreeNode $ merkleRoot $ merkleTree + [ InputNode (T.encodeUtf8 (_txDomain txLog)) + , InputNode (T.encodeUtf8 (_txKey txLog)) + , InputNode (_txValue txLog) + ] + | txLog <- txLogs ] - | txLog <- txLogs - ] - encodedLogRoot = runPutS $ encodeMerkleLogHash $ MerkleLogHash $ merkleRoot logMerkleTree - fakePayloadHash <- runGetS decodeBlockPayloadHash encodedLogRoot - return $ newBlockHeader - mempty - fakePayloadHash - (Nonce 0) - (view blockCreationTime (_parentHeader ph) .+^ TimeSpan (1_000_000 :: Micros)) - ph + in + runPutS $ encodeMerkleLogHash $ MerkleLogHash $ merkleRoot payloadLogMerkleTree + fakeBlockHash <- runGetS decodeBlockHash $ + let + blockLogMerkleTree = merkleTree @ChainwebMerkleHashAlgorithm @ByteString + [ TreeNode $ merkleRoot $ merkleTree + [ InputNode (runPutS $ encodeBlockHash $ unwrapParent $ _rankedBlockHashHash <$> parent) + , InputNode (runPutS $ encodeBlockPayloadHash @ChainwebMerkleHashAlgorithm fakePayloadHash) + ] + ] + in + runPutS $ encodeMerkleLogHash $ MerkleLogHash $ merkleRoot blockLogMerkleTree + return (fakeBlockHash, fakePayloadHash) -- TODO things to test later: -- that a tree of blocks can be explored, such that reaching any particular block gives identical results to running to that block from genesis -- more specific regressions, like in the Pact 4 checkpointer test runBlocks - :: Checkpointer GenericLogger - -> ParentHeader + :: SQLiteEnv + -> Parent RankedBlockHash -> [DbBlock (Const ())] - -> IO [(BlockHeader, DbBlock Identity)] -runBlocks cp ph blks = do - ((), finishedBlks) <- Checkpointer.restoreAndSave cp (Just ph) $ traverse_ Stream.yield - [ Pact5RunnableBlock $ \db _ph startHandle -> do - doPact5DbTransaction db startHandle Nothing $ \txdb -> do - _ <- ignoreGas noInfo $ _pdbBeginTx txdb Transactional - blk' <- traverse (runDbAction txdb) blk - txLogs <- ignoreGas noInfo $ _pdbCommitTx txdb - bh <- blockHeaderFromTxLogs (fromJuste _ph) txLogs - return ([(bh, blk')], bh) - | blk <- blks - ] - return finishedBlks + -> IO [(BlockCtx, (BlockHash, BlockPayloadHash), DbBlock Identity)] +runBlocks sql rootBlockCtx blks = do + loop rootBlockCtx blks + where + loop parent (block:blocks) = do + logger <- getTestLogger + fakeNewBlockCtx <- Checkpointer.mkFakeNewBlockCtx + (fakeBlockInfo, block', _finalBlockHandle) <- + (throwIfNoHistory =<<) $ + Checkpointer.readFrom logger testVer cid sql fakeNewBlockCtx parent $ + executeBlockTransaction parent block + let childBlockCtx = BlockCtx + { _bctxParentCreationTime = _newBlockCtxParentCreationTime fakeNewBlockCtx + , _bctxParentHash = Parent $ fst fakeBlockInfo + , _bctxParentHeight = Parent $ childBlockHeight testVer cid parent + , _bctxChainId = cid + , _bctxChainwebVersion = testVer + , _bctxMinerReward = _newBlockCtxMinerReward fakeNewBlockCtx + } + let parentBlockCtx = BlockCtx + { _bctxParentCreationTime = _newBlockCtxParentCreationTime fakeNewBlockCtx + , _bctxParentHash = _rankedBlockHashHash <$> parent + , _bctxParentHeight = _rankedBlockHashHeight <$> parent + , _bctxChainId = cid + , _bctxChainwebVersion = testVer + , _bctxMinerReward = _newBlockCtxMinerReward fakeNewBlockCtx + } + _ <- Checkpointer.restoreAndSave logger testVer cid sql + (NE.singleton (parentBlockCtx, \blockEnv blockHandle -> do + (fakeBlockInfo', _blk, finalBlockHandle) <- executeBlockTransaction parent block blockEnv blockHandle + fakeBlockInfo' & P.equals fakeBlockInfo + return ((), finalBlockHandle, fakeBlockInfo) + )) + ((parentBlockCtx, fakeBlockInfo, block') :) <$> loop (_bctxParentRankedBlockHash childBlockCtx) blocks + loop _ [] = return [] + executeBlockTransaction parent block blockEnv blockHandle = do + ((childRankedBlockHash, blk'), finalBlockHandle) <- doChainwebPactDbTransaction (_psBlockDbEnv blockEnv) blockHandle Nothing $ \txdb _spv -> do + _ <- ignoreGas noInfo $ _pdbBeginTx txdb Transactional + blk' <- traverse (runDbAction txdb) block + txLogs <- ignoreGas noInfo $ _pdbCommitTx txdb + blockInfo <- blockHeaderFromTxLogs parent txLogs + return (blockInfo, blk') + return (childRankedBlockHash, blk', finalBlockHandle) -- Check that a block's result at the time it was added to the checkpointer -- is consistent with us executing that block with `readFrom` -assertBlock :: Checkpointer GenericLogger -> ParentHeader -> (BlockHeader, DbBlock Identity) -> IO () -assertBlock cp ph (expectedBh, blk) = do - hist <- Checkpointer.readFrom cp (Just ph) Pact5T $ \db startHandle -> do - ((), _endHandle) <- doPact5DbTransaction db startHandle Nothing $ \txdb -> do +assertBlock :: SQLiteEnv -> BlockCtx -> (BlockHash, BlockPayloadHash) -> DbBlock Identity -> IO () +assertBlock sql blockCtx expectedBlockInfo blk = do + fakeNewBlockCtx <- Checkpointer.mkFakeNewBlockCtx + logger <- getTestLogger + hist <- Checkpointer.readFrom logger testVer cid sql fakeNewBlockCtx (_bctxParentRankedBlockHash blockCtx) $ \blockEnv startHandle -> do + ((), _endHandle) <- doChainwebPactDbTransaction (_psBlockDbEnv blockEnv) startHandle Nothing $ \txdb _spv -> do _ <- ignoreGas noInfo $ _pdbBeginTx txdb Transactional blk' <- forM blk (runDbAction' txdb) txLogs <- ignoreGas noInfo $ _pdbCommitTx txdb @@ -221,42 +258,55 @@ assertBlock cp ph (expectedBh, blk) = do DbCreateTable _tn (Pair expected actual) -> assertEqual "create table result" expected actual - actualBh <- blockHeaderFromTxLogs ph txLogs - assertEqual "block header" expectedBh actualBh + actualBlockInfo <- + blockHeaderFromTxLogs (_bctxParentRankedBlockHash blockCtx) txLogs + assertEqual "block header" expectedBlockInfo actualBlockInfo return () throwIfNoHistory hist tests :: TestTree tests = testGroup "Pact5 Checkpointer tests" - [ withResourceT (liftIO . initCheckpointer testVer cid =<< withTempSQLiteResource) $ \cpIO -> + [ withResourceT withTempSQLiteResource $ \sqlIO -> testCase "valid PactDb before genesis" $ do - cp <- cpIO + sql <- sqlIO + ChainwebPactDb.initSchema sql + Checkpointer.setConsensusState sql $ genesisConsensusState testVer cid + logger <- getTestLogger + fakeNewBlockCtx <- Checkpointer.mkFakeNewBlockCtx ((), _handle) <- (throwIfNoHistory =<<) $ - Checkpointer.readFrom cp Nothing Pact5T $ \db blockHandle -> do - doPact5DbTransaction db blockHandle Nothing $ \txdb -> + Checkpointer.readFrom logger testVer cid sql fakeNewBlockCtx genesisParentRanked + $ \db blockHandle -> do + doChainwebPactDbTransaction (_psBlockDbEnv db) blockHandle Nothing $ \txdb _spv -> Pact.Core.runPactDbRegression txdb return () - , withResourceT (liftIO . initCheckpointer testVer cid =<< withTempSQLiteResource) $ \cpIO -> + , withResourceT withTempSQLiteResource $ \sqlIO -> testProperty "readFrom with linear block history is valid" $ withTests 1000 $ property $ do blocks <- forAll genBlockHistory - evalIO $ do - cp <- cpIO + sql <- evalIO sqlIO + finishedBlocks <- evalIO $ do + ChainwebPactDb.initSchema sql + Checkpointer.setConsensusState sql $ genesisConsensusState testVer cid + logger <- getTestLogger -- extend this empty chain with the genesis block - ((), ()) <- Checkpointer.restoreAndSave cp Nothing $ Stream.yield $ Pact5RunnableBlock $ \_ _ hndl -> - return (((), gh), hndl) + _ <- Checkpointer.restoreAndSave logger testVer cid sql $ + ( + NE.singleton (blockCtxOfEvaluationCtx testVer cid (genesisEvaluationCtx testVer cid), + \_ hndl -> return ((), hndl, (view blockHash (genesisBlockHeader testVer cid), genesisBlockPayloadHash testVer cid))) + ) + handle @_ @SomeException + (\ex -> putStrLn (displayException ex) >> throw ex) + (runBlocks sql (Parent $ view rankedBlockHash gh) blocks) -- run all of the generated blocks - finishedBlocks <- runBlocks cp (ParentHeader gh) blocks - let - finishedBlocksWithParents = - zip (fmap ParentHeader $ gh : (fst <$> finishedBlocks)) finishedBlocks - -- assert that using readFrom to read from a parent, then executing the same block, - -- gives the same results - forM_ finishedBlocksWithParents $ \(ph, block) -> do - assertBlock cp ph block + annotateShow finishedBlocks + -- assert that using readFrom to read from a parent, then executing the same block, + -- gives the same results + evalIO $ forM_ finishedBlocks $ \(parent, blockInfo, block) -> do + assertBlock sql parent blockInfo block ] + testVer :: ChainwebVersion -testVer = pact5CheckpointerTestVersion singletonChainGraph +testVer = checkpointerTestVersion singletonChainGraph cid :: ChainId cid = unsafeChainId 0 @@ -264,6 +314,11 @@ cid = unsafeChainId 0 gh :: BlockHeader gh = genesisBlockHeader testVer cid +genesisParentRanked :: Parent RankedBlockHash +genesisParentRanked = Parent $ RankedBlockHash + (genesisHeight testVer cid) + (unwrapParent $ genesisParentBlockHash testVer cid) + instance (forall a. Show a => Show (f a)) => Show (DbAction f) where showsPrec n (DbRead tn k v) = showParen (n > 10) $ showString "DbRead " . showsPrec 11 tn diff --git a/test/unit/Chainweb/Test/Pact5/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs similarity index 97% rename from test/unit/Chainweb/Test/Pact5/CutFixture.hs rename to test/unit/Chainweb/Test/Pact/CutFixture.hs index c1f972b64a..b71cb55477 100644 --- a/test/unit/Chainweb/Test/Pact5/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -25,7 +25,7 @@ -- multiple chains. Usually, you initialize it with `mkFixture`, insert -- transactions into the mempool as desired, and use `advanceAllChains` to -- trigger mining on all chains at once. -module Chainweb.Test.Pact5.CutFixture +module Chainweb.Test.Pact.CutFixture ( Fixture(..) , HasFixture(..) , mkFixture @@ -58,12 +58,12 @@ import Chainweb.Miner.Pact import Chainweb.Pact.PactService.Pact4.ExecBlock () import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types -import Chainweb.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Storage.Table.RocksDB import Chainweb.Sync.WebBlockHeaderStore -import Chainweb.Test.Pact5.Utils +import Chainweb.Test.Pact.Utils import Chainweb.Test.Utils import Chainweb.Time import Chainweb.Utils @@ -213,7 +213,7 @@ mine cid miner newBlockStrat pact cutDb c = do tryMineForChain miner newBlockStrat pact cutDb c cid >>= \case Left _ -> throwM $ InternalInvariantViolation $ "Failed to create new cut on chain " <> toText cid <> "." - <> "This is a bug in Chainweb.Test.Pact5.CutFixture or one of its users; check that this chain's adjacent chains aren't too far behind." + <> "This is a bug in Chainweb.Test.Pact.CutFixture or one of its users; check that this chain's adjacent chains aren't too far behind." <> "\nCut: \n" <> Text.unlines (cutToTextShort c) Right x -> do diff --git a/test/unit/Chainweb/Test/Pact5/HyperlanePluginTests.hs b/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs similarity index 99% rename from test/unit/Chainweb/Test/Pact5/HyperlanePluginTests.hs rename to test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs index f71000cfe1..f194cbf50d 100644 --- a/test/unit/Chainweb/Test/Pact5/HyperlanePluginTests.hs +++ b/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs @@ -1,6 +1,6 @@ {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} -module Chainweb.Test.Pact5.HyperlanePluginTests (tests) where +module Chainweb.Test.Pact.HyperlanePluginTests (tests) where import Control.Lens import Control.Monad.IO.Class @@ -17,10 +17,10 @@ import Test.Tasty.HUnit (testCaseSteps) import Chainweb.Graph import Chainweb.Storage.Table.RocksDB (RocksDb) -import Chainweb.Test.Pact5.CmdBuilder -import Chainweb.Test.Pact5.CutFixture (advanceAllChains_) -import Chainweb.Test.Pact5.RemotePactTest hiding (tests) -import Chainweb.Test.Pact5.Utils +import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Test.Pact.CutFixture (advanceAllChains_) +import Chainweb.Test.Pact.RemotePactTest hiding (tests) +import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions import Chainweb.Test.Utils import Chainweb.Utils diff --git a/test/unit/Chainweb/Test/Pact5/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs similarity index 98% rename from test/unit/Chainweb/Test/Pact5/PactServiceTest.hs rename to test/unit/Chainweb/Test/Pact/PactServiceTest.hs index 544c912e77..fcf4a54f33 100644 --- a/test/unit/Chainweb/Test/Pact5/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -16,13 +16,13 @@ {-# options_ghc -fno-warn-gadt-mono-local-binds #-} -module Chainweb.Test.Pact5.PactServiceTest +module Chainweb.Test.Pact.PactServiceTest ( tests ) where import Data.List qualified as List -import "pact" Pact.Types.Command qualified as Pact4 -import "pact" Pact.Types.Hash qualified as Pact4 +import "pact" Pact.Types.Command qualified as Pact +import "pact" Pact.Types.Hash qualified as Pact import Chainweb.BlockHeader import Chainweb.ChainId import Chainweb.Chainweb @@ -39,13 +39,13 @@ import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactInProcApi import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types -import Chainweb.Pact4.Transaction qualified as Pact4 -import Chainweb.Pact5.Transaction qualified as Pact5 +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.Transaction qualified as Pact5 import Chainweb.Payload import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb, _bdbWebBlockHeaderDb), addTestBlockDb, getCutTestBlockDb, getParentTestBlockDb, mkTestBlockDb, setCutTestBlockDb) -import Chainweb.Test.Pact5.CmdBuilder -import Chainweb.Test.Pact5.Utils hiding (withTempSQLiteResource) +import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Test.Pact.Utils hiding (withTempSQLiteResource) import Chainweb.Test.TestVersions import Chainweb.Test.Utils import Chainweb.Time @@ -78,7 +78,7 @@ import Pact.Core.Gas.Types import Pact.Core.Hash qualified as Pact5 import Pact.Core.Names import Pact.Core.PactValue -import Pact.Types.Gas qualified as Pact4 +import Pact.Types.Gas qualified as Pact import PropertyMatchers ((?)) import PropertyMatchers qualified as P import Test.Tasty diff --git a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs similarity index 99% rename from test/unit/Chainweb/Test/Pact5/RemotePactTest.hs rename to test/unit/Chainweb/Test/Pact/RemotePactTest.hs index 656cd78de9..641cbd2fb4 100644 --- a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -28,7 +28,7 @@ -- temporary {-# OPTIONS_GHC -Wno-partial-type-signatures #-} -module Chainweb.Test.Pact5.RemotePactTest +module Chainweb.Test.Pact.RemotePactTest ( tests , mkFixture , Fixture(..) @@ -101,8 +101,8 @@ import Pact.Core.Hash import Pact.Core.Names import Pact.Core.PactValue import Pact.Core.SPV -import Pact.Types.API qualified as Pact4 -import Pact.Types.ChainId qualified as Pact4 +import Pact.Types.API qualified as Pact +import Pact.Types.ChainId qualified as Pact import Chainweb.ChainId import Chainweb.CutDB.RestAPI.Server (someCutGetServer) @@ -113,10 +113,10 @@ import Chainweb.Pact.RestAPI.Server import Chainweb.Pact.Types import Chainweb.RestAPI.Utils (someServerApplication) import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Pact5.CmdBuilder -import Chainweb.Test.Pact5.CutFixture (advanceAllChains, advanceAllChains_) -import Chainweb.Test.Pact5.CutFixture qualified as CutFixture -import Chainweb.Test.Pact5.Utils +import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Test.Pact.CutFixture (advanceAllChains, advanceAllChains_) +import Chainweb.Test.Pact.CutFixture qualified as CutFixture +import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions import Chainweb.Test.Utils import Chainweb.Utils @@ -1244,7 +1244,7 @@ pollWithDepth pollWithDepth fx v cid rks mConfirmationDepth = do clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx let rksNel = NE.fromList rks - pollResult <- runClientM (pactPollWithQueryApiClient v cid mConfirmationDepth (Pact5.PollRequest rksNel)) clientEnv + pollResult <- runClientM (pactPollApiClient v cid mConfirmationDepth (Pact5.PollRequest rksNel)) clientEnv case pollResult of Left e -> do throwM (PollException (show e)) diff --git a/test/unit/Chainweb/Test/Pact5/SPVTest.hs b/test/unit/Chainweb/Test/Pact/SPVTest.hs similarity index 91% rename from test/unit/Chainweb/Test/Pact5/SPVTest.hs rename to test/unit/Chainweb/Test/Pact/SPVTest.hs index c42a6bfbed..9281d0dd41 100644 --- a/test/unit/Chainweb/Test/Pact5/SPVTest.hs +++ b/test/unit/Chainweb/Test/Pact/SPVTest.hs @@ -19,7 +19,7 @@ {-# options_ghc -Wwarn #-} {-# options_ghc -w #-} -module Chainweb.Test.Pact5.SPVTest +module Chainweb.Test.Pact.SPVTest ( tests ) where @@ -48,7 +48,7 @@ import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse (ChainwebMerkleHashAlgorithm) import Chainweb.Miner.Pact import Chainweb.Miner.Pact (noMiner) -import Chainweb.Pact5.Backend.ChainwebPactDb (Pact5Db (doPact5DbTransaction)) +import Chainweb.Pact.Backend.ChainwebPactDb (Pact5Db (doPact5DbTransaction)) import Chainweb.Pact.Backend.SQLite.DirectV2 (close_v2) import Chainweb.Pact.Backend.Utils import Chainweb.Pact.PactService @@ -62,23 +62,23 @@ import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Pact.Types (defaultModuleCacheLimit, psBlockDbEnv, BlockInProgress (_blockInProgressTransactions)) import Chainweb.Pact.Utils (emptyPayload) -import Chainweb.Pact4.Transaction qualified as Pact4 -import Chainweb.Pact4.TransactionExec (applyGenesisCmd) -import Chainweb.Pact4.TransactionExec qualified -import Chainweb.Pact5.Transaction -import Chainweb.Pact5.Transaction qualified as Pact5 -import Chainweb.Pact5.TransactionExec -import Chainweb.Pact5.TransactionExec qualified -import Chainweb.Pact5.TransactionExec qualified as Pact5 -import Chainweb.Pact5.Types +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.TransactionExec (applyGenesisCmd) +import Chainweb.Pact.TransactionExec qualified +import Chainweb.Pact.Transaction +import Chainweb.Pact.Transaction qualified as Pact5 +import Chainweb.Pact.TransactionExec +import Chainweb.Pact.TransactionExec qualified +import Chainweb.Pact.TransactionExec qualified as Pact5 +import Chainweb.Pact.Types import Chainweb.Payload import Chainweb.Payload (PayloadWithOutputs_ (_payloadWithOutputsPayloadHash), Transaction (Transaction)) import Chainweb.Payload.PayloadStore import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb, _bdbWebBlockHeaderDb), addTestBlockDb, getCutTestBlockDb, getParentTestBlockDb, mkTestBlockDb, setCutTestBlockDb) import Chainweb.Test.Pact4.Utils (stdoutDummyLogger, testPactServiceConfig, withBlockHeaderDb) -import Chainweb.Test.Pact5.CmdBuilder -import Chainweb.Test.Pact5.Utils +import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions import Chainweb.Test.Utils import Chainweb.Time @@ -128,8 +128,8 @@ import GHC.Stack import Hedgehog hiding (Update) import Hedgehog.Gen qualified as Gen import Hedgehog.Range qualified as Range -import "pact" Pact.Types.Command qualified as Pact4 -import "pact" Pact.Types.Hash qualified as Pact4 +import "pact" Pact.Types.Command qualified as Pact +import "pact" Pact.Types.Hash qualified as Pact import Numeric.AffineSpace import Pact.Core.Builtin import Pact.Core.Capabilities @@ -156,7 +156,7 @@ import Pact.Core.SPV (noSPVSupport) import Pact.Core.Serialise import Pact.Core.StableEncoding (encodeStable) import Pact.Core.Verifiers -import Pact.Types.Gas qualified as Pact4 +import Pact.Types.Gas qualified as Pact import PropertyMatchers ((?)) import PropertyMatchers qualified as P import Streaming.Prelude qualified as Stream diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs new file mode 100644 index 0000000000..46609bb2f1 --- /dev/null +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -0,0 +1,995 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} + +module Chainweb.Test.Pact.TransactionExecTest (tests) where + +import Chainweb.BlockHeader +import Chainweb.Graph (singletonChainGraph, petersonChainGraph) +import Chainweb.Miner.Pact (Miner(..), MinerId(..), MinerGuard(..), noMiner) +import Chainweb.Pact.PactService (initialPayloadState, withPactService) +import Chainweb.Pact.PactService.Checkpointer (readFrom, mkFakeNewBlockCtx) +import Chainweb.Pact.Types +import Chainweb.Pact.Transaction +import Chainweb.Pact.TransactionExec +import Chainweb.Pact.Types +import Chainweb.Storage.Table.RocksDB +import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb, _bdbWebBlockHeaderDb), mkTestBlockDb) +import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Test.TestVersions +import Chainweb.Test.Utils +import Chainweb.Utils (T2(..)) +import Chainweb.Version +import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) +import Control.Lens hiding (only) +import Control.Monad.Except +import Control.Monad.IO.Class +import Control.Monad.Reader +import Control.Monad.Trans.Resource +import Data.Decimal +import Data.Functor.Product +import Data.HashMap.Strict qualified as HashMap +import Data.IORef +import Data.Maybe (fromMaybe) +import Data.Set qualified as Set +import Data.String (fromString) +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import Data.Text.IO qualified as T +import Chainweb.Test.Pact.Utils hiding (withTempSQLiteResource) +import GHC.Stack +import Pact.Core.Capabilities +import Pact.Core.Command.Types +import Pact.Core.Compile(CompileValue(..)) +import Pact.Core.Errors +import Pact.Core.Evaluate +import Pact.Core.Gas.TableGasModel +import Pact.Core.Gas.Types +import Pact.Core.Hash +import Pact.Core.Names +import Pact.Core.PactValue +import Pact.Core.Persistence hiding (pactDb) +import Pact.Core.SPV (noSPVSupport) +import Pact.Core.Signer +import Pact.Core.Verifiers +import Pact.Types.KeySet qualified as Pact +import Pact.JSON.Encode qualified as J +import PropertyMatchers ((?), pattern (:=>)) +import PropertyMatchers qualified as P +import Test.Tasty +import Test.Tasty.HUnit (assertEqual, testCase) +import Text.Printf +import Chainweb.Logger +import Chainweb.Pact.Backend.InMemDb qualified as InMemDb +import Chainweb.Pact.Backend.Types +import Control.Monad.State.Strict +import qualified Network.HTTP.Client as HTTP +import qualified Data.Pool as Pool +import Chainweb.Parent +import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as PIN0 + +tests :: RocksDb -> TestTree +tests baseRdb = testGroup "Pact5 TransactionExecTest" + [ testCase "buyGas should take gas tokens from the transaction sender" (buyGasShouldTakeGasTokensFromTheTransactionSender baseRdb) + -- , testCase "buyGas failures" (buyGasFailures baseRdb) + -- , testCase "redeem gas should give gas tokens to the transaction sender and miner" (redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner baseRdb) + -- , testCase "redeem gas failure" (redeemGasFailure baseRdb) + -- , testCase "purchase gas tx too big" (purchaseGasTxTooBig baseRdb) + -- , testCase "run payload should return an EvalResult related to the input command" (runPayloadShouldReturnEvalResultRelatedToTheInputCommand baseRdb) + -- , testCase "applyLocal spec" (applyLocalSpec baseRdb) + -- , testCase "applyCmd spec" (applyCmdSpec baseRdb) + -- , testCase "applyCmd verifier spec" (applyCmdVerifierSpec baseRdb) + -- , testCase "applyCmd failure spec" (applyCmdFailureSpec baseRdb) + -- , testCase "applyCmd coin.transfer" (applyCmdCoinTransfer baseRdb) + -- , testCase "applyCoinbase spec" (applyCoinbaseSpec baseRdb) + -- , testCase "test coin upgrade" (testCoinUpgrade baseRdb) + -- , testCase "test local only fails outside of local" (testLocalOnlyFailsOutsideOfLocal baseRdb) + -- , testCase "payload failure all gas should go to the miner - type error" (payloadFailureShouldPayAllGasToTheMinerTypeError baseRdb) + -- , testCase "payload failure all gas should go to the miner - insufficient funds" (payloadFailureShouldPayAllGasToTheMinerInsufficientFunds baseRdb) + -- , testCase "event spec" (testEvents baseRdb) + -- , testCase "writes from failed transaction should not make it into the db" (testWritesFromFailedTxDontMakeItIn baseRdb) + -- , testCase "quirk spec" (quirkSpec baseRdb) + -- , testCase "test writes to nonexistent tables" (testWritesToNonExistentTables baseRdb) + -- , testCase "test CommandResult 5 is valid for 4" (testCommandResult5To4 baseRdb) + -- , testCase "test hash-keccak256" (testKeccak256 baseRdb) + ] + +-- | Run with the context being that the parent block is the genesis block +-- This test suite is assumed to never need more blocks than that, because it's +-- focused on the transaction level. Tests that need to be aware of blocks, for +-- example to observe database writes, belong in a different suite, like +-- PactServiceTest or RemotePactTest. +readFromAfterGenesis :: ChainwebVersion -> RocksDb -> (BlockEnv -> BlockHandle -> IO a) -> IO a +readFromAfterGenesis ver rdb act = runResourceT $ do + sql <- withTempSQLiteResource + tdb <- mkTestBlockDb ver rdb + liftIO $ do + -- fake ro-sql pool, assuming we're using this single-threaded + roSqlPool <- Pool.newPool (Pool.defaultPoolConfig (return sql) (\_ -> return ()) 10 10) + logger <- testLogger + withPactService Nothing ver cid mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (testPactServiceConfig PIN0.payloadBlock) $ \serviceEnv -> do + initialPayloadState logger serviceEnv + fakeNewBlockCtx <- mkFakeNewBlockCtx + throwIfNoHistory =<< + readFrom logger ver cid sql fakeNewBlockCtx + (Parent (gh ver cid ^. rankedBlockHash)) + act + +buyGasShouldTakeGasTokensFromTheTransactionSender :: RocksDb -> IO () +buyGasShouldTakeGasTokensFromTheTransactionSender rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + assertEqual "starting balance" (Just 100_000_000) startSender00Bal + + cmd <- buildCwCmd v (defaultCmd cid) + { _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 200) + } + + -- let txCtx = TxContext { _tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner } + gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled + logger <- testLogger + _ <- buyGas logger gasEnv pactDb noMiner (_psBlockCtx blockEnv) (view payloadObj <$> cmd) + + endSender00Bal <- readBal pactDb "sender00" + assertEqual "balance after buying gas" (Just $ 100_000_000 - 200 * 2) endSender00Bal + +-- buyGasFailures :: RocksDb -> IO () +-- buyGasFailures rdb = readFromAfterGenesis v rdb $ do +-- pactTransaction Nothing $ \pactDb -> do +-- startSender00Bal <- readBal pactDb "sender00" +-- assertEqual "starting balance" (Just 100_000_000) startSender00Bal + +-- -- buying gas with insufficient balance to pay for the full supply +-- -- (gas price * gas limit) should return an error +-- do +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbGasPrice = GasPrice 70_000 +-- , _cbGasLimit = GasLimit (Gas 100_000) +-- } +-- let txCtx' = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled +-- logger <- testLogger +-- buyGas logger gasEnv pactDb txCtx' (view payloadObj <$> cmd) +-- >>= P.match (_Left . _BuyGasPactError . _PEUserRecoverableError) +-- ? P.fun (view _1) +-- ? P.equals (UserEnforceError "Insufficient funds") + +-- -- multiple gas payer caps should lead to an error, because it's unclear +-- -- which module will pay for gas +-- do +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbSigners = +-- [ mkEd25519Signer' sender00 +-- [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] +-- , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin" Nothing)) [] +-- , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin2" Nothing)) [] +-- ] +-- ] +-- } +-- let txCtx' = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled +-- logger <- testLogger +-- buyGas logger gasEnv pactDb txCtx' (view payloadObj <$> cmd) +-- >>= P.equals ? Left BuyGasMultipleGasPayerCaps + +-- redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner :: RocksDb -> IO () +-- redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner rdb = readFromAfterGenesis v rdb $ do +-- pactTransaction Nothing $ \pactDb -> do +-- startSender00Bal <- readBal pactDb "sender00" +-- assertEqual "starting balance" (Just 100_000_000) startSender00Bal +-- startMinerBal <- readBal pactDb "NoMiner" + +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 10) +-- } +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- -- redeeming gas with 3 gas used, with a limit of 10, should return 7 gas worth of tokens +-- -- to the gas payer + +-- -- TODO: should we be throwing some predicates at the redeem gas result? +-- logger <- testLogger +-- redeemGas logger pactDb txCtx (Gas 3) Nothing (view payloadObj <$> cmd) +-- >>= P.match _Right ? P.succeed +-- endSender00Bal <- readBal pactDb "sender00" +-- assertEqual "balance after redeeming gas" (Just $ 100_000_000 + (10 - 3) * 2) endSender00Bal +-- endMinerBal <- readBal pactDb "NoMiner" +-- assertEqual "miner balance after redeeming gas" (Just $ fromMaybe 0 startMinerBal + 3 * 2) endMinerBal + +-- redeemGasFailure :: RocksDb -> IO () +-- redeemGasFailure rdb = readFromAfterGenesis v rdb $ do +-- pactTransaction Nothing $ \pactDb -> do +-- let miner = Miner (MinerId "sender00") +-- $ MinerKeys +-- $ Pact4.mkKeySet +-- [Pact4.PublicKeyText $ fst sender00] +-- "keys-all" + +-- cmd <- buildCwCmd v +-- $ set cbRPC (mkExec ("(coin.rotate \"sender00\" (read-keyset 'ks))") (mkKeySetData "ks" [sender01])) +-- $ set cbSigners +-- [ mkEd25519Signer' sender00 +-- [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] +-- , CapToken (QualifiedName "ROTATE" (ModuleName "coin" Nothing)) [PString "sender00"] +-- ] +-- ] +-- $ defaultCmd cid +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = miner} +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Left +-- ? P.match (_RedeemGasError . _2 . _RedeemGasPactError) +-- ? P.match (_PEUserRecoverableError . _1) +-- ? P.equals (UserEnforceError "account guards do not match") + +-- purchaseGasTxTooBig :: RocksDb -> IO () +-- purchaseGasTxTooBig rdb = readFromAfterGenesis v rdb $ do +-- pactTransaction Nothing $ \pactDb -> do +-- cmd <- buildCwCmd v +-- $ set cbSender "sender00" +-- $ set cbSigners [mkEd25519Signer' sender00 []] +-- $ set cbGasLimit (GasLimit (Gas 1)) -- We set the gas limit to lower than the initialGas passed to applyCmd so that this test fails +-- $ defaultCmd cid +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 2) (view payloadObj <$> cmd) +-- >>= P.match _Left +-- ? P.match _PurchaseGasTxTooBigForGasLimit +-- ? P.succeed + +-- payloadFailureShouldPayAllGasToTheMinerTypeError :: RocksDb -> IO () +-- payloadFailureShouldPayAllGasToTheMinerTypeError rdb = readFromAfterGenesis v rdb $ do +-- pactTransaction Nothing $ \pactDb -> do +-- startSender00Bal <- readBal pactDb "sender00" +-- assertEqual "starting balance" (Just 100_000_000) startSender00Bal +-- startMinerBal <- readBal pactDb "NoMiner" + +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(+ 1 \"hello\")" +-- , _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- let gasToMiner = 2 * 1_000 -- gasPrice * gasLimit +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- [ P.fun _crResult +-- ? P.match (_PactResultErr . _PEExecutionError . _1) +-- ? P.match _NativeArgumentsError P.succeed +-- , P.fun _crEvents ? P.list +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal 2000.0]) +-- (P.equals coinModuleName) +-- ] +-- , P.fun _crGas ? P.equals ? Gas 1_000 +-- , P.fun _crLogs ? P.match _Just ? +-- P.list +-- [ P.checkAll +-- [ P.fun _txDomain ? P.equals "coin_coin-table" +-- , P.fun _txKey ? P.equals "sender00" +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals "coin_coin-table" +-- , P.fun _txKey ? P.equals "NoMiner" +-- ] +-- ] +-- ] +-- endSender00Bal <- readBal pactDb "sender00" +-- assertEqual "sender balance after payload failure" (fmap (subtract gasToMiner) startSender00Bal) endSender00Bal +-- endMinerBal <- readBal pactDb "NoMiner" +-- assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal + +-- payloadFailureShouldPayAllGasToTheMinerInsufficientFunds :: RocksDb -> IO () +-- payloadFailureShouldPayAllGasToTheMinerInsufficientFunds rdb = readFromAfterGenesis v rdb $ +-- pactTransaction Nothing $ \pactDb -> do +-- startSender00Bal <- readBal pactDb "sender00" +-- assertEqual "starting balance" (Just 100_000_000) startSender00Bal +-- startMinerBal <- readBal pactDb "NoMiner" + +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' $ fromString $ +-- "(coin.transfer \"sender00\" \"sender01\" " +-- <> printf "%.f" (realToFrac @_ @Double $ fromMaybe 0 startSender00Bal + 1) +-- <> ".0 )" +-- , _cbSigners = +-- [ mkEd25519Signer' sender00 +-- [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] +-- , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 1_000_000_000] +-- ] +-- ] +-- , _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- let gasToMiner = 2 * 1_000 -- gasPrice * gasLimit +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- [ P.fun _crResult +-- ? P.match (_PactResultErr . _PEUserRecoverableError . _1) +-- ? P.equals (UserEnforceError "Insufficient funds") +-- , P.fun _crEvents +-- ? P.list +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal 2000.0]) +-- (P.equals coinModuleName) +-- ] +-- , P.fun _crGas ? P.equals ? Gas 1_000 +-- , P.fun _crLogs ? P.match _Just ? +-- P.list +-- [ P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "sender00" +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "NoMiner" +-- ] +-- ] +-- ] +-- endSender00Bal <- readBal pactDb "sender00" +-- assertEqual "sender balance after payload failure" (fmap (subtract gasToMiner) startSender00Bal) endSender00Bal +-- endMinerBal <- readBal pactDb "NoMiner" +-- assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal + +-- runPayloadShouldReturnEvalResultRelatedToTheInputCommand :: RocksDb -> IO () +-- runPayloadShouldReturnEvalResultRelatedToTheInputCommand rdb = readFromAfterGenesis v rdb $ +-- pactTransaction Nothing $ \pactDb -> do +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" +-- } +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- gasEnv <- mkTableGasEnv (MilliGasLimit (gasToMilliGas $ Gas 10)) GasLogsEnabled +-- logger <- testLogger +-- payloadResult <- runExceptT $ +-- runReaderT +-- (runTransactionM +-- (runPayload Transactional Set.empty pactDb noSPVSupport [] managedNamespacePolicy gasEnv txCtx (TxBlockIdx 0) (view payloadObj <$> cmd))) +-- (TransactionEnv logger gasEnv) +-- gasUsed <- readIORef (_geGasRef gasEnv) + +-- assertEqual "runPayload gas used" (MilliGas 1_750) gasUsed + +-- pure payloadResult >>= P.match _Right ? P.checkAll +-- [ P.fun _erOutput ? P.equals [InterpretValue (PInteger 15) noInfo] +-- , P.fun _erEvents ? P.equals [] +-- , P.fun _erLogs ? P.equals [] +-- , P.fun _erExec ? P.equals Nothing +-- , P.fun _erGas ? P.equals ? Gas 2 +-- , P.fun _erLoadedModules ? P.equals mempty +-- , P.fun _erTxId ? P.equals ? Just (TxId 9) +-- -- TODO: test _erLogGas? +-- ] + +-- -- applyLocal should mostly be the same as applyCmd, this is mostly a smoke test +-- applyLocalSpec :: RocksDb -> IO () +-- applyLocalSpec rdb = readFromAfterGenesis v rdb $ +-- pactTransaction Nothing $ \pactDb -> do +-- startSender00Bal <- readBal pactDb "sender00" +-- assertEqual "starting balance" (Just 100_000_000) startSender00Bal +-- startMinerBal <- readBal pactDb "NoMiner" + +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" +-- , _cbSigners = [] +-- } +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) +-- >>= P.checkAll +-- -- Local has no buy gas, therefore +-- -- no gas buy event +-- [ P.fun _crEvents ? P.equals ? [] +-- , P.fun _crResult ? P.equals ? PactResultOk (PInteger 15) +-- -- reflects payload gas usage +-- , P.fun _crGas ? P.equals ? Gas 2 +-- , P.fun _crContinuation ? P.equals ? Nothing +-- , P.fun _crLogs ? P.equals ? Just [] +-- , P.fun _crMetaData ? P.match _Just P.succeed +-- ] + +-- endSender00Bal <- readBal pactDb "sender00" +-- assertEqual "ending balance should be equal" startSender00Bal endSender00Bal +-- endMinerBal <- readBal pactDb "NoMiner" +-- assertEqual "miner balance after redeeming gas should have increased" startMinerBal endMinerBal + +-- applyCmdSpec :: RocksDb -> IO () +-- applyCmdSpec rdb = readFromAfterGenesis v rdb $ do +-- pactTransaction Nothing $ \pactDb -> do +-- startSender00Bal <- readBal pactDb "sender00" +-- let expectedStartingBal = 100_000_000 +-- assertEqual "starting balance" (Just expectedStartingBal) startSender00Bal +-- startMinerBal <- readBal pactDb "NoMiner" + +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" +-- , _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 500) +-- -- no caps should be equivalent to the GAS cap +-- , _cbSigners = [mkEd25519Signer' sender00 []] +-- } +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- let expectedGasConsumed = 73 +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- -- only the event reflecting the final transfer to the miner for gas used +-- [ P.fun _crEvents ? P.list +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal 146.0]) +-- (P.equals coinModuleName) +-- ] +-- , P.fun _crResult ? P.equals ? PactResultOk (PInteger 15) +-- -- reflects buyGas gas usage, as well as that of the payload +-- , P.fun _crGas ? P.equals ? Gas expectedGasConsumed +-- , P.fun _crContinuation ? P.equals ? Nothing +-- , P.fun _crLogs ? P.match _Just ? +-- P.list +-- [ P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "sender00" +-- -- TODO: test the values here? +-- -- here, we're only testing that the write pattern matches +-- -- gas buy and redeem, not the contents of the writes. +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "sender00" +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "NoMiner" +-- ] +-- ] +-- ] + +-- endSender00Bal <- readBal pactDb "sender00" +-- assertEqual "ending balance should be less gas money" (Just (expectedStartingBal - fromIntegral expectedGasConsumed * 2)) endSender00Bal +-- endMinerBal <- readBal pactDb "NoMiner" +-- assertEqual "miner balance after redeeming gas should have increased" +-- (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed) * 2) +-- endMinerBal + +-- -- test cache contents +-- use pbBlockHandle >>= \bh -> liftIO $ bh +-- & P.fun _blockHandlePending +-- ? P.fun _pendingWrites +-- ? P.checkAll +-- [ P.fun InMemDb.userTables +-- ? P.alignExact ? HashMap.fromList +-- [ TableName "coin-table" (ModuleName "coin" Nothing) :=> +-- P.alignExact ? HashMap.fromList +-- [ RowKey "NoMiner" :=> +-- P.match InMemDb._WriteEntry P.succeed +-- , RowKey "sender00" :=> +-- P.match InMemDb._WriteEntry P.succeed +-- ] +-- ] + +-- , P.fun InMemDb.modules +-- ? P.alignExact ? HashMap.fromList +-- [ ModuleName "coin" Nothing :=> +-- P.match InMemDb._ReadEntry P.succeed +-- ] +-- ] + +-- quirkSpec :: RocksDb -> IO () +-- quirkSpec rdb = readFromAfterGenesis quirkVer rdb $ +-- pactTransaction Nothing $ \pactDb -> do +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(* 1000 200000)" +-- , _cbSigners = [mkEd25519Signer' sender00 []] +-- , _cbSender = "sender00" +-- , _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 70_000) +-- } +-- let txCtx = TxContext +-- { _tcParentHeader = ParentHeader (gh quirkVer cid) +-- , _tcMiner = noMiner +-- } +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- -- gas buy event +-- [ P.fun _crEvents ? P.list +-- [ event +-- (P.equals "TRANSFER") +-- -- gas 1, gas price 2 +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal (1 * 2)]) +-- (P.equals coinModuleName) +-- ] +-- , P.fun _crResult ? P.equals ? PactResultOk (PInteger (1000 * 200000)) +-- -- quirked gas +-- , P.fun _crGas ? P.equals ? Gas 1 +-- ] +-- where +-- quirkVer = quirkedGasPact5InstantCpmTestVersion peterson + +-- applyCmdVerifierSpec :: RocksDb -> IO () +-- applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ +-- pactTransaction Nothing $ \pactDb -> do +-- -- Define module with capability +-- do +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' $ T.unlines +-- [ "(namespace 'free)" +-- , "(module m G" +-- , " (defcap G () (enforce-verifier 'allow))" +-- , " (defun x () (with-capability (G) 1))" +-- , ")" +-- ] +-- , _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 70_000) +-- } +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- -- gas buy event +-- [ P.fun _crEvents ? P.list +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal 570]) +-- (P.equals coinModuleName) +-- ] +-- , P.fun _crResult ? P.equals ? PactResultOk (PString "Loaded module free.m, hash Uj0lQPPu9CKvw13K4VP4DZoaPKOphk_-vuq823hLSLo") +-- -- reflects buyGas gas usage, as well as that of the payload +-- , P.fun _crGas ? P.equals ? Gas 285 +-- , P.fun _crContinuation ? P.equals ? Nothing +-- ] + +-- let baseCmd = (defaultCmd cid) +-- { _cbRPC = mkExec' "(free.m.x)" +-- , _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 300) +-- } + +-- -- Invoke module when verifier capability isn't present. Should fail. +-- do +-- cmd <- buildCwCmd v baseCmd +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- -- gas buy event +-- [ P.fun _crResult +-- ? P.match (_PactResultErr . _PEUserRecoverableError . _1) +-- ? P.equals ? VerifierFailure (VerifierName "allow") "not in transaction" +-- , P.fun _crEvents ? P.list +-- [ P.checkAll +-- [ P.fun _peName ? P.equals ? "TRANSFER" +-- , P.fun _peArgs ? P.equals ? [PString "sender00", PString "NoMiner", PDecimal 600] +-- , P.fun _peModule ? P.equals ? ModuleName "coin" Nothing +-- ] +-- ] +-- -- reflects buyGas gas usage, as well as that of the payload +-- , P.fun _crGas ? P.equals ? Gas 300 +-- , P.fun _crContinuation ? P.equals ? Nothing +-- ] + +-- -- Invoke module when verifier capability is present. Should succeed. +-- do +-- let cap :: SigCapability +-- cap = SigCapability $ CapToken (QualifiedName "G" (ModuleName "m" (Just (NamespaceName "free")))) [] +-- cmd <- buildCwCmd v baseCmd +-- { _cbVerifiers = +-- [ Verifier +-- { _verifierName = VerifierName "allow" +-- , _verifierProof = ParsedVerifierProof $ PString $ T.decodeUtf8 $ J.encodeStrict cap +-- , _verifierCaps = [cap] +-- } +-- ] +-- } +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- -- gas buy event +-- [ P.fun _crEvents ? P.list +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal 362]) +-- (P.equals coinModuleName) +-- ] +-- , P.fun _crResult ? P.equals ? PactResultOk (PInteger 1) +-- -- reflects buyGas gas usage, as well as that of the payload +-- , P.fun _crGas ? P.equals ? Gas 181 +-- , P.fun _crContinuation ? P.equals ? Nothing +-- , P.fun _crMetaData ? P.equals ? Nothing +-- ] + +-- applyCmdFailureSpec :: RocksDb -> IO () +-- applyCmdFailureSpec rdb = readFromAfterGenesis v rdb $ +-- pactTransaction Nothing $ \pactDb -> do +-- startSender00Bal <- readBal pactDb "sender00" +-- assertEqual "starting balance" (Just 100_000_000) startSender00Bal +-- startMinerBal <- readBal pactDb "NoMiner" + +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(+ 1 \"abc\")" +-- , _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 500) +-- } +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- let expectedGasConsumed = 500 +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- -- gas buy event + +-- [ P.fun _crEvents +-- ? P.list +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal 1000]) +-- (P.equals coinModuleName) +-- ] +-- -- tx errored +-- , P.fun _crResult ? P.match _PactResultErr P.succeed +-- -- reflects buyGas gas usage, as well as that of the payload +-- , P.fun _crGas ? P.equals ? Gas expectedGasConsumed +-- , P.fun _crContinuation ? P.equals ? Nothing +-- , P.fun _crLogs ? P.match _Just ? +-- P.list +-- [ P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "sender00" +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "NoMiner" +-- ] +-- ] +-- ] + +-- endSender00Bal <- readBal pactDb "sender00" +-- assertEqual "ending balance should be less gas money" (Just 99_999_000) endSender00Bal +-- endMinerBal <- readBal pactDb "NoMiner" +-- assertEqual "miner balance after redeeming gas should have increased" +-- (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed) * 2) +-- endMinerBal + +-- applyCmdCoinTransfer :: RocksDb -> IO () +-- applyCmdCoinTransfer rdb = readFromAfterGenesis v rdb $ do +-- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner +-- pactTransaction Nothing $ \pactDb -> do +-- startSender00Bal <- readBal pactDb "sender00" +-- assertEqual "starting balance" (Just 100_000_000) startSender00Bal +-- startMinerBal <- readBal pactDb "NoMiner" + +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0)" +-- , _cbSigners = +-- [ mkEd25519Signer' sender00 +-- [ CapToken (QualifiedName "GAS" coinModuleName) [] +-- , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 420] ] +-- ] +-- , _cbGasPrice = GasPrice 0.1 +-- , _cbGasLimit = GasLimit (Gas 1_000) +-- } +-- -- Note: if/when core changes gas prices, tweak here. +-- let expectedGasConsumed = 227 +-- logger <- testLogger +-- e <- applyCmd logger (Just logger) pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- e & P.match _Right +-- ? P.checkAll +-- [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") +-- , P.fun _crEvents ? P.list +-- -- transfer event and gas redeem event +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "sender01", PDecimal 420]) +-- (P.equals coinModuleName) +-- , event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal 22.7]) +-- (P.equals coinModuleName) +-- ] +-- -- reflects buyGas gas usage, as well as that of the payload +-- , P.fun _crGas ? P.equals ? Gas expectedGasConsumed +-- , P.fun _crContinuation ? P.equals ? Nothing +-- , P.fun _crLogs ? P.match _Just ? +-- P.list +-- [ P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "sender00" +-- -- TODO: test the values here? +-- -- here, we're only testing that the write pattern matches +-- -- gas buy and redeem, not the contents of the writes. +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "sender00" +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "sender01" +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "sender00" +-- ] +-- , P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "NoMiner" +-- ] +-- ] +-- ] + +-- endSender00Bal <- readBal pactDb "sender00" +-- assertEqual "ending balance should be less gas money" (Just 99_999_557.3) endSender00Bal +-- endMinerBal <- readBal pactDb "NoMiner" +-- assertEqual "miner balance after redeeming gas should have increased" +-- (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed * 0.1)) +-- endMinerBal + +-- applyCoinbaseSpec :: RocksDb -> IO () +-- applyCoinbaseSpec rdb = readFromAfterGenesis v rdb $ +-- pactTransaction Nothing $ \pactDb -> do +-- startMinerBal <- readBal pactDb "NoMiner" + +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- applyCoinbase logger pactDb 5 txCtx +-- >>= P.match _Right +-- ? P.checkAll +-- [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") +-- , P.fun _crGas ? P.equals ? Gas 0 +-- , P.fun _crLogs ? P.match _Just ? P.list +-- [ P.checkAll +-- [ P.fun _txDomain ? P.equals ? "coin_coin-table" +-- , P.fun _txKey ? P.equals ? "NoMiner" +-- ] +-- ] +-- , P.fun _crEvents ? P.list +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "", PString "NoMiner", PDecimal 5.0]) +-- (P.equals coinModuleName) +-- ] +-- ] +-- endMinerBal <- readBal pactDb "NoMiner" +-- assertEqual "miner balance should include block reward" +-- (Just $ fromMaybe 0 startMinerBal + 5) +-- endMinerBal + +-- testCoinUpgrade :: RocksDb -> IO () +-- testCoinUpgrade rdb = readFromAfterGenesis vUpgrades rdb $ do +-- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner +-- pactTransaction Nothing $ \pactDb -> do + +-- logger <- testLogger +-- getCoinModuleHash logger txCtx pactDb +-- >>= P.equals ? PactResultOk (PString "wOTjNC3gtOAjqgCY8S9hQ-LBiwcPUE7j4iBDE0TmdJo") + +-- applyUpgrades logger pactDb txCtx + +-- getCoinModuleHash logger txCtx pactDb +-- >>= P.equals ? PactResultOk (PString "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE") +-- where +-- getCoinModuleHash logger txCtx pactDb = do +-- cmd <- buildCwCmd vUpgrades (defaultCmd cid) +-- { _cbRPC = mkExec' "(at 'hash (describe-module 'coin))" +-- } +-- _crResult <$> applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) + + +-- testEvents :: RocksDb -> IO () +-- testEvents rdb = readFromAfterGenesis v rdb $ +-- pactTransaction Nothing $ \pactDb -> do +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0) (coin.transfer 'sender00 'sender01 69.0)" +-- , _cbSigners = +-- [ mkEd25519Signer' sender00 +-- [ CapToken (QualifiedName "GAS" coinModuleName) [] +-- , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 489] +-- ] +-- ] +-- , _cbGasPrice = GasPrice 2 +-- , _cbGasLimit = GasLimit (Gas 1100) +-- } +-- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} +-- logger <- testLogger +-- e <- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + +-- e & P.match _Right +-- ? P.checkAll +-- [ P.fun _crEvents ? P.list +-- [ P.checkAll +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "sender01", PDecimal 420]) +-- (P.equals coinModuleName) +-- , P.fun _peModuleHash ? P.fun moduleHashToText +-- ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" +-- ] +-- , P.checkAll +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "sender01", PDecimal 69]) +-- (P.equals coinModuleName) +-- , P.fun _peModuleHash ? P.fun moduleHashToText +-- ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" +-- ] +-- , P.checkAll +-- [ event +-- (P.equals "TRANSFER") +-- (P.equals [PString "sender00", PString "NoMiner", PDecimal 766]) +-- (P.equals coinModuleName) +-- , P.fun _peModuleHash ? P.fun moduleHashToText +-- ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" +-- ] +-- ] +-- ] + +-- testLocalOnlyFailsOutsideOfLocal :: RocksDb -> IO () +-- testLocalOnlyFailsOutsideOfLocal rdb = readFromAfterGenesis v rdb $ do +-- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner +-- pactTransaction Nothing $ \pactDb -> do +-- let testLocalOnly txt = do +-- cmd <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' txt +-- } + +-- logger <- testLogger +-- -- should succeed in local +-- applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) +-- >>= P.fun _crResult (P.match _PactResultOk P.succeed) + +-- -- should fail in non-local +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.fun _crResult +-- ? P.match (_PactResultErr . _PEExecutionError . _1 . _OperationIsLocalOnly) P.succeed + +-- testLocalOnly "(describe-module \"coin\")" + +-- testWritesFromFailedTxDontMakeItIn :: RocksDb -> IO () +-- testWritesFromFailedTxDontMakeItIn rdb = readFromAfterGenesis v rdb $ do +-- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner +-- pactTransaction Nothing $ \pactDb -> do + +-- moduleDeploy <- buildCwCmd v (defaultCmd cid) +-- { _cbRPC = mkExec' "(module m g (defcap g () (enforce false \"non-upgradeable\"))) (enforce false \"boom\")" +-- , _cbGasLimit = GasLimit (Gas 200_000) +-- , _cbSigners = [mkEd25519Signer' sender00 []] +-- } + +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> moduleDeploy) +-- >>= P.match _Right +-- ? P.fun _crResult +-- ? P.match _PactResultErr P.succeed + +-- -- Assert that the writes from the failed transaction didn't make it into the db +-- -- but there is some write to the coin contract +-- use pbBlockHandle +-- >>= (liftIO .) +-- ? P.fun _blockHandlePending +-- ? P.fun _pendingWrites +-- ? P.checkAll +-- [ P.fun InMemDb.modules +-- ? traverseOf_ (traversed . InMemDb._WriteEntry) +-- ? P.fail "no writes made to module table" +-- , P.fun InMemDb.userTables +-- ? P.match (ix (TableName "coin-table" (ModuleName "coin" Nothing))) +-- ? P.alignExact ? HashMap.fromList +-- [ RowKey "NoMiner" :=> P.match InMemDb._WriteEntry P.succeed +-- , RowKey "sender00" :=> P.match InMemDb._WriteEntry P.succeed +-- ] +-- ] + +-- testWritesToNonExistentTables :: RocksDb -> IO () +-- testWritesToNonExistentTables rdb = readFromAfterGenesis v rdb $ do +-- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner +-- pactTransaction Nothing $ \pactDb -> do +-- cmd <- buildCwCmd v +-- $ set cbRPC (mkExec' $ T.concat +-- [ "(namespace 'free)" +-- , "(module m G" +-- , "(defcap G () true)" +-- , "(defschema o i:integer)" +-- , "(deftable t:{o})" +-- , ")" +-- , "(insert t 'k {'i: 2})" +-- ] +-- ) +-- $ defaultCmd cid + +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.fun _crResult +-- ? P.match (_PactResultErr . _PEExecutionError . _1) +-- ? P.equals (DbOpFailure (NoSuchTable (TableName "t" (ModuleName "m" (Just "free"))))) + +-- testKeccak256 :: RocksDb -> IO () +-- testKeccak256 rdb = readFromAfterGenesis v rdb $ do +-- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner +-- pactTransaction Nothing $ \pactDb -> do +-- cmd <- buildCwCmd v +-- $ set cbRPC (mkExec' "(hash-keccak256 [\"T73FllCNJKKgAQ4UCYC4CfucbVXsdRJYkd2YXTdmW9gPm-tqUCB1iKvzzu6Md82KWtSKngqgdO04hzg2JJbS-yyHVDuzNJ6mSZfOPntCTqktEi9X27CFWoAwWEN_4Ir7DItecXm5BEu_TYGnFjsxOeMIiLU2sPlX7_macWL0ylqnVqSpgt-tvzHvJVCDxLXGwbmaEH19Ov_9uJFHwsxMmiZD9Hjl4tOTrqN7THy0tel9rc8WtrUKrg87VJ7OR3Rtts5vZ91EBs1OdVldUQPRP536eTcpJNMo-N0fy-taji6L9Mdt4I4_xGqgIfmJxJMpx6ysWmiFVte8vLKl1L5p0yhOnEDsSDjuhZISDOIKC2NeytqoT9VpBQn1T3fjWkF8WEZIvJg5uXTge_qwA46QKV0LE5AlMKgw0cK91T8fnJ-u1Dyk7tCo3XYbx-292iiih8YM1Cr1-cdY5cclAjHAmlglY2ia_GXit5p6K2ggBmd1LpEBdG8DGE4jmeTtiDXLjprpDilq8iCuI0JZ_gvQvMYPekpf8_cMXtTenIxRmhDpYvZzyCxek1F4aoo7_VcAMYV71Mh_T8ox7U1Q4U8hB9oCy1BYcAt06iQai0HXhGFljxsrkL_YSkwsnWVDhhqzxWRRdX3PubpgMzSI290C1gG0Gq4xfKdHTrbm3Q\"])") +-- $ defaultCmd cid + +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.match _Right +-- ? P.checkAll +-- [ P.fun _crResult ? P.equals ? PactResultOk (PString "DqM-LjT1ckQGQCRMfx9fBGl86XE5vacqZVjYZjwCs4g") +-- , P.fun _crGas ? P.equals ? Gas 75 +-- ] + +-- testCommandResult5To4 :: RocksDb -> IO () +-- testCommandResult5To4 rdb = readFromAfterGenesis v rdb $ do +-- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner +-- pactTransaction Nothing $ \pactDb -> do +-- cmd <- buildCwCmd v +-- $ set cbRPC (mkExec' "(+ 1 'hello)") +-- $ defaultCmd cid + +-- logger <- testLogger +-- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) +-- >>= P.checkAll +-- [ P.match _Right +-- ? P.fun _crResult +-- ? P.match _PactResultErr +-- ? P.succeed +-- , P.match _Right +-- ? P.fun (fmap pactErrorToOnChainError) +-- ? P.fun hashPact5TxLogs +-- ? P.fun toPact4CommandResult +-- ? P.forced +-- ] + +cid :: ChainId +cid = unsafeChainId 0 + +gh :: ChainwebVersion -> ChainId -> BlockHeader +gh = genesisBlockHeader + +-- vUpgrades :: ChainwebVersion +-- vUpgrades = slowCpmTestVersion singletonChainGraph + +v :: ChainwebVersion +v = instantCpmTestVersion petersonChainGraph + +-- | this utility for reading balances from the pactdb also takes care of +-- making a transaction for the read to live in +readBal :: (HasCallStack) => PactDb b Info -> T.Text -> IO (Maybe Decimal) +readBal pactDb acctName = do + _ <- ignoreGas noInfo $ _pdbBeginTx pactDb Transactional + acct <- ignoreGas noInfo $ _pdbRead pactDb + (DUserTables (TableName "coin-table" (ModuleName "coin" Nothing))) + (RowKey acctName) + _ <- ignoreGas noInfo $ _pdbCommitTx pactDb + return $! acct ^? _Just . ix "balance" . _PDecimal + +testLogger :: IO GenericLogger +testLogger = do + logLevel <- liftIO getTestLogLevel + pure $ genericLogger logLevel T.putStrLn diff --git a/test/unit/Chainweb/Test/Pact5/TransactionTests.hs b/test/unit/Chainweb/Test/Pact/TransactionTests.hs similarity index 93% rename from test/unit/Chainweb/Test/Pact5/TransactionTests.hs rename to test/unit/Chainweb/Test/Pact/TransactionTests.hs index aee393c044..572bc62109 100644 --- a/test/unit/Chainweb/Test/Pact5/TransactionTests.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionTests.hs @@ -6,9 +6,9 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} -module Chainweb.Test.Pact5.TransactionTests (tests) where +module Chainweb.Test.Pact.TransactionTests (tests) where -import Chainweb.Pact5.Templates +import Chainweb.Pact.Templates import Chainweb.Miner.Pact import Control.Lens hiding ((.=)) import Data.Foldable @@ -21,7 +21,7 @@ import Pact.Core.Repl import Pact.Core.Repl.Utils import Control.Monad (when) import Data.Text qualified as Text -import Pact.Types.KeySet qualified as Pact4 +import Pact.Types.KeySet qualified as Pact import Test.Tasty import Test.Tasty.HUnit import Data.Map.Strict qualified as Map @@ -30,7 +30,7 @@ import Data.Map.Strict qualified as Map -- Global settings tests :: TestTree -tests = testGroup "Chainweb.Test.Pact5.TransactionTests" +tests = testGroup "Chainweb.Test.Pact.TransactionTests" [ testCase "coin contract v6" $ runReplTest coinReplV6 , testCase "namespace v1" $ runReplTest nsReplV1 , testCase "namespace v2" $ runReplTest nsReplV2 diff --git a/test/unit/Chainweb/Test/Pact5/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact5/TransactionExecTest.hs deleted file mode 100644 index 80c1596a35..0000000000 --- a/test/unit/Chainweb/Test/Pact5/TransactionExecTest.hs +++ /dev/null @@ -1,989 +0,0 @@ -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE QuantifiedConstraints #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE UndecidableInstances #-} - -module Chainweb.Test.Pact5.TransactionExecTest (tests) where - -import Chainweb.BlockHeader -import Chainweb.Graph (singletonChainGraph, petersonChainGraph) -import Chainweb.Miner.Pact (Miner(..), MinerId(..), MinerKeys(..), noMiner) -import Chainweb.Pact.PactService (initialPayloadState, withPactService) -import Chainweb.Pact.PactService.Checkpointer (readFrom, SomeBlockM(..)) -import Chainweb.Pact.Types -import Chainweb.Pact5.Transaction -import Chainweb.Pact5.TransactionExec -import Chainweb.Pact5.Types -import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb, _bdbWebBlockHeaderDb), mkTestBlockDb) -import Chainweb.Test.Pact5.CmdBuilder -import Chainweb.Test.TestVersions -import Chainweb.Test.Utils -import Chainweb.Utils (T2(..)) -import Chainweb.Version -import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) -import Control.Lens hiding (only) -import Control.Monad.Except -import Control.Monad.IO.Class -import Control.Monad.Reader -import Control.Monad.Trans.Resource -import Data.Decimal -import Data.Functor.Product -import Data.HashMap.Strict qualified as HashMap -import Data.IORef -import Data.Maybe (fromMaybe) -import Data.Set qualified as Set -import Data.String (fromString) -import Data.Text qualified as T -import Data.Text.Encoding qualified as T -import Data.Text.IO qualified as T -import Chainweb.Test.Pact5.Utils hiding (withTempSQLiteResource) -import GHC.Stack -import Pact.Core.Capabilities -import Pact.Core.Command.Types -import Pact.Core.Compile(CompileValue(..)) -import Pact.Core.Errors -import Pact.Core.Evaluate -import Pact.Core.Gas.TableGasModel -import Pact.Core.Gas.Types -import Pact.Core.Hash -import Pact.Core.Names -import Pact.Core.PactValue -import Pact.Core.Persistence hiding (pactDb) -import Pact.Core.SPV (noSPVSupport) -import Pact.Core.Signer -import Pact.Core.Verifiers -import Pact.Types.KeySet qualified as Pact4 -import Pact.JSON.Encode qualified as J -import PropertyMatchers ((?), pattern (:=>)) -import PropertyMatchers qualified as P -import Test.Tasty -import Test.Tasty.HUnit (assertEqual, testCase) -import Text.Printf -import Chainweb.Logger -import Chainweb.Pact.Backend.InMemDb qualified as InMemDb -import Chainweb.Pact.Backend.Types - -tests :: RocksDb -> TestTree -tests baseRdb = testGroup "Pact5 TransactionExecTest" - [ testCase "buyGas should take gas tokens from the transaction sender" (buyGasShouldTakeGasTokensFromTheTransactionSender baseRdb) - , testCase "buyGas failures" (buyGasFailures baseRdb) - , testCase "redeem gas should give gas tokens to the transaction sender and miner" (redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner baseRdb) - , testCase "redeem gas failure" (redeemGasFailure baseRdb) - , testCase "purchase gas tx too big" (purchaseGasTxTooBig baseRdb) - , testCase "run payload should return an EvalResult related to the input command" (runPayloadShouldReturnEvalResultRelatedToTheInputCommand baseRdb) - , testCase "applyLocal spec" (applyLocalSpec baseRdb) - , testCase "applyCmd spec" (applyCmdSpec baseRdb) - , testCase "applyCmd verifier spec" (applyCmdVerifierSpec baseRdb) - , testCase "applyCmd failure spec" (applyCmdFailureSpec baseRdb) - , testCase "applyCmd coin.transfer" (applyCmdCoinTransfer baseRdb) - , testCase "applyCoinbase spec" (applyCoinbaseSpec baseRdb) - , testCase "test coin upgrade" (testCoinUpgrade baseRdb) - , testCase "test local only fails outside of local" (testLocalOnlyFailsOutsideOfLocal baseRdb) - , testCase "payload failure all gas should go to the miner - type error" (payloadFailureShouldPayAllGasToTheMinerTypeError baseRdb) - , testCase "payload failure all gas should go to the miner - insufficient funds" (payloadFailureShouldPayAllGasToTheMinerInsufficientFunds baseRdb) - , testCase "event spec" (testEvents baseRdb) - , testCase "writes from failed transaction should not make it into the db" (testWritesFromFailedTxDontMakeItIn baseRdb) - , testCase "quirk spec" (quirkSpec baseRdb) - , testCase "test writes to nonexistent tables" (testWritesToNonExistentTables baseRdb) - , testCase "test CommandResult 5 is valid for 4" (testCommandResult5To4 baseRdb) - , testCase "test hash-keccak256" (testKeccak256 baseRdb) - ] - --- | Run with the context being that the parent block is the genesis block --- This test suite is assumed to never need more blocks than that, because it's --- focused on the transaction level. Tests that need to be aware of blocks, for --- example to observe database writes, belong in a different suite, like --- PactServiceTest or RemotePactTest. -readFromAfterGenesis :: ChainwebVersion -> RocksDb -> PactBlockM GenericLogger RocksDbTable a -> IO a -readFromAfterGenesis ver rdb act = runResourceT $ do - sql <- withTempSQLiteResource - tdb <- mkTestBlockDb ver rdb - liftIO $ do - bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb tdb) cid - logger <- testLogger - T2 a _finalPactState <- withPactService ver cid logger Nothing bhdb (_bdbPayloadDb tdb) sql testPactServiceConfig $ do - initialPayloadState ver cid - throwIfNoHistory =<< - readFrom - (Just $ ParentHeader (gh ver cid)) - (SomeBlockM $ Pair (error "Pact4") act) - return a - -buyGasShouldTakeGasTokensFromTheTransactionSender :: RocksDb -> IO () -buyGasShouldTakeGasTokensFromTheTransactionSender rdb = readFromAfterGenesis v rdb $ - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - assertEqual "starting balance" (Just 100_000_000) startSender00Bal - - cmd <- buildCwCmd v (defaultCmd cid) - { _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 200) - } - - let txCtx = TxContext { _tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner } - gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled - logger <- testLogger - _ <- buyGas logger gasEnv pactDb txCtx (view payloadObj <$> cmd) - - endSender00Bal <- readBal pactDb "sender00" - assertEqual "balance after buying gas" (Just $ 100_000_000 - 200 * 2) endSender00Bal - -buyGasFailures :: RocksDb -> IO () -buyGasFailures rdb = readFromAfterGenesis v rdb $ do - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - assertEqual "starting balance" (Just 100_000_000) startSender00Bal - - -- buying gas with insufficient balance to pay for the full supply - -- (gas price * gas limit) should return an error - do - cmd <- buildCwCmd v (defaultCmd cid) - { _cbGasPrice = GasPrice 70_000 - , _cbGasLimit = GasLimit (Gas 100_000) - } - let txCtx' = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled - logger <- testLogger - buyGas logger gasEnv pactDb txCtx' (view payloadObj <$> cmd) - >>= P.match (_Left . _BuyGasPactError . _PEUserRecoverableError) - ? P.fun (view _1) - ? P.equals (UserEnforceError "Insufficient funds") - - -- multiple gas payer caps should lead to an error, because it's unclear - -- which module will pay for gas - do - cmd <- buildCwCmd v (defaultCmd cid) - { _cbSigners = - [ mkEd25519Signer' sender00 - [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] - , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin" Nothing)) [] - , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin2" Nothing)) [] - ] - ] - } - let txCtx' = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled - logger <- testLogger - buyGas logger gasEnv pactDb txCtx' (view payloadObj <$> cmd) - >>= P.equals ? Left BuyGasMultipleGasPayerCaps - -redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner :: RocksDb -> IO () -redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner rdb = readFromAfterGenesis v rdb $ do - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - assertEqual "starting balance" (Just 100_000_000) startSender00Bal - startMinerBal <- readBal pactDb "NoMiner" - - cmd <- buildCwCmd v (defaultCmd cid) - { _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 10) - } - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - -- redeeming gas with 3 gas used, with a limit of 10, should return 7 gas worth of tokens - -- to the gas payer - - -- TODO: should we be throwing some predicates at the redeem gas result? - logger <- testLogger - redeemGas logger pactDb txCtx (Gas 3) Nothing (view payloadObj <$> cmd) - >>= P.match _Right ? P.succeed - endSender00Bal <- readBal pactDb "sender00" - assertEqual "balance after redeeming gas" (Just $ 100_000_000 + (10 - 3) * 2) endSender00Bal - endMinerBal <- readBal pactDb "NoMiner" - assertEqual "miner balance after redeeming gas" (Just $ fromMaybe 0 startMinerBal + 3 * 2) endMinerBal - -redeemGasFailure :: RocksDb -> IO () -redeemGasFailure rdb = readFromAfterGenesis v rdb $ do - pactTransaction Nothing $ \pactDb -> do - let miner = Miner (MinerId "sender00") - $ MinerKeys - $ Pact4.mkKeySet - [Pact4.PublicKeyText $ fst sender00] - "keys-all" - - cmd <- buildCwCmd v - $ set cbRPC (mkExec ("(coin.rotate \"sender00\" (read-keyset 'ks))") (mkKeySetData "ks" [sender01])) - $ set cbSigners - [ mkEd25519Signer' sender00 - [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] - , CapToken (QualifiedName "ROTATE" (ModuleName "coin" Nothing)) [PString "sender00"] - ] - ] - $ defaultCmd cid - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = miner} - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Left - ? P.match (_RedeemGasError . _2 . _RedeemGasPactError) - ? P.match (_PEUserRecoverableError . _1) - ? P.equals (UserEnforceError "account guards do not match") - -purchaseGasTxTooBig :: RocksDb -> IO () -purchaseGasTxTooBig rdb = readFromAfterGenesis v rdb $ do - pactTransaction Nothing $ \pactDb -> do - cmd <- buildCwCmd v - $ set cbSender "sender00" - $ set cbSigners [mkEd25519Signer' sender00 []] - $ set cbGasLimit (GasLimit (Gas 1)) -- We set the gas limit to lower than the initialGas passed to applyCmd so that this test fails - $ defaultCmd cid - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 2) (view payloadObj <$> cmd) - >>= P.match _Left - ? P.match _PurchaseGasTxTooBigForGasLimit - ? P.succeed - -payloadFailureShouldPayAllGasToTheMinerTypeError :: RocksDb -> IO () -payloadFailureShouldPayAllGasToTheMinerTypeError rdb = readFromAfterGenesis v rdb $ do - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - assertEqual "starting balance" (Just 100_000_000) startSender00Bal - startMinerBal <- readBal pactDb "NoMiner" - - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(+ 1 \"hello\")" - , _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 1000) - } - let gasToMiner = 2 * 1_000 -- gasPrice * gasLimit - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - [ P.fun _crResult - ? P.match (_PactResultErr . _PEExecutionError . _1) - ? P.match _NativeArgumentsError P.succeed - , P.fun _crEvents ? P.list - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "NoMiner", PDecimal 2000.0]) - (P.equals coinModuleName) - ] - , P.fun _crGas ? P.equals ? Gas 1_000 - , P.fun _crLogs ? P.match _Just ? - P.list - [ P.checkAll - [ P.fun _txDomain ? P.equals "coin_coin-table" - , P.fun _txKey ? P.equals "sender00" - ] - , P.checkAll - [ P.fun _txDomain ? P.equals "coin_coin-table" - , P.fun _txKey ? P.equals "NoMiner" - ] - ] - ] - endSender00Bal <- readBal pactDb "sender00" - assertEqual "sender balance after payload failure" (fmap (subtract gasToMiner) startSender00Bal) endSender00Bal - endMinerBal <- readBal pactDb "NoMiner" - assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal - -payloadFailureShouldPayAllGasToTheMinerInsufficientFunds :: RocksDb -> IO () -payloadFailureShouldPayAllGasToTheMinerInsufficientFunds rdb = readFromAfterGenesis v rdb $ - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - assertEqual "starting balance" (Just 100_000_000) startSender00Bal - startMinerBal <- readBal pactDb "NoMiner" - - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' $ fromString $ - "(coin.transfer \"sender00\" \"sender01\" " - <> printf "%.f" (realToFrac @_ @Double $ fromMaybe 0 startSender00Bal + 1) - <> ".0 )" - , _cbSigners = - [ mkEd25519Signer' sender00 - [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] - , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 1_000_000_000] - ] - ] - , _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 1000) - } - let gasToMiner = 2 * 1_000 -- gasPrice * gasLimit - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - [ P.fun _crResult - ? P.match (_PactResultErr . _PEUserRecoverableError . _1) - ? P.equals (UserEnforceError "Insufficient funds") - , P.fun _crEvents - ? P.list - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "NoMiner", PDecimal 2000.0]) - (P.equals coinModuleName) - ] - , P.fun _crGas ? P.equals ? Gas 1_000 - , P.fun _crLogs ? P.match _Just ? - P.list - [ P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "sender00" - ] - , P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "NoMiner" - ] - ] - ] - endSender00Bal <- readBal pactDb "sender00" - assertEqual "sender balance after payload failure" (fmap (subtract gasToMiner) startSender00Bal) endSender00Bal - endMinerBal <- readBal pactDb "NoMiner" - assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal - -runPayloadShouldReturnEvalResultRelatedToTheInputCommand :: RocksDb -> IO () -runPayloadShouldReturnEvalResultRelatedToTheInputCommand rdb = readFromAfterGenesis v rdb $ - pactTransaction Nothing $ \pactDb -> do - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" - } - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - gasEnv <- mkTableGasEnv (MilliGasLimit (gasToMilliGas $ Gas 10)) GasLogsEnabled - logger <- testLogger - payloadResult <- runExceptT $ - runReaderT - (runTransactionM - (runPayload Transactional Set.empty pactDb noSPVSupport [] managedNamespacePolicy gasEnv txCtx (TxBlockIdx 0) (view payloadObj <$> cmd))) - (TransactionEnv logger gasEnv) - gasUsed <- readIORef (_geGasRef gasEnv) - - assertEqual "runPayload gas used" (MilliGas 1_750) gasUsed - - pure payloadResult >>= P.match _Right ? P.checkAll - [ P.fun _erOutput ? P.equals [InterpretValue (PInteger 15) noInfo] - , P.fun _erEvents ? P.equals [] - , P.fun _erLogs ? P.equals [] - , P.fun _erExec ? P.equals Nothing - , P.fun _erGas ? P.equals ? Gas 2 - , P.fun _erLoadedModules ? P.equals mempty - , P.fun _erTxId ? P.equals ? Just (TxId 9) - -- TODO: test _erLogGas? - ] - --- applyLocal should mostly be the same as applyCmd, this is mostly a smoke test -applyLocalSpec :: RocksDb -> IO () -applyLocalSpec rdb = readFromAfterGenesis v rdb $ - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - assertEqual "starting balance" (Just 100_000_000) startSender00Bal - startMinerBal <- readBal pactDb "NoMiner" - - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" - , _cbSigners = [] - } - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) - >>= P.checkAll - -- Local has no buy gas, therefore - -- no gas buy event - [ P.fun _crEvents ? P.equals ? [] - , P.fun _crResult ? P.equals ? PactResultOk (PInteger 15) - -- reflects payload gas usage - , P.fun _crGas ? P.equals ? Gas 2 - , P.fun _crContinuation ? P.equals ? Nothing - , P.fun _crLogs ? P.equals ? Just [] - , P.fun _crMetaData ? P.match _Just P.succeed - ] - - endSender00Bal <- readBal pactDb "sender00" - assertEqual "ending balance should be equal" startSender00Bal endSender00Bal - endMinerBal <- readBal pactDb "NoMiner" - assertEqual "miner balance after redeeming gas should have increased" startMinerBal endMinerBal - -applyCmdSpec :: RocksDb -> IO () -applyCmdSpec rdb = readFromAfterGenesis v rdb $ do - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - let expectedStartingBal = 100_000_000 - assertEqual "starting balance" (Just expectedStartingBal) startSender00Bal - startMinerBal <- readBal pactDb "NoMiner" - - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" - , _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 500) - -- no caps should be equivalent to the GAS cap - , _cbSigners = [mkEd25519Signer' sender00 []] - } - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - let expectedGasConsumed = 73 - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - -- only the event reflecting the final transfer to the miner for gas used - [ P.fun _crEvents ? P.list - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "NoMiner", PDecimal 146.0]) - (P.equals coinModuleName) - ] - , P.fun _crResult ? P.equals ? PactResultOk (PInteger 15) - -- reflects buyGas gas usage, as well as that of the payload - , P.fun _crGas ? P.equals ? Gas expectedGasConsumed - , P.fun _crContinuation ? P.equals ? Nothing - , P.fun _crLogs ? P.match _Just ? - P.list - [ P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "sender00" - -- TODO: test the values here? - -- here, we're only testing that the write pattern matches - -- gas buy and redeem, not the contents of the writes. - ] - , P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "sender00" - ] - , P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "NoMiner" - ] - ] - ] - - endSender00Bal <- readBal pactDb "sender00" - assertEqual "ending balance should be less gas money" (Just (expectedStartingBal - fromIntegral expectedGasConsumed * 2)) endSender00Bal - endMinerBal <- readBal pactDb "NoMiner" - assertEqual "miner balance after redeeming gas should have increased" - (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed) * 2) - endMinerBal - - -- test cache contents - use pbBlockHandle >>= \bh -> liftIO $ bh - & P.fun _blockHandlePending - ? P.fun _pendingWrites - ? P.checkAll - [ P.fun InMemDb.userTables - ? P.alignExact ? HashMap.fromList - [ TableName "coin-table" (ModuleName "coin" Nothing) :=> - P.alignExact ? HashMap.fromList - [ RowKey "NoMiner" :=> - P.match InMemDb._WriteEntry P.succeed - , RowKey "sender00" :=> - P.match InMemDb._WriteEntry P.succeed - ] - ] - - , P.fun InMemDb.modules - ? P.alignExact ? HashMap.fromList - [ ModuleName "coin" Nothing :=> - P.match InMemDb._ReadEntry P.succeed - ] - ] - -quirkSpec :: RocksDb -> IO () -quirkSpec rdb = readFromAfterGenesis quirkVer rdb $ - pactTransaction Nothing $ \pactDb -> do - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(* 1000 200000)" - , _cbSigners = [mkEd25519Signer' sender00 []] - , _cbSender = "sender00" - , _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 70_000) - } - let txCtx = TxContext - { _tcParentHeader = ParentHeader (gh quirkVer cid) - , _tcMiner = noMiner - } - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - -- gas buy event - [ P.fun _crEvents ? P.list - [ event - (P.equals "TRANSFER") - -- gas 1, gas price 2 - (P.equals [PString "sender00", PString "NoMiner", PDecimal (1 * 2)]) - (P.equals coinModuleName) - ] - , P.fun _crResult ? P.equals ? PactResultOk (PInteger (1000 * 200000)) - -- quirked gas - , P.fun _crGas ? P.equals ? Gas 1 - ] - where - quirkVer = quirkedGasPact5InstantCpmTestVersion peterson - -applyCmdVerifierSpec :: RocksDb -> IO () -applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ - pactTransaction Nothing $ \pactDb -> do - -- Define module with capability - do - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' $ T.unlines - [ "(namespace 'free)" - , "(module m G" - , " (defcap G () (enforce-verifier 'allow))" - , " (defun x () (with-capability (G) 1))" - , ")" - ] - , _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 70_000) - } - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - -- gas buy event - [ P.fun _crEvents ? P.list - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "NoMiner", PDecimal 570]) - (P.equals coinModuleName) - ] - , P.fun _crResult ? P.equals ? PactResultOk (PString "Loaded module free.m, hash Uj0lQPPu9CKvw13K4VP4DZoaPKOphk_-vuq823hLSLo") - -- reflects buyGas gas usage, as well as that of the payload - , P.fun _crGas ? P.equals ? Gas 285 - , P.fun _crContinuation ? P.equals ? Nothing - ] - - let baseCmd = (defaultCmd cid) - { _cbRPC = mkExec' "(free.m.x)" - , _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 300) - } - - -- Invoke module when verifier capability isn't present. Should fail. - do - cmd <- buildCwCmd v baseCmd - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - -- gas buy event - [ P.fun _crResult - ? P.match (_PactResultErr . _PEUserRecoverableError . _1) - ? P.equals ? VerifierFailure (VerifierName "allow") "not in transaction" - , P.fun _crEvents ? P.list - [ P.checkAll - [ P.fun _peName ? P.equals ? "TRANSFER" - , P.fun _peArgs ? P.equals ? [PString "sender00", PString "NoMiner", PDecimal 600] - , P.fun _peModule ? P.equals ? ModuleName "coin" Nothing - ] - ] - -- reflects buyGas gas usage, as well as that of the payload - , P.fun _crGas ? P.equals ? Gas 300 - , P.fun _crContinuation ? P.equals ? Nothing - ] - - -- Invoke module when verifier capability is present. Should succeed. - do - let cap :: SigCapability - cap = SigCapability $ CapToken (QualifiedName "G" (ModuleName "m" (Just (NamespaceName "free")))) [] - cmd <- buildCwCmd v baseCmd - { _cbVerifiers = - [ Verifier - { _verifierName = VerifierName "allow" - , _verifierProof = ParsedVerifierProof $ PString $ T.decodeUtf8 $ J.encodeStrict cap - , _verifierCaps = [cap] - } - ] - } - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - -- gas buy event - [ P.fun _crEvents ? P.list - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "NoMiner", PDecimal 362]) - (P.equals coinModuleName) - ] - , P.fun _crResult ? P.equals ? PactResultOk (PInteger 1) - -- reflects buyGas gas usage, as well as that of the payload - , P.fun _crGas ? P.equals ? Gas 181 - , P.fun _crContinuation ? P.equals ? Nothing - , P.fun _crMetaData ? P.equals ? Nothing - ] - -applyCmdFailureSpec :: RocksDb -> IO () -applyCmdFailureSpec rdb = readFromAfterGenesis v rdb $ - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - assertEqual "starting balance" (Just 100_000_000) startSender00Bal - startMinerBal <- readBal pactDb "NoMiner" - - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(+ 1 \"abc\")" - , _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 500) - } - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - let expectedGasConsumed = 500 - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - -- gas buy event - - [ P.fun _crEvents - ? P.list - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "NoMiner", PDecimal 1000]) - (P.equals coinModuleName) - ] - -- tx errored - , P.fun _crResult ? P.match _PactResultErr P.succeed - -- reflects buyGas gas usage, as well as that of the payload - , P.fun _crGas ? P.equals ? Gas expectedGasConsumed - , P.fun _crContinuation ? P.equals ? Nothing - , P.fun _crLogs ? P.match _Just ? - P.list - [ P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "sender00" - ] - , P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "NoMiner" - ] - ] - ] - - endSender00Bal <- readBal pactDb "sender00" - assertEqual "ending balance should be less gas money" (Just 99_999_000) endSender00Bal - endMinerBal <- readBal pactDb "NoMiner" - assertEqual "miner balance after redeeming gas should have increased" - (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed) * 2) - endMinerBal - -applyCmdCoinTransfer :: RocksDb -> IO () -applyCmdCoinTransfer rdb = readFromAfterGenesis v rdb $ do - txCtx <- TxContext <$> view psParentHeader <*> pure noMiner - pactTransaction Nothing $ \pactDb -> do - startSender00Bal <- readBal pactDb "sender00" - assertEqual "starting balance" (Just 100_000_000) startSender00Bal - startMinerBal <- readBal pactDb "NoMiner" - - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0)" - , _cbSigners = - [ mkEd25519Signer' sender00 - [ CapToken (QualifiedName "GAS" coinModuleName) [] - , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 420] ] - ] - , _cbGasPrice = GasPrice 0.1 - , _cbGasLimit = GasLimit (Gas 1_000) - } - -- Note: if/when core changes gas prices, tweak here. - let expectedGasConsumed = 227 - logger <- testLogger - e <- applyCmd logger (Just logger) pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - e & P.match _Right - ? P.checkAll - [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") - , P.fun _crEvents ? P.list - -- transfer event and gas redeem event - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "sender01", PDecimal 420]) - (P.equals coinModuleName) - , event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "NoMiner", PDecimal 22.7]) - (P.equals coinModuleName) - ] - -- reflects buyGas gas usage, as well as that of the payload - , P.fun _crGas ? P.equals ? Gas expectedGasConsumed - , P.fun _crContinuation ? P.equals ? Nothing - , P.fun _crLogs ? P.match _Just ? - P.list - [ P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "sender00" - -- TODO: test the values here? - -- here, we're only testing that the write pattern matches - -- gas buy and redeem, not the contents of the writes. - ] - , P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "sender00" - ] - , P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "sender01" - ] - , P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "sender00" - ] - , P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "NoMiner" - ] - ] - ] - - endSender00Bal <- readBal pactDb "sender00" - assertEqual "ending balance should be less gas money" (Just 99_999_557.3) endSender00Bal - endMinerBal <- readBal pactDb "NoMiner" - assertEqual "miner balance after redeeming gas should have increased" - (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed * 0.1)) - endMinerBal - -applyCoinbaseSpec :: RocksDb -> IO () -applyCoinbaseSpec rdb = readFromAfterGenesis v rdb $ - pactTransaction Nothing $ \pactDb -> do - startMinerBal <- readBal pactDb "NoMiner" - - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - applyCoinbase logger pactDb 5 txCtx - >>= P.match _Right - ? P.checkAll - [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") - , P.fun _crGas ? P.equals ? Gas 0 - , P.fun _crLogs ? P.match _Just ? P.list - [ P.checkAll - [ P.fun _txDomain ? P.equals ? "coin_coin-table" - , P.fun _txKey ? P.equals ? "NoMiner" - ] - ] - , P.fun _crEvents ? P.list - [ event - (P.equals "TRANSFER") - (P.equals [PString "", PString "NoMiner", PDecimal 5.0]) - (P.equals coinModuleName) - ] - ] - endMinerBal <- readBal pactDb "NoMiner" - assertEqual "miner balance should include block reward" - (Just $ fromMaybe 0 startMinerBal + 5) - endMinerBal - -testCoinUpgrade :: RocksDb -> IO () -testCoinUpgrade rdb = readFromAfterGenesis vUpgrades rdb $ do - txCtx <- TxContext <$> view psParentHeader <*> pure noMiner - pactTransaction Nothing $ \pactDb -> do - - logger <- testLogger - getCoinModuleHash logger txCtx pactDb - >>= P.equals ? PactResultOk (PString "wOTjNC3gtOAjqgCY8S9hQ-LBiwcPUE7j4iBDE0TmdJo") - - applyUpgrades logger pactDb txCtx - - getCoinModuleHash logger txCtx pactDb - >>= P.equals ? PactResultOk (PString "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE") - where - getCoinModuleHash logger txCtx pactDb = do - cmd <- buildCwCmd vUpgrades (defaultCmd cid) - { _cbRPC = mkExec' "(at 'hash (describe-module 'coin))" - } - _crResult <$> applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) - - -testEvents :: RocksDb -> IO () -testEvents rdb = readFromAfterGenesis v rdb $ - pactTransaction Nothing $ \pactDb -> do - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0) (coin.transfer 'sender00 'sender01 69.0)" - , _cbSigners = - [ mkEd25519Signer' sender00 - [ CapToken (QualifiedName "GAS" coinModuleName) [] - , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 489] - ] - ] - , _cbGasPrice = GasPrice 2 - , _cbGasLimit = GasLimit (Gas 1100) - } - let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} - logger <- testLogger - e <- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - - e & P.match _Right - ? P.checkAll - [ P.fun _crEvents ? P.list - [ P.checkAll - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "sender01", PDecimal 420]) - (P.equals coinModuleName) - , P.fun _peModuleHash ? P.fun moduleHashToText - ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" - ] - , P.checkAll - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "sender01", PDecimal 69]) - (P.equals coinModuleName) - , P.fun _peModuleHash ? P.fun moduleHashToText - ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" - ] - , P.checkAll - [ event - (P.equals "TRANSFER") - (P.equals [PString "sender00", PString "NoMiner", PDecimal 766]) - (P.equals coinModuleName) - , P.fun _peModuleHash ? P.fun moduleHashToText - ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" - ] - ] - ] - -testLocalOnlyFailsOutsideOfLocal :: RocksDb -> IO () -testLocalOnlyFailsOutsideOfLocal rdb = readFromAfterGenesis v rdb $ do - txCtx <- TxContext <$> view psParentHeader <*> pure noMiner - pactTransaction Nothing $ \pactDb -> do - let testLocalOnly txt = do - cmd <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' txt - } - - logger <- testLogger - -- should succeed in local - applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) - >>= P.fun _crResult (P.match _PactResultOk P.succeed) - - -- should fail in non-local - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.fun _crResult - ? P.match (_PactResultErr . _PEExecutionError . _1 . _OperationIsLocalOnly) P.succeed - - testLocalOnly "(describe-module \"coin\")" - -testWritesFromFailedTxDontMakeItIn :: RocksDb -> IO () -testWritesFromFailedTxDontMakeItIn rdb = readFromAfterGenesis v rdb $ do - txCtx <- TxContext <$> view psParentHeader <*> pure noMiner - pactTransaction Nothing $ \pactDb -> do - - moduleDeploy <- buildCwCmd v (defaultCmd cid) - { _cbRPC = mkExec' "(module m g (defcap g () (enforce false \"non-upgradeable\"))) (enforce false \"boom\")" - , _cbGasLimit = GasLimit (Gas 200_000) - , _cbSigners = [mkEd25519Signer' sender00 []] - } - - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> moduleDeploy) - >>= P.match _Right - ? P.fun _crResult - ? P.match _PactResultErr P.succeed - - -- Assert that the writes from the failed transaction didn't make it into the db - -- but there is some write to the coin contract - use pbBlockHandle - >>= (liftIO .) - ? P.fun _blockHandlePending - ? P.fun _pendingWrites - ? P.checkAll - [ P.fun InMemDb.modules - ? traverseOf_ (traversed . InMemDb._WriteEntry) - ? P.fail "no writes made to module table" - , P.fun InMemDb.userTables - ? P.match (ix (TableName "coin-table" (ModuleName "coin" Nothing))) - ? P.alignExact ? HashMap.fromList - [ RowKey "NoMiner" :=> P.match InMemDb._WriteEntry P.succeed - , RowKey "sender00" :=> P.match InMemDb._WriteEntry P.succeed - ] - ] - -testWritesToNonExistentTables :: RocksDb -> IO () -testWritesToNonExistentTables rdb = readFromAfterGenesis v rdb $ do - txCtx <- TxContext <$> view psParentHeader <*> pure noMiner - pactTransaction Nothing $ \pactDb -> do - cmd <- buildCwCmd v - $ set cbRPC (mkExec' $ T.concat - [ "(namespace 'free)" - , "(module m G" - , "(defcap G () true)" - , "(defschema o i:integer)" - , "(deftable t:{o})" - , ")" - , "(insert t 'k {'i: 2})" - ] - ) - $ defaultCmd cid - - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.fun _crResult - ? P.match (_PactResultErr . _PEExecutionError . _1) - ? P.equals (DbOpFailure (NoSuchTable (TableName "t" (ModuleName "m" (Just "free"))))) - -testKeccak256 :: RocksDb -> IO () -testKeccak256 rdb = readFromAfterGenesis v rdb $ do - txCtx <- TxContext <$> view psParentHeader <*> pure noMiner - pactTransaction Nothing $ \pactDb -> do - cmd <- buildCwCmd v - $ set cbRPC (mkExec' "(hash-keccak256 [\"T73FllCNJKKgAQ4UCYC4CfucbVXsdRJYkd2YXTdmW9gPm-tqUCB1iKvzzu6Md82KWtSKngqgdO04hzg2JJbS-yyHVDuzNJ6mSZfOPntCTqktEi9X27CFWoAwWEN_4Ir7DItecXm5BEu_TYGnFjsxOeMIiLU2sPlX7_macWL0ylqnVqSpgt-tvzHvJVCDxLXGwbmaEH19Ov_9uJFHwsxMmiZD9Hjl4tOTrqN7THy0tel9rc8WtrUKrg87VJ7OR3Rtts5vZ91EBs1OdVldUQPRP536eTcpJNMo-N0fy-taji6L9Mdt4I4_xGqgIfmJxJMpx6ysWmiFVte8vLKl1L5p0yhOnEDsSDjuhZISDOIKC2NeytqoT9VpBQn1T3fjWkF8WEZIvJg5uXTge_qwA46QKV0LE5AlMKgw0cK91T8fnJ-u1Dyk7tCo3XYbx-292iiih8YM1Cr1-cdY5cclAjHAmlglY2ia_GXit5p6K2ggBmd1LpEBdG8DGE4jmeTtiDXLjprpDilq8iCuI0JZ_gvQvMYPekpf8_cMXtTenIxRmhDpYvZzyCxek1F4aoo7_VcAMYV71Mh_T8ox7U1Q4U8hB9oCy1BYcAt06iQai0HXhGFljxsrkL_YSkwsnWVDhhqzxWRRdX3PubpgMzSI290C1gG0Gq4xfKdHTrbm3Q\"])") - $ defaultCmd cid - - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.match _Right - ? P.checkAll - [ P.fun _crResult ? P.equals ? PactResultOk (PString "DqM-LjT1ckQGQCRMfx9fBGl86XE5vacqZVjYZjwCs4g") - , P.fun _crGas ? P.equals ? Gas 75 - ] - -testCommandResult5To4 :: RocksDb -> IO () -testCommandResult5To4 rdb = readFromAfterGenesis v rdb $ do - txCtx <- TxContext <$> view psParentHeader <*> pure noMiner - pactTransaction Nothing $ \pactDb -> do - cmd <- buildCwCmd v - $ set cbRPC (mkExec' "(+ 1 'hello)") - $ defaultCmd cid - - logger <- testLogger - applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.checkAll - [ P.match _Right - ? P.fun _crResult - ? P.match _PactResultErr - ? P.succeed - , P.match _Right - ? P.fun (fmap pactErrorToOnChainError) - ? P.fun hashPact5TxLogs - ? P.fun toPact4CommandResult - ? P.forced - ] - -cid :: ChainId -cid = unsafeChainId 0 - -gh :: ChainwebVersion -> ChainId -> BlockHeader -gh = genesisBlockHeader - -vUpgrades :: ChainwebVersion -vUpgrades = pact5SlowCpmTestVersion singletonChainGraph - -v :: ChainwebVersion -v = pact5InstantCpmTestVersion petersonChainGraph - --- | this utility for reading balances from the pactdb also takes care of --- making a transaction for the read to live in -readBal :: (HasCallStack) => PactDb b Info -> T.Text -> IO (Maybe Decimal) -readBal pactDb acctName = do - _ <- ignoreGas noInfo $ _pdbBeginTx pactDb Transactional - acct <- ignoreGas noInfo $ _pdbRead pactDb - (DUserTables (TableName "coin-table" (ModuleName "coin" Nothing))) - (RowKey acctName) - _ <- ignoreGas noInfo $ _pdbCommitTx pactDb - return $! acct ^? _Just . ix "balance" . _PDecimal - -testLogger :: IO GenericLogger -testLogger = do - logLevel <- liftIO getTestLogLevel - pure $ genericLogger logLevel T.putStrLn diff --git a/test/unit/Chainweb/Test/TreeDB.hs b/test/unit/Chainweb/Test/TreeDB.hs index aca17c229a..19425ce508 100644 --- a/test/unit/Chainweb/Test/TreeDB.hs +++ b/test/unit/Chainweb/Test/TreeDB.hs @@ -235,7 +235,7 @@ entryOrder_prop f (SparseTree t0) = ioProperty . withTreeDb f t $ \db _ -> do pure . isJust $ foldlM g S.empty hs where g acc h = let acc' = S.insert (view blockHash h) acc - in bool Nothing (Just acc') $ isGenesisBlockHeader h || S.member (view blockParent h) acc' + in bool Nothing (Just acc') $ isGenesisBlockHeader h || S.member (unwrapParent (view blockParent h)) acc' t :: Tree (DbEntry db) t = fmap (^. from isoBH) t0 diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index 9017062eeb..318ff72e09 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -72,13 +72,13 @@ import qualified Chainweb.Test.Pact4.SQLite (tests) import qualified Chainweb.Test.Pact4.TTL (tests) import qualified Chainweb.Test.Pact4.TransactionTests (tests) import qualified Chainweb.Test.Pact4.VerifierPluginTest (tests) -import qualified Chainweb.Test.Pact5.CheckpointerTest -import qualified Chainweb.Test.Pact5.HyperlanePluginTests -import qualified Chainweb.Test.Pact5.PactServiceTest -import qualified Chainweb.Test.Pact5.RemotePactTest -import qualified Chainweb.Test.Pact5.SPVTest -import qualified Chainweb.Test.Pact5.TransactionExecTest -import qualified Chainweb.Test.Pact5.TransactionTests +import qualified Chainweb.Test.Pact.CheckpointerTest +import qualified Chainweb.Test.Pact.HyperlanePluginTests +import qualified Chainweb.Test.Pact.PactServiceTest +import qualified Chainweb.Test.Pact.RemotePactTest +import qualified Chainweb.Test.Pact.SPVTest +import qualified Chainweb.Test.Pact.TransactionExecTest +import qualified Chainweb.Test.Pact.TransactionTests import qualified Chainweb.Test.RestAPI (tests) import qualified Chainweb.Test.Roundtrips (tests) import qualified Chainweb.Test.SPV (tests) @@ -157,13 +157,13 @@ suite rdb = , Chainweb.Test.Pact4.SQLite.tests , Chainweb.Test.CutDB.tests rdb , Chainweb.Test.Pact4.TransactionTests.tests -- TODO: fix, awaiting for Jose to add loadScript function - , Chainweb.Test.Pact5.CheckpointerTest.tests - , Chainweb.Test.Pact5.TransactionExecTest.tests rdb - , Chainweb.Test.Pact5.PactServiceTest.tests rdb - , Chainweb.Test.Pact5.SPVTest.tests rdb - , Chainweb.Test.Pact5.RemotePactTest.tests rdb - , Chainweb.Test.Pact5.HyperlanePluginTests.tests rdb - , Chainweb.Test.Pact5.TransactionTests.tests + , Chainweb.Test.Pact.CheckpointerTest.tests + , Chainweb.Test.Pact.TransactionExecTest.tests rdb + , Chainweb.Test.Pact.PactServiceTest.tests rdb + , Chainweb.Test.Pact.SPVTest.tests rdb + , Chainweb.Test.Pact.RemotePactTest.tests rdb + , Chainweb.Test.Pact.HyperlanePluginTests.tests rdb + , Chainweb.Test.Pact.TransactionTests.tests , Chainweb.Test.Roundtrips.tests , Chainweb.Test.RestAPI.tests rdb , testGroup "SPV" From 429fa61a0e020c88ede8673d90db5b7cc463c0c9 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 4 Apr 2025 10:20:59 -0400 Subject: [PATCH 104/378] reviving tests --- cwtools/ea/Ea.hs | 100 +- cwtools/ea/Ea/Genesis.hs | 33 - .../coin-contract/v1/load-fungible-asset.yaml | 3 +- src/Chainweb/BlockHeader.hs | 1 + .../Genesis/Development0Payload.hs | 4 +- .../Genesis/Development1to19Payload.hs | 4 +- .../Genesis/InstantTimedCPM0Payload.hs | 20 +- .../Genesis/InstantTimedCPM1to9Payload.hs | 20 +- .../QuirkedGasPact5InstantTimedCPM0Payload.hs | 20 +- ...irkedGasPact5InstantTimedCPM1to9Payload.hs | 4 +- .../BlockHeader/Genesis/Testnet050Payload.hs | 4 +- .../Genesis/Testnet051to19Payload.hs | 4 +- src/Chainweb/BlockHeader/Internal.hs | 6 + src/Chainweb/Pact/PactService/Checkpointer.hs | 6 +- src/Chainweb/Pact/Types.hs | 14 + test/lib/Chainweb/Test/TestVersions.hs | 6 +- .../Chainweb/Test/Pact/TransactionExecTest.hs | 1611 ++++++++--------- 17 files changed, 854 insertions(+), 1006 deletions(-) diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index f5fd91adc0..efe97b24cb 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -82,28 +82,16 @@ main = do registerVersion Development mapConcurrently_ id - [ recapDevnet - , devnet + [ devnet -- , fastnet , instantnet -- , pact5Instantnet , quirkedPact5Instantnet - , testnet04 , testnet05 - , mainnet - , genTxModules - , genCoinV3Payloads - , genCoinV4Payloads - , genCoinV5Payloads - , genCoinV6Payloads + -- , genCoinV6Payloads ] putStrLn "Done." where - recapDevnet = mkPayloads - [ recapDevelopment0 - , recapDevelopmentN - , recapDevelopmentKAD - ] devnet = mkPayloads [ fastDevelopment0 , fastDevelopmentN @@ -150,7 +138,7 @@ writePayload gen payload = do -- mkPayload :: Genesis -> IO Text mkPayload gen@(Genesis v _ cidr@(ChainIdRange l u) c k a ns cc) = do - printf ("Generating Genesis Payload for %s on " <> show_ cidr <> "...\n") $ show v + -- printf ("Generating Genesis Payload for %s on " <> show_ cidr <> "...\n") $ show v payloadModules <- for [l..u] $ \cid -> genPayloadModule v (fullGenesisTag gen) (unsafeChainId cid) =<< mkChainwebTxs txs -- checks that the modules on each chain are the same @@ -161,25 +149,6 @@ mkPayload gen@(Genesis v _ cidr@(ChainIdRange l u) c k a ns cc) = do txs :: [FilePath] txs = cc <> toList ns <> toList k <> toList a <> toList c -genCoinV3Payloads :: IO () -genCoinV3Payloads = genTxModule "CoinV3" [coinContractV3] - -genCoinV4Payloads :: IO () -genCoinV4Payloads = genTxModule "CoinV4" - [ fungibleXChainV1 - , coinContractV4 - ] - -genCoinV5Payloads :: IO () -genCoinV5Payloads = genTxModule "CoinV5" - [ coinContractV5 - ] - -genCoinV6Payloads :: IO () -genCoinV6Payloads = genTxModule "CoinV6" - [ coinContractV6 - ] - --------------------- -- Payload Generation --------------------- @@ -281,66 +250,3 @@ sep s f = go . toList ------------------------------------------------------ -- Transaction Generation for coin v2 and remediations ------------------------------------------------------ - -genTxModules :: IO () -genTxModules = void $ do - genDevTxs - genMainnetTxs - genOtherTxs - gen20ChainRemeds - putStrLn "Done." - where - gen tag remeds = genTxModule tag $ upgrades <> remeds - genOtherTxs = gen "Other" [] - genDevTxs = gen "RecapDevelopment" - ["pact/coin-contract/remediations/devother/remediations.yaml"] - - genMain :: Int -> IO () - genMain chain = gen ("Mainnet" <> sshow chain) - ["pact/coin-contract/remediations/mainnet/remediations" <> show chain <> ".yaml"] - - genMainnetTxs = mapM_ genMain [0..9] - - gen20ChainRemeds = genTxModule "MainnetKAD" - ["pact/coin-contract/remediations/mainnet/remediations20chain.yaml"] - - upgrades = [fungibleAssetV2, coinContractV2] - -genTxModule :: Text -> [FilePath] -> IO () -genTxModule tag txFiles = do - putStrLn $ "Generating tx module for " ++ show tag - cwTxs <- mkChainwebTxs txFiles - - let encTxs = map quoteTx cwTxs - quoteTx tx = " \"" <> encTx tx <> "\"" - encTx = encodeB64UrlNoPaddingText . codecEncode Pact.commandCodec - modl = T.unlines $ startTxModule tag <> [T.intercalate "\n ,\n" encTxs] <> endTxModule - fileName = "src/Chainweb/Pact/Transactions/" <> tag <> "Transactions.hs" - - TIO.writeFile (T.unpack fileName) modl - -startTxModule :: Text -> [Text] -startTxModule tag = - [ "{-# LANGUAGE OverloadedStrings #-}" - , "" - , "-- This module is auto-generated. DO NOT EDIT IT MANUALLY." - , "" - , "module Chainweb.Pact.Transactions." <> tag <> "Transactions ( transactions ) where" - , "" - , "import Data.Bifunctor (first)" - , "import System.IO.Unsafe" - , "" - , "import qualified Chainweb.Pact.Transaction as Pact" - , "import Chainweb.Utils" - , "" - , "transactions :: [Pact.Transaction]" - , "transactions =" - , " let decodeTx t =" - , " fromEitherM . (first (userError . show)) . codecDecode (Pact.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t" - , " in unsafePerformIO $ mapM decodeTx [" - ] - -endTxModule :: [Text] -endTxModule = - [ " ]" - ] diff --git a/cwtools/ea/Ea/Genesis.hs b/cwtools/ea/Ea/Genesis.hs index 00cbbb2810..1a78791ff2 100644 --- a/cwtools/ea/Ea/Genesis.hs +++ b/cwtools/ea/Ea/Genesis.hs @@ -13,9 +13,6 @@ module Ea.Genesis , chainIdRangeTag -- * Devnet Genesis Txs -, recapDevelopment0 -, recapDevelopmentN -, recapDevelopmentKAD , fastDevelopment0 , fastDevelopmentN @@ -73,7 +70,6 @@ import Chainweb.Graph import Chainweb.Test.TestVersions import Chainweb.Version import Chainweb.Version.Development -import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.Testnet04 import Chainweb.Version.Testnet05 @@ -165,35 +161,6 @@ fungibleAssetV2 = "pact/coin-contract/v2/load-fungible-asset-v2.yaml" fungibleXChainV1 :: FilePath fungibleXChainV1 = "pact/coin-contract/v4/load-fungible-xchain-v1.yaml" --- ---------------------------------------------------------------------- -- --- Devnet - RecapDevelopment - -recapDevelopment0 :: Genesis -recapDevelopment0 = Genesis - { _version = RecapDevelopment - , _tag = "RecapDevelopment" - , _txChainIds = onlyChainId 0 - , _coinbase = Just dev0Grants - , _keysets = Just devKeysets - , _allocations = Just devAllocations - , _namespaces = Just devNs - , _coinContract = [fungibleAssetV1, coinContractV1, gasPayer] - } - -recapDevelopmentN :: Genesis -recapDevelopmentN = recapDevelopment0 - & txChainIds .~ mkChainIdRange 1 9 - & coinbase ?~ devNGrants - -recapDevelopmentKAD :: Genesis -recapDevelopmentKAD = recapDevelopment0 - & txChainIds .~ mkChainIdRange 10 19 - & coinbase ?~ devnetKadOps - & keysets .~ Nothing - & allocations .~ Nothing - & namespaces ?~ devNs - & coinContract .~ [fungibleAssetV1, fungibleAssetV2, coinContractV2Install, gasPayer] - -- ---------------------------------------------------------------------- -- -- Devnet - Development diff --git a/pact/coin-contract/v1/load-fungible-asset.yaml b/pact/coin-contract/v1/load-fungible-asset.yaml index cd2f25c9bf..c1327d7e1f 100644 --- a/pact/coin-contract/v1/load-fungible-asset.yaml +++ b/pact/coin-contract/v1/load-fungible-asset.yaml @@ -1,3 +1,4 @@ +type: exec codeFile: fungible-v1.pact nonce: genesis-01 -keyPairs: [] \ No newline at end of file +keyPairs: [] diff --git a/src/Chainweb/BlockHeader.hs b/src/Chainweb/BlockHeader.hs index 5dc47b1905..58fb5016d7 100644 --- a/src/Chainweb/BlockHeader.hs +++ b/src/Chainweb/BlockHeader.hs @@ -86,6 +86,7 @@ module Chainweb.BlockHeader , I.childBlockHeight , I.parentBlockHeight , I.genesisParentBlockHash +, I.genesisRankedParentBlockHash , I.genesisBlockHeader , I.genesisBlockHeaders , I.genesisBlockHeadersAtHeight diff --git a/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs index 78cc1a8492..d8c22e6109 100644 --- a/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "S7PsDlRGDjPYLdeM3eeEZdvuYtdAthe3_LIVVwlDOaU" + expectedHash = unsafeFromText "oq5QZGFJNhFZDMmrLCWVp_-gBcp1_DBqHYimdL7v468" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -35,5 +35,5 @@ payloadBlock , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") - , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") + , (unsafeFromText "eyJoYXNoIjoiejJtR1ZxektqeHp5bUdHLWNpWjBCSEpGM1EzeGtvOFYyNEE4ejBxcEl4ayIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJ6Mm1HVnF6S2p4enltR0ctY2laMEJISkYzUTN4a284VjI0QTh6MHFwSXhrIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") ] diff --git a/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs index d00700cff7..cebc2270e6 100644 --- a/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "yHOBLd5WogOZQno-TBaG3gFePMVDvY1RPFuAib4SyWY" + expectedHash = unsafeFromText "gMX0oIeFRI4dKlraTBi2pLwfvVDqBH-5qEbcWAkk1jg" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -35,5 +35,5 @@ payloadBlock , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") - , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") + , (unsafeFromText "eyJoYXNoIjoibWd3Z3lCdTRGS3NEQ042Q3dWOEJNcFdNcnBwb2UtVkJKYTFYRjktLTFQRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJtZ3dneUJ1NEZLc0RDTjZDd1Y4Qk1wV01ycHBvZS1WQkphMVhGOS0tMVBFIiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") ] diff --git a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs index a1e1262079..bd649126f1 100644 --- a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs @@ -21,19 +21,19 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM" + expectedHash = unsafeFromText "oq5QZGFJNhFZDMmrLCWVp_-gBcp1_DBqHYimdL7v468" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" coinbase = unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6Ik5PX0NPSU5CQVNFIn0sInJlcUtleSI6IkRsZFJ3Q2JsUTdMb3F5NndZSm5hb2RIbDMwZDNqM2VILXF0RnpmRXY0NmciLCJsb2dzIjpudWxsLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjpudWxsfQ" txs = V.fromList - [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEifSwicmVxS2V5IjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsImxvZ3MiOiJRRmEyOHRuOXkydFdMVzdUc3lHdzNOTkhpYzhxYjJ6UGtudXRpWWhnQXc4IiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MH0") - , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxIn0sInJlcUtleSI6InlubnAxWFVTUk4yazFGMGE2dnVzN0RadEg2Y3BzejFYZl9Hd1dMZ0xYU00iLCJsb2dzIjoiVmRxMHp0SjBnRThOLTNkLW1tRmRRXzZ0ZGp0dnEtNXIwX050dXBvT1hpVSIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjF9") - , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIifSwicmVxS2V5IjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsImxvZ3MiOiJWaGlrLVYzOHByQXRpbHhTV3RZNWYxUnpmVjFUYVJBQzF0N3VVVXZjbGxnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6Mn0") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IlV5bUQ3bm5GUWZTMGF5aUVNT3RuWEdKWDhpbGpYX3JnQzdPbENMdHhZVEEiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") - , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxIn0sInJlcUtleSI6IlNCM1c1RUxpems5eHpTVlpPTF93bHpuVTY4eWlIT0M5cFlIa3hwVV8wZ28iLCJsb2dzIjoiZlZuSFlta19QNmJSY3VjeVg1RDdLamNLYkVsVDlEcU9vZW9yUFEtUXdsMCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjR9") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCBkZWZpbmVkIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiNHlRZEx6VWU5QUttdko3cnhEdkJtUnVIZjVic0lvSjNBMjZCaGdicl9NOCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") - , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCBkZWZpbmVkIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiU0t3RUk1NWN4M2pMdU16RWRZQ1NtZ1RSRWhmVjg2alQwVjd3WVdvZWxfQSIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") - , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkRTeVFBWXBXSW9OazlOSUFNaTgzODRBTDhnSU9LRzc2Y1FaMzZNaGtkOGciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") - , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImdMVllyaXhlNHpPZWxpejNkYXlZTDhzWGd0RVVWczJQVndlWGRDYXBnVmsiLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiJ3T1RqTkMzZ3RPQWpxZ0NZOFM5aFEtTEJpd2NQVUU3ajRpQkRFMFRtZEpvIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiJ3T1RqTkMzZ3RPQWpxZ0NZOFM5aFEtTEJpd2NQVUU3ajRpQkRFMFRtZEpvIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoid09Uak5DM2d0T0FqcWdDWThTOWhRLUxCaXdjUFVFN2o0aUJERTBUbWRKbyJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") + [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") + , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") + , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") + , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") + , (unsafeFromText "eyJoYXNoIjoiejJtR1ZxektqeHp5bUdHLWNpWjBCSEpGM1EzeGtvOFYyNEE4ejBxcEl4ayIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJ6Mm1HVnF6S2p4enltR0ctY2laMEJISkYzUTN4a284VjI0QTh6MHFwSXhrIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") ] diff --git a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs index e5352c2192..397d3878f3 100644 --- a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs @@ -21,19 +21,19 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4" + expectedHash = unsafeFromText "gMX0oIeFRI4dKlraTBi2pLwfvVDqBH-5qEbcWAkk1jg" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" coinbase = unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6Ik5PX0NPSU5CQVNFIn0sInJlcUtleSI6IkRsZFJ3Q2JsUTdMb3F5NndZSm5hb2RIbDMwZDNqM2VILXF0RnpmRXY0NmciLCJsb2dzIjpudWxsLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjpudWxsfQ" txs = V.fromList - [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEifSwicmVxS2V5IjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsImxvZ3MiOiJRRmEyOHRuOXkydFdMVzdUc3lHdzNOTkhpYzhxYjJ6UGtudXRpWWhnQXc4IiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MH0") - , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxIn0sInJlcUtleSI6InlubnAxWFVTUk4yazFGMGE2dnVzN0RadEg2Y3BzejFYZl9Hd1dMZ0xYU00iLCJsb2dzIjoiVmRxMHp0SjBnRThOLTNkLW1tRmRRXzZ0ZGp0dnEtNXIwX050dXBvT1hpVSIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjF9") - , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIifSwicmVxS2V5IjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsImxvZ3MiOiJWaGlrLVYzOHByQXRpbHhTV3RZNWYxUnpmVjFUYVJBQzF0N3VVVXZjbGxnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6Mn0") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IlV5bUQ3bm5GUWZTMGF5aUVNT3RuWEdKWDhpbGpYX3JnQzdPbENMdHhZVEEiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") - , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxIn0sInJlcUtleSI6IlNCM1c1RUxpems5eHpTVlpPTF93bHpuVTY4eWlIT0M5cFlIa3hwVV8wZ28iLCJsb2dzIjoiZlZuSFlta19QNmJSY3VjeVg1RDdLamNLYkVsVDlEcU9vZW9yUFEtUXdsMCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjR9") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCBkZWZpbmVkIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiNHlRZEx6VWU5QUttdko3cnhEdkJtUnVIZjVic0lvSjNBMjZCaGdicl9NOCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") - , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCBkZWZpbmVkIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiU0t3RUk1NWN4M2pMdU16RWRZQ1NtZ1RSRWhmVjg2alQwVjd3WVdvZWxfQSIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") - , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkRTeVFBWXBXSW9OazlOSUFNaTgzODRBTDhnSU9LRzc2Y1FaMzZNaGtkOGciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") - , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6Iks5dDlKdFBZc1hGQ0dhNk5TTEI2V2xJdTFZaEJqWG94bTFPZkY2Y0xFd00iLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiJ3T1RqTkMzZ3RPQWpxZ0NZOFM5aFEtTEJpd2NQVUU3ajRpQkRFMFRtZEpvIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IndPVGpOQzNndE9BanFnQ1k4UzloUS1MQml3Y1BVRTdqNGlCREUwVG1kSm8ifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") + [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") + , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") + , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") + , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") + , (unsafeFromText "eyJoYXNoIjoibWd3Z3lCdTRGS3NEQ042Q3dWOEJNcFdNcnBwb2UtVkJKYTFYRjktLTFQRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJtZ3dneUJ1NEZLc0RDTjZDd1Y4Qk1wV01ycHBvZS1WQkphMVhGOS0tMVBFIiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") ] diff --git a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs index acf2763895..009ae28c5d 100644 --- a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs @@ -21,19 +21,19 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "svZ-z0prqOd5zB6JgkFc-owzjgXrpicuewSsMbkcjkg" + expectedHash = unsafeFromText "oq5QZGFJNhFZDMmrLCWVp_-gBcp1_DBqHYimdL7v468" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" coinbase = unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6Ik5PX0NPSU5CQVNFIn0sInJlcUtleSI6IkRsZFJ3Q2JsUTdMb3F5NndZSm5hb2RIbDMwZDNqM2VILXF0RnpmRXY0NmciLCJsb2dzIjpudWxsLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjpudWxsfQ" txs = V.fromList - [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") - , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") - , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") - , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") - , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") - , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") - , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") + [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") + , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") + , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") + , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") + , (unsafeFromText "eyJoYXNoIjoiejJtR1ZxektqeHp5bUdHLWNpWjBCSEpGM1EzeGtvOFYyNEE4ejBxcEl4ayIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJ6Mm1HVnF6S2p4enltR0ctY2laMEJISkYzUTN4a284VjI0QTh6MHFwSXhrIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") ] diff --git a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs index 91f9a7a569..6ef59ceb1a 100644 --- a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "yHOBLd5WogOZQno-TBaG3gFePMVDvY1RPFuAib4SyWY" + expectedHash = unsafeFromText "gMX0oIeFRI4dKlraTBi2pLwfvVDqBH-5qEbcWAkk1jg" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -35,5 +35,5 @@ payloadBlock , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") - , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") + , (unsafeFromText "eyJoYXNoIjoibWd3Z3lCdTRGS3NEQ042Q3dWOEJNcFdNcnBwb2UtVkJKYTFYRjktLTFQRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJtZ3dneUJ1NEZLc0RDTjZDd1Y4Qk1wV01ycHBvZS1WQkphMVhGOS0tMVBFIiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") ] diff --git a/src/Chainweb/BlockHeader/Genesis/Testnet050Payload.hs b/src/Chainweb/BlockHeader/Genesis/Testnet050Payload.hs index 90528d8b15..f73ba43373 100644 --- a/src/Chainweb/BlockHeader/Genesis/Testnet050Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Testnet050Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "ts_JI5EOsrmC1BA8438POH2AhsVzKYuYk1vZ3VxoSa0" + expectedHash = unsafeFromText "qHSz97etvPogsI08Eps2EOxpmdf3RnDGYKOS9WOBvws" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -33,7 +33,7 @@ payloadBlock , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") - , (unsafeFromText "eyJoYXNoIjoiRlAyekNwNnRLaXlub011REJKNTVWc1Z6dHlyRGpKMVNhck5ycElIZWtQTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiU0IgPDQzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDI1PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiUFNfQzNcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIkZUU19DMVwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjI5ZjcxMDdiMzlmNmMxOTQ3Zjk2NjIyZjY4OWJlYzY4ZTAxNTczMDc2ZDYwNjExZDNlZjZiOGNiYzQzY2ZhMGJcIixcIjdjMjFmN2I1M2M4MTY3OTgxNTI0ODcwMjRkNzFiZmNiZDQ3YjViODBhZTQzNTk0Yjk0ZGQzODQ1NDgxOGFlZmNcIixcIjFmZjk2OTk0NWFiNjA4OGNiZDNmM2E2YTZiOTcxODc2OTcxOTI3NjNkNzhmMjdmNTViZGRkYmJjMTU3YzAwYzBcIl19LFwiS2FkZW5hXCI6e1wicHJlZFwiOlwia2V5cy1hbGxcIixcImtleXNcIjpbXCIzNWZmMTI5OGM1NmZmYjYwMjI1YmVlZDMyYTYxYWRhNDFhNTdlMjM5ODI2ZWVhOThlZDI5ZTQ4ZmY4YmFjNDNmXCJdfSxcIkVtaWx5XCI6e1wicHJlZFwiOlwia2V5cy1hbGxcIixcImtleXNcIjpbXCIzNWZmMTI5OGM1NmZmYjYwMjI1YmVlZDMyYTYxYWRhNDFhNTdlMjM5ODI2ZWVhOThlZDI5ZTQ4ZmY4YmFjNDNmXCJdfSxcIlNBIDw0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDI2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiUFNfQzhcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIlNCIDw0MD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNBIDw3PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiQ1MxX0MyXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiOTY1MjlkYmI4MzkwMjc1NDg5MTJiYzZhMWNmZmI2OWM2NzdlZDk3NjYzNTAwMThjYzhjNDZhM2Y4MTlmZTE3NFwiLFwiZTUzNzA4NTY0ZjFhNzhmNmU1YTkzMDlmZDhiOWRkODU2NDY4MzJlMzc0MTYyY2QzYzY3NDIwZTIwMmE3ZTQ1YlwiLFwiNGJiNmI3MWUyZGRjNjEzOTIyNzRkMThkMTQyY2RjOGQ0ZjUwZTU0MzQzMDk4MTM4ZjlhN2QxN2ZiNTYyZDZlYVwiXX0sXCJTQSA8Mj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIkNvaW5saXN0IEdsb2JhbFwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjVhYzAyZDZjZTgwNDcxMmVjNTgyMmFiOTgxM2ZkNDgzMjY1YzEwNmRhM2ZmZjYwY2Q0OTI1ODRiYzI4OTY1ODFcIixcIjExMzYyNTI1MTY0NzU1Mzk4Mjk5YmRmMWQzZGUxZTRjNzVmYWNhOWQ3YzVkZjA0NTM5NTk5OTI4ZjRjOGQ5ZjdcIixcIjgyMTg1ODc1MmY3ZDhlOTg2YWI2YjA4Y2QwNDgxZmY0MDY4ZTMwOGNhMWQyZmY2OGFmMTI0MWVmNTc1Y2UyNTVcIl19LFwiU0IgPDk-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJFQl9DOVwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcIlNBIDwyMD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlBTX0M1XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQiA8MTg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8NDU-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MjM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MzU-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTc-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQSA8MTQ-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8Nj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlBTX0MyXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQiA8Mjk-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MzA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQSA8MTE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQSA8OD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNCIDwzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDMzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0EgPDEyPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiUFNfQzRcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIlNCIDwxMT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDw0MT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwyNz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlBTX0MxXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJDUzJfQzBcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI5YTJhMTJiYTc5MTkwOWEyYjVlOTJhYjMzNjZmNjBmNDY4OWU1MzMxZWYyODViNzA0ZWZhM2ZkODNiYzk1OWY0XCIsXCI3YmNmNzhmMDllNjRlMzdmZTQ3MDVkNWFiYjM3MzU0OTZmMzFmMDRkMzQ3YzEzOGY1ZGQzZjRlZTI2ODNhOTUyXCIsXCJmNTRlOWYyZDgxYmU0MjJhMDJhZGIwYTE3MmY4OTc1MmZjM2U1ZjlkYWU0MzUyMDg3NzZiZDVkODNhMmY2ZjI0XCJdfSxcIlNBIDw2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0EgPDE4PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDM5PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDQ2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDIwPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0EgPDE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8Nz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIk1BSU5ORVRcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJhNDk0NzM3OWM3ZGJiNzZlZTYwYmRlZGQ2NDg0N2ExNWFhNTY1N2VmNDA2MjAxZTQ2ODYxNDc1YzkxZDNjNTFmXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI0OTI2Zjk2M2VlNWEwYzZkZjY3YTFkY2UyNDJmYmMxMWE5MDBjNDMzMmM3N2JkZGZiMGRjNWI2NmY2OTdmZWQwXCJdfSxcIlNBIDwxNT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNCIDwzND5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwxNj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwzNz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwxNT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDw0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0EgPDE2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiUFNfQzBcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIlNUX0MxXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiY2NmNmZhMWZkMzJlNThiMDQwNWYxNjlmZmI0NTVhMWEwNDM0Y2ZhZmY1Yjg1NDk4NjI4MjFmZjZiNTUxZDViM1wiLFwiYWY4MThhNWU4NjcyYWI5M2IwODU5ODdiOTQyZTg3YWE1MmZmNmM4MWJmZmFmYWYzNDM3YzAxYzhhNDFhNmZhOFwiLFwiODc0NjgzMWM1NTgwOTUwZTc0YWVmODY2NTljNmY4ZjFhY2I3MjE1MDY3ZWQ5MWQxMWZlNjkxOTQ1ZjljOGIxZVwiXX0sXCJTQSA8MTM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8MzI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwyND5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNBIDw1PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiQ29pbmxpc3QgTm9uLVVTXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQiA8NDI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MjE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJhbGxvY2F0aW9uMDBcIjpbXCJkODJkMGRjZGU5ODI1NTA1ZDg2YWZiNmRjYzEwNDExZDZiNjdhNDI5YTc5ZTIxYmRhNGJiMTE5YmYyOGFiODcxXCJdLFwiUFNfQzdcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIlNCIDwzOD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNBIDwxOT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNBIDwzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDE5PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDQ0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDIyPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTQ-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MzY-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQSA8MTc-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8NT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlBTX0M5XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQSA8MTA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQSA8OT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlBTX0M2XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQiA8MzE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8Mjg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8Mj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcImFsbG9jYXRpb24wMVwiOltcImI0YzhhM2VhOTFkMzE0NmIwNTYwOTk0NzQwZjBlM2VlZDkxYzU5ZDJlZWNhMWRjOTlmMGMyODcyODQ1YzI5NGRcIl19LFwiY29kZVwiOlwiKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwyPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8Mz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDw0PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8ND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDw1PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8Nj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDw3PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8Nz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDg-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDw4PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8OT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDk-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxMD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDEwPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxMT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDEyPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxMz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDEzPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDE1PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxNj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDE2PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxNz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDE4PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxOT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDE5PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MjA-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwyMD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Nj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw3PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8OD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw5PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8OT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDEwPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTA-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxMT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDExPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxMj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDEzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxND5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDE0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxNT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxNz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDE3PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTg-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxOD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE5PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTk-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyMD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDIwPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MjE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyMT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDIyPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MjI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyMz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDIzPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MjQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDI1PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MjU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyNj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI2PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mjc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyNz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDI4PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mjg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyOT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI5PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzA-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzMD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDMxPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MzE-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzMj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDMyPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzM-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzMz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDM0PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MzQ-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzNT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDM1PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzY-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzNj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDM3PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mzc-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzOD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDM4PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mzk-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzOT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQwPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDA-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw0MT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDQxPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NDI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0Mj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw0ND5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDQ0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NDU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0NT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQ2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MwXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MxXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MxXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MyXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MyXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MzXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MzXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M0XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M0XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M1XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M1XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M2XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M2XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M3XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M3XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M4XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M4XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M5XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M5XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkVCX0M5XFxcIiAocmVhZC1rZXlzZXQgXFxcIkVCX0M5XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkNvaW5saXN0IE5vbi1VU1xcXCIgKHJlYWQta2V5c2V0IFxcXCJDb2lubGlzdCBOb24tVVNcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiQ29pbmxpc3QgR2xvYmFsXFxcIiAocmVhZC1rZXlzZXQgXFxcIkNvaW5saXN0IEdsb2JhbFxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJGVFNfQzFcXFwiIChyZWFkLWtleXNldCBcXFwiRlRTX0MxXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkNTMV9DMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJDUzFfQzJcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiQ1MyX0MwXFxcIiAocmVhZC1rZXlzZXQgXFxcIkNTMl9DMFxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTVF9DMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJTVF9DMVxcXCIpKVxcblxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJFbWlseVxcXCIgKHJlYWQta2V5c2V0IFxcXCJFbWlseVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJLYWRlbmFcXFwiIChyZWFkLWtleXNldCBcXFwiS2FkZW5hXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIk1BSU5ORVRcXFwiIChyZWFkLWtleXNldCBcXFwiTUFJTk5FVFxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwidGVzdG5ldC1rZXlzZXRzLU5cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkZQMnpDcDZ0S2l5bm9NdURCSjU1VnNWenR5ckRqSjFTYXJOcnBJSGVrUE0iLCJsb2dzIjoiY0NzZzlqbXRKalplOG5lZjF2VjBlU0p0cmhlUUtJeGFrUEFFVHhVVDFnayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") + , (unsafeFromText "eyJoYXNoIjoic2xSZzl5bURMYTRsaFNrbFZLM2N2U2NOaEVWS2kxdV9MUm1oYkhfX1lyRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiU0IgPDQzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDI1PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiUFNfQzNcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIkZUU19DMVwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjFmZjk2OTk0NWFiNjA4OGNiZDNmM2E2YTZiOTcxODc2OTcxOTI3NjNkNzhmMjdmNTViZGRkYmJjMTU3YzAwYzBcIixcIjI5ZjcxMDdiMzlmNmMxOTQ3Zjk2NjIyZjY4OWJlYzY4ZTAxNTczMDc2ZDYwNjExZDNlZjZiOGNiYzQzY2ZhMGJcIixcIjdjMjFmN2I1M2M4MTY3OTgxNTI0ODcwMjRkNzFiZmNiZDQ3YjViODBhZTQzNTk0Yjk0ZGQzODQ1NDgxOGFlZmNcIl19LFwiS2FkZW5hXCI6e1wicHJlZFwiOlwia2V5cy1hbGxcIixcImtleXNcIjpbXCIzNWZmMTI5OGM1NmZmYjYwMjI1YmVlZDMyYTYxYWRhNDFhNTdlMjM5ODI2ZWVhOThlZDI5ZTQ4ZmY4YmFjNDNmXCJdfSxcIkVtaWx5XCI6e1wicHJlZFwiOlwia2V5cy1hbGxcIixcImtleXNcIjpbXCIzNWZmMTI5OGM1NmZmYjYwMjI1YmVlZDMyYTYxYWRhNDFhNTdlMjM5ODI2ZWVhOThlZDI5ZTQ4ZmY4YmFjNDNmXCJdfSxcIlNBIDw0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDI2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiUFNfQzhcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIlNCIDw0MD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNBIDw3PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiQ1MxX0MyXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNGJiNmI3MWUyZGRjNjEzOTIyNzRkMThkMTQyY2RjOGQ0ZjUwZTU0MzQzMDk4MTM4ZjlhN2QxN2ZiNTYyZDZlYVwiLFwiOTY1MjlkYmI4MzkwMjc1NDg5MTJiYzZhMWNmZmI2OWM2NzdlZDk3NjYzNTAwMThjYzhjNDZhM2Y4MTlmZTE3NFwiLFwiZTUzNzA4NTY0ZjFhNzhmNmU1YTkzMDlmZDhiOWRkODU2NDY4MzJlMzc0MTYyY2QzYzY3NDIwZTIwMmE3ZTQ1YlwiXX0sXCJTQSA8Mj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIkNvaW5saXN0IEdsb2JhbFwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjExMzYyNTI1MTY0NzU1Mzk4Mjk5YmRmMWQzZGUxZTRjNzVmYWNhOWQ3YzVkZjA0NTM5NTk5OTI4ZjRjOGQ5ZjdcIixcIjVhYzAyZDZjZTgwNDcxMmVjNTgyMmFiOTgxM2ZkNDgzMjY1YzEwNmRhM2ZmZjYwY2Q0OTI1ODRiYzI4OTY1ODFcIixcIjgyMTg1ODc1MmY3ZDhlOTg2YWI2YjA4Y2QwNDgxZmY0MDY4ZTMwOGNhMWQyZmY2OGFmMTI0MWVmNTc1Y2UyNTVcIl19LFwiU0IgPDk-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJFQl9DOVwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcIlNBIDwyMD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlBTX0M1XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQiA8MTg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8NDU-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MjM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MzU-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTc-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQSA8MTQ-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8Nj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlBTX0MyXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQiA8Mjk-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MzA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQSA8MTE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQSA8OD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNCIDwzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDMzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0EgPDEyPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiUFNfQzRcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIlNCIDwxMT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDw0MT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwyNz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlBTX0MxXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJDUzJfQzBcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI3YmNmNzhmMDllNjRlMzdmZTQ3MDVkNWFiYjM3MzU0OTZmMzFmMDRkMzQ3YzEzOGY1ZGQzZjRlZTI2ODNhOTUyXCIsXCI5YTJhMTJiYTc5MTkwOWEyYjVlOTJhYjMzNjZmNjBmNDY4OWU1MzMxZWYyODViNzA0ZWZhM2ZkODNiYzk1OWY0XCIsXCJmNTRlOWYyZDgxYmU0MjJhMDJhZGIwYTE3MmY4OTc1MmZjM2U1ZjlkYWU0MzUyMDg3NzZiZDVkODNhMmY2ZjI0XCJdfSxcIlNBIDw2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0EgPDE4PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDM5PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDQ2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDIwPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0EgPDE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8Nz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIk1BSU5ORVRcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI0OTI2Zjk2M2VlNWEwYzZkZjY3YTFkY2UyNDJmYmMxMWE5MDBjNDMzMmM3N2JkZGZiMGRjNWI2NmY2OTdmZWQwXCIsXCJhNDk0NzM3OWM3ZGJiNzZlZTYwYmRlZGQ2NDg0N2ExNWFhNTY1N2VmNDA2MjAxZTQ2ODYxNDc1YzkxZDNjNTFmXCJdfSxcIlNBIDwxNT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNCIDwzND5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwxNj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwzNz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwxNT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDw0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0EgPDE2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiUFNfQzBcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIlNUX0MxXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiODc0NjgzMWM1NTgwOTUwZTc0YWVmODY2NTljNmY4ZjFhY2I3MjE1MDY3ZWQ5MWQxMWZlNjkxOTQ1ZjljOGIxZVwiLFwiYWY4MThhNWU4NjcyYWI5M2IwODU5ODdiOTQyZTg3YWE1MmZmNmM4MWJmZmFmYWYzNDM3YzAxYzhhNDFhNmZhOFwiLFwiY2NmNmZhMWZkMzJlNThiMDQwNWYxNjlmZmI0NTVhMWEwNDM0Y2ZhZmY1Yjg1NDk4NjI4MjFmZjZiNTUxZDViM1wiXX0sXCJTQSA8MTM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8MzI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwyND5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNBIDw1PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiQ29pbmxpc3QgTm9uLVVTXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQiA8NDI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MjE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJhbGxvY2F0aW9uMDBcIjpbXCJkODJkMGRjZGU5ODI1NTA1ZDg2YWZiNmRjYzEwNDExZDZiNjdhNDI5YTc5ZTIxYmRhNGJiMTE5YmYyOGFiODcxXCJdLFwiUFNfQzdcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIlNCIDwzOD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNBIDwxOT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNBIDwzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDE5PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDQ0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDIyPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTQ-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MzY-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQSA8MTc-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8NT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlBTX0M5XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQSA8MTA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQSA8OT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlBTX0M2XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQiA8MzE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8Mjg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8Mj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcImFsbG9jYXRpb24wMVwiOltcImI0YzhhM2VhOTFkMzE0NmIwNTYwOTk0NzQwZjBlM2VlZDkxYzU5ZDJlZWNhMWRjOTlmMGMyODcyODQ1YzI5NGRcIl19LFwiY29kZVwiOlwiKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwyPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8Mz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDw0PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8ND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDw1PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8Nj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDw3PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8Nz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDg-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDw4PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8OT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDk-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxMD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDEwPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxMT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDEyPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxMz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDEzPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDE1PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxNj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDE2PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxNz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDE4PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxOT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDE5PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MjA-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwyMD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Nj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw3PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8OD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw5PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8OT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDEwPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTA-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxMT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDExPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxMj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDEzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxND5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDE0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxNT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxNz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDE3PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTg-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxOD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE5PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTk-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyMD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDIwPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MjE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyMT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDIyPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MjI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyMz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDIzPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MjQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDI1PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MjU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyNj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI2PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mjc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyNz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDI4PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mjg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyOT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI5PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzA-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzMD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDMxPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MzE-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzMj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDMyPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzM-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzMz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDM0PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MzQ-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzNT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDM1PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzY-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzNj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDM3PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mzc-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzOD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDM4PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mzk-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzOT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQwPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDA-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw0MT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDQxPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NDI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0Mj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw0ND5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDQ0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NDU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0NT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQ2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MwXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MxXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MxXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MyXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MyXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MzXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MzXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M0XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M0XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M1XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M1XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M2XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M2XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M3XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M3XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M4XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M4XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M5XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M5XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkVCX0M5XFxcIiAocmVhZC1rZXlzZXQgXFxcIkVCX0M5XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkNvaW5saXN0IE5vbi1VU1xcXCIgKHJlYWQta2V5c2V0IFxcXCJDb2lubGlzdCBOb24tVVNcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiQ29pbmxpc3QgR2xvYmFsXFxcIiAocmVhZC1rZXlzZXQgXFxcIkNvaW5saXN0IEdsb2JhbFxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJGVFNfQzFcXFwiIChyZWFkLWtleXNldCBcXFwiRlRTX0MxXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkNTMV9DMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJDUzFfQzJcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiQ1MyX0MwXFxcIiAocmVhZC1rZXlzZXQgXFxcIkNTMl9DMFxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTVF9DMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJTVF9DMVxcXCIpKVxcblxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJFbWlseVxcXCIgKHJlYWQta2V5c2V0IFxcXCJFbWlseVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJLYWRlbmFcXFwiIChyZWFkLWtleXNldCBcXFwiS2FkZW5hXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIk1BSU5ORVRcXFwiIChyZWFkLWtleXNldCBcXFwiTUFJTk5FVFxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwidGVzdG5ldC1rZXlzZXRzLU5cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6InNsUmc5eW1ETGE0bGhTa2xWSzNjdlNjTmhFVktpMXVfTFJtaGJIX19ZckUiLCJsb2dzIjoiY0NzZzlqbXRKalplOG5lZjF2VjBlU0p0cmhlUUtJeGFrUEFFVHhVVDFnayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiV2hKVTlyMXZWc0NOdmFWdUdBaHRYempMdTR3UlR3TGsxNlQ2OEY2N3FMdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcIkVtaWx5XFxcIiAodGltZSBcXFwiMjAxOS0xMC0wMVQwMDowMDowMFpcXFwiKSBcXFwiRW1pbHlcXFwiIDY2NjY2Ni42NylcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJNQUlOTkVUXzFcXFwiICh0aW1lIFxcXCIyMDE5LTEwLTI5VDE0OjAwOjAwWlxcXCIpIFxcXCJNQUlOTkVUXFxcIiA2NjY2NjYuNjcpXFxuKGNvaW4uY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudCBcXFwiTUFJTk5FVF8yXFxcIiAodGltZSBcXFwiMjAxOS0xMC0yOVQxNzowMDowMFpcXFwiKSBcXFwiTUFJTk5FVFxcXCIgNjY2NjY2LjY3KVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcIk1BSU5ORVRfM1xcXCIgKHRpbWUgXFxcIjIwMTktMTEtMjlUMTc6MDA6MDBaXFxcIikgXFxcIk1BSU5ORVRcXFwiIDY2NjY2Ni42NylcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDBcXFwiICh0aW1lIFxcXCIxOTAwLTEwLTE1VDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDBcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDFcXFwiICh0aW1lIFxcXCIyMDIwLTEwLTMxVDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDJcXFwiICh0aW1lIFxcXCIxOTAwLTEwLTMxVDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDJcXFwiIDEwMDAwMDAuMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcInRlc3RuZXQtYWxsb2NhdGlvbnMtMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJXaEpVOXIxdlZzQ052YVZ1R0FodFh6akx1NHdSVHdMazE2VDY4RjY3cUx3IiwibG9ncyI6IkI1elZaYVRDWk9sbERjR2tGRDRhcHJPTi1mUlY1ZFlUWEJyTmtSSVo4LTQiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoibU1neUsxRGpvRGt6Z3ZxdHhvaW84WUt4X3JjY2UtaFR3bWRQdkc5emJXRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiNGM3MzU2NmI1ZDhmYTRjZDJhMjYyMGY3YjJiYzU3Mjg0YzdmYzVjNDhmOGYwZGIyYmYxNjc1Y2FkMDhkYzRhZlwiOltcIjRjNzM1NjZiNWQ4ZmE0Y2QyYTI2MjBmN2IyYmM1NzI4NGM3ZmM1YzQ4ZjhmMGRiMmJmMTY3NWNhZDA4ZGM0YWZcIl0sXCJlbWlseVwiOltcIjM2ODA1OWFiMjViZWM5MmYxOWE0ZjhjZTE3MjAzMGU1ZDY3MmU0ODc5YWFhZThkNWJlOGI3MDVkZDNkZmFlN2ZcIl0sXCJjYmI5NGIxMmY1NWJkNTQwYTVhYTUwNzYzNTExMTkzOGYyMjgyYzI4OTFiNzhjMTNlNDYyZmEwMThjNDE0NWQ4XCI6W1wiY2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOFwiXSxcIjQ5YjQxMDM0YWI5YmEwYjdmZDkyZmQ1M2ViNzlkN2ZkM2Q2MDExOTFhMTUyNjk2ODI4MzBmOWM5MmUzNjg4YWRcIjpbXCI0OWI0MTAzNGFiOWJhMGI3ZmQ5MmZkNTNlYjc5ZDdmZDNkNjAxMTkxYTE1MjY5NjgyODMwZjljOTJlMzY4OGFkXCJdLFwiYW5hc3Rhc2lhXCI6W1wiMWVjNzRkZTcyNGIzYzcwMDczN2Q5OTkwNmFiYTQ4ZGJlZDZiOTAxNDBiYmM2NDEzM2M1NTg1NzFiMTg4ODNjM1wiXSxcIjVlNGE4ZDUxZmEzOTU4MDI2Y2RkZDkzYmJkODc0ZTFiZGFlMTQ5YTEyYWM2ZjAxNTJlYTI1Mzk0MmVmYzk4OWNcIjpbXCI1ZTRhOGQ1MWZhMzk1ODAyNmNkZGQ5M2JiZDg3NGUxYmRhZTE0OWExMmFjNmYwMTUyZWEyNTM5NDJlZmM5ODljXCJdLFwiMWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZlwiOltcIjFjODM1ZDRlNjc5MTdmZDI1NzgxYjExZGIxYzEyZWZiYzQyOTZjNWM3ZmU5ODFkMzViYmNmNGE0NmE1MzQ0MWZcIl0sXCI4OWYwYTg0MGUzYmFiNGMzMmUwN2IxNDg2MmYwYTBmNDE0NTQzZGEwMzZlY2YzMzlkM2VkN2VjOTNmYTRhNDFiXCI6W1wiODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYlwiXSxcImZhYjkyYzQ3Y2NiMWZkZDI1MTczZTI5ODFlYmIyOWRlOTg5YWNlYTg4NzkyZTM0Y2M4NzI5OGY2YTE1Nzg4NDJcIjpbXCJmYWI5MmM0N2NjYjFmZGQyNTE3M2UyOTgxZWJiMjlkZTk4OWFjZWE4ODc5MmUzNGNjODcyOThmNmExNTc4ODQyXCJdLFwiamVmZlwiOltcImM1MzIxN2JiMDUzYjgyN2I1ZmJiYjMyMmZjZWQ2N2E4NjI5OGVjNDBhNTlhZWVmMTViMjE5MGEwNWFkMGE2YmRcIl0sXCJzdHVhcnRcIjpbXCIwZTMxODBkZDRhZTlkNDM4NWEyMWQ3Y2E5NWNjNzIyOWIwZjZiMmNhOWUwZjQ4NjFkZDRlZWQ2ODUwMDRkZjY2XCJdLFwibGVhaFwiOltcIjIxNjY0MzEzN2ZiNWY2YTEyN2RiYjEwNjM3MDJjYmI3YzFmMTUzNDUxYTFhZDRhZmE5ZjkyZTc3NDQ3Y2MzMzBcIl0sXCIxNTRkNmYyMzk4NjRjMTljNDNlZjM3N2MwM2NjOGRmOGUwZDFlNzkyYTE0M2EwMTQyMzUwMzAyOGNlOTYzYWZhXCI6W1wiMTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYVwiXSxcIjNlODMyMTNiZWRmNmJlMGFhZjhlZDNmOGU3MmZmYjlhMjQwOTMwYjg2OThiOTY0NmU4ZDkxM2I2MWM5M2U0YWJcIjpbXCIzZTgzMjEzYmVkZjZiZTBhYWY4ZWQzZjhlNzJmZmI5YTI0MDkzMGI4Njk4Yjk2NDZlOGQ5MTNiNjFjOTNlNGFiXCJdLFwid2lsbFwiOltcIjgzMGFkNzczNTEwYWIwZjVhMGQ0NTY0YjdiMzFkMmIwYjFiYTdmZDVhMTE2ZjY2NTk4YTFjNjNjYzIxNDJmZDVcIl0sXCJsaW5kYVwiOltcImUxMDAyZGIwODk0Mjk5YmIzNzIzOGJhYjAyMTcwMTgwYzBhZTU2ZjU5MzIxNjliYjJmY2Y3NTYwNTlmZWExNzVcIl0sXCI1NTQ3NTRmNDhiMTZkZjI0YjU1MmY2ODMyZGRhMDkwNjQyZWQ5NjU4NTU5ZmVmOWYzZWUxYmI0NjM3ZWE3Yzk0XCI6W1wiNTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NFwiXSxcIjRhN2MyMGQwODdkZjc2NzUzZTI4ZDc2NWViODRlMTMzODQ3OWRkNmRmMTIxZGRkYTNlNTk1NzZkMGEzMmE1YmZcIjpbXCI0YTdjMjBkMDg3ZGY3Njc1M2UyOGQ3NjVlYjg0ZTEzMzg0NzlkZDZkZjEyMWRkZGEzZTU5NTc2ZDBhMzJhNWJmXCJdLFwiaGVla3l1blwiOltcImRmYjE2YjEzZTQwMzJhNjg3OGZkOTg1MDZiMjJjYjBkNmU1OTMyYzU0MWU2NTZiN2VlNWQ2OWQ3MmU2ZWI3NmVcIl19LFwiY29kZVwiOlwiKGNvaW4uY29pbmJhc2UgXFxcImFuYXN0YXNpYVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbmFzdGFzaWFcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJlbWlseVxcXCIgKHJlYWQta2V5c2V0IFxcXCJlbWlseVxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImhlZWt5dW5cXFwiIChyZWFkLWtleXNldCBcXFwiaGVla3l1blxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImplZmZcXFwiIChyZWFkLWtleXNldCBcXFwiamVmZlxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImxlYWhcXFwiIChyZWFkLWtleXNldCBcXFwibGVhaFxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImxpbmRhXFxcIiAocmVhZC1rZXlzZXQgXFxcImxpbmRhXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwic3R1YXJ0XFxcIiAocmVhZC1rZXlzZXQgXFxcInN0dWFydFxcXCIpIDEwMDAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwid2lsbFxcXCIgKHJlYWQta2V5c2V0IFxcXCJ3aWxsXFxcIikgMTAwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJLYWRlbmFcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJLYWRlbmFcXFwiKSAxMDAwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6NGE3YzIwZDA4N2RmNzY3NTNlMjhkNzY1ZWI4NGUxMzM4NDc5ZGQ2ZGYxMjFkZGRhM2U1OTU3NmQwYTMyYTViZlxcXCIgKHJlYWQta2V5c2V0IFxcXCI0YTdjMjBkMDg3ZGY3Njc1M2UyOGQ3NjVlYjg0ZTEzMzg0NzlkZDZkZjEyMWRkZGEzZTU5NTc2ZDBhMzJhNWJmXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazpmYWI5MmM0N2NjYjFmZGQyNTE3M2UyOTgxZWJiMjlkZTk4OWFjZWE4ODc5MmUzNGNjODcyOThmNmExNTc4ODQyXFxcIiAocmVhZC1rZXlzZXQgXFxcImZhYjkyYzQ3Y2NiMWZkZDI1MTczZTI5ODFlYmIyOWRlOTg5YWNlYTg4NzkyZTM0Y2M4NzI5OGY2YTE1Nzg4NDJcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOmNiYjk0YjEyZjU1YmQ1NDBhNWFhNTA3NjM1MTExOTM4ZjIyODJjMjg5MWI3OGMxM2U0NjJmYTAxOGM0MTQ1ZDhcXFwiIChyZWFkLWtleXNldCBcXFwiY2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOFxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6ODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYlxcXCIgKHJlYWQta2V5c2V0IFxcXCI4OWYwYTg0MGUzYmFiNGMzMmUwN2IxNDg2MmYwYTBmNDE0NTQzZGEwMzZlY2YzMzlkM2VkN2VjOTNmYTRhNDFiXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazo0YzczNTY2YjVkOGZhNGNkMmEyNjIwZjdiMmJjNTcyODRjN2ZjNWM0OGY4ZjBkYjJiZjE2NzVjYWQwOGRjNGFmXFxcIiAocmVhZC1rZXlzZXQgXFxcIjRjNzM1NjZiNWQ4ZmE0Y2QyYTI2MjBmN2IyYmM1NzI4NGM3ZmM1YzQ4ZjhmMGRiMmJmMTY3NWNhZDA4ZGM0YWZcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOjFjODM1ZDRlNjc5MTdmZDI1NzgxYjExZGIxYzEyZWZiYzQyOTZjNWM3ZmU5ODFkMzViYmNmNGE0NmE1MzQ0MWZcXFwiIChyZWFkLWtleXNldCBcXFwiMWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZlxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6NTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NFxcXCIgKHJlYWQta2V5c2V0IFxcXCI1NTQ3NTRmNDhiMTZkZjI0YjU1MmY2ODMyZGRhMDkwNjQyZWQ5NjU4NTU5ZmVmOWYzZWUxYmI0NjM3ZWE3Yzk0XFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazozZTgzMjEzYmVkZjZiZTBhYWY4ZWQzZjhlNzJmZmI5YTI0MDkzMGI4Njk4Yjk2NDZlOGQ5MTNiNjFjOTNlNGFiXFxcIiAocmVhZC1rZXlzZXQgXFxcIjNlODMyMTNiZWRmNmJlMGFhZjhlZDNmOGU3MmZmYjlhMjQwOTMwYjg2OThiOTY0NmU4ZDkxM2I2MWM5M2U0YWJcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOjVlNGE4ZDUxZmEzOTU4MDI2Y2RkZDkzYmJkODc0ZTFiZGFlMTQ5YTEyYWM2ZjAxNTJlYTI1Mzk0MmVmYzk4OWNcXFwiIChyZWFkLWtleXNldCBcXFwiNWU0YThkNTFmYTM5NTgwMjZjZGRkOTNiYmQ4NzRlMWJkYWUxNDlhMTJhYzZmMDE1MmVhMjUzOTQyZWZjOTg5Y1xcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6MTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYVxcXCIgKHJlYWQta2V5c2V0IFxcXCIxNTRkNmYyMzk4NjRjMTljNDNlZjM3N2MwM2NjOGRmOGUwZDFlNzkyYTE0M2EwMTQyMzUwMzAyOGNlOTYzYWZhXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazo0OWI0MTAzNGFiOWJhMGI3ZmQ5MmZkNTNlYjc5ZDdmZDNkNjAxMTkxYTE1MjY5NjgyODMwZjljOTJlMzY4OGFkXFxcIiAocmVhZC1rZXlzZXQgXFxcIjQ5YjQxMDM0YWI5YmEwYjdmZDkyZmQ1M2ViNzlkN2ZkM2Q2MDExOTFhMTUyNjk2ODI4MzBmOWM5MmUzNjg4YWRcXFwiKSAxMDAwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwidGVzdG5ldC1ncmFudHMtMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJtTWd5SzFEam9Ea3pndnF0eG9pbzhZS3hfcmNjZS1oVHdtZFB2Rzl6YldFIiwibG9ncyI6Inkwcl9hODB2QzJhc2RsZW9GWVhXT1dHVlR2QVROWUlYRFR2MHRkTTgtZ28iLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJhbmFzdGFzaWEiLDEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJlbWlseSIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImhlZWt5dW4iLDEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJqZWZmIiwxMDAwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwibGVhaCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImxpbmRhIiwxMDAwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwic3R1YXJ0IiwxMDAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJ3aWxsIiwxMDAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJLYWRlbmEiLDEwMDAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NGE3YzIwZDA4N2RmNzY3NTNlMjhkNzY1ZWI4NGUxMzM4NDc5ZGQ2ZGYxMjFkZGRhM2U1OTU3NmQwYTMyYTViZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6ZmFiOTJjNDdjY2IxZmRkMjUxNzNlMjk4MWViYjI5ZGU5ODlhY2VhODg3OTJlMzRjYzg3Mjk4ZjZhMTU3ODg0MiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6Y2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6ODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NGM3MzU2NmI1ZDhmYTRjZDJhMjYyMGY3YjJiYzU3Mjg0YzdmYzVjNDhmOGYwZGIyYmYxNjc1Y2FkMDhkYzRhZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6MWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6M2U4MzIxM2JlZGY2YmUwYWFmOGVkM2Y4ZTcyZmZiOWEyNDA5MzBiODY5OGI5NjQ2ZThkOTEzYjYxYzkzZTRhYiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NWU0YThkNTFmYTM5NTgwMjZjZGRkOTNiYmQ4NzRlMWJkYWUxNDlhMTJhYzZmMDE1MmVhMjUzOTQyZWZjOTg5YyIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6MTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYSIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NDliNDEwMzRhYjliYTBiN2ZkOTJmZDUzZWI3OWQ3ZmQzZDYwMTE5MWExNTI2OTY4MjgzMGY5YzkyZTM2ODhhZCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9XSwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6OH0") ] diff --git a/src/Chainweb/BlockHeader/Genesis/Testnet051to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Testnet051to19Payload.hs index 70ae0c52f8..9066e953e7 100644 --- a/src/Chainweb/BlockHeader/Genesis/Testnet051to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Testnet051to19Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "uYCVzPyI_LfFpn96YMtxJDN2-a_iJxLG0vHzzqmSz5k" + expectedHash = unsafeFromText "mPKc96IGEqyu2Vgo0_e4d1kDJUqZwyteX3oD36fIMqI" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -33,7 +33,7 @@ payloadBlock , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") - , (unsafeFromText "eyJoYXNoIjoiRlAyekNwNnRLaXlub011REJKNTVWc1Z6dHlyRGpKMVNhck5ycElIZWtQTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiU0IgPDQzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDI1PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiUFNfQzNcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIkZUU19DMVwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjI5ZjcxMDdiMzlmNmMxOTQ3Zjk2NjIyZjY4OWJlYzY4ZTAxNTczMDc2ZDYwNjExZDNlZjZiOGNiYzQzY2ZhMGJcIixcIjdjMjFmN2I1M2M4MTY3OTgxNTI0ODcwMjRkNzFiZmNiZDQ3YjViODBhZTQzNTk0Yjk0ZGQzODQ1NDgxOGFlZmNcIixcIjFmZjk2OTk0NWFiNjA4OGNiZDNmM2E2YTZiOTcxODc2OTcxOTI3NjNkNzhmMjdmNTViZGRkYmJjMTU3YzAwYzBcIl19LFwiS2FkZW5hXCI6e1wicHJlZFwiOlwia2V5cy1hbGxcIixcImtleXNcIjpbXCIzNWZmMTI5OGM1NmZmYjYwMjI1YmVlZDMyYTYxYWRhNDFhNTdlMjM5ODI2ZWVhOThlZDI5ZTQ4ZmY4YmFjNDNmXCJdfSxcIkVtaWx5XCI6e1wicHJlZFwiOlwia2V5cy1hbGxcIixcImtleXNcIjpbXCIzNWZmMTI5OGM1NmZmYjYwMjI1YmVlZDMyYTYxYWRhNDFhNTdlMjM5ODI2ZWVhOThlZDI5ZTQ4ZmY4YmFjNDNmXCJdfSxcIlNBIDw0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDI2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiUFNfQzhcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIlNCIDw0MD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNBIDw3PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiQ1MxX0MyXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiOTY1MjlkYmI4MzkwMjc1NDg5MTJiYzZhMWNmZmI2OWM2NzdlZDk3NjYzNTAwMThjYzhjNDZhM2Y4MTlmZTE3NFwiLFwiZTUzNzA4NTY0ZjFhNzhmNmU1YTkzMDlmZDhiOWRkODU2NDY4MzJlMzc0MTYyY2QzYzY3NDIwZTIwMmE3ZTQ1YlwiLFwiNGJiNmI3MWUyZGRjNjEzOTIyNzRkMThkMTQyY2RjOGQ0ZjUwZTU0MzQzMDk4MTM4ZjlhN2QxN2ZiNTYyZDZlYVwiXX0sXCJTQSA8Mj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIkNvaW5saXN0IEdsb2JhbFwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjVhYzAyZDZjZTgwNDcxMmVjNTgyMmFiOTgxM2ZkNDgzMjY1YzEwNmRhM2ZmZjYwY2Q0OTI1ODRiYzI4OTY1ODFcIixcIjExMzYyNTI1MTY0NzU1Mzk4Mjk5YmRmMWQzZGUxZTRjNzVmYWNhOWQ3YzVkZjA0NTM5NTk5OTI4ZjRjOGQ5ZjdcIixcIjgyMTg1ODc1MmY3ZDhlOTg2YWI2YjA4Y2QwNDgxZmY0MDY4ZTMwOGNhMWQyZmY2OGFmMTI0MWVmNTc1Y2UyNTVcIl19LFwiU0IgPDk-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJFQl9DOVwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcIlNBIDwyMD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlBTX0M1XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQiA8MTg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8NDU-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MjM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MzU-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTc-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQSA8MTQ-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8Nj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlBTX0MyXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQiA8Mjk-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MzA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQSA8MTE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQSA8OD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNCIDwzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDMzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0EgPDEyPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiUFNfQzRcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIlNCIDwxMT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDw0MT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwyNz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlBTX0MxXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJDUzJfQzBcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI5YTJhMTJiYTc5MTkwOWEyYjVlOTJhYjMzNjZmNjBmNDY4OWU1MzMxZWYyODViNzA0ZWZhM2ZkODNiYzk1OWY0XCIsXCI3YmNmNzhmMDllNjRlMzdmZTQ3MDVkNWFiYjM3MzU0OTZmMzFmMDRkMzQ3YzEzOGY1ZGQzZjRlZTI2ODNhOTUyXCIsXCJmNTRlOWYyZDgxYmU0MjJhMDJhZGIwYTE3MmY4OTc1MmZjM2U1ZjlkYWU0MzUyMDg3NzZiZDVkODNhMmY2ZjI0XCJdfSxcIlNBIDw2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0EgPDE4PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDM5PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDQ2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDIwPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0EgPDE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8Nz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIk1BSU5ORVRcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJhNDk0NzM3OWM3ZGJiNzZlZTYwYmRlZGQ2NDg0N2ExNWFhNTY1N2VmNDA2MjAxZTQ2ODYxNDc1YzkxZDNjNTFmXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI0OTI2Zjk2M2VlNWEwYzZkZjY3YTFkY2UyNDJmYmMxMWE5MDBjNDMzMmM3N2JkZGZiMGRjNWI2NmY2OTdmZWQwXCJdfSxcIlNBIDwxNT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNCIDwzND5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwxNj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwzNz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwxNT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDw0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0EgPDE2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiUFNfQzBcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIlNUX0MxXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiY2NmNmZhMWZkMzJlNThiMDQwNWYxNjlmZmI0NTVhMWEwNDM0Y2ZhZmY1Yjg1NDk4NjI4MjFmZjZiNTUxZDViM1wiLFwiYWY4MThhNWU4NjcyYWI5M2IwODU5ODdiOTQyZTg3YWE1MmZmNmM4MWJmZmFmYWYzNDM3YzAxYzhhNDFhNmZhOFwiLFwiODc0NjgzMWM1NTgwOTUwZTc0YWVmODY2NTljNmY4ZjFhY2I3MjE1MDY3ZWQ5MWQxMWZlNjkxOTQ1ZjljOGIxZVwiXX0sXCJTQSA8MTM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8MzI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNCIDwyND5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNBIDw1PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiQ29pbmxpc3QgTm9uLVVTXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQiA8NDI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MjE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJhbGxvY2F0aW9uMDBcIjpbXCJkODJkMGRjZGU5ODI1NTA1ZDg2YWZiNmRjYzEwNDExZDZiNjdhNDI5YTc5ZTIxYmRhNGJiMTE5YmYyOGFiODcxXCJdLFwiUFNfQzdcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCIsXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCJdfSxcIlNCIDwzOD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlNBIDwxOT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNBIDwzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDE5PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDQ0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDIyPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIl19LFwiU0IgPDg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTQ-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MzY-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQSA8MTc-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8NT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcIlBTX0M5XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQSA8MTA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQSA8OT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlBTX0M2XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiLFwiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiXX0sXCJTQiA8MzE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8MTM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8Mjg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiXX0sXCJTQiA8Mj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCJdfSxcImFsbG9jYXRpb24wMVwiOltcImI0YzhhM2VhOTFkMzE0NmIwNTYwOTk0NzQwZjBlM2VlZDkxYzU5ZDJlZWNhMWRjOTlmMGMyODcyODQ1YzI5NGRcIl19LFwiY29kZVwiOlwiKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwyPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8Mz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDw0PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8ND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDw1PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8Nj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDw3PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8Nz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDg-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDw4PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8OT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDk-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxMD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDEwPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxMT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDEyPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxMz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDEzPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDE1PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxNj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDE2PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxNz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDE4PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxOT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDE5PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MjA-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwyMD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Nj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw3PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8OD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw5PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8OT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDEwPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTA-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxMT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDExPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxMj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDEzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxND5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDE0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxNT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxNz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDE3PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTg-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxOD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE5PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTk-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyMD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDIwPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MjE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyMT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDIyPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MjI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyMz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDIzPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MjQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDI1PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MjU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyNj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI2PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mjc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyNz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDI4PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mjg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyOT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI5PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzA-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzMD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDMxPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MzE-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzMj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDMyPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzM-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzMz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDM0PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MzQ-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzNT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDM1PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzY-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzNj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDM3PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mzc-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzOD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDM4PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mzk-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzOT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQwPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDA-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw0MT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDQxPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NDI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0Mj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw0ND5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDQ0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NDU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0NT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQ2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MwXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MxXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MxXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MyXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MyXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MzXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MzXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M0XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M0XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M1XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M1XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M2XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M2XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M3XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M3XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M4XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M4XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M5XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M5XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkVCX0M5XFxcIiAocmVhZC1rZXlzZXQgXFxcIkVCX0M5XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkNvaW5saXN0IE5vbi1VU1xcXCIgKHJlYWQta2V5c2V0IFxcXCJDb2lubGlzdCBOb24tVVNcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiQ29pbmxpc3QgR2xvYmFsXFxcIiAocmVhZC1rZXlzZXQgXFxcIkNvaW5saXN0IEdsb2JhbFxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJGVFNfQzFcXFwiIChyZWFkLWtleXNldCBcXFwiRlRTX0MxXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkNTMV9DMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJDUzFfQzJcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiQ1MyX0MwXFxcIiAocmVhZC1rZXlzZXQgXFxcIkNTMl9DMFxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTVF9DMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJTVF9DMVxcXCIpKVxcblxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJFbWlseVxcXCIgKHJlYWQta2V5c2V0IFxcXCJFbWlseVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJLYWRlbmFcXFwiIChyZWFkLWtleXNldCBcXFwiS2FkZW5hXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIk1BSU5ORVRcXFwiIChyZWFkLWtleXNldCBcXFwiTUFJTk5FVFxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwidGVzdG5ldC1rZXlzZXRzLU5cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkZQMnpDcDZ0S2l5bm9NdURCSjU1VnNWenR5ckRqSjFTYXJOcnBJSGVrUE0iLCJsb2dzIjoiY0NzZzlqbXRKalplOG5lZjF2VjBlU0p0cmhlUUtJeGFrUEFFVHhVVDFnayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") + , (unsafeFromText "eyJoYXNoIjoic2xSZzl5bURMYTRsaFNrbFZLM2N2U2NOaEVWS2kxdV9MUm1oYkhfX1lyRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiU0IgPDQzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDI1PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiUFNfQzNcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIkZUU19DMVwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjFmZjk2OTk0NWFiNjA4OGNiZDNmM2E2YTZiOTcxODc2OTcxOTI3NjNkNzhmMjdmNTViZGRkYmJjMTU3YzAwYzBcIixcIjI5ZjcxMDdiMzlmNmMxOTQ3Zjk2NjIyZjY4OWJlYzY4ZTAxNTczMDc2ZDYwNjExZDNlZjZiOGNiYzQzY2ZhMGJcIixcIjdjMjFmN2I1M2M4MTY3OTgxNTI0ODcwMjRkNzFiZmNiZDQ3YjViODBhZTQzNTk0Yjk0ZGQzODQ1NDgxOGFlZmNcIl19LFwiS2FkZW5hXCI6e1wicHJlZFwiOlwia2V5cy1hbGxcIixcImtleXNcIjpbXCIzNWZmMTI5OGM1NmZmYjYwMjI1YmVlZDMyYTYxYWRhNDFhNTdlMjM5ODI2ZWVhOThlZDI5ZTQ4ZmY4YmFjNDNmXCJdfSxcIkVtaWx5XCI6e1wicHJlZFwiOlwia2V5cy1hbGxcIixcImtleXNcIjpbXCIzNWZmMTI5OGM1NmZmYjYwMjI1YmVlZDMyYTYxYWRhNDFhNTdlMjM5ODI2ZWVhOThlZDI5ZTQ4ZmY4YmFjNDNmXCJdfSxcIlNBIDw0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDI2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiUFNfQzhcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIlNCIDw0MD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNBIDw3PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiQ1MxX0MyXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNGJiNmI3MWUyZGRjNjEzOTIyNzRkMThkMTQyY2RjOGQ0ZjUwZTU0MzQzMDk4MTM4ZjlhN2QxN2ZiNTYyZDZlYVwiLFwiOTY1MjlkYmI4MzkwMjc1NDg5MTJiYzZhMWNmZmI2OWM2NzdlZDk3NjYzNTAwMThjYzhjNDZhM2Y4MTlmZTE3NFwiLFwiZTUzNzA4NTY0ZjFhNzhmNmU1YTkzMDlmZDhiOWRkODU2NDY4MzJlMzc0MTYyY2QzYzY3NDIwZTIwMmE3ZTQ1YlwiXX0sXCJTQSA8Mj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIkNvaW5saXN0IEdsb2JhbFwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjExMzYyNTI1MTY0NzU1Mzk4Mjk5YmRmMWQzZGUxZTRjNzVmYWNhOWQ3YzVkZjA0NTM5NTk5OTI4ZjRjOGQ5ZjdcIixcIjVhYzAyZDZjZTgwNDcxMmVjNTgyMmFiOTgxM2ZkNDgzMjY1YzEwNmRhM2ZmZjYwY2Q0OTI1ODRiYzI4OTY1ODFcIixcIjgyMTg1ODc1MmY3ZDhlOTg2YWI2YjA4Y2QwNDgxZmY0MDY4ZTMwOGNhMWQyZmY2OGFmMTI0MWVmNTc1Y2UyNTVcIl19LFwiU0IgPDk-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJFQl9DOVwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcIlNBIDwyMD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlBTX0M1XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQiA8MTg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8NDU-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MjM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MzU-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTc-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQSA8MTQ-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8Nj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlBTX0MyXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQiA8Mjk-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MzA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQSA8MTE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQSA8OD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNCIDwzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDMzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0EgPDEyPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiUFNfQzRcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIlNCIDwxMT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDw0MT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwyNz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlBTX0MxXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJDUzJfQzBcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI3YmNmNzhmMDllNjRlMzdmZTQ3MDVkNWFiYjM3MzU0OTZmMzFmMDRkMzQ3YzEzOGY1ZGQzZjRlZTI2ODNhOTUyXCIsXCI5YTJhMTJiYTc5MTkwOWEyYjVlOTJhYjMzNjZmNjBmNDY4OWU1MzMxZWYyODViNzA0ZWZhM2ZkODNiYzk1OWY0XCIsXCJmNTRlOWYyZDgxYmU0MjJhMDJhZGIwYTE3MmY4OTc1MmZjM2U1ZjlkYWU0MzUyMDg3NzZiZDVkODNhMmY2ZjI0XCJdfSxcIlNBIDw2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0EgPDE4PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDM5PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDQ2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDIwPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0EgPDE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8Nz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIk1BSU5ORVRcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI0OTI2Zjk2M2VlNWEwYzZkZjY3YTFkY2UyNDJmYmMxMWE5MDBjNDMzMmM3N2JkZGZiMGRjNWI2NmY2OTdmZWQwXCIsXCJhNDk0NzM3OWM3ZGJiNzZlZTYwYmRlZGQ2NDg0N2ExNWFhNTY1N2VmNDA2MjAxZTQ2ODYxNDc1YzkxZDNjNTFmXCJdfSxcIlNBIDwxNT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNCIDwzND5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwxNj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwzNz5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwxNT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDw0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0EgPDE2PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiUFNfQzBcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIlNUX0MxXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiODc0NjgzMWM1NTgwOTUwZTc0YWVmODY2NTljNmY4ZjFhY2I3MjE1MDY3ZWQ5MWQxMWZlNjkxOTQ1ZjljOGIxZVwiLFwiYWY4MThhNWU4NjcyYWI5M2IwODU5ODdiOTQyZTg3YWE1MmZmNmM4MWJmZmFmYWYzNDM3YzAxYzhhNDFhNmZhOFwiLFwiY2NmNmZhMWZkMzJlNThiMDQwNWYxNjlmZmI0NTVhMWEwNDM0Y2ZhZmY1Yjg1NDk4NjI4MjFmZjZiNTUxZDViM1wiXX0sXCJTQSA8MTM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8MzI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNCIDwyND5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNBIDw1PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiQ29pbmxpc3QgTm9uLVVTXCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQiA8NDI-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MjE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJhbGxvY2F0aW9uMDBcIjpbXCJkODJkMGRjZGU5ODI1NTA1ZDg2YWZiNmRjYzEwNDExZDZiNjdhNDI5YTc5ZTIxYmRhNGJiMTE5YmYyOGFiODcxXCJdLFwiUFNfQzdcIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIwZTFhMGJjYTUxNDM3YjJmM2NiNWFkMDUxZGQwYzVlNWJjNTM1M2E5YzVkN2E2Y2MwMDVhZWIwODFhZmNjMWY4XCIsXCI3ZDkyODk3YWM0YzE3MWIxZTYxOTc4MmYxZmJiYmM0NzdkNjIyMjY4MzZlNzY3NjE1YjdjZTdlZmFhMjVhYTk1XCIsXCJjOGNlMTYyNDc5OTg0NmI1Y2I3YjFmMTlmNTY3NmYxMTZkNTRiNWU4NTc3NmJkMjhmMWI5YjhmZDY4ZDkwMTMwXCJdfSxcIlNCIDwzOD5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlNBIDwxOT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlNBIDwzPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjU4MjExMzlmMmQwMzM5YTE4ZGNiNGUxNWZlZmUwNWQ3YTdhNTUxN2VmZmU5MzI3ODMxYjI5Mzc1ODI4OTljYjFcIixcIjU5OWVjNTU4YWJhNjY0NTUxMzEzYTBmYmU3NmRkMDI3MjRmYTI0NWM5MDk1MmNhMDM4ZWNkYmZlNzI5ZDQ4M2FcIixcImZkMTMyOTFhNDExYzM0MWVmNjI2NWQ4OWNjMTQwMTNmNWE2NzEyZWE2N2NlMzk1YzMzYjBhYTY0MWVjYzU3YmZcIl19LFwiU0IgPDE5PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDQ0PlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDIyPlwiOntcInByZWRcIjpcImtleXMtMlwiLFwia2V5c1wiOltcIjIzMmM5ZDJkNzJmNDcxOTAyNDJlNmZkMzZjNjA1MjVhYjY5OWZjZDNiZDIyZjg0ZjA2MTM0MjYwOTEwNDE1YWZcIixcIjliMzM4Mzc5YzI3OWEwNGJmYjRmZmNlNDBjM2E4ODZkNGJkYTljN2QzYzI5OWE0ODNkYzI5Y2NjM2M4YzU1MzlcIixcImU0MjYzNGY3NWUwZDI3YTk1ZTVkZTBmMTMxN2U0NDIyMTA3NzUzNWIyMDA3NjdlZDQyY2E0MzBmN2Y2NWU4NmJcIl19LFwiU0IgPDg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTQ-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MzY-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQSA8MTc-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQiA8NT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcIlBTX0M5XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQSA8MTA-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiNTgyMTEzOWYyZDAzMzlhMThkY2I0ZTE1ZmVmZTA1ZDdhN2E1NTE3ZWZmZTkzMjc4MzFiMjkzNzU4Mjg5OWNiMVwiLFwiNTk5ZWM1NThhYmE2NjQ1NTEzMTNhMGZiZTc2ZGQwMjcyNGZhMjQ1YzkwOTUyY2EwMzhlY2RiZmU3MjlkNDgzYVwiLFwiZmQxMzI5MWE0MTFjMzQxZWY2MjY1ZDg5Y2MxNDAxM2Y1YTY3MTJlYTY3Y2UzOTVjMzNiMGFhNjQxZWNjNTdiZlwiXX0sXCJTQSA8OT5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCI1ODIxMTM5ZjJkMDMzOWExOGRjYjRlMTVmZWZlMDVkN2E3YTU1MTdlZmZlOTMyNzgzMWIyOTM3NTgyODk5Y2IxXCIsXCI1OTllYzU1OGFiYTY2NDU1MTMxM2EwZmJlNzZkZDAyNzI0ZmEyNDVjOTA5NTJjYTAzOGVjZGJmZTcyOWQ0ODNhXCIsXCJmZDEzMjkxYTQxMWMzNDFlZjYyNjVkODljYzE0MDEzZjVhNjcxMmVhNjdjZTM5NWMzM2IwYWE2NDFlY2M1N2JmXCJdfSxcIlBTX0M2XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMGUxYTBiY2E1MTQzN2IyZjNjYjVhZDA1MWRkMGM1ZTViYzUzNTNhOWM1ZDdhNmNjMDA1YWViMDgxYWZjYzFmOFwiLFwiN2Q5Mjg5N2FjNGMxNzFiMWU2MTk3ODJmMWZiYmJjNDc3ZDYyMjI2ODM2ZTc2NzYxNWI3Y2U3ZWZhYTI1YWE5NVwiLFwiYzhjZTE2MjQ3OTk4NDZiNWNiN2IxZjE5ZjU2NzZmMTE2ZDU0YjVlODU3NzZiZDI4ZjFiOWI4ZmQ2OGQ5MDEzMFwiXX0sXCJTQiA8MzE-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8MTM-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8Mjg-XCI6e1wicHJlZFwiOlwia2V5cy0yXCIsXCJrZXlzXCI6W1wiMjMyYzlkMmQ3MmY0NzE5MDI0MmU2ZmQzNmM2MDUyNWFiNjk5ZmNkM2JkMjJmODRmMDYxMzQyNjA5MTA0MTVhZlwiLFwiOWIzMzgzNzljMjc5YTA0YmZiNGZmY2U0MGMzYTg4NmQ0YmRhOWM3ZDNjMjk5YTQ4M2RjMjljY2MzYzhjNTUzOVwiLFwiZTQyNjM0Zjc1ZTBkMjdhOTVlNWRlMGYxMzE3ZTQ0MjIxMDc3NTM1YjIwMDc2N2VkNDJjYTQzMGY3ZjY1ZTg2YlwiXX0sXCJTQiA8Mj5cIjp7XCJwcmVkXCI6XCJrZXlzLTJcIixcImtleXNcIjpbXCIyMzJjOWQyZDcyZjQ3MTkwMjQyZTZmZDM2YzYwNTI1YWI2OTlmY2QzYmQyMmY4NGYwNjEzNDI2MDkxMDQxNWFmXCIsXCI5YjMzODM3OWMyNzlhMDRiZmI0ZmZjZTQwYzNhODg2ZDRiZGE5YzdkM2MyOTlhNDgzZGMyOWNjYzNjOGM1NTM5XCIsXCJlNDI2MzRmNzVlMGQyN2E5NWU1ZGUwZjEzMTdlNDQyMjEwNzc1MzViMjAwNzY3ZWQ0MmNhNDMwZjdmNjVlODZiXCJdfSxcImFsbG9jYXRpb24wMVwiOltcImI0YzhhM2VhOTFkMzE0NmIwNTYwOTk0NzQwZjBlM2VlZDkxYzU5ZDJlZWNhMWRjOTlmMGMyODcyODQ1YzI5NGRcIl19LFwiY29kZVwiOlwiKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwyPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8Mz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDw0PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8ND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDw1PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8Nj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDw3PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8Nz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDg-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDw4PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8OT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDk-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxMD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDEwPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxMT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDEyPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxMz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDEzPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDE1PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxNj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDE2PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MTc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwxNz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0EgPDE4PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQSA8MTg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNBIDwxOT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0EgPDE5PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQSA8MjA-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNBIDwyMD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Nj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw3PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8OD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw5PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8OT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDEwPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTA-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxMT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDExPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxMj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDEzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxND5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDE0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxNT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwxNz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDE3PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MTg-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwxOD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDE5PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MTk-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyMD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDIwPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MjE-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyMT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDIyPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MjI-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyMz5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDIzPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MjQ-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyND5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDI1PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MjU-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyNj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI2PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mjc-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwyNz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDI4PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mjg-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwyOT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDI5PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzA-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzMD5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDMxPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MzE-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzMj5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDMyPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzM-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzMz5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDM0PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8MzQ-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzNT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDM1PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8MzY-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzNj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDM3PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8Mzc-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDwzOD5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDM4PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8Mzk-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDwzOT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQwPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDA-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw0MT5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDQxPlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NDI-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0Mj5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQzPlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDM-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlNCIDw0ND5cXFwiIChyZWFkLWtleXNldCBcXFwiU0IgPDQ0PlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTQiA8NDU-XFxcIiAocmVhZC1rZXlzZXQgXFxcIlNCIDw0NT5cXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiU0IgPDQ2PlxcXCIgKHJlYWQta2V5c2V0IFxcXCJTQiA8NDY-XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MwXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MxXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MxXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MyXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MyXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0MzXFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0MzXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M0XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M0XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M1XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M1XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M2XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M2XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M3XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M3XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M4XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M4XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIlBTX0M5XFxcIiAocmVhZC1rZXlzZXQgXFxcIlBTX0M5XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkVCX0M5XFxcIiAocmVhZC1rZXlzZXQgXFxcIkVCX0M5XFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkNvaW5saXN0IE5vbi1VU1xcXCIgKHJlYWQta2V5c2V0IFxcXCJDb2lubGlzdCBOb24tVVNcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiQ29pbmxpc3QgR2xvYmFsXFxcIiAocmVhZC1rZXlzZXQgXFxcIkNvaW5saXN0IEdsb2JhbFxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJGVFNfQzFcXFwiIChyZWFkLWtleXNldCBcXFwiRlRTX0MxXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIkNTMV9DMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJDUzFfQzJcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiQ1MyX0MwXFxcIiAocmVhZC1rZXlzZXQgXFxcIkNTMl9DMFxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJTVF9DMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJTVF9DMVxcXCIpKVxcblxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJFbWlseVxcXCIgKHJlYWQta2V5c2V0IFxcXCJFbWlseVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJLYWRlbmFcXFwiIChyZWFkLWtleXNldCBcXFwiS2FkZW5hXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcIk1BSU5ORVRcXFwiIChyZWFkLWtleXNldCBcXFwiTUFJTk5FVFxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwidGVzdG5ldC1rZXlzZXRzLU5cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6InNsUmc5eW1ETGE0bGhTa2xWSzNjdlNjTmhFVktpMXVfTFJtaGJIX19ZckUiLCJsb2dzIjoiY0NzZzlqbXRKalplOG5lZjF2VjBlU0p0cmhlUUtJeGFrUEFFVHhVVDFnayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiV2hKVTlyMXZWc0NOdmFWdUdBaHRYempMdTR3UlR3TGsxNlQ2OEY2N3FMdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcIkVtaWx5XFxcIiAodGltZSBcXFwiMjAxOS0xMC0wMVQwMDowMDowMFpcXFwiKSBcXFwiRW1pbHlcXFwiIDY2NjY2Ni42NylcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJNQUlOTkVUXzFcXFwiICh0aW1lIFxcXCIyMDE5LTEwLTI5VDE0OjAwOjAwWlxcXCIpIFxcXCJNQUlOTkVUXFxcIiA2NjY2NjYuNjcpXFxuKGNvaW4uY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudCBcXFwiTUFJTk5FVF8yXFxcIiAodGltZSBcXFwiMjAxOS0xMC0yOVQxNzowMDowMFpcXFwiKSBcXFwiTUFJTk5FVFxcXCIgNjY2NjY2LjY3KVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcIk1BSU5ORVRfM1xcXCIgKHRpbWUgXFxcIjIwMTktMTEtMjlUMTc6MDA6MDBaXFxcIikgXFxcIk1BSU5ORVRcXFwiIDY2NjY2Ni42NylcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDBcXFwiICh0aW1lIFxcXCIxOTAwLTEwLTE1VDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDBcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDFcXFwiICh0aW1lIFxcXCIyMDIwLTEwLTMxVDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uMDJcXFwiICh0aW1lIFxcXCIxOTAwLTEwLTMxVDE4OjAwOjAwWlxcXCIpIFxcXCJhbGxvY2F0aW9uMDJcXFwiIDEwMDAwMDAuMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcInRlc3RuZXQtYWxsb2NhdGlvbnMtMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJXaEpVOXIxdlZzQ052YVZ1R0FodFh6akx1NHdSVHdMazE2VDY4RjY3cUx3IiwibG9ncyI6IkI1elZaYVRDWk9sbERjR2tGRDRhcHJPTi1mUlY1ZFlUWEJyTmtSSVo4LTQiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoieDZKbnc4YXhaYVJLTVphVEtmTzA2dXMyQTRrRTBES2owRm5BVEZXVWVXVSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiNGM3MzU2NmI1ZDhmYTRjZDJhMjYyMGY3YjJiYzU3Mjg0YzdmYzVjNDhmOGYwZGIyYmYxNjc1Y2FkMDhkYzRhZlwiOltcIjRjNzM1NjZiNWQ4ZmE0Y2QyYTI2MjBmN2IyYmM1NzI4NGM3ZmM1YzQ4ZjhmMGRiMmJmMTY3NWNhZDA4ZGM0YWZcIl0sXCJlbWlseVwiOltcIjM2ODA1OWFiMjViZWM5MmYxOWE0ZjhjZTE3MjAzMGU1ZDY3MmU0ODc5YWFhZThkNWJlOGI3MDVkZDNkZmFlN2ZcIl0sXCJjYmI5NGIxMmY1NWJkNTQwYTVhYTUwNzYzNTExMTkzOGYyMjgyYzI4OTFiNzhjMTNlNDYyZmEwMThjNDE0NWQ4XCI6W1wiY2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOFwiXSxcIjQ5YjQxMDM0YWI5YmEwYjdmZDkyZmQ1M2ViNzlkN2ZkM2Q2MDExOTFhMTUyNjk2ODI4MzBmOWM5MmUzNjg4YWRcIjpbXCI0OWI0MTAzNGFiOWJhMGI3ZmQ5MmZkNTNlYjc5ZDdmZDNkNjAxMTkxYTE1MjY5NjgyODMwZjljOTJlMzY4OGFkXCJdLFwiYW5hc3Rhc2lhXCI6W1wiMWVjNzRkZTcyNGIzYzcwMDczN2Q5OTkwNmFiYTQ4ZGJlZDZiOTAxNDBiYmM2NDEzM2M1NTg1NzFiMTg4ODNjM1wiXSxcIjVlNGE4ZDUxZmEzOTU4MDI2Y2RkZDkzYmJkODc0ZTFiZGFlMTQ5YTEyYWM2ZjAxNTJlYTI1Mzk0MmVmYzk4OWNcIjpbXCI1ZTRhOGQ1MWZhMzk1ODAyNmNkZGQ5M2JiZDg3NGUxYmRhZTE0OWExMmFjNmYwMTUyZWEyNTM5NDJlZmM5ODljXCJdLFwiMWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZlwiOltcIjFjODM1ZDRlNjc5MTdmZDI1NzgxYjExZGIxYzEyZWZiYzQyOTZjNWM3ZmU5ODFkMzViYmNmNGE0NmE1MzQ0MWZcIl0sXCI4OWYwYTg0MGUzYmFiNGMzMmUwN2IxNDg2MmYwYTBmNDE0NTQzZGEwMzZlY2YzMzlkM2VkN2VjOTNmYTRhNDFiXCI6W1wiODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYlwiXSxcImZhYjkyYzQ3Y2NiMWZkZDI1MTczZTI5ODFlYmIyOWRlOTg5YWNlYTg4NzkyZTM0Y2M4NzI5OGY2YTE1Nzg4NDJcIjpbXCJmYWI5MmM0N2NjYjFmZGQyNTE3M2UyOTgxZWJiMjlkZTk4OWFjZWE4ODc5MmUzNGNjODcyOThmNmExNTc4ODQyXCJdLFwiamVmZlwiOltcImM1MzIxN2JiMDUzYjgyN2I1ZmJiYjMyMmZjZWQ2N2E4NjI5OGVjNDBhNTlhZWVmMTViMjE5MGEwNWFkMGE2YmRcIl0sXCJzdHVhcnRcIjpbXCIwZTMxODBkZDRhZTlkNDM4NWEyMWQ3Y2E5NWNjNzIyOWIwZjZiMmNhOWUwZjQ4NjFkZDRlZWQ2ODUwMDRkZjY2XCJdLFwibGVhaFwiOltcIjIxNjY0MzEzN2ZiNWY2YTEyN2RiYjEwNjM3MDJjYmI3YzFmMTUzNDUxYTFhZDRhZmE5ZjkyZTc3NDQ3Y2MzMzBcIl0sXCIxNTRkNmYyMzk4NjRjMTljNDNlZjM3N2MwM2NjOGRmOGUwZDFlNzkyYTE0M2EwMTQyMzUwMzAyOGNlOTYzYWZhXCI6W1wiMTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYVwiXSxcIjNlODMyMTNiZWRmNmJlMGFhZjhlZDNmOGU3MmZmYjlhMjQwOTMwYjg2OThiOTY0NmU4ZDkxM2I2MWM5M2U0YWJcIjpbXCIzZTgzMjEzYmVkZjZiZTBhYWY4ZWQzZjhlNzJmZmI5YTI0MDkzMGI4Njk4Yjk2NDZlOGQ5MTNiNjFjOTNlNGFiXCJdLFwid2lsbFwiOltcIjgzMGFkNzczNTEwYWIwZjVhMGQ0NTY0YjdiMzFkMmIwYjFiYTdmZDVhMTE2ZjY2NTk4YTFjNjNjYzIxNDJmZDVcIl0sXCJsaW5kYVwiOltcImUxMDAyZGIwODk0Mjk5YmIzNzIzOGJhYjAyMTcwMTgwYzBhZTU2ZjU5MzIxNjliYjJmY2Y3NTYwNTlmZWExNzVcIl0sXCI1NTQ3NTRmNDhiMTZkZjI0YjU1MmY2ODMyZGRhMDkwNjQyZWQ5NjU4NTU5ZmVmOWYzZWUxYmI0NjM3ZWE3Yzk0XCI6W1wiNTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NFwiXSxcIjRhN2MyMGQwODdkZjc2NzUzZTI4ZDc2NWViODRlMTMzODQ3OWRkNmRmMTIxZGRkYTNlNTk1NzZkMGEzMmE1YmZcIjpbXCI0YTdjMjBkMDg3ZGY3Njc1M2UyOGQ3NjVlYjg0ZTEzMzg0NzlkZDZkZjEyMWRkZGEzZTU5NTc2ZDBhMzJhNWJmXCJdLFwiaGVla3l1blwiOltcImRmYjE2YjEzZTQwMzJhNjg3OGZkOTg1MDZiMjJjYjBkNmU1OTMyYzU0MWU2NTZiN2VlNWQ2OWQ3MmU2ZWI3NmVcIl19LFwiY29kZVwiOlwiKGNvaW4uY29pbmJhc2UgXFxcImFuYXN0YXNpYVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbmFzdGFzaWFcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJlbWlseVxcXCIgKHJlYWQta2V5c2V0IFxcXCJlbWlseVxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImhlZWt5dW5cXFwiIChyZWFkLWtleXNldCBcXFwiaGVla3l1blxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImplZmZcXFwiIChyZWFkLWtleXNldCBcXFwiamVmZlxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImxlYWhcXFwiIChyZWFkLWtleXNldCBcXFwibGVhaFxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImxpbmRhXFxcIiAocmVhZC1rZXlzZXQgXFxcImxpbmRhXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwic3R1YXJ0XFxcIiAocmVhZC1rZXlzZXQgXFxcInN0dWFydFxcXCIpIDEwMDAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwid2lsbFxcXCIgKHJlYWQta2V5c2V0IFxcXCJ3aWxsXFxcIikgMTAwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJLYWRlbmFcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJLYWRlbmFcXFwiKSAxMDAwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6NGE3YzIwZDA4N2RmNzY3NTNlMjhkNzY1ZWI4NGUxMzM4NDc5ZGQ2ZGYxMjFkZGRhM2U1OTU3NmQwYTMyYTViZlxcXCIgKHJlYWQta2V5c2V0IFxcXCI0YTdjMjBkMDg3ZGY3Njc1M2UyOGQ3NjVlYjg0ZTEzMzg0NzlkZDZkZjEyMWRkZGEzZTU5NTc2ZDBhMzJhNWJmXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazpmYWI5MmM0N2NjYjFmZGQyNTE3M2UyOTgxZWJiMjlkZTk4OWFjZWE4ODc5MmUzNGNjODcyOThmNmExNTc4ODQyXFxcIiAocmVhZC1rZXlzZXQgXFxcImZhYjkyYzQ3Y2NiMWZkZDI1MTczZTI5ODFlYmIyOWRlOTg5YWNlYTg4NzkyZTM0Y2M4NzI5OGY2YTE1Nzg4NDJcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOmNiYjk0YjEyZjU1YmQ1NDBhNWFhNTA3NjM1MTExOTM4ZjIyODJjMjg5MWI3OGMxM2U0NjJmYTAxOGM0MTQ1ZDhcXFwiIChyZWFkLWtleXNldCBcXFwiY2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOFxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6ODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYlxcXCIgKHJlYWQta2V5c2V0IFxcXCI4OWYwYTg0MGUzYmFiNGMzMmUwN2IxNDg2MmYwYTBmNDE0NTQzZGEwMzZlY2YzMzlkM2VkN2VjOTNmYTRhNDFiXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazo0YzczNTY2YjVkOGZhNGNkMmEyNjIwZjdiMmJjNTcyODRjN2ZjNWM0OGY4ZjBkYjJiZjE2NzVjYWQwOGRjNGFmXFxcIiAocmVhZC1rZXlzZXQgXFxcIjRjNzM1NjZiNWQ4ZmE0Y2QyYTI2MjBmN2IyYmM1NzI4NGM3ZmM1YzQ4ZjhmMGRiMmJmMTY3NWNhZDA4ZGM0YWZcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOjFjODM1ZDRlNjc5MTdmZDI1NzgxYjExZGIxYzEyZWZiYzQyOTZjNWM3ZmU5ODFkMzViYmNmNGE0NmE1MzQ0MWZcXFwiIChyZWFkLWtleXNldCBcXFwiMWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZlxcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6NTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NFxcXCIgKHJlYWQta2V5c2V0IFxcXCI1NTQ3NTRmNDhiMTZkZjI0YjU1MmY2ODMyZGRhMDkwNjQyZWQ5NjU4NTU5ZmVmOWYzZWUxYmI0NjM3ZWE3Yzk0XFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazozZTgzMjEzYmVkZjZiZTBhYWY4ZWQzZjhlNzJmZmI5YTI0MDkzMGI4Njk4Yjk2NDZlOGQ5MTNiNjFjOTNlNGFiXFxcIiAocmVhZC1rZXlzZXQgXFxcIjNlODMyMTNiZWRmNmJlMGFhZjhlZDNmOGU3MmZmYjlhMjQwOTMwYjg2OThiOTY0NmU4ZDkxM2I2MWM5M2U0YWJcXFwiKSAxMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJrOjVlNGE4ZDUxZmEzOTU4MDI2Y2RkZDkzYmJkODc0ZTFiZGFlMTQ5YTEyYWM2ZjAxNTJlYTI1Mzk0MmVmYzk4OWNcXFwiIChyZWFkLWtleXNldCBcXFwiNWU0YThkNTFmYTM5NTgwMjZjZGRkOTNiYmQ4NzRlMWJkYWUxNDlhMTJhYzZmMDE1MmVhMjUzOTQyZWZjOTg5Y1xcXCIpIDEwMDAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcIms6MTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYVxcXCIgKHJlYWQta2V5c2V0IFxcXCIxNTRkNmYyMzk4NjRjMTljNDNlZjM3N2MwM2NjOGRmOGUwZDFlNzkyYTE0M2EwMTQyMzUwMzAyOGNlOTYzYWZhXFxcIikgMTAwMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiazo0OWI0MTAzNGFiOWJhMGI3ZmQ5MmZkNTNlYjc5ZDdmZDNkNjAxMTkxYTE1MjY5NjgyODMwZjljOTJlMzY4OGFkXFxcIiAocmVhZC1rZXlzZXQgXFxcIjQ5YjQxMDM0YWI5YmEwYjdmZDkyZmQ1M2ViNzlkN2ZkM2Q2MDExOTFhMTUyNjk2ODI4MzBmOWM5MmUzNjg4YWRcXFwiKSAxMDAwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwidGVzdG5ldC1ncmFudHMtTlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJ4NkpudzhheFphUktNWmFUS2ZPMDZ1czJBNGtFMERLajBGbkFURldVZVdVIiwibG9ncyI6Inkwcl9hODB2QzJhc2RsZW9GWVhXT1dHVlR2QVROWUlYRFR2MHRkTTgtZ28iLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJhbmFzdGFzaWEiLDEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJlbWlseSIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImhlZWt5dW4iLDEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJqZWZmIiwxMDAwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwibGVhaCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImxpbmRhIiwxMDAwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwic3R1YXJ0IiwxMDAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJ3aWxsIiwxMDAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJLYWRlbmEiLDEwMDAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NGE3YzIwZDA4N2RmNzY3NTNlMjhkNzY1ZWI4NGUxMzM4NDc5ZGQ2ZGYxMjFkZGRhM2U1OTU3NmQwYTMyYTViZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6ZmFiOTJjNDdjY2IxZmRkMjUxNzNlMjk4MWViYjI5ZGU5ODlhY2VhODg3OTJlMzRjYzg3Mjk4ZjZhMTU3ODg0MiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6Y2JiOTRiMTJmNTViZDU0MGE1YWE1MDc2MzUxMTE5MzhmMjI4MmMyODkxYjc4YzEzZTQ2MmZhMDE4YzQxNDVkOCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6ODlmMGE4NDBlM2JhYjRjMzJlMDdiMTQ4NjJmMGEwZjQxNDU0M2RhMDM2ZWNmMzM5ZDNlZDdlYzkzZmE0YTQxYiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NGM3MzU2NmI1ZDhmYTRjZDJhMjYyMGY3YjJiYzU3Mjg0YzdmYzVjNDhmOGYwZGIyYmYxNjc1Y2FkMDhkYzRhZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6MWM4MzVkNGU2NzkxN2ZkMjU3ODFiMTFkYjFjMTJlZmJjNDI5NmM1YzdmZTk4MWQzNWJiY2Y0YTQ2YTUzNDQxZiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NTU0NzU0ZjQ4YjE2ZGYyNGI1NTJmNjgzMmRkYTA5MDY0MmVkOTY1ODU1OWZlZjlmM2VlMWJiNDYzN2VhN2M5NCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6M2U4MzIxM2JlZGY2YmUwYWFmOGVkM2Y4ZTcyZmZiOWEyNDA5MzBiODY5OGI5NjQ2ZThkOTEzYjYxYzkzZTRhYiIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NWU0YThkNTFmYTM5NTgwMjZjZGRkOTNiYmQ4NzRlMWJkYWUxNDlhMTJhYzZmMDE1MmVhMjUzOTQyZWZjOTg5YyIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6MTU0ZDZmMjM5ODY0YzE5YzQzZWYzNzdjMDNjYzhkZjhlMGQxZTc5MmExNDNhMDE0MjM1MDMwMjhjZTk2M2FmYSIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsIms6NDliNDEwMzRhYjliYTBiN2ZkOTJmZDUzZWI3OWQ3ZmQzZDYwMTE5MWExNTI2OTY4MjgzMGY5YzkyZTM2ODhhZCIsMTAwMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9XSwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6OH0") ] diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index 2d30750ae2..b66da877e5 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -121,6 +121,7 @@ module Chainweb.BlockHeader.Internal , childBlockHeight , parentBlockHeight , genesisParentBlockHash +, genesisRankedParentBlockHash , genesisBlockHeader , genesisBlockHeaders , genesisBlockHeadersAtHeight @@ -652,6 +653,11 @@ genesisParentBlockHash v p = Parent $ BlockHash $ MerkleLogHash , encodeMerkleInputNode encodeChainId (_chainId p) ] +genesisRankedParentBlockHash :: HasChainId p => ChainwebVersion -> p -> Parent RankedBlockHash +genesisRankedParentBlockHash v p = Parent $ RankedBlockHash + (genesisHeight v (_chainId p)) + (unwrapParent $ genesisParentBlockHash v p) + {-# NOINLINE genesisBlockHeaderCache #-} genesisBlockHeaderCache :: IORef (HashMap ChainwebVersionCode (HashMap ChainId BlockHeader)) genesisBlockHeaderCache = unsafePerformIO $ do diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 34a6f061f8..7ead0ceecf 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -166,10 +166,10 @@ readFrom logger v cid sql newBlockCtx parent doRead = do , _bctxChainId = cid } liftIO $ withSavepoint sql ReadFromSavepoint $ do - latestHeader <- _syncStateRankedBlockHash . _consensusStateLatest <$> - ChainwebPactDb.throwOnDbError (fromJuste <$> ChainwebPactDb.getConsensusState sql) + !latestHeader <- maybe (genesisRankedParentBlockHash v cid) (Parent . _syncStateRankedBlockHash . _consensusStateLatest) <$> + ChainwebPactDb.throwOnDbError (ChainwebPactDb.getConsensusState sql) -- is the parent the latest header, i.e., can we get away without rewinding? - let parentIsLatestHeader = latestHeader == unwrapParent parent + let parentIsLatestHeader = latestHeader == parent let currentHeight = _bctxCurrentBlockHeight blockCtx if pact5 v cid currentHeight then PactDb.getEndTxId v cid sql parent >>= traverse \startTxId -> do diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 7604699da3..713ec03de1 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -103,10 +103,18 @@ module Chainweb.Pact.Types , TransactionOutputProofB64(..) , TxInvalidError(..) + , _BuyGasError + , _RedeemGasError + , _PurchaseGasTxTooBigForGasLimit + , _TxInsertError + , _TxExceedsBlockGasLimit , BlockInvalidError(..) , BlockOutputMismatchError(..) , BuyGasError(..) + , _BuyGasPactError + , _BuyGasMultipleGasPayerCaps , RedeemGasError(..) + , _RedeemGasPactError , logg_ , logDebug_ @@ -644,10 +652,14 @@ data BuyGasError | BuyGasMultipleGasPayerCaps deriving stock (Show, Eq, Generic) +makePrisms ''BuyGasError + data RedeemGasError = RedeemGasPactError !(Pact.PactError Pact.Info) deriving stock (Show, Eq, Generic) +makePrisms ''RedeemGasError + data TxInvalidError = BuyGasError !BuyGasError | RedeemGasError !RedeemGasError @@ -656,6 +668,8 @@ data TxInvalidError | TxExceedsBlockGasLimit !Int deriving stock (Show, Eq, Generic) +makePrisms ''TxInvalidError + data BlockInvalidError = BlockInvalidDueToOutputMismatch BlockOutputMismatchError | BlockInvalidDueToInvalidTxs (NonEmpty (Pact.RequestKey, InsertError)) diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index 7255349a3b..b849d6dfe0 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -278,9 +278,9 @@ quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ \v -> v $ HM.singleton (BlockHeight 1, TxBlockIdx 0) (Pact.Gas 1) } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ [] -- TODO: PP - -- (unsafeChainId 0, QPIN0.payloadBlock) : - -- [(n, QPINN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + { _genesisBlockPayload = onChains $ + (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : + [(n, _payloadWithOutputsPayloadHash INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index 46609bb2f1..02984faed7 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -64,7 +64,7 @@ import Pact.Core.Persistence hiding (pactDb) import Pact.Core.SPV (noSPVSupport) import Pact.Core.Signer import Pact.Core.Verifiers -import Pact.Types.KeySet qualified as Pact +import Pact.Core.Guards qualified as Pact import Pact.JSON.Encode qualified as J import PropertyMatchers ((?), pattern (:=>)) import PropertyMatchers qualified as P @@ -83,13 +83,13 @@ import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as PIN0 tests :: RocksDb -> TestTree tests baseRdb = testGroup "Pact5 TransactionExecTest" [ testCase "buyGas should take gas tokens from the transaction sender" (buyGasShouldTakeGasTokensFromTheTransactionSender baseRdb) - -- , testCase "buyGas failures" (buyGasFailures baseRdb) - -- , testCase "redeem gas should give gas tokens to the transaction sender and miner" (redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner baseRdb) - -- , testCase "redeem gas failure" (redeemGasFailure baseRdb) - -- , testCase "purchase gas tx too big" (purchaseGasTxTooBig baseRdb) - -- , testCase "run payload should return an EvalResult related to the input command" (runPayloadShouldReturnEvalResultRelatedToTheInputCommand baseRdb) - -- , testCase "applyLocal spec" (applyLocalSpec baseRdb) - -- , testCase "applyCmd spec" (applyCmdSpec baseRdb) + , testCase "buyGas failures" (buyGasFailures baseRdb) + , testCase "redeem gas should give gas tokens to the transaction sender and miner" (redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner baseRdb) + , testCase "redeem gas failure" (redeemGasFailure baseRdb) + , testCase "purchase gas tx too big" (purchaseGasTxTooBig baseRdb) + , testCase "run payload should return an EvalResult related to the input command" (runPayloadShouldReturnEvalResultRelatedToTheInputCommand baseRdb) + , testCase "applyLocal spec" (applyLocalSpec baseRdb) + , testCase "applyCmd spec" (applyCmdSpec baseRdb) -- , testCase "applyCmd verifier spec" (applyCmdVerifierSpec baseRdb) -- , testCase "applyCmd failure spec" (applyCmdFailureSpec baseRdb) -- , testCase "applyCmd coin.transfer" (applyCmdCoinTransfer baseRdb) @@ -119,7 +119,7 @@ readFromAfterGenesis ver rdb act = runResourceT $ do -- fake ro-sql pool, assuming we're using this single-threaded roSqlPool <- Pool.newPool (Pool.defaultPoolConfig (return sql) (\_ -> return ()) 10 10) logger <- testLogger - withPactService Nothing ver cid mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (testPactServiceConfig PIN0.payloadBlock) $ \serviceEnv -> do + withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (testPactServiceConfig PIN0.payloadBlock) $ \serviceEnv -> do initialPayloadState logger serviceEnv fakeNewBlockCtx <- mkFakeNewBlockCtx throwIfNoHistory =<< @@ -138,7 +138,6 @@ buyGasShouldTakeGasTokensFromTheTransactionSender rdb = readFromAfterGenesis v r , _cbGasLimit = GasLimit (Gas 200) } - -- let txCtx = TxContext { _tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner } gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled logger <- testLogger _ <- buyGas logger gasEnv pactDb noMiner (_psBlockCtx blockEnv) (view payloadObj <$> cmd) @@ -146,825 +145,779 @@ buyGasShouldTakeGasTokensFromTheTransactionSender rdb = readFromAfterGenesis v r endSender00Bal <- readBal pactDb "sender00" assertEqual "balance after buying gas" (Just $ 100_000_000 - 200 * 2) endSender00Bal --- buyGasFailures :: RocksDb -> IO () --- buyGasFailures rdb = readFromAfterGenesis v rdb $ do --- pactTransaction Nothing $ \pactDb -> do --- startSender00Bal <- readBal pactDb "sender00" --- assertEqual "starting balance" (Just 100_000_000) startSender00Bal - --- -- buying gas with insufficient balance to pay for the full supply --- -- (gas price * gas limit) should return an error --- do --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbGasPrice = GasPrice 70_000 --- , _cbGasLimit = GasLimit (Gas 100_000) --- } --- let txCtx' = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled --- logger <- testLogger --- buyGas logger gasEnv pactDb txCtx' (view payloadObj <$> cmd) --- >>= P.match (_Left . _BuyGasPactError . _PEUserRecoverableError) --- ? P.fun (view _1) --- ? P.equals (UserEnforceError "Insufficient funds") - --- -- multiple gas payer caps should lead to an error, because it's unclear --- -- which module will pay for gas --- do --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbSigners = --- [ mkEd25519Signer' sender00 --- [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] --- , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin" Nothing)) [] --- , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin2" Nothing)) [] --- ] --- ] --- } --- let txCtx' = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled --- logger <- testLogger --- buyGas logger gasEnv pactDb txCtx' (view payloadObj <$> cmd) --- >>= P.equals ? Left BuyGasMultipleGasPayerCaps - --- redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner :: RocksDb -> IO () --- redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner rdb = readFromAfterGenesis v rdb $ do --- pactTransaction Nothing $ \pactDb -> do --- startSender00Bal <- readBal pactDb "sender00" --- assertEqual "starting balance" (Just 100_000_000) startSender00Bal --- startMinerBal <- readBal pactDb "NoMiner" - --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 10) --- } --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- -- redeeming gas with 3 gas used, with a limit of 10, should return 7 gas worth of tokens --- -- to the gas payer - --- -- TODO: should we be throwing some predicates at the redeem gas result? --- logger <- testLogger --- redeemGas logger pactDb txCtx (Gas 3) Nothing (view payloadObj <$> cmd) --- >>= P.match _Right ? P.succeed --- endSender00Bal <- readBal pactDb "sender00" --- assertEqual "balance after redeeming gas" (Just $ 100_000_000 + (10 - 3) * 2) endSender00Bal --- endMinerBal <- readBal pactDb "NoMiner" --- assertEqual "miner balance after redeeming gas" (Just $ fromMaybe 0 startMinerBal + 3 * 2) endMinerBal - --- redeemGasFailure :: RocksDb -> IO () --- redeemGasFailure rdb = readFromAfterGenesis v rdb $ do --- pactTransaction Nothing $ \pactDb -> do --- let miner = Miner (MinerId "sender00") --- $ MinerKeys --- $ Pact4.mkKeySet --- [Pact4.PublicKeyText $ fst sender00] --- "keys-all" - --- cmd <- buildCwCmd v --- $ set cbRPC (mkExec ("(coin.rotate \"sender00\" (read-keyset 'ks))") (mkKeySetData "ks" [sender01])) --- $ set cbSigners --- [ mkEd25519Signer' sender00 --- [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] --- , CapToken (QualifiedName "ROTATE" (ModuleName "coin" Nothing)) [PString "sender00"] --- ] --- ] --- $ defaultCmd cid --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = miner} --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Left --- ? P.match (_RedeemGasError . _2 . _RedeemGasPactError) --- ? P.match (_PEUserRecoverableError . _1) --- ? P.equals (UserEnforceError "account guards do not match") - --- purchaseGasTxTooBig :: RocksDb -> IO () --- purchaseGasTxTooBig rdb = readFromAfterGenesis v rdb $ do --- pactTransaction Nothing $ \pactDb -> do --- cmd <- buildCwCmd v --- $ set cbSender "sender00" --- $ set cbSigners [mkEd25519Signer' sender00 []] --- $ set cbGasLimit (GasLimit (Gas 1)) -- We set the gas limit to lower than the initialGas passed to applyCmd so that this test fails --- $ defaultCmd cid --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 2) (view payloadObj <$> cmd) --- >>= P.match _Left --- ? P.match _PurchaseGasTxTooBigForGasLimit --- ? P.succeed - --- payloadFailureShouldPayAllGasToTheMinerTypeError :: RocksDb -> IO () --- payloadFailureShouldPayAllGasToTheMinerTypeError rdb = readFromAfterGenesis v rdb $ do --- pactTransaction Nothing $ \pactDb -> do --- startSender00Bal <- readBal pactDb "sender00" --- assertEqual "starting balance" (Just 100_000_000) startSender00Bal --- startMinerBal <- readBal pactDb "NoMiner" - --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(+ 1 \"hello\")" --- , _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- let gasToMiner = 2 * 1_000 -- gasPrice * gasLimit --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- [ P.fun _crResult --- ? P.match (_PactResultErr . _PEExecutionError . _1) --- ? P.match _NativeArgumentsError P.succeed --- , P.fun _crEvents ? P.list --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "NoMiner", PDecimal 2000.0]) --- (P.equals coinModuleName) --- ] --- , P.fun _crGas ? P.equals ? Gas 1_000 --- , P.fun _crLogs ? P.match _Just ? --- P.list --- [ P.checkAll --- [ P.fun _txDomain ? P.equals "coin_coin-table" --- , P.fun _txKey ? P.equals "sender00" --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals "coin_coin-table" --- , P.fun _txKey ? P.equals "NoMiner" --- ] --- ] --- ] --- endSender00Bal <- readBal pactDb "sender00" --- assertEqual "sender balance after payload failure" (fmap (subtract gasToMiner) startSender00Bal) endSender00Bal --- endMinerBal <- readBal pactDb "NoMiner" --- assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal - --- payloadFailureShouldPayAllGasToTheMinerInsufficientFunds :: RocksDb -> IO () --- payloadFailureShouldPayAllGasToTheMinerInsufficientFunds rdb = readFromAfterGenesis v rdb $ --- pactTransaction Nothing $ \pactDb -> do --- startSender00Bal <- readBal pactDb "sender00" --- assertEqual "starting balance" (Just 100_000_000) startSender00Bal --- startMinerBal <- readBal pactDb "NoMiner" - --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' $ fromString $ --- "(coin.transfer \"sender00\" \"sender01\" " --- <> printf "%.f" (realToFrac @_ @Double $ fromMaybe 0 startSender00Bal + 1) --- <> ".0 )" --- , _cbSigners = --- [ mkEd25519Signer' sender00 --- [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] --- , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 1_000_000_000] --- ] --- ] --- , _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- let gasToMiner = 2 * 1_000 -- gasPrice * gasLimit --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- [ P.fun _crResult --- ? P.match (_PactResultErr . _PEUserRecoverableError . _1) --- ? P.equals (UserEnforceError "Insufficient funds") --- , P.fun _crEvents --- ? P.list --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "NoMiner", PDecimal 2000.0]) --- (P.equals coinModuleName) --- ] --- , P.fun _crGas ? P.equals ? Gas 1_000 --- , P.fun _crLogs ? P.match _Just ? --- P.list --- [ P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "sender00" --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "NoMiner" --- ] --- ] --- ] --- endSender00Bal <- readBal pactDb "sender00" --- assertEqual "sender balance after payload failure" (fmap (subtract gasToMiner) startSender00Bal) endSender00Bal --- endMinerBal <- readBal pactDb "NoMiner" --- assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal - --- runPayloadShouldReturnEvalResultRelatedToTheInputCommand :: RocksDb -> IO () --- runPayloadShouldReturnEvalResultRelatedToTheInputCommand rdb = readFromAfterGenesis v rdb $ --- pactTransaction Nothing $ \pactDb -> do --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" --- } --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- gasEnv <- mkTableGasEnv (MilliGasLimit (gasToMilliGas $ Gas 10)) GasLogsEnabled --- logger <- testLogger --- payloadResult <- runExceptT $ --- runReaderT --- (runTransactionM --- (runPayload Transactional Set.empty pactDb noSPVSupport [] managedNamespacePolicy gasEnv txCtx (TxBlockIdx 0) (view payloadObj <$> cmd))) --- (TransactionEnv logger gasEnv) --- gasUsed <- readIORef (_geGasRef gasEnv) - --- assertEqual "runPayload gas used" (MilliGas 1_750) gasUsed - --- pure payloadResult >>= P.match _Right ? P.checkAll --- [ P.fun _erOutput ? P.equals [InterpretValue (PInteger 15) noInfo] --- , P.fun _erEvents ? P.equals [] --- , P.fun _erLogs ? P.equals [] --- , P.fun _erExec ? P.equals Nothing --- , P.fun _erGas ? P.equals ? Gas 2 --- , P.fun _erLoadedModules ? P.equals mempty --- , P.fun _erTxId ? P.equals ? Just (TxId 9) --- -- TODO: test _erLogGas? --- ] - --- -- applyLocal should mostly be the same as applyCmd, this is mostly a smoke test --- applyLocalSpec :: RocksDb -> IO () --- applyLocalSpec rdb = readFromAfterGenesis v rdb $ --- pactTransaction Nothing $ \pactDb -> do --- startSender00Bal <- readBal pactDb "sender00" --- assertEqual "starting balance" (Just 100_000_000) startSender00Bal --- startMinerBal <- readBal pactDb "NoMiner" - --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" --- , _cbSigners = [] --- } --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) --- >>= P.checkAll --- -- Local has no buy gas, therefore --- -- no gas buy event --- [ P.fun _crEvents ? P.equals ? [] --- , P.fun _crResult ? P.equals ? PactResultOk (PInteger 15) --- -- reflects payload gas usage --- , P.fun _crGas ? P.equals ? Gas 2 --- , P.fun _crContinuation ? P.equals ? Nothing --- , P.fun _crLogs ? P.equals ? Just [] --- , P.fun _crMetaData ? P.match _Just P.succeed --- ] - --- endSender00Bal <- readBal pactDb "sender00" --- assertEqual "ending balance should be equal" startSender00Bal endSender00Bal --- endMinerBal <- readBal pactDb "NoMiner" --- assertEqual "miner balance after redeeming gas should have increased" startMinerBal endMinerBal - --- applyCmdSpec :: RocksDb -> IO () --- applyCmdSpec rdb = readFromAfterGenesis v rdb $ do --- pactTransaction Nothing $ \pactDb -> do --- startSender00Bal <- readBal pactDb "sender00" --- let expectedStartingBal = 100_000_000 --- assertEqual "starting balance" (Just expectedStartingBal) startSender00Bal --- startMinerBal <- readBal pactDb "NoMiner" - --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" --- , _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 500) --- -- no caps should be equivalent to the GAS cap --- , _cbSigners = [mkEd25519Signer' sender00 []] --- } --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- let expectedGasConsumed = 73 --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- -- only the event reflecting the final transfer to the miner for gas used --- [ P.fun _crEvents ? P.list --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "NoMiner", PDecimal 146.0]) --- (P.equals coinModuleName) --- ] --- , P.fun _crResult ? P.equals ? PactResultOk (PInteger 15) --- -- reflects buyGas gas usage, as well as that of the payload --- , P.fun _crGas ? P.equals ? Gas expectedGasConsumed --- , P.fun _crContinuation ? P.equals ? Nothing --- , P.fun _crLogs ? P.match _Just ? --- P.list --- [ P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "sender00" --- -- TODO: test the values here? --- -- here, we're only testing that the write pattern matches --- -- gas buy and redeem, not the contents of the writes. --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "sender00" --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "NoMiner" --- ] --- ] --- ] - --- endSender00Bal <- readBal pactDb "sender00" --- assertEqual "ending balance should be less gas money" (Just (expectedStartingBal - fromIntegral expectedGasConsumed * 2)) endSender00Bal --- endMinerBal <- readBal pactDb "NoMiner" --- assertEqual "miner balance after redeeming gas should have increased" --- (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed) * 2) --- endMinerBal - --- -- test cache contents --- use pbBlockHandle >>= \bh -> liftIO $ bh --- & P.fun _blockHandlePending --- ? P.fun _pendingWrites --- ? P.checkAll --- [ P.fun InMemDb.userTables --- ? P.alignExact ? HashMap.fromList --- [ TableName "coin-table" (ModuleName "coin" Nothing) :=> --- P.alignExact ? HashMap.fromList --- [ RowKey "NoMiner" :=> --- P.match InMemDb._WriteEntry P.succeed --- , RowKey "sender00" :=> --- P.match InMemDb._WriteEntry P.succeed --- ] --- ] - --- , P.fun InMemDb.modules --- ? P.alignExact ? HashMap.fromList --- [ ModuleName "coin" Nothing :=> --- P.match InMemDb._ReadEntry P.succeed --- ] --- ] - --- quirkSpec :: RocksDb -> IO () --- quirkSpec rdb = readFromAfterGenesis quirkVer rdb $ --- pactTransaction Nothing $ \pactDb -> do --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(* 1000 200000)" --- , _cbSigners = [mkEd25519Signer' sender00 []] --- , _cbSender = "sender00" --- , _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 70_000) --- } --- let txCtx = TxContext --- { _tcParentHeader = ParentHeader (gh quirkVer cid) --- , _tcMiner = noMiner --- } --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- -- gas buy event --- [ P.fun _crEvents ? P.list --- [ event --- (P.equals "TRANSFER") --- -- gas 1, gas price 2 --- (P.equals [PString "sender00", PString "NoMiner", PDecimal (1 * 2)]) --- (P.equals coinModuleName) --- ] --- , P.fun _crResult ? P.equals ? PactResultOk (PInteger (1000 * 200000)) --- -- quirked gas --- , P.fun _crGas ? P.equals ? Gas 1 --- ] --- where --- quirkVer = quirkedGasPact5InstantCpmTestVersion peterson - --- applyCmdVerifierSpec :: RocksDb -> IO () --- applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ --- pactTransaction Nothing $ \pactDb -> do --- -- Define module with capability --- do --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' $ T.unlines --- [ "(namespace 'free)" --- , "(module m G" --- , " (defcap G () (enforce-verifier 'allow))" --- , " (defun x () (with-capability (G) 1))" --- , ")" --- ] --- , _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 70_000) --- } --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- -- gas buy event --- [ P.fun _crEvents ? P.list --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "NoMiner", PDecimal 570]) --- (P.equals coinModuleName) --- ] --- , P.fun _crResult ? P.equals ? PactResultOk (PString "Loaded module free.m, hash Uj0lQPPu9CKvw13K4VP4DZoaPKOphk_-vuq823hLSLo") --- -- reflects buyGas gas usage, as well as that of the payload --- , P.fun _crGas ? P.equals ? Gas 285 --- , P.fun _crContinuation ? P.equals ? Nothing --- ] - --- let baseCmd = (defaultCmd cid) --- { _cbRPC = mkExec' "(free.m.x)" --- , _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 300) --- } - --- -- Invoke module when verifier capability isn't present. Should fail. --- do --- cmd <- buildCwCmd v baseCmd --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- -- gas buy event --- [ P.fun _crResult --- ? P.match (_PactResultErr . _PEUserRecoverableError . _1) --- ? P.equals ? VerifierFailure (VerifierName "allow") "not in transaction" --- , P.fun _crEvents ? P.list --- [ P.checkAll --- [ P.fun _peName ? P.equals ? "TRANSFER" --- , P.fun _peArgs ? P.equals ? [PString "sender00", PString "NoMiner", PDecimal 600] --- , P.fun _peModule ? P.equals ? ModuleName "coin" Nothing --- ] --- ] --- -- reflects buyGas gas usage, as well as that of the payload --- , P.fun _crGas ? P.equals ? Gas 300 --- , P.fun _crContinuation ? P.equals ? Nothing --- ] - --- -- Invoke module when verifier capability is present. Should succeed. --- do --- let cap :: SigCapability --- cap = SigCapability $ CapToken (QualifiedName "G" (ModuleName "m" (Just (NamespaceName "free")))) [] --- cmd <- buildCwCmd v baseCmd --- { _cbVerifiers = --- [ Verifier --- { _verifierName = VerifierName "allow" --- , _verifierProof = ParsedVerifierProof $ PString $ T.decodeUtf8 $ J.encodeStrict cap --- , _verifierCaps = [cap] --- } --- ] --- } --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- -- gas buy event --- [ P.fun _crEvents ? P.list --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "NoMiner", PDecimal 362]) --- (P.equals coinModuleName) --- ] --- , P.fun _crResult ? P.equals ? PactResultOk (PInteger 1) --- -- reflects buyGas gas usage, as well as that of the payload --- , P.fun _crGas ? P.equals ? Gas 181 --- , P.fun _crContinuation ? P.equals ? Nothing --- , P.fun _crMetaData ? P.equals ? Nothing --- ] - --- applyCmdFailureSpec :: RocksDb -> IO () --- applyCmdFailureSpec rdb = readFromAfterGenesis v rdb $ --- pactTransaction Nothing $ \pactDb -> do --- startSender00Bal <- readBal pactDb "sender00" --- assertEqual "starting balance" (Just 100_000_000) startSender00Bal --- startMinerBal <- readBal pactDb "NoMiner" - --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(+ 1 \"abc\")" --- , _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 500) --- } --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- let expectedGasConsumed = 500 --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- -- gas buy event - --- [ P.fun _crEvents --- ? P.list --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "NoMiner", PDecimal 1000]) --- (P.equals coinModuleName) --- ] --- -- tx errored --- , P.fun _crResult ? P.match _PactResultErr P.succeed --- -- reflects buyGas gas usage, as well as that of the payload --- , P.fun _crGas ? P.equals ? Gas expectedGasConsumed --- , P.fun _crContinuation ? P.equals ? Nothing --- , P.fun _crLogs ? P.match _Just ? --- P.list --- [ P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "sender00" --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "NoMiner" --- ] --- ] --- ] - --- endSender00Bal <- readBal pactDb "sender00" --- assertEqual "ending balance should be less gas money" (Just 99_999_000) endSender00Bal --- endMinerBal <- readBal pactDb "NoMiner" --- assertEqual "miner balance after redeeming gas should have increased" --- (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed) * 2) --- endMinerBal - --- applyCmdCoinTransfer :: RocksDb -> IO () --- applyCmdCoinTransfer rdb = readFromAfterGenesis v rdb $ do --- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner --- pactTransaction Nothing $ \pactDb -> do --- startSender00Bal <- readBal pactDb "sender00" --- assertEqual "starting balance" (Just 100_000_000) startSender00Bal --- startMinerBal <- readBal pactDb "NoMiner" - --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0)" --- , _cbSigners = --- [ mkEd25519Signer' sender00 --- [ CapToken (QualifiedName "GAS" coinModuleName) [] --- , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 420] ] --- ] --- , _cbGasPrice = GasPrice 0.1 --- , _cbGasLimit = GasLimit (Gas 1_000) --- } --- -- Note: if/when core changes gas prices, tweak here. --- let expectedGasConsumed = 227 --- logger <- testLogger --- e <- applyCmd logger (Just logger) pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- e & P.match _Right --- ? P.checkAll --- [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") --- , P.fun _crEvents ? P.list --- -- transfer event and gas redeem event --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "sender01", PDecimal 420]) --- (P.equals coinModuleName) --- , event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "NoMiner", PDecimal 22.7]) --- (P.equals coinModuleName) --- ] --- -- reflects buyGas gas usage, as well as that of the payload --- , P.fun _crGas ? P.equals ? Gas expectedGasConsumed --- , P.fun _crContinuation ? P.equals ? Nothing --- , P.fun _crLogs ? P.match _Just ? --- P.list --- [ P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "sender00" --- -- TODO: test the values here? --- -- here, we're only testing that the write pattern matches --- -- gas buy and redeem, not the contents of the writes. --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "sender00" --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "sender01" --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "sender00" --- ] --- , P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "NoMiner" --- ] --- ] --- ] - --- endSender00Bal <- readBal pactDb "sender00" --- assertEqual "ending balance should be less gas money" (Just 99_999_557.3) endSender00Bal --- endMinerBal <- readBal pactDb "NoMiner" --- assertEqual "miner balance after redeeming gas should have increased" --- (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed * 0.1)) --- endMinerBal - --- applyCoinbaseSpec :: RocksDb -> IO () --- applyCoinbaseSpec rdb = readFromAfterGenesis v rdb $ --- pactTransaction Nothing $ \pactDb -> do --- startMinerBal <- readBal pactDb "NoMiner" - --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- applyCoinbase logger pactDb 5 txCtx --- >>= P.match _Right --- ? P.checkAll --- [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") --- , P.fun _crGas ? P.equals ? Gas 0 --- , P.fun _crLogs ? P.match _Just ? P.list --- [ P.checkAll --- [ P.fun _txDomain ? P.equals ? "coin_coin-table" --- , P.fun _txKey ? P.equals ? "NoMiner" --- ] --- ] --- , P.fun _crEvents ? P.list --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "", PString "NoMiner", PDecimal 5.0]) --- (P.equals coinModuleName) --- ] --- ] --- endMinerBal <- readBal pactDb "NoMiner" --- assertEqual "miner balance should include block reward" --- (Just $ fromMaybe 0 startMinerBal + 5) --- endMinerBal - --- testCoinUpgrade :: RocksDb -> IO () --- testCoinUpgrade rdb = readFromAfterGenesis vUpgrades rdb $ do --- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner --- pactTransaction Nothing $ \pactDb -> do - --- logger <- testLogger --- getCoinModuleHash logger txCtx pactDb --- >>= P.equals ? PactResultOk (PString "wOTjNC3gtOAjqgCY8S9hQ-LBiwcPUE7j4iBDE0TmdJo") - --- applyUpgrades logger pactDb txCtx - --- getCoinModuleHash logger txCtx pactDb --- >>= P.equals ? PactResultOk (PString "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE") --- where --- getCoinModuleHash logger txCtx pactDb = do --- cmd <- buildCwCmd vUpgrades (defaultCmd cid) --- { _cbRPC = mkExec' "(at 'hash (describe-module 'coin))" --- } --- _crResult <$> applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) - - --- testEvents :: RocksDb -> IO () --- testEvents rdb = readFromAfterGenesis v rdb $ --- pactTransaction Nothing $ \pactDb -> do --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0) (coin.transfer 'sender00 'sender01 69.0)" --- , _cbSigners = --- [ mkEd25519Signer' sender00 --- [ CapToken (QualifiedName "GAS" coinModuleName) [] --- , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 489] --- ] --- ] --- , _cbGasPrice = GasPrice 2 --- , _cbGasLimit = GasLimit (Gas 1100) --- } --- let txCtx = TxContext {_tcParentHeader = ParentHeader (gh v cid), _tcMiner = noMiner} --- logger <- testLogger --- e <- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - --- e & P.match _Right --- ? P.checkAll --- [ P.fun _crEvents ? P.list --- [ P.checkAll --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "sender01", PDecimal 420]) --- (P.equals coinModuleName) --- , P.fun _peModuleHash ? P.fun moduleHashToText --- ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" --- ] --- , P.checkAll --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "sender01", PDecimal 69]) --- (P.equals coinModuleName) --- , P.fun _peModuleHash ? P.fun moduleHashToText --- ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" --- ] --- , P.checkAll --- [ event --- (P.equals "TRANSFER") --- (P.equals [PString "sender00", PString "NoMiner", PDecimal 766]) --- (P.equals coinModuleName) --- , P.fun _peModuleHash ? P.fun moduleHashToText --- ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" --- ] --- ] --- ] - --- testLocalOnlyFailsOutsideOfLocal :: RocksDb -> IO () --- testLocalOnlyFailsOutsideOfLocal rdb = readFromAfterGenesis v rdb $ do --- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner --- pactTransaction Nothing $ \pactDb -> do --- let testLocalOnly txt = do --- cmd <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' txt --- } - --- logger <- testLogger --- -- should succeed in local --- applyLocal logger Nothing pactDb txCtx noSPVSupport (view payloadObj <$> cmd) --- >>= P.fun _crResult (P.match _PactResultOk P.succeed) - --- -- should fail in non-local --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.fun _crResult --- ? P.match (_PactResultErr . _PEExecutionError . _1 . _OperationIsLocalOnly) P.succeed - --- testLocalOnly "(describe-module \"coin\")" - --- testWritesFromFailedTxDontMakeItIn :: RocksDb -> IO () --- testWritesFromFailedTxDontMakeItIn rdb = readFromAfterGenesis v rdb $ do --- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner --- pactTransaction Nothing $ \pactDb -> do - --- moduleDeploy <- buildCwCmd v (defaultCmd cid) --- { _cbRPC = mkExec' "(module m g (defcap g () (enforce false \"non-upgradeable\"))) (enforce false \"boom\")" --- , _cbGasLimit = GasLimit (Gas 200_000) --- , _cbSigners = [mkEd25519Signer' sender00 []] --- } - --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> moduleDeploy) --- >>= P.match _Right --- ? P.fun _crResult --- ? P.match _PactResultErr P.succeed - --- -- Assert that the writes from the failed transaction didn't make it into the db --- -- but there is some write to the coin contract --- use pbBlockHandle --- >>= (liftIO .) --- ? P.fun _blockHandlePending --- ? P.fun _pendingWrites --- ? P.checkAll --- [ P.fun InMemDb.modules --- ? traverseOf_ (traversed . InMemDb._WriteEntry) --- ? P.fail "no writes made to module table" --- , P.fun InMemDb.userTables --- ? P.match (ix (TableName "coin-table" (ModuleName "coin" Nothing))) --- ? P.alignExact ? HashMap.fromList --- [ RowKey "NoMiner" :=> P.match InMemDb._WriteEntry P.succeed --- , RowKey "sender00" :=> P.match InMemDb._WriteEntry P.succeed --- ] --- ] - --- testWritesToNonExistentTables :: RocksDb -> IO () --- testWritesToNonExistentTables rdb = readFromAfterGenesis v rdb $ do --- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner --- pactTransaction Nothing $ \pactDb -> do --- cmd <- buildCwCmd v --- $ set cbRPC (mkExec' $ T.concat --- [ "(namespace 'free)" --- , "(module m G" --- , "(defcap G () true)" --- , "(defschema o i:integer)" --- , "(deftable t:{o})" --- , ")" --- , "(insert t 'k {'i: 2})" --- ] --- ) --- $ defaultCmd cid - --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.fun _crResult --- ? P.match (_PactResultErr . _PEExecutionError . _1) --- ? P.equals (DbOpFailure (NoSuchTable (TableName "t" (ModuleName "m" (Just "free"))))) - --- testKeccak256 :: RocksDb -> IO () --- testKeccak256 rdb = readFromAfterGenesis v rdb $ do --- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner --- pactTransaction Nothing $ \pactDb -> do --- cmd <- buildCwCmd v --- $ set cbRPC (mkExec' "(hash-keccak256 [\"T73FllCNJKKgAQ4UCYC4CfucbVXsdRJYkd2YXTdmW9gPm-tqUCB1iKvzzu6Md82KWtSKngqgdO04hzg2JJbS-yyHVDuzNJ6mSZfOPntCTqktEi9X27CFWoAwWEN_4Ir7DItecXm5BEu_TYGnFjsxOeMIiLU2sPlX7_macWL0ylqnVqSpgt-tvzHvJVCDxLXGwbmaEH19Ov_9uJFHwsxMmiZD9Hjl4tOTrqN7THy0tel9rc8WtrUKrg87VJ7OR3Rtts5vZ91EBs1OdVldUQPRP536eTcpJNMo-N0fy-taji6L9Mdt4I4_xGqgIfmJxJMpx6ysWmiFVte8vLKl1L5p0yhOnEDsSDjuhZISDOIKC2NeytqoT9VpBQn1T3fjWkF8WEZIvJg5uXTge_qwA46QKV0LE5AlMKgw0cK91T8fnJ-u1Dyk7tCo3XYbx-292iiih8YM1Cr1-cdY5cclAjHAmlglY2ia_GXit5p6K2ggBmd1LpEBdG8DGE4jmeTtiDXLjprpDilq8iCuI0JZ_gvQvMYPekpf8_cMXtTenIxRmhDpYvZzyCxek1F4aoo7_VcAMYV71Mh_T8ox7U1Q4U8hB9oCy1BYcAt06iQai0HXhGFljxsrkL_YSkwsnWVDhhqzxWRRdX3PubpgMzSI290C1gG0Gq4xfKdHTrbm3Q\"])") --- $ defaultCmd cid - --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.match _Right --- ? P.checkAll --- [ P.fun _crResult ? P.equals ? PactResultOk (PString "DqM-LjT1ckQGQCRMfx9fBGl86XE5vacqZVjYZjwCs4g") --- , P.fun _crGas ? P.equals ? Gas 75 --- ] - --- testCommandResult5To4 :: RocksDb -> IO () --- testCommandResult5To4 rdb = readFromAfterGenesis v rdb $ do --- txCtx <- TxContext <$> view psParentHeader <*> pure noMiner --- pactTransaction Nothing $ \pactDb -> do --- cmd <- buildCwCmd v --- $ set cbRPC (mkExec' "(+ 1 'hello)") --- $ defaultCmd cid - --- logger <- testLogger --- applyCmd logger Nothing pactDb txCtx (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) --- >>= P.checkAll --- [ P.match _Right --- ? P.fun _crResult --- ? P.match _PactResultErr --- ? P.succeed --- , P.match _Right --- ? P.fun (fmap pactErrorToOnChainError) --- ? P.fun hashPact5TxLogs --- ? P.fun toPact4CommandResult --- ? P.forced --- ] +buyGasFailures :: RocksDb -> IO () +buyGasFailures rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + assertEqual "starting balance" (Just 100_000_000) startSender00Bal + + -- buying gas with insufficient balance to pay for the full supply + -- (gas price * gas limit) should return an error + do + cmd <- buildCwCmd v (defaultCmd cid) + { _cbGasPrice = GasPrice 70_000 + , _cbGasLimit = GasLimit (Gas 100_000) + } + gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled + logger <- testLogger + buyGas logger gasEnv pactDb noMiner (_psBlockCtx blockEnv) (view payloadObj <$> cmd) + >>= P.match (_Left . _BuyGasPactError . _PEUserRecoverableError) + ? P.fun (view _1) + ? P.equals (UserEnforceError "Insufficient funds") + + -- multiple gas payer caps should lead to an error, because it's unclear + -- which module will pay for gas + do + cmd <- buildCwCmd v (defaultCmd cid) + { _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] + , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin" Nothing)) [] + , CapToken (QualifiedName "GAS_PAYER" (ModuleName "coin2" Nothing)) [] + ] + ] + } + gasEnv <- mkTableGasEnv (MilliGasLimit mempty) GasLogsEnabled + logger <- testLogger + buyGas logger gasEnv pactDb noMiner (_psBlockCtx blockEnv) (view payloadObj <$> cmd) + >>= P.equals ? Left BuyGasMultipleGasPayerCaps + +redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner :: RocksDb -> IO () +redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner rdb = + readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + assertEqual "starting balance" (Just 100_000_000) startSender00Bal + startMinerBal <- readBal pactDb "NoMiner" + + cmd <- buildCwCmd v (defaultCmd cid) + { _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 10) + } + -- redeeming gas with 3 gas used, with a limit of 10, should return 7 gas worth of tokens + -- to the gas payer + + -- TODO: should we be throwing some predicates at the redeem gas result? + logger <- testLogger + redeemGas logger pactDb noMiner (_psBlockCtx blockEnv) (Gas 3) Nothing (view payloadObj <$> cmd) + >>= P.match _Right ? P.succeed + endSender00Bal <- readBal pactDb "sender00" + assertEqual "balance after redeeming gas" (Just $ 100_000_000 + (10 - 3) * 2) endSender00Bal + endMinerBal <- readBal pactDb "NoMiner" + assertEqual "miner balance after redeeming gas" (Just $ fromMaybe 0 startMinerBal + 3 * 2) endMinerBal + +redeemGasFailure :: RocksDb -> IO () +redeemGasFailure rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + let miner = Miner (MinerId "sender00") + $ MinerGuard + $ Pact.GKeyset + $ Pact.KeySet + (Set.singleton (Pact.PublicKeyText $ fst sender00)) + Pact.KeysAll + + cmd <- buildCwCmd v + $ set cbRPC (mkExec ("(coin.rotate \"sender00\" (read-keyset 'ks))") (mkKeySetData "ks" [sender01])) + $ set cbSigners + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] + , CapToken (QualifiedName "ROTATE" (ModuleName "coin" Nothing)) [PString "sender00"] + ] + ] + $ defaultCmd cid + logger <- testLogger + applyCmd logger Nothing pactDb miner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Left + ? P.match (_RedeemGasError . _RedeemGasPactError) + ? P.match (_PEUserRecoverableError . _1) + ? P.equals (UserEnforceError "account guards do not match") + +purchaseGasTxTooBig :: RocksDb -> IO () +purchaseGasTxTooBig rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + cmd <- buildCwCmd v + $ set cbSender "sender00" + $ set cbSigners [mkEd25519Signer' sender00 []] + $ set cbGasLimit (GasLimit (Gas 1)) -- We set the gas limit to lower than the initialGas passed to applyCmd so that this test fails + $ defaultCmd cid + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 2) (view payloadObj <$> cmd) + >>= P.match _Left + ? P.match _PurchaseGasTxTooBigForGasLimit + ? P.succeed + +payloadFailureShouldPayAllGasToTheMinerTypeError :: RocksDb -> IO () +payloadFailureShouldPayAllGasToTheMinerTypeError rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + assertEqual "starting balance" (Just 100_000_000) startSender00Bal + startMinerBal <- readBal pactDb "NoMiner" + + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(+ 1 \"hello\")" + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 1000) + } + let gasToMiner = 2 * 1_000 -- gasPrice * gasLimit + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + [ P.fun _crResult + ? P.match (_PactResultErr . _PEExecutionError . _1) + ? P.match _NativeArgumentsError P.succeed + , P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 2000.0]) + (P.equals coinModuleName) + ] + , P.fun _crGas ? P.equals ? Gas 1_000 + , P.fun _crLogs ? P.match _Just ? + P.list + [ P.checkAll + [ P.fun _txDomain ? P.equals "coin_coin-table" + , P.fun _txKey ? P.equals "sender00" + ] + , P.checkAll + [ P.fun _txDomain ? P.equals "coin_coin-table" + , P.fun _txKey ? P.equals "NoMiner" + ] + ] + ] + endSender00Bal <- readBal pactDb "sender00" + assertEqual "sender balance after payload failure" (fmap (subtract gasToMiner) startSender00Bal) endSender00Bal + endMinerBal <- readBal pactDb "NoMiner" + assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal + +payloadFailureShouldPayAllGasToTheMinerInsufficientFunds :: RocksDb -> IO () +payloadFailureShouldPayAllGasToTheMinerInsufficientFunds rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + assertEqual "starting balance" (Just 100_000_000) startSender00Bal + startMinerBal <- readBal pactDb "NoMiner" + + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' $ fromString $ + "(coin.transfer \"sender00\" \"sender01\" " + <> printf "%.f" (realToFrac @_ @Double $ fromMaybe 0 startSender00Bal + 1) + <> ".0 )" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] + , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 1_000_000_000] + ] + ] + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 1000) + } + let gasToMiner = 2 * 1_000 -- gasPrice * gasLimit + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + [ P.fun _crResult + ? P.match (_PactResultErr . _PEUserRecoverableError . _1) + ? P.equals (UserEnforceError "Insufficient funds") + , P.fun _crEvents + ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 2000.0]) + (P.equals coinModuleName) + ] + , P.fun _crGas ? P.equals ? Gas 1_000 + , P.fun _crLogs ? P.match _Just ? + P.list + [ P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "NoMiner" + ] + ] + ] + endSender00Bal <- readBal pactDb "sender00" + assertEqual "sender balance after payload failure" (fmap (subtract gasToMiner) startSender00Bal) endSender00Bal + endMinerBal <- readBal pactDb "NoMiner" + assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal + +runPayloadShouldReturnEvalResultRelatedToTheInputCommand :: RocksDb -> IO () +runPayloadShouldReturnEvalResultRelatedToTheInputCommand rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" + } + gasEnv <- mkTableGasEnv (MilliGasLimit (gasToMilliGas $ Gas 10)) GasLogsEnabled + logger <- testLogger + payloadResult <- runExceptT $ + runReaderT + (runTransactionM + (runPayload Transactional Set.empty pactDb noSPVSupport [] managedNamespacePolicy gasEnv (_psBlockCtx blockEnv) (TxBlockIdx 0) (view payloadObj <$> cmd))) + (TransactionEnv logger gasEnv) + gasUsed <- readIORef (_geGasRef gasEnv) + + assertEqual "runPayload gas used" (MilliGas 1_750) gasUsed + + pure payloadResult >>= P.match _Right ? P.checkAll + [ P.fun _erOutput ? P.equals [InterpretValue (PInteger 15) noInfo] + , P.fun _erEvents ? P.equals [] + , P.fun _erLogs ? P.equals [] + , P.fun _erExec ? P.equals Nothing + , P.fun _erGas ? P.equals ? Gas 2 + , P.fun _erLoadedModules ? P.equals mempty + , P.fun _erTxId ? P.equals ? Just (TxId 9) + -- TODO: test _erLogGas? + ] + +-- applyLocal should mostly be the same as applyCmd, this is mostly a smoke test +applyLocalSpec :: RocksDb -> IO () +applyLocalSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + assertEqual "starting balance" (Just 100_000_000) startSender00Bal + startMinerBal <- readBal pactDb "NoMiner" + + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" + , _cbSigners = [] + } + logger <- testLogger + applyLocal logger Nothing pactDb (_psBlockCtx blockEnv) noSPVSupport (view payloadObj <$> cmd) + >>= P.checkAll + -- Local has no buy gas, therefore + -- no gas buy event + [ P.fun _crEvents ? P.equals ? [] + , P.fun _crResult ? P.equals ? PactResultOk (PInteger 15) + -- reflects payload gas usage + , P.fun _crGas ? P.equals ? Gas 2 + , P.fun _crContinuation ? P.equals ? Nothing + , P.fun _crLogs ? P.equals ? Just [] + , P.fun _crMetaData ? P.match _Just P.succeed + ] + + endSender00Bal <- readBal pactDb "sender00" + assertEqual "ending balance should be equal" startSender00Bal endSender00Bal + endMinerBal <- readBal pactDb "NoMiner" + assertEqual "miner balance after redeeming gas should have increased" startMinerBal endMinerBal + +applyCmdSpec :: RocksDb -> IO () +applyCmdSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + bh <- flip execStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + let expectedStartingBal = 100_000_000 + assertEqual "starting balance" (Just expectedStartingBal) startSender00Bal + startMinerBal <- readBal pactDb "NoMiner" + + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 500) + -- no caps should be equivalent to the GAS cap + , _cbSigners = [mkEd25519Signer' sender00 []] + } + let expectedGasConsumed = 73 + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + -- only the event reflecting the final transfer to the miner for gas used + [ P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 146.0]) + (P.equals coinModuleName) + ] + , P.fun _crResult ? P.equals ? PactResultOk (PInteger 15) + -- reflects buyGas gas usage, as well as that of the payload + , P.fun _crGas ? P.equals ? Gas expectedGasConsumed + , P.fun _crContinuation ? P.equals ? Nothing + , P.fun _crLogs ? P.match _Just ? + P.list + [ P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + -- TODO: test the values here? + -- here, we're only testing that the write pattern matches + -- gas buy and redeem, not the contents of the writes. + ] + , P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "NoMiner" + ] + ] + ] + + endSender00Bal <- readBal pactDb "sender00" + assertEqual "ending balance should be less gas money" (Just (expectedStartingBal - fromIntegral expectedGasConsumed * 2)) endSender00Bal + endMinerBal <- readBal pactDb "NoMiner" + assertEqual "miner balance after redeeming gas should have increased" + (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed) * 2) + endMinerBal + + -- test cache contents + liftIO $ bh + & P.fun _blockHandlePending + ? P.fun _pendingWrites + ? P.checkAll + [ P.fun InMemDb.userTables + ? P.alignExact ? HashMap.fromList + [ TableName "coin-table" (ModuleName "coin" Nothing) :=> + P.alignExact ? HashMap.fromList + [ RowKey "NoMiner" :=> + P.match InMemDb._WriteEntry P.succeed + , RowKey "sender00" :=> + P.match InMemDb._WriteEntry P.succeed + ] + ] + + , P.fun InMemDb.modules + ? P.alignExact ? HashMap.fromList + [ ModuleName "coin" Nothing :=> + P.match InMemDb._ReadEntry P.succeed + ] + ] + +quirkSpec :: RocksDb -> IO () +quirkSpec rdb = readFromAfterGenesis quirkVer rdb $ \blockEnv blockHandle -> + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(* 1000 200000)" + , _cbSigners = [mkEd25519Signer' sender00 []] + , _cbSender = "sender00" + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 70_000) + } + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + -- gas buy event + [ P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + -- gas 1, gas price 2 + (P.equals [PString "sender00", PString "NoMiner", PDecimal (1 * 2)]) + (P.equals coinModuleName) + ] + , P.fun _crResult ? P.equals ? PactResultOk (PInteger (1000 * 200000)) + -- quirked gas + , P.fun _crGas ? P.equals ? Gas 1 + ] + where + quirkVer = quirkedGasPact5InstantCpmTestVersion peterson + +applyCmdVerifierSpec :: RocksDb -> IO () +applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + -- Define module with capability + do + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' $ T.unlines + [ "(namespace 'free)" + , "(module m G" + , " (defcap G () (enforce-verifier 'allow))" + , " (defun x () (with-capability (G) 1))" + , ")" + ] + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 70_000) + } + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + -- gas buy event + [ P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 570]) + (P.equals coinModuleName) + ] + , P.fun _crResult ? P.equals ? PactResultOk (PString "Loaded module free.m, hash Uj0lQPPu9CKvw13K4VP4DZoaPKOphk_-vuq823hLSLo") + -- reflects buyGas gas usage, as well as that of the payload + , P.fun _crGas ? P.equals ? Gas 285 + , P.fun _crContinuation ? P.equals ? Nothing + ] + + let baseCmd = (defaultCmd cid) + { _cbRPC = mkExec' "(free.m.x)" + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 300) + } + + -- Invoke module when verifier capability isn't present. Should fail. + do + cmd <- buildCwCmd v baseCmd + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + -- gas buy event + [ P.fun _crResult + ? P.match (_PactResultErr . _PEUserRecoverableError . _1) + ? P.equals ? VerifierFailure (VerifierName "allow") "not in transaction" + , P.fun _crEvents ? P.list + [ P.checkAll + [ P.fun _peName ? P.equals ? "TRANSFER" + , P.fun _peArgs ? P.equals ? [PString "sender00", PString "NoMiner", PDecimal 600] + , P.fun _peModule ? P.equals ? ModuleName "coin" Nothing + ] + ] + -- reflects buyGas gas usage, as well as that of the payload + , P.fun _crGas ? P.equals ? Gas 300 + , P.fun _crContinuation ? P.equals ? Nothing + ] + + -- Invoke module when verifier capability is present. Should succeed. + do + let cap :: SigCapability + cap = SigCapability $ CapToken (QualifiedName "G" (ModuleName "m" (Just (NamespaceName "free")))) [] + cmd <- buildCwCmd v baseCmd + { _cbVerifiers = + [ Verifier + { _verifierName = VerifierName "allow" + , _verifierProof = ParsedVerifierProof $ PString $ T.decodeUtf8 $ J.encodeStrict cap + , _verifierCaps = [cap] + } + ] + } + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + -- gas buy event + [ P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 362]) + (P.equals coinModuleName) + ] + , P.fun _crResult ? P.equals ? PactResultOk (PInteger 1) + -- reflects buyGas gas usage, as well as that of the payload + , P.fun _crGas ? P.equals ? Gas 181 + , P.fun _crContinuation ? P.equals ? Nothing + , P.fun _crMetaData ? P.equals ? Nothing + ] + +applyCmdFailureSpec :: RocksDb -> IO () +applyCmdFailureSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + assertEqual "starting balance" (Just 100_000_000) startSender00Bal + startMinerBal <- readBal pactDb "NoMiner" + + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(+ 1 \"abc\")" + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 500) + } + let expectedGasConsumed = 500 + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + -- gas buy event + + [ P.fun _crEvents + ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 1000]) + (P.equals coinModuleName) + ] + -- tx errored + , P.fun _crResult ? P.match _PactResultErr P.succeed + -- reflects buyGas gas usage, as well as that of the payload + , P.fun _crGas ? P.equals ? Gas expectedGasConsumed + , P.fun _crContinuation ? P.equals ? Nothing + , P.fun _crLogs ? P.match _Just ? + P.list + [ P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "NoMiner" + ] + ] + ] + + endSender00Bal <- readBal pactDb "sender00" + assertEqual "ending balance should be less gas money" (Just 99_999_000) endSender00Bal + endMinerBal <- readBal pactDb "NoMiner" + assertEqual "miner balance after redeeming gas should have increased" + (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed) * 2) + endMinerBal + +applyCmdCoinTransfer :: RocksDb -> IO () +applyCmdCoinTransfer rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startSender00Bal <- readBal pactDb "sender00" + assertEqual "starting balance" (Just 100_000_000) startSender00Bal + startMinerBal <- readBal pactDb "NoMiner" + + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0)" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" coinModuleName) [] + , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 420] ] + ] + , _cbGasPrice = GasPrice 0.1 + , _cbGasLimit = GasLimit (Gas 1_000) + } + -- Note: if/when core changes gas prices, tweak here. + let expectedGasConsumed = 227 + logger <- testLogger + e <- applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + e & P.match _Right + ? P.checkAll + [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") + , P.fun _crEvents ? P.list + -- transfer event and gas redeem event + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "sender01", PDecimal 420]) + (P.equals coinModuleName) + , event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 22.7]) + (P.equals coinModuleName) + ] + -- reflects buyGas gas usage, as well as that of the payload + , P.fun _crGas ? P.equals ? Gas expectedGasConsumed + , P.fun _crContinuation ? P.equals ? Nothing + , P.fun _crLogs ? P.match _Just ? + P.list + [ P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + -- TODO: test the values here? + -- here, we're only testing that the write pattern matches + -- gas buy and redeem, not the contents of the writes. + ] + , P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender01" + ] + , P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "sender00" + ] + , P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "NoMiner" + ] + ] + ] + + endSender00Bal <- readBal pactDb "sender00" + assertEqual "ending balance should be less gas money" (Just 99_999_557.3) endSender00Bal + endMinerBal <- readBal pactDb "NoMiner" + assertEqual "miner balance after redeeming gas should have increased" + (Just $ fromMaybe 0 startMinerBal + (fromIntegral expectedGasConsumed * 0.1)) + endMinerBal + +applyCoinbaseSpec :: RocksDb -> IO () +applyCoinbaseSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + startMinerBal <- readBal pactDb "NoMiner" + + logger <- testLogger + applyCoinbase logger pactDb 5 (_psBlockCtx blockEnv) + >>= P.match _Right + ? P.checkAll + [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") + , P.fun _crGas ? P.equals ? Gas 0 + , P.fun _crLogs ? P.match _Just ? P.list + [ P.checkAll + [ P.fun _txDomain ? P.equals ? "coin_coin-table" + , P.fun _txKey ? P.equals ? "NoMiner" + ] + ] + , P.fun _crEvents ? P.list + [ event + (P.equals "TRANSFER") + (P.equals [PString "", PString "NoMiner", PDecimal 5.0]) + (P.equals coinModuleName) + ] + ] + endMinerBal <- readBal pactDb "NoMiner" + assertEqual "miner balance should include block reward" + (Just $ fromMaybe 0 startMinerBal + 5) + endMinerBal + +testEvents :: RocksDb -> IO () +testEvents rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0) (coin.transfer 'sender00 'sender01 69.0)" + , _cbSigners = + [ mkEd25519Signer' sender00 + [ CapToken (QualifiedName "GAS" coinModuleName) [] + , CapToken (QualifiedName "TRANSFER" coinModuleName) [PString "sender00", PString "sender01", PDecimal 489] + ] + ] + , _cbGasPrice = GasPrice 2 + , _cbGasLimit = GasLimit (Gas 1100) + } + logger <- testLogger + e <- applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + + e & P.match _Right + ? P.checkAll + [ P.fun _crEvents ? P.list + [ P.checkAll + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "sender01", PDecimal 420]) + (P.equals coinModuleName) + , P.fun _peModuleHash ? P.fun moduleHashToText + ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" + ] + , P.checkAll + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "sender01", PDecimal 69]) + (P.equals coinModuleName) + , P.fun _peModuleHash ? P.fun moduleHashToText + ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" + ] + , P.checkAll + [ event + (P.equals "TRANSFER") + (P.equals [PString "sender00", PString "NoMiner", PDecimal 766]) + (P.equals coinModuleName) + , P.fun _peModuleHash ? P.fun moduleHashToText + ? P.equals "3iIBQdJnst44Z2ZgXoHPkAauybJ0h85l_en_SGHNibE" + ] + ] + ] + +testLocalOnlyFailsOutsideOfLocal :: RocksDb -> IO () +testLocalOnlyFailsOutsideOfLocal rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + let testLocalOnly txt = do + cmd <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' txt + } + + logger <- testLogger + -- should succeed in local + applyLocal logger Nothing pactDb (_psBlockCtx blockEnv) noSPVSupport (view payloadObj <$> cmd) + >>= P.fun _crResult (P.match _PactResultOk P.succeed) + + -- should fail in non-local + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.fun _crResult + ? P.match (_PactResultErr . _PEExecutionError . _1 . _OperationIsLocalOnly) P.succeed + + testLocalOnly "(describe-module \"coin\")" + +testWritesFromFailedTxDontMakeItIn :: RocksDb -> IO () +testWritesFromFailedTxDontMakeItIn rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + bh <- flip execStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + + moduleDeploy <- buildCwCmd v (defaultCmd cid) + { _cbRPC = mkExec' "(module m g (defcap g () (enforce false \"non-upgradeable\"))) (enforce false \"boom\")" + , _cbGasLimit = GasLimit (Gas 200_000) + , _cbSigners = [mkEd25519Signer' sender00 []] + } + + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> moduleDeploy) + >>= P.match _Right + ? P.fun _crResult + ? P.match _PactResultErr P.succeed + + -- Assert that the writes from the failed transaction didn't make it into the db + -- but there is some write to the coin contract + liftIO $ bh + & P.fun _blockHandlePending + ? P.fun _pendingWrites + ? P.checkAll + [ P.fun InMemDb.modules + ? traverseOf_ (traversed . InMemDb._WriteEntry) + ? P.fail "no writes made to module table" + , P.fun InMemDb.userTables + ? P.match (ix (TableName "coin-table" (ModuleName "coin" Nothing))) + ? P.alignExact ? HashMap.fromList + [ RowKey "NoMiner" :=> P.match InMemDb._WriteEntry P.succeed + , RowKey "sender00" :=> P.match InMemDb._WriteEntry P.succeed + ] + ] + +testWritesToNonExistentTables :: RocksDb -> IO () +testWritesToNonExistentTables rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + cmd <- buildCwCmd v + $ set cbRPC (mkExec' $ T.concat + [ "(namespace 'free)" + , "(module m G" + , "(defcap G () true)" + , "(defschema o i:integer)" + , "(deftable t:{o})" + , ")" + , "(insert t 'k {'i: 2})" + ] + ) + $ defaultCmd cid + + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.fun _crResult + ? P.match (_PactResultErr . _PEExecutionError . _1) + ? P.equals (DbOpFailure (NoSuchTable (TableName "t" (ModuleName "m" (Just "free"))))) + +testKeccak256 :: RocksDb -> IO () +testKeccak256 rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + cmd <- buildCwCmd v + $ set cbRPC (mkExec' "(hash-keccak256 [\"T73FllCNJKKgAQ4UCYC4CfucbVXsdRJYkd2YXTdmW9gPm-tqUCB1iKvzzu6Md82KWtSKngqgdO04hzg2JJbS-yyHVDuzNJ6mSZfOPntCTqktEi9X27CFWoAwWEN_4Ir7DItecXm5BEu_TYGnFjsxOeMIiLU2sPlX7_macWL0ylqnVqSpgt-tvzHvJVCDxLXGwbmaEH19Ov_9uJFHwsxMmiZD9Hjl4tOTrqN7THy0tel9rc8WtrUKrg87VJ7OR3Rtts5vZ91EBs1OdVldUQPRP536eTcpJNMo-N0fy-taji6L9Mdt4I4_xGqgIfmJxJMpx6ysWmiFVte8vLKl1L5p0yhOnEDsSDjuhZISDOIKC2NeytqoT9VpBQn1T3fjWkF8WEZIvJg5uXTge_qwA46QKV0LE5AlMKgw0cK91T8fnJ-u1Dyk7tCo3XYbx-292iiih8YM1Cr1-cdY5cclAjHAmlglY2ia_GXit5p6K2ggBmd1LpEBdG8DGE4jmeTtiDXLjprpDilq8iCuI0JZ_gvQvMYPekpf8_cMXtTenIxRmhDpYvZzyCxek1F4aoo7_VcAMYV71Mh_T8ox7U1Q4U8hB9oCy1BYcAt06iQai0HXhGFljxsrkL_YSkwsnWVDhhqzxWRRdX3PubpgMzSI290C1gG0Gq4xfKdHTrbm3Q\"])") + $ defaultCmd cid + + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.match _Right + ? P.checkAll + [ P.fun _crResult ? P.equals ? PactResultOk (PString "DqM-LjT1ckQGQCRMfx9fBGl86XE5vacqZVjYZjwCs4g") + , P.fun _crGas ? P.equals ? Gas 75 + ] + +testCommandResult5To4 :: RocksDb -> IO () +testCommandResult5To4 rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do + cmd <- buildCwCmd v + $ set cbRPC (mkExec' "(+ 1 'hello)") + $ defaultCmd cid + + logger <- testLogger + applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) + >>= P.checkAll + [ P.match _Right + ? P.fun _crResult + ? P.match _PactResultErr + ? P.succeed + , P.match _Right + ? P.fun (fmap pactErrorToOnChainError) + ? P.fun hashPact5TxLogs + ? P.fun toPact4CommandResult + ? P.forced + ] cid :: ChainId cid = unsafeChainId 0 From 47dc7cc60bc0c95b44580b509cd4a8ed5a43d54d Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 15 Apr 2025 13:31:03 -0400 Subject: [PATCH 105/378] Revive most of TransactionExecTest Change-Id: Id0000000531ca6f9b45317f74275b19ba5d168d3 --- chainweb.cabal | 7 +- src/Chainweb/Pact/PactService.hs | 100 +++++++++--------- src/Chainweb/Pact/PactService/Checkpointer.hs | 33 +++--- src/Chainweb/Pact/PactService/ExecBlock.hs | 31 +++--- src/Chainweb/Pact/Types.hs | 10 +- src/Chainweb/PayloadProvider/Pact.hs | 2 +- src/Chainweb/Utils.hs | 2 +- .../Chainweb/Test/Pact/TransactionExecTest.hs | 42 ++++---- 8 files changed, 111 insertions(+), 116 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index 415535ca9c..9db3fc7299 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -545,7 +545,7 @@ library chainweb-test-utils , pact-time:numeric >=0.3.0.1 , pact-tng >=5.0 , pact-tng:pact-request-api >=5.0 - , property-matchers ^>= 0.4 + , property-matchers ^>= 0.7 , quickcheck-instances >= 0.3 , random >= 1.2 , resourcet >= 1.3 @@ -695,7 +695,7 @@ test-suite chainweb-tests , pact-tng:pact-repl , patience >= 0.3 , prettyprinter - , property-matchers ^>= 0.4 + , property-matchers ^>= 0.7 , pretty-show , quickcheck-instances >= 0.3 , random >= 1.2 @@ -863,7 +863,7 @@ benchmark bench , pact , pact-tng , pact-tng:pact-request-api - , property-matchers + , property-matchers ^>= 0.7 , random >= 1.2 , safe-exceptions , streaming @@ -872,4 +872,3 @@ benchmark bench , unordered-containers , vector >= 0.12.2 , yaml >= 0.11 - , property-matchers ^>= 0.4 diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 2a910873f0..73117e82b1 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -121,7 +121,6 @@ import Control.Monad.State.Strict import GHC.Stack (HasCallStack) import qualified Data.Pool as Pool import qualified Data.List.NonEmpty as NEL -import Chainweb.Pact.PactService.Checkpointer (mkFakeNewBlockCtx) import qualified Control.Parallel.Strategies as Strategies import qualified Chainweb.Pact.NoCoinbase as Pact @@ -226,42 +225,6 @@ getMiner serviceEnv = case _psMiner serviceEnv of Nothing -> error "Chainweb.Pact.PactService: Mining is disabled, but was invoked. This is a bug in chainweb." Just miner -> return miner -revertStateOnFailure :: Monad m => StateT s (ExceptT e m) a -> StateT s m (Either e a) -revertStateOnFailure s = do - StateT $ \old -> - runExceptT (runStateT s old) <&> f old - where - f old = \case - Left err -> (Left err, old) - Right (success, new) -> (Right success, new) - -makeEmptyBlock - :: forall logger tbl. (Logger logger) - => logger - -> ServiceEnv tbl - -> BlockEnv - -> StateT BlockHandle IO BlockInProgress -makeEmptyBlock logger serviceEnv blockEnv = do - miner <- liftIO $ getMiner serviceEnv - let blockGasLimit = _psNewBlockGasLimit serviceEnv - coinbaseOutput <- revertStateOnFailure (Pact.runCoinbase logger blockEnv miner) >>= \case - Left coinbaseError -> error $ "Error during coinbase: " <> sshow coinbaseError - Right coinbaseOutput -> - -- pretend that coinbase can throw an error, when we know it can't. - -- perhaps we can make the Transactions express this, may not be worth it. - return $ coinbaseOutput & Pact.crResult . Pact._PactResultErr %~ absurd - hndl <- get - return BlockInProgress - { _blockInProgressHandle = hndl - , _blockInProgressBlockCtx = view psBlockCtx blockEnv - , _blockInProgressRemainingGasLimit = blockGasLimit - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = coinbaseOutput - , _transactionPairs = mempty - } - , _blockInProgressNumber = 0 - } - -- | only for use in generating genesis blocks in tools. -- execNewGenesisBlock @@ -273,9 +236,9 @@ execNewGenesisBlock execNewGenesisBlock logger serviceEnv newTrans = do let v = _chainwebVersion serviceEnv let cid = _chainId serviceEnv - fakeNewBlockCtx <- mkFakeNewBlockCtx + let parentCreationTime = Parent (v ^?! versionGenesis . genesisTime . atChain cid) let genesisParent = Parent $ RankedBlockHash (genesisHeight v cid) (unwrapParent $ genesisParentBlockHash v cid) - historicalBlock <- Checkpointer.readFrom logger v cid (_psReadWriteSql serviceEnv) fakeNewBlockCtx genesisParent $ \blockEnv startHandle -> do + historicalBlock <- Checkpointer.readFrom logger v cid (_psReadWriteSql serviceEnv) parentCreationTime genesisParent $ \blockEnv startHandle -> do let bipStart = BlockInProgress { _blockInProgressHandle = startHandle @@ -331,7 +294,6 @@ execReadOnlyReplay logger serviceEnv blocks = do lookupPayloadWithHeight (_payloadStoreTable pdb) (Just $ _evaluationCtxCurrentHeight evalCtx) (_evaluationCtxPayload evalCtx) let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload) let isUpgradeBlock = isJust $ v ^? versionUpgrades . atChain cid . ix (_evaluationCtxCurrentHeight evalCtx) - newBlockCtx <- Checkpointer.mkFakeNewBlockCtx if isPayloadEmpty && not isUpgradeBlock then Pool.withResource readSqlPool $ \sql -> do hist <- Checkpointer.readFrom @@ -339,7 +301,7 @@ execReadOnlyReplay logger serviceEnv blocks = do v cid sql - newBlockCtx + (_evaluationCtxParentCreationTime evalCtx) (_evaluationCtxRankedParentHash evalCtx) (\blockEnv blockHandle -> runExceptT $ flip evalStateT blockHandle $ @@ -373,7 +335,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do where doLocal = Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> do - fakeNewBlockCtx <- liftIO $ Checkpointer.mkFakeNewBlockCtx + fakeNewBlockCtx <- liftIO Checkpointer.mkFakeParentCreationTime Checkpointer.readFromNthParent logger v cid sql fakeNewBlockCtx (fromIntegral rewindDepth) $ \blockEnv blockHandle -> do let blockCtx = view psBlockCtx blockEnv let requestKey = Pact.cmdToRequestKey cwtx @@ -457,6 +419,42 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do | enableLocalTimeout = Just (2 * 1_000_000) | otherwise = Nothing +makeEmptyBlock + :: forall logger tbl. (Logger logger) + => logger + -> ServiceEnv tbl + -> BlockEnv + -> StateT BlockHandle IO BlockInProgress +makeEmptyBlock logger serviceEnv blockEnv = do + miner <- liftIO $ getMiner serviceEnv + let blockGasLimit = _psNewBlockGasLimit serviceEnv + coinbaseOutput <- revertStateOnFailure (Pact.runCoinbase logger blockEnv miner) >>= \case + Left coinbaseError -> error $ "Error during coinbase: " <> sshow coinbaseError + Right coinbaseOutput -> + -- pretend that coinbase can throw an error, when we know it can't. + -- perhaps we can make the Transactions express this, may not be worth it. + return $ coinbaseOutput & Pact.crResult . Pact._PactResultErr %~ absurd + hndl <- get + return BlockInProgress + { _blockInProgressHandle = hndl + , _blockInProgressBlockCtx = view psBlockCtx blockEnv + , _blockInProgressRemainingGasLimit = blockGasLimit + , _blockInProgressTransactions = Transactions + { _transactionCoinbase = coinbaseOutput + , _transactionPairs = mempty + } + , _blockInProgressNumber = 0 + } + where + revertStateOnFailure :: Monad m => StateT s (ExceptT e m) a -> StateT s m (Either e a) + revertStateOnFailure s = do + StateT $ \old -> + runExceptT (runStateT s old) <&> f old + where + f old = \case + Left err -> (Left err, old) + Right (success, new) -> (Right success, new) + syncToFork :: forall tbl logger . (CanPayloadCas tbl, Logger logger) @@ -543,7 +541,7 @@ syncToFork logger serviceEnv hints forkInfo = do -- told to start mining, we produce an empty block -- immediately. then we set up a separate thread -- to add new transactions to the block. - emptyBlock <- Checkpointer.readFromLatest logger v cid sql newBlockCtx $ \blockEnv blockHandle -> + emptyBlock <- Checkpointer.readFromLatest logger v cid sql (_newBlockCtxParentCreationTime newBlockCtx) $ \blockEnv blockHandle -> flip evalStateT blockHandle $ makeEmptyBlock logger serviceEnv blockEnv let payloadVar = view psMiningPayloadVar serviceEnv @@ -555,7 +553,7 @@ syncToFork logger serviceEnv hints forkInfo = do refresherThread <- liftIO $ async (refreshPayloads logger serviceEnv) liftIO $ - atomically $ writeTMVar payloadVar (refresherThread, newBlockCtx, emptyBlock) + atomically $ writeTMVar payloadVar (refresherThread, emptyBlock) _ -> return () return newConsensusState @@ -627,9 +625,9 @@ refreshPayloads logger serviceEnv = do -- note that if this is empty, we wait; taking from it is the way to make us stop let logOutraced = liftIO $ logFunctionText logger Debug $ "Refresher outraced by new block" - (_, newBlockCtx, blockInProgress) <- liftIO $ atomically $ readTMVar payloadVar + (_, blockInProgress) <- liftIO $ atomically $ readTMVar payloadVar maybeRefreshedBlockInProgress <- Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> - Checkpointer.readFrom logger v cid sql newBlockCtx (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) $ \blockEnv _bh -> do + Checkpointer.readFrom logger v cid sql (_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress) (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) $ \blockEnv _bh -> do let dbEnv = view psBlockDbEnv blockEnv continueBlock logger serviceEnv dbEnv blockInProgress case maybeRefreshedBlockInProgress of @@ -637,12 +635,12 @@ refreshPayloads logger serviceEnv = do NoHistory -> logOutraced Historical refreshedBlockInProgress -> do outraced <- liftIO $ atomically $ do - (_, _, latestBlockInProgress) <- readTMVar payloadVar + (_, latestBlockInProgress) <- readTMVar payloadVar -- the block has been replaced, this is a possible race if _blockInProgressBlockCtx latestBlockInProgress /= _blockInProgressBlockCtx refreshedBlockInProgress then return True else do - writeTMVar payloadVar . (_3 .~ refreshedBlockInProgress) =<< readTMVar payloadVar + writeTMVar payloadVar . (_2 .~ refreshedBlockInProgress) =<< readTMVar payloadVar return False if outraced then logOutraced @@ -691,8 +689,8 @@ execPreInsertCheckReq execPreInsertCheckReq logger serviceEnv txs = do let requestKeys = V.map Pact.cmdToRequestKey txs logFunctionText logger Info $ "(pre-insert check " <> sshow requestKeys <> ")" - newBlockCtx <- Checkpointer.mkFakeNewBlockCtx - let act sql = Checkpointer.readFromLatest logger v cid sql newBlockCtx $ \blockEnv bh -> do + fakeParentCreationTime <- Checkpointer.mkFakeParentCreationTime + let act sql = Checkpointer.readFromLatest logger v cid sql fakeParentCreationTime $ \blockEnv bh -> do forM txs $ \tx -> fmap (either Just (\_ -> Nothing)) $ runExceptT $ do -- it's safe to use initialBlockHandle here because it's @@ -746,7 +744,7 @@ execLookupPactTxs logger serviceEnv confDepth txs = do -- pactLabel "execLookupP if V.null txs then return (Historical mempty) else do - go =<< liftIO Checkpointer.mkFakeNewBlockCtx + go =<< liftIO Checkpointer.mkFakeParentCreationTime where depth = maybe 0 (fromIntegral . _confirmationDepth) confDepth v = _chainwebVersion serviceEnv diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 7ead0ceecf..405591a2a5 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -33,7 +33,7 @@ -- Checkpointer interaction for Pact service. -- module Chainweb.Pact.PactService.Checkpointer - ( mkFakeNewBlockCtx + ( mkFakeParentCreationTime , readFromLatest , readFromNthParent , readFrom @@ -89,14 +89,9 @@ import Control.Monad.State.Strict -- readFrom demands to know this context in case it's mining a block. -- But it's not really used by /local, or polling, so we fabricate it -- for those users. -mkFakeNewBlockCtx :: IO NewBlockCtx -mkFakeNewBlockCtx = do - fakeCreationTime <- Parent . BlockCreationTime <$> getCurrentTimeIntegral - return NewBlockCtx - -- fake - { _newBlockCtxMinerReward = MinerReward 1848 - , _newBlockCtxParentCreationTime = fakeCreationTime - } +mkFakeParentCreationTime :: IO (Parent BlockCreationTime) +mkFakeParentCreationTime = do + Parent . BlockCreationTime <$> getCurrentTimeIntegral -- read-only rewind to the latest block. -- note: because there is a race between getting the latest header @@ -110,11 +105,11 @@ readFromLatest -> ChainwebVersion -> ChainId -> SQLiteEnv - -> NewBlockCtx + -> Parent BlockCreationTime -> (BlockEnv -> BlockHandle -> IO a) -> IO a -readFromLatest logger v cid sql newBlockCtx doRead = - readFromNthParent logger v cid sql newBlockCtx 0 doRead >>= \case +readFromLatest logger v cid sql parentCreationTime doRead = + readFromNthParent logger v cid sql parentCreationTime 0 doRead >>= \case NoHistory -> error "readFromLatest: failed to grab the latest header, this is a bug in chainweb" Historical a -> return a @@ -126,11 +121,11 @@ readFromNthParent -> ChainwebVersion -> ChainId -> SQLiteEnv - -> NewBlockCtx + -> Parent BlockCreationTime -> Word -> (BlockEnv -> BlockHandle -> IO a) -> IO (Historical a) -readFromNthParent logger v cid sql newBlockCtx n doRead = do +readFromNthParent logger v cid sql parentCreationTime n doRead = do withSavepoint sql ReadFromNSavepoint $ do latest <- _consensusStateLatest . fromJuste <$> getConsensusState sql if genesisHeight v cid + fromIntegral @Word @BlockHeight n < _syncStateHeight latest @@ -141,7 +136,7 @@ readFromNthParent logger v cid sql newBlockCtx n doRead = do -- this case for shallow nodes without enough history Nothing -> return NoHistory Just nthBlock -> - readFrom logger v cid sql newBlockCtx (parentBlockHeight v cid nthBlock) doRead + readFrom logger v cid sql parentCreationTime (parentBlockHeight v cid nthBlock) doRead -- read-only rewind to a target block. -- if that target block is missing, return Nothing. @@ -151,17 +146,17 @@ readFrom -> ChainwebVersion -> ChainId -> SQLiteEnv - -> NewBlockCtx + -> Parent BlockCreationTime -- ^ you can fake this if you're not making a new block -> Parent RankedBlockHash -> (BlockEnv -> BlockHandle -> IO a) -> IO (Historical a) -readFrom logger v cid sql newBlockCtx parent doRead = do +readFrom logger v cid sql parentCreationTime parent doRead = do let blockCtx = BlockCtx - { _bctxParentCreationTime = _newBlockCtxParentCreationTime newBlockCtx + { _bctxParentCreationTime = parentCreationTime , _bctxParentHash = _rankedBlockHashHash <$> parent , _bctxParentHeight = _rankedBlockHashHeight <$> parent - , _bctxMinerReward = _newBlockCtxMinerReward newBlockCtx + , _bctxMinerReward = blockMinerReward v (childBlockHeight v cid parent) , _bctxChainwebVersion = v , _bctxChainId = cid } diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index d74fe0ccd0..2de9af2e6c 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -145,7 +145,7 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do let startTxs = _transactionPairs (_blockInProgressTransactions blockInProgress) let startTxsRequestKeys = - foldMap' (S.singleton . pactRequestKeyToTransactionHash . view Pact.crReqKey . snd) startTxs + foldMap' (S.singleton . pactRequestKeyToTransactionHash . view Pact.crReqKey . ssnd) startTxs let initState = BlockFill { _bfTxHashes = startTxsRequestKeys , _bfGasLimit = _blockInProgressRemainingGasLimit blockInProgress @@ -162,7 +162,7 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do liftIO $ mpaBadlistTx mpAccess (V.fromList $ fmap pactRequestKeyToTransactionHash $ concat invalids) - liftIO $ logFunctionText logger Debug $ "Order of completed transactions: " <> sshow (map (Pact.unRequestKey . Pact._crReqKey . snd) $ concat $ reverse valids) + liftIO $ logFunctionText logger Debug $ "Order of completed transactions: " <> sshow (map (Pact.unRequestKey . Pact._crReqKey . ssnd) $ concat $ reverse valids) let !blockInProgress' = blockInProgress & blockInProgressHandle .~ finalBlockHandle @@ -171,7 +171,7 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do & blockInProgressRemainingGasLimit .~ finalGasLimit - liftIO $ logFunctionText logger Debug $ "Final block transaction order: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . snd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) + liftIO $ logFunctionText logger Debug $ "Final block transaction order: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . ssnd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) return blockInProgress' return blockInProgress' @@ -203,8 +203,10 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do execNewTransactions prevRemainingGas txTimeLimit newTxs liftIO $ do - logFunctionText logger Debug $ "Refill: included request keys: " <> sshow @[Hash] (fmap (Pact.unRequestKey . Pact._crReqKey . snd) newCompletedTransactions) - logFunctionText logger Debug $ "Refill: badlisted request keys: " <> sshow @[Hash] (fmap Pact.unRequestKey newInvalidTransactions) + logFunctionText logger Debug $ "Refill: included request keys: " + <> sshow @[Hash] (fmap (Pact.unRequestKey . Pact._crReqKey . ssnd) newCompletedTransactions) + logFunctionText logger Debug $ "Refill: badlisted request keys: " + <> sshow @[Hash] (fmap Pact.unRequestKey newInvalidTransactions) let newBlockFillState = BlockFill { _bfCount = succ prevFillCount @@ -271,7 +273,7 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do logFunctionText logger Debug "applyCmdInBlock buy gas succeeded" ((as, timedOut), s'') <- runStateT rest s' let !txBytes = commandToBytes tx - return ((Right (txBytes, a):as, timedOut), s'') + return ((Right (T2 txBytes a):as, timedOut), s'') ) (return ([], False)) (zip [0..] (V.toList txs)) @@ -289,7 +291,7 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do let blockCtx = _psBlockCtx blockEnv liftIO $ mpaGetBlock mpAccess blockFillState validate (evaluationCtxOfBlockCtx blockCtx) -type CompletedTransactions = [(Chainweb.Transaction, Pact.OffChainCommandResult)] +type CompletedTransactions = [T2 Chainweb.Transaction Pact.OffChainCommandResult] type InvalidTransactions = [Pact.RequestKey] -- Apply a Pact command in the current block. @@ -501,12 +503,6 @@ pact5TransactionsFromPayload plData = do toCWTransaction bs = evaluate (force (codecDecode commandCodec $ _transactionBytes bs)) --- introduce a new state temporarily for an inner action -weaveStatesFst :: Monad m => StateT (s1, s2) m a -> s2 -> StateT s1 m (a, s2) -weaveStatesFst act s2 = StateT $ \s1 -> do - (a, (s1', s2')) <- runStateT act (s1, s2) - return ((a, s2'), s1') - execExistingBlock :: (CanReadablePayloadCas tbl, Logger logger) => logger @@ -541,7 +537,7 @@ execExistingBlock logger serviceEnv blockEnv payload = do (V.fromList -> results, _finalBlockGasLimit) <- flip weaveStatesFst blockGasLimit $ -- flip runStateT (postCoinbaseBlockHandle, blockGasLimit) $ forM (zip [0..] (V.toList txs)) $ \(txIdxInBlock, tx) -> - (tx,) <$> + T2 tx <$> (mapStateT (mapExceptT (liftIO . fmap (over _Left BlockInvalidDueToInvalidTxAtRuntime))) $ applyCmdInBlock logger serviceEnv blockEnv miner (TxBlockIdx txIdxInBlock) tx) -- incorporate the final state of the transactions into the block state @@ -551,6 +547,13 @@ execExistingBlock logger serviceEnv blockEnv payload = do pwo <- liftEither . over _Left BlockInvalidDueToOutputMismatch $ validateHashes blockCtx payload miner (Transactions results coinbaseResult) return (totalGasUsed, pwo, txs) + where + -- introduce a new state, s2, temporarily for an inner action + weaveStatesFst :: Monad m => StateT (s1, s2) m a -> s2 -> StateT s1 m (a, s2) + weaveStatesFst act s2 = StateT $ \s1 -> do + (a, (s1', s2')) <- runStateT act (s1, s2) + return ((a, s2'), s1') + -- | Check that the two payloads agree. If we have access to the outputs, we -- check those too. diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 713ec03de1..131044bb6d 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -195,7 +195,7 @@ import Control.Concurrent.Async import qualified Data.Aeson as A data Transactions t r = Transactions - { _transactionPairs :: !(Vector (t, r)) + { _transactionPairs :: !(Vector (T2 t r)) , _transactionCoinbase :: !r } deriving stock (Eq, Show, Functor, Foldable, Traversable, Generic) @@ -526,7 +526,7 @@ data ServiceEnv tbl = ServiceEnv -- If unset, defaults to a reasonable value based on the transaction's gas limit. , _psMiner :: Maybe Miner -- ^ Miner identity for use in newly mined blocks. - , _psMiningPayloadVar :: TMVar (Async (), NewBlockCtx, BlockInProgress) + , _psMiningPayloadVar :: TMVar (Async (), BlockInProgress) -- ^ Latest mining payload produced, and block continuation thread. , _psNewBlockGasLimit :: Pact.GasLimit -- ^ Block gas limit in newly produced blocks. @@ -731,12 +731,12 @@ toPayloadWithOutputs -> Chainweb.PayloadWithOutputs toPayloadWithOutputs mi ts = let - oldSeq :: Vector (Chainweb.Transaction, OffChainCommandResult) + oldSeq :: Vector (T2 Chainweb.Transaction OffChainCommandResult) oldSeq = _transactionPairs ts trans :: Vector Chainweb.Transaction - trans = fst <$> oldSeq + trans = sfst <$> oldSeq transOuts :: Vector Chainweb.TransactionOutput - transOuts = Chainweb.TransactionOutput . pactCommandResultToBytes . hashPactTxLogs . snd <$> oldSeq + transOuts = Chainweb.TransactionOutput . pactCommandResultToBytes . hashPactTxLogs . ssnd <$> oldSeq miner :: Chainweb.MinerData miner = toMinerData mi diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index dec440850f..2370580700 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -59,7 +59,7 @@ instance (Logger logger, CanPayloadCas tbl) => PayloadProvider (PactPayloadProvi latestPayloadSTM :: Logger logger => PactPayloadProvider logger tbl -> STM NewPayload latestPayloadSTM (PactPayloadProvider _logger e) = do - (_, _, bip) <- readTMVar (_psMiningPayloadVar e) + (_, bip) <- readTMVar (_psMiningPayloadVar e) let pwo = toPayloadWithOutputs (fromJuste $ _psMiner e) (_blockInProgressTransactions bip) diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index e4c6c6d55e..813b65b94d 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -1354,7 +1354,7 @@ thd (_,_,c) = c -- Strict Tuple data T2 a b = T2 !a !b - deriving (Show, Eq, Ord, Generic, NFData, Functor) + deriving (Show, Eq, Ord, Generic, NFData, Functor, Foldable, Traversable) instance (Semigroup a, Semigroup b) => Semigroup (T2 a b) where T2 a b <> T2 a' b' = T2 (a <> a') (b <> b') diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index 02984faed7..4805268839 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -20,7 +20,7 @@ import Chainweb.BlockHeader import Chainweb.Graph (singletonChainGraph, petersonChainGraph) import Chainweb.Miner.Pact (Miner(..), MinerId(..), MinerGuard(..), noMiner) import Chainweb.Pact.PactService (initialPayloadState, withPactService) -import Chainweb.Pact.PactService.Checkpointer (readFrom, mkFakeNewBlockCtx) +import Chainweb.Pact.PactService.Checkpointer (readFrom, mkFakeParentCreationTime) import Chainweb.Pact.Types import Chainweb.Pact.Transaction import Chainweb.Pact.TransactionExec @@ -75,7 +75,6 @@ import Chainweb.Logger import Chainweb.Pact.Backend.InMemDb qualified as InMemDb import Chainweb.Pact.Backend.Types import Control.Monad.State.Strict -import qualified Network.HTTP.Client as HTTP import qualified Data.Pool as Pool import Chainweb.Parent import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as PIN0 @@ -90,20 +89,20 @@ tests baseRdb = testGroup "Pact5 TransactionExecTest" , testCase "run payload should return an EvalResult related to the input command" (runPayloadShouldReturnEvalResultRelatedToTheInputCommand baseRdb) , testCase "applyLocal spec" (applyLocalSpec baseRdb) , testCase "applyCmd spec" (applyCmdSpec baseRdb) - -- , testCase "applyCmd verifier spec" (applyCmdVerifierSpec baseRdb) - -- , testCase "applyCmd failure spec" (applyCmdFailureSpec baseRdb) - -- , testCase "applyCmd coin.transfer" (applyCmdCoinTransfer baseRdb) - -- , testCase "applyCoinbase spec" (applyCoinbaseSpec baseRdb) + , testCase "applyCmd verifier spec" (applyCmdVerifierSpec baseRdb) + , testCase "applyCmd failure spec" (applyCmdFailureSpec baseRdb) + , testCase "applyCmd coin.transfer" (applyCmdCoinTransfer baseRdb) + , testCase "applyCoinbase spec" (applyCoinbaseSpec baseRdb) -- , testCase "test coin upgrade" (testCoinUpgrade baseRdb) - -- , testCase "test local only fails outside of local" (testLocalOnlyFailsOutsideOfLocal baseRdb) - -- , testCase "payload failure all gas should go to the miner - type error" (payloadFailureShouldPayAllGasToTheMinerTypeError baseRdb) - -- , testCase "payload failure all gas should go to the miner - insufficient funds" (payloadFailureShouldPayAllGasToTheMinerInsufficientFunds baseRdb) - -- , testCase "event spec" (testEvents baseRdb) - -- , testCase "writes from failed transaction should not make it into the db" (testWritesFromFailedTxDontMakeItIn baseRdb) - -- , testCase "quirk spec" (quirkSpec baseRdb) - -- , testCase "test writes to nonexistent tables" (testWritesToNonExistentTables baseRdb) + , testCase "test local only fails outside of local" (testLocalOnlyFailsOutsideOfLocal baseRdb) + , testCase "payload failure all gas should go to the miner - type error" (payloadFailureShouldPayAllGasToTheMinerTypeError baseRdb) + , testCase "payload failure all gas should go to the miner - insufficient funds" (payloadFailureShouldPayAllGasToTheMinerInsufficientFunds baseRdb) + , testCase "event spec" (testEvents baseRdb) + , testCase "writes from failed transaction should not make it into the db" (testWritesFromFailedTxDontMakeItIn baseRdb) + , testCase "quirk spec" (quirkSpec baseRdb) + , testCase "test writes to nonexistent tables" (testWritesToNonExistentTables baseRdb) -- , testCase "test CommandResult 5 is valid for 4" (testCommandResult5To4 baseRdb) - -- , testCase "test hash-keccak256" (testKeccak256 baseRdb) + , testCase "test hash-keccak256" (testKeccak256 baseRdb) ] -- | Run with the context being that the parent block is the genesis block @@ -121,9 +120,9 @@ readFromAfterGenesis ver rdb act = runResourceT $ do logger <- testLogger withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (testPactServiceConfig PIN0.payloadBlock) $ \serviceEnv -> do initialPayloadState logger serviceEnv - fakeNewBlockCtx <- mkFakeNewBlockCtx + fakeParentCreationTime <- mkFakeParentCreationTime throwIfNoHistory =<< - readFrom logger ver cid sql fakeNewBlockCtx + readFrom logger ver cid sql fakeParentCreationTime (Parent (gh ver cid ^. rankedBlockHash)) act @@ -736,7 +735,9 @@ applyCoinbaseSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> startMinerBal <- readBal pactDb "NoMiner" logger <- testLogger - applyCoinbase logger pactDb 5 (_psBlockCtx blockEnv) + -- reward is 23.04523 (first entry in miner_rewards.csv) divided by #chains + let expectedMiningReward = 23.04523 / 10 + applyCoinbase logger pactDb noMiner (_psBlockCtx blockEnv) >>= P.match _Right ? P.checkAll [ P.fun _crResult ? P.equals ? PactResultOk (PString "Write succeeded") @@ -750,13 +751,13 @@ applyCoinbaseSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> , P.fun _crEvents ? P.list [ event (P.equals "TRANSFER") - (P.equals [PString "", PString "NoMiner", PDecimal 5.0]) + (P.equals [PString "", PString "NoMiner", PDecimal expectedMiningReward]) (P.equals coinModuleName) ] ] endMinerBal <- readBal pactDb "NoMiner" assertEqual "miner balance should include block reward" - (Just $ fromMaybe 0 startMinerBal + 5) + (Just $ fromMaybe 0 startMinerBal + expectedMiningReward) endMinerBal testEvents :: RocksDb -> IO () @@ -914,8 +915,7 @@ testCommandResult5To4 rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle - ? P.succeed , P.match _Right ? P.fun (fmap pactErrorToOnChainError) - ? P.fun hashPact5TxLogs - ? P.fun toPact4CommandResult + ? P.fun hashPactTxLogs ? P.forced ] From 2ace3ec260d9e7551c114332fb2942051b1fa793 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 15 Apr 2025 13:31:03 -0400 Subject: [PATCH 106/378] Starting to adapt PactServiceTest but stopping I would like to have a tool that lets me find forks, along the lines of what catchup needs to do, to send them to the payload provider. Change-Id: Id0000000749d6a4f42822c5e7d74d315b2473b99 --- chainweb.cabal | 1 + src/Chainweb/Backup.hs | 26 +- src/Chainweb/Pact/Backend/Compaction.hs | 9 +- src/Chainweb/Pact/Backend/PactState.hs | 18 +- src/Chainweb/Pact/Backend/PactState/Diff.hs | 48 +- src/Chainweb/Pact/Backend/Utils.hs | 6 +- src/Chainweb/Pact/PactService.hs | 35 +- src/Chainweb/PayloadProvider/Pact.hs | 18 +- test/lib/Chainweb/Test/TestVersions.hs | 2 +- .../Chainweb/Test/Pact/PactServiceTest.hs | 998 +++++++++--------- .../unit/Chainweb/Test/Pact/RemotePactTest.hs | 1 - .../Chainweb/Test/Pact/TransactionExecTest.hs | 50 +- .../Chainweb/Test/Pact/TransactionTests.hs | 12 +- 13 files changed, 603 insertions(+), 621 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index 9db3fc7299..4cc4db34e0 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -425,6 +425,7 @@ library , pem >=0.2 , primitive >= 0.7.1.0 , random >= 1.2 + , resourcet >= 1.3 , resource-pool >= 0.4 , rocksdb-haskell-kadena >= 1.1.0 , safe-exceptions >= 0.1 diff --git a/src/Chainweb/Backup.hs b/src/Chainweb/Backup.hs index a3daf520a7..5a121d2925 100644 --- a/src/Chainweb/Backup.hs +++ b/src/Chainweb/Backup.hs @@ -21,6 +21,8 @@ import Control.Lens import Control.Concurrent.Async import Control.Monad import Control.Monad.Catch +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource import Data.HashSet(HashSet) import Data.String import qualified Data.Text as T @@ -47,12 +49,12 @@ data BackupOptions = BackupOptions } data BackupEnv logger = BackupEnv - { _backupRocksDb :: !RocksDb - , _backupDir :: !FilePath - , _backupPactDbDir :: !FilePath - , _backupChainIds :: !(HashSet ChainId) - , _backupLogger :: !logger - } + { _backupRocksDb :: !RocksDb + , _backupDir :: !FilePath + , _backupPactDbDir :: !FilePath + , _backupChainIds :: !(HashSet ChainId) + , _backupLogger :: !logger + } data BackupStatus = BackupDone | BackupInProgress | BackupFailed @@ -97,12 +99,12 @@ makeBackup env options = do logCr Info "rocksdb checkpoint made" when (_backupPact options) $ do logCr Info $ "backing up pact databases" <> T.pack thisBackup - forConcurrently_ (_backupChainIds env) $ \cid -> do - withSqliteDb cid (_backupLogger env) (_backupPactDbDir env) False $ \db -> - void $ qry db - ("VACUUM main INTO ?") - [SText $ fromString (thisBackup "0" "sqlite" chainDbFileName cid)] - [] + forConcurrently_ (_backupChainIds env) $ \cid -> runResourceT $ do + db <- withSqliteDb cid (_backupLogger env) (_backupPactDbDir env) False + liftIO $ void $ qry db + ("VACUUM main INTO ?") + [SText $ fromString (thisBackup "0" "sqlite" chainDbFileName cid)] + [] logCr Info $ "pact databases backed up" checkBackup :: Logger logger => BackupEnv logger -> FilePath -> IO (Maybe BackupStatus) diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index 9272df2c7d..63f68bc9bf 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -37,6 +37,7 @@ module Chainweb.Pact.Backend.Compaction import "base" Control.Exception hiding (Handler) import "base" Control.Monad (forM, forM_, unless, void, when) import "base" Control.Monad.IO.Class (MonadIO(liftIO)) +import Control.Monad.Trans.Resource (runResourceT) import "base" Data.Function ((&)) import "base" Data.Int (Int64) import "base" Data.Maybe (fromMaybe) @@ -388,10 +389,10 @@ compact cfg = do } when (not cfg.noPactState) $ do forChains_ cfg.concurrent cids $ \cid -> do - withPerChainFileLogger cfg.logDir cid LL.Debug $ \logger -> do - withChainDb cid logger (pactDir cfg.fromDir) $ \_ srcDb -> do - withChainDb cid logger (pactDir cfg.toDir) $ \_ targetDb -> do - compactPactState logger retainment targetBlockHeight srcDb targetDb + withPerChainFileLogger cfg.logDir cid LL.Debug $ \logger -> runResourceT $ do + srcDb <- withChainDb cid logger (pactDir cfg.fromDir) + targetDb <- withChainDb cid logger (pactDir cfg.toDir) + liftIO $ compactPactState logger retainment targetBlockHeight srcDb targetDb compactTable :: (Logger logger) => logger -- ^ logger diff --git a/src/Chainweb/Pact/Backend/PactState.hs b/src/Chainweb/Pact/Backend/PactState.hs index 0d01bde6b1..904bc5e5b7 100644 --- a/src/Chainweb/Pact/Backend/PactState.hs +++ b/src/Chainweb/Pact/Backend/PactState.hs @@ -88,6 +88,7 @@ import Streaming.Prelude (Stream, Of) import Streaming.Prelude qualified as S import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils hiding (tbl) +import Control.Monad.Trans.Resource excludedTables :: [Utf8] excludedTables = checkpointerTables ++ compactionTables @@ -129,8 +130,9 @@ getLatestCommonBlockHeight :: (Logger logger) -> [ChainId] -> IO BlockHeight getLatestCommonBlockHeight logger path cids = do - fmap minimum $ forM cids $ \cid -> withChainDb cid logger path $ \_ sqlEnv -> do - getLatestBlockHeight sqlEnv + fmap minimum $ forM cids $ \cid -> runResourceT $ do + sqlEnv <- withChainDb cid logger path + liftIO $ getLatestBlockHeight sqlEnv getEarliestCommonBlockHeight :: (Logger logger) => logger @@ -138,8 +140,9 @@ getEarliestCommonBlockHeight :: (Logger logger) -> [ChainId] -> IO BlockHeight getEarliestCommonBlockHeight logger path cids = do - fmap maximum $ forM cids $ \cid -> withChainDb cid logger path $ \_ sqlEnv -> do - getEarliestBlockHeight sqlEnv + fmap maximum $ forM cids $ \cid -> runResourceT $ do + sqlEnv <- withChainDb cid logger path + liftIO $ getEarliestBlockHeight sqlEnv -- | Wrapper around 'withSqliteDb' that adds the chainId label to the logger -- and sets resetDb to False. @@ -147,12 +150,11 @@ withChainDb :: (Logger logger) => ChainId -> logger -> FilePath - -> (logger -> SQLiteEnv -> IO x) - -> IO x -withChainDb cid logger' path f = do + -> ResourceT IO SQLiteEnv +withChainDb cid logger' path = do let logger = addChainIdLabel cid logger' let resetDb = False - withSqliteDb cid logger path resetDb (f logger) + withSqliteDb cid logger path resetDb -- | Get all Pact table names in the database. getPactTableNames :: Database -> Stream (Of Utf8) IO () diff --git a/src/Chainweb/Pact/Backend/PactState/Diff.hs b/src/Chainweb/Pact/Backend/PactState/Diff.hs index 5dd52e29fc..49353068b5 100644 --- a/src/Chainweb/Pact/Backend/PactState/Diff.hs +++ b/src/Chainweb/Pact/Backend/PactState/Diff.hs @@ -50,6 +50,7 @@ import Options.Applicative import Streaming.Prelude (Stream, Of) import System.Exit (exitFailure) import System.LogLevel (LogLevel(..)) +import Control.Monad.Trans.Resource (runResourceT) data PactDiffConfig = PactDiffConfig { firstDbDir :: FilePath @@ -90,30 +91,31 @@ main = do sqliteFileExists2 <- doesPactDbExist cid cfg.secondDbDir if | not sqliteFileExists1 -> do - logText Warn $ "[SQLite for chain in " <> Text.pack cfg.firstDbDir <> " doesn't exist. Skipping]" + logText Warn $ "[SQLite for chain in " <> Text.pack cfg.firstDbDir <> " doesn't exist. Skipping]" | not sqliteFileExists2 -> do - logText Warn $ "[SQLite for chain in " <> Text.pack cfg.secondDbDir <> " doesn't exist. Skipping]" - | otherwise -> do - withChainDb cid logger cfg.firstDbDir $ \_ db1 -> do - withChainDb cid logger cfg.secondDbDir $ \_ db2 -> do - logText Info "[Starting diff]" - let getPactState db = getLatestPactStateAtDiffable db cfg.target - let diff :: Stream (Of (Text, Stream (Of RowKeyDiffExists) IO ())) IO () - diff = diffLatestPactState (getPactState db1) (getPactState db2) - isDifferent <- S.foldMap_ id $ flip S.mapM diff $ \(tblName, tblDiff) -> do - logText Info $ "[Starting table " <> tblName <> "]" - d <- S.foldMap_ id $ flip S.mapM tblDiff $ \d -> do - logFunctionJson logger Warn $ rowKeyDiffExistsToObject d - pure Difference - logText Info $ "[Finished table " <> tblName <> "]" - pure d - - logText Info $ case isDifferent of - Difference -> "[Non-empty diff]" - NoDifference -> "[Empty diff]" - logText Info $ "[Finished chain " <> chainIdToText cid <> "]" - - atomicModifyIORef' isDifferentRef $ \m -> (M.insert cid isDifferent m, ()) + logText Warn $ "[SQLite for chain in " <> Text.pack cfg.secondDbDir <> " doesn't exist. Skipping]" + | otherwise -> runResourceT $ do + db1 <- withChainDb cid logger cfg.firstDbDir + db2 <- withChainDb cid logger cfg.secondDbDir + liftIO $ do + logText Info "[Starting diff]" + let getPactState db = getLatestPactStateAtDiffable db cfg.target + let diff :: Stream (Of (Text, Stream (Of RowKeyDiffExists) IO ())) IO () + diff = diffLatestPactState (getPactState db1) (getPactState db2) + isDifferent <- S.foldMap_ id $ flip S.mapM diff $ \(tblName, tblDiff) -> do + logText Info $ "[Starting table " <> tblName <> "]" + d <- S.foldMap_ id $ flip S.mapM tblDiff $ \d -> do + logFunctionJson logger Warn $ rowKeyDiffExistsToObject d + pure Difference + logText Info $ "[Finished table " <> tblName <> "]" + pure d + + logText Info $ case isDifferent of + Difference -> "[Non-empty diff]" + NoDifference -> "[Empty diff]" + logText Info $ "[Finished chain " <> chainIdToText cid <> "]" + + atomicModifyIORef' isDifferentRef $ \m -> (M.insert cid isDifferent m, ()) isDifferent <- readIORef isDifferentRef case M.foldMapWithKey (\_ d -> d) isDifferent of diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 647c3a8cbf..7a982f66aa 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -80,6 +80,7 @@ module Chainweb.Pact.Backend.Utils import Control.Exception.Safe import Control.Monad import Control.Monad.State.Strict +import Control.Monad.Trans.Resource (ResourceT, allocate) import Data.Bits import Data.Foldable @@ -252,9 +253,8 @@ withSqliteDb -> logger -> FilePath -> Bool - -> (SQLiteEnv -> IO a) - -> IO a -withSqliteDb cid logger dbDir resetDb = bracket + -> ResourceT IO SQLiteEnv +withSqliteDb cid logger dbDir resetDb = snd <$> allocate (startSqliteDb cid logger dbDir resetDb) stopSqliteDb diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 73117e82b1..fb30195ac1 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -28,8 +28,6 @@ -- module Chainweb.Pact.PactService ( initialPayloadState - -- , execNewBlock - -- , execContinueBlock , syncToFork -- , execTransactions , execLocal @@ -42,10 +40,13 @@ module Chainweb.Pact.PactService import Control.Concurrent.Async import Control.Concurrent.STM -import Control.Exception.Safe import Control.Lens hiding ((:>)) import Control.Monad +import Control.Monad.Cont (evalContT) +import Control.Monad.Except import Control.Monad.Reader +import Control.Monad.State.Strict +import Control.Monad.Trans.Resource import Data.Either import Data.Foldable (traverse_) @@ -98,12 +99,10 @@ import Data.Coerce (coerce) import Data.Void import Chainweb.Pact.PactService.ExecBlock qualified as Pact import qualified Pact.Core.Evaluate as Pact -import Control.Monad.Except import qualified Pact.Core.Errors as Pact import Chainweb.Pact.Backend.Types import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer import qualified Pact.Core.StableEncoding as Pact -import Control.Monad.Cont (evalContT) import qualified Data.List.NonEmpty as NonEmpty import Chainweb.PayloadProvider import Chainweb.Storage.Table @@ -117,7 +116,6 @@ import qualified Data.DList as DList import Chainweb.Ranked import qualified Chainweb.Pact.Backend.ChainwebPactDb as ChainwebPactDb import qualified Pact.Core.ChainData as Pact -import Control.Monad.State.Strict import GHC.Stack (HasCallStack) import qualified Data.Pool as Pool import qualified Data.List.NonEmpty as NEL @@ -136,16 +134,20 @@ withPactService -> Pool SQLiteEnv -> SQLiteEnv -> PactServiceConfig - -> (ServiceEnv tbl -> IO a) - -> IO a -withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config act = do + -> ResourceT IO (ServiceEnv tbl) +withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config = do SomeChainwebVersionT @v _ <- pure $ someChainwebVersionVal ver SomeChainIdT @c _ <- pure $ someChainIdVal cid let payloadClient = Rest.payloadClient @v @c @'PactProvider - payloadStore <- newPayloadStore http (logFunction chainwebLogger) pdb payloadClient - miningPayloadVar <- newEmptyTMVarIO - ChainwebPactDb.initSchema readWriteSqlenv - candidatePdb <- MapTable.emptyTable + payloadStore <- liftIO $ newPayloadStore http (logFunction chainwebLogger) pdb payloadClient + (_, miningPayloadVar) <- allocate newEmptyTMVarIO + (\v -> do + refresherThread <- fmap (view _1) <$> atomically (tryReadTMVar v) + traverse_ cancel refresherThread + ) + + liftIO $ ChainwebPactDb.initSchema readWriteSqlenv + candidatePdb <- liftIO MapTable.emptyTable let !pse = ServiceEnv { _psVersion = ver @@ -168,12 +170,7 @@ withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb , _psGenesisPayload = _pactGenesisPayload config } - let run = act pse - let cancelRefresher = do - refresherThread <- fmap (view _1) <$> atomically (tryReadTMVar (_psMiningPayloadVar pse)) - traverse_ cancel refresherThread - - run `finally` cancelRefresher + return pse initialPayloadState :: Logger logger diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 2370580700..30c4c960ad 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -12,6 +12,7 @@ module Chainweb.PayloadProvider.Pact ( PactPayloadProvider(..) , withPactPayloadProvider + , pactMemPoolAccess ) where import Control.Concurrent.STM @@ -21,7 +22,6 @@ import System.LogLevel import Control.Lens import qualified Network.HTTP.Client as HTTP import qualified Data.Vector as V -import Control.Monad.Reader import Chainweb.ChainId import Chainweb.Counter @@ -38,6 +38,7 @@ import Chainweb.PayloadProvider import Chainweb.Utils import Chainweb.Version import qualified Data.Pool as Pool +import Control.Monad.Trans.Resource (ResourceT, allocate) data PactPayloadProvider logger tbl = PactPayloadProvider logger (ServiceEnv tbl) @@ -102,18 +103,19 @@ withPactPayloadProvider -> PayloadDb tbl -> FilePath -> PactServiceConfig - -> (PactPayloadProvider logger tbl -> IO a) - -> IO a -withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir config action = - withSqliteDb cid logger pactDbDir False $ \readWriteSqlenv -> do - readOnlySqlPool <- Pool.newPool $ Pool.defaultPoolConfig + -> ResourceT IO (PactPayloadProvider logger tbl) +withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir config = do + readWriteSqlenv <- withSqliteDb cid logger pactDbDir False + (_, readOnlySqlPool) <- allocate + (Pool.newPool $ Pool.defaultPoolConfig (startReadSqliteDb cid logger pactDbDir) stopSqliteDb 10 -- seconds to keep them around unused 2 -- connections at most & Pool.setNumStripes (Just 2) -- two stripes, one connection per stripe - PactService.withPactService ver cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config - (action . PactPayloadProvider logger) + ) + Pool.destroyAllResources + PactPayloadProvider logger <$> PactService.withPactService ver cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config where mpa = pactMemPoolAccess mp $ addLabel ("sub-component", "MempoolAccess") logger diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index b849d6dfe0..fffd4bb878 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -47,7 +47,7 @@ import Chainweb.Version import Chainweb.Version.Registry import P2P.Peer -import Chainweb.Payload (PayloadWithOutputs_(_payloadWithOutputsPayloadHash)) +import Chainweb.Payload (PayloadWithOutputs_(_payloadWithOutputsPayloadHash), PayloadWithOutputs) import qualified Pact.Core.Names as Pact import qualified Pact.Core.Gas as Pact diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index fcf4a54f33..621a762da0 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -29,15 +29,10 @@ import Chainweb.Chainweb import Chainweb.Cut import Chainweb.Graph (singletonChainGraph) import Chainweb.Logger -import Chainweb.Mempool.Consensus import Chainweb.Mempool.InMem import Chainweb.Mempool.Mempool (InsertType (..), MempoolBackend (..)) import Chainweb.Miner.Pact import Chainweb.Pact.PactService -import Chainweb.Pact.PactService.Pact4.ExecBlock () -import Chainweb.Pact.Service.BlockValidation -import Chainweb.Pact.Service.PactInProcApi -import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Transaction qualified as Pact5 @@ -52,7 +47,6 @@ import Chainweb.Time import Chainweb.Utils import Chainweb.Version import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) -import Chainweb.WebPactExecutionService import Control.Concurrent hiding (throwTo) import Control.Concurrent.Async (forConcurrently) import Control.Exception (AsyncException (..)) @@ -84,532 +78,545 @@ import PropertyMatchers qualified as P import Test.Tasty import Test.Tasty.HUnit (assertBool, assertEqual, assertFailure, testCase) import Text.Printf (printf) +import qualified Data.Pool as Pool +import qualified Data.Pool as Pool +import Chainweb.Pact.Types (testPactServiceConfig, psMempoolAccess) +import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN +import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 +import Chainweb.PayloadProvider.Pact (pactMemPoolAccess) +import Chainweb.Pact.PactService (execPreInsertCheckReq) data Fixture = Fixture { _fixtureBlockDb :: TestBlockDb - , _fixtureMempools :: ChainMap (MempoolBackend Pact4.UnparsedTransaction) - , _fixturePactQueues :: ChainMap PactQueue + , _fixtureMempools :: ChainMap (MempoolBackend Pact.Transaction) + , _fixturePacts :: ChainMap (ServiceEnv RocksDbTable) } -mkFixtureWith :: PactServiceConfig -> RocksDb -> ResourceT IO Fixture +v :: ChainwebVersion +v = instantCpmTestVersion singletonChainGraph + +mkFixtureWith :: (ChainId -> PactServiceConfig) -> RocksDb -> ResourceT IO Fixture mkFixtureWith pactServiceConfig baseRdb = do - sqlite <- withTempSQLiteResource tdb <- mkTestBlockDb v baseRdb perChain <- iforM (HashSet.toMap (chainIds v)) $ \chain () -> do - bhdb <- liftIO $ getWebBlockHeaderDb (_bdbWebBlockHeaderDb tdb) chain - pactQueue <- liftIO $ newPactQueue 2_000 - pactExecutionServiceVar <- liftIO $ newMVar (mkPactExecutionService pactQueue) - let mempoolCfg = validatingMempoolConfig chain v (Pact4.GasLimit 150_000) (Pact4.GasPrice 1e-8) pactExecutionServiceVar + sqlite <- withTempSQLiteResource + let pdb = _bdbPayloadDb tdb logLevel <- liftIO getTestLogLevel let logger = genericLogger logLevel Text.putStrLn + fakeRoSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sqlite) (\_ -> return ()) 10 10) + serviceEnv <- withPactService v chain Nothing mempty logger Nothing pdb fakeRoSqlPool sqlite (pactServiceConfig chain) + let mempoolCfg = + validatingMempoolConfig chain v + (GasLimit (Gas 150_000)) + (GasPrice 1e-8) + (execPreInsertCheckReq logger serviceEnv) mempool <- liftIO $ startInMemoryMempoolTest mempoolCfg - mempoolConsensus <- liftIO $ mkMempoolConsensus mempool bhdb (Just (_bdbPayloadDb tdb)) - let mempoolAccess = pactMemPoolAccess mempoolConsensus logger - _ <- Resource.allocate - (forkIO $ runPactService v chain logger Nothing pactQueue mempoolAccess bhdb (_bdbPayloadDb tdb) sqlite pactServiceConfig) - (\tid -> throwTo tid ThreadKilled) - return (mempool, pactQueue) + let mempoolAccess = pactMemPoolAccess mempool logger + return (mempool, serviceEnv & psMempoolAccess .~ mempoolAccess) let fixture = Fixture { _fixtureBlockDb = tdb , _fixtureMempools = OnChains $ fst <$> perChain - , _fixturePactQueues = OnChains $ snd <$> perChain + , _fixturePacts = OnChains $ snd <$> perChain } -- The mempool expires txs based on current time, but newBlock expires txs based on parent creation time. -- So by running an empty block with the creationTime set to the current time, we get these goals to align -- for future blocks we run. - _ <- liftIO $ advanceAllChains fixture $ onChains [] + -- _ <- liftIO $ advanceAllChains fixture $ onChains [] return fixture +genesisPayload chain = + if chain == unsafeChainId 0 + then IN0.payloadBlock + else INN.payloadBlock + mkFixture :: RocksDb -> ResourceT IO Fixture mkFixture baseRdb = do - mkFixtureWith testPactServiceConfig baseRdb + mkFixtureWith (testPactServiceConfig . genesisPayload) baseRdb tests :: RocksDb -> TestTree tests baseRdb = testGroup "Pact5 PactServiceTest" - [ testCase "simple end to end" (simpleEndToEnd baseRdb) - , testCase "continue block spec" (continueBlockSpec baseRdb) - , testCase "new block empty" (newBlockEmpty baseRdb) - , testCase "new block timeout spec" (newBlockTimeoutSpec baseRdb) - , testCase "new block excludes invalid transactions" (testNewBlockExcludesInvalid baseRdb) - , testCase "lookup pact txs spec" (lookupPactTxsSpec baseRdb) - , testCase "failed txs should go into blocks" (failedTxsShouldGoIntoBlocks baseRdb) - , testCase "modules with higher level transitive dependencies (simple)" (modulesWithHigherLevelTransitiveDependenciesSimple baseRdb) - , testCase "modules with higher level transitive dependencies (complex)" (modulesWithHigherLevelTransitiveDependenciesComplex baseRdb) - ] - -simpleEndToEnd :: RocksDb -> IO () -simpleEndToEnd baseRdb = runResourceT $ do - fixture <- mkFixture baseRdb - liftIO $ do - cmd1 <- buildCwCmd v (transferCmd 1.0) - cmd2 <- buildCwCmd v (transferCmd 2.0) - - results <- advanceAllChainsWithTxs fixture $ onChain chain0 [cmd1, cmd2] - - -- we only care that they succeed; specifics regarding their outputs are in TransactionExecTest - results & - P.alignExact ? onChain chain0 ? - P.alignExact ? Vector.replicate 2 successfulTx - -newBlockEmpty :: RocksDb -> IO () -newBlockEmpty baseRdb = runResourceT $ do - fixture <- mkFixture baseRdb - liftIO $ do - cmd <- buildCwCmd v (transferCmd 1.0) - _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolInsertPact5 mempool CheckedInsert [cmd] - -- -- Test that NewBlockEmpty ignores the mempool - emptyBip <- throwIfNotPact5 =<< throwIfNoHistory =<< - newBlock noMiner NewBlockEmpty (ParentHeader ph) pactQueue - let emptyPwo = finalizeBlock emptyBip - assertEqual "empty block has no transactions" 0 (Vector.length $ _payloadWithOutputsTransactions emptyPwo) - return emptyPwo - - results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue _ -> do - nonEmptyBip <- throwIfNotPact5 =<< throwIfNoHistory =<< - newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - return $ finalizeBlock nonEmptyBip - - results & - P.alignExact ? onChain chain0 ? - P.alignExact ? Vector.replicate 1 successfulTx - -continueBlockSpec :: RocksDb -> IO () -continueBlockSpec baseRdb = runResourceT $ do - fixture <- mkFixture baseRdb - liftIO $ do - startCut <- getCut fixture - - -- construct some transactions that we plan to put into the block - cmd1 <- buildCwCmd v (transferCmd 1.0) - cmd2 <- buildCwCmd v (transferCmd 2.0) - cmd3 <- buildCwCmd v (transferCmd 3.0) - - allAtOnceResults <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - -- insert all transactions - mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3] - -- construct a new block with all of said transactions - bipAllAtOnce <- throwIfNotPact5 =<< throwIfNoHistory =<< - newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - return $ finalizeBlock bipAllAtOnce - -- assert that 3 successful txs are in the block - allAtOnceResults & - P.alignExact ? onChain chain0 ? - P.alignExact ? Vector.replicate 3 successfulTx - - -- reset back to the empty block for the next phase - -- next, produce the same block by repeatedly extending a block - -- with the same transactions as were included in the original. - -- note that this will reinsert all txs in the full block into the - -- mempool, so we need to clear it after, or else the block will - -- contain all of the transactions before we extend it. - revert fixture startCut - continuedResults <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolClear mempool - mempoolInsertPact5 mempool CheckedInsert [cmd3] - bipStart <- throwIfNotPact5 =<< throwIfNoHistory =<< - newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - - mempoolInsertPact5 mempool CheckedInsert [cmd2] - bipContinued <- throwIfNoHistory =<< continueBlock bipStart pactQueue - - mempoolInsertPact5 mempool CheckedInsert [cmd1] - bipFinal <- throwIfNoHistory =<< continueBlock bipContinued pactQueue - - -- We must make progress on the same parent header - assertEqual "same parent header after continuing block" - (_blockInProgressParentHeader bipStart) - (_blockInProgressParentHeader bipContinued) - assertBool "made progress (1)" - (bipStart /= bipContinued) - assertEqual "same parent header after finishing block" - (_blockInProgressParentHeader bipContinued) - (_blockInProgressParentHeader bipFinal) - assertBool "made progress (2)" - (bipContinued /= bipFinal) - - return $ finalizeBlock bipFinal - - -- assert that the continued results are equal to doing it all at once - continuedResults & P.equals allAtOnceResults - --- * test that the NewBlock timeout works properly and doesn't leave any extra state from a timed-out transaction -newBlockTimeoutSpec :: RocksDb -> IO () -newBlockTimeoutSpec baseRdb = runResourceT $ do - let pactServiceConfig = testPactServiceConfig - { _pactTxTimeLimit = Just (Micros 35_000) - -- this may need to be tweaked for CI. - -- it should be long enough that `timeoutTx` times out - -- but neither `tx1` nor `tx2` time out. - } - fixture <- mkFixtureWith pactServiceConfig baseRdb - - liftIO $ do - tx1 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "1" - , _cbGasPrice = GasPrice 1.0 - , _cbGasLimit = GasLimit (Gas 400) - } - tx2 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "2" - , _cbGasPrice = GasPrice 2.0 - , _cbGasLimit = GasLimit (Gas 400) - } - timeoutTx <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' $ "(fold + 0 (enumerate 1 500000))" - , _cbGasPrice = GasPrice 1.5 - , _cbGasLimit = GasLimit (Gas 5000) - } - - _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolInsertPact5 mempool CheckedInsert [tx2, timeoutTx, tx1] - bip <- throwIfNotPact5 =<< throwIfNoHistory =<< - newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - -- Mempool orders by GasPrice. 'buildCwCmd' sets the gas price to the transfer amount. - -- We hope for 'timeoutTx' to fail, meaning that only 'txTransfer2' is in the block. - bip & P.fun _blockInProgressTransactions ? P.fun _transactionPairs - ? P.alignExact ? Vector.fromList - [ P.pair - (P.fun _cmdHash ? P.equals (_cmdHash tx2)) - successfulTx - ] - return $ finalizeBlock bip - - pure () - -testNewBlockExcludesInvalid :: RocksDb -> IO () -testNewBlockExcludesInvalid baseRdb = runResourceT $ do - fixture <- mkFixture baseRdb - liftIO $ do - -- The mempool should reject a tx that doesn't parse as valid pact. - badParse <- buildCwCmdNoParse v (defaultCmd chain0) - { _cbRPC = mkExec' "(not a valid pact tx" - } - - regularTx1 <- buildCwCmd v $ transferCmd 1.0 - -- The mempool checks that a tx does not already exist in the chain before adding it. - let badUnique = regularTx1 - - -- The mempool checks that a tx does not have a creation time too far into the future. - badFuture <- buildCwCmd v $ (transferCmd 1.0) - { _cbCreationTime = Just $ TxCreationTime (2 ^ (32 :: Word)) - } - - -- The mempool checks that a tx does not have a creation time too far into the past. - badPast <- buildCwCmd v $ (transferCmd 1.0) - { _cbCreationTime = Just $ TxCreationTime 0 - } - - regularTx2 <- buildCwCmd v $ transferCmd 1.0 - -- The mempool checks that a tx has a valid hash. - let badTxHash = regularTx2 - { _cmdHash = Pact5.Hash "bad hash" - } - - badSigs <- buildCwCmdNoParse v (defaultCmd chain0) - { _cbSigners = - [ CmdSigner - { _csSigner = Signer - { _siScheme = Nothing - , _siPubKey = fst sender00 - , _siAddress = Nothing - , _siCapList = [] - } - , _csPrivKey = snd sender01 - } - ] - } - - badChain <- buildCwCmd v $ transferCmd 1.0 & set cbChainId (chainIdToText $ unsafeChainId 1) - - let pact4Hash = Pact5.Hash . Pact4.unHash . Pact4.toUntypedHash . Pact4._cmdHash - _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolInsertPact5 mempool CheckedInsert [regularTx1] - bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - return $ finalizeBlock bip - - _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolInsert mempool UncheckedInsert $ Vector.fromList [badParse, badSigs] - mempoolInsertPact5 mempool UncheckedInsert [badChain, badUnique, badFuture, badPast, badTxHash] - bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - let expectedTxs = [] - let actualTxs = Vector.toList $ Vector.map (unRequestKey . _crReqKey . snd) $ _transactionPairs $ _blockInProgressTransactions bip - assertEqual "block has excluded all invalid transactions" expectedTxs actualTxs - return $ finalizeBlock bip - - -- we need to wait until this above block is validate for `badUnique` - -- to disappear, because only the parent block is used to find txs to - -- delete from the mempool - let mempool = _fixtureMempools fixture ^?! atChain chain0 - mempoolInsertPact5 mempool CheckedInsert [badUnique, badFuture, badPast, badTxHash] - - let badTxHashes = - [ pact4Hash badParse - , _cmdHash badUnique - , _cmdHash badFuture - , _cmdHash badPast - , _cmdHash badTxHash - , pact4Hash badSigs - ] - inMempool <- mempoolLookupPact5 mempool (Vector.fromList badTxHashes) - forM_ (zip [0 :: Word ..] badTxHashes) $ \(i, badHash) -> do - assertBool ("bad tx [index = " <> sshow i <> ", hash = " <> sshow badTxHash <> "] should have been evicted from the mempool") $ not $ HashSet.member badHash inMempool - - return () - -lookupPactTxsSpec :: RocksDb -> IO () -lookupPactTxsSpec baseRdb = runResourceT $ do - fixture <- mkFixture baseRdb - liftIO $ do - cmd1 <- buildCwCmd v (transferCmd 1.0) - cmd2 <- buildCwCmd v (transferCmd 2.0) - - -- Depth 0 - _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2] - bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - return $ finalizeBlock bip - - let rks = List.sort $ List.map (Pact5.unHash . _cmdHash) [cmd1, cmd2] - - let lookupExpect :: Maybe Word -> IO () - lookupExpect depth = do - txs <- lookupPactTxs (fmap (ConfirmationDepth . fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) - assertEqual ("all txs should be available with depth=" ++ show depth) (HashSet.fromList rks) (HashMap.keysSet txs) - let lookupDontExpect :: Maybe Word -> IO () - lookupDontExpect depth = do - txs <- lookupPactTxs (fmap (ConfirmationDepth. fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) - assertEqual ("no txs should be available with depth=" ++ show depth) HashSet.empty (HashMap.keysSet txs) - - lookupExpect Nothing - lookupExpect (Just 0) - lookupDontExpect (Just 1) - - -- Depth 1 - _ <- advanceAllChains fixture $ onChains [] - - lookupExpect Nothing - lookupExpect (Just 0) - lookupExpect (Just 1) - lookupDontExpect (Just 2) - - -- Depth 2 - _ <- advanceAllChains fixture $ onChains [] - - lookupExpect Nothing - lookupExpect (Just 0) - lookupExpect (Just 1) - lookupExpect (Just 2) - lookupDontExpect (Just 3) - -failedTxsShouldGoIntoBlocks :: RocksDb -> IO () -failedTxsShouldGoIntoBlocks baseRdb = runResourceT $ do - fixture <- mkFixture baseRdb - - liftIO $ do - cmd1 <- buildCwCmd v (transferCmd 1.0) - cmd2 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (module mod G (defcap G () true) (defun f () true)) (describe-module \"free.mod\")" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.1 - , _cbGasLimit = GasLimit (Gas 1000) - } - - -- Depth 0 - _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2] - bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - let block = finalizeBlock bip - assertEqual "block has 2 txs even though one of them failed" 2 (Vector.length $ _payloadWithOutputsTransactions block) - return block - - return () - -modulesWithHigherLevelTransitiveDependenciesSimple :: RocksDb -> IO () -modulesWithHigherLevelTransitiveDependenciesSimple baseRdb = runResourceT $ do - fixture <- mkFixture baseRdb - liftIO $ do - cmd1 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.9 - , _cbGasLimit = GasLimit (Gas 1000) - } - cmd2 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (module foo g (defcap g () true) (defun calls-foo () barbar.FOO_CONST))" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.8 - , _cbGasLimit = GasLimit (Gas 1000) - } - cmd3 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (foo.calls-foo)))" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.7 - , _cbGasLimit = GasLimit (Gas 1000) - } - cmd4 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.6 - , _cbGasLimit = GasLimit (Gas 1000) - } - cmd5 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.5 - , _cbGasLimit = GasLimit (Gas 1000) - } - - results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] - bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - let block = finalizeBlock bip - return block - - results & - P.alignExact ? onChain chain0 ? - P.alignExact ? Vector.replicate 5 successfulTx - - results & - P.alignExact ? onChain chain0 ? - P.alignExact ? Vector.fromList - [ P.fun _crGas ? P.equals (Gas 173) - , P.fun _crGas ? P.equals (Gas 305) - , P.fun _crGas ? P.equals (Gas 348) - , P.fun _crGas ? P.equals (Gas 389) - , P.fun _crGas ? P.equals (Gas 81) - ] - - return () - -modulesWithHigherLevelTransitiveDependenciesComplex :: RocksDb -> IO () -modulesWithHigherLevelTransitiveDependenciesComplex baseRdb = runResourceT $ do - fixture <- mkFixture baseRdb - liftIO $ do - cmd1 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.9 - , _cbGasLimit = GasLimit (Gas 1000) - } - cmd2 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' $ T.unlines - [ "(namespace 'free)" - , "(module foo g" - , " (defcap g () true)" - , " (defun calls-foo (sender:string amount:integer)" - , " (with-capability (FOO_MANAGED sender amount)" - , " (with-capability (FOO_CAP)" - , " barbar.FOO_CONST" - , " )" - , " )" - , " )" - , " (defcap FOO_CAP () true)" - , "" - , " (defun foo-mgr (a:integer b:integer) (+ a b))" - , "" - , " (defcap FOO_MANAGED (sender:string a:integer)" - , " @managed a foo-mgr" - , " true" - , " )" - , " )" - ] - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.8 - , _cbGasLimit = GasLimit (Gas 1000) - } - cmd3 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (install-capability (foo.FOO_MANAGED \"bob\" 100)) (foo.calls-foo \"bob\" 100)))" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.7 - , _cbGasLimit = GasLimit (Gas 1000) - } - cmd4 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.6 - , _cbGasLimit = GasLimit (Gas 1000) - } - cmd5 <- buildCwCmd v (defaultCmd chain0) - { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" - -- for ordering the transactions as they appear in the block - , _cbGasPrice = GasPrice 0.5 - , _cbGasLimit = GasLimit (Gas 1000) - } - - results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do - mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] - bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - let block = finalizeBlock bip - return block - - results & - P.alignExact ? onChain chain0 ? - P.alignExact ? Vector.replicate 5 successfulTx - - results & - P.alignExact ? onChain chain0 ? - P.alignExact ? Vector.fromList - [ P.fun _crGas ? P.equals (Gas 173) - , P.fun _crGas ? P.equals (Gas 715) - , P.fun _crGas ? P.equals (Gas 648) - , P.fun _crGas ? P.equals (Gas 618) - , P.fun _crGas ? P.equals (Gas 82) - ] - - return () - -{- -tests = do - -- * test that ValidateBlock does a destructive rewind to the parent of the block being validated - -- * test ValidateBlock's behavior if its parent doesn't exist in the chain database - - do - -- * test that read-only replay gives results that agree with running the block - blocks <- doBlocks (replicate 10 [tx1, tx2]) - - -- * test that read-only replay fails with the block missing - - - -- * test that PreInsertCheck does a Pact 5 check after the fork and Pact 4 check before the fork - -- - -- * test that the mempool only gives valid transactions - -- * test that blocks fit the block gas limit always - -- * test that blocks can include txs even if their gas limits together exceed that block gas limit - -- pact5 upgrade tests: - -- * test that a defpact can straddle the pact5 upgrade - -- * test that pact5 can load pact4 modules - -- * test that rewinding past the pact5 boundary is possible --} + [] + -- [ testCase "simple end to end" (simpleEndToEnd baseRdb) + -- , testCase "continue block spec" (continueBlockSpec baseRdb) + -- , testCase "new block empty" (newBlockEmpty baseRdb) + -- , testCase "new block timeout spec" (newBlockTimeoutSpec baseRdb) + -- , testCase "new block excludes invalid transactions" (testNewBlockExcludesInvalid baseRdb) + -- , testCase "lookup pact txs spec" (lookupPactTxsSpec baseRdb) + -- , testCase "failed txs should go into blocks" (failedTxsShouldGoIntoBlocks baseRdb) + -- , testCase "modules with higher level transitive dependencies (simple)" (modulesWithHigherLevelTransitiveDependenciesSimple baseRdb) + -- , testCase "modules with higher level transitive dependencies (complex)" (modulesWithHigherLevelTransitiveDependenciesComplex baseRdb) + -- ] + +-- simpleEndToEnd :: RocksDb -> IO () +-- simpleEndToEnd baseRdb = runResourceT $ do +-- fixture <- mkFixture baseRdb +-- liftIO $ do +-- cmd1 <- buildCwCmd v (transferCmd 1.0) +-- cmd2 <- buildCwCmd v (transferCmd 2.0) + +-- results <- advanceAllChainsWithTxs fixture $ onChain chain0 [cmd1, cmd2] + +-- -- we only care that they succeed; specifics regarding their outputs are in TransactionExecTest +-- results & +-- P.alignExact ? onChain chain0 ? +-- P.alignExact ? Vector.replicate 2 successfulTx + +-- newBlockEmpty :: RocksDb -> IO () +-- newBlockEmpty baseRdb = runResourceT $ do +-- fixture <- mkFixture baseRdb +-- liftIO $ do +-- cmd <- buildCwCmd v (transferCmd 1.0) +-- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolInsertPact5 mempool CheckedInsert [cmd] +-- -- -- Test that NewBlockEmpty ignores the mempool +-- emptyBip <- throwIfNotPact5 =<< throwIfNoHistory =<< +-- newBlock noMiner NewBlockEmpty (ParentHeader ph) pactQueue +-- let emptyPwo = finalizeBlock emptyBip +-- assertEqual "empty block has no transactions" 0 (Vector.length $ _payloadWithOutputsTransactions emptyPwo) +-- return emptyPwo + +-- results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue _ -> do +-- nonEmptyBip <- throwIfNotPact5 =<< throwIfNoHistory =<< +-- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- return $ finalizeBlock nonEmptyBip + +-- results & +-- P.alignExact ? onChain chain0 ? +-- P.alignExact ? Vector.replicate 1 successfulTx + +-- continueBlockSpec :: RocksDb -> IO () +-- continueBlockSpec baseRdb = runResourceT $ do +-- fixture <- mkFixture baseRdb +-- liftIO $ do +-- startCut <- getCut fixture + +-- -- construct some transactions that we plan to put into the block +-- cmd1 <- buildCwCmd v (transferCmd 1.0) +-- cmd2 <- buildCwCmd v (transferCmd 2.0) +-- cmd3 <- buildCwCmd v (transferCmd 3.0) + +-- allAtOnceResults <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- -- insert all transactions +-- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3] +-- -- construct a new block with all of said transactions +-- bipAllAtOnce <- throwIfNotPact5 =<< throwIfNoHistory =<< +-- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- return $ finalizeBlock bipAllAtOnce +-- -- assert that 3 successful txs are in the block +-- allAtOnceResults & +-- P.alignExact ? onChain chain0 ? +-- P.alignExact ? Vector.replicate 3 successfulTx + +-- -- reset back to the empty block for the next phase +-- -- next, produce the same block by repeatedly extending a block +-- -- with the same transactions as were included in the original. +-- -- note that this will reinsert all txs in the full block into the +-- -- mempool, so we need to clear it after, or else the block will +-- -- contain all of the transactions before we extend it. +-- revert fixture startCut +-- continuedResults <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolClear mempool +-- mempoolInsertPact5 mempool CheckedInsert [cmd3] +-- bipStart <- throwIfNotPact5 =<< throwIfNoHistory =<< +-- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue + +-- mempoolInsertPact5 mempool CheckedInsert [cmd2] +-- bipContinued <- throwIfNoHistory =<< continueBlock bipStart pactQueue + +-- mempoolInsertPact5 mempool CheckedInsert [cmd1] +-- bipFinal <- throwIfNoHistory =<< continueBlock bipContinued pactQueue + +-- -- We must make progress on the same parent header +-- assertEqual "same parent header after continuing block" +-- (_blockInProgressParentHeader bipStart) +-- (_blockInProgressParentHeader bipContinued) +-- assertBool "made progress (1)" +-- (bipStart /= bipContinued) +-- assertEqual "same parent header after finishing block" +-- (_blockInProgressParentHeader bipContinued) +-- (_blockInProgressParentHeader bipFinal) +-- assertBool "made progress (2)" +-- (bipContinued /= bipFinal) + +-- return $ finalizeBlock bipFinal + +-- -- assert that the continued results are equal to doing it all at once +-- continuedResults & P.equals allAtOnceResults + +-- -- * test that the NewBlock timeout works properly and doesn't leave any extra state from a timed-out transaction +-- newBlockTimeoutSpec :: RocksDb -> IO () +-- newBlockTimeoutSpec baseRdb = runResourceT $ do +-- let pactServiceConfig = testPactServiceConfig +-- { _pactTxTimeLimit = Just (Micros 35_000) +-- -- this may need to be tweaked for CI. +-- -- it should be long enough that `timeoutTx` times out +-- -- but neither `tx1` nor `tx2` time out. +-- } +-- fixture <- mkFixtureWith pactServiceConfig baseRdb + +-- liftIO $ do +-- tx1 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "1" +-- , _cbGasPrice = GasPrice 1.0 +-- , _cbGasLimit = GasLimit (Gas 400) +-- } +-- tx2 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "2" +-- , _cbGasPrice = GasPrice 2.0 +-- , _cbGasLimit = GasLimit (Gas 400) +-- } +-- timeoutTx <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' $ "(fold + 0 (enumerate 1 500000))" +-- , _cbGasPrice = GasPrice 1.5 +-- , _cbGasLimit = GasLimit (Gas 5000) +-- } + +-- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolInsertPact5 mempool CheckedInsert [tx2, timeoutTx, tx1] +-- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< +-- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- -- Mempool orders by GasPrice. 'buildCwCmd' sets the gas price to the transfer amount. +-- -- We hope for 'timeoutTx' to fail, meaning that only 'txTransfer2' is in the block. +-- bip & P.fun _blockInProgressTransactions ? P.fun _transactionPairs +-- ? P.alignExact ? Vector.fromList +-- [ P.pair +-- (P.fun _cmdHash ? P.equals (_cmdHash tx2)) +-- successfulTx +-- ] +-- return $ finalizeBlock bip + +-- pure () + +-- testNewBlockExcludesInvalid :: RocksDb -> IO () +-- testNewBlockExcludesInvalid baseRdb = runResourceT $ do +-- fixture <- mkFixture baseRdb +-- liftIO $ do +-- -- The mempool should reject a tx that doesn't parse as valid pact. +-- badParse <- buildCwCmdNoParse v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(not a valid pact tx" +-- } + +-- regularTx1 <- buildCwCmd v $ transferCmd 1.0 +-- -- The mempool checks that a tx does not already exist in the chain before adding it. +-- let badUnique = regularTx1 + +-- -- The mempool checks that a tx does not have a creation time too far into the future. +-- badFuture <- buildCwCmd v $ (transferCmd 1.0) +-- { _cbCreationTime = Just $ TxCreationTime (2 ^ (32 :: Word)) +-- } + +-- -- The mempool checks that a tx does not have a creation time too far into the past. +-- badPast <- buildCwCmd v $ (transferCmd 1.0) +-- { _cbCreationTime = Just $ TxCreationTime 0 +-- } + +-- regularTx2 <- buildCwCmd v $ transferCmd 1.0 +-- -- The mempool checks that a tx has a valid hash. +-- let badTxHash = regularTx2 +-- { _cmdHash = Pact5.Hash "bad hash" +-- } + +-- badSigs <- buildCwCmdNoParse v (defaultCmd chain0) +-- { _cbSigners = +-- [ CmdSigner +-- { _csSigner = Signer +-- { _siScheme = Nothing +-- , _siPubKey = fst sender00 +-- , _siAddress = Nothing +-- , _siCapList = [] +-- } +-- , _csPrivKey = snd sender01 +-- } +-- ] +-- } + +-- badChain <- buildCwCmd v $ transferCmd 1.0 & set cbChainId (chainIdToText $ unsafeChainId 1) + +-- let pact4Hash = Pact5.Hash . Pact4.unHash . Pact4.toUntypedHash . Pact4._cmdHash +-- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolInsertPact5 mempool CheckedInsert [regularTx1] +-- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- return $ finalizeBlock bip + +-- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolInsert mempool UncheckedInsert $ Vector.fromList [badParse, badSigs] +-- mempoolInsertPact5 mempool UncheckedInsert [badChain, badUnique, badFuture, badPast, badTxHash] +-- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- let expectedTxs = [] +-- let actualTxs = Vector.toList $ Vector.map (unRequestKey . _crReqKey . snd) $ _transactionPairs $ _blockInProgressTransactions bip +-- assertEqual "block has excluded all invalid transactions" expectedTxs actualTxs +-- return $ finalizeBlock bip + +-- -- we need to wait until this above block is validate for `badUnique` +-- -- to disappear, because only the parent block is used to find txs to +-- -- delete from the mempool +-- let mempool = _fixtureMempools fixture ^?! atChain chain0 +-- mempoolInsertPact5 mempool CheckedInsert [badUnique, badFuture, badPast, badTxHash] + +-- let badTxHashes = +-- [ pact4Hash badParse +-- , _cmdHash badUnique +-- , _cmdHash badFuture +-- , _cmdHash badPast +-- , _cmdHash badTxHash +-- , pact4Hash badSigs +-- ] +-- inMempool <- mempoolLookupPact5 mempool (Vector.fromList badTxHashes) +-- forM_ (zip [0 :: Word ..] badTxHashes) $ \(i, badHash) -> do +-- assertBool ("bad tx [index = " <> sshow i <> ", hash = " <> sshow badTxHash <> "] should have been evicted from the mempool") $ not $ HashSet.member badHash inMempool + +-- return () + +-- lookupPactTxsSpec :: RocksDb -> IO () +-- lookupPactTxsSpec baseRdb = runResourceT $ do +-- fixture <- mkFixture baseRdb +-- liftIO $ do +-- cmd1 <- buildCwCmd v (transferCmd 1.0) +-- cmd2 <- buildCwCmd v (transferCmd 2.0) + +-- -- Depth 0 +-- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2] +-- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- return $ finalizeBlock bip + +-- let rks = List.sort $ List.map (Pact5.unHash . _cmdHash) [cmd1, cmd2] + +-- let lookupExpect :: Maybe Word -> IO () +-- lookupExpect depth = do +-- txs <- lookupPactTxs (fmap (ConfirmationDepth . fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) +-- assertEqual ("all txs should be available with depth=" ++ show depth) (HashSet.fromList rks) (HashMap.keysSet txs) +-- let lookupDontExpect :: Maybe Word -> IO () +-- lookupDontExpect depth = do +-- txs <- lookupPactTxs (fmap (ConfirmationDepth. fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) +-- assertEqual ("no txs should be available with depth=" ++ show depth) HashSet.empty (HashMap.keysSet txs) + +-- lookupExpect Nothing +-- lookupExpect (Just 0) +-- lookupDontExpect (Just 1) + +-- -- Depth 1 +-- _ <- advanceAllChains fixture $ onChains [] + +-- lookupExpect Nothing +-- lookupExpect (Just 0) +-- lookupExpect (Just 1) +-- lookupDontExpect (Just 2) + +-- -- Depth 2 +-- _ <- advanceAllChains fixture $ onChains [] + +-- lookupExpect Nothing +-- lookupExpect (Just 0) +-- lookupExpect (Just 1) +-- lookupExpect (Just 2) +-- lookupDontExpect (Just 3) + +-- failedTxsShouldGoIntoBlocks :: RocksDb -> IO () +-- failedTxsShouldGoIntoBlocks baseRdb = runResourceT $ do +-- fixture <- mkFixture baseRdb + +-- liftIO $ do +-- cmd1 <- buildCwCmd v (transferCmd 1.0) +-- cmd2 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (module mod G (defcap G () true) (defun f () true)) (describe-module \"free.mod\")" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.1 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } + +-- -- Depth 0 +-- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2] +-- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- let block = finalizeBlock bip +-- assertEqual "block has 2 txs even though one of them failed" 2 (Vector.length $ _payloadWithOutputsTransactions block) +-- return block + +-- return () + +-- modulesWithHigherLevelTransitiveDependenciesSimple :: RocksDb -> IO () +-- modulesWithHigherLevelTransitiveDependenciesSimple baseRdb = runResourceT $ do +-- fixture <- mkFixture baseRdb +-- liftIO $ do +-- cmd1 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.9 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- cmd2 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (module foo g (defcap g () true) (defun calls-foo () barbar.FOO_CONST))" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.8 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- cmd3 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (foo.calls-foo)))" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.7 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- cmd4 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.6 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- cmd5 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.5 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } + +-- results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] +-- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- let block = finalizeBlock bip +-- return block + +-- results & +-- P.alignExact ? onChain chain0 ? +-- P.alignExact ? Vector.replicate 5 successfulTx + +-- results & +-- P.alignExact ? onChain chain0 ? +-- P.alignExact ? Vector.fromList +-- [ P.fun _crGas ? P.equals (Gas 173) +-- , P.fun _crGas ? P.equals (Gas 305) +-- , P.fun _crGas ? P.equals (Gas 348) +-- , P.fun _crGas ? P.equals (Gas 389) +-- , P.fun _crGas ? P.equals (Gas 81) +-- ] + +-- return () + +-- modulesWithHigherLevelTransitiveDependenciesComplex :: RocksDb -> IO () +-- modulesWithHigherLevelTransitiveDependenciesComplex baseRdb = runResourceT $ do +-- fixture <- mkFixture baseRdb +-- liftIO $ do +-- cmd1 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.9 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- cmd2 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' $ T.unlines +-- [ "(namespace 'free)" +-- , "(module foo g" +-- , " (defcap g () true)" +-- , " (defun calls-foo (sender:string amount:integer)" +-- , " (with-capability (FOO_MANAGED sender amount)" +-- , " (with-capability (FOO_CAP)" +-- , " barbar.FOO_CONST" +-- , " )" +-- , " )" +-- , " )" +-- , " (defcap FOO_CAP () true)" +-- , "" +-- , " (defun foo-mgr (a:integer b:integer) (+ a b))" +-- , "" +-- , " (defcap FOO_MANAGED (sender:string a:integer)" +-- , " @managed a foo-mgr" +-- , " true" +-- , " )" +-- , " )" +-- ] +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.8 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- cmd3 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (install-capability (foo.FOO_MANAGED \"bob\" 100)) (foo.calls-foo \"bob\" 100)))" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.7 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- cmd4 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.6 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } +-- cmd5 <- buildCwCmd v (defaultCmd chain0) +-- { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" +-- -- for ordering the transactions as they appear in the block +-- , _cbGasPrice = GasPrice 0.5 +-- , _cbGasLimit = GasLimit (Gas 1000) +-- } + +-- results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do +-- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] +-- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- let block = finalizeBlock bip +-- return block + +-- results & +-- P.alignExact ? onChain chain0 ? +-- P.alignExact ? Vector.replicate 5 successfulTx + +-- results & +-- P.alignExact ? onChain chain0 ? +-- P.alignExact ? Vector.fromList +-- [ P.fun _crGas ? P.equals (Gas 173) +-- , P.fun _crGas ? P.equals (Gas 715) +-- , P.fun _crGas ? P.equals (Gas 648) +-- , P.fun _crGas ? P.equals (Gas 618) +-- , P.fun _crGas ? P.equals (Gas 82) +-- ] + +-- return () + +-- {- +-- tests = do +-- -- * test that ValidateBlock does a destructive rewind to the parent of the block being validated +-- -- * test ValidateBlock's behavior if its parent doesn't exist in the chain database + +-- do +-- -- * test that read-only replay gives results that agree with running the block +-- blocks <- doBlocks (replicate 10 [tx1, tx2]) + +-- -- * test that read-only replay fails with the block missing + + +-- -- * test that PreInsertCheck does a Pact 5 check after the fork and Pact 4 check before the fork +-- -- +-- -- * test that the mempool only gives valid transactions +-- -- * test that blocks fit the block gas limit always +-- -- * test that blocks can include txs even if their gas limits together exceed that block gas limit +-- -- pact5 upgrade tests: +-- -- * test that a defpact can straddle the pact5 upgrade +-- -- * test that pact5 can load pact4 modules +-- -- * test that rewinding past the pact5 boundary is possible +-- -} chain0 :: ChainId chain0 = unsafeChainId 0 -v :: ChainwebVersion -v = pact5InstantCpmTestVersion singletonChainGraph - -advanceAllChainsWithTxs :: Fixture -> ChainMap [Pact5.Transaction] -> IO (ChainMap (Vector TestPact5CommandResult)) -advanceAllChainsWithTxs fixture txsPerChain = - advanceAllChains fixture $ - txsPerChain <&> \txs ph pactQueue mempool -> do - mempoolClear mempool - mempoolInsertPact5 mempool CheckedInsert txs - nb <- throwIfNotPact5 =<< throwIfNoHistory =<< - newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - return $ finalizeBlock nb +-- advanceAllChainsWithTxs :: Fixture -> ChainMap [Pact5.Transaction] -> IO (ChainMap (Vector TestPact5CommandResult)) +-- advanceAllChainsWithTxs fixture txsPerChain = +-- advanceAllChains fixture $ +-- txsPerChain <&> \txs ph pactQueue mempool -> do +-- mempoolClear mempool +-- mempoolInsertPact5 mempool CheckedInsert txs +-- nb <- throwIfNotPact5 =<< throwIfNoHistory =<< +-- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue +-- return $ finalizeBlock nb -- this mines a block on *all chains*. if you don't specify a payload on a chain, -- it adds empty blocks! advanceAllChains :: () => Fixture - -> ChainMap (BlockHeader -> PactQueue -> MempoolBackend Pact4.UnparsedTransaction -> IO PayloadWithOutputs) + -> ChainMap (BlockHeader -> ServiceEnv RocksDbTable -> MempoolBackend Pact.Transaction -> IO PayloadWithOutputs) -> IO (ChainMap (Vector TestPact5CommandResult)) advanceAllChains Fixture{..} blocks = do commandResults <- forConcurrently (HashSet.toList (chainIds v)) $ \c -> do ph <- getParentTestBlockDb _fixtureBlockDb c creationTime <- getCurrentTimeIntegral - let pactQueue = _fixturePactQueues ^?! atChain c + let serviceEnv = _fixturePacts ^?! atChain c let mempool = _fixtureMempools ^?! atChain c let makeEmptyBlock p _ _ = do bip <- throwIfNotPact5 =<< throwIfNoHistory =<< @@ -650,13 +657,6 @@ revert Fixture{..} c = do ph <- getParentTestBlockDb _fixtureBlockDb chain pactSyncToBlock ph (_fixturePactQueues ^?! atChain chain) -throwIfNotPact5 :: ForSomePactVersion f -> IO (f Pact5) -throwIfNotPact5 h = case h of - ForSomePactVersion Pact4T _ -> do - assertFailure "throwIfNotPact5: should be pact5" - ForSomePactVersion Pact5T a -> do - pure a - transferCmd :: Decimal -> CmdBuilder transferCmd transferAmount = (defaultCmd chain0) { _cbRPC = mkExec' $ diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index 641cbd2fb4..7f592e474b 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -121,7 +121,6 @@ import Chainweb.Test.TestVersions import Chainweb.Test.Utils import Chainweb.Utils import Chainweb.Version -import Chainweb.WebPactExecutionService import Chainweb.Version.Mainnet (mainnet) diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index 4805268839..44eefbd19e 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -17,29 +17,25 @@ module Chainweb.Test.Pact.TransactionExecTest (tests) where import Chainweb.BlockHeader -import Chainweb.Graph (singletonChainGraph, petersonChainGraph) +import Chainweb.Graph (petersonChainGraph) import Chainweb.Miner.Pact (Miner(..), MinerId(..), MinerGuard(..), noMiner) import Chainweb.Pact.PactService (initialPayloadState, withPactService) import Chainweb.Pact.PactService.Checkpointer (readFrom, mkFakeParentCreationTime) import Chainweb.Pact.Types import Chainweb.Pact.Transaction import Chainweb.Pact.TransactionExec -import Chainweb.Pact.Types import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb, _bdbWebBlockHeaderDb), mkTestBlockDb) +import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb), mkTestBlockDb) import Chainweb.Test.Pact.CmdBuilder import Chainweb.Test.TestVersions import Chainweb.Test.Utils -import Chainweb.Utils (T2(..)) import Chainweb.Version -import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) import Control.Lens hiding (only) import Control.Monad.Except import Control.Monad.IO.Class import Control.Monad.Reader import Control.Monad.Trans.Resource import Data.Decimal -import Data.Functor.Product import Data.HashMap.Strict qualified as HashMap import Data.IORef import Data.Maybe (fromMaybe) @@ -93,7 +89,6 @@ tests baseRdb = testGroup "Pact5 TransactionExecTest" , testCase "applyCmd failure spec" (applyCmdFailureSpec baseRdb) , testCase "applyCmd coin.transfer" (applyCmdCoinTransfer baseRdb) , testCase "applyCoinbase spec" (applyCoinbaseSpec baseRdb) - -- , testCase "test coin upgrade" (testCoinUpgrade baseRdb) , testCase "test local only fails outside of local" (testLocalOnlyFailsOutsideOfLocal baseRdb) , testCase "payload failure all gas should go to the miner - type error" (payloadFailureShouldPayAllGasToTheMinerTypeError baseRdb) , testCase "payload failure all gas should go to the miner - insufficient funds" (payloadFailureShouldPayAllGasToTheMinerInsufficientFunds baseRdb) @@ -101,7 +96,6 @@ tests baseRdb = testGroup "Pact5 TransactionExecTest" , testCase "writes from failed transaction should not make it into the db" (testWritesFromFailedTxDontMakeItIn baseRdb) , testCase "quirk spec" (quirkSpec baseRdb) , testCase "test writes to nonexistent tables" (testWritesToNonExistentTables baseRdb) - -- , testCase "test CommandResult 5 is valid for 4" (testCommandResult5To4 baseRdb) , testCase "test hash-keccak256" (testKeccak256 baseRdb) ] @@ -114,17 +108,17 @@ readFromAfterGenesis :: ChainwebVersion -> RocksDb -> (BlockEnv -> BlockHandle - readFromAfterGenesis ver rdb act = runResourceT $ do sql <- withTempSQLiteResource tdb <- mkTestBlockDb ver rdb + -- fake ro-sql pool, assuming we're using this single-threaded + roSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sql) (\_ -> return ()) 10 10) + logger <- liftIO $ testLogger + serviceEnv <- withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (testPactServiceConfig PIN0.payloadBlock) liftIO $ do - -- fake ro-sql pool, assuming we're using this single-threaded - roSqlPool <- Pool.newPool (Pool.defaultPoolConfig (return sql) (\_ -> return ()) 10 10) - logger <- testLogger - withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (testPactServiceConfig PIN0.payloadBlock) $ \serviceEnv -> do - initialPayloadState logger serviceEnv - fakeParentCreationTime <- mkFakeParentCreationTime - throwIfNoHistory =<< - readFrom logger ver cid sql fakeParentCreationTime - (Parent (gh ver cid ^. rankedBlockHash)) - act + initialPayloadState logger serviceEnv + fakeParentCreationTime <- mkFakeParentCreationTime + throwIfNoHistory =<< + readFrom logger ver cid sql fakeParentCreationTime + (Parent (gh ver cid ^. rankedBlockHash)) + act buyGasShouldTakeGasTokensFromTheTransactionSender :: RocksDb -> IO () buyGasShouldTakeGasTokensFromTheTransactionSender rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> @@ -899,26 +893,6 @@ testKeccak256 rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do , P.fun _crGas ? P.equals ? Gas 75 ] -testCommandResult5To4 :: RocksDb -> IO () -testCommandResult5To4 rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do - flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do - cmd <- buildCwCmd v - $ set cbRPC (mkExec' "(+ 1 'hello)") - $ defaultCmd cid - - logger <- testLogger - applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) - >>= P.checkAll - [ P.match _Right - ? P.fun _crResult - ? P.match _PactResultErr - ? P.succeed - , P.match _Right - ? P.fun (fmap pactErrorToOnChainError) - ? P.fun hashPactTxLogs - ? P.forced - ] - cid :: ChainId cid = unsafeChainId 0 diff --git a/test/unit/Chainweb/Test/Pact/TransactionTests.hs b/test/unit/Chainweb/Test/Pact/TransactionTests.hs index 572bc62109..0693d59765 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionTests.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionTests.hs @@ -21,10 +21,12 @@ import Pact.Core.Repl import Pact.Core.Repl.Utils import Control.Monad (when) import Data.Text qualified as Text -import Pact.Types.KeySet qualified as Pact import Test.Tasty import Test.Tasty.HUnit import Data.Map.Strict qualified as Map +import Pact.Core.Guards +import qualified Data.Set as Set +import Pact.Core.Names -- ---------------------------------------------------------------------- -- -- Global settings @@ -85,7 +87,7 @@ injectionTest = do badMinerId :: MinerId badMinerId = MinerId "alpha\" (read-keyset \"miner-keyset\") 9999999.99)(coin.coinbase \"alpha" -minerKeys0 :: MinerKeys -minerKeys0 = MinerKeys $ Pact4.mkKeySet - ["f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27"] - "default" +minerKeys0 :: MinerGuard +minerKeys0 = MinerGuard $ GKeyset $ KeySet + (Set.singleton (PublicKeyText "f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27")) + (CustomPredicate (TBN $ BareName "default")) From 00e082c8db1c3f2c0e72bb2e89cd57876c5c2031 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 15 Apr 2025 16:54:13 -0400 Subject: [PATCH 107/378] Delete freeze --- cabal.project.freeze | 404 ------------------------------------------- 1 file changed, 404 deletions(-) delete mode 100644 cabal.project.freeze diff --git a/cabal.project.freeze b/cabal.project.freeze deleted file mode 100644 index 7e0ab8db67..0000000000 --- a/cabal.project.freeze +++ /dev/null @@ -1,404 +0,0 @@ -active-repositories: hackage.haskell.org:merge -constraints: any.Cabal ==3.12.1.0, - any.Cabal-syntax ==3.12.1.0, - any.Decimal ==0.5.2, - any.Diff ==1.0.2, - any.Glob ==0.10.2, - any.HUnit ==1.6.2.0, - any.JuicyPixels ==3.3.9, - any.OneTuple ==0.4.2, - any.Only ==0.1, - any.QuickCheck ==2.15.0.1, - any.RSA ==2.4.1, - any.SHA ==1.6.4.4, - any.StateVar ==1.2.2, - any.adjunctions ==4.4.2, - any.aeson ==2.2.3.0, - any.aeson-pretty ==0.8.10, - any.alex ==3.5.2.0, - any.ansi-terminal ==1.1.2, - any.ansi-terminal-types ==1.1, - any.ap-normalize ==0.1.0.1, - any.appar ==0.1.8, - any.array ==0.5.6.0, - any.asn1-encoding ==0.9.6, - any.asn1-parse ==0.9.5, - any.asn1-types ==0.3.4, - any.assoc ==1.1.1, - any.async ==2.2.5, - any.atomic-primops ==0.8.8, - any.attoparsec ==0.14.4, - any.authenticate-oauth ==1.7, - any.auto-update ==0.2.6, - any.barbies ==2.1.1.0, - any.base ==4.19.1.0, - any.base-compat ==0.14.1, - any.base-compat-batteries ==0.14.1, - any.base-orphans ==0.9.3, - any.base-unicode-symbols ==0.2.4.2, - any.base16-bytestring ==1.0.2.0, - any.base64-bytestring ==1.2.1.0, - any.base64-bytestring-kadena ==0.1, - any.basement ==0.0.16, - any.bifunctors ==5.6.2, - any.binary ==0.8.9.1, - any.binary-orphans ==1.0.5, - any.bitvec ==1.1.5.0, - any.blaze-builder ==0.4.2.3, - any.blaze-html ==0.9.2.0, - any.blaze-markup ==0.8.3.0, - any.boring ==0.2.2, - any.bound ==2.0.7, - any.bsb-http-chunked ==0.0.0.4, - any.bytebuild ==0.3.16.3, - any.byteorder ==1.0.4, - any.bytes ==0.17.4, - any.byteslice ==0.2.14.0, - any.bytesmith ==0.3.11.1, - any.bytestring ==0.12.1.0, - any.bytestring-builder ==0.10.8.2.0, - any.cache ==0.1.3.0, - any.call-stack ==0.4.0, - any.case-insensitive ==1.2.1.0, - any.cassava ==0.5.3.2, - any.cborg ==0.2.10.0, - any.cereal ==0.5.8.3, - chainweb -debug -ed25519 -ghc-flags, - chainweb-node -debug -ed25519 -ghc-flags, - any.character-ps ==0.1, - any.charset ==0.3.11, - any.chronos ==1.1.6.2, - any.citeproc ==0.8.1.2, - any.clock ==0.8.4, - any.co-log-core ==0.3.2.4, - any.code-page ==0.2.1, - any.colour ==2.3.6, - any.commonmark ==0.2.6.1, - any.commonmark-extensions ==0.2.5.6, - any.commonmark-pandoc ==0.2.2.3, - any.comonad ==5.0.9, - any.concurrent-output ==1.10.21, - any.conduit ==1.3.6, - any.conduit-extra ==1.3.6, - any.configuration-tools ==0.7.0, - any.constraints ==0.14.2, - any.containers ==0.6.8, - any.contiguous ==0.6.4.2, - any.contravariant ==1.5.5, - any.cookie ==0.5.0, - any.criterion ==1.6.4.0, - any.criterion-measurement ==0.2.3.0, - any.crypto-api ==0.13.3, - any.crypto-pubkey-types ==0.4.3, - any.crypto-token ==0.1.2, - any.cryptohash-md5 ==0.11.101.0, - any.cryptohash-sha1 ==0.11.101.0, - any.crypton ==1.0.1, - any.crypton-connection ==0.4.3, - any.crypton-x509 ==1.7.7, - any.crypton-x509-store ==1.6.9, - any.crypton-x509-system ==1.6.7, - any.crypton-x509-validation ==1.6.13, - any.cryptonite ==0.30, - any.cuckoo ==0.3.1, - cwtools -debug -ed25519 -ghc-flags -remote-db, - any.data-bword ==0.1.0.2, - any.data-default ==0.8.0.0, - any.data-default-class ==0.2.0.0, - any.data-dword ==0.3.2.1, - any.data-fix ==0.3.4, - any.data-ordlist ==0.4.7.0, - any.dec ==0.0.6, - any.deepseq ==1.5.0.0, - any.dense-linear-algebra ==0.1.0.0, - any.deriving-compat ==0.6.7, - any.digest ==0.0.2.1, - any.digraph ==0.3.0, - any.direct-sqlite ==2.3.29, - any.directory ==1.3.8.1, - any.distributive ==0.6.2.1, - any.djot ==0.1.2.2, - any.dlist ==1.0, - any.doclayout ==0.5, - any.doctemplates ==0.11.0.1, - any.easy-file ==0.2.5, - any.emojis ==0.1.4.1, - any.enclosed-exceptions ==1.0.3, - any.entropy ==0.4.1.11, - any.erf ==2.0.0.0, - any.errors ==2.3.0, - any.ethereum ==0.1.0.2, - ethereum -ethhash -openssl-use-pkg-config, - any.exceptions ==0.10.7, - any.extra ==1.8, - any.fast-logger ==3.2.5, - any.file-embed ==0.0.16.0, - any.filepath ==1.4.200.1, - any.fingertree ==0.1.5.0, - any.finite-typelits ==0.2.1.0, - any.free ==5.2, - any.generic-data ==1.1.0.2, - any.generic-lens ==2.2.2.0, - any.generic-lens-core ==2.2.1.0, - any.generically ==0.1.1, - any.ghc-bignum ==1.3, - any.ghc-boot-th ==9.8.2, - any.ghc-compact ==0.1.0.0, - any.ghc-heap ==9.8.2, - any.ghc-prim ==0.11.0, - any.gridtables ==0.1.0.0, - any.groups ==0.5.3, - any.growable-vector ==0.1, - any.haddock-library ==1.11.0, - any.half ==0.3.2, - any.happy ==2.1.5, - any.happy-lib ==2.1.5, - any.hashable ==1.5.0.0, - any.hashes ==0.3.0.1, - any.haskeline ==0.8.2.1, - any.haskell-lexer ==1.1.2, - any.haskell-src-exts ==1.23.1, - any.haskell-src-meta ==0.8.14, - any.heaps ==0.4.1, - any.hedgehog ==1.5, - any.hourglass ==0.2.12, - any.http-api-data ==0.6.1, - any.http-client ==0.7.18, - any.http-client-tls ==0.3.6.4, - any.http-date ==0.0.11, - any.http-media ==0.8.1.1, - any.http-semantics ==0.3.0, - any.http-types ==0.12.4, - any.http2 ==5.3.9, - any.indexed-list-literals ==0.2.1.3, - any.indexed-profunctors ==0.1.1.1, - any.indexed-traversable ==0.1.4, - any.indexed-traversable-instances ==0.1.2, - any.integer-conversion ==0.1.1, - any.integer-gmp ==1.1, - any.integer-logarithms ==1.0.4, - any.invariant ==0.6.4, - any.iproute ==1.7.15, - any.ipynb ==0.2, - any.ixset-typed ==0.5.1.0, - any.jira-wiki-markup ==1.5.1, - any.js-chart ==2.9.4.1, - any.kan-extensions ==5.2.6, - any.lens ==5.3.3, - any.lens-aeson ==1.2.3, - any.libyaml ==0.1.4, - any.libyaml-clib ==0.2.5, - any.lifted-async ==0.10.2.7, - any.lifted-base ==0.2.3.12, - any.loglevel ==0.1.0.0, - any.lrucaching ==0.3.4, - any.lsp ==2.7.0.1, - any.lsp-types ==2.3.0.1, - any.managed ==1.0.10, - any.massiv ==1.0.4.1, - any.math-functions ==0.3.4.4, - any.megaparsec ==9.7.0, - any.memory ==0.18.0, - any.merkle-log ==0.2.0, - any.microlens ==0.4.13.1, - any.microstache ==1.0.3, - any.mime-types ==0.1.2.0, - any.mmorph ==1.2.0, - any.mod ==0.2.0.1, - any.monad-control ==1.0.3.1, - any.mono-traversable ==1.0.21.0, - any.mtl ==2.3.1, - any.mtl-compat ==0.2.2, - any.mwc-probability ==2.3.1, - any.mwc-random ==0.15.1.0, - any.natural-arithmetic ==0.2.1.0, - any.neat-interpolation ==0.5.1.4, - any.network ==3.2.7.0, - any.network-byte-order ==0.1.7, - any.network-control ==0.1.3, - any.network-info ==0.2.1, - any.network-uri ==2.6.4.2, - any.nothunks ==0.3.0.0, - any.old-locale ==1.0.0.7, - any.old-time ==1.1.0.4, - any.optparse-applicative ==0.18.1.0, - any.ordered-containers ==0.2.4, - any.os-string ==2.0.7, - any.pact ==4.13.2, - pact -build-tool +cryptonite-ed25519 -tests-in-lib, - any.pact-json ==0.1.0.0, - any.pact-time ==0.3.0.1, - pact-time -with-time, - any.pact-tng ==5.1, - pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, - any.pandoc ==3.6.2, - any.pandoc-types ==1.23.1, - any.parallel ==3.2.2.0, - any.parsec ==3.1.17.0, - any.parser-combinators ==1.3.0, - any.parsers ==0.12.12, - any.patience ==0.3, - any.pem ==0.2.4, - any.poly ==0.5.1.0, - any.pretty ==1.1.3.6, - any.pretty-show ==1.10, - any.pretty-simple ==4.1.3.0, - any.prettyprinter ==1.7.1, - any.prettyprinter-ansi-terminal ==1.1.3, - any.primitive ==0.9.0.0, - any.primitive-addr ==0.1.0.3, - any.primitive-offset ==0.2.0.1, - any.primitive-unlifted ==2.1.0.0, - any.process ==1.6.18.0, - any.profunctors ==5.6.2, - any.property-matchers ==0.4.0.0, - any.psqueues ==0.2.8.0, - any.pvar ==1.0.0.0, - any.quickcheck-instances ==0.3.32, - any.ralist ==0.4.0.0, - any.random ==1.2.1.3, - any.recover-rtti ==0.5.0, - any.recv ==0.1.0, - any.reducers ==3.12.5, - any.reflection ==2.1.9, - any.regex-base ==0.94.0.2, - any.regex-tdfa ==1.3.2.2, - any.resource-pool ==0.4.0.0, - any.resourcet ==1.3.0, - any.retry ==0.9.3.1, - any.rocksdb-haskell-kadena ==1.1.0, - rocksdb-haskell-kadena -with-tbb, - any.row-types ==1.0.1.2, - any.rts ==1.0.2, - any.run-st ==0.1.3.3, - any.safe ==0.3.21, - any.safe-exceptions ==0.1.7.4, - any.safecopy ==0.10.4.2, - any.scheduler ==2.0.0.1, - any.scientific ==0.3.8.0, - any.semialign ==1.3.1, - any.semigroupoids ==6.0.1, - any.semigroups ==0.20, - any.semirings ==0.7, - any.serialise ==0.2.6.1, - any.servant ==0.20.2, - any.servant-client ==0.20.2, - any.servant-client-core ==0.20.2, - any.servant-server ==0.20.2, - any.sha-validation ==0.1.0.1, - any.show-combinators ==0.2.0.0, - any.simple-sendfile ==0.2.32, - any.singleton-bool ==0.1.8, - any.skylighting ==0.14.6, - any.skylighting-core ==0.14.6, - any.skylighting-format-ansi ==0.1, - any.skylighting-format-blaze-html ==0.1.1.3, - any.skylighting-format-context ==0.1.0.2, - any.skylighting-format-latex ==0.1, - any.skylighting-format-typst ==0.1, - any.socks ==0.6.1, - any.some ==1.0.6, - any.sop-core ==0.5.0.2, - any.sorted-list ==0.2.2.0, - any.split ==0.2.5, - any.splitmix ==0.1.1, - any.statistics ==0.16.2.1, - any.stm ==2.5.2.1, - any.stm-chans ==3.0.0.9, - any.stopwatch ==0.1.0.6, - any.streaming ==0.2.4.0, - any.streaming-commons ==0.2.2.6, - any.strict ==0.5.1, - any.strict-concurrency ==0.2.4.3, - any.syb ==0.7.2.4, - any.tagged ==0.8.9, - any.tagsoup ==0.14.8, - any.tasty ==1.5.3, - any.tasty-golden ==2.3.5, - any.tasty-hedgehog ==1.4.0.2, - any.tasty-hunit ==0.10.2, - any.tasty-json ==0.1.0.0, - any.tasty-quickcheck ==0.11.1, - any.template-haskell ==2.21.0.0, - any.temporary ==1.3, - any.terminal-progress-bar ==0.4.2, - any.terminal-size ==0.3.4, - any.terminfo ==0.4.1.6, - any.texmath ==0.12.8.13, - any.text ==2.1.1, - any.text-conversions ==0.3.1.1, - any.text-iso8601 ==0.1.1, - any.text-rope ==0.3, - any.text-short ==0.1.6, - any.th-abstraction ==0.7.1.0, - any.th-compat ==0.1.6, - any.th-expand-syns ==0.4.12.0, - any.th-lift ==0.8.6, - any.th-lift-instances ==0.1.20, - any.th-orphans ==0.13.16, - any.th-reify-many ==0.1.10, - any.these ==1.2.1, - any.time ==1.12.2, - any.time-compat ==1.9.8, - any.time-locale-compat ==0.1.1.5, - any.time-manager ==0.2.2, - any.tls ==2.1.6, - any.tls-session-manager ==0.0.7, - any.token-bucket ==0.1.0.1, - any.toml-parser ==2.0.1.0, - any.torsor ==0.1.0.1, - any.transformers ==0.6.1.0, - any.transformers-base ==0.4.6, - any.transformers-compat ==0.7.2, - any.trifecta ==2.1.4, - any.tuples ==0.1.0.0, - any.typed-process ==0.2.12.0, - any.typst ==0.6.1, - any.typst-symbols ==0.1.7, - any.unicode-collation ==0.1.3.6, - any.unicode-data ==0.6.0, - any.unicode-transforms ==0.4.0.1, - any.uniplate ==1.6.13, - any.unix ==2.8.4.0, - any.unix-compat ==0.7.3, - any.unix-time ==0.4.16, - any.unlifted ==0.2.2.0, - any.unliftio ==0.2.25.0, - any.unliftio-core ==0.2.1.0, - any.unordered-containers ==0.2.20, - any.utf8-string ==1.0.2, - any.uuid ==1.3.16, - any.uuid-types ==1.0.6, - any.validation ==1.1.3, - any.vault ==0.3.1.5, - any.vector ==0.13.2.0, - any.vector-algorithms ==0.9.0.3, - any.vector-binary-instances ==0.2.5.2, - any.vector-sized ==1.6.1, - any.vector-stream ==0.1.0.1, - any.vector-th-unbox ==0.2.2, - any.void ==0.7.3, - any.wai ==3.2.4, - any.wai-app-static ==3.1.9, - any.wai-cors ==0.2.7, - any.wai-extra ==3.1.17, - any.wai-logger ==2.5.0, - any.wai-middleware-throttle ==0.3.0.1, - any.wai-middleware-validation ==0.1.0.2, - any.warp ==3.4.7, - any.warp-tls ==3.4.12, - any.wherefrom-compat ==0.1.1.1, - any.wide-word ==0.1.6.0, - any.witherable ==0.5, - any.wl-pprint-annotated ==0.1.0.1, - any.word8 ==0.1.3, - any.wreq ==0.5.4.3, - any.xml ==1.3.14, - any.xml-conduit ==1.9.1.4, - any.xml-types ==0.3.8, - any.yaml ==0.11.11.2, - any.yet-another-logger ==0.4.2, - any.zigzag ==0.1.0.0, - any.zip-archive ==0.4.3.2, - any.zlib ==0.7.1.0 -index-state: hackage.haskell.org 2025-02-25T16:56:27Z From 2c974c67011158c832f91d0049138e31291c343b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 18 Apr 2025 21:17:08 +0200 Subject: [PATCH 108/378] SPV arguments --- cabal.project | 2 +- chainweb.cabal | 104 ++- src/Chainweb/Crypto/MerkleLog.hs | 174 ++-- src/Chainweb/MerkleUniverse.hs | 421 ++++++++- src/Chainweb/Miner/Coordinator.hs | 15 +- src/Chainweb/PayloadProvider/EVM/Header.hs | 53 +- src/Chainweb/PayloadProvider/EVM/Receipt.hs | 597 ++++++++++++ src/Chainweb/PayloadProvider/EVM/Utils.hs | 31 +- .../PayloadProvider/Initialization.hs | 2 - .../PayloadProvider/Minimal/Payload.hs | 13 +- src/Chainweb/PayloadProvider/SPV.hs | 854 +++++++++++------- src/Chainweb/SPV/Argument.hs | 655 ++++++++++++++ src/Chainweb/Utils.hs | 2 +- src/Chainweb/Utils/Serialization.hs | 1 - test/payload-provider/PayloadProviderTests.hs | 48 + .../Test/Chainweb/SPV/Argument.hs | 537 +++++++++++ 16 files changed, 3004 insertions(+), 505 deletions(-) create mode 100644 src/Chainweb/PayloadProvider/EVM/Receipt.hs create mode 100644 src/Chainweb/SPV/Argument.hs create mode 100644 test/payload-provider/PayloadProviderTests.hs create mode 100644 test/payload-provider/Test/Chainweb/SPV/Argument.hs diff --git a/cabal.project b/cabal.project index 3090560120..0403fef957 100644 --- a/cabal.project +++ b/cabal.project @@ -131,7 +131,7 @@ source-repository-package source-repository-package type: git location: https://github.com/kadena-io/merkle-log.git - tag: 46ad8b617c2cac290bfb09066c1e5f0b6503584b + tag: 0d68305d8b596182080b6899c336ee6f91baf8ed --sha256: 1az7jcggcj275djnfsvhdg3n7hjrj6vp8rj137fxrg4hazh0hyz0 source-repository-package diff --git a/chainweb.cabal b/chainweb.cabal index 26058b7afa..041593a586 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -232,6 +232,7 @@ library , Chainweb.PayloadProvider.EVM.Header , Chainweb.PayloadProvider.EVM.HeaderDB , Chainweb.PayloadProvider.EVM.JsonRPC + , Chainweb.PayloadProvider.EVM.Receipt , Chainweb.PayloadProvider.EVM.SPV , Chainweb.PayloadProvider.EVM.Utils , Chainweb.PayloadProvider.Initialization @@ -243,7 +244,7 @@ library , Chainweb.PayloadProvider.P2P.RestAPI , Chainweb.PayloadProvider.P2P.RestAPI.Client , Chainweb.PayloadProvider.P2P.RestAPI.Server - -- , Chainweb.PayloadProvider.SPV + , Chainweb.PayloadProvider.SPV , Chainweb.PowHash , Chainweb.Ranked , Chainweb.RestAPI @@ -255,6 +256,7 @@ library , Chainweb.RestAPI.Orphans , Chainweb.RestAPI.Utils , Chainweb.SPV + , Chainweb.SPV.Argument , Chainweb.SPV.CreateProof , Chainweb.SPV.EventProof , Chainweb.SPV.OutputProof @@ -614,6 +616,106 @@ library chainweb-test-utils if flag(ed25519) cpp-options: -DWITH_ED25519=1 +-- Temporary for payload provider development. +-- Merge with unit tests as soon as those are fixed. +test-suite payload-provider-tests + import: warning-flags, debugging-flags + default-language: Haskell2010 + ghc-options: + -threaded + -Wno-x-partial -Wno-unrecognised-warning-flags + type: exitcode-stdio-1.0 + hs-source-dirs: test/payload-provider + main-is: PayloadProviderTests.hs + other-modules: + Test.Chainweb.SPV.Argument + build-depends: + -- internal + , chainweb + , chainweb:chainweb-test-utils + + -- external + , aeson >= 2.2 + , base >= 4.12 && < 5 + , bytestring >= 0.10.12 + , chainweb-storage >= 0.1 + , ethereum + , hashes >=0.2.2.0 + , lens >= 4.17 + , merkle-log >=0.2 + , raw-strings-qq >=1.1 + , tasty >= 1.0 + , tasty-hunit >= 0.9 + , tasty-json >= 0.1 + , text >=2.0 + , vector >= 0.12.2 + + -- , tasty-json >= 0.1 + -- , tasty-quickcheck >= 0.9 + -- , Decimal >= 0.4.2 + -- , QuickCheck >= 2.14 + -- , async >= 2.2 + -- , base >= 4.12 && < 5 + -- , base16-bytestring >= 1.0 + -- , base64-bytestring-kadena == 0.1 + -- , byteslice >= 0.2.12 + -- , bytesmith >= 0.3.10 + -- , cassava >= 0.5.1 + -- , containers >= 0.5 + -- , crypton >= 0.31 + -- , crypton-connection >=0.4 + -- , data-dword >= 0.3 + -- , data-ordlist >= 0.4.7 + -- , deepseq >= 1.4 + -- , direct-sqlite >= 2.3.27 + -- , exceptions + -- , ghc-compact >= 0.1 + -- , hashable >= 1.3 + -- , hedgehog >= 1.4 + -- , http-client >= 0.5 + -- , http-client-tls >=0.3 + -- , http-types >= 0.12 + -- , lens-aeson >= 1.2.2 + -- , loglevel >= 0.1 + -- , memory >=0.14 + -- , mtl >= 2.3 + -- , network >= 3.1.2 + -- , pact + -- , pact-json >= 0.1 + -- , pact-time:numeric >=0.3.0.1 + -- , pact-tng + -- , pact-tng:pact-request-api + -- , pact-tng:test-utils + -- , pact-tng:pact-repl + -- , patience >= 0.3 + -- , prettyprinter + -- , property-matchers ^>= 0.4 + -- , pretty-show + -- , quickcheck-instances >= 0.3 + -- , random >= 1.2 + -- , resource-pool >= 0.4 + -- , resourcet >= 1.3 + -- , safe-exceptions >= 0.1 + -- , scheduler >= 1.4 + -- , servant-client >= 0.18.2 + -- , sha-validation >=0.1 + -- , statistics >= 0.15 + -- , stm + -- , streaming >= 0.2.2 + -- , strict-concurrency >= 0.2 + -- , tasty >= 1.0 + -- , tasty-hedgehog >= 1.4.0.2 + -- , tasty-hunit >= 0.9 + -- , tasty-quickcheck >= 0.9 + -- , time >= 1.12.2 + -- , tls >=2.1.4 + -- , transformers >= 0.5 + -- , unordered-containers >= 0.2.20 + -- , wai >= 3.2 + -- , warp >= 3.3.6 + -- , warp-tls >= 3.4 + -- , yaml >= 0.11 + -- Chainweb Unit tests -- -- Tests in this test-suite diff --git a/src/Chainweb/Crypto/MerkleLog.hs b/src/Chainweb/Crypto/MerkleLog.hs index 9dddb80c31..a1c0e8978c 100644 --- a/src/Chainweb/Crypto/MerkleLog.hs +++ b/src/Chainweb/Crypto/MerkleLog.hs @@ -16,6 +16,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TypeFamilyDependencies #-} -- | -- Module: Chainweb.Crypto.MerkleLog @@ -115,6 +116,7 @@ module Chainweb.Crypto.MerkleLog , mapLogEntries , entriesHeaderSize , entriesBody +, fromMerkleNodeM -- * Merkle Log , MerkleLog(..) @@ -131,13 +133,16 @@ module Chainweb.Crypto.MerkleLog , computeMerkleLogRoot -- * Merkle Log Proofs -, headerProof +, headerProofV1 +, headerProofV2 , headerTree , headerTree_ -, bodyProof +, bodyProofV1 +, bodyProofV2 , bodyTree , bodyTree_ , proofSubject +, proofClaim -- * Utils for Defining Instances , decodeMerkleInputNode @@ -147,12 +152,7 @@ module Chainweb.Crypto.MerkleLog , encodeMerkleTreeNode -- ** IsMerkleLogEntry instance for use with @deriving via@ -, ByteArrayMerkleLogEntry(..) , MerkleRootLogEntry(..) -, Word8MerkleLogEntry(..) -, Word16BeMerkleLogEntry(..) -, Word32BeMerkleLogEntry(..) -, Word64BeMerkleLogEntry(..) -- * Exceptions , MerkleLogException(..) @@ -162,10 +162,8 @@ module Chainweb.Crypto.MerkleLog import Control.Monad.Catch -import Data.Array.Byte import Data.ByteString qualified as B import Data.ByteString.Builder qualified as BB -import Data.ByteString.Short qualified as BS import Data.Coerce import Data.Foldable import Data.Kind @@ -221,7 +219,7 @@ toWordBE -> m w toWordBE bs = case peekByteString bs of Left e -> throwM $ MerkleLogDecodeException e - Right x -> return x + Right x -> return $ be x -- -------------------------------------------------------------------------- -- -- $inputs @@ -271,7 +269,7 @@ toWordBE bs = case peekByteString bs of -- universe a type-level 'Nat' that represents a 'Word16' value. -- class MerkleUniverse k where - type MerkleTagVal k (a :: k) :: Nat + type MerkleTagVal k (a :: k) = (g :: Nat) | g -> a -- | Term level representation of the 'MerkleTagVal' of a type in a Merkle -- universe. @@ -310,6 +308,12 @@ type InUniverse u (t :: u) = (MerkleUniverse u, KnownNat (MerkleTagVal u t)) -- root of a nested Merkle tree or corresponds to an input node. -- class (MerkleHashAlgorithm a, InUniverse u (Tag b)) => IsMerkleLogEntry a u b | b -> u where + + -- | Morralay, this type family is injective. Please, see the comment on + -- the ChainwebHashTag type for details. + -- + -- We don't enforce this here to keep the type machinary simple. + -- type Tag b :: u toMerkleNode @@ -337,7 +341,7 @@ fromMerkleNodeM = either throwM return . fromMerkleNode @a -- * a fixed size polymorphic list of header values and -- * a monomorphic sequence of body values. -- --- Both the header and the body may possibly be empty. The type of the former is + -- Both the header and the body may possibly be empty. The type of the former is -- represented by an empty type-level list, while the latter is represented by -- 'Void'. -- @@ -419,8 +423,14 @@ _merkleLogTree . MerkleHashAlgorithm a => MerkleLog a u h b -> V1.MerkleTree a -_merkleLogTree = V1.merkleTree - . toList +_merkleLogTree = V1.merkleTree . _merkleLogLeafs + +_merkleLogLeafs + :: forall a u h b + . MerkleHashAlgorithm a + => MerkleLog a u h b + -> [MerkleNodeType a] +_merkleLogLeafs = toList . mapLogEntries (toMerkleNodeTagged @a) . _merkleLogEntries @@ -501,8 +511,6 @@ fromMerkleNodeTagged (InputNode bs) = do tag = tagVal @u @(Tag b) fromMerkleNodeTagged r = fromMerkleNodeM @a r - - -- | 'IsMerkleLog' values often include a hash of the value itself, which -- represents cyclic dependency of a value on itself. This function allows to -- create such an value from its representation as a sequence of merkle log @@ -605,15 +613,27 @@ computeMerkleLogRoot = _merkleLogRoot . toLog @a -- should use that instead. Another option would be to use two separate trees, -- but, again, our current use case wouldn't justify the overhead. -- -headerProof +headerProofV1 :: forall c a u b m . MonadThrow m => HasHeader a u c (MkLogType a u b) => HasMerkleLog a u b => b -> m (V1.MerkleProof a) -headerProof = uncurry3 (V1.merkleTreeProof @a) . headerTree @c @a -{-# INLINE headerProof #-} +headerProofV1 = uncurry3 (V1.merkleTreeProof @a) . headerTree @c @a +{-# INLINE headerProofV1 #-} + +headerProofV2 + :: forall c a u b m + . MonadThrow m + => HasHeader a u c (MkLogType a u b) + => HasMerkleLog a u b + => b + -> m (MerkleProof a) +headerProofV2 b = merkleProof @a p (_merkleLogLeafs @a (toLog @a b)) + where + p = headerPos @a @u @c @(MkLogType a u b) +{-# INLINE headerProofV2 #-} -- | Create the parameters for creating nested inclusion proofs with the -- 'merkleProof_' of the merkle-log package. @@ -658,7 +678,7 @@ headerTree_ b = (p, _merkleLogTree @a mlog) -- should either make sure that it is memoized or provide a version that takes a -- 'MerkleLog' value. -- -bodyProof +bodyProofV1 :: forall a u b m . MonadThrow m => HasMerkleLog a u b @@ -666,8 +686,22 @@ bodyProof -> Int -- ^ the index in the body of the log -> m (V1.MerkleProof a) -bodyProof b = uncurry3 V1.merkleTreeProof . bodyTree @a b -{-# INLINE bodyProof #-} +bodyProofV1 b = uncurry3 V1.merkleTreeProof . bodyTree @a b +{-# INLINE bodyProofV1 #-} + +bodyProofV2 + :: forall a u b m + . MonadThrow m + => HasMerkleLog a u b + => b + -> Int + -- ^ the index in the body of the log + -> m (MerkleProof a) +bodyProofV2 b i = merkleProof @a p (_merkleLogLeafs @a mlog) + where + mlog = (toLog @a b) + p = i + headerSize mlog +{-# INLINE bodyProofV2 #-} -- | Create the parameters for creating nested inclusion proofs with the -- 'merkleProof_' of the merkle-log package. @@ -706,7 +740,7 @@ bodyTree_ b i = (i_, _merkleLogTree @a mlog) mlog = toLog @a b i_ = i + headerSize mlog --- | Extract the proof subject from a 'MerkleProof' value. +-- | Extract the proof subject from a 'V1.MerkleProof' value. -- proofSubject :: forall a u b m @@ -719,6 +753,17 @@ proofSubject p = fromMerkleNodeTagged @a subj V1.MerkleProofSubject subj = V1._merkleProofSubject p {-# INLINE proofSubject #-} +-- | Extract the proof claim from a 'MerkleProof' value. +-- +proofClaim + :: forall a u b m + . MonadThrow m + => IsMerkleLogEntry a u b + => MerkleProof a + -> m b +proofClaim = fromMerkleNodeTagged @a . _merkleProofClaim +{-# INLINE proofClaim #-} + -- -------------------------------------------------------------------------- -- -- Tools Defining Instances @@ -750,22 +795,6 @@ decodeMerkleTreeNode (InputNode _) = throwM expectedTreeNodeException -- -------------------------------------------------------------------------- -- -- Support for Deriving Via --- | Support for deriving IsMerkleLogEntry for types that are Coercible with --- 'ByteArray' via the @DerivingVia@ extension. --- -newtype ByteArrayMerkleLogEntry u (t :: u) b = ByteArrayMerkleLogEntry b - -instance - (MerkleHashAlgorithm a, InUniverse u t, Coercible ByteArray b) - => IsMerkleLogEntry a u (ByteArrayMerkleLogEntry u (t :: u) b) - where - type Tag (ByteArrayMerkleLogEntry u t b) = t - toMerkleNode (ByteArrayMerkleLogEntry b) = InputNode (BS.fromShort $ coerce b) - fromMerkleNode (InputNode x) = return $! ByteArrayMerkleLogEntry (fromByteString x) - fromMerkleNode (TreeNode _) = throwM expectedInputNodeException - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} - -- | Support for deriving IsMerkleLogEntry for types that are newtype wrappers of -- 'MerkleRoot' via the @DerivingVia@ extension. -- @@ -779,70 +808,3 @@ instance (MerkleHashAlgorithm a, InUniverse u t) => IsMerkleLogEntry a u (Merkle {-# INLINE toMerkleNode #-} {-# INLINE fromMerkleNode #-} --- | Support for deriving IsMerkleLogEntry for types that are newtype wrappers of --- 'Word8' via the @DerivingVia@ extension. --- -newtype Word8MerkleLogEntry (t :: u) = Word8MerkleLogEntry { _getWord8LogEntry :: Word8 } - -instance - (MerkleHashAlgorithm a, InUniverse u t) => IsMerkleLogEntry a u (Word8MerkleLogEntry (t :: u)) - where - type Tag (Word8MerkleLogEntry t) = t - toMerkleNode = InputNode . B.singleton . _getWord8LogEntry - fromMerkleNode (InputNode x) = case B.uncons x of - Nothing -> throwM - $ MerkleLogDecodeException "failed to deserialize Word8 from empty ByteString" - Just (!c,"") -> return $! Word8MerkleLogEntry c - Just _ -> throwM - $ MerkleLogDecodeException "failed to deserialize Word8. Pending bytes in input" - fromMerkleNode (TreeNode _) = throwM expectedInputNodeException - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} - --- | Support for deriving IsMerkleLogEntry for types that are newtype wrappers of --- 'Word16' via the @DerivingVia@ extension. --- -newtype Word16BeMerkleLogEntry (t :: u) = Word16BeMerkleLogEntry - { _getWord16BeLogEntry :: Word16 } - -instance - (MerkleHashAlgorithm a, InUniverse u t) => IsMerkleLogEntry a u (Word16BeMerkleLogEntry (t :: u)) - where - type Tag (Word16BeMerkleLogEntry t) = t - toMerkleNode = InputNode . buildByteString . BB.word16BE . _getWord16BeLogEntry - fromMerkleNode (InputNode x) = Word16BeMerkleLogEntry <$> toWordBE x - fromMerkleNode (TreeNode _) = throwM expectedInputNodeException - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} - --- | Support for deriving IsMerkleLogEntry for types that are newtype wrappers of --- 'Word32' via the @DerivingVia@ extension. --- -newtype Word32BeMerkleLogEntry (t :: u) = Word32BeMerkleLogEntry - { _getWord32BeLogEntry :: Word32 } - -instance - (MerkleHashAlgorithm a, InUniverse u t) => IsMerkleLogEntry a u (Word32BeMerkleLogEntry (t :: u)) - where - type Tag (Word32BeMerkleLogEntry t) = t - toMerkleNode = InputNode . buildByteString . BB.word32BE . _getWord32BeLogEntry - fromMerkleNode (InputNode x) = Word32BeMerkleLogEntry <$> toWordBE x - fromMerkleNode (TreeNode _) = throwM expectedInputNodeException - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} - --- | Support for deriving IsMerkleLogEntry for types that are newtype wrappers of --- 'Word64' via the @DerivingVia@ extension. --- -newtype Word64BeMerkleLogEntry (t :: u) = Word64BeMerkleLogEntry - { _getWord64BeLogEntry :: Word64 } - -instance - (MerkleHashAlgorithm a, InUniverse u t) => IsMerkleLogEntry a u (Word64BeMerkleLogEntry (t :: u)) - where - type Tag (Word64BeMerkleLogEntry t) = t - toMerkleNode = InputNode . buildByteString . BB.word64BE . _getWord64BeLogEntry - fromMerkleNode (InputNode x) = Word64BeMerkleLogEntry <$> toWordBE x - fromMerkleNode (TreeNode _) = throwM expectedInputNodeException - {-# INLINE toMerkleNode #-} - {-# INLINE fromMerkleNode #-} diff --git a/src/Chainweb/MerkleUniverse.hs b/src/Chainweb/MerkleUniverse.hs index d68155736f..d37448e280 100644 --- a/src/Chainweb/MerkleUniverse.hs +++ b/src/Chainweb/MerkleUniverse.hs @@ -1,15 +1,24 @@ -{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE EmptyCase #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE TypeOperators #-} -- | -- Module: Chainweb.MerkleUniverse @@ -31,15 +40,22 @@ module Chainweb.MerkleUniverse , merkleRootTypeToText , merkleRootTypeFromText , MerkleRootMismatch(..) + +, fromSomeTagVal +, fromTagVal +, Sing (..) +, pattern STagVal +, sTagVal ) where import Control.DeepSeq import Control.Monad.Catch -import Data.Hash.SHA2 - import Data.Aeson -import qualified Data.Text as T +import Data.Hash.SHA2 +import Data.Kind +import Data.Singletons +import Data.Text qualified as T import Data.Void import GHC.Generics @@ -49,6 +65,11 @@ import GHC.Stack import Chainweb.Crypto.MerkleLog import Chainweb.Utils +import Control.Monad +import Data.Word +import GHC.TypeNats +import Data.Type.Equality +import Unsafe.Coerce -- -------------------------------------------------------------------------- -- -- Chainweb Merkle Hash Algorithm @@ -58,11 +79,11 @@ type ChainwebMerkleHashAlgorithm = Sha2_512_256 -- -------------------------------------------------------------------------- -- -- Chainweb Merkle Universe --- | Tags for Leaf Nodes in the Chainweb Merkle Tree +-- | The closed kind of tags for Leaf Nodes in the Chainweb Merkle Tree. -- -- IMPORTANT NOTE: -- --- A tag MUST uniquely identify the each particular use of a type in the Merkle +-- A tag MUST uniquely identify each particular use of a type in the Merkle -- Tree. NEVER EVER reuse a tag at a different place in the tree. -- -- Merkle Proofs for the Chainweb Merkle tree witness the existence of a given @@ -70,6 +91,12 @@ type ChainwebMerkleHashAlgorithm = Sha2_512_256 -- in different roles in multiple places in the tree, the proof will be -- ambiguous. -- +-- Particular care must be taken for types that are used as roots of the tree +-- (MerkleRoot :: MerkleNotType). It is generally assumed that the preimage of +-- of the respective hashes are computed from leafs that themselfs are tagged. +-- If an attack can control the preimage of a root hash, they can create valid +-- proofs for arbtirary values. +-- data ChainwebHashTag = VoidTag | MerkleRootTag @@ -122,7 +149,8 @@ data ChainwebHashTag | EthBlobGasUsedTag | EthExcessBlobGasTag | EthParentBeaconBlockRootTag - deriving (Show, Eq) + | EthReceiptTag + deriving (Show, Eq, Bounded, Enum) instance MerkleUniverse ChainwebHashTag where type MerkleTagVal ChainwebHashTag 'VoidTag = 0x0000 @@ -176,6 +204,7 @@ instance MerkleUniverse ChainwebHashTag where type MerkleTagVal ChainwebHashTag 'EthBlobGasUsedTag = 0x0051 type MerkleTagVal ChainwebHashTag 'EthExcessBlobGasTag = 0x0052 type MerkleTagVal ChainwebHashTag 'EthParentBeaconBlockRootTag = 0x0053 + type MerkleTagVal ChainwebHashTag 'EthReceiptTag = 0x0054 instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Void where type Tag Void = 'VoidTag @@ -226,3 +255,381 @@ data MerkleRootMismatch = MerkleRootMismatch deriving (Show, Eq, Ord, Generic, NFData) instance Exception MerkleRootMismatch + +-- -------------------------------------------------------------------------- -- +-- Singletons + +data instance Sing :: ChainwebHashTag -> Type where + SVoidTag + :: SNat (MerkleTagVal ChainwebHashTag VoidTag) + -> Sing 'VoidTag + SMerkleRootTag + :: SNat (MerkleTagVal ChainwebHashTag MerkleRootTag) + -> Sing 'MerkleRootTag + SChainIdTag + :: SNat (MerkleTagVal ChainwebHashTag ChainIdTag) + -> Sing 'ChainIdTag + SBlockHeightTag + :: SNat (MerkleTagVal ChainwebHashTag BlockHeightTag) + -> Sing 'BlockHeightTag + SBlockWeightTag + :: SNat (MerkleTagVal ChainwebHashTag BlockWeightTag) + -> Sing 'BlockWeightTag + SBlockPayloadHashTag + :: SNat (MerkleTagVal ChainwebHashTag BlockPayloadHashTag) + -> Sing 'BlockPayloadHashTag + SFeatureFlagsTag + :: SNat (MerkleTagVal ChainwebHashTag FeatureFlagsTag) + -> Sing 'FeatureFlagsTag + SBlockCreationTimeTag + :: SNat (MerkleTagVal ChainwebHashTag BlockCreationTimeTag) + -> Sing 'BlockCreationTimeTag + SChainwebVersionTag + :: SNat (MerkleTagVal ChainwebHashTag ChainwebVersionTag) + -> Sing 'ChainwebVersionTag + SPowHashTag + :: SNat (MerkleTagVal ChainwebHashTag PowHashTag) + -> Sing 'PowHashTag + SBlockHashTag + :: SNat (MerkleTagVal ChainwebHashTag BlockHashTag) + -> Sing 'BlockHashTag + SHashTargetTag + :: SNat (MerkleTagVal ChainwebHashTag HashTargetTag) + -> Sing 'HashTargetTag + STransactionTag + :: SNat (MerkleTagVal ChainwebHashTag TransactionTag) + -> Sing 'TransactionTag + STransactionOutputTag + :: SNat (MerkleTagVal ChainwebHashTag TransactionOutputTag) + -> Sing 'TransactionOutputTag + SBlockTransactionsHashTag + :: SNat (MerkleTagVal ChainwebHashTag BlockTransactionsHashTag) + -> Sing 'BlockTransactionsHashTag + SBlockOutputsHashTag + :: SNat (MerkleTagVal ChainwebHashTag BlockOutputsHashTag) + -> Sing 'BlockOutputsHashTag + SMinerDataTag + :: SNat (MerkleTagVal ChainwebHashTag MinerDataTag) + -> Sing 'MinerDataTag + SCoinbaseOutputTag + :: SNat (MerkleTagVal ChainwebHashTag CoinbaseOutputTag) + -> Sing 'CoinbaseOutputTag + SEpochStartTimeTag + :: SNat (MerkleTagVal ChainwebHashTag EpochStartTimeTag) + -> Sing 'EpochStartTimeTag + SBlockNonceTag + :: SNat (MerkleTagVal ChainwebHashTag BlockNonceTag) + -> Sing 'BlockNonceTag + SOutputEventsTag + :: SNat (MerkleTagVal ChainwebHashTag OutputEventsTag) + -> Sing 'OutputEventsTag + SBlockEventsHashTag + :: SNat (MerkleTagVal ChainwebHashTag BlockEventsHashTag) + -> Sing 'BlockEventsHashTag + SRequestKeyTag + :: SNat (MerkleTagVal ChainwebHashTag RequestKeyTag) + -> Sing 'RequestKeyTag + SPactEventTag + :: SNat (MerkleTagVal ChainwebHashTag PactEventTag) + -> Sing 'PactEventTag + SMinimalPayloadTag + :: SNat (MerkleTagVal ChainwebHashTag MinimalPayloadTag) + -> Sing 'MinimalPayloadTag + SEthParentHashTag + :: SNat (MerkleTagVal ChainwebHashTag EthParentHashTag) + -> Sing 'EthParentHashTag + SEthOmmersHashTag + :: SNat (MerkleTagVal ChainwebHashTag EthOmmersHashTag) + -> Sing 'EthOmmersHashTag + SEthBeneficiaryTag + :: SNat (MerkleTagVal ChainwebHashTag EthBeneficiaryTag) + -> Sing 'EthBeneficiaryTag + SEthStateRootTag + :: SNat (MerkleTagVal ChainwebHashTag EthStateRootTag) + -> Sing 'EthStateRootTag + SEthTransactionsRootTag + :: SNat (MerkleTagVal ChainwebHashTag EthTransactionsRootTag) + -> Sing 'EthTransactionsRootTag + SEthReceiptsRootTag + :: SNat (MerkleTagVal ChainwebHashTag EthReceiptsRootTag) + -> Sing 'EthReceiptsRootTag + SEthBloomTag + :: SNat (MerkleTagVal ChainwebHashTag EthBloomTag) + -> Sing 'EthBloomTag + SEthDifficultyTag + :: SNat (MerkleTagVal ChainwebHashTag EthDifficultyTag) + -> Sing 'EthDifficultyTag + SEthBlockNumberTag + :: SNat (MerkleTagVal ChainwebHashTag EthBlockNumberTag) + -> Sing 'EthBlockNumberTag + SEthGasLimitTag + :: SNat (MerkleTagVal ChainwebHashTag EthGasLimitTag) + -> Sing 'EthGasLimitTag + SEthGasUsedTag + :: SNat (MerkleTagVal ChainwebHashTag EthGasUsedTag) + -> Sing 'EthGasUsedTag + SEthTimestampTag + :: SNat (MerkleTagVal ChainwebHashTag EthTimestampTag) + -> Sing 'EthTimestampTag + SEthExtraDataTag + :: SNat (MerkleTagVal ChainwebHashTag EthExtraDataTag) + -> Sing 'EthExtraDataTag + SEthRandaoTag + :: SNat (MerkleTagVal ChainwebHashTag EthRandaoTag) + -> Sing 'EthRandaoTag + SEthNonceTag + :: SNat (MerkleTagVal ChainwebHashTag EthNonceTag) + -> Sing 'EthNonceTag + SEthBaseFeePerGasTag + :: SNat (MerkleTagVal ChainwebHashTag EthBaseFeePerGasTag) + -> Sing 'EthBaseFeePerGasTag + SEthWithdrawalsRootTag + :: SNat (MerkleTagVal ChainwebHashTag EthWithdrawalsRootTag) + -> Sing 'EthWithdrawalsRootTag + SEthBlobGasUsedTag + :: SNat (MerkleTagVal ChainwebHashTag EthBlobGasUsedTag) + -> Sing 'EthBlobGasUsedTag + SEthExcessBlobGasTag + :: SNat (MerkleTagVal ChainwebHashTag EthExcessBlobGasTag) + -> Sing 'EthExcessBlobGasTag + SEthParentBeaconBlockRootTag + :: SNat (MerkleTagVal ChainwebHashTag EthParentBeaconBlockRootTag) + -> Sing 'EthParentBeaconBlockRootTag + SEthReceiptTag + :: SNat (MerkleTagVal ChainwebHashTag EthReceiptTag) + -> Sing 'EthReceiptTag + +deriving instance Show (Sing (a :: ChainwebHashTag)) + +instance Eq (Sing (a :: ChainwebHashTag)) where + _ == _ = True + +instance TestEquality (Sing @ChainwebHashTag) where + testEquality a b = case testEquality (sTagVal a) (sTagVal b) of + Just Refl -> Just $ unsafeCoerce Refl + -- This is justified by the injectivity of 'MerkleTagVal' + Nothing -> Nothing + +sTagVal :: Sing (a :: ChainwebHashTag) -> SNat (MerkleTagVal ChainwebHashTag a) +sTagVal (SVoidTag n) = n +sTagVal (SMerkleRootTag n) = n +sTagVal (SChainIdTag n) = n +sTagVal (SBlockHeightTag n) = n +sTagVal (SBlockWeightTag n) = n +sTagVal (SBlockPayloadHashTag n) = n +sTagVal (SFeatureFlagsTag n) = n +sTagVal (SBlockCreationTimeTag n) = n +sTagVal (SChainwebVersionTag n) = n +sTagVal (SPowHashTag n) = n +sTagVal (SBlockHashTag n) = n +sTagVal (SHashTargetTag n) = n +sTagVal (STransactionTag n) = n +sTagVal (STransactionOutputTag n) = n +sTagVal (SBlockTransactionsHashTag n) = n +sTagVal (SBlockOutputsHashTag n) = n +sTagVal (SMinerDataTag n) = n +sTagVal (SCoinbaseOutputTag n) = n +sTagVal (SEpochStartTimeTag n) = n +sTagVal (SBlockNonceTag n) = n +sTagVal (SOutputEventsTag n) = n +sTagVal (SBlockEventsHashTag n) = n +sTagVal (SRequestKeyTag n) = n +sTagVal (SPactEventTag n) = n +sTagVal (SMinimalPayloadTag n) = n +sTagVal (SEthParentHashTag n) = n +sTagVal (SEthOmmersHashTag n) = n +sTagVal (SEthBeneficiaryTag n) = n +sTagVal (SEthStateRootTag n) = n +sTagVal (SEthTransactionsRootTag n) = n +sTagVal (SEthReceiptsRootTag n) = n +sTagVal (SEthBloomTag n) = n +sTagVal (SEthDifficultyTag n) = n +sTagVal (SEthBlockNumberTag n) = n +sTagVal (SEthGasLimitTag n) = n +sTagVal (SEthGasUsedTag n) = n +sTagVal (SEthTimestampTag n) = n +sTagVal (SEthExtraDataTag n) = n +sTagVal (SEthRandaoTag n) = n +sTagVal (SEthNonceTag n) = n +sTagVal (SEthBaseFeePerGasTag n) = n +sTagVal (SEthWithdrawalsRootTag n) = n +sTagVal (SEthBlobGasUsedTag n) = n +sTagVal (SEthExcessBlobGasTag n) = n +sTagVal (SEthParentBeaconBlockRootTag n) = n +sTagVal (SEthReceiptTag n) = n + +pattern STagVal + :: forall (a :: ChainwebHashTag) + . SNat (MerkleTagVal ChainwebHashTag a) + -> Sing a +pattern STagVal n <- (sTagVal -> n) +{-# COMPLETE STagVal #-} + +instance SingI 'VoidTag where sing = SVoidTag SNat +instance SingI 'MerkleRootTag where sing = SMerkleRootTag SNat +instance SingI 'ChainIdTag where sing = SChainIdTag SNat +instance SingI 'BlockHeightTag where sing = SBlockHeightTag SNat +instance SingI 'BlockWeightTag where sing = SBlockWeightTag SNat +instance SingI 'BlockPayloadHashTag where sing = SBlockPayloadHashTag SNat +instance SingI 'FeatureFlagsTag where sing = SFeatureFlagsTag SNat +instance SingI 'BlockCreationTimeTag where sing = SBlockCreationTimeTag SNat +instance SingI 'ChainwebVersionTag where sing = SChainwebVersionTag SNat +instance SingI 'PowHashTag where sing = SPowHashTag SNat +instance SingI 'BlockHashTag where sing = SBlockHashTag SNat +instance SingI 'HashTargetTag where sing = SHashTargetTag SNat +instance SingI 'TransactionTag where sing = STransactionTag SNat +instance SingI 'TransactionOutputTag where sing = STransactionOutputTag SNat +instance SingI 'BlockTransactionsHashTag where sing = SBlockTransactionsHashTag SNat +instance SingI 'BlockOutputsHashTag where sing = SBlockOutputsHashTag SNat +instance SingI 'MinerDataTag where sing = SMinerDataTag SNat +instance SingI 'CoinbaseOutputTag where sing = SCoinbaseOutputTag SNat +instance SingI 'EpochStartTimeTag where sing = SEpochStartTimeTag SNat +instance SingI 'BlockNonceTag where sing = SBlockNonceTag SNat +instance SingI 'OutputEventsTag where sing = SOutputEventsTag SNat +instance SingI 'BlockEventsHashTag where sing = SBlockEventsHashTag SNat +instance SingI 'RequestKeyTag where sing = SRequestKeyTag SNat +instance SingI 'PactEventTag where sing = SPactEventTag SNat +instance SingI 'MinimalPayloadTag where sing = SMinimalPayloadTag SNat +instance SingI 'EthParentHashTag where sing = SEthParentHashTag SNat +instance SingI 'EthOmmersHashTag where sing = SEthOmmersHashTag SNat +instance SingI 'EthBeneficiaryTag where sing = SEthBeneficiaryTag SNat +instance SingI 'EthStateRootTag where sing = SEthStateRootTag SNat +instance SingI 'EthTransactionsRootTag where sing = SEthTransactionsRootTag SNat +instance SingI 'EthReceiptsRootTag where sing = SEthReceiptsRootTag SNat +instance SingI 'EthBloomTag where sing = SEthBloomTag SNat +instance SingI 'EthDifficultyTag where sing = SEthDifficultyTag SNat +instance SingI 'EthBlockNumberTag where sing = SEthBlockNumberTag SNat +instance SingI 'EthGasLimitTag where sing = SEthGasLimitTag SNat +instance SingI 'EthGasUsedTag where sing = SEthGasUsedTag SNat +instance SingI 'EthTimestampTag where sing = SEthTimestampTag SNat +instance SingI 'EthExtraDataTag where sing = SEthExtraDataTag SNat +instance SingI 'EthRandaoTag where sing = SEthRandaoTag SNat +instance SingI 'EthNonceTag where sing = SEthNonceTag SNat +instance SingI 'EthBaseFeePerGasTag where sing = SEthBaseFeePerGasTag SNat +instance SingI 'EthWithdrawalsRootTag where sing = SEthWithdrawalsRootTag SNat +instance SingI 'EthBlobGasUsedTag where sing = SEthBlobGasUsedTag SNat +instance SingI 'EthExcessBlobGasTag where sing = SEthExcessBlobGasTag SNat +instance SingI 'EthParentBeaconBlockRootTag where sing = SEthParentBeaconBlockRootTag SNat +instance SingI 'EthReceiptTag where sing = SEthReceiptTag SNat + +instance SingKind ChainwebHashTag where + type Demote ChainwebHashTag = ChainwebHashTag + fromSing (SVoidTag SNat) = VoidTag + fromSing (SMerkleRootTag SNat) = MerkleRootTag + fromSing (SChainIdTag SNat) = ChainIdTag + fromSing (SBlockHeightTag SNat) = BlockHeightTag + fromSing (SBlockWeightTag SNat) = BlockWeightTag + fromSing (SBlockPayloadHashTag SNat) = BlockPayloadHashTag + fromSing (SFeatureFlagsTag SNat) = FeatureFlagsTag + fromSing (SBlockCreationTimeTag SNat) = BlockCreationTimeTag + fromSing (SChainwebVersionTag SNat) = ChainwebVersionTag + fromSing (SPowHashTag SNat) = PowHashTag + fromSing (SBlockHashTag SNat) = BlockHashTag + fromSing (SHashTargetTag SNat) = HashTargetTag + fromSing (STransactionTag SNat) = TransactionTag + fromSing (STransactionOutputTag SNat) = TransactionOutputTag + fromSing (SBlockTransactionsHashTag SNat) = BlockTransactionsHashTag + fromSing (SBlockOutputsHashTag SNat) = BlockOutputsHashTag + fromSing (SMinerDataTag SNat) = MinerDataTag + fromSing (SCoinbaseOutputTag SNat) = CoinbaseOutputTag + fromSing (SEpochStartTimeTag SNat) = EpochStartTimeTag + fromSing (SBlockNonceTag SNat) = BlockNonceTag + fromSing (SOutputEventsTag SNat) = OutputEventsTag + fromSing (SBlockEventsHashTag SNat) = BlockEventsHashTag + fromSing (SRequestKeyTag SNat) = RequestKeyTag + fromSing (SPactEventTag SNat) = PactEventTag + fromSing (SMinimalPayloadTag SNat) = MinimalPayloadTag + fromSing (SEthParentHashTag SNat) = EthParentHashTag + fromSing (SEthOmmersHashTag SNat) = EthOmmersHashTag + fromSing (SEthBeneficiaryTag SNat) = EthBeneficiaryTag + fromSing (SEthStateRootTag SNat) = EthStateRootTag + fromSing (SEthTransactionsRootTag SNat) = EthTransactionsRootTag + fromSing (SEthReceiptsRootTag SNat) = EthReceiptsRootTag + fromSing (SEthBloomTag SNat) = EthBloomTag + fromSing (SEthDifficultyTag SNat) = EthDifficultyTag + fromSing (SEthBlockNumberTag SNat) = EthBlockNumberTag + fromSing (SEthGasLimitTag SNat) = EthGasLimitTag + fromSing (SEthGasUsedTag SNat) = EthGasUsedTag + fromSing (SEthTimestampTag SNat) = EthTimestampTag + fromSing (SEthExtraDataTag SNat) = EthExtraDataTag + fromSing (SEthRandaoTag SNat) = EthRandaoTag + fromSing (SEthNonceTag SNat) = EthNonceTag + fromSing (SEthBaseFeePerGasTag SNat) = EthBaseFeePerGasTag + fromSing (SEthWithdrawalsRootTag SNat) = EthWithdrawalsRootTag + fromSing (SEthBlobGasUsedTag SNat) = EthBlobGasUsedTag + fromSing (SEthExcessBlobGasTag SNat) = EthExcessBlobGasTag + fromSing (SEthParentBeaconBlockRootTag SNat) = EthParentBeaconBlockRootTag + fromSing (SEthReceiptTag SNat) = EthReceiptTag + + toSing VoidTag = SomeSing (SVoidTag SNat) + toSing MerkleRootTag = SomeSing (SMerkleRootTag SNat) + toSing ChainIdTag = SomeSing (SChainIdTag SNat) + toSing BlockHeightTag = SomeSing (SBlockHeightTag SNat) + toSing BlockWeightTag = SomeSing (SBlockWeightTag SNat) + toSing BlockPayloadHashTag = SomeSing (SBlockPayloadHashTag SNat) + toSing FeatureFlagsTag = SomeSing (SFeatureFlagsTag SNat) + toSing BlockCreationTimeTag = SomeSing (SBlockCreationTimeTag SNat) + toSing ChainwebVersionTag = SomeSing (SChainwebVersionTag SNat) + toSing PowHashTag = SomeSing (SPowHashTag SNat) + toSing BlockHashTag = SomeSing (SBlockHashTag SNat) + toSing HashTargetTag = SomeSing (SHashTargetTag SNat) + toSing TransactionTag = SomeSing (STransactionTag SNat) + toSing TransactionOutputTag = SomeSing (STransactionOutputTag SNat) + toSing BlockTransactionsHashTag = SomeSing (SBlockTransactionsHashTag SNat) + toSing BlockOutputsHashTag = SomeSing (SBlockOutputsHashTag SNat) + toSing MinerDataTag = SomeSing (SMinerDataTag SNat) + toSing CoinbaseOutputTag = SomeSing (SCoinbaseOutputTag SNat) + toSing EpochStartTimeTag = SomeSing (SEpochStartTimeTag SNat) + toSing BlockNonceTag = SomeSing (SBlockNonceTag SNat) + toSing OutputEventsTag = SomeSing (SOutputEventsTag SNat) + toSing BlockEventsHashTag = SomeSing (SBlockEventsHashTag SNat) + toSing RequestKeyTag = SomeSing (SRequestKeyTag SNat) + toSing PactEventTag = SomeSing (SPactEventTag SNat) + toSing MinimalPayloadTag = SomeSing (SMinimalPayloadTag SNat) + toSing EthParentHashTag = SomeSing (SEthParentHashTag SNat) + toSing EthOmmersHashTag = SomeSing (SEthOmmersHashTag SNat) + toSing EthBeneficiaryTag = SomeSing (SEthBeneficiaryTag SNat) + toSing EthStateRootTag = SomeSing (SEthStateRootTag SNat) + toSing EthTransactionsRootTag = SomeSing (SEthTransactionsRootTag SNat) + toSing EthReceiptsRootTag = SomeSing (SEthReceiptsRootTag SNat) + toSing EthBloomTag = SomeSing (SEthBloomTag SNat) + toSing EthDifficultyTag = SomeSing (SEthDifficultyTag SNat) + toSing EthBlockNumberTag = SomeSing (SEthBlockNumberTag SNat) + toSing EthGasLimitTag = SomeSing (SEthGasLimitTag SNat) + toSing EthGasUsedTag = SomeSing (SEthGasUsedTag SNat) + toSing EthTimestampTag = SomeSing (SEthTimestampTag SNat) + toSing EthExtraDataTag = SomeSing (SEthExtraDataTag SNat) + toSing EthRandaoTag = SomeSing (SEthRandaoTag SNat) + toSing EthNonceTag = SomeSing (SEthNonceTag SNat) + toSing EthBaseFeePerGasTag = SomeSing (SEthBaseFeePerGasTag SNat) + toSing EthWithdrawalsRootTag = SomeSing (SEthWithdrawalsRootTag SNat) + toSing EthBlobGasUsedTag = SomeSing (SEthBlobGasUsedTag SNat) + toSing EthExcessBlobGasTag = SomeSing (SEthExcessBlobGasTag SNat) + toSing EthParentBeaconBlockRootTag = SomeSing (SEthParentBeaconBlockRootTag SNat) + toSing EthReceiptTag = SomeSing (SEthReceiptTag SNat) + +tagList :: [ChainwebHashTag] +tagList = [minBound .. maxBound] + +fromTagVal :: forall m . MonadThrow m => Word16 -> m ChainwebHashTag +fromTagVal x = case msum (f <$> tagList) of + Just t -> return t + Nothing -> throwM $ DecodeException $ "unknown tag value: " <> sshow x + where + f :: ChainwebHashTag -> Maybe ChainwebHashTag + f t = case (toSing @ChainwebHashTag t) of + (SomeSing (STagVal (SNat @s))) -> do + guard (natVal_ @s == int x) + Just t + +fromSomeTagVal :: forall m . MonadThrow m => Word16 -> m (SomeSing ChainwebHashTag) +fromSomeTagVal x = case msum (f <$> tagList) of + Just t -> return t + Nothing -> throwM $ DecodeException $ "unknown tag value: " <> sshow x + where + f :: ChainwebHashTag -> Maybe (SomeSing ChainwebHashTag) + f t = case (toSing @ChainwebHashTag t) of + r@(SomeSing (STagVal (SNat @s))) -> do + guard (natVal_ @s == int x) + Just r + diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 467897a6ea..51beaf7448 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -64,11 +64,11 @@ import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainValue +import Chainweb.Core.Brief import Chainweb.Cut hiding (join) import Chainweb.Cut.Create import Chainweb.Cut.CutHashes import Chainweb.CutDB -import Chainweb.Core.Brief import Chainweb.Logger (Logger, logFunction) import Chainweb.Logging.Miner import Chainweb.Miner.Config @@ -79,35 +79,28 @@ import Chainweb.Time (Micros(..), getCurrentTimeIntegral) import Chainweb.Utils hiding (check) import Chainweb.Version import Chainweb.WebBlockHeaderDB - import Control.Applicative +import Control.Concurrent.Async import Control.Concurrent.STM (atomically, STM, retry) import Control.Concurrent.STM.TVar import Control.Lens import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class - import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS +import Data.Hashable import Data.LogMessage (JsonLog(..), LogFunction, LogFunctionText) import Data.Map.Strict qualified as M import Data.Maybe import Data.Text qualified as T - +import Data.Vector qualified as V import GHC.Generics (Generic) import GHC.Stack - import Numeric.Natural - import Streaming.Prelude qualified as S - import System.LogLevel (LogLevel(..)) import System.Random (randomRIO) -import Chainweb.Ranked -import Control.Concurrent.Async -import qualified Data.Vector as V -import Data.Hashable -- -------------------------------------------------------------------------- -- -- Utils diff --git a/src/Chainweb/PayloadProvider/EVM/Header.hs b/src/Chainweb/PayloadProvider/EVM/Header.hs index eeb9526218..871dedea2c 100644 --- a/src/Chainweb/PayloadProvider/EVM/Header.hs +++ b/src/Chainweb/PayloadProvider/EVM/Header.hs @@ -1,4 +1,6 @@ {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} @@ -9,6 +11,7 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -18,9 +21,6 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE ConstraintKinds #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE NumericUnderscores #-} {-# OPTIONS_GHC -Wno-orphans #-} @@ -93,10 +93,10 @@ import Chainweb.BlockCreationTime qualified as Chainweb import Chainweb.BlockHash qualified as Chainweb import Chainweb.BlockHeight qualified as Chainweb import Chainweb.BlockPayloadHash -import Chainweb.Crypto.MerkleLog hiding (headerProof) -import Chainweb.Crypto.MerkleLog qualified as MerkleLog +import Chainweb.Crypto.MerkleLog import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse +import Chainweb.PayloadProvider.EVM.Receipt (Receipt) import Chainweb.PayloadProvider.EVM.Utils import Chainweb.Storage.Table import Chainweb.Time @@ -111,8 +111,8 @@ import Data.ByteString.Short qualified as BS import Data.ByteString.Short qualified as SBS import Data.Function import Data.Hashable (Hashable(..)) +import Data.MerkleLog qualified as V2 import Data.MerkleLog.Common -import Data.MerkleLog.V1 qualified as V1 import Data.Ratio ((%)) import Data.Text qualified as T import Data.Void @@ -391,18 +391,15 @@ headerProof => a ~ ChainwebMerkleHashAlgorithm => HasHeader a ChainwebHashTag c (MkLogType a ChainwebHashTag Header) => Header - -> m (V1.MerkleProof a) -headerProof = MerkleLog.headerProof @c + -> m (V2.MerkleProof a) +headerProof = headerProofV2 @c {-# INLINE headerProof #-} -- | Runs a header proof. Returns the BlockPayloadHash of the EVM execution -- header for which inclusion is proven. -- -runHeaderProof - :: MonadThrow m - => V1.MerkleProof ChainwebMerkleHashAlgorithm - -> m BlockPayloadHash -runHeaderProof p = BlockPayloadHash . MerkleLogHash <$> V1.runMerkleProof p +runHeaderProof :: V2.MerkleProof ChainwebMerkleHashAlgorithm -> BlockPayloadHash +runHeaderProof = BlockPayloadHash . MerkleLogHash . V2.runProof {-# INLINE runHeaderProof #-} -- -------------------------------------------------------------------------- -- @@ -496,8 +493,8 @@ instance FromJSON Header where <*> o .: "blobGasUsed" <*> o .: "excessBlobGas" <*> o .: "parentBeaconBlockRoot" - <*> pure (error "Chainweb.PayloadProvider.EVM.Header: _hdrHash") - <*> pure (error "Chainweb.PayloadProvider.EVM.Header: _hdrPayloadHash") + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrHash JSON during deserialization") + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrPayloadHash JSON during deserialization") return hdr { _hdrHash = computeBlockHash hdr , _hdrPayloadHash = computeBlockPayloadHash hdr @@ -565,8 +562,8 @@ instance RLP Header where <*> getRlp -- blob gas used <*> getRlp -- excess blob gas <*> getRlp -- parent beacon block root - <*> pure (error "Chainweb.PayloadProvider.EVM.Header: _hdrHash") - <*> pure (error "Chainweb.PayloadProvider.EVM.Header: _hdrPayloadHash") + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrHash during RLP deserialization") + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrPayloadHash during RLP deserialization") return hdr { _hdrHash = computeBlockHash hdr , _hdrPayloadHash = computeBlockPayloadHash hdr @@ -578,21 +575,6 @@ instance RLP Header where -- -------------------------------------------------------------------------- -- -- MerkleLog Entries -newtype RlpMerkleLogEntry (tag :: ChainwebHashTag) t = RlpMerkleLogEntry t - deriving newtype RLP -instance - ( KnownNat (MerkleTagVal ChainwebHashTag tag) - , MerkleHashAlgorithm a - , RLP t - ) - => IsMerkleLogEntry a ChainwebHashTag (RlpMerkleLogEntry tag t) where - type Tag (RlpMerkleLogEntry tag t) = tag - toMerkleNode = InputNode . putRlpByteString - fromMerkleNode (InputNode bs) = case get getRlp bs of - Left e -> throwM $ MerkleLogDecodeException (T.pack e) - Right x -> Right x - fromMerkleNode (TreeNode _) = throwM expectedInputNodeException - deriving via (RlpMerkleLogEntry 'EthParentHashTag ParentHash) instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ParentHash @@ -608,9 +590,6 @@ deriving via (RlpMerkleLogEntry 'EthStateRootTag StateRoot) deriving via (RlpMerkleLogEntry 'EthTransactionsRootTag TransactionsRoot) instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag TransactionsRoot -deriving via (RlpMerkleLogEntry 'EthReceiptsRootTag ReceiptsRoot) - instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ReceiptsRoot - deriving via (RlpMerkleLogEntry 'EthBloomTag Bloom) instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Bloom @@ -734,8 +713,8 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag Header where , _hdrBlobGasUsed = hBlobGasUsed , _hdrExcessBlobGas = hExcessBlobGas , _hdrParentBeaconBlockRoot = hParentBeaconBlockRoot - , _hdrHash = error "Chainweb.PayloadProvider.EVM.Header: _hdrHash" - , _hdrPayloadHash = error "Chainweb.PayloadProvider.EVM.Header: _hdrPayloadHash" + , _hdrHash = error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrHash during MerkleLog deserialization" + , _hdrPayloadHash = error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrPayloadHash during MerkleLog deserialization" } ( hParentHash :+: hOmmersHash diff --git a/src/Chainweb/PayloadProvider/EVM/Receipt.hs b/src/Chainweb/PayloadProvider/EVM/Receipt.hs new file mode 100644 index 0000000000..f3d53149f1 --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/Receipt.hs @@ -0,0 +1,597 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} + +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.Receipt +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.EVM.Receipt +( LogTopic(..) +, LogData(..) +, LogEntry(..) +, RpcLogEntry(..) +, fromRpcLogEntry +, TxStatus(..) +, Receipt(..) +, ReceiptsRoot(..) +, TransactionIndex(..) +, RpcReceipt(..) +, fromRpcReceipt +, encodeReceipt +, receiptTrieProof +, rpcReceiptTrieProof +) where + +import Control.Applicative +import Chainweb.Crypto.MerkleLog +import Chainweb.MerkleUniverse +import Chainweb.PayloadProvider.EVM.Utils +import Chainweb.Utils +import Data.Aeson +import Data.Aeson.Types (Pair) +import Data.Bifunctor +import Data.ByteString qualified as B +import Ethereum.Misc +import Ethereum.RLP +import Ethereum.Trie +import Ethereum.Utils hiding (int) +import Numeric.Natural + +-- -------------------------------------------------------------------------- -- +-- Log Entry + +newtype LogTopic = LogTopic (BytesN 32) + deriving (Show, Eq) + deriving newtype (RLP) + deriving ToJSON via (HexBytes (BytesN 32)) + deriving FromJSON via (HexBytes (BytesN 32)) + +newtype LogData = LogData B.ByteString + deriving (Show, Eq) + deriving newtype (RLP) + deriving ToJSON via (HexBytes B.ByteString) + deriving FromJSON via (HexBytes B.ByteString) + +-- | LogEntry +-- +-- https://github.com/ethereum/py-evm/blob/main/eth/rlp/logs.py: +-- +-- @ +-- fields = [ +-- ("address", address), +-- ("topics", CountableList(uint32)), +-- ("data", binary) +-- ] +-- @ +-- +data LogEntry = LogEntry + { _logEntryAddress :: !Address + , _logEntryTopics :: ![LogTopic] + , _logEntryData :: !LogData + } + deriving (Show, Eq) + +instance RLP LogEntry where + putRlp a = putRlpL + [ putRlp $! _logEntryAddress a + , putRlpL $! putRlp <$> _logEntryTopics a + , putRlp $! _logEntryData a + ] + getRlp = label "LogEntry" $ getRlpL $ LogEntry + <$> label "logEntryAddress" getRlp + <*> label "logEntryTopics" getRlp + <*> label "logEntryData" getRlp + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance ToJSON LogEntry where + toEncoding = pairs . mconcat . logEntryProperties + toJSON = object . logEntryProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON LogEntry where + parseJSON = withObject "LogEntry" $ \o -> LogEntry + <$> o .: "address" + <*> o .: "topics" + <*> o .: "data" + {-# INLINE parseJSON #-} + +logEntryProperties :: KeyValue e kv => LogEntry -> [kv] +logEntryProperties r = + [ "address" .= _logEntryAddress r + , "data" .= _logEntryData r + , "topics" .= _logEntryTopics r + ] +{-# INLINE logEntryProperties #-} +{-# SPECIALIZE logEntryProperties :: LogEntry -> [Series] #-} +{-# SPECIALIZE logEntryProperties :: LogEntry -> [Pair] #-} + +-- -------------------------------------------------------------------------- -- +-- JSON RPC Log Entries + +newtype TransactionIndex = TransactionIndex Natural + deriving (Show, Eq, Ord) + deriving newtype (RLP) + deriving ToJSON via (HexQuantity Natural) + deriving FromJSON via (HexQuantity Natural) + +data RpcLogEntry = RpcLogEntry + { _rpcLogEntryAddress :: !Address + -- ^ 20 Bytes - address from which this log originated. + , _rpcLogEntryTopics :: ![LogTopic] + -- ^ Array of 0 to 4 32 Bytes of indexed log arguments. (In solidity: The first topic is the + -- hash of the signature of the event (e.g. Deposit(address,bytes32,uint256)), except you + -- declared the event with the anonymous specifier.) + , _rpcLogEntryData :: !LogData + -- ^ contains one or more 32 Bytes non-indexed arguments of the log. + , _rpcLogEntryBlockHash :: !BlockHash + -- ^ 32 Bytes - hash of the block where this log was in. null when its pending. null when + -- its pending log. + , _rpcLogEntryBlockNumber :: !BlockNumber + -- ^ the block number where this log was in. null when its pending. null when its pending + -- log. + , _rpcLogEntryLogIndex :: !TransactionIndex + -- ^ integer of the log index position in the block. null when its pending log. + , _rpcLogEntryRemoved :: !Bool + -- ^ true when the log was removed, due to a chain reorganization. false if it's a valid + -- log. + , _rpcLogEntryTransactionHash :: !TransactionHash + -- ^ 32 Bytes - hash of the transactions this log was created from. null when its pending + -- log. + , _rpcLogEntryTransactionIndex :: !TransactionIndex + -- ^ integer of the transactions index position log was created from. null when its pending + -- log. + } + deriving (Eq, Show) + +fromRpcLogEntry :: RpcLogEntry -> LogEntry +fromRpcLogEntry rpc = LogEntry + { _logEntryAddress = _rpcLogEntryAddress rpc + , _logEntryTopics = _rpcLogEntryTopics rpc + , _logEntryData = _rpcLogEntryData rpc + } + +instance ToJSON RpcLogEntry where + toEncoding = pairs . mconcat . rpcLogEntryProperties + toJSON = object . rpcLogEntryProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON RpcLogEntry where + parseJSON = withObject "RpcLogEntry" $ \o -> RpcLogEntry + <$> o .: "address" + <*> o .: "topics" + <*> o .: "data" + <*> o .: "blockHash" + <*> o .: "blockNumber" + <*> o .: "logIndex" + <*> o .: "removed" + <*> o .: "transactionHash" + <*> o .: "transactionIndex" + {-# INLINE parseJSON #-} + +rpcLogEntryProperties :: KeyValue e kv => RpcLogEntry -> [kv] +rpcLogEntryProperties r = + [ "address" .= _rpcLogEntryAddress r + , "blockHash" .= _rpcLogEntryBlockHash r + , "blockNumber" .= _rpcLogEntryBlockNumber r + , "data" .= _rpcLogEntryData r + , "logIndex" .= _rpcLogEntryLogIndex r + , "removed" .= _rpcLogEntryRemoved r + , "topics" .= _rpcLogEntryTopics r + , "transactionHash" .= _rpcLogEntryTransactionHash r + , "transactionIndex" .= _rpcLogEntryTransactionIndex r + ] +{-# INLINE rpcLogEntryProperties #-} +{-# SPECIALIZE rpcLogEntryProperties :: RpcLogEntry -> [Series] #-} +{-# SPECIALIZE rpcLogEntryProperties :: RpcLogEntry -> [Pair] #-} + +-- -------------------------------------------------------------------------- -- +-- Tx Status + +newtype TxStatus = TxStatus Natural + deriving (Show, Eq) + deriving ToJSON via (HexQuantity Natural) + deriving FromJSON via (HexQuantity Natural) + +-- | This is the instance used in RLP encodings for Receipts in the Receipt +-- Merkle tree for computing the receipt root in Consensus Headers. +-- +-- The Yellow paper doesn't specify how the tx status is encoded in the +-- RLP encoding of receipts. This encoding is derived from +-- +-- +instance RLP TxStatus where + putRlp (TxStatus 1) = putRlp @B.ByteString "\x01" + putRlp (TxStatus 0) = putRlp @B.ByteString "" + putRlp x = error $ "unsupported tx status: " <> show x + + getRlp = label "TxStatus" $ getRlp @B.ByteString >>= \case + "\x01" -> return $ TxStatus 1 + "" -> return $ TxStatus 0 + x -> fail $ "unsupported tx status: " <> show x + + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +-- -------------------------------------------------------------------------- -- +-- Transaction Type + +-- | Ethereum Transaction Types: +-- +data TransactionType + = LegacyTransaction + -- ^ Legacy Transactions. + -- + -- The RLP encoding for legacy receipts is @rlp([status, + -- cumulativeGasUsed, logsBloom, logs])@. This binary format is used as + -- value in the trie for the receipt root hash. + -- + -- cf. https://eips.ethereum.org/EIPS/eip-2718 + | Eip2930Transaction + -- ^ EIP-2930: 0x01 + -- + -- The EIP-2718 ReceiptPayload for this transaction is @rlp([status, + -- cumulativeGasUsed, logsBloom, logs])@. + -- + -- cf. https://eips.ethereum.org/EIPS/eip-2930 + | Eip1559Transaction + -- ^ EIP-1559: 0x02 + -- + -- The EIP-2718 ReceiptPayload for this transaction is @rlp([status, + -- cumulative_transaction_gas_used, logs_bloom, logs])@. + -- + -- cf. https://eips.ethereum.org/EIPS/eip-1559 + | Eip4844Transaction + -- ^ EIP-4844: 0x03 + -- + -- The EIP-2718 ReceiptPayload for this transaction is @rlp([status, + -- cumulative_transaction_gas_used, logs_bloom, logs])@. + -- + -- cf. https://eips.ethereum.org/EIPS/eip-4844 + deriving (Show, Eq, Ord, Enum, Bounded) + +instance ToJSON TransactionType where + toJSON = toJSON . HexQuantity . int @_ @Natural . fromEnum + toEncoding = toEncoding . HexQuantity . int @_ @Natural . fromEnum + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance FromJSON TransactionType where + parseJSON v = do + HexQuantity x <- parseJSON v + case (x :: Natural) of + 0x00 -> return LegacyTransaction + 0x01 -> return Eip2930Transaction + 0x02 -> return Eip1559Transaction + 0x03 -> return Eip4844Transaction + _ -> fail $ "invalid transaction type: " <> show x + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Receipt + +-- | Receipt +-- +-- The encoding that is used in the trie for computing the receipt root hash is +-- as follows: +-- +-- https://eips.ethereum.org/EIPS/eip-2718: +-- +-- @ +-- (TransactionType || ReceiptPayload) or LegacyReceipt +-- @ +-- +-- where +-- +-- - TransactionType is a positive unsigned 8-bit number between 0 and 0x7f that +-- represents the type of the transaction +-- - ReceiptPayload is an opaque byte array whose interpretation is dependent on +-- the TransactionType and defined in future EIPs +-- - LegacyReceipt is @rlp([status, cumulativeGasUsed, logsBloom, logs])@ + +-- -------------------------------------------------------------------------- -- +-- Receipt Payload + +-- | According to EIP-2718 a ReceiptPayload is an opaque byte array whose +-- interpretation is dependent on the TransactionType and defined in future +-- EIPs. +-- +-- However, up to the Cancun fork the ReceiptPayload are the same for all +-- transaction types. +-- +data ReceiptPayload = ReceiptPayload + { _receiptPayloadStatus :: !TxStatus + -- ^ Status code of the transaction + -- + -- A non-negative integer + + , _receiptPayloadGasUsed :: !GasUsed + -- ^ Gas used in block up to and including this tx. + -- + -- A non-negative integer value + + , _receiptPayloadBloom :: !Bloom + -- ^ Bloomfilter of the logs + -- + -- A 2048 bit (256 bytes) hash value + + , _receiptPayloadLogs :: ![LogEntry] + -- ^ Logs that are created during execution of the tx + -- + -- The sequence Rl is a series of log entries + } + deriving (Show, Eq) + +-- The RLP encoding for Receipt Payloads accodring to EIP-2718, EIP-2930, +-- EIP-1559 and EIP-4844. +-- +-- @ +-- rlp([status, cumulativeGasUsed, logsBloom, logs]) +-- @ +-- +-- cf. https://eips.ethereum.org/EIPS/eip-2718: +-- +instance RLP ReceiptPayload where + putRlp r = putRlpL + [ putRlp $! _receiptPayloadStatus r + , putRlp $! _receiptPayloadGasUsed r + , putRlp $! _receiptPayloadBloom r + , putRlp $! _receiptPayloadLogs r + ] + + getRlp = label "ReceiptPayload" $ getRlpL $ ReceiptPayload + <$> getRlp -- status + <*> getRlp -- gas used + <*> getRlp -- bloom + <*> getRlp -- logs + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +-- | Receipt +-- +data Receipt + = Receipt + { _receiptType :: !TransactionType + -- ^ Transaction type + -- + , _receiptPayload :: !ReceiptPayload + -- ^ As of the Cancun for receipts payloads are the same for all + -- transaction types. If this changes in the future, the Receipt + -- type should probably use different constructors for different + -- transaction types. + } + deriving (Show, Eq) + +-- | Status code of the transaction +-- +-- A non-negative integer +_receiptStatus :: Receipt -> TxStatus +_receiptStatus = _receiptPayloadStatus . _receiptPayload + +-- | Gas used in block up to and including this tx. +-- +-- A non-negative integer value +_receiptGasUsed :: Receipt -> GasUsed +_receiptGasUsed = _receiptPayloadGasUsed . _receiptPayload + +-- | Bloomfilter of the logs +-- +-- A 2048 bit (256 bytes) hash value +_receiptBloom :: Receipt -> Bloom +_receiptBloom = _receiptPayloadBloom . _receiptPayload + +-- | Logs that are created during execution of the tx +-- +-- The sequence Rl is a series of log entries +_receiptLogs :: Receipt -> [LogEntry] +_receiptLogs = _receiptPayloadLogs . _receiptPayload + +-- | RLP encodings of receipts are used inconsistently in different contexts +-- based on the transaction type. +-- +instance RLP Receipt where + putRlp r = case _receiptType r of + LegacyTransaction -> putRlp (_receiptPayload r) + t -> putRlp + $ B.singleton (int $ fromEnum t) + <> putByteString (putRlp (_receiptPayload r)) + + -- This is sound because the RLP encodings for a list (legacy txs) and + -- a bytestring (EIP-2718 txs) differe at the first byte. This also means + -- the first branch of the alternative fails fast. + getRlp = label "Receipt" $ getLegacyTx <|> getTx + where + getLegacyTx = label "LegacyTransaction" $ Receipt + <$> pure LegacyTransaction + <*> getRlp @ReceiptPayload + getTx = do + b <- getRlp @B.ByteString + case B.uncons b of + Just (0x01, r) -> label "Eip2930Transaction" $ Receipt + <$> pure Eip2930Transaction + <*> case get (getRlp @ReceiptPayload) r of + Left e -> fail $ "Eip2930Transaction: " <> show e + Right r' -> return r' + Just (0x02, r) -> label "Eip1559Transaction" $ Receipt + <$> pure Eip1559Transaction + <*> case get (getRlp @ReceiptPayload) r of + Left e -> fail $ "Eip2930Transaction: " <> show e + Right r' -> return r' + Just (0x03, r) -> label "Eip4844Transaction" $ Receipt + <$> pure Eip4844Transaction + <*> case get (getRlp @ReceiptPayload) r of + Left e -> fail $ "Eip2930Transaction: " <> show e + Right r' -> return r' + Just (x, _) -> fail $ + "invalid transaction type: expected 0x01, 0x02 or 0x03, but got: " <> sshow x + Nothing -> fail "invalid receipt encoding" + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +encodeReceipt :: Receipt -> B.ByteString +encodeReceipt r = case _receiptType r of + LegacyTransaction -> putByteString $ putRlp (_receiptPayload r) + t -> B.singleton (int $ fromEnum t) <> putByteString (putRlp (_receiptPayload r)) + +-- instance ToJSON Receipt where +-- toEncoding = pairs . mconcat . receiptProperties +-- toJSON = object . receiptProperties +-- {-# INLINE toEncoding #-} +-- {-# INLINE toJSON #-} +-- +-- instance FromJSON Receipt where +-- parseJSON = withObject "Receipt" $ \o -> Receipt +-- <$> o .: "status" +-- <*> o .: "cumulativeGasUsed" +-- <*> o .: "bloom" +-- <*> o .: "logs" +-- {-# INLINE parseJSON #-} +-- +-- receiptProperties :: KeyValue e kv => Receipt -> [kv] +-- receiptProperties o = +-- [ "status" .= _receiptStatus o +-- , "cumulativeGasUsed" .= _receiptGasUsed o +-- , "bloom" .= _receiptBloom o +-- , "logs" .= _receiptLogs o +-- ] +-- {-# INLINE receiptProperties #-} +-- {-# SPECIALIZE receiptProperties :: Receipt -> [Series] #-} +-- {-# SPECIALIZE receiptProperties :: Receipt -> [Pair] #-} + +deriving via (RlpMerkleLogEntry 'EthReceiptsRootTag ReceiptsRoot) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ReceiptsRoot + +deriving via (RlpMerkleLogEntry 'EthReceiptTag Receipt) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Receipt + +-- -------------------------------------------------------------------------- -- +-- JSON RPC API Receipts + +data RpcReceipt = RpcReceipt + { _rpcReceiptType :: !TransactionType + -- ^ Transaction type + , _rpcReceiptGasUsed :: !GasUsed + -- ^ the amount of gas used by this specific transaction alone. + , _rpcReceiptBloom :: !Bloom + -- ^ 256 Bytes - Bloom filter for light clients to quickly retrieve related logs. + , _rpcReceiptLogs :: ![RpcLogEntry] + -- ^ Array - Array of log objects, which this transaction generated. + , _rpcReceiptStatus :: !TxStatus + -- ^ Status code of the transaction, either 1 (success) or 0 (failure) + -- + -- For pre Byzantium this is "root", 32 bytes of post-transaction stateroot + + , _rpcReceiptBlockHash :: !BlockHash + -- ^ 32 Bytes - hash of the block where this transaction was in. + , _rpcReceiptBlockNumber :: !BlockNumber + -- ^ block number where this transaction was in. + , _rpcReceiptContractAddress :: !(Maybe Address) + -- ^ 20 Bytes - the contract address created, if the transaction was a contract creation, otherwise - null. + , _rpcReceiptCumulativeGasUsed :: !GasUsed + -- ^ the total amount of gas used when this transaction was executed in the block. + , _rpcReceiptFrom :: !Address + -- ^ 20 Bytes - address of the sender. + , _rpcReceiptTo :: !(Maybe Address) + -- ^ 20 Bytes - address of the receiver. Null when the transaction is a contract creation transaction. + , _rpcReceiptTransactionHash :: !TransactionHash + -- ^ 32 Bytes - hash of the transaction. + , _rpcReceiptTransactionIndex :: !TransactionIndex + -- ^ integer of the transactions index position in the block. + } + deriving (Eq, Show) + +fromRpcReceipt :: RpcReceipt -> Receipt +fromRpcReceipt rpc = Receipt + { _receiptType = _rpcReceiptType rpc + , _receiptPayload = ReceiptPayload + { _receiptPayloadStatus = _rpcReceiptStatus rpc + -- , _receiptPayloadGasUsed = _rpcReceiptGasUsed rpc + , _receiptPayloadGasUsed = _rpcReceiptCumulativeGasUsed rpc + -- this comes as a surprise, but seems to be required for computing the correct + -- receipt root in the consensus header. + , _receiptPayloadBloom = _rpcReceiptBloom rpc + , _receiptPayloadLogs = fromRpcLogEntry <$> _rpcReceiptLogs rpc + } + } + +instance ToJSON RpcReceipt where + toEncoding = pairs . mconcat . rpcReceiptProperties + toJSON = object . rpcReceiptProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON RpcReceipt where + parseJSON = withObject "RpcReceipt" $ \o -> RpcReceipt + <$> (o .:? "type" .!= LegacyTransaction) + <*> o .: "gasUsed" + <*> o .: "logsBloom" + <*> o .: "logs" + <*> o .: "status" + <*> o .: "blockHash" + <*> o .: "blockNumber" + <*> o .: "contractAddress" + <*> o .: "cumulativeGasUsed" + <*> o .: "from" + <*> o .: "to" + <*> o .: "transactionHash" + <*> o .: "transactionIndex" + {-# INLINE parseJSON #-} + +rpcReceiptProperties :: KeyValue e kv => RpcReceipt -> [kv] +rpcReceiptProperties r = + [ "type" .= _rpcReceiptType r + , "blockHash" .= _rpcReceiptBlockHash r + , "blockNumber" .= _rpcReceiptBlockNumber r + , "contractAddress" .= _rpcReceiptContractAddress r + , "cumulativeGasUsed" .= _rpcReceiptCumulativeGasUsed r + , "from" .= _rpcReceiptFrom r + , "gasUsed" .= _rpcReceiptGasUsed r + , "logs" .= _rpcReceiptLogs r + , "logsBloom" .= _rpcReceiptBloom r + , "status" .= _rpcReceiptStatus r + , "to" .= _rpcReceiptTo r + , "transactionHash" .= _rpcReceiptTransactionHash r + , "transactionIndex" .= _rpcReceiptTransactionIndex r + ] +{-# INLINE rpcReceiptProperties #-} +{-# SPECIALIZE rpcReceiptProperties :: RpcReceipt -> [Series] #-} +{-# SPECIALIZE rpcReceiptProperties :: RpcReceipt -> [Pair] #-} + +-- -------------------------------------------------------------------------- -- +-- + +rpcReceiptTrieProof + :: [RpcReceipt] + -> TransactionIndex + -> Proof +rpcReceiptTrieProof rs = receiptTrieProof kv + where + kv = (\x -> (_rpcReceiptTransactionIndex x, fromRpcReceipt x)) <$> rs + +receiptTrieProof + :: [(TransactionIndex, Receipt)] + -- ^ block receipts + -> TransactionIndex + -> Proof +receiptTrieProof receipts idx = createProof kv (putRlpByteString idx) + where + kv = bimap putRlpByteString encodeReceipt <$> receipts diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs index 944b0a9ee8..11b0a0c0ec 100644 --- a/src/Chainweb/PayloadProvider/EVM/Utils.hs +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -15,6 +15,10 @@ {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TypeFamilies #-} -- | -- Module: Chainweb.PayloadProvider.EVM.Utils @@ -39,6 +43,9 @@ module Chainweb.PayloadProvider.EVM.Utils , nullBlockHash , decodeRlpM , dropN + +-- * Merkle Log Entries for EVM Types +, RlpMerkleLogEntry(..) ) where import Chainweb.BlockHash qualified as Chainweb @@ -55,7 +62,7 @@ import Data.Text qualified as T import Data.Text.Encoding qualified as T import Data.Text.Read qualified as T import Ethereum.Misc qualified as E -import Ethereum.RLP (RLP, get, getRlp) +import Ethereum.RLP (RLP, get, getRlp, putRlpByteString) import Ethereum.Receipt import Ethereum.Transaction (Wei (..)) import Ethereum.Utils hiding (int, natVal_) @@ -63,6 +70,9 @@ import Foreign.Storable (Storable) import GHC.Generics (Generic) import GHC.TypeLits import Text.Printf +import Chainweb.MerkleUniverse +import Chainweb.Crypto.MerkleLog +import Data.MerkleLog (MerkleNodeType(..)) -- -------------------------------------------------------------------------- -- -- Utils (should be moved to the ethereum package) @@ -278,3 +288,22 @@ instance HasTextRepresentation DefaultBlockParameter where x -> DefaultBlockNumber . E.BlockNumber . fromHexQuanity <$> fromText x {-# INLINE fromText #-} +-- -------------------------------------------------------------------------- -- + +newtype RlpMerkleLogEntry (tag :: ChainwebHashTag) t = RlpMerkleLogEntry t + deriving newtype RLP + +instance + ( KnownNat (MerkleTagVal ChainwebHashTag tag) + , MerkleHashAlgorithm a + , RLP t + ) + => IsMerkleLogEntry a ChainwebHashTag (RlpMerkleLogEntry tag t) + where + type Tag (RlpMerkleLogEntry tag t) = tag + toMerkleNode = InputNode . putRlpByteString + fromMerkleNode (InputNode bs) = case get getRlp bs of + Left e -> throwM $ MerkleLogDecodeException (T.pack e) + Right x -> Right x + fromMerkleNode (TreeNode _) = throwM expectedInputNodeException + diff --git a/src/Chainweb/PayloadProvider/Initialization.hs b/src/Chainweb/PayloadProvider/Initialization.hs index d779d6941f..f2a7a2fef3 100644 --- a/src/Chainweb/PayloadProvider/Initialization.hs +++ b/src/Chainweb/PayloadProvider/Initialization.hs @@ -11,11 +11,9 @@ module Chainweb.PayloadProvider.Initialization ( Resources(..) ) where -import Chainweb.BlockHeaderDB import Chainweb.ChainId import Chainweb.Version import Chainweb.Payload.PayloadStore -import Chainweb.PayloadProvider import Chainweb.Pact.Types (MemPoolAccess) import Data.Text qualified as T diff --git a/src/Chainweb/PayloadProvider/Minimal/Payload.hs b/src/Chainweb/PayloadProvider/Minimal/Payload.hs index cba7a94b61..30dff5cebb 100644 --- a/src/Chainweb/PayloadProvider/Minimal/Payload.hs +++ b/src/Chainweb/PayloadProvider/Minimal/Payload.hs @@ -63,7 +63,7 @@ import Data.Aeson.Types (Pair) import Data.ByteString.Short qualified as BS import Data.Function import Data.Hashable -import Data.MerkleLog.V1 qualified as V1 (MerkleProof, runMerkleProof) +import Data.MerkleLog qualified as V2 (MerkleProof, runProof) import Data.Void import Data.Word import GHC.Generics (Generic) @@ -234,18 +234,15 @@ proof => a ~ ChainwebMerkleHashAlgorithm -- => HasHeader a ChainwebHashTag c (MkLogType a ChainwebHashTag Payload) => Payload - -> m (V1.MerkleProof a) -proof = headerProof @Payload + -> m (V2.MerkleProof a) +proof = headerProofV2 @Payload {-# INLINE proof #-} -- | Runs a proof. Returns the BlockPayloadHash of the payload for which -- inclusion is proven. -- -runProof - :: MonadThrow m - => V1.MerkleProof ChainwebMerkleHashAlgorithm - -> m BlockPayloadHash -runProof p = BlockPayloadHash . MerkleLogHash <$> V1.runMerkleProof p +runProof :: V2.MerkleProof ChainwebMerkleHashAlgorithm -> BlockPayloadHash +runProof = BlockPayloadHash . MerkleLogHash . V2.runProof {-# INLINE runProof #-} -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/PayloadProvider/SPV.hs b/src/Chainweb/PayloadProvider/SPV.hs index 40d96da3b4..98afb57ba7 100644 --- a/src/Chainweb/PayloadProvider/SPV.hs +++ b/src/Chainweb/PayloadProvider/SPV.hs @@ -1,24 +1,30 @@ -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BlockArguments #-} {-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} -{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE KindSignatures #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PolyKinds #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE StandaloneDeriving #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# OPTIONS_GHC -Wno-orphans #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE StandaloneKindSignatures #-} {-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} + +{-# OPTIONS_GHC -Wno-orphans #-} -- | -- Module: Chainweb.PayloadProvider.SPV @@ -29,43 +35,36 @@ -- module Chainweb.PayloadProvider.SPV ( Argument(..) -, runProof +, SomeArgument(..) +, runArg +, compose ) where -import Chainweb.BlockHeight +import Chainweb.BlockHash import Chainweb.BlockPayloadHash -import Chainweb.ChainId -import Chainweb.ChainId import Chainweb.Crypto.MerkleLog import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse -import Chainweb.MinerReward import Chainweb.Payload -import Chainweb.PayloadProvider -import Chainweb.PayloadProvider.EVM.EthRpcAPI qualified as RPC -import Chainweb.PayloadProvider.EVM.JsonRPC -import Chainweb.PayloadProvider.EVM.JsonRPC qualified as RPC -import Chainweb.SPV -import Chainweb.Utils -import Chainweb.Utils.Serialization -import Chainweb.Version +import Chainweb.PayloadProvider.EVM.Header ({- IsMerkleLogEntry instances -}) +import Chainweb.PayloadProvider.EVM.Receipt qualified as EVM import Control.Exception import Control.Monad -import Control.Monad.Catch qualified as C +import Control.Monad.Catch import Data.ByteString qualified as B -import Data.ByteString.Short qualified as BS +import Data.Coerce import Data.Kind +import Data.MerkleLog (MerkleRoot) import Data.MerkleLog qualified as ML +import Data.MerkleLog.V1 qualified as V1 import Data.Text qualified as T +import Data.Type.Equality +import Data.Typeable import Data.Word -import Ethereum.Misc import Ethereum.Misc qualified as E +import Ethereum.RLP (getRlpL) import Ethereum.RLP qualified as E -import Ethereum.Receipt qualified as E -import Ethereum.Receipt.ReceiptProof qualified as E import Ethereum.Trie qualified as E -import GHC.Generics -import GHC.TypeNats import Numeric.Natural -- -------------------------------------------------------------------------- -- @@ -106,6 +105,18 @@ import Numeric.Natural -- * amount [uint256] -- -------------------------------------------------------------------------- -- +-- +-- Backward compatibility: +-- +-- * Proof format: +-- * Existing proof *must* continue to work during reorg and the transition. +-- * Can we skip proof verification during replay? +-- +-- Before EVM transition: +-- 1. Introduce support for validating new format +-- 2. Enable creation of new format. +-- +-- -- -------------------------------------------------------------------------- -- -- TODO: @@ -122,90 +133,34 @@ import Numeric.Natural -- Chainweb Merkle universe. -- -------------------------------------------------------------------------- -- --- TagUsage - -data TagType - = LeafTag - | InnerTag - -type family GetTagType (t :: ChainwebHashTag) :: TagType where - - -- GetTagType VoidTag = 'LeafTag - -- GetTagType MerkleRootTag = 'InnerTag - - -- BlockHeader - GetTagType ChainIdTag = 'LeafTag - GetTagType BlockHeightTag = 'LeafTag - GetTagType BlockWeightTag = 'LeafTag - GetTagType BlockPayloadHashTag = 'LeafTag - GetTagType BlockNonceTag = 'LeafTag - GetTagType BlockCreationTimeTag = 'LeafTag - GetTagType ChainwebVersionTag = 'LeafTag - GetTagType PowHashTag = 'LeafTag - GetTagType BlockHashTag = 'InnerTag - GetTagType HashTargetTag = 'LeafTag - GetTagType EpochStartTimeTag = 'LeafTag - GetTagType FeatureFlagsTag = 'LeafTag - - -- Pact Payloads - GetTagType TransactionTag = 'LeafTag - GetTagType TransactionOutputTag = 'LeafTag - GetTagType BlockTransactionsHashTag = 'InnerTag - GetTagType BlockOutputsHashTag = 'InnerTag - GetTagType MinerDataTag = 'LeafTag - GetTagType CoinbaseOutputTag = 'LeafTag - - -- Pact Event (not Currently Chainweb Merkle Tree) - GetTagType OutputEventsTag = 'InnerTag - GetTagType BlockEventsHashTag = 'InnerTag - GetTagType RequestKeyTag = 'LeafTag - GetTagType PactEventTag = 'LeafTag - - -- Minimal Payload Provider - GetTagType MinimalPayloadTag = 'LeafTag - - -- EVM Payload Provider - GetTagType EthParentHashTag = 'LeafTag - GetTagType EthOmmersHashTag = 'LeafTag - GetTagType EthBeneficiaryTag = 'LeafTag - GetTagType EthStateRootTag = 'LeafTag - GetTagType EthTransactionsRootTag = 'LeafTag - GetTagType EthReceiptsRootTag = 'LeafTag - GetTagType EthBloomTag = 'LeafTag - GetTagType EthDifficultyTag = 'LeafTag - GetTagType EthBlockNumberTag = 'LeafTag - GetTagType EthGasLimitTag = 'LeafTag - GetTagType EthGasUsedTag = 'LeafTag - GetTagType EthTimestampTag = 'LeafTag - GetTagType EthExtraDataTag = 'LeafTag - GetTagType EthRandaoTag = 'LeafTag - GetTagType EthNonceTag = 'LeafTag - GetTagType EthBaseFeePerGasTag = 'LeafTag - GetTagType EthWithdrawalsRootTag = 'LeafTag - GetTagType EthBlobGasUsedTag = 'LeafTag - GetTagType EthExcessBlobGasTag = 'LeafTag - GetTagType EthParentBeaconBlockRootTag = 'LeafTag + +type ChainwebMerkleLogEntry a = + ( Eq a + , Typeable a + , Show a + , IsMerkleLogEntry ChainwebMerkleHashAlgorithm ChainwebHashTag a + ) + +tagVal' :: forall b . ChainwebMerkleLogEntry b => Word16 +tagVal' = tagVal @ChainwebHashTag @(Tag b) -- -------------------------------------------------------------------------- -- Claims data Conjunct a b where - Conjunct :: (InclusionClaim a, InclusionClaim b) => a -> b -> Conjunct a b + Conjunct + :: (ChainwebMerkleLogEntry a, ChainwebMerkleLogEntry b) + => a + -> b + -> Conjunct a b + +deriving instance (Show a, Show b) => Show (Conjunct a b) +deriving instance (Eq a, Eq b) => Eq (Conjunct a b) class InclusionClaim a -instance {-# OVERLAPPABLE #-} - (IsMerkleLogEntry ChainwebMerkleHashAlgorithm ChainwebHashTag a) - => InclusionClaim a +instance {-# OVERLAPPABLE #-} (ChainwebMerkleLogEntry a) => InclusionClaim a instance {-# OVERLAPPING #-} InclusionClaim (Conjunct a b) --- class ClaimType a --- instance {-# OVERLAPPABLE #-} (ChainwebMerkleEntry a, GetTagType (Tag a) ~ 'LeafTag) => ClaimType a --- instance {-# OVERLAPPING #-} (ClaimType a, ClaimType b) => ClaimType (a, b) --- --- class RootType a --- instance {-# OVERLAPPABLE #-} (ChainwebMerkleEntry a, GetTagType (Tag a) ~ 'InnerTag) => RootType a --- instance {-# OVERLAPPING #-} (RootType a, RootType b) => RootType (a, b) - -- -------------------------------------------------------------------------- -- -- Exceptions @@ -214,6 +169,469 @@ newtype VerificationException = VerificationException T.Text instance Exception VerificationException +-- -------------------------------------------------------------------------- -- + +-- TODO: replace concrete types by Tags? +-- +-- The types themself are not available to all payload providers, but the tags +-- are. +-- +-- TODO do we need extensible arguments, e.g. as a data family or just class? +-- +-- For now we try to the set of supported Proofs and claim types small and +-- fixed. +-- +-- The claim types are included in the Chainweb core library to which all +-- payload providers have access. Proof verification validates the evidence and +-- also deserializes the claim, which inlcudes verification of the tag. + +-- | Arguments +-- +-- An argument provides evidence for the statement: +-- +-- \(\text{root} \in \text{Chainweb} \rightarrow \text{claim} \in \text{Chainweb}\) +-- +-- where `claim` is a value that is stored on chain and has a type that is a +-- member of the Chainweb Merkle universe. +-- +-- This statement is established by applying the argument to the claim, which +-- may succeed or fail. +-- +data Argument (claim :: Type) (root :: Type) where + Trivial + :: + ( ChainwebMerkleLogEntry a + , Coercible a (MerkleRoot ChainwebMerkleHashAlgorithm) + ) + => a + -> Argument a a + PactLegacyProof + :: V1.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument TransactionOutput BlockHash + HeaderArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument BlockHash BlockHash + ParentHeaderArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument BlockHash BlockHash + BlockPayloadHashArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument BlockPayloadHash BlockHash + PactOutputArgument + :: + ( ChainwebMerkleLogEntry root + , Coercible root (MerkleRoot ChainwebMerkleHashAlgorithm) + ) + => ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument TransactionOutput root + EthHeaderArgument + :: + ( ChainwebMerkleLogEntry root + , Coercible root (MerkleRoot ChainwebMerkleHashAlgorithm) + ) + => ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument E.ReceiptsRoot root + EthReceiptArgument + :: E.Proof + -> Argument EVM.Receipt E.ReceiptsRoot + + -- Composed Arguments (used when the underlying proofs can't be composed) + ComposeArgument + :: + ( ChainwebMerkleLogEntry a + , ChainwebMerkleLogEntry claim + , ChainwebMerkleLogEntry root + ) + => Argument claim a + -> Argument a root + -> Argument claim root + + -- Future work: + -- + -- Conjunect a0 a1 is not a MerkleLogEntry. Hence we need create an + -- abstraction that that covers both MerkleLogEntries and Conjunctions of + -- MerkleLogEntries. + -- + -- ComposeArgument2 + -- :: + -- ( ChainwebMerkleLogEntry a0 + -- , ChainwebMerkleLogEntry a1 + -- , ChainwebMerkleLogEntry root + -- , ChainwebMerkleLogEntry claim0 + -- , ChainwebMerkleLogEntry claim1 + -- ) + -- => Argument claim0 a0 + -- -> Argument claim1 a1 + -- -> Argument (Conjunct a0 a1) root + -- -> Argument (Conjunct claim0 claim1) root + +deriving instance (Show claim, Show root) => Show (Argument claim root) + +argumentClaim :: MonadThrow m => Argument claim root -> m claim +argumentClaim (Trivial r) = return r +argumentClaim (PactLegacyProof p) = fromMerkleNodeM + $ V1._getMerkleProofSubject + $ V1._merkleProofSubject p +argumentClaim (HeaderArgument p) = fromMerkleNodeM $ ML._merkleProofClaim p +argumentClaim (ParentHeaderArgument p) = fromMerkleNodeM $ ML._merkleProofClaim p +argumentClaim (BlockPayloadHashArgument p) = fromMerkleNodeM $ ML._merkleProofClaim p +argumentClaim (PactOutputArgument p) = fromMerkleNodeM $ ML._merkleProofClaim p +argumentClaim (EthHeaderArgument p) = fromMerkleNodeM $ ML._merkleProofClaim p +argumentClaim (EthReceiptArgument p) = case E._proofValue p of + Nothing -> throwM $ VerificationException "Invalid Receipt proof: missing claim" + Just r -> case E.get E.getRlp r of + Left e -> throwM $ VerificationException $ "Invalid receipt: " <> T.pack e + Right r' -> return r' +argumentClaim (ComposeArgument a0 _) = argumentClaim a0 +-- argumentClaim (ComposeArgument2 a0 a1 _) = Conjunct +-- <$> argumentClaim a0 +-- <*> argumentClaim a1 + +-- -------------------------------------------------------------------------- -- +-- Serialization + +data SomeArgument where + SomeArgument + :: forall (claim :: Type) (root :: Type) + . (ChainwebMerkleLogEntry claim, ChainwebMerkleLogEntry root) + => Argument claim root + -> SomeArgument + +instance MerkleHashAlgorithm a => E.RLP (MerkleRoot a) where + putRlp r = E.putRlp $ ML.encodeMerkleRoot r + getRlp = E.label "MerkleRoot " $ do + bs <- E.getRlp @B.ByteString + case ML.decodeMerkleRoot bs of + Left e -> fail $ "Invalid MerkleRoot: " <> show e + Right r -> return r + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance MerkleHashAlgorithm a => E.RLP (ML.MerkleNodeType a) where + putRlp (ML.TreeNode h) = E.putRlpL + [ E.putRlp (0 :: Word8) + , E.putRlp h + ] + putRlp (ML.InputNode b) = E.putRlpL + [ E.putRlp (1 :: Word8) + , E.putRlp b + ] + getRlp = E.label "MerkleNodeType" $ do + E.getRlp @Word8 >>= \case + 0 -> E.label "TreeNode" (ML.TreeNode <$> E.getRlp) + 1 -> E.label "InputNode" (ML.InputNode <$> E.getRlp) + _ -> fail "invalid MerkleNodeType" + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance MerkleHashAlgorithm a => E.RLP (V1.MerkleProof a) where + putRlp p = E.putRlpL + [ E.putRlp $ V1._getMerkleProofSubject (V1._merkleProofSubject p) + , E.putRlp $ V1.encodeMerkleProofObject (V1._merkleProofObject p) + ] + getRlp = E.label "V1.MerkleProof" $ V1.MerkleProof + <$> E.label "MerkleProofSubject" (V1.MerkleProofSubject <$> E.getRlp) + <*> E.label "MerkleProofObject" do + bs <- E.getRlp @B.ByteString + case V1.decodeMerkleProofObject bs of + Left e -> fail $ "Invalid MerkleProofObject: " <> show e + Right r -> return r + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance E.RLP (ML.MerkleProof ChainwebMerkleHashAlgorithm) where + putRlp p = E.putRlpL + [ E.putRlp (ML._merkleProofClaim p) + , E.putRlp (fromIntegral @_ @Natural $ ML._merkleProofTrace p) + , E.putRlp (ML._merkleProofEvidence p) + ] + getRlp = E.label "MerkleProof" $ + ML.MerkleProof + <$> E.label "claim" E.getRlp + <*> E.label "trace" (fromIntegral @Natural <$> E.getRlp) + <*> E.label "evidence" E.getRlp + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance MerkleHashAlgorithm a => E.RLP (BlockPayloadHash_ a) where + putRlp = E.putRlp . coerce @_ @(MerkleRoot a) + getRlp = E.label "BlockPayloadHash" $ coerce <$> E.getRlp @(MerkleRoot a) + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance MerkleHashAlgorithm a => E.RLP (BlockHash_ a) where + putRlp = E.putRlp . coerce @_ @(MerkleRoot a) + getRlp = E.label "BlockHash" $ coerce <$> E.getRlp @(MerkleRoot a) + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance E.RLP SomeArgument where + putRlp (SomeArgument @claim @root arg) = case arg of + (Trivial p) -> go 0 (coerce @_ @(MerkleRoot ChainwebMerkleHashAlgorithm) p) + (PactLegacyProof p) -> go 1 p + (HeaderArgument p) -> go 2 p + (ParentHeaderArgument p) -> go 3 p + (BlockPayloadHashArgument p) -> go 4 p + (PactOutputArgument p) -> go 5 p + (EthHeaderArgument p) -> go 6 p + (EthReceiptArgument p) -> go 7 p + (ComposeArgument a0 a1) -> E.putRlpL + [ E.putRlp (8 :: Word8) + , E.putRlp (SomeArgument a0) + , E.putRlp (SomeArgument a1) + ] + where + go :: E.RLP p => Word8 -> p -> E.Put + go tag p = E.putRlpL + [ E.putRlp (tag :: Word8) + , E.putRlp (tagVal' @claim) + , E.putRlp (tagVal' @root) + , E.putRlp p + ] + + getRlp = E.label "Argument" $ getRlpL $ do + tag <- E.label "Argument Tag" (E.getRlp @Word8) + claim <- E.label "Claim Tag" $ E.getRlp @Word16 + root <- E.label "Root Tag" $ E.getRlp @Word16 + case tag of + 0 -> E.label "Trivial" $ if + | root == tagVal' @BlockPayloadHash -> SomeArgument <$> + enforceType claim root (Trivial @BlockPayloadHash <$> E.getRlp) + | root == tagVal' @BlockHash -> SomeArgument <$> + enforceType claim root (Trivial @BlockHash <$> E.getRlp) + | otherwise -> + fail "invalid root type" + 1 -> E.label "PactLegacyProof" $ SomeArgument <$> + getTagged (PactLegacyProof <$> E.getRlp) + 2 -> E.label "HeaderArgument" $ SomeArgument <$> + getTagged (HeaderArgument <$> E.getRlp) + 3 -> E.label "ParentHeaderArgument" $ SomeArgument <$> + getTagged (ParentHeaderArgument <$> E.getRlp) + 4 -> E.label "BlockPayloadHashArgument" $ SomeArgument <$> + getTagged (BlockPayloadHashArgument <$> E.getRlp) + 5 -> E.label "PactOutputArgument" $ if + | root == tagVal' @BlockPayloadHash -> SomeArgument <$> + enforceType claim root (PactOutputArgument @BlockPayloadHash <$> E.getRlp) + | root == tagVal' @BlockHash -> SomeArgument <$> + enforceType claim root (PactOutputArgument @BlockHash <$> E.getRlp) + | otherwise -> + fail "invalid root type" + 6 -> E.label "EthHeaderArgument" $ if + | root == tagVal' @BlockPayloadHash -> SomeArgument <$> + enforceType claim root (EthHeaderArgument @BlockPayloadHash <$> E.getRlp) + | root == tagVal' @BlockHash -> SomeArgument <$> + enforceType claim root (EthHeaderArgument @BlockHash <$> E.getRlp) + | otherwise -> + fail "invalid root type" + 7 -> E.label "EthReceiptArgument" $ SomeArgument <$> + getTagged (EthReceiptArgument <$> E.getRlp) + 8 -> E.label "ComposeArgument" $ do + SomeArgument @c @a a0 <- E.getRlp + SomeArgument @a' @r a1 <- E.getRlp + arg <- getTagged @c @r $ case (eqT @a @a') of + Just Refl -> return $ ComposeArgument a0 a1 + _ -> fail "invalid compose argument" + return $ SomeArgument arg + _ -> fail "invalid argument type" + where + getTagged + :: forall claim root + . ChainwebMerkleLogEntry claim + => ChainwebMerkleLogEntry root + => E.Get (Argument claim root) + -> E.Get (Argument claim root) + getTagged p = do + claim <- E.label "Claim Tag" $ E.getRlp @Word16 + root <- E.label "Root Tag" $ E.getRlp @Word16 + enforceType claim root p + + enforceType + :: forall claim root + . ChainwebMerkleLogEntry claim + => ChainwebMerkleLogEntry root + => Word16 + -> Word16 + -> E.Get (Argument claim root) + -> E.Get (Argument claim root) + enforceType claim root p = do + unless (claim == tagVal' @claim) (fail "invalid claim type") + unless (root == tagVal' @root) (fail "invalid root type") + p + +-- -------------------------------------------------------------------------- -- +-- | Argument composition. +-- +-- The implementation balances the composition tree to be right associative. +-- This maximizes the composition on the lower level proofs. +-- +-- Nested calls to this can be quadratic in the worst case when associating from +-- the left. So one should avoid that. Instead arguments should be composed from +-- the right. +-- +compose + :: MonadThrow m + => ChainwebMerkleLogEntry a + => ChainwebMerkleLogEntry claim + => ChainwebMerkleLogEntry root + => Argument claim a + -> Argument a root + -> m (Argument claim root) +compose (Trivial _) a = return a -- FXME: check that claims match? +compose a (Trivial _) = return a -- FIXME: check that roots match? +compose (HeaderArgument m0) (HeaderArgument m1) + = HeaderArgument <$> ML.composeProofs m0 m1 +compose (ParentHeaderArgument m0) (HeaderArgument m1) + = ParentHeaderArgument <$> ML.composeProofs m0 m1 +compose (BlockPayloadHashArgument m0) (HeaderArgument m1) + = BlockPayloadHashArgument <$> ML.composeProofs m0 m1 +compose (PactOutputArgument m0) (BlockPayloadHashArgument m1) + = PactOutputArgument <$> ML.composeProofs m0 m1 +compose (PactOutputArgument m0) (ParentHeaderArgument m1) + = PactOutputArgument <$> ML.composeProofs m0 m1 +compose (EthHeaderArgument m0) (BlockPayloadHashArgument m1) + = EthHeaderArgument <$> ML.composeProofs m0 m1 +compose (ComposeArgument a0 a1) b = ComposeArgument a0 <$> compose a1 b +compose a (ComposeArgument b0 b1) = do + -- First compose a and b0. This may result in a ComposeArgument in which + -- case we and up with the previous case. + a' <- compose a b0 + compose a' b1 +compose a b = return (ComposeArgument a b) + + +-- -------------------------------------------------------------------------- -- +-- | Compute the proof root and provide evidence that the proof claim is +-- included in the root of the respective Chainweb Merkle tree. +-- +-- The claim is either a leave and a member of the Chainweb Merkle universe or +-- it is an inner node of the Chainweb Merkle tree. In case of the latter no +-- futher evidence about its type is included. +-- +-- IMPORTANT NOTE: +-- +-- If the result is an inner tree node (e.g. a 'BlockHash' or a +-- 'BlockPayloadHash') type is not checked and purely informational. There is no +-- evidence for it being correct. (we could add that evidence in the future, but +-- right now it is not provided.) +-- +-- It is an important security invariant of Chainweb that the preimage of each +-- root is completely guarded by types values in the Chainweb Merkle universe. +-- +runArg + :: MonadThrow m + => Argument claim root + -> m root +runArg (Trivial r) = return r +runArg (PactLegacyProof evidence) = do + r <- V1.runMerkleProof evidence + return (coerce r) +runArg (PactOutputArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (ParentHeaderArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (HeaderArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (BlockPayloadHashArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (EthReceiptArgument evidence) = do + -- We do not need to check the proof key because all values under this + -- root are receipts. However, we must make sure that the root has the + -- correct type, which is done either when checked against an oracle or when + -- it is used as claim in another proof. + r <- E.ReceiptsRoot <$> validateTrieProof evidence + return r +runArg (EthHeaderArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (ComposeArgument a0 a1) = do + r0 <- runArg a0 + c1 <- argumentClaim a1 + when (r0 /= c1) $ throwM $ VerificationException "Composition failed" + runArg a1 + +-- runProof (Trivial r) = do +-- return r +-- runProof (ComposeArgument2 a0 a1 a3) (Conjunct c0 c1) = do +-- r0 <- runProof a0 c0 +-- r1 <- runProof a1 c1 +-- runProof a3 (Conjunct r0 r1) + +-- -------------------------------------------------------------------------- -- +-- | Ethereum Trie Proofs +-- + +validateTrieProof + :: MonadThrow m + => E.Proof + -> m E.Keccak256Hash +validateTrieProof p = case E.validateProof p of + Left _ -> + throwM (VerificationException "Malformed Proof") + Right False -> + throwM (VerificationException "Invalid Proof") + Right True -> + return $ E._proofRoot p + +-- -------------------------------------------------------------------------- -- +-- ATTIC + +-- tests + +-- Pact Output Proof script: +-- +-- import Chainweb.PayloadProvider.Pact.Genesis +-- import Chainweb.Version.Mainnet01 +-- import Chainweb.Utils +-- import Chainweb.Crypto.MerkleLog +-- import Chainweb.MerkleUniverse +-- import Chainweb.Payload +-- import Chainweb.BlockHeader +-- import Data.MerkleLog qualified as ML +-- import Control.Lens +-- script :: IO () +-- script = do +-- let plds = Chainweb.PayloadProvider.Pact.Genesis.genesisPayload Mainnet01 +-- pld0 <- preview (ixg $ unsafeChainId 0) plds +-- let (_, outs) = payloadWithOutputsToBlockObjects pld0 +-- p0 <- bodyProofV2 @ChainwebMerkleHashAlgorithm outs 0 +-- let bpld0 = payloadDataToBlockPayload (payloadWithOutputsToPayloadData pld0) +-- x <- headerProofV2 @BlockOutputsHash @ChainwebMerkleHashAlgorithm bpld0 +-- p <- ML.composeProofs p0 x +-- ML.runProof p +-- let poarg = PactOutputArgument @BlockPayloadHash p +-- runArg poarg +-- +-- let gh = genesisBlockHeader Mainnet01 (unsafeChainId 0) +-- hp <- headerProofV2 @BlockPayloadHash @ChainwebMerkleHashAlgorithm gh +-- ML.runProof hp +-- let harg = BlockPayloadHashArgument hp +-- runArg harg +-- +-- arg <- compose poarg harg +-- runArg arg + +-- -------------------------------------------------------------------------- -- +-- | Pact Output Proof + +-- instance E.RLP (ML.MerkleProof a) where +-- putRlp p = E.putRlp p +-- getRlp = E.label "MerkleProof" $ MerkleProof <$> E.getRlp +-- {-# INLINE putRlp #-} +-- {-# INLINE getRlp #-} + +-- createMerkleProof +-- :: [(B.ByteString, B.ByteString)] +-- -- ^ Key-value pairs that are stroed in the Trie +-- -> B.ByteString +-- -- ^ the key of the proofs +-- -> ML.MerkleProof a +-- createMerkleProof ps key = TrieProof +-- { _trieProofKey = E._proofKey p +-- , _trieProofNodes = E._proofNodes p +-- , _trieProofRoot = E._proofRoot p +-- } +-- where +-- p = E.createProof ps key + -- -------------------------------------------------------------------------- -- -- NEW SPV @@ -280,225 +698,3 @@ instance Exception VerificationException -- blob = _payloadProofBlob p -- obj = encodeB64UrlNoPaddingText . encodeMerkleProofObject -- {-# INLINE payloadProofProperties #-} --- -------------------------------------------------------------------------- -- - --- TODO: replace concrete types by Tags. --- --- The types themself are not available to all payload providers, but the tags --- are. - --- TODO do we need extensible arguments, e.g. as a data family or just class? - --- Data type of Argument --- --- An argument provides evidence for the statement: --- --- \(\text{root} \in \text{Chainweb} \rightarrow \text{claim} \in \text{Chainweb}\) --- --- where `claim` is a value that is stored on chain and has a type that is a --- member of the Chainweb Merkle universe. --- --- This statement is established by applying the argument to the claim, which --- may succeed or fail. --- -data Argument (claim :: Type) (root :: Type) where - Trivial :: Argument a a - BlockPayloadHashArgument - :: ML.MerkleProof ChainwebMerkleHashAlgorithm - -> Argument BlockPayloadHash BlockHash - ParentHeaderArgument - :: ML.MerkleProof ChainwebMerkleHashAlgorithm - -> Argument ParentHash BlockHash - PactOutputArgument - :: ML.MerkleProof ChainwebMerkleHashAlgorithm - -> Argument TransactionOutput BlockPayloadHash - EthReceiptArgument - :: TrieProof - -> Argument E.Receipt ReceiptsRoot - EthHeaderArgument - :: ML.MerkleProof ChainwebMerkleHashAlgorithm - -> Argument ReceiptsRoot BlockPayloadHash - - -- Composed Arguments - ComposeArgument - :: InclusionClaim a - => Argument claim a - -> Argument a root - -> Argument claim root - ComposeArgument2 - :: (InclusionClaim a0, InclusionClaim a1) - => Argument claim0 a0 - -> Argument claim1 a1 - -> Argument (Conjunct a0 a1) root - -> Argument (Conjunct claim0 claim1) root - --- What would be the benefit of generic Arguments? Probably not much. When --- serialized, neither the root nor the claim is included. Therefore the --- overhead would be just the respective constructor tag. - --- -- Generic Arguments --- MerkleArgument --- :: (InclusionClaim claim) --- => MerkleProof Sha2_512_256 --- -> Argument claim root --- TrieArgument --- :: (InclusionClaim claim) --- => TrieProof --- -> Argument claim root - -compose - :: InclusionClaim claim - => InclusionClaim a - => Argument claim a - -> Argument a root - -> Maybe (Argument claim root) -compose Trivial a = Just a -compose a Trivial = Just a -compose (BlockPayloadHashArgument m0) (ParentHeaderArgument m1) - = MerkleArgument (ML.composeProof m0 m1) - - --- | Compute the proof root and provide evidence that the proof claim is --- included in the root of the respective Chainweb Merkle tree. --- --- The claim is either a leave and a member of the Chainweb Merkle universe or --- it is an inner node of the Chainweb Merkle tree. In case of the latter no --- futher evidence about its type is included. --- --- IMPORTANT NOTE: --- --- If the result is an inner tree node (e.g. a 'BlockHash' or a --- 'BlockPayloadHash') type is not checked and purely informational. There is no --- evidence for it being correct. (we could add that evidence in the future, but --- right now it is not provided.) --- --- It is an important security invariant of Chainweb that the preimage of each --- root is completely guarded by types values in the Chainweb Merkle universe. --- -runProof - :: InclusionClaim claim - => InclusionClaim root - => Argument claim root - -> claim - -> Either VerificationException root -runProof (TransactionOutputProof evidence) claim = - runTransactionOutputProof evidence claim -runProof (EthReceiptArgument evidence) claim = - ReceiptsRoot <$> validateTrieProof (Just $ E.putRlpByteString claim) evidence -runProof (ComposeArgument a0 a1) claim = runProof a0 claim >>= runProof a1 -runProof (ComposeArgument2 a0 a1 a3) (Conjunct c0 c1) = do - r0 <- runProof a0 c0 - r1 <- runProof a1 c1 - runProof a3 (Conjunct r0 r1) - - --- -- | Compute the proof root and provide evidence that is included as a leave in --- -- the root of the respective Chainweb Merkle tree. The evidence includes that --- -- the claim is the correct type in the Chainweb Merkle universe. --- -- --- -- When running proofs we restrict claims to members of the Merkle universe. It --- -- is an important security invariant of Chainweb that the preimage of each root --- -- is completely guarded by types values in the Chainweb Merkle universe. --- -- --- runLeafProof --- :: ClaimType claim --- => RootType root --- => Argument claim root --- -> claim --- -> Either VerificationException root --- runLeafProof = runProof - --- -------------------------------------------------------------------------- -- --- | Pact Output Proof - -instance E.RLP (ML.MerkleProof a) where - putRlp p = E.putRlp p - getRlp = E.label "MerkleProof" $ MerkleProof <$> E.getRlp - {-# INLINE putRlp #-} - {-# INLINE getRlp #-} - -createMerkleProof - :: [(B.ByteString, B.ByteString)] - -- ^ Key-value pairs that are stroed in the Trie - -> B.ByteString - -- ^ the key of the proofs - -> ML.MerkleProof a -createMerkleProof ps key = TrieProof - { _trieProofKey = E._proofKey p - , _trieProofNodes = E._proofNodes p - , _trieProofRoot = E._proofRoot p - } - where - p = E.createProof ps key - --- -------------------------------------------------------------------------- -- --- | Ethereum Trie Proofs --- --- Note that trie proofs are "forward": given the root, the key, and the --- evidence, they produce the claim. --- --- Whereas Merkle proofs are "backward": given the claim, and some evidence they --- produce the claim (and possibly the key, i.e. the position in the tree). --- --- Our implementation of verification turns the argument for trie proofs around --- to support backward reasoning. --- -data TrieProof = TrieProof - { _trieProofKey :: !B.ByteString - , _trieProofNodes :: ![B.ByteString] - , _trieProofRoot :: !Keccak256Hash - } - deriving (Show, Eq) - -instance E.RLP TrieProof where - putRlp p = E.putRlp - ( _trieProofKey p - , _trieProofNodes p - , _trieProofRoot p - ) - getRlp = E.label "TrieProof" $ E.getRlpL $ TrieProof - <$> E.label "proofKey" E.getRlp - <*> E.label "proofNodes" E.getRlp - <*> E.label "proofRoot" E.getRlp - {-# INLINE putRlp #-} - {-# INLINE getRlp #-} - --- | Crate a proof for the value is stored in the trie for the given key. --- --- If no value is stored for the given key the returned proof includes a --- '_proofValue' of 'Nothing' and witnesses that the key doesn't exist in the --- trie. --- -createTrieProof - :: [(B.ByteString, B.ByteString)] - -- ^ Key-value pairs that are stroed in the Trie - -> B.ByteString - -- ^ the key of the proofs - -> TrieProof -createTrieProof ps key = TrieProof - { _trieProofKey = E._proofKey p - , _trieProofNodes = E._proofNodes p - , _trieProofRoot = E._proofRoot p - } - where - p = E.createProof ps key - -validateTrieProof - :: Maybe B.ByteString - -> TrieProof - -> Either VerificationException Keccak256Hash -validateTrieProof claim p = case E.validateProof q of - Left _ -> - Left (VerificationException "Malformed Proof") - Right False -> - Left (VerificationException "Invalid Proof") - Right True -> - Right $ _trieProofRoot p - where - q = E.Proof - { E._proofKey = _trieProofKey p - , E._proofNodes = _trieProofNodes p - , E._proofRoot = _trieProofRoot p - , E._proofValue = claim - } - diff --git a/src/Chainweb/SPV/Argument.hs b/src/Chainweb/SPV/Argument.hs new file mode 100644 index 0000000000..34d6a5e5e5 --- /dev/null +++ b/src/Chainweb/SPV/Argument.hs @@ -0,0 +1,655 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE KindSignatures #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +-- | +-- Module: Chainweb.SPV.Argument +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.SPV.Argument +( Argument(..) +, argumentClaim +, SomeArgument(..) +, runArg +, compose +, validateTrieProof +) where + +import Chainweb.BlockHash +import Chainweb.BlockPayloadHash +import Chainweb.Crypto.MerkleLog +import Chainweb.MerkleLogHash +import Chainweb.MerkleUniverse +import Chainweb.Payload +import Chainweb.PayloadProvider.EVM.Header ({- IsMerkleLogEntry instances -}) +import Chainweb.PayloadProvider.EVM.Receipt qualified as EVM +import Control.Exception +import Control.Monad +import Control.Monad.Catch +import Data.ByteString qualified as B +import Data.Coerce +import Data.Kind +import Data.MerkleLog (MerkleRoot) +import Data.MerkleLog qualified as ML +import Data.MerkleLog.V1 qualified as V1 +import Data.Text qualified as T +import Data.Type.Equality +import Data.Typeable +import Data.Word +import Ethereum.Misc qualified as E +import Ethereum.RLP (getRlpL) +import Ethereum.RLP qualified as E +import Ethereum.Trie qualified as E +import Numeric.Natural + +-- -------------------------------------------------------------------------- -- +-- Alternative Approach + +-- Protocol flow: +-- +-- 1. Event emission [on-chain]: the user creates the event. The event may be the +-- native event format of the payload provider, a custom language extension, +-- or an agreement to write a particular value to the contract store. +-- +-- 2. Proof creation [off-chain]: the user creates a proof (usually through +-- calling an API) that contains the relevant event data along with evidence +-- for its inclusion on the source chain and its provenance. +-- +-- 3. Send redeem transaction [off-chain]: the user sends a transaction that +-- includes the proof to the target chain. +-- +-- 4. Proof verification [on-chain]: the execution environment on the receiving +-- chain verifies the evidence in the proof and extract the relevant message +-- and provenance data. It performance additional checks on that data and, if +-- successful, notifies the receiver contract. +-- +-- 4. Redeem [on-chain]: the receiving contract obtains the notification data. +-- It performs some checks on the data. If the checks succeed it performs the +-- redeem operations. +-- +-- +-- TOPIC FORMAT: +-- +-- 0: event type signature hash (keccak256()) +-- 1: targetChain [uint32, bigendian] +-- 2: target address [address, last 20 bytes] +-- 3: ignored (optional for indexing) +-- +-- LogData: +-- * receiver address B.ByteString (eth address 20 bytes) +-- * amount [uint256] + +-- -------------------------------------------------------------------------- -- +-- +-- Backward compatibility: +-- +-- * Proof format: +-- * Existing proof *must* continue to work during reorg and the transition. +-- * Can we skip proof verification during replay? +-- +-- Before EVM transition: +-- 1. Introduce support for validating new format +-- 2. Enable creation of new format. +-- +-- + +-- -------------------------------------------------------------------------- -- +-- TODO: +-- +-- The types in the Merkle Universe must be available to all payload providers +-- (the alternative would be to define a unique event format across all payload +-- providers and require that all payload providers support direct proofs of +-- those events in their respective payload Merkle tree. +-- +-- For instance the EVM provider could collect events from the EVM and transform +-- those to the common format and add those to a new event tree. +-- +-- We should create a package that collects and reexports all types from the +-- Chainweb Merkle universe. + +-- -------------------------------------------------------------------------- -- + +type ChainwebMerkleLogEntry a = + ( Eq a + , Typeable a + , Show a + , IsMerkleLogEntry ChainwebMerkleHashAlgorithm ChainwebHashTag a + ) + +tagVal' :: forall b . ChainwebMerkleLogEntry b => Word16 +tagVal' = tagVal @ChainwebHashTag @(Tag b) + +-- -------------------------------------------------------------------------- +-- Claims + +data Conjunct a b where + Conjunct + :: (ChainwebMerkleLogEntry a, ChainwebMerkleLogEntry b) + => a + -> b + -> Conjunct a b + +deriving instance (Show a, Show b) => Show (Conjunct a b) +deriving instance (Eq a, Eq b) => Eq (Conjunct a b) + +class InclusionClaim a +instance {-# OVERLAPPABLE #-} (ChainwebMerkleLogEntry a) => InclusionClaim a +instance {-# OVERLAPPING #-} InclusionClaim (Conjunct a b) + +-- -------------------------------------------------------------------------- -- +-- Exceptions + +newtype VerificationException = VerificationException T.Text + deriving (Show, Eq) + +instance Exception VerificationException + +-- -------------------------------------------------------------------------- -- + +-- TODO: replace concrete types by Tags? +-- +-- The types themself are not available to all payload providers, but the tags +-- are. +-- +-- TODO do we need extensible arguments, e.g. as a data family or just class? +-- +-- For now we try to the set of supported Proofs and claim types small and +-- fixed. +-- +-- The claim types are included in the Chainweb core library to which all +-- payload providers have access. Proof verification validates the evidence and +-- also deserializes the claim, which inlcudes verification of the tag. + +-- | Arguments +-- +-- An argument provides evidence for the statement: +-- +-- \(\text{root} \in \text{Chainweb} \rightarrow \text{claim} \in \text{Chainweb}\) +-- +-- where `claim` is a value that is stored on chain and has a type that is a +-- member of the Chainweb Merkle universe. +-- +-- This statement is established by applying the argument to the claim, which +-- may succeed or fail. +-- +data Argument (claim :: Type) (root :: Type) where + Trivial + :: + ( ChainwebMerkleLogEntry a + , Coercible a (MerkleRoot ChainwebMerkleHashAlgorithm) + ) + => a + -> Argument a a + PactLegacyProof + :: V1.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument TransactionOutput BlockHash + HeaderArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument BlockHash BlockHash + ParentHeaderArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument BlockHash BlockHash + BlockPayloadHashArgument + :: ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument BlockPayloadHash BlockHash + PactOutputArgument + :: + ( ChainwebMerkleLogEntry root + , Coercible root (MerkleRoot ChainwebMerkleHashAlgorithm) + ) + => ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument TransactionOutput root + EthHeaderArgument + :: + ( ChainwebMerkleLogEntry root + , Coercible root (MerkleRoot ChainwebMerkleHashAlgorithm) + ) + => ML.MerkleProof ChainwebMerkleHashAlgorithm + -> Argument EVM.ReceiptsRoot root + EthReceiptArgument + :: E.Proof + -> Argument EVM.Receipt EVM.ReceiptsRoot + + -- Composed Arguments (used when the underlying proofs can't be composed) + ComposeArgument + :: + ( ChainwebMerkleLogEntry a + , ChainwebMerkleLogEntry claim + , ChainwebMerkleLogEntry root + ) + => Argument claim a + -> Argument a root + -> Argument claim root + + -- Future work: + -- + -- Conjunect a0 a1 is not a MerkleLogEntry. Hence we need create an + -- abstraction that that covers both MerkleLogEntries and Conjunctions of + -- MerkleLogEntries. + -- + -- ComposeArgument2 + -- :: + -- ( ChainwebMerkleLogEntry a0 + -- , ChainwebMerkleLogEntry a1 + -- , ChainwebMerkleLogEntry root + -- , ChainwebMerkleLogEntry claim0 + -- , ChainwebMerkleLogEntry claim1 + -- ) + -- => Argument claim0 a0 + -- -> Argument claim1 a1 + -- -> Argument (Conjunct a0 a1) root + -- -> Argument (Conjunct claim0 claim1) root + +deriving instance (Show claim, Show root) => Show (Argument claim root) + +argumentClaim :: MonadThrow m => Argument claim root -> m claim +argumentClaim (Trivial r) = return r +argumentClaim (PactLegacyProof p) = proofSubject p +argumentClaim (HeaderArgument p) = proofClaim p +argumentClaim (ParentHeaderArgument p) = proofClaim p +argumentClaim (BlockPayloadHashArgument p) = proofClaim p +argumentClaim (PactOutputArgument p) = proofClaim p +argumentClaim (EthHeaderArgument p) = proofClaim p +argumentClaim (EthReceiptArgument p) = case E._proofValue p of + Nothing -> throwM $ VerificationException "Invalid Receipt proof: missing claim" + Just r -> case E.get E.getRlp r of + Left e -> throwM $ VerificationException $ "Invalid receipt: " <> T.pack e + Right r' -> return r' +argumentClaim (ComposeArgument a0 _) = argumentClaim a0 +-- argumentClaim (ComposeArgument2 a0 a1 _) = Conjunct +-- <$> argumentClaim a0 +-- <*> argumentClaim a1 + +-- -------------------------------------------------------------------------- -- +-- Serialization + +data SomeArgument where + SomeArgument + :: forall (claim :: Type) (root :: Type) + . (ChainwebMerkleLogEntry claim, ChainwebMerkleLogEntry root) + => Argument claim root + -> SomeArgument + +instance MerkleHashAlgorithm a => E.RLP (MerkleRoot a) where + putRlp r = E.putRlp $ ML.encodeMerkleRoot r + getRlp = E.label "MerkleRoot " $ do + bs <- E.getRlp @B.ByteString + case ML.decodeMerkleRoot bs of + Left e -> fail $ "Invalid MerkleRoot: " <> show e + Right r -> return r + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance MerkleHashAlgorithm a => E.RLP (ML.MerkleNodeType a) where + putRlp (ML.TreeNode h) = E.putRlpL + [ E.putRlp (0 :: Word8) + , E.putRlp h + ] + putRlp (ML.InputNode b) = E.putRlpL + [ E.putRlp (1 :: Word8) + , E.putRlp b + ] + getRlp = E.label "MerkleNodeType" $ do + E.getRlp @Word8 >>= \case + 0 -> E.label "TreeNode" (ML.TreeNode <$> E.getRlp) + 1 -> E.label "InputNode" (ML.InputNode <$> E.getRlp) + _ -> fail "invalid MerkleNodeType" + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance MerkleHashAlgorithm a => E.RLP (V1.MerkleProof a) where + putRlp p = E.putRlpL + [ E.putRlp $ V1._getMerkleProofSubject (V1._merkleProofSubject p) + , E.putRlp $ V1.encodeMerkleProofObject (V1._merkleProofObject p) + ] + getRlp = E.label "V1.MerkleProof" $ V1.MerkleProof + <$> E.label "MerkleProofSubject" (V1.MerkleProofSubject <$> E.getRlp) + <*> E.label "MerkleProofObject" do + bs <- E.getRlp @B.ByteString + case V1.decodeMerkleProofObject bs of + Left e -> fail $ "Invalid MerkleProofObject: " <> show e + Right r -> return r + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance E.RLP (ML.MerkleProof ChainwebMerkleHashAlgorithm) where + putRlp p = E.putRlpL + [ E.putRlp (ML._merkleProofClaim p) + , E.putRlp (fromIntegral @_ @Natural $ ML._merkleProofTrace p) + , E.putRlp (ML._merkleProofEvidence p) + ] + getRlp = E.label "MerkleProof" $ + ML.MerkleProof + <$> E.label "claim" E.getRlp + <*> E.label "trace" (fromIntegral @Natural <$> E.getRlp) + <*> E.label "evidence" E.getRlp + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance MerkleHashAlgorithm a => E.RLP (BlockPayloadHash_ a) where + putRlp = E.putRlp . coerce @_ @(MerkleRoot a) + getRlp = E.label "BlockPayloadHash" $ coerce <$> E.getRlp @(MerkleRoot a) + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance MerkleHashAlgorithm a => E.RLP (BlockHash_ a) where + putRlp = E.putRlp . coerce @_ @(MerkleRoot a) + getRlp = E.label "BlockHash" $ coerce <$> E.getRlp @(MerkleRoot a) + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +instance E.RLP SomeArgument where + putRlp (SomeArgument @claim @root arg) = case arg of + (Trivial p) -> go 0 (coerce @_ @(MerkleRoot ChainwebMerkleHashAlgorithm) p) + (PactLegacyProof p) -> go 1 p + (HeaderArgument p) -> go 2 p + (ParentHeaderArgument p) -> go 3 p + (BlockPayloadHashArgument p) -> go 4 p + (PactOutputArgument p) -> go 5 p + (EthHeaderArgument p) -> go 6 p + (EthReceiptArgument p) -> go 7 p + (ComposeArgument a0 a1) -> E.putRlpL + [ E.putRlp (8 :: Word8) + , E.putRlp (SomeArgument a0) + , E.putRlp (SomeArgument a1) + ] + where + go :: E.RLP p => Word8 -> p -> E.Put + go tag p = E.putRlpL + [ E.putRlp (tag :: Word8) + , E.putRlp (tagVal' @claim) + , E.putRlp (tagVal' @root) + , E.putRlp p + ] + + getRlp = E.label "Argument" $ getRlpL $ do + tag <- E.label "Argument Tag" (E.getRlp @Word8) + claim <- E.label "Claim Tag" $ E.getRlp @Word16 + root <- E.label "Root Tag" $ E.getRlp @Word16 + case tag of + 0 -> E.label "Trivial" $ if + | root == tagVal' @BlockPayloadHash -> SomeArgument <$> + enforceType claim root (Trivial @BlockPayloadHash <$> E.getRlp) + | root == tagVal' @BlockHash -> SomeArgument <$> + enforceType claim root (Trivial @BlockHash <$> E.getRlp) + | otherwise -> + fail "invalid root type" + 1 -> E.label "PactLegacyProof" $ SomeArgument <$> + getTagged (PactLegacyProof <$> E.getRlp) + 2 -> E.label "HeaderArgument" $ SomeArgument <$> + getTagged (HeaderArgument <$> E.getRlp) + 3 -> E.label "ParentHeaderArgument" $ SomeArgument <$> + getTagged (ParentHeaderArgument <$> E.getRlp) + 4 -> E.label "BlockPayloadHashArgument" $ SomeArgument <$> + getTagged (BlockPayloadHashArgument <$> E.getRlp) + 5 -> E.label "PactOutputArgument" $ if + | root == tagVal' @BlockPayloadHash -> SomeArgument <$> + enforceType claim root (PactOutputArgument @BlockPayloadHash <$> E.getRlp) + | root == tagVal' @BlockHash -> SomeArgument <$> + enforceType claim root (PactOutputArgument @BlockHash <$> E.getRlp) + | otherwise -> + fail "invalid root type" + 6 -> E.label "EthHeaderArgument" $ if + | root == tagVal' @BlockPayloadHash -> SomeArgument <$> + enforceType claim root (EthHeaderArgument @BlockPayloadHash <$> E.getRlp) + | root == tagVal' @BlockHash -> SomeArgument <$> + enforceType claim root (EthHeaderArgument @BlockHash <$> E.getRlp) + | otherwise -> + fail "invalid root type" + 7 -> E.label "EthReceiptArgument" $ SomeArgument <$> + getTagged (EthReceiptArgument <$> E.getRlp) + 8 -> E.label "ComposeArgument" $ do + SomeArgument @c @a a0 <- E.getRlp + SomeArgument @a' @r a1 <- E.getRlp + arg <- getTagged @c @r $ case (eqT @a @a') of + Just Refl -> return $ ComposeArgument a0 a1 + _ -> fail "invalid compose argument" + return $ SomeArgument arg + _ -> fail "invalid argument type" + where + getTagged + :: forall claim root + . ChainwebMerkleLogEntry claim + => ChainwebMerkleLogEntry root + => E.Get (Argument claim root) + -> E.Get (Argument claim root) + getTagged p = do + claim <- E.label "Claim Tag" $ E.getRlp @Word16 + root <- E.label "Root Tag" $ E.getRlp @Word16 + enforceType claim root p + + enforceType + :: forall claim root + . ChainwebMerkleLogEntry claim + => ChainwebMerkleLogEntry root + => Word16 + -> Word16 + -> E.Get (Argument claim root) + -> E.Get (Argument claim root) + enforceType claim root p = do + unless (claim == tagVal' @claim) (fail "invalid claim type") + unless (root == tagVal' @root) (fail "invalid root type") + p + +-- -------------------------------------------------------------------------- -- +-- | Argument composition. +-- +-- The implementation balances the composition tree to be right associative. +-- This maximizes the composition on the lower level proofs. +-- +-- Nested calls to this can be quadratic in the worst case when associating from +-- the left. So one should avoid that. Instead arguments should be composed from +-- the right. +-- +compose + :: MonadThrow m + => ChainwebMerkleLogEntry a + => ChainwebMerkleLogEntry claim + => ChainwebMerkleLogEntry root + => Argument claim a + -> Argument a root + -> m (Argument claim root) +compose (Trivial _) a = return a -- FXME: check that claims match? +compose a (Trivial _) = return a -- FIXME: check that roots match? +compose (HeaderArgument m0) (HeaderArgument m1) + = HeaderArgument <$> ML.composeProofs m0 m1 +compose (ParentHeaderArgument m0) (HeaderArgument m1) + = ParentHeaderArgument <$> ML.composeProofs m0 m1 +compose (BlockPayloadHashArgument m0) (HeaderArgument m1) + = BlockPayloadHashArgument <$> ML.composeProofs m0 m1 +compose (PactOutputArgument m0) (BlockPayloadHashArgument m1) + = PactOutputArgument <$> ML.composeProofs m0 m1 +compose (PactOutputArgument m0) (ParentHeaderArgument m1) + = PactOutputArgument <$> ML.composeProofs m0 m1 +compose (EthHeaderArgument m0) (BlockPayloadHashArgument m1) + = EthHeaderArgument <$> ML.composeProofs m0 m1 +compose (ComposeArgument a0 a1) b = ComposeArgument a0 <$> compose a1 b +compose a (ComposeArgument b0 b1) = do + -- First compose a and b0. This may result in a ComposeArgument in which + -- case we and up with the previous case. + a' <- compose a b0 + compose a' b1 +compose a b = return (ComposeArgument a b) + + +-- -------------------------------------------------------------------------- -- +-- | Compute the proof root and provide evidence that the proof claim is +-- included in the root of the respective Chainweb Merkle tree. +-- +-- The claim is either a leave and a member of the Chainweb Merkle universe or +-- it is an inner node of the Chainweb Merkle tree. In case of the latter no +-- futher evidence about its type is included. +-- +-- IMPORTANT NOTE: +-- +-- If the result is an inner tree node (e.g. a 'BlockHash' or a +-- 'BlockPayloadHash') type is not checked and purely informational. There is no +-- evidence for it being correct. (we could add that evidence in the future, but +-- right now it is not provided.) +-- +-- It is an important security invariant of Chainweb that the preimage of each +-- root is completely guarded by types values in the Chainweb Merkle universe. +-- +runArg + :: MonadThrow m + => Argument claim root + -> m root +runArg (Trivial r) = return r +runArg (PactLegacyProof evidence) = do + r <- V1.runMerkleProof evidence + return (coerce r) +runArg (PactOutputArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (ParentHeaderArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (HeaderArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (BlockPayloadHashArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (EthReceiptArgument evidence) = do + -- We do not need to check the proof key because all values under this + -- root are receipts. However, we must make sure that the root has the + -- correct type, which is done either when checked against an oracle or when + -- it is used as claim in another proof. + r <- EVM.ReceiptsRoot <$> validateTrieProof evidence + return r +runArg (EthHeaderArgument evidence) = do + return $ coerce $ ML.runProof evidence +runArg (ComposeArgument a0 a1) = do + r0 <- runArg a0 + c1 <- argumentClaim a1 + when (r0 /= c1) $ throwM $ VerificationException "Composition failed" + runArg a1 + +-- runProof (Trivial r) = do +-- return r +-- runProof (ComposeArgument2 a0 a1 a3) (Conjunct c0 c1) = do +-- r0 <- runProof a0 c0 +-- r1 <- runProof a1 c1 +-- runProof a3 (Conjunct r0 r1) + +-- -------------------------------------------------------------------------- -- +-- | Ethereum Trie Proofs +-- + +validateTrieProof + :: MonadThrow m + => E.Proof + -> m E.Keccak256Hash +validateTrieProof p = case E.validateProof p of + Left _ -> + throwM (VerificationException "Malformed Proof") + Right False -> + throwM (VerificationException "Invalid Proof") + Right True -> + return $ E._proofRoot p + +-- -------------------------------------------------------------------------- -- +-- ATTIC + +-- -------------------------------------------------------------------------- -- +-- | Pact Output Proof + +-- instance E.RLP (ML.MerkleProof a) where +-- putRlp p = E.putRlp p +-- getRlp = E.label "MerkleProof" $ MerkleProof <$> E.getRlp +-- {-# INLINE putRlp #-} +-- {-# INLINE getRlp #-} + +-- createMerkleProof +-- :: [(B.ByteString, B.ByteString)] +-- -- ^ Key-value pairs that are stroed in the Trie +-- -> B.ByteString +-- -- ^ the key of the proofs +-- -> ML.MerkleProof a +-- createMerkleProof ps key = TrieProof +-- { _trieProofKey = E._proofKey p +-- , _trieProofNodes = E._proofNodes p +-- , _trieProofRoot = E._proofRoot p +-- } +-- where +-- p = E.createProof ps key + +-- -------------------------------------------------------------------------- -- +-- NEW SPV + +-- proofProperties +-- :: forall e kv +-- . KeyValue e kv +-- => ChainId +-- -> MerkleProof Sha2_512_256 +-- -> [kv] +-- proofProperties cid p = +-- [ "chain" .= cid +-- , "event" .= JsonProofSubject (_getMerkleProofSubject $ _merkleProofSubject p) +-- , "algorithm" .= ("Sha2_512_256" :: T.Text) +-- ] +-- where +-- obj = encodeB64UrlNoPaddingText . encodeMerkleProofObject +-- +-- data XChainMessage = XChainMessage +-- { _xChainMsgTargetChain :: !ChainId +-- , _xChainMsgReceiverAccount :: !B.ByteString +-- , _xChainMsgAmount :: !Stu +-- } +-- +-- data XChainEvent = XChainEvent +-- { _xChainSourceChain :: !ChainId +-- -- ^ not in the ETH log +-- , _xChainSourceContract :: !B.ByteString +-- -- ^ Added by the system +-- , _xChainSourceBlockHeight :: !Word64 +-- -- Not in EHT Log +-- , _xChainSourceTxIdx :: !Word64 +-- -- ^ this is not in the event it is provided by the user when they select +-- -- the event +-- , _xChainSourceEventIdx :: !Word64 +-- -- ^ this is not authenticated by the proof. It is provided by the user and +-- -- instructs the precompile to limit the proof to just this one event. +-- , _xChainMessage :: !XChainMessage +-- -- ^ The actual user payload. Cf. below for an example for simple ERC-20 +-- -- cross chain transfers. +-- } + + +-- data PayloadProof a = PayloadProof +-- { _payloadProofRootType :: !MerkleRootType +-- -- ^ The type of the Merle root. +-- , _payloadProofBlob :: !(MerkleProof a) +-- -- ^ The Merkle proof blob which coaintains both, the proof object and +-- -- the proof subject. +-- } deriving (Show, Eq, Generic, NFData) +-- +-- payloadProofProperties +-- :: forall a e kv +-- . MerkleHashAlgorithmName a +-- => KeyValue e kv +-- => PayloadProof a +-- -> [kv] +-- payloadProofProperties p = +-- [ "rootType" .= _payloadProofRootType p +-- , "object" .= (obj . _merkleProofObject) blob +-- , "subject" .= JsonProofSubject (_getMerkleProofSubject $ _merkleProofSubject blob) +-- , "algorithm" .= merkleHashAlgorithmName @a +-- ] +-- where +-- blob = _payloadProofBlob p +-- obj = encodeB64UrlNoPaddingText . encodeMerkleProofObject +-- {-# INLINE payloadProofProperties #-} diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index 335c0cb346..43243d7f43 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -1596,7 +1596,7 @@ peekByteString => B.ByteString -> Either T.Text w peekByteString bs - | l < s = Left $ "toWordBe: size of input bytestring to small" + | l < s = Left $ "peekByteString: size of input bytestring to small" <> ". Expected: " <> sshow s <> ". Actual: " <> sshow l | otherwise = Right $ unsafeDupablePerformIO $ diff --git a/src/Chainweb/Utils/Serialization.hs b/src/Chainweb/Utils/Serialization.hs index d43900ff1d..243ca6d0d4 100644 --- a/src/Chainweb/Utils/Serialization.hs +++ b/src/Chainweb/Utils/Serialization.hs @@ -192,7 +192,6 @@ class WordEncoding w where encodeWordBe :: w -> Put decodeWordBe :: Get w - instance WordEncoding Word8 where encodeWordLe = putWord8 decodeWordLe = getWord8 diff --git a/test/payload-provider/PayloadProviderTests.hs b/test/payload-provider/PayloadProviderTests.hs new file mode 100644 index 0000000000..e73ead741e --- /dev/null +++ b/test/payload-provider/PayloadProviderTests.hs @@ -0,0 +1,48 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE NumericUnderscores #-} + +-- | +-- Module: PayloadProviderPTests +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Main +( main +) where + +import Test.Tasty +import Test.Tasty.JsonReporter + +-- chainweb modules + +import Chainweb.Storage.Table.RocksDB +import Chainweb.Version.Development +import Chainweb.Version.RecapDevelopment +import Chainweb.Version.Registry + +-- chainweb-test-tools modules + +import Test.Chainweb.SPV.Argument qualified + +main :: IO () +main = do + registerVersion RecapDevelopment + registerVersion Development + withTempRocksDb "payload-provider-tests" $ \rdb -> + defaultMainWithIngredients (consoleAndJsonReporter : defaultIngredients) + $ adjustOption adj + $ testGroup "Chainweb Payload Provider Tests" + $ suite rdb + + where + adj NoTimeout = Timeout (1_000_000 * 60 * 10) "10m" + adj x = x + +suite :: RocksDb -> [TestTree] +suite rdb = + [ testGroup "Chainweb Payload Provider Unit Tests" + [ Test.Chainweb.SPV.Argument.tests + ] + ] diff --git a/test/payload-provider/Test/Chainweb/SPV/Argument.hs b/test/payload-provider/Test/Chainweb/SPV/Argument.hs new file mode 100644 index 0000000000..05f47b82f2 --- /dev/null +++ b/test/payload-provider/Test/Chainweb/SPV/Argument.hs @@ -0,0 +1,537 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE LambdaCase #-} + +-- | +-- Module: Test.Chainweb.SPV.Argument +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Test.Chainweb.SPV.Argument +( tests +) where + +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.ChainId +import Chainweb.Crypto.MerkleLog +import Chainweb.MerkleLogHash +import Chainweb.MerkleUniverse +import Chainweb.Payload +import Chainweb.PayloadProvider.EVM.Genesis qualified as EVM +import Chainweb.PayloadProvider.EVM.Header qualified as EVM +import Chainweb.PayloadProvider.EVM.Receipt qualified as EVM +import Chainweb.PayloadProvider.Pact.Genesis qualified as Pact +import Chainweb.SPV.Argument +import Chainweb.Utils +import Chainweb.Version +import Chainweb.Version.EvmDevelopment +import Chainweb.Version.Mainnet +import Chainweb.Version.Registry +import Control.Lens +import Control.Monad +import Data.Aeson +import Data.ByteString qualified as B +import Data.Coerce +import Data.Hash.SHA2 +import Data.List qualified as L +import Data.List.NonEmpty qualified as NE +import Data.MerkleLog qualified as ML +import Data.MerkleLog.V1 qualified as MLV1 +import Data.Text qualified as T +import Data.Typeable +import Data.Vector qualified as V +import Ethereum.Block qualified as E +import Ethereum.Misc qualified as E +import Numeric.Natural +import Test.Tasty +import Test.Tasty.HUnit +import Text.RawString.QQ + +-- -------------------------------------------------------------------------- -- +-- Tests + +tests :: TestTree +tests = testGroup "Chainweb.SPV.Argument" + [ testCase "Pact Output Argument" test_pactOutputArgument + , testCase "Legacy Pact Output Argument" test_legacyPactOutputArgument + , testCase "EVM JSON Roundtrips" test_jsonRoundtrips + , testCase "EVM Header Arguments" test_evmHeaderArguments + ] + +-- -------------------------------------------------------------------------- -- +-- JSON Roundrip Tests for Test Data + +test_jsonRoundtrip + :: Typeable a + => Show a + => Eq a + => ToJSON a + => FromJSON a + => a + -> IO () +test_jsonRoundtrip a = assertEqual + ("json roundtrip for type " <> show (typeOf a)) + (Right a) + (eitherDecode (encode a)) + +test_jsonRoundtrips :: IO () +test_jsonRoundtrips = do + registerVersion EvmDevelopment + test_jsonRoundtrip testHeader + test_jsonRoundtrip testPayload + test_jsonRoundtrip testRpcReceipt + +-- -------------------------------------------------------------------------- -- +-- Test Data + +genesisData :: ChainwebVersion -> ChainId -> IO (BlockHeader, PayloadWithOutputs) +genesisData version cid = do + let hdr = genesisBlockHeader version cid + pwo <- case preview (ixg cid) (Pact.genesisPayload Mainnet01) of + Just x -> return x + Nothing -> error "genesis block for chain 0 not found" + assertEqual "block payload hash of header and payload match" + (view blockPayloadHash hdr) + (_payloadWithOutputsPayloadHash pwo) + return (hdr, pwo) + +-- evmGenesisData :: ChainId -> IO (BlockHeader, PayloadWithOutputs) +-- evmGenesisData cid = do +-- let hdr = EVM.genesisBlocks EvmDevelopment cid +-- EVM.hdr hdr +-- -- pwo <- case preview (ixg cid) (genesisPayload EvmDevelopment) of +-- -- Just x -> return x +-- -- Nothing -> error "genesis block for chain 0 not found" +-- -- assertEqual "block payload hash of header and payload match" +-- -- (view blockPayloadHash hdr) +-- -- (_payloadWithOutputsPayloadHash pwo) +-- -- return (hdr, pwo) + +-- -------------------------------------------------------------------------- -- +-- Tests + +test_pactOutputArgument :: IO () +test_pactOutputArgument = do + (hdr, pwo) <- genesisData Mainnet01 (unsafeChainId 0) + hdrArg <- test_hdr hdr + + let txCount = length $ _payloadWithOutputsTransactions pwo + + -- run test for each tx in the block + forM_ [0 .. txCount - 1] $ \txPos -> do + + pwoArg <- test_txOutput txPos pwo + + -- compose pact output and header arguments + arg <- compose pwoArg hdrArg + argRoot <- runArg arg + assertEqual "composed argument gives correct root hash" + (view blockHash hdr) + argRoot + + claim <- argumentClaim arg + assertEqual "argument claim is correct" + (snd (_payloadWithOutputsTransactions pwo V.! txPos)) + claim + +test_legacyPactOutputArgument :: IO () +test_legacyPactOutputArgument = do + (hdr, pwo) <- genesisData Mainnet01 (unsafeChainId 0) + + -- create tx output proof for tx 0 + let outs = snd $ payloadWithOutputsToBlockObjects pwo + let txCount = length (_blockOutputs outs) + + let pld = payloadDataToBlockPayload (payloadWithOutputsToPayloadData pwo) + let pldTree = headerTree_ @BlockOutputsHash @ChainwebMerkleHashAlgorithm pld + let hdrTree = headerTree_ @BlockPayloadHash @ChainwebMerkleHashAlgorithm hdr + + -- run test for each tx in the block + forM_ [0 .. txCount - 1] $ \txPos -> do + let (outs0, outs0Pos, outs0Tree) = + bodyTree @ChainwebMerkleHashAlgorithm outs txPos + + -- create block payload proof for for outputs + proof <- MLV1.merkleTreeProof_ outs0 $ + (outs0Pos, outs0Tree) NE.:| [ pldTree, hdrTree ] + + root <- MLV1.runMerkleProof proof + assertEqual "body proof of tx output gives correct root hash" + (view blockHash hdr) + (coerce root) + + -- create legacy pact output argument + let arg = PactLegacyProof proof + argRoot <- runArg arg + assertEqual "pact output argument gives correct root hash" + (view blockHash hdr) + argRoot + + claim <- argumentClaim arg + assertEqual "argument claim is correct" + (snd (_payloadWithOutputsTransactions pwo V.! txPos)) + claim + +test_hdr + :: BlockHeader + -> IO (Argument BlockPayloadHash BlockHash) +test_hdr hdr = do + hdrProof <- headerProofV2 @BlockPayloadHash @ChainwebMerkleHashAlgorithm hdr + assertEqual "header proof gives correct root hash" + (view blockHash hdr) + (coerce $ ML.runProof hdrProof) + + -- create header argument + let hdrArg = BlockPayloadHashArgument hdrProof + hdrArgRoot <- runArg hdrArg + assertEqual "header argument gives correct root hash" + (view blockHash hdr) + hdrArgRoot + + return hdrArg + +test_txOutput + :: Int + -> PayloadWithOutputs + -> IO (Argument TransactionOutput BlockPayloadHash) +test_txOutput txPos pwo = do + -- create tx output proof for tx 0 + let outs = snd $ payloadWithOutputsToBlockObjects pwo + outs0Proof <- bodyProofV2 @ChainwebMerkleHashAlgorithm outs txPos + assertEqual "body proof of tx output gives correct root hash" + (_payloadWithOutputsOutputsHash pwo) + (coerce $ ML.runProof outs0Proof) + + -- create block payload proof for for outputs + let pld = payloadDataToBlockPayload (payloadWithOutputsToPayloadData pwo) + pldProof <- headerProofV2 @BlockOutputsHash @ChainwebMerkleHashAlgorithm pld + assertEqual "block payload proof gives correct root hash" + (_payloadWithOutputsPayloadHash pwo) + (coerce $ ML.runProof pldProof) + + -- compose proofs into pact output proof + pwoProof <- ML.composeProofs outs0Proof pldProof + assertEqual "composed pact output proof gives correct root hash" + (_payloadWithOutputsPayloadHash pwo) + (coerce $ ML.runProof pwoProof) + + -- create pact output argument + let pwoArg = PactOutputArgument @BlockPayloadHash pwoProof + pwoArgRoot <- runArg pwoArg + assertEqual "pact output argument gives correct root hash" + (_payloadWithOutputsPayloadHash pwo) + pwoArgRoot + + return pwoArg + +-- -------------------------------------------------------------------------- -- +-- EVM Receipt Tests + +data ReceiptTestData = ReceiptTestData + { _receiptTestDataHeader :: BlockHeader + , _receiptTestDataPayload :: EVM.Header + , _receiptTestDataRpcReceipts :: [EVM.RpcReceipt] + } + deriving (Show, Eq) + +simpleTestData :: ReceiptTestData +simpleTestData = ReceiptTestData + { _receiptTestDataHeader = testHeader + , _receiptTestDataPayload = testPayload + , _receiptTestDataRpcReceipts = [testRpcReceipt] + } + +test_evmHeaderArguments :: IO () +test_evmHeaderArguments = + test_evmHeaderArgument simpleTestData 0 + +test_evmHeaderArgument :: ReceiptTestData -> Natural -> IO () +test_evmHeaderArgument d idx = do + let trieProof = EVM.rpcReceiptTrieProof rs (EVM.TransactionIndex 0) + trieProofRoot <- validateTrieProof trieProof + assertEqual "receipt proof gives correct root hash" + (EVM._hdrReceiptsRoot pld) + (EVM.ReceiptsRoot trieProofRoot) + + -- create EthReceiptArgument + let evmReceiptArg = EthReceiptArgument trieProof + evmReceiptArgRoot <- runArg evmReceiptArg + assertEqual "receipt argument gives correct root hash" + (EVM._hdrReceiptsRoot pld) + evmReceiptArgRoot + + -- create EVM Payload Proof + evmPayloadProof <- headerProofV2 @EVM.ReceiptsRoot @ChainwebMerkleHashAlgorithm + pld + assertEqual "EVM payload proof gives correct root hash" + (EVM._hdrPayloadHash pld) + (coerce $ ML.runProof evmPayloadProof) + + -- create EVM BlockHeaderArgument + let evmHdrArg = EthHeaderArgument evmPayloadProof + evmHdrArgRoot <- runArg evmHdrArg + assertEqual "header argument gives correct root hash" + (EVM._hdrPayloadHash pld) + evmHdrArgRoot + + -- compose EVM header and receipt arguments + payloadArg <- compose evmReceiptArg evmHdrArg + payloadArgRoot <- runArg payloadArg + assertEqual "composed argument gives correct root hash" + (EVM._hdrPayloadHash pld) + payloadArgRoot + + -- create Header Proof + hdrProof <- headerProofV2 @BlockPayloadHash @ChainwebMerkleHashAlgorithm hdr + assertEqual "header proof gives correct root hash" + (view blockHash hdr) + (coerce $ ML.runProof hdrProof) + + let hdrArgument = BlockPayloadHashArgument hdrProof + hdrArgumentRoot <- runArg hdrArgument + assertEqual "header argument gives correct root hash" + (view blockHash hdr) + hdrArgumentRoot + + -- compose payload argument and header argument + arg <- compose payloadArg hdrArgument >>= \a -> case a of + ComposeArgument EthReceiptArgument{} EthHeaderArgument{} -> return a + _ -> assertFailure "Argument is not composition of EthReceiptArgument and EthHeaderArgument" :: IO (Argument EVM.Receipt BlockHash) + argRoot <- runArg arg + assertEqual "composed argument gives correct root hash" + (view blockHash hdr) + argRoot + + where + rs = _receiptTestDataRpcReceipts d + pld = _receiptTestDataPayload d + hdr = _receiptTestDataHeader d + +-- -------------------------------------------------------------------------- -- +-- EVM Test Data + +testReceipt :: EVM.Receipt +testReceipt = EVM.fromRpcReceipt testRpcReceipt + +testRpcReceipt :: EVM.RpcReceipt +testRpcReceipt = case eitherDecodeStrictText jsonReceiptStr of + Left err -> error $ "failed to decode receipt: " <> show err + Right x -> x + where + jsonReceiptStr :: T.Text + jsonReceiptStr = [r| + { + "type": "0x2", + "status": "0x1", + "cumulativeGasUsed": "0x9dcf", + "logs": [ + { + "address": "0x0826f2fc8902ca5afe82570560ba752a7f53675c", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000fb8fb7f9bdc8951040a6d195764905138f7462ed", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "data": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000", + "blockHash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f", + "blockNumber": "0x162", + "blockTimestamp": "0x67ef2c65", + "transactionHash": "0x3247ba77980ba47c6a59afc8d73bf5d70eff69e7f129b680b15451e45c3b4d5d", + "transactionIndex": "0x0", + "logIndex": "0x0", + "removed": false + }, + { + "address": "0x0826f2fc8902ca5afe82570560ba752a7f53675c", + "topics": [ + "0x9d2528c24edd576da7816ca2bdaa28765177c54b32fb18e2ca18567fbc2a9550", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x000000000000000000000000ead0610198d4d802f9d14ec7e8d85d30233ccc6b", + "0x0000000000000000000000000000000000000000000000000000000000000001" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000028f2d8ef4e0fe6b2e945cf5c33a0118a30a623540000000000000000000000000000000000000000000000056bc75e2d63100000", + "blockHash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f", + "blockNumber": "0x162", + "blockTimestamp": "0x67ef2c65", + "transactionHash": "0x3247ba77980ba47c6a59afc8d73bf5d70eff69e7f129b680b15451e45c3b4d5d", + "transactionIndex": "0x0", + "logIndex": "0x1", + "removed": false + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000800000000000000000000040100000000000000000000000008000000000000000000040000000000000000000000000000020000000000000000000800000000000000000000002010000000000000000000000000000100000000004000000000000000000001000000000000000000000000000000000000000000000000000000008000000000000000002000000002000000000000000000000000000000000000000040000000000060004000000000000000000000000000000000000200000000000000000000000000", + "transactionHash": "0x3247ba77980ba47c6a59afc8d73bf5d70eff69e7f129b680b15451e45c3b4d5d", + "transactionIndex": "0x0", + "blockHash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f", + "blockNumber": "0x162", + "gasUsed": "0x9dcf", + "effectiveGasPrice": "0x3b9aca07", + "from": "0xfb8fb7f9bdc8951040a6d195764905138f7462ed", + "to": "0x0826f2fc8902ca5afe82570560ba752a7f53675c", + "contractAddress": null + } + |] + +testPayload :: EVM.Header +testPayload = case eitherDecodeStrictText payloadStr of + Left err -> error $ "failed to decode payload: " <> show err + Right x -> x + where + payloadStr :: T.Text + payloadStr = [r| + { + "parentHash": "0x692d784f999068270a78491f50404073f988a324598799866f740e2a52857a6a", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0xd42d71cdc2a0a78fe7fbe7236c19925f62c442ba", + "stateRoot": "0x05f700ac2df8e2985433b63e3641e308976dd1a0dc0d29b600f71cc20939cba9", + "transactionsRoot": "0x8748b3f2e8d6a4a78a6f4b4b39e608cd3765505fd317020ba4c4a1aae4a6018c", + "receiptsRoot": "0x37cb5c601f1a535769d6a36c35f34c22fea9e9a1a011144381b8e3c7bb31657c", + "logsBloom": "0x00000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000800000000000000000000040100000000000000000000000008000000000000000000040000000000000000000000000000020000000000000000000800000000000000000000002010000000000000000000000000000100000000004000000000000000000001000000000000000000000000000000000000000000000000000000008000000000000000002000000002000000000000000000000000000000000000000040000000000060004000000000000000000000000000000000000200000000000000000000000000", + "difficulty": "0x0", + "number": "0x162", + "gasLimit": "0x1c9c380", + "gasUsed": "0x9dcf", + "timestamp": "0x67ef2c65", + "extraData": "0x726574682f76312e312e352f6c696e7578", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x7", + "withdrawalsRoot": "0x2f1a2b19c3801a44641db3f88532b818b585754dc389fd18ab6a0299e2ebdbc2", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0x80af8b91b32cae2e3c3b17ef0b6ce9124e01176bf953f192633a6cc718f129ba", + "hash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f" + } + |] + +testHeader :: BlockHeader +testHeader = case eitherDecodeStrictText headerStr of + Left err -> error $ "failed to decode header: " <> show err + Right (ObjectEncoded x) -> x + where + headerStr :: T.Text + headerStr = [r| + { + "nonce": "0", + "creationTime": 1743727718036200, + "parent": "gK-LkbMsri48OxfvC2zpEk4BF2v5U_GSYzpsxxjxKbo", + "adjacents": { + "10": "RJuqDTjhoclukSr7qPOkg0IOAcEJ1bIeltehw_-OHTo", + "5": "xOqpgPjJl0VehoCDX4se2i-cK1Ql_R75YHa1pAuSuWo", + "15": "B0iB0OTRlVtQgOyCpdomGfJTyXF92R8pcs06upXTOrY" + }, + "target": "PKUF-3dimGnG6F3iN6MvPUA1hTNt8eECAAAAAAAAAAA", + "payloadHash": "RXrRaAvhF74-xJcQzVT8ZHJqW7FtgP4y0WWQCy13w9U", + "chainId": 0, + "weight": "9puuQnDNZ7WAKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "height": 354, + "chainwebVersion": "evm-development", + "epochStart": 1743727581567100, + "featureFlags": 0, + "hash": "9NDFjt_VVE2_fJD54eLcybRfbp_BAOR2T2HxavWe7ho" + } + |] + + +-- EVM Development block (chain 0, height 354) +-- { +-- "receipts": [ +-- { +-- "type": "0x2", +-- "status": "0x1", +-- "cumulativeGasUsed": "0x9dcf", +-- "logs": [ +-- { +-- "address": "0x0826f2fc8902ca5afe82570560ba752a7f53675c", +-- "topics": [ +-- "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", +-- "0x000000000000000000000000fb8fb7f9bdc8951040a6d195764905138f7462ed", +-- "0x0000000000000000000000000000000000000000000000000000000000000000" +-- ], +-- "data": "0x0000000000000000000000000000000000000000000000056bc75e2d63100000", +-- "blockHash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f", +-- "blockNumber": "0x162", +-- "blockTimestamp": "0x67ef2c65", +-- "transactionHash": "0x3247ba77980ba47c6a59afc8d73bf5d70eff69e7f129b680b15451e45c3b4d5d", +-- "transactionIndex": "0x0", +-- "logIndex": "0x0", +-- "removed": false +-- }, +-- { +-- "address": "0x0826f2fc8902ca5afe82570560ba752a7f53675c", +-- "topics": [ +-- "0x9d2528c24edd576da7816ca2bdaa28765177c54b32fb18e2ca18567fbc2a9550", +-- "0x0000000000000000000000000000000000000000000000000000000000000001", +-- "0x000000000000000000000000ead0610198d4d802f9d14ec7e8d85d30233ccc6b", +-- "0x0000000000000000000000000000000000000000000000000000000000000001" +-- ], +-- "data": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000028f2d8ef4e0fe6b2e945cf5c33a0118a30a623540000000000000000000000000000000000000000000000056bc75e2d63100000", +-- "blockHash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f", +-- "blockNumber": "0x162", +-- "blockTimestamp": "0x67ef2c65", +-- "transactionHash": "0x3247ba77980ba47c6a59afc8d73bf5d70eff69e7f129b680b15451e45c3b4d5d", +-- "transactionIndex": "0x0", +-- "logIndex": "0x1", +-- "removed": false +-- } +-- ], +-- "logsBloom": "0x00000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000800000000000000000000040100000000000000000000000008000000000000000000040000000000000000000000000000020000000000000000000800000000000000000000002010000000000000000000000000000100000000004000000000000000000001000000000000000000000000000000000000000000000000000000008000000000000000002000000002000000000000000000000000000000000000000040000000000060004000000000000000000000000000000000000200000000000000000000000000", +-- "transactionHash": "0x3247ba77980ba47c6a59afc8d73bf5d70eff69e7f129b680b15451e45c3b4d5d", +-- "transactionIndex": "0x0", +-- "blockHash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f", +-- "blockNumber": "0x162", +-- "gasUsed": "0x9dcf", +-- "effectiveGasPrice": "0x3b9aca07", +-- "from": "0xfb8fb7f9bdc8951040a6d195764905138f7462ed", +-- "to": "0x0826f2fc8902ca5afe82570560ba752a7f53675c", +-- "contractAddress": null +-- } +-- ], +-- "payload": { +-- "parentHash": "0x692d784f999068270a78491f50404073f988a324598799866f740e2a52857a6a", +-- "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", +-- "miner": "0xd42d71cdc2a0a78fe7fbe7236c19925f62c442ba", +-- "stateRoot": "0x05f700ac2df8e2985433b63e3641e308976dd1a0dc0d29b600f71cc20939cba9", +-- "transactionsRoot": "0x8748b3f2e8d6a4a78a6f4b4b39e608cd3765505fd317020ba4c4a1aae4a6018c", +-- "receiptsRoot": "0x37cb5c601f1a535769d6a36c35f34c22fea9e9a1a011144381b8e3c7bb31657c", +-- "logsBloom": "0x00000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000800000000000000000000040100000000000000000000000008000000000000000000040000000000000000000000000000020000000000000000000800000000000000000000002010000000000000000000000000000100000000004000000000000000000001000000000000000000000000000000000000000000000000000000008000000000000000002000000002000000000000000000000000000000000000000040000000000060004000000000000000000000000000000000000200000000000000000000000000", +-- "difficulty": "0x0", +-- "number": "0x162", +-- "gasLimit": "0x1c9c380", +-- "gasUsed": "0x9dcf", +-- "timestamp": "0x67ef2c65", +-- "extraData": "0x726574682f76312e312e352f6c696e7578", +-- "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", +-- "nonce": "0x0000000000000000", +-- "baseFeePerGas": "0x7", +-- "withdrawalsRoot": "0x2f1a2b19c3801a44641db3f88532b818b585754dc389fd18ab6a0299e2ebdbc2", +-- "blobGasUsed": "0x0", +-- "excessBlobGas": "0x0", +-- "parentBeaconBlockRoot": "0x80af8b91b32cae2e3c3b17ef0b6ce9124e01176bf953f192633a6cc718f129ba", +-- "hash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f" +-- }, +-- "nonce": "0", +-- "creationTime": 1743727718036200, +-- "parent": "gK-LkbMsri48OxfvC2zpEk4BF2v5U_GSYzpsxxjxKbo", +-- "adjacents": { +-- "10": "RJuqDTjhoclukSr7qPOkg0IOAcEJ1bIeltehw_-OHTo", +-- "5": "xOqpgPjJl0VehoCDX4se2i-cK1Ql_R75YHa1pAuSuWo", +-- "15": "B0iB0OTRlVtQgOyCpdomGfJTyXF92R8pcs06upXTOrY" +-- }, +-- "target": "PKUF-3dimGnG6F3iN6MvPUA1hTNt8eECAAAAAAAAAAA", +-- "payloadHash": "RXrRaAvhF74-xJcQzVT8ZHJqW7FtgP4y0WWQCy13w9U", +-- "chainId": 0, +-- "weight": "9puuQnDNZ7WAKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", +-- "height": 354, +-- "chainwebVersion": "evm-development", +-- "epochStart": 1743727581567100, +-- "featureFlags": 0, +-- "hash": "9NDFjt_VVE2_fJD54eLcybRfbp_BAOR2T2HxavWe7ho" +-- } + From c9d907ce9f1ddeb26694805e15f67176c189d865 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 19 Apr 2025 17:54:39 +0200 Subject: [PATCH 109/378] improve evm-genesis tool --- cwtools/cwtools.cabal | 3 + cwtools/evm-genesis/Main.hs | 195 ++++++++++++++++++++++++++++++++++-- 2 files changed, 189 insertions(+), 9 deletions(-) diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index 5947402673..30f1c771e9 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -353,3 +353,6 @@ executable evm-genesis , base , network-uri , http-client + , process + , directory + , retry diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 71be41f437..0ef84fb05c 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -1,6 +1,8 @@ {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} -- | -- Module: evm-genesis.Main @@ -18,29 +20,89 @@ import Chainweb.PayloadProvider.EVM.EthRpcAPI qualified as E import Chainweb.PayloadProvider.EVM.Header qualified as E import Chainweb.PayloadProvider.EVM.JsonRPC qualified as E import Chainweb.Utils +import Control.Monad +import Data.Aeson import Data.String +import Data.Text qualified as T import Data.Text.IO qualified as T import Ethereum.Misc qualified as E import Ethereum.RLP qualified as E import Network.HTTP.Client qualified as HTTP import Network.URI +import Numeric.Natural +import System.Directory import System.Environment +import System.IO +import System.Process +import Text.Printf +import Control.Retry + +-- -------------------------------------------------------------------------- -- +-- Main main :: IO () main = do - -- read uri form command line - uri <- getArgs >>= \case - [str] -> fromText (fromString str) - l -> error $ "unexpected CLI arguments " <> show l + cids <- traverse (fromText . T.pack) =<< getArgs + hdrs <- forM cids $ \cid -> do + createDirectoryIfMissing True "./chain-specs" + let specFileName = "./chain-specs/chain-spec-" <> show cid <> ".json" + encodeFile specFileName $ specFile cid + hdr <- queryNode cid specFileName + return (cid, hdr) + T.putStrLn $ encodeToText + [ object + [ "chainId" .= cid + , "blockPayloadHash" .= E._hdrPayloadHash hdr + , "blockPayload" .= encodeB64UrlNoPaddingText (E.putRlpByteString hdr) + ] + | (cid, hdr) <- hdrs + ] + +-- -------------------------------------------------------------------------- -- +-- Querying the Node - -- query header +queryNode :: Natural -> FilePath -> IO E.Header +queryNode cid spec = withRethNode cid spec $ \uri -> do ctx <- mkRpcCtx uri hdr <- getBlockAtNumber ctx 0 + return hdr - -- print block payload hash - T.putStrLn $ encodeToText $ E._hdrPayloadHash hdr - -- encode header to base64 - T.putStrLn $ encodeB64UrlNoPaddingText $ E.putRlpByteString hdr +-- | Run reth node with chain-spec file at default port 8545 and execute an +-- action with the node URI. +-- +withRethNode :: Natural -> FilePath -> (URI -> IO a) -> IO a +withRethNode cid specfile act = + withCreateProcess runProc $ \_stdin _stdout _stderr _ph -> do + recoverAll policy $ \_ -> do + hPutStrLn stderr $ "Waiting for reth node for chain " <> show cid <> " to start..." + uri <- fromText (fromString ("http://localhost:" <> rethPort)) + r <- act uri + hFlush stdout + return r + where + policy = constantDelay 500_000 <> limitRetries 20 + runProc = proc cmd (dockerArgs <> rethArgs) + cmd = "docker" + dockerArgs = + [ "run" + , "--rm" + , "-t" + , "-p", rethPort <> ":" <> rethPort + , "--volume=" <> specfile <> ":/spec.json" + , "ghcr.io/kadena-io/evm-devnet-kadena-reth" + ] + rethArgs = + [ "--chain=/spec.json" + , "-q" + , "node" + , "--http" + , "--http.addr=0.0.0.0" + , "--http.port=" <> rethPort + , "--http.api=eth,rpc" + ] + -- we use different ports for each reth to avoid conflicts when ports are + -- not becoming available again fast enough + rethPort = show (8545 + cid) getBlockAtNumber :: E.JsonRpcHttpCtx @@ -62,3 +124,118 @@ mkRpcCtx u = do , E._jsonRpcHttpCtxMakeBearerToken = Nothing } +-- -------------------------------------------------------------------------- -- +-- Spec File For EVM Devnet + +specFile :: Natural -> Value +specFile cid = object [ + "config" .= object [ + "chainId" .= (1789 + cid - 20), + "daoForkSupport" .= True, + "terminalTotalDifficultyPassed" .= True, + "terminalTotalDifficulty" .= i 0, + "daoForkBlock" .= i 0, + "homesteadBlock" .= i 0, + "eip150Block" .= i 0, + "eip155Block" .= i 0, + "eip158Block" .= i 0, + "byzantiumBlock" .= i 0, + "constantinopleBlock" .= i 0, + "petersburgBlock" .= i 0, + "istanbulBlock" .= i 0, + "muirGlacierBlock" .= i 0, + "berlinBlock" .= i 0, + "londonBlock" .= i 0, + "arrowGlacierBlock" .= i 0, + "graphGlacierBlock" .= i 0, + "mergeForkBlock" .= i 0, + "mergeNetsplitBlock" .= i 0, + "shanghaiTime" .= i 0, + "cancunTime" .= i 0, + "pragueTime" .= i 0 + ], + "timestamp".= t "0x6490fdd2", + "extraData".= t "0x", + "gasLimit".= t "0x1c9c380", + "alloc".= object [ + "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ + "balance".= t "0x0", + "code".= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", + "storage".= object [ + "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) + ] + ], + "0x8849BAbdDcfC1327Ad199877861B577cEBd8A7b6".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xFB8Fb7f9bdc8951040a6D195764905138F7462Ed".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0x28f2d8ef4e0fe6B2E945cF5C33a0118a30a62354".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xa24a79678c9fffEF3E9A1f3cb7e51f88F173B3D5".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0x47fAE86F6416e6115a80635238AFd2F18D69926B".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0x87466A8266b9DFB3Dc9180a9c43946c4AB2c2cb2".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xA310Df9740eb6CC2F5E41C59C87e339142834eA4".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xD4EECE51cf451b60F59b271c5a748A8a9F16bC01".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xE08643a1C4786b573d739625FD268732dBB3d033".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0x33018A42499f10B54d9dBCeBB71831C805D64cE3".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xa3659D39C901d5985450eE18a63B5b0811fDa521".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0x7e99c2f1731D3750b74A2a0623C1F1DcB8cCa45e".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xFd70Bef78778Ce8554e79D97521b69183960C574".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xEE2722c39db6014Eacc5FBe43601136825b00977".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xeDD5a9185F9F1C04a011117ad61564415057bf8F".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0x99b832eb3F76ac3277b00beADC1e487C594ffb4c".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xda1380825f827C6Ea92DFB547EF0a341Cbe21d77".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0xc201d4A5E6De676938533A0997802634E859e78b".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0x03e95Af0fC4971EdCa12E6d2d1540c28314d15d5".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ], + "0x3492DA004098d728201fD82657f1207a6E5426bd".= object [ + "balance" .= t "0xd3c21bcecceda1000000" + ] + ], + "number" .= t "0x0", + "nonce" .= t "0x0", + "difficulty" .= t "0x0", + "mixHash" .= t "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase" .= t "0x0000000000000000000000000000000000000000" + ] + where + t :: T.Text -> T.Text + t = id + + i :: Natural -> Natural + i = id + From bb5fe7919ba8ef96d9fdebd50f23a4573241f4e4 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 21 Apr 2025 13:16:52 +0200 Subject: [PATCH 110/378] fix build of chainweb-node binary --- node/src/ChainwebNode.hs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 1dce10d360..90111245de 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -90,7 +90,7 @@ import Chainweb.Difficulty import Chainweb.Logger import Chainweb.Logging.Config import Chainweb.Logging.Miner -import Chainweb.Mempool.Consensus (ReintroducedTxsLog) +-- import Chainweb.Mempool.Consensus (ReintroducedTxsLog) import Chainweb.Mempool.InMemTypes (MempoolStats(..)) -- import Chainweb.Miner.Coordinator (MiningStats) import Chainweb.Pact.Backend.DbCache (DbCacheStats) @@ -170,9 +170,9 @@ pChainwebNodeConfiguration = id <*< nodeConfigDatabaseDirectory .:: fmap Just % textOption % long "database-directory" <> help "directory where the databases are persisted" - <*< nodeConfigResetChainDbs .:: enableDisableFlag - % long "reset-chain-databases" - <> help "Reset the chain databases for all chains on startup" + -- <*< nodeConfigResetChainDbs .:: enableDisableFlag + -- % long "reset-chain-databases" + -- <> help "Reset the chain databases for all chains on startup" getRocksDbDir :: HasCallStack => ChainwebNodeConfiguration -> IO FilePath getRocksDbDir conf = (\base -> base "0" "rocksDb") <$> getDbBaseDir conf @@ -328,7 +328,6 @@ runDatabaseMonitor logger rocksDbDir pactDbDir = L.withLoggerLabel ("component", node :: HasCallStack => Logger logger => ChainwebNodeConfiguration -> logger -> IO () node conf logger = do dbBaseDir <- getDbBaseDir conf - when (_nodeConfigResetChainDbs conf) $ removeDirectoryRecursive dbBaseDir rocksDbDir <- getRocksDbDir conf pactDbDir <- getPactDbDir conf dbBackupsDir <- getBackupsDir conf @@ -379,10 +378,10 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do $ withBaseHandleBackend "ChainwebApp" mgr pkgInfoScopes (_logConfigBackend logCfg) -- we don't log tx failures in replay - let !txFailureHandler = - if _configOnlySyncPact chainwebCfg || _configReadOnlyReplay chainwebCfg - then [dropLogHandler (Proxy :: Proxy Pact4TxFailureLog), dropLogHandler (Proxy :: Proxy Pact5TxFailureLog)] - else [] + -- let !txFailureHandler = + -- if _configOnlySyncPact chainwebCfg || _configReadOnlyReplay chainwebCfg + -- then [dropLogHandler (Proxy :: Proxy Pact5TxFailureLog)] + -- else [] -- Telemetry Backends monitorBackend <- managed @@ -406,8 +405,8 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do $ mkTelemetryLogger @RequestResponseLog mgr teleLogConfig queueStatsBackend <- managed $ mkTelemetryLogger @QueueStats mgr teleLogConfig - reintroBackend <- managed - $ mkTelemetryLogger @ReintroducedTxsLog mgr teleLogConfig + -- reintroBackend <- managed + -- $ mkTelemetryLogger @ReintroducedTxsLog mgr teleLogConfig traceBackend <- managed $ mkTelemetryLogger @Trace mgr teleLogConfig mempoolStatsBackend <- managed @@ -427,7 +426,7 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do $ L.withLogger (_logConfigLogger logCfg) $ logHandles (concat [ [ logFilterHandle (_logConfigFilter logCfg) ] - , txFailureHandler + -- , txFailureHandler , [ logHandler monitorBackend , logHandler p2pInfoBackend @@ -439,7 +438,7 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do -- , logHandler miningStatsBackend , logHandler requestLogBackend , logHandler queueStatsBackend - , logHandler reintroBackend + -- , logHandler reintroBackend , logHandler traceBackend , logHandler mempoolStatsBackend , logHandler blockUpdateBackend From e75781f2ee9fa0f9fa2d8f99e8683b33d13dc98c Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 21 Apr 2025 13:17:16 +0200 Subject: [PATCH 111/378] export all known chain graphs --- src/Chainweb/Graph.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Graph.hs b/src/Chainweb/Graph.hs index 9f7a148e47..eb65099d12 100644 --- a/src/Chainweb/Graph.hs +++ b/src/Chainweb/Graph.hs @@ -80,7 +80,11 @@ module Chainweb.Graph , petersenChainGraph , twentyChainGraph , hoffmanSingletonChainGraph - +, d3k4ChainGraph +, d4k3ChainGraph +, d4k4ChainGraph +, d5k3ChainGraph +, d5k4ChainGraph ) where import Control.Arrow ((&&&)) From 6d85226b6e8b3b67178fde2667cd1de050e6fbd8 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 21 Apr 2025 13:17:51 +0200 Subject: [PATCH 112/378] use 20 EVMs and d4k4 graph with EvmDevelopment --- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 54 +++++-- src/Chainweb/Version/EvmDevelopment.hs | 159 ++++++++++++++++---- 2 files changed, 174 insertions(+), 39 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index c6506489c3..13b6169d03 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -62,16 +62,10 @@ import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) -- -- How to get the headers: -- --- 1. Run the EVM with the chain specification for the network: +-- 1. Query the EVM genesis header and compute block payload hash and header: -- -- @ --- docker compose up -d chainweb-evm-chain0 --- @ --- --- 2. Query the EVM genesis header and compute block payload hash and header: --- --- @ --- cabal run cwtools:exe:evm-genesis -- http://localhost:8545 +-- cabal run cwtools:exe:evm-genesis -- @ -- genesisBlocks @@ -83,11 +77,47 @@ genesisBlocks genesisBlocks v c = go (_chainwebVersion v) (_chainId c) where -- Ethereum NetworkID 1789 - go EvmDevelopment (ChainId 0) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKubUszUzclOu3VLZ6Oz-L6Nph9pRr7Ilc6Sy0N9diB6oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 20) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGnV3kPaffv02pFz2XaK1abwEUqbp-L5K03hpucRJqlToFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" -- Ethereum NetworkID 1790 - go EvmDevelopment (ChainId 1) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKtIkoG2zy4Z3rQ8TVs8INePmK-1cYAD8aBvkf-57uIVoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 21) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJSFmb3gp1R0kVepD6Cb9wKqd4mvZhjGCfLWOJYhfOtXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 22) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIHnBqDOlLrBUgs1mmDAGcyoZGAiI4MDWIm5TSP2DfXHoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 23) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOG8PeZNPmM3lrR8_XYZiV77h9JISltItpJrMg3uWzISoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 24) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoL_zw0Kq1MfRVs38rgQLmxq1zFk8ac976rfmBmNiPvc0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 25) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAR82Mmf_nzgqOuk-JuK2c6y4cUsnjj_mthufymqwKl4oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 26) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoLIoqmCAur1Y7jpeSGyRpx71kbUqXT-R5dTjIswNvgyXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 27) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoEa3cdjo4w54WX1slUf3N6RgIFddjDWTfYNUg4mMF_ExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 28) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJGErv8Pw9R_vy2jSuU8h22F9iPtZMzBDYhF33EA0PGhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 29) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMh5sX_kYQkGl1nZ-RHbBlbBwbp-VcznAOB0nAVnXOzroFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 30) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGcOIoUzg5FTvy0MC0EZJsxo030YmtHVbISpQi0iaD3poFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 31) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNkhLvaaRHuHwXutFpApXpQMuKIUXeN0riiBvHCjpezvoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 32) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAZn-r-AVUqdftFL0aAYgnXg3Ihh3TpvNHF1Lk2f5dO7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 33) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoI5MZQFQY_vcdKf1l4cilyj0fjzDsAGNw5ZFew7mLNB7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 34) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFIGyZq62efyMMlN-YowvgItKWe3F4BY3CG2g_9q7jfRoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 35) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBV-hCyAvRGG3t7rNKZd1fKF1ZuQjEBKezJpX2508UDioFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 36) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFH9aUrJ9ByuptHRBLi9Qz5ZfxB9PHQo49SlwaBIgb6ZoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 37) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNMYg9Jdt7H6aQ8qvbWwbcqKrAYx8dFuK4ZQjkUPo-jNoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 38) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoArbi8-FGeh8uGxs7MxDXJUwVMznEnD_W-imVRojX9PnoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + go EvmDevelopment (ChainId 39) = f + "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoPitbBNoKjvjSCImGCqCmwST-kMGThKeiK5WSDMw-CfqoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" go _ _ = error "requested genesis block for unsupported chain" f t = case decodeB64UrlNoPaddingText t >>= decodeRlpM of diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 413893e2f5..281424ed04 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -27,6 +27,30 @@ pattern EvmDevelopment :: ChainwebVersion pattern EvmDevelopment <- ((== evmDevnet) -> True) where EvmDevelopment = evmDevnet +-- How to compute the hashes: +-- +-- Mininal Payload Provider: +-- +-- @ +-- -- create dummy payload hashes +-- import Chainweb.Payload.Provider.Minimal.Payload +-- import Chainweb.Version.Registry +-- import Chainweb.Version.EvmDevelopment +-- +-- registerVersion EvmDevelopment +-- mapM_ (\i -> T.putStrLn (sshow i <> " " <> encodeToText (view payloadHash $ genesisPayload EvmDevelopment $ unsafeChainId i))) [40..97] +-- @ +-- +-- EVM Payload Provider: +-- +-- @ +-- cabal run evm-genesis -- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +-- @ +-- +-- Pact Provider: +-- +-- TODO (use ea?) + evmDevnet :: ChainwebVersion evmDevnet = ChainwebVersion { _versionCode = ChainwebVersionCode 0x0000_000a @@ -37,7 +61,7 @@ evmDevnet = ChainwebVersion Pact5Fork -> AllChains ForkNever _ -> AllChains ForkAtGenesis , _versionUpgrades = AllChains mempty - , _versionGraphs = Bottom (minBound, twentyChainGraph) + , _versionGraphs = Bottom (minBound, d4k4ChainGraph) , _versionBlockDelay = BlockDelay 30_000_000 , _versionWindow = WindowWidth 120 , _versionHeaderBaseSizeBytes = 318 - 110 @@ -45,30 +69,111 @@ evmDevnet = ChainwebVersion , _versionGenesis = VersionGenesis { _genesisBlockTarget = AllChains $ HashTarget (maxBound `div` 500_000) , _genesisTime = onChains - $ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) - : (unsafeChainId 1, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) - : [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |]) | i <- [2..19] ] + $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0..19] ] + <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..39] ] + <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [40..97] ] , _genesisBlockPayload = onChains $ - [ (unsafeChainId 0, unsafeFromText "cRFtAlQ2aQn3xzMlieY3UGixp2a7z2eXdl4N4w6HRLg") - , (unsafeChainId 1, unsafeFromText "1lkrseazRVRa4-TYmVV2XcOjyY3Ba0KA-IX4r1bwwtI") - , (unsafeChainId 2, unsafeFromText "Gnh6QWze67ODyy4BoV4ZOeih72e_Cqos2BJM41sMgVc") - , (unsafeChainId 3, unsafeFromText "Ta08GYak3xnTr0HvJq9e37RTigd56N7m2aj_cxI1oC0") - , (unsafeChainId 4, unsafeFromText "eliqzAQ0JGxPD_73dwO7mXsX_tEOz6HJuLsDNJxqSd4") - , (unsafeChainId 5, unsafeFromText "F7-cmj0XXGKxjKm-dDSmMSpD9jwCjzrdQmwQgsjPj2g") - , (unsafeChainId 6, unsafeFromText "VK7rBExdlAUo9maErP19WJSgVMTc37xpEa_VXWELF74") - , (unsafeChainId 7, unsafeFromText "CnAxuzvToxp-bNZ_lnhCAJCEU4hzXuNJmGRMJx5bBWE") - , (unsafeChainId 8, unsafeFromText "abMl-fqTLY1EmiiFILE_orgsbAB_kKAshAx-zIQFoEM") - , (unsafeChainId 9, unsafeFromText "o5G8VKfB7I1Qv_Y8paCFHIS6vZuMYUgYBtV6-fDqzYA") - , (unsafeChainId 10, unsafeFromText "1DJiGUHIrXDNS7M3RJlUorw_07gjLRetb1q9tlt7aZs") - , (unsafeChainId 11, unsafeFromText "ot-tryHcaC7uvHkT1MqYsbLDV1ApZa2KGvjzddIonYU") - , (unsafeChainId 12, unsafeFromText "MjGGd7Osb09dEE2mfpDFISrAfWNnDqEqEYuRm5nFXSs") - , (unsafeChainId 13, unsafeFromText "ljaDzDMfFMOO4AZYC63_KIPAINZwRXUzKZ5FyB1pDjs") - , (unsafeChainId 14, unsafeFromText "W9lvCyH_NAJx89mnCQcwl5OknI89IM_5rn_TR6KkobQ") - , (unsafeChainId 15, unsafeFromText "PGKV488wnfsEgv28CtuAT16JNWmMRB-42TDMIr4jRGQ") - , (unsafeChainId 16, unsafeFromText "8J1yMti75X1Gnjn2AEpWMw-8nzOK6ysHo5c4SBIiNGo") - , (unsafeChainId 17, unsafeFromText "XlIsxdG3YnbxapDq71wY85-ghlIK3c5vfD_WEXgKcRM") - , (unsafeChainId 18, unsafeFromText "968Xg-0Jqm1nTgFi69or2yuFprmg7_SDKcrcWIItW74") - , (unsafeChainId 19, unsafeFromText "7CRqrZPgJ9JYuCWAQtO2bqxlFDUw_i2Hlk52duk8A0s") + -- Minimal Payload Provider + [ (unsafeChainId 0, unsafeFromText "rXG-6Jg02UjBYZt0OIwxZ3QtdnwH-C7pX4cX5FHmGE8") + , (unsafeChainId 1, unsafeFromText "xeJGd3yT9SII6Uzm7AwOjoyTSjRGJUI-hC5gBWKLGkw") + , (unsafeChainId 2, unsafeFromText "lXQTK0XTngWwc-POGHOg9_MBqiObaZCXDKR08WfWfL8") + , (unsafeChainId 3, unsafeFromText "GD4-7rjII-MGkVBnXurpn0Pb4aMolNnKDvmlKGMaPRA") + , (unsafeChainId 4, unsafeFromText "iZRTSTlKtzqpgMb0LDukvb9U55RlAJuxXVDWIVYXTIU") + , (unsafeChainId 5, unsafeFromText "B0AotFJgnzdUZo3pHBTT2E9LItJevMGKlYDK4mF9ZzQ") + , (unsafeChainId 6, unsafeFromText "X-TifEHiGkavUAbHgQ0AwtAm6ctCHTkKqg4FVFIzFHQ") + , (unsafeChainId 7, unsafeFromText "WWxH5-JZYdNgRo1KIVzts6YyW8Df1eP8fin4bx4QAM4") + , (unsafeChainId 8, unsafeFromText "TugVzxHnoOEO1nLjsd2IDTHXE1Z2E83sG-OrFgogaBE") + , (unsafeChainId 9, unsafeFromText "2k748hkHJZgRvwJAsc1bxTvWzbbo4d2oSW719NrcS9g") + , (unsafeChainId 10, unsafeFromText "TCfeZhl_v8YJr77HIbxNkWB-j0KQXwmJQx_lkJSpB0w") + , (unsafeChainId 11, unsafeFromText "ZQ7__f2OWthXBnzSWNp80HM5dEz9AyYQ_VCaNKwiX_c") + , (unsafeChainId 12, unsafeFromText "aY5Mc9L2FrkrN6-z-gDBJbuPO5I7B5Cz6giyGSiMzKk") + , (unsafeChainId 13, unsafeFromText "XtZwHbCPrs0ByLZ-1a-hvC_u8GE93tHk-e0uCPqXUz8") + , (unsafeChainId 14, unsafeFromText "A97xRHSsEO9Hn0WdVwZPVNHClzDj-4KXr4Heqf0x36A") + , (unsafeChainId 15, unsafeFromText "iU54vz0G3eiWwXiyeK9FAiqMGV0dqD7TfdgQRLVrVg0") + , (unsafeChainId 16, unsafeFromText "hh_No_CZO9S28lQvU9SQin-ZsxjfIedX_zuYABfoYGQ") + , (unsafeChainId 17, unsafeFromText "tQ0TWds1dMFX9E8EtEXndtmhEDDYtWrgpkv6NmVmJaY") + , (unsafeChainId 18, unsafeFromText "gMs-6QtsYgghAZqTNZ7DBlyOHR2XryNIE4n9rpn-h_I") + , (unsafeChainId 19, unsafeFromText "Evv6R7db1V3t2mrDTosjpL4fjrN9Cn2vdr8tE34hPUY") + -- EVM Payload Provider + , (unsafeChainId 20, unsafeFromText "fSrq8wiBgto56jF8jWWmQ4BCMoR2XzTsFh8RYuFyvJM") + , (unsafeChainId 21, unsafeFromText "_Wv_1kmEUCWjtDvnhyfcWoZMsml2ezuR1YwwUQOZqH0") + , (unsafeChainId 22, unsafeFromText "pKDYEr-_RxOla6FgAosDSGjNXIDnksAiNxKOAiRbDOE") + , (unsafeChainId 23, unsafeFromText "p0ivYZAy5SAVh5QH0loiORXgcZqwyOv1yvE5Wb0Eh1w") + , (unsafeChainId 24, unsafeFromText "xqi3q2Ycy_7QfU80739ok6C2jSHZNBqshVh45mLVcdw") + , (unsafeChainId 25, unsafeFromText "JIZ16mtTtCjH47cX6tiA_sS0DdvHN3N0s8d9-LIJbNk") + , (unsafeChainId 26, unsafeFromText "8Pm1VpSzWLpcQ7T4Hf4TWJ2oeztescGg_U95FdZq6Vg") + , (unsafeChainId 27, unsafeFromText "Ac24HD57R_4W8qrAAqe3tdwoLlaR-3L0ES2p1_2JFLE") + , (unsafeChainId 28, unsafeFromText "-lLFm9Lu6eDgyFaNI9msdaHazUqCWJP44PIZTmfGUf0") + , (unsafeChainId 29, unsafeFromText "uguTxEXodT8r8ICE6gZmi7l9aD_9nB-cBal_-JBpZ_M") + , (unsafeChainId 30, unsafeFromText "GlmowvGyANjcmlWsD0LJHki8E71H-4Yo8YGBMPYWhfw") + , (unsafeChainId 31, unsafeFromText "RpbZ_CmoCL7ZEdzRWjAoLCR2kbLUMMzxuwu-UndxR7o") + , (unsafeChainId 32, unsafeFromText "n0FR2nBGo4HbyxLoIClmwzZT0RznxxbJcaCkkTd3X7Y") + , (unsafeChainId 33, unsafeFromText "Of2R_J3W8i6-H4JgjmVmsIp3bH0bm1ZXyg_5FzYV6c8") + , (unsafeChainId 34, unsafeFromText "eeun0LRSyrinygZmVeUfqBzKJ4K2LoCRPQ7255cE7mg") + , (unsafeChainId 35, unsafeFromText "FM_h3zF-BIdMhsKoDsFU_xJOnF2WXp4ue6tMCG25oF8") + , (unsafeChainId 36, unsafeFromText "gveQfdrWNSERjopJqzEPVygNfOddVTp9NRDHcS3KXa0") + , (unsafeChainId 37, unsafeFromText "3_8fKJHVlJTKrmlYeLe5BE7gVpj4wjPg_jBxHsAkV24") + , (unsafeChainId 38, unsafeFromText "oEbS9GxnyOsx509ujJqfp1iYtQzDmMspqaXJUrFxaqY") + , (unsafeChainId 39, unsafeFromText "RV9VQgd5sMMK0flHPFUAhS804gz7z1tN39g4H9lkpfg") + -- Minimal Payload Provider + , (unsafeChainId 40, unsafeFromText "H3VBsNGh-SQE-0d_qlYSHnS2obzUeo6Zi1XDDvhndYo") + , (unsafeChainId 41, unsafeFromText "N6hVHz6vo0frpS3eyqvtMeZg1eFbAMJ1CS315M-JpWw") + , (unsafeChainId 42, unsafeFromText "9mo8CRwvTLLJ4cSQtErBfOIxzwpale-AwnbXPWQd184") + , (unsafeChainId 43, unsafeFromText "SotTkMw2Eq5UbOObv5kyaUlMqHp6-PUSKDeLHWYCr2s") + , (unsafeChainId 44, unsafeFromText "1IB27CAQ6MpXFV-OiyDVIxbXh91BK2Bl6PYiAwvyg-E") + , (unsafeChainId 45, unsafeFromText "ou9ns1_Q72IsaXoVjCErGGGmzsI07IIx6Vo14gU0Fl8") + , (unsafeChainId 46, unsafeFromText "dZsEBdKKdeLkTS35IV54npY01mB9HOWO3TvoXE0xoWg") + , (unsafeChainId 47, unsafeFromText "oLbpBWhnhCdKHy_q009-06PYug12KMtA5u9mv82_I8s") + , (unsafeChainId 48, unsafeFromText "coBTWu6iFvyDX_3W2dSnuwK9WheRa9_40kh564myiXw") + , (unsafeChainId 49, unsafeFromText "fiOvn4JEAf7NXGAP3YkXhygalCnKCwzhe2dC1VO4YiU") + , (unsafeChainId 50, unsafeFromText "UL6_gjNoxuryRd3xBj3OU00A6IjvHtdnggucOISDgc4") + , (unsafeChainId 51, unsafeFromText "Wtu06D5r5DcNSZ8ZxxPv3Jvq4dtYW1PnHJtrxmdVSzw") + , (unsafeChainId 52, unsafeFromText "yrSDpQnAtnw-WYVrXMd-zAt2ZCOBNz7mACG-UjX5Tl0") + , (unsafeChainId 53, unsafeFromText "c743P-dTKKA6PpKCJ6ZC9im7bo1BpJWViZ6xjZJokkw") + , (unsafeChainId 54, unsafeFromText "qlj-G-PO_TtM5mp2C6UI6GgVWR2H1um2v6VOMrXjX4M") + , (unsafeChainId 55, unsafeFromText "XO6ZWLmRlyiGygt2pdDZpxZfwHrmkLsBM99rJSxa31M") + , (unsafeChainId 56, unsafeFromText "jiXkFn_Nv73-X8d3xUtsY25lNN2g35sjSsu43X1pOEM") + , (unsafeChainId 57, unsafeFromText "Dr15tRuU6JSXOARB46_r1DGb9e2WX4a61BoiJ9Uq5p0") + , (unsafeChainId 58, unsafeFromText "KYlhzAW01sBwnO2dXl9_0BuRNV64nJJCSSo6JdDNMZA") + , (unsafeChainId 59, unsafeFromText "ywo1yl72s89SQibkkKuRm4tmBnp8guONArOLa03lETU") + , (unsafeChainId 60, unsafeFromText "t4u3_IuTXANdi2NrM2prmWCOFSc3AkrwHYziL1LSsEU") + , (unsafeChainId 61, unsafeFromText "Ucp32OmDetbZPozGHES6F7HKqbAnIfynOsfCzUo3lDI") + , (unsafeChainId 62, unsafeFromText "VEsZDVjM1lJkfJUWTXEyC7wH27vgDoviFD2Rt22vJ1I") + , (unsafeChainId 63, unsafeFromText "NDsftZSHa8N1yDdkgQJQ1rk1J3vRFFxFzCSrd8SzEUo") + , (unsafeChainId 64, unsafeFromText "80yIgBalnINZyprtYhZVCgOgMbB2DoW5Xq42FJf7nKk") + , (unsafeChainId 65, unsafeFromText "BMi5YZ0GpYTemGe6x_FtXEI5JTO9rinekaNnhEV87Jc") + , (unsafeChainId 66, unsafeFromText "uAIl8FGVbOPzitFHovTPJtPHaCQzYA8ZOaE6PxBx3Ng") + , (unsafeChainId 67, unsafeFromText "tmzmkzngbTHNfywBySBDE-OLXwhjgn2gNhqK86uFRXk") + , (unsafeChainId 68, unsafeFromText "MNkeZut1raJk7-Vh6Yf2HR-Lhf7x97ZYqtZvM-czHmA") + , (unsafeChainId 69, unsafeFromText "BMa_Ucv2c0Q9TU9wE8HZaX5hFv919yqh57s_ijfb0aA") + , (unsafeChainId 70, unsafeFromText "YZlG4QzDr285OQMCs5k9ZS6SNYSNjq2gr9RnJ1DmyBw") + , (unsafeChainId 71, unsafeFromText "ZDwNBLBZSTRertPlENXC6CUj6StykJLucUnN_DydnLc") + , (unsafeChainId 72, unsafeFromText "BtxzFZrXiRYOMouRmU7eA9pohvahn_GdKPREhcuGCY4") + , (unsafeChainId 73, unsafeFromText "cGIf74TXx8V_XrUUr2B9MU8adtQeQc1hpk6XOey1GOU") + , (unsafeChainId 74, unsafeFromText "j3hvD0Yjztjc_trqX4OMHPOqEWTd04GKvPfmt4r92D8") + , (unsafeChainId 75, unsafeFromText "eSIZD24zvKNw-2OJtWJujayy3AKU2h11RhRWZUC6MtM") + , (unsafeChainId 76, unsafeFromText "P3H3_4I6vJTQOszcrYreI6LOhSRVgVv7Hb0nFawzbsM") + , (unsafeChainId 77, unsafeFromText "CKomlT6tiEs6DCa2VNy3519TjAiwFNx8EkkI54JKY0I") + , (unsafeChainId 78, unsafeFromText "2NldG3su7R_RXpes25X09t8evnjNMuZoL8j3PcNMfXE") + , (unsafeChainId 79, unsafeFromText "fMlZOHs-mzu5u2DXAiCKzhZNOlCMROY2YXxHMCdiHyo") + , (unsafeChainId 80, unsafeFromText "mDocxA63bstQQ-vqzM02_avZmSjrPFFxcvcVa6ZCYMU") + , (unsafeChainId 81, unsafeFromText "hqkNDhLpy-9usTAyu77mvwoCD8YDlW9O66EFQ98ARsg") + , (unsafeChainId 82, unsafeFromText "jSGKQqn_KP4RbFsbTT_6VWFTj9WOqAqv-INoMyJntAw") + , (unsafeChainId 83, unsafeFromText "fvOd-k-4j_OFmAQo1M2Vy1T2O18UkpcjRYlSMVcjxXo") + , (unsafeChainId 84, unsafeFromText "s26jheDzrGXuWZ8mddMkIDU9UICfsRg9z-TQwDCRWos") + , (unsafeChainId 85, unsafeFromText "ZTR2dcyy-ZgOX5OVsKMV0t4Bkp4mf2ocvUs8KGZVw74") + , (unsafeChainId 86, unsafeFromText "AnmY1tCYiwIf35bZZXXdx3ZVOfmwsT0jvOlLA0s0NHY") + , (unsafeChainId 87, unsafeFromText "aBL54Dj7EkUqrrWykyQfFGa9vCf3nS34QogArm33188") + , (unsafeChainId 88, unsafeFromText "YGs9zbwtzR0FqoVwAITceumfKfCMilFYfSxkJU6pf70") + , (unsafeChainId 89, unsafeFromText "J1CdN2TlCux7xAoX8m7fdGyJHV_IeEgboyQMvI2ToO8") + , (unsafeChainId 90, unsafeFromText "_mphcuK5KOq2_-DQB9bqlM6C4f6eAnO7cCLSK--gLsE") + , (unsafeChainId 91, unsafeFromText "TvoF0OcZ86zj_C9nExaubCzXRgGTGMQ9wViM1Zm0f-A") + , (unsafeChainId 92, unsafeFromText "dAAvn6c3IkClNgEpjSTt-ZrPCG8YcqBsma_vvLnRji4") + , (unsafeChainId 93, unsafeFromText "RBJolQY0GKyqcGBUef18tAr51aRS52IQ8HJoJX6EPR8") + , (unsafeChainId 94, unsafeFromText "lPLkYdoQHw_auHSFnlcNL3fI_oI2b5jCCpaSXbw5rZ4") + , (unsafeChainId 95, unsafeFromText "M4Webe0zta7bJ_53pHTIjU5d25TLG6FISfiLw1eFFgg") + , (unsafeChainId 96, unsafeFromText "jiQb8cx7bl48fvqA6QeLXh_YXP2Bzg8gSroKGfceqUk") + , (unsafeChainId 97, unsafeFromText "ZE5xfgDK6KW4q8o98qCWZ4NJL74NiMG1hu3DUZrHatI") ] } @@ -91,7 +196,7 @@ evmDevnet = ChainwebVersion -- FIXME make this safe for graph changes , _versionPayloadProviderTypes = onChains - $ (unsafeChainId 0, EvmProvider 1789) - : (unsafeChainId 1, EvmProvider 1790) - : [ (unsafeChainId i, MinimalProvider) | i <- [2..19] ] + $ [ (unsafeChainId i, MinimalProvider) | i <- [0..19] ] + <> [ (unsafeChainId i, EvmProvider (1789 - 20 + int i)) | i <- [20..39] ] + <> [ (unsafeChainId i, MinimalProvider) | i <- [40..97] ] } From b94ae5d951db35903648b441d1de6a34c5852759 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 21 Apr 2025 22:59:06 +0200 Subject: [PATCH 113/378] update freeze file --- cabal.project.freeze | 133 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 7 deletions(-) diff --git a/cabal.project.freeze b/cabal.project.freeze index 92b56ea816..b3957403d2 100644 --- a/cabal.project.freeze +++ b/cabal.project.freeze @@ -1,33 +1,45 @@ active-repositories: hackage.haskell.org:merge -constraints: any.Cabal ==3.12.1.0, - any.Cabal-syntax ==3.12.1.0, +constraints: any.Cabal ==3.10.2.0, + any.Cabal-syntax ==3.10.2.0, any.Decimal ==0.5.2, any.Diff ==1.0.2, any.Glob ==0.10.2, any.HUnit ==1.6.2.0, any.JuicyPixels ==3.3.9, + JuicyPixels -mmap, any.OneTuple ==0.4.2, any.Only ==0.1, any.QuickCheck ==2.15.0.1, + QuickCheck -old-random +templatehaskell, any.RSA ==2.4.1, any.SHA ==1.6.4.4, + SHA -exe, any.StateVar ==1.2.2, any.adjunctions ==4.4.3, any.aeson ==2.2.3.0, + aeson +ordered-keymap, any.aeson-pretty ==0.8.10, + aeson-pretty -lib-only, any.alex ==3.5.3.0, any.ansi-terminal ==1.1.2, + ansi-terminal -example, any.ansi-terminal-types ==1.1, any.ap-normalize ==0.1.0.1, + ap-normalize -test-with-clang, any.appar ==0.1.8, any.array ==0.5.6.0, any.asn1-encoding ==0.9.6, any.asn1-parse ==0.9.5, any.asn1-types ==0.3.4, any.assoc ==1.1.1, + assoc -tagged, any.async ==2.2.5, + async -bench, any.atomic-primops ==0.8.8, + atomic-primops -debug, any.attoparsec ==0.14.4, + attoparsec -developer, + any.attoparsec-aeson ==2.2.2.0, any.authenticate-oauth ==1.7, any.auto-update ==0.2.6, any.barbies ==2.1.1.0, @@ -36,40 +48,56 @@ constraints: any.Cabal ==3.12.1.0, any.base-compat-batteries ==0.14.1, any.base-orphans ==0.9.3, any.base-unicode-symbols ==0.2.4.2, + base-unicode-symbols +base-4-8 -old-base, + any.base16 ==1.0, any.base16-bytestring ==1.0.2.0, any.base64-bytestring ==1.2.1.0, any.base64-bytestring-kadena ==0.1, any.basement ==0.0.16, any.bifunctors ==5.6.2, + bifunctors +tagged, any.binary ==0.8.9.1, any.binary-orphans ==1.0.5, any.bitvec ==1.1.5.0, + bitvec +simd, any.blaze-builder ==0.4.2.3, any.blaze-html ==0.9.2.0, any.blaze-markup ==0.8.3.0, any.boring ==0.2.2, + boring +tagged, any.bound ==2.0.7, + bound +template-haskell, any.bsb-http-chunked ==0.0.0.4, any.bytebuild ==0.3.16.3, + bytebuild -checked, any.byteorder ==1.0.4, any.bytes ==0.17.4, any.byteslice ==0.2.14.0, + byteslice +avoid-rawmemchr, any.bytesmith ==0.3.11.1, any.bytestring ==0.12.1.0, any.bytestring-builder ==0.10.8.2.0, + bytestring-builder +bytestring_has_builder, + any.cabal-doctest ==1.0.11, any.cache ==0.1.3.0, any.call-stack ==0.4.0, any.case-insensitive ==1.2.1.0, any.cassava ==0.5.3.2, any.cborg ==0.2.10.0, + cborg +optimize-gmp, any.cereal ==0.5.8.3, + cereal -bytestring-builder, chainweb -debug -ed25519 -ghc-flags, chainweb-node -debug -ed25519 -ghc-flags, any.character-ps ==0.1, any.charset ==0.3.12, any.chronos ==1.1.6.2, any.citeproc ==0.8.1.3, + citeproc -executable -icu, any.clock ==0.8.4, + clock -llvm, + any.cmdargs ==0.10.22, + cmdargs +quotation -testprog, any.co-log-core ==0.3.2.5, any.code-page ==0.2.1, any.colour ==2.3.6, @@ -77,30 +105,37 @@ constraints: any.Cabal ==3.12.1.0, any.commonmark-extensions ==0.2.6, any.commonmark-pandoc ==0.2.3, any.comonad ==5.0.9, + comonad +containers +distributive +indexed-traversable, any.concurrent-output ==1.10.21, any.conduit ==1.3.6.1, any.conduit-extra ==1.3.7, any.configuration-tools ==0.7.1, + configuration-tools -remote-configs, any.constraints ==0.14.2, any.containers ==0.6.8, any.contiguous ==0.6.4.2, any.contravariant ==1.5.5, + contravariant +semigroups +statevar +tagged, any.cookie ==0.5.1, any.criterion ==1.6.4.0, + criterion -embed-data-files -fast, any.criterion-measurement ==0.2.3.0, + criterion-measurement -fast, any.crypto-api ==0.13.3, + crypto-api -all_cpolys, any.crypto-pubkey-types ==0.4.3, any.crypto-token ==0.1.2, any.cryptohash-md5 ==0.11.101.0, any.cryptohash-sha1 ==0.11.101.0, any.crypton ==1.0.4, + crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes, any.crypton-connection ==0.4.4, any.crypton-x509 ==1.7.7, any.crypton-x509-store ==1.6.10, any.crypton-x509-system ==1.6.7, any.crypton-x509-validation ==1.6.14, any.cryptonite ==0.30, - any.cuckoo ==0.3.1, + cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes, cwtools -debug -ed25519 -ghc-flags -remote-db, any.data-bword ==0.1.0.2, any.data-default ==0.8.0.1, @@ -113,19 +148,25 @@ constraints: any.Cabal ==3.12.1.0, any.dense-linear-algebra ==0.1.0.0, any.deriving-compat ==0.6.7, any.digest ==0.0.2.1, + digest -have_arm64_crc32c -have_builtin_prefetch -have_mm_prefetch -have_sse42 -have_strong_getauxval -have_weak_getauxval +pkg-config, any.digraph ==0.3.2, any.direct-sqlite ==2.3.29, + direct-sqlite +dbstat +fulltextsearch +haveusleep +json1 -mathfunctions -systemlib +urifilenames, any.directory ==1.3.8.1, any.distributive ==0.6.2.1, + distributive +semigroups +tagged, any.djot ==0.1.2.2, any.dlist ==1.0, + dlist -werror, any.doclayout ==0.5, any.doctemplates ==0.11.0.1, any.easy-file ==0.2.5, any.ech-config ==0.0.1, + ech-config -devel, any.emojis ==0.1.4.1, any.enclosed-exceptions ==1.0.3, any.entropy ==0.4.1.11, + entropy -donotgetentropy, any.erf ==2.0.0.0, any.errors ==2.3.0, any.ethereum ==0.1.0.2, @@ -138,7 +179,9 @@ constraints: any.Cabal ==3.12.1.0, any.fingertree ==0.1.5.0, any.finite-typelits ==0.2.1.0, any.free ==5.2, + any.generic-arbitrary ==1.0.1.2, any.generic-data ==1.1.0.2, + generic-data -enable-inspect, any.generic-lens ==2.2.2.0, any.generic-lens-core ==2.2.1.0, any.generically ==0.1.1, @@ -155,7 +198,9 @@ constraints: any.Cabal ==3.12.1.0, any.happy ==2.1.5, any.happy-lib ==2.1.5, any.hashable ==1.5.0.0, + hashable -arch-native -random-initial-seed, any.hashes ==0.3.0.1, + hashes -benchmark-cryptonite -openssl-use-pkg-config -test-cryptonite +with-openssl, any.haskeline ==0.8.2.1, any.haskell-lexer ==1.2.1, any.haskell-src-exts ==1.23.1, @@ -164,14 +209,19 @@ constraints: any.Cabal ==3.12.1.0, any.hedgehog ==1.5, any.hourglass ==0.2.12, any.hpke ==0.0.0, + any.hsc2hs ==0.68.10, + hsc2hs -in-ghc-tree, any.http-api-data ==0.6.2, + http-api-data -use-text-show, any.http-client ==0.7.19, + http-client +network-uri, any.http-client-tls ==0.3.6.4, any.http-date ==0.0.11, any.http-media ==0.8.1.1, any.http-semantics ==0.3.0, any.http-types ==0.12.4, any.http2 ==5.3.9, + http2 -devel -h2spec, any.indexed-list-literals ==0.2.1.3, any.indexed-profunctors ==0.1.1.1, any.indexed-traversable ==0.1.4, @@ -179,6 +229,7 @@ constraints: any.Cabal ==3.12.1.0, any.integer-conversion ==0.1.1, any.integer-gmp ==1.1, any.integer-logarithms ==1.0.4, + integer-logarithms -check-bounds +integer-gmp, any.invariant ==0.6.4, any.iproute ==1.7.15, any.ipynb ==0.2, @@ -187,43 +238,57 @@ constraints: any.Cabal ==3.12.1.0, any.js-chart ==2.9.4.1, any.kan-extensions ==5.2.6, any.lens ==5.3.4, + lens -benchmark-uniplate -dump-splices +inlining -j +test-hunit +test-properties +test-templates +trustworthy, any.lens-aeson ==1.2.3, any.libyaml ==0.1.4, + libyaml -no-unicode -system-libyaml, any.libyaml-clib ==0.2.5, any.lifted-async ==0.10.2.7, any.lifted-base ==0.2.3.12, any.loglevel ==0.1.0.0, any.lrucaching ==0.3.4, any.lsp ==2.7.0.1, + lsp -demo, any.lsp-types ==2.3.0.1, + lsp-types -force-ospath, any.managed ==1.0.10, any.massiv ==1.0.4.1, + massiv -unsafe-checks, any.math-functions ==0.3.4.4, + math-functions +system-erf +system-expm1, any.megaparsec ==9.7.0, + megaparsec -dev, any.memory ==0.18.0, + memory +support_bytestring +support_deepseq, any.merkle-log ==0.2.0, any.microlens ==0.4.14.0, any.microstache ==1.0.3, any.mime-types ==0.1.2.0, any.mmorph ==1.2.0, any.mod ==0.2.0.1, + mod +semirings +vector, any.monad-control ==1.0.3.1, any.mono-traversable ==1.0.21.0, any.mtl ==2.3.1, any.mtl-compat ==0.2.2, + mtl-compat -two-point-one -two-point-two, any.mwc-probability ==2.3.1, any.mwc-random ==0.15.2.0, + mwc-random -benchpapi, any.natural-arithmetic ==0.2.2.0, any.neat-interpolation ==0.5.1.4, any.network ==3.2.7.0, + network -devel, any.network-byte-order ==0.1.7, any.network-control ==0.1.6, any.network-info ==0.2.1, any.network-uri ==2.6.4.2, any.nothunks ==0.3.0.0, + nothunks +bytestring +text +vector, any.old-locale ==1.0.0.7, any.old-time ==1.1.0.4, any.optparse-applicative ==0.18.1.0, + optparse-applicative +process, any.ordered-containers ==0.2.4, any.os-string ==2.0.7, any.pact ==4.13.2, @@ -231,21 +296,27 @@ constraints: any.Cabal ==3.12.1.0, any.pact-json ==0.1.0.0, any.pact-time ==0.3.0.1, pact-time -with-time, - any.pact-tng ==5.1, + any.pact-tng ==5.2, pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, any.pandoc ==3.6.4, + pandoc -embed_data_files, any.pandoc-types ==1.23.1, any.parallel ==3.2.2.0, any.parsec ==3.1.17.0, any.parser-combinators ==1.3.0, + parser-combinators -dev, any.parsers ==0.12.12, + parsers +attoparsec +binary +parsec, any.patience ==0.3, any.pem ==0.2.4, any.poly ==0.5.1.0, + poly +sparse, any.pretty ==1.1.3.6, any.pretty-show ==1.10, any.pretty-simple ==4.1.3.0, + pretty-simple -buildexample +buildexe, any.prettyprinter ==1.7.1, + prettyprinter -buildreadme +text, any.prettyprinter-ansi-terminal ==1.1.3, any.primitive ==0.9.1.0, any.primitive-addr ==0.1.0.3, @@ -253,21 +324,27 @@ constraints: any.Cabal ==3.12.1.0, any.primitive-unlifted ==2.1.0.0, any.process ==1.6.18.0, any.profunctors ==5.6.2, - any.property-matchers ==0.4.0.0, + any.property-matchers ==0.7.0.0, any.psqueues ==0.2.8.1, any.pvar ==1.0.0.0, any.quickcheck-instances ==0.3.32, any.ralist ==0.4.0.0, any.random ==1.3.1, + any.raw-strings-qq ==1.1, any.recover-rtti ==0.5.0, any.recv ==0.1.0, any.reducers ==3.12.5, any.reflection ==2.1.9, + reflection -slow +template-haskell, + any.regex ==1.1.0.2, any.regex-base ==0.94.0.3, + any.regex-pcre-builtin ==0.95.2.3.8.44, any.regex-tdfa ==1.3.2.3, + regex-tdfa +doctest -force-o2, any.resource-pool ==0.4.0.0, any.resourcet ==1.3.0, any.retry ==0.9.3.1, + retry -lib-werror, any.rocksdb-haskell-kadena ==1.1.0, rocksdb-haskell-kadena -with-tbb, any.row-types ==1.0.1.2, @@ -278,11 +355,17 @@ constraints: any.Cabal ==3.12.1.0, any.safecopy ==0.10.4.2, any.scheduler ==2.0.1.0, any.scientific ==0.3.8.0, + scientific -integer-simple, any.semialign ==1.3.1, + semialign +semigroupoids, any.semigroupoids ==6.0.1, + semigroupoids +comonad +containers +contravariant +distributive +tagged +unordered-containers, any.semigroups ==0.20, + semigroups +binary +bytestring -bytestring-builder +containers +deepseq +hashable +tagged +template-haskell +text +transformers +unordered-containers, any.semirings ==0.7, + semirings +containers +unordered-containers, any.serialise ==0.2.6.1, + serialise +newtime15, any.servant ==0.20.2, any.servant-client ==0.20.2, any.servant-client-core ==0.20.2, @@ -290,9 +373,12 @@ constraints: any.Cabal ==3.12.1.0, any.sha-validation ==0.1.0.1, any.show-combinators ==0.2.0.0, any.simple-sendfile ==0.2.32, + simple-sendfile +allow-bsd -fallback, any.singleton-bool ==0.1.8, any.skylighting ==0.14.6, + skylighting -executable, any.skylighting-core ==0.14.6, + skylighting-core -executable, any.skylighting-format-ansi ==0.1, any.skylighting-format-blaze-html ==0.1.1.3, any.skylighting-format-context ==0.1.0.2, @@ -300,23 +386,31 @@ constraints: any.Cabal ==3.12.1.0, any.skylighting-format-typst ==0.1, any.socks ==0.6.1, any.some ==1.0.6, + some +newtype-unsafe, any.sop-core ==0.5.0.2, any.sorted-list ==0.2.3.1, any.split ==0.2.5, any.splitmix ==0.1.1, + splitmix -optimised-mixer, any.statistics ==0.16.3.0, + statistics -benchpapi, any.stm ==2.5.2.1, any.stm-chans ==3.0.0.9, any.stopwatch ==0.1.0.6, + stopwatch -test_delay_upper_bound -test_threaded, any.streaming ==0.2.4.0, any.streaming-commons ==0.2.3.0, + streaming-commons -use-bytestring-builder, any.strict ==0.5.1, any.strict-concurrency ==0.2.4.3, any.syb ==0.7.2.4, any.tagged ==0.8.9, + tagged +deepseq +transformers, any.tagsoup ==0.14.8, any.tasty ==1.5.3, + tasty +unix, any.tasty-golden ==2.3.5, + tasty-golden -build-example, any.tasty-hedgehog ==1.4.0.2, any.tasty-hunit ==0.10.2, any.tasty-json ==0.1.0.0, @@ -327,11 +421,14 @@ constraints: any.Cabal ==3.12.1.0, any.terminal-size ==0.3.4, any.terminfo ==0.4.1.6, any.texmath ==0.12.9, + texmath -executable -server, any.text ==2.1.1, any.text-conversions ==0.3.1.1, any.text-iso8601 ==0.1.1, any.text-rope ==0.3, + text-rope -debug, any.text-short ==0.1.6, + text-short -asserts, any.th-abstraction ==0.7.1.0, any.th-compat ==0.1.6, any.th-expand-syns ==0.4.12.0, @@ -343,23 +440,32 @@ constraints: any.Cabal ==3.12.1.0, any.time ==1.12.2, any.time-compat ==1.9.8, any.time-locale-compat ==0.1.1.5, + time-locale-compat +old-locale, any.time-manager ==0.2.2, any.tls ==2.1.9, + tls -devel, any.tls-session-manager ==0.0.8, any.token-bucket ==0.1.0.1, + token-bucket +use-cbits, any.toml-parser ==2.0.1.0, any.torsor ==0.1.0.1, any.transformers ==0.6.1.0, any.transformers-base ==0.4.6, + transformers-base +orphaninstances, any.transformers-compat ==0.7.2, + transformers-compat -five +five-three -four +generic-deriving +mtl -three -two, any.trifecta ==2.1.4, any.tuples ==0.1.0.0, any.typed-process ==0.2.13.0, any.typst ==0.7, + typst -executable, any.typst-symbols ==0.1.7, any.unicode-collation ==0.1.3.6, + unicode-collation -doctests -executable, any.unicode-data ==0.6.0, + unicode-data -dev-has-icu, any.unicode-transforms ==0.4.0.1, + unicode-transforms -bench-show -dev -has-icu -has-llvm -use-gauge, any.uniplate ==1.6.13, any.unix ==2.8.4.0, any.unix-compat ==0.7.4, @@ -368,26 +474,34 @@ constraints: any.Cabal ==3.12.1.0, any.unliftio ==0.2.25.1, any.unliftio-core ==0.2.1.0, any.unordered-containers ==0.2.20, + unordered-containers -debug, any.utf8-string ==1.0.2, any.uuid ==1.3.16, any.uuid-types ==1.0.6, any.validation ==1.1.3, any.vault ==0.3.1.5, + vault +useghc, any.vector ==0.13.2.0, + vector +boundschecks -internalchecks -unsafechecks -wall, any.vector-algorithms ==0.9.1.0, + vector-algorithms +bench +boundschecks -internalchecks -llvm -unsafechecks, any.vector-binary-instances ==0.2.5.2, any.vector-sized ==1.6.1, any.vector-stream ==0.1.0.1, any.vector-th-unbox ==0.2.2, any.void ==0.7.3, + void -safe, any.wai ==3.2.4, any.wai-app-static ==3.1.9, + wai-app-static +crypton -print, any.wai-cors ==0.2.7, any.wai-extra ==3.1.17, + wai-extra -build-example, any.wai-logger ==2.5.0, any.wai-middleware-throttle ==0.3.0.1, any.wai-middleware-validation ==0.1.0.2, any.warp ==3.4.7, + warp +allow-sendfilefd -network-bytestring -warp-debug +x509, any.warp-tls ==3.4.13, any.wherefrom-compat ==0.1.1.1, any.wide-word ==0.1.7.0, @@ -395,12 +509,17 @@ constraints: any.Cabal ==3.12.1.0, any.wl-pprint-annotated ==0.1.0.1, any.word8 ==0.1.3, any.wreq ==0.5.4.3, + wreq -aws -developer +doctest -httpbin, any.xml ==1.3.14, any.xml-conduit ==1.10.0.0, any.xml-types ==0.3.8, any.yaml ==0.11.11.2, + yaml +no-examples +no-exe, any.yet-another-logger ==0.4.2, + yet-another-logger -tbmqueue, any.zigzag ==0.1.0.0, any.zip-archive ==0.4.3.2, - any.zlib ==0.7.1.0 -index-state: hackage.haskell.org 2025-04-14T16:11:29Z + zip-archive -executable, + any.zlib ==0.7.1.0, + zlib -bundled-c-zlib +non-blocking-ffi +pkg-config +index-state: hackage.haskell.org 2025-04-21T12:07:04Z From 4c7feab6571c160020a18b225ef91d02056fedf0 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 17 Apr 2025 16:11:01 -0400 Subject: [PATCH 114/378] Test fixes --- chainweb.cabal | 6 +- src/Chainweb/Core/Brief.hs | 7 +- src/Chainweb/Mempool/InMem.hs | 2 +- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 38 +- src/Chainweb/Pact/PactService.hs | 114 +-- src/Chainweb/Pact/PactService/Checkpointer.hs | 38 +- src/Chainweb/Pact/PactService/ExecBlock.hs | 16 +- src/Chainweb/Pact/TransactionExec.hs | 11 +- src/Chainweb/Pact/Types.hs | 4 +- src/Chainweb/Parent.hs | 3 + src/Chainweb/PayloadProvider.hs | 16 +- src/Chainweb/PayloadProvider/EVM.hs | 2 +- src/Chainweb/PayloadProvider/Pact.hs | 5 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 4 +- src/Chainweb/Version.hs | 8 +- test/lib/Chainweb/Test/Cut/TestBlockDb.hs | 11 +- test/lib/Chainweb/Test/Pact/CmdBuilder.hs | 27 +- .../Chainweb/Test/Pact/CheckpointerTest.hs | 64 +- test/unit/Chainweb/Test/Pact/CutFixture.hs | 3 - .../Chainweb/Test/Pact/PactServiceTest.hs | 710 ++++++++++-------- 20 files changed, 596 insertions(+), 493 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index e3e52e13fe..fc918ee07a 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -611,9 +611,9 @@ test-suite chainweb-tests Chainweb.Test.BlockHeaderDB Chainweb.Test.BlockHeaderDB.PruneForks Chainweb.Test.Chainweb.Utils.Paging - Chainweb.Test.CutDB + -- Chainweb.Test.CutDB Chainweb.Test.Difficulty - Chainweb.Test.Mempool + -- Chainweb.Test.Mempool Chainweb.Test.Mempool.Consensus Chainweb.Test.Mempool.InMem Chainweb.Test.Mempool.RestAPI @@ -626,7 +626,7 @@ test-suite chainweb-tests Chainweb.Test.Pact.HyperlanePluginTests Chainweb.Test.Pact.PactServiceTest Chainweb.Test.Pact.RemotePactTest - Chainweb.Test.Pact.SPVTest + -- Chainweb.Test.Pact.SPVTest Chainweb.Test.Pact.TransactionExecTest Chainweb.Test.Pact.TransactionTests Chainweb.Test.RestAPI diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index 324537e806..7254ec5e35 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -32,11 +32,13 @@ import Chainweb.Parent import Chainweb.Ranked import Chainweb.Utils import Control.Lens +import Data.Aeson +import Data.Foldable import Data.HashMap.Strict qualified as HM import Data.List qualified as L +import Data.List.NonEmpty (NonEmpty) import Data.Text qualified as T import Numeric.Natural -import Data.Aeson -- -------------------------------------------------------------------------- -- -- Adhoc class for brief logging output @@ -64,6 +66,9 @@ instance Brief a => Brief (Maybe a) where instance Brief a => Brief [a] where brief l = "[" <> (T.intercalate "," $ brief <$> l) <> "]" +instance Brief a => Brief (NonEmpty a) where + brief = brief . toList + instance Brief a => Brief (Parent a) where brief = brief . unwrapParent diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 2df84d4aea..4a5a298b9c 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -631,7 +631,7 @@ getBlockInMem logg cfg lock (BlockFill gasLimit txHashes _) txValidate evalCtx = err s = error $ mconcat [ "Error decoding tx (\"" , s - , "\"): tx was: " + , "\"): did you disable checks and insert an invalid transaction? tx was: " , T.unpack (T.decodeUtf8 tx) ] getSize = txGasLimit txcfg diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 2751ae2f18..693223b510 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -73,7 +73,7 @@ module Chainweb.Pact.Backend.ChainwebPactDb , commitBlockStateToDatabase , initSchema , lookupBlockWithHeight - , lookupParentBlockHash + , lookupBlockHash , lookupRankedBlockHash , getPayloadsAfter , getEarliestBlock @@ -258,7 +258,7 @@ chainwebPactBlockDb env = ChainwebPactDb } let headerOracle = HeaderOracle { chain = _chainId env - , consult = throwOnDbError . lookupParentBlockHash (_blockHandlerDb env) + , consult = throwOnDbError . lookupBlockHash (_blockHandlerDb env) . unwrapParent } let spv = pactSPV headerOracle r <- kont pactDb spv @@ -870,7 +870,7 @@ getPayloadsAfter :: HasCallStack => SQLiteEnv -> Parent BlockHeight -> ExceptT L getPayloadsAfter db parentHeight = do qry db "SELECT blockheight, payloadhash FROM BlockHistory WHERE blockheight > ?" [SInt (fromIntegral @BlockHeight @Int64 (unwrapParent parentHeight))] - [RBlob] >>= traverse + [RInt, RBlob] >>= traverse \case [SInt bh, SBlob bhash] -> return $! Ranked (fromIntegral @Int64 @BlockHeight bh) $ either error id $ runGetEitherS decodeBlockPayloadHash bhash @@ -892,33 +892,31 @@ getEarliestBlock db = do in return (RankedBlockHash (fromIntegral hgt) hash) go _ = fail "Chainweb.Pact.Backend.RelationalCheckpointer.doGetEarliest: impossible. This is a bug in chainweb-node." -lookupBlockWithHeight :: SQ3.Database -> BlockHeight -> ExceptT LocatedSQ3Error IO (Maybe (Ranked (Parent BlockHash))) +lookupBlockWithHeight :: HasCallStack => SQ3.Database -> BlockHeight -> ExceptT LocatedSQ3Error IO (Maybe (Ranked BlockHash)) lookupBlockWithHeight db bheight = do - qry db qtext [SInt $ fromIntegral bheight] [RInt] >>= \case - [[SBlob parentHash]] -> return $! Just $! - Ranked bheight (either error Parent $ runGetEitherS decodeBlockHash parentHash) - [_] -> error "lookupBlock: output type mismatch" - _ -> error "Expected single-row result" + qry db qtext [SInt $ fromIntegral bheight] [RBlob] >>= \case + [[SBlob hash]] -> return $! Just $! + Ranked bheight (either error id $ runGetEitherS decodeBlockHash hash) + [] -> return Nothing + res -> error $ "Invalid result, " <> sshow res where - qtext = "SELECT parenthash FROM BlockHistory WHERE blockheight = ?;" + qtext = "SELECT hash FROM BlockHistory WHERE blockheight = ?;" -lookupParentBlockHash :: SQ3.Database -> Parent BlockHash -> ExceptT LocatedSQ3Error IO Bool -lookupParentBlockHash db (Parent parentHash) = do - qry db qtext [SBlob (runPutS (encodeBlockHash parentHash))] [RInt] >>= \case +lookupBlockHash :: HasCallStack => SQ3.Database -> BlockHash -> ExceptT LocatedSQ3Error IO Bool +lookupBlockHash db hash = do + qry db qtext [SBlob (runPutS (encodeBlockHash hash))] [RInt] >>= \case [[SInt n]] -> return $! n == 1 - [_] -> error "lookupBlock: output type mismatch" - _ -> error "Expected single-row result" + res -> error $ "Invalid result, " <> sshow res where - qtext = "SELECT COUNT(*) FROM BlockHistory WHERE parenthash = ?;" + qtext = "SELECT COUNT(*) FROM BlockHistory WHERE hash = ?;" -lookupRankedBlockHash :: SQ3.Database -> RankedBlockHash -> IO Bool +lookupRankedBlockHash :: HasCallStack => SQ3.Database -> RankedBlockHash -> IO Bool lookupRankedBlockHash db rankedBHash = throwOnDbError $ do qry db qtext [ SInt $ fromIntegral (_rankedBlockHashHeight rankedBHash) , SBlob $ runPutS $ encodeBlockHash $ _rankedBlockHashHash rankedBHash ] [RInt] >>= \case [[SInt n]] -> return $! n == 1 - [_] -> error "lookupBlock: output type mismatch" - _ -> error "Expected single-row result" + res -> error $ "Invalid result, " <> sshow res where - qtext = "SELECT COUNT(*) FROM BlockHistory WHERE blockheight = ? AND parenthash = ?;" + qtext = "SELECT COUNT(*) FROM BlockHistory WHERE blockheight = ? AND hash = ?;" diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index fb30195ac1..a6c9f393e9 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -36,6 +36,7 @@ module Chainweb.Pact.PactService , execReadOnlyReplay , withPactService , execNewGenesisBlock + , makeEmptyBlock ) where import Control.Concurrent.Async @@ -121,6 +122,7 @@ import qualified Data.Pool as Pool import qualified Data.List.NonEmpty as NEL import qualified Control.Parallel.Strategies as Strategies import qualified Chainweb.Pact.NoCoinbase as Pact +import Chainweb.Core.Brief withPactService :: (Logger logger, CanReadablePayloadCas tbl) @@ -217,11 +219,6 @@ runGenesisIfNeeded logger serviceEnv = do v = _chainwebVersion serviceEnv cid = _chainId serviceEnv -getMiner :: HasCallStack => ServiceEnv tbl -> IO Miner -getMiner serviceEnv = case _psMiner serviceEnv of - Nothing -> error "Chainweb.Pact.PactService: Mining is disabled, but was invoked. This is a bug in chainweb." - Just miner -> return miner - -- | only for use in generating genesis blocks in tools. -- execNewGenesisBlock @@ -249,7 +246,7 @@ execNewGenesisBlock logger serviceEnv newTrans = do , _blockInProgressBlockCtx = _psBlockCtx blockEnv } - let fakeServiceEnv = serviceEnv + let fakeMempoolServiceEnv = serviceEnv & psMempoolAccess .~ mempty { mpaGetBlock = \bf pbc evalCtx -> do if _bfCount bf == 0 @@ -264,7 +261,7 @@ execNewGenesisBlock logger serviceEnv newTrans = do } & psMiner .~ Just noMiner - results <- Pact.continueBlock logger fakeServiceEnv (_psBlockDbEnv blockEnv) bipStart + results <- Pact.continueBlock logger fakeMempoolServiceEnv (_psBlockDbEnv blockEnv) bipStart let !pwo = toPayloadWithOutputs noMiner (_blockInProgressTransactions results) @@ -421,27 +418,29 @@ makeEmptyBlock => logger -> ServiceEnv tbl -> BlockEnv - -> StateT BlockHandle IO BlockInProgress -makeEmptyBlock logger serviceEnv blockEnv = do - miner <- liftIO $ getMiner serviceEnv - let blockGasLimit = _psNewBlockGasLimit serviceEnv - coinbaseOutput <- revertStateOnFailure (Pact.runCoinbase logger blockEnv miner) >>= \case - Left coinbaseError -> error $ "Error during coinbase: " <> sshow coinbaseError - Right coinbaseOutput -> - -- pretend that coinbase can throw an error, when we know it can't. - -- perhaps we can make the Transactions express this, may not be worth it. - return $ coinbaseOutput & Pact.crResult . Pact._PactResultErr %~ absurd - hndl <- get - return BlockInProgress - { _blockInProgressHandle = hndl - , _blockInProgressBlockCtx = view psBlockCtx blockEnv - , _blockInProgressRemainingGasLimit = blockGasLimit - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = coinbaseOutput - , _transactionPairs = mempty + -> BlockHandle + -> IO BlockInProgress +makeEmptyBlock logger serviceEnv blockEnv initialBlockHandle = + flip evalStateT initialBlockHandle $ do + miner <- liftIO getMiner + let blockGasLimit = _psNewBlockGasLimit serviceEnv + coinbaseOutput <- revertStateOnFailure (Pact.runCoinbase logger blockEnv miner) >>= \case + Left coinbaseError -> error $ "Error during coinbase: " <> sshow coinbaseError + Right coinbaseOutput -> + -- pretend that coinbase can throw an error, when we know it can't. + -- perhaps we can make the Transactions express this, may not be worth it. + return $ coinbaseOutput & Pact.crResult . Pact._PactResultErr %~ absurd + hndl <- get + return BlockInProgress + { _blockInProgressHandle = hndl + , _blockInProgressBlockCtx = view psBlockCtx blockEnv + , _blockInProgressRemainingGasLimit = blockGasLimit + , _blockInProgressTransactions = Transactions + { _transactionCoinbase = coinbaseOutput + , _transactionPairs = mempty + } + , _blockInProgressNumber = 0 } - , _blockInProgressNumber = 0 - } where revertStateOnFailure :: Monad m => StateT s (ExceptT e m) a -> StateT s m (Either e a) revertStateOnFailure s = do @@ -451,6 +450,10 @@ makeEmptyBlock logger serviceEnv blockEnv = do f old = \case Left err -> (Left err, old) Right (success, new) -> (Right success, new) + getMiner :: HasCallStack => IO Miner + getMiner = case _psMiner serviceEnv of + Nothing -> error "Chainweb.Pact.PactService: Mining is disabled, but was invoked. This is a bug in chainweb." + Just miner -> return miner syncToFork :: forall tbl logger @@ -469,45 +472,50 @@ syncToFork logger serviceEnv hints forkInfo = do -- check if some past block had the target as its parent; if so, that -- means we can rewind to it latestBlockRewindable <- - Checkpointer.lookupParentBlockHash sql (Parent $ _syncStateBlockHash (_consensusStateLatest pactConsensusState)) + Checkpointer.lookupBlockHash sql (_latestBlockHash forkInfo._forkInfoTargetState) if atTarget then do -- no work to do at all except set consensus state -- TODO PP: disallow rewinding final? - logFunctionText logger Debug $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState + logFunctionText logger Debug $ "no work done to move to " <> brief forkInfo._forkInfoTargetState Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState return (mempty, mempty, forkInfo._forkInfoTargetState) else if latestBlockRewindable then do -- we just have to rewind and set the final + safe blocks -- TODO PP: disallow rewinding final? - logFunctionText logger Debug $ "pure rewind to " <> sshow forkInfo._forkInfoTargetState - rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) - Checkpointer.rewindTo v cid sql (_syncStateRankedBlockHash (_consensusStateLatest pactConsensusState)) + logFunctionText logger Debug $ "pure rewind to " <> brief forkInfo._forkInfoTargetState + rewoundTxs <- getRewoundTxs (Parent $ forkInfo._forkInfoTargetState._consensusStateLatest._syncStateHeight) + Checkpointer.rewindTo v cid sql (_syncStateRankedBlockHash (_consensusStateLatest forkInfo._forkInfoTargetState)) Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) else do - logFunctionText logger Debug $ "no work done to move to " <> sshow forkInfo._forkInfoTargetState let traceBlockHashes = drop 1 (unwrapParent . _evaluationCtxRankedParentHash <$> forkInfo._forkInfoTrace) <> - [_syncStateRankedBlockHash pactConsensusState._consensusStateLatest] + [_syncStateRankedBlockHash forkInfo._forkInfoTargetState._consensusStateLatest] + logFunctionText logger Debug $ + "playing blocks to move to " <> brief forkInfo._forkInfoTargetState + <> " using trace blocks " <> brief traceBlockHashes findForkChain (zip forkInfo._forkInfoTrace traceBlockHashes) >>= \case Nothing -> do - logFunctionText logger Error $ "impossible to move to " <> sshow forkInfo._forkInfoTargetState + logFunctionText logger Error $ "impossible to move to " <> brief forkInfo._forkInfoTargetState -- error: we have no way to get to the target block. just report -- our current state and do nothing else. return (mempty, mempty, pactConsensusState) Just forkChainBottomToTop -> do - rewoundTxs <- getRewoundTxs (Parent $ _syncStateHeight (_consensusStateLatest pactConsensusState)) + rewoundTxs <- getRewoundTxs (Parent $ forkInfo._forkInfoTargetState._consensusStateLatest._syncStateHeight) -- the happy case: we can find a way to get to the target block -- look up all of the payloads to see if we've run them before -- even then we still have to run them, because they aren't in the checkpointer knownPayloads <- liftIO $ tableLookupBatch' pdb (each . _2) ((\e -> (e, _evaluationCtxRankedPayloadHash $ fst e)) <$> forkChainBottomToTop) - logFunctionText logger Debug $ "unknown blocks in context: " <> sshow (length $ NEL.filter (isNothing . snd) knownPayloads) + let unknownPayloads = NEL.filter (isNothing . snd) knownPayloads + when (not (null unknownPayloads)) + $ logFunctionText logger Debug $ "unknown blocks in context: " <> sshow (length unknownPayloads) runnableBlocks <- forM knownPayloads $ \((evalCtx, rankedBHash), maybePayload) -> do + logFunctionText logger Debug $ "running block: " <> brief rankedBHash payload <- case maybePayload of -- fetch payload if missing Nothing -> getPayloadForContext logger serviceEnv hints evalCtx @@ -538,8 +546,9 @@ syncToFork logger serviceEnv hints forkInfo = do -- told to start mining, we produce an empty block -- immediately. then we set up a separate thread -- to add new transactions to the block. + logFunctionText logger Debug "producing new block" emptyBlock <- Checkpointer.readFromLatest logger v cid sql (_newBlockCtxParentCreationTime newBlockCtx) $ \blockEnv blockHandle -> - flip evalStateT blockHandle $ makeEmptyBlock logger serviceEnv blockEnv + makeEmptyBlock logger serviceEnv blockEnv blockHandle let payloadVar = view psMiningPayloadVar serviceEnv -- cancel payload refresher thread @@ -566,23 +575,34 @@ syncToFork logger serviceEnv hints forkInfo = do :: [(EvaluationCtx p, RankedBlockHash)] -> IO (Maybe (NEL.NonEmpty (EvaluationCtx p, RankedBlockHash))) findForkChain [] = return Nothing - findForkChain (tip:chain) = go (NEL.singleton tip) chain + findForkChain (tip:chain) = go [] (tip:chain) where go - :: NEL.NonEmpty (EvaluationCtx p, RankedBlockHash) + :: [(EvaluationCtx p, RankedBlockHash)] -> [(EvaluationCtx p, RankedBlockHash)] -> IO (Maybe (NEL.NonEmpty (EvaluationCtx p, RankedBlockHash))) go !acc (tip':chain') = do -- note that if we see the eval ctx in the checkpointer, - -- that means that the block has been evaluated, thus we do - -- not include `tip` in the resulting list. - known <- Checkpointer.lookupRankedBlockHash sql (snd tip') + -- that means that the parent block has been evaluated, thus we do + -- include `tip` in the resulting list. + known <- Checkpointer.lookupRankedBlockHash sql (unwrapParent $ _evaluationCtxRankedParentHash $ fst tip') if known - then return $ Just acc + then do + logFunctionText logger Debug $ "fork point: " <> brief (printable tip') + return $ Just $ tip' NEL.:| acc -- if we don't know this block, remember it for later as we'll -- need to execute it on top - else go (tip' `NEL.cons` acc) chain' - go _acc [] = return Nothing + else do + logFunctionText logger Debug $ + "block not in checkpointer: " + <> brief (printable tip') + go (tip' : acc) chain' + go _ [] = do + logFunctionText logger Debug $ + "no fork point found for chain: " + <> brief (printable <$> (tip:chain)) + return Nothing + printable (a, b) = (_evaluationCtxRankedParentHash a, b) -- remember to call this *before* executing the actual rewind, -- and only alter the mempool *after* the db transaction is done. @@ -737,7 +757,7 @@ execLookupPactTxs -> Maybe ConfirmationDepth -> Vector SB.ShortByteString -> IO (Historical (HM.HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash))) -execLookupPactTxs logger serviceEnv confDepth txs = do -- pactLabel "execLookupPactTxs" $ do +execLookupPactTxs logger serviceEnv confDepth txs = do if V.null txs then return (Historical mempty) else do diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 405591a2a5..75c6284529 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -45,7 +45,7 @@ module Chainweb.Pact.PactService.Checkpointer , getEarliestBlock -- , lookupBlock , lookupRankedBlockHash - , lookupParentBlockHash + , lookupBlockHash , lookupBlockWithHeight , getPayloadsAfter , getConsensusState @@ -85,6 +85,7 @@ import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE import Chainweb.Pact.Backend.ChainwebPactDb (lookupRankedBlockHash) import Control.Monad.State.Strict +import System.LogLevel -- readFrom demands to know this context in case it's mining a block. -- But it's not really used by /local, or polling, so we fabricate it @@ -100,7 +101,7 @@ mkFakeParentCreationTime = do -- we just keep grabbing the new "latest header" until we succeed. -- note: this function will never rewind before genesis. readFromLatest - :: Logger logger + :: (HasCallStack, Logger logger) => logger -> ChainwebVersion -> ChainId @@ -116,7 +117,7 @@ readFromLatest logger v cid sql parentCreationTime doRead = -- read-only rewind to the nth parent before the latest block. -- note: this function will never rewind before genesis. readFromNthParent - :: Logger logger + :: (HasCallStack, Logger logger) => logger -> ChainwebVersion -> ChainId @@ -127,21 +128,28 @@ readFromNthParent -> IO (Historical a) readFromNthParent logger v cid sql parentCreationTime n doRead = do withSavepoint sql ReadFromNSavepoint $ do - latest <- _consensusStateLatest . fromJuste <$> getConsensusState sql - if genesisHeight v cid + fromIntegral @Word @BlockHeight n < _syncStateHeight latest - then return NoHistory + latest <- + _consensusStateLatest . fromMaybe (error "readFromNthParent is illegal to call before genesis") + <$> getConsensusState sql + if genesisHeight v cid + fromIntegral @Word @BlockHeight n > _syncStateHeight latest + then do + logFunctionText logger Warn $ "readFromNthParent asked to rewind beyond genesis, to " + <> sshow (int (_syncStateHeight latest) - int n :: Integer) + return NoHistory else do let targetHeight = _syncStateHeight latest - fromIntegral @Word @BlockHeight n lookupBlockWithHeight sql targetHeight >>= \case -- this case for shallow nodes without enough history - Nothing -> return NoHistory + Nothing -> do + logFunctionText logger Warn "readFromNthParent asked to rewind beyond known blocks" + return NoHistory Just nthBlock -> - readFrom logger v cid sql parentCreationTime (parentBlockHeight v cid nthBlock) doRead + readFrom logger v cid sql parentCreationTime (Parent nthBlock) doRead -- read-only rewind to a target block. -- if that target block is missing, return Nothing. readFrom - :: Logger logger + :: (HasCallStack, Logger logger) => logger -> ChainwebVersion -> ChainId @@ -284,17 +292,13 @@ setConsensusState :: SQLiteEnv -> ConsensusState -> IO () setConsensusState sql cs = do ChainwebPactDb.throwOnDbError $ ChainwebPactDb.setConsensusState sql cs -lookupBlockWithHeight :: SQLiteEnv -> BlockHeight -> IO (Maybe (Ranked (Parent BlockHash))) +lookupBlockWithHeight :: HasCallStack => SQLiteEnv -> BlockHeight -> IO (Maybe (Ranked BlockHash)) lookupBlockWithHeight sql bh = do ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockWithHeight sql bh --- lookupBlockByHash :: SQLiteEnv -> EvaluationCtx p -> IO Bool --- lookupBlockByHash sql rpbh = do --- ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockByHash sql rpbh - -lookupParentBlockHash :: SQLiteEnv -> Parent BlockHash -> IO Bool -lookupParentBlockHash sql pbh = do - ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupParentBlockHash sql pbh +lookupBlockHash :: HasCallStack => SQLiteEnv -> BlockHash -> IO Bool +lookupBlockHash sql pbh = do + ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockHash sql pbh getPayloadsAfter :: SQLiteEnv -> Parent BlockHeight -> IO [Ranked BlockPayloadHash] getPayloadsAfter sql b = do diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index 2de9af2e6c..04dfa8981f 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -162,7 +162,6 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do liftIO $ mpaBadlistTx mpAccess (V.fromList $ fmap pactRequestKeyToTransactionHash $ concat invalids) - liftIO $ logFunctionText logger Debug $ "Order of completed transactions: " <> sshow (map (Pact.unRequestKey . Pact._crReqKey . ssnd) $ concat $ reverse valids) let !blockInProgress' = blockInProgress & blockInProgressHandle .~ finalBlockHandle @@ -171,7 +170,7 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do & blockInProgressRemainingGasLimit .~ finalGasLimit - liftIO $ logFunctionText logger Debug $ "Final block transaction order: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . ssnd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) + liftIO $ logFunctionText logger Debug $ "block with new transactions: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . ssnd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) return blockInProgress' return blockInProgress' @@ -193,19 +192,17 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do pure stop | otherwise = do newTxs <- liftIO $ getBlockTxs blockEnv prevBlockFillState - liftIO $ logFunctionText logger Debug $ "Refill: fetched transaction: " <> sshow (V.length newTxs) + liftIO $ logFunctionText logger Debug $ "Refill: fetched transactions: " <> sshow (V.length newTxs) if V.null newTxs - then do - liftIO $ logFunctionText logger Debug "Refill: no new transactions" - pure stop + then pure stop else do (newCompletedTransactions, newInvalidTransactions, newBlockGasLimit, timedOut) <- execNewTransactions prevRemainingGas txTimeLimit newTxs liftIO $ do - logFunctionText logger Debug $ "Refill: included request keys: " + logFunctionText logger Debug $ "Refill: included: " <> sshow @[Hash] (fmap (Pact.unRequestKey . Pact._crReqKey . ssnd) newCompletedTransactions) - logFunctionText logger Debug $ "Refill: badlisted request keys: " + <> ", badlisted: " <> sshow @[Hash] (fmap Pact.unRequestKey newInvalidTransactions) let newBlockFillState = BlockFill @@ -270,7 +267,6 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do ((as, timedOut), s') <- runStateT rest s return ((Left (Pact._cmdHash tx):as, timedOut), s') Just (Right (a, s')) -> do - logFunctionText logger Debug "applyCmdInBlock buy gas succeeded" ((as, timedOut), s'') <- runStateT rest s' let !txBytes = commandToBytes tx return ((Right (T2 txBytes a):as, timedOut), s'') @@ -436,7 +432,7 @@ validateParsedChainwebTx _logger blockEnv tx cid = blockCtx ^. chainId v = blockCtx ^. chainwebVersion bh = _bctxCurrentBlockHeight blockCtx - txValidationTime = undefined + txValidationTime = _bctxParentCreationTime blockCtx checkChain :: ExceptT InsertError IO () checkChain = unless (Pact.assertChainId cid txCid) $ diff --git a/src/Chainweb/Pact/TransactionExec.hs b/src/Chainweb/Pact/TransactionExec.hs index 621b1ef409..b052d94a08 100644 --- a/src/Chainweb/Pact/TransactionExec.hs +++ b/src/Chainweb/Pact/TransactionExec.hs @@ -300,7 +300,7 @@ applyCmd -- ^ command with payload to execute -> IO (Either TxInvalidError (CommandResult [TxLog ByteString] (Pact.PactError Info))) applyCmd logger maybeGasLogger db miner txCtx txIdxInBlock spv initialGas cmd = do - logDebug_ logger $ "applyCmd: " <> sshow (_cmdHash cmd) + logDebug_ logger "applyCmd" let flags = Set.fromList [ FlagDisableRuntimeRTC , FlagDisableHistoryInTransactionalMode @@ -702,8 +702,7 @@ buyGas -> IO (Either BuyGasError EvalResult) buyGas logger origGasEnv db (Miner mid mks) txCtx cmd = do let gasEnv = origGasEnv & geGasModel . gmGasLimit .~ Just (MilliGasLimit (MilliGas 1_500_000)) - logFunctionText logger L.Debug $ - "buying gas for " <> sshow (_cmdHash cmd) + logFunctionText logger L.Debug "buying gas" -- TODO: use quirked gas? let gasPayerCaps = [ cap @@ -811,8 +810,7 @@ redeemGas :: (Logger logger) -> IO (Either RedeemGasError EvalResult) redeemGas logger db (Miner mid mks) txCtx gasUsed maybeFundTxPactId cmd | isChainweb224Pact, Nothing <- maybeFundTxPactId = do - logFunctionText logger L.Debug $ - "redeeming gas (post-2.24) for " <> sshow (_cmdHash cmd) + logFunctionText logger L.Debug "redeeming gas (post-2.24)" -- if we're past chainweb 2.24, we don't use defpacts for gas; see 'pact/coin-contract/coin.pact#redeem-gas' let (redeemGasTerm, redeemGasData) = mkRedeemGasTerm mid mks sender gasTotal gasFee -- todo: gas logs @@ -837,8 +835,7 @@ redeemGas logger db (Miner mid mks) txCtx gasUsed maybeFundTxPactId cmd | not isChainweb224Pact, Just fundTxPactId <- maybeFundTxPactId = do freeGasEnv <- mkFreeGasEnv GasLogsDisabled - logFunctionText logger L.Debug $ - "redeeming gas (pre-2.24) for " <> sshow (_cmdHash cmd) + logFunctionText logger L.Debug "redeeming gas (pre-2.24)" -- before chainweb 2.24, we use defpacts for gas; see: 'pact/coin-contract/coin.pact#fund-tx' let redeemGasData = PObject $ Map.singleton "fee" (PDecimal $ _pact5GasSupply gasFee) diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 131044bb6d..bdc9863502 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -167,7 +167,7 @@ import Chainweb.BlockPayloadHash import Chainweb.Counter import Chainweb.Logger import Chainweb.Mempool.Mempool -import Chainweb.Miner.Pact (Miner, toMinerData) +import Chainweb.Miner.Pact (Miner, toMinerData, noMiner) import Chainweb.Pact.Backend.ChainwebPactDb import Chainweb.Pact.Backend.Types import Chainweb.Pact.Transaction qualified as Pact @@ -457,7 +457,7 @@ testPactServiceConfig genesisPayload = PactServiceConfig , _pactFullHistoryRequired = False , _pactEnableLocalTimeout = False , _pactTxTimeLimit = Nothing - , _pactMiner = Nothing + , _pactMiner = Just noMiner , _pactGenesisPayload = genesisPayload } diff --git a/src/Chainweb/Parent.hs b/src/Chainweb/Parent.hs index 198d922830..1365ef2624 100644 --- a/src/Chainweb/Parent.hs +++ b/src/Chainweb/Parent.hs @@ -25,6 +25,9 @@ import Chainweb.Version newtype Parent h = Parent { unwrapParent :: h } deriving stock (Show, Functor, Foldable, Traversable, Eq, Ord, Generic) deriving newtype (NFData, ToJSON, FromJSON, Hashable, LeftTorsor) +instance Applicative Parent where + pure = Parent + Parent f <*> Parent a = Parent (f a) instance HasChainId h => HasChainId (Parent h) where _chainId = _chainId . unwrapParent diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index b7230d46a9..73602746d0 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -495,7 +495,7 @@ data ForkInfo = ForkInfo -- It may be more intuitive and convenient to store the trace in reverse -- order. -- - , _forkInfoBasePayloadHash :: !BlockPayloadHash + , _forkInfoBasePayloadHash :: !(Parent BlockPayloadHash) -- ^ The payload hash of the parent block of the first entry in the -- fork info trace. If the fork info trace is empty, this is the payload -- hash of the latest block in the consensus state. @@ -516,20 +516,20 @@ data ForkInfo = ForkInfo } deriving (Show, Eq, Ord) -_forkInfoBaseHeight :: ForkInfo -> BlockHeight +_forkInfoBaseHeight :: ForkInfo -> Parent BlockHeight _forkInfoBaseHeight fi = case _forkInfoTrace fi of - [] -> _latestHeight (_forkInfoTargetState fi) - (h:_) -> unwrapParent $ _evaluationCtxParentHeight h + [] -> Parent $ _latestHeight (_forkInfoTargetState fi) + (h:_) -> _evaluationCtxParentHeight h -_forkInfoBaseRankedPayloadHash :: ForkInfo -> RankedBlockPayloadHash +_forkInfoBaseRankedPayloadHash :: ForkInfo -> Parent RankedBlockPayloadHash _forkInfoBaseRankedPayloadHash fi = RankedBlockPayloadHash - (_forkInfoBaseHeight fi) - (_forkInfoBasePayloadHash fi) + <$> _forkInfoBaseHeight fi + <*> _forkInfoBasePayloadHash fi assertForkInfoInvariants :: MonadThrow m => ForkInfo -> m () assertForkInfoInvariants forkInfo = do when (null (_forkInfoTrace forkInfo)) $ - unless (trgPayloadHash forkInfo == _forkInfoBasePayloadHash forkInfo) $ + unless (trgPayloadHash forkInfo == unwrapParent (_forkInfoBasePayloadHash forkInfo)) $ throwM $ InvalidForkInfo "The base payload hash must match the target payload hash, when the fork info trace is empty" diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 170ae89713..ad433ac6db 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1078,7 +1078,7 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do -- network. -- let rankedBaseHash = _forkInfoBaseRankedPayloadHash forkInfo - tableLookup p rankedBaseHash >>= \case + tableLookup p (unwrapParent rankedBaseHash) >>= \case -- If we don't know that base, there's nothing we can do Nothing -> do diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 30c4c960ad..d34190422a 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -39,6 +39,7 @@ import Chainweb.Utils import Chainweb.Version import qualified Data.Pool as Pool import Control.Monad.Trans.Resource (ResourceT, allocate) +import Chainweb.Core.Brief data PactPayloadProvider logger tbl = PactPayloadProvider logger (ServiceEnv tbl) @@ -139,8 +140,8 @@ pactMemPoolGetBlock -> EvaluationCtx () -> IO (Vector to)) pactMemPoolGetBlock mp theLogger bf validate ctx = do - logFn theLogger Debug $! "pactMemPoolAccess - getting new block of transactions for " - <> "height = " <> sshow (_evaluationCtxCurrentHeight ctx) <> ", hash = " <> sshow (_evaluationCtxParentHash ctx) + logFn theLogger Debug $! "pactMemPoolAccess - getting new block of transactions for parent " + <> brief (_evaluationCtxRankedParentHash ctx) mempoolGetBlock mp bf validate ctx where logFn :: Logger l => l -> LogFunctionText -- just for giving GHC some type hints diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index efe5312388..7c2e0bf0da 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -333,7 +333,7 @@ forkInfoForHeader wdb hdr pldData state <- consensusState wdb hdr return $ ForkInfo { _forkInfoTrace = [] - , _forkInfoBasePayloadHash = _latestPayloadHash state + , _forkInfoBasePayloadHash = Parent $ _latestPayloadHash state , _forkInfoTargetState = state , _forkInfoNewBlockCtx = Just nbctx } @@ -349,7 +349,7 @@ forkInfoForHeader wdb hdr pldData state <- consensusState wdb hdr return $ ForkInfo { _forkInfoTrace = [consensusPayload <$ blockHeaderToEvaluationCtx phdr] - , _forkInfoBasePayloadHash = view blockPayloadHash (unwrapParent phdr) + , _forkInfoBasePayloadHash = view blockPayloadHash <$> phdr , _forkInfoTargetState = state , _forkInfoNewBlockCtx = Just nbctx } diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index aa2f3d98e3..0278b15e3a 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -139,6 +139,7 @@ module Chainweb.Version , indexByForkHeights , latestBehaviorAt , onAllChains + , onAllChainsM , domainAddr2PeerInfo -- * Internal. Don't use. Exported only for testing @@ -799,10 +800,13 @@ latestBehaviorAt v = foldlOf' behaviorChanges max 0 v + 1 , versionGraphs . to ruleHead . _1 ] +onAllChains :: ChainwebVersion -> (ChainId -> a) -> ChainMap a +onAllChains v f = runIdentity $ onAllChainsM v (Identity . f) + -- | Easy construction of a `ChainMap` with entries for every chain -- in a `ChainwebVersion`. -onAllChains :: Applicative m => ChainwebVersion -> (ChainId -> m a) -> m (ChainMap a) -onAllChains v f = OnChains <$> +onAllChainsM :: Applicative m => ChainwebVersion -> (ChainId -> m a) -> m (ChainMap a) +onAllChainsM v f = OnChains <$> HM.traverseWithKey (\cid () -> f cid) (HS.toMap (chainIds v)) diff --git a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs index 02c4f73d4e..084c6631e0 100644 --- a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs +++ b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs @@ -46,6 +46,7 @@ import Chainweb.WebBlockHeaderDB import Chainweb.Storage.Table.RocksDB import Chainweb.BlockHeight +import Chainweb.Parent data TestBlockDb = TestBlockDb { _bdbWebBlockHeaderDb :: WebBlockHeaderDb @@ -113,18 +114,18 @@ addTestBlockDb (TestBlockDb wdb pdb cmv) bh n gbt cid outs = do Left e -> throwM $ userError ("addTestBlockDb: " <> show e) -- | Get header for chain on current cut. -getParentTestBlockDb :: TestBlockDb -> ChainId -> IO BlockHeader +getParentTestBlockDb :: TestBlockDb -> ChainId -> IO (Parent BlockHeader) getParentTestBlockDb (TestBlockDb _ _ cmv) cid = do c <- readMVar cmv fromMaybeM (userError $ "Internal error, parent not found for cid " ++ show cid) $ - HM.lookup cid $ _cutMap c + Parent <$> HM.lookup cid (_cutMap c) -- | Get header for chain on current cut. -getParentBlockTestBlockDb :: TestBlockDb -> ChainId -> IO Block +getParentBlockTestBlockDb :: TestBlockDb -> ChainId -> IO (Parent Block) getParentBlockTestBlockDb tdb cid = do - bh <- getParentTestBlockDb tdb cid + Parent bh <- getParentTestBlockDb tdb cid pwo <- fromJuste <$> lookupPayloadWithHeight (_bdbPayloadDb tdb) (Just $ view blockHeight bh) (view blockPayloadHash bh) - return Block + return $ Parent Block { _blockHeader = bh , _blockPayloadWithOutputs = pwo } diff --git a/test/lib/Chainweb/Test/Pact/CmdBuilder.hs b/test/lib/Chainweb/Test/Pact/CmdBuilder.hs index 9ee268e427..f0ee95cda5 100644 --- a/test/lib/Chainweb/Test/Pact/CmdBuilder.hs +++ b/test/lib/Chainweb/Test/Pact/CmdBuilder.hs @@ -23,6 +23,7 @@ import Control.Lens hiding ((.=)) import Pact.Core.Command.Types import Data.Text (Text) import GHC.Generics +import GHC.Stack import Pact.Core.Capabilities import Pact.Core.Guards import Pact.Core.Verifiers (Verifier, ParsedVerifierProof) @@ -186,28 +187,18 @@ defaultCmd cid = CmdBuilder -- | Build parsed + verified Pact command -- TODO: Use the new `assertPact4Command` function. -buildCwCmd :: (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact.Transaction +buildCwCmd :: (HasCallStack, MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact.Transaction buildCwCmd v cmd = buildTextCmd v cmd >>= \(c :: Command Text) -> case validateCommand v c of - Left err -> throwM $ userError $ "buildCwCmd failed: " ++ err + Left err -> error $ "buildCwCmd failed: " ++ err Right cmd' -> return cmd' --- -- | Build a Pact4 command without parsing it. This can be useful for inserting txs directly into the mempool for testing. --- buildCwCmdNoParse :: forall m. (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact4.UnparsedTransaction --- buildCwCmdNoParse v cmd = do --- cmd5 <- buildTextCmd v cmd --- cmd4 <- case Aeson.fromJSON @(Pact4.Command Text) $ J._getLegacyValue $ J.toLegacyJsonViaEncode cmd5 of --- Aeson.Error e -> throwM $ userError $ "buildCwCmdNoParse failed: " ++ e --- Aeson.Success c -> return c - --- let decodePayload :: ByteString -> m (Pact4.Payload Pact4.PublicMeta Text) --- decodePayload bs = case Aeson.eitherDecodeStrict' bs of --- Left err -> throwM $ userError $ "buildCwCmdNoParse failed to decode json payload: " ++ err --- Right payload -> return payload - --- let payloadBytes = T.encodeUtf8 $ Pact4._cmdPayload cmd4 --- payload <- decodePayload payloadBytes --- return $ Pact4.mkPayloadWithText $ fmap (\_ -> (payloadBytes, payload)) cmd4 +-- | Build parsed, not verified Pact command +buildCwCmdNoSigCheck :: (HasCallStack, MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact.Transaction +buildCwCmdNoSigCheck v cmd = buildTextCmd v cmd >>= \(cmdText :: Command Text) -> + case Pact.parseCommand cmdText of + Left err -> error $ "buildCwCmd failed: " ++ sshow err + Right parsedCmd -> return parsedCmd -- | Build unparsed, unverified command -- diff --git a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs index b04f572a09..130e301028 100644 --- a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs +++ b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs @@ -23,7 +23,7 @@ import Data.ByteString (ByteString) import Data.Functor.Product import Data.List.NonEmpty qualified as NE import Data.Map qualified as Map -import Data.MerkleLog (MerkleNodeType (..), merkleRoot, merkleTree) +import Data.MerkleLog (MerkleNodeType (..), merkleRoot) import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T @@ -52,13 +52,17 @@ import Chainweb.Pact.Backend.Types import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer import Chainweb.Pact.Types import Chainweb.Parent -import Chainweb.PayloadProvider (NewBlockCtx (_newBlockCtxMinerReward, _newBlockCtxParentCreationTime), genesisConsensusState) +import Chainweb.PayloadProvider import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions import Chainweb.Test.Utils hiding (withTempSQLiteResource) import Chainweb.Utils import Chainweb.Utils.Serialization (runGetS, runPutS) import Chainweb.Version +import Chainweb.MinerReward +import Control.Monad.State.Strict +import qualified Chainweb.Payload as Chainweb +import Chainweb.Pact.Utils (emptyPayload) -- | A @DbAction f@ is a description of some action on the database together with an f-full of results for it. type DbValue = Integer @@ -161,8 +165,8 @@ blockHeaderFromTxLogs :: Parent RankedBlockHash -> [TxLog ByteString] -> IO (Blo blockHeaderFromTxLogs parent txLogs = do fakePayloadHash <- runGetS decodeBlockPayloadHash $ let - payloadLogMerkleTree = merkleTree @ChainwebMerkleHashAlgorithm @ByteString - [ TreeNode $ merkleRoot $ merkleTree + payloadLogMerkleTree = merkleRoot @ChainwebMerkleHashAlgorithm + [ TreeNode $ merkleRoot $ [ InputNode (T.encodeUtf8 (_txDomain txLog)) , InputNode (T.encodeUtf8 (_txKey txLog)) , InputNode (_txValue txLog) @@ -170,17 +174,17 @@ blockHeaderFromTxLogs parent txLogs = do | txLog <- txLogs ] in - runPutS $ encodeMerkleLogHash $ MerkleLogHash $ merkleRoot payloadLogMerkleTree + runPutS $ encodeMerkleLogHash $ MerkleLogHash payloadLogMerkleTree fakeBlockHash <- runGetS decodeBlockHash $ let - blockLogMerkleTree = merkleTree @ChainwebMerkleHashAlgorithm @ByteString - [ TreeNode $ merkleRoot $ merkleTree + blockLogMerkleTree = merkleRoot @ChainwebMerkleHashAlgorithm + [ TreeNode $ merkleRoot $ [ InputNode (runPutS $ encodeBlockHash $ unwrapParent $ _rankedBlockHashHash <$> parent) , InputNode (runPutS $ encodeBlockPayloadHash @ChainwebMerkleHashAlgorithm fakePayloadHash) ] ] in - runPutS $ encodeMerkleLogHash $ MerkleLogHash $ merkleRoot blockLogMerkleTree + runPutS $ encodeMerkleLogHash $ MerkleLogHash blockLogMerkleTree return (fakeBlockHash, fakePayloadHash) -- TODO things to test later: @@ -197,32 +201,35 @@ runBlocks sql rootBlockCtx blks = do where loop parent (block:blocks) = do logger <- getTestLogger - fakeNewBlockCtx <- Checkpointer.mkFakeNewBlockCtx + fakeParentCreationTime <- Checkpointer.mkFakeParentCreationTime (fakeBlockInfo, block', _finalBlockHandle) <- (throwIfNoHistory =<<) $ - Checkpointer.readFrom logger testVer cid sql fakeNewBlockCtx parent $ + Checkpointer.readFrom logger testVer cid sql fakeParentCreationTime parent $ executeBlockTransaction parent block let childBlockCtx = BlockCtx - { _bctxParentCreationTime = _newBlockCtxParentCreationTime fakeNewBlockCtx + { _bctxParentCreationTime = fakeParentCreationTime , _bctxParentHash = Parent $ fst fakeBlockInfo , _bctxParentHeight = Parent $ childBlockHeight testVer cid parent , _bctxChainId = cid , _bctxChainwebVersion = testVer - , _bctxMinerReward = _newBlockCtxMinerReward fakeNewBlockCtx + , _bctxMinerReward = blockMinerReward testVer (childBlockHeight testVer cid parent) } let parentBlockCtx = BlockCtx - { _bctxParentCreationTime = _newBlockCtxParentCreationTime fakeNewBlockCtx + { _bctxParentCreationTime = fakeParentCreationTime , _bctxParentHash = _rankedBlockHashHash <$> parent , _bctxParentHeight = _rankedBlockHashHeight <$> parent , _bctxChainId = cid , _bctxChainwebVersion = testVer - , _bctxMinerReward = _newBlockCtxMinerReward fakeNewBlockCtx + , _bctxMinerReward = blockMinerReward testVer (unwrapParent $ _rankedBlockHashHeight <$> parent) } _ <- Checkpointer.restoreAndSave logger testVer cid sql - (NE.singleton (parentBlockCtx, \blockEnv blockHandle -> do - (fakeBlockInfo', _blk, finalBlockHandle) <- executeBlockTransaction parent block blockEnv blockHandle - fakeBlockInfo' & P.equals fakeBlockInfo - return ((), finalBlockHandle, fakeBlockInfo) + (NE.singleton (parentBlockCtx, \blockEnv -> do + blockHandle <- get + (fakeBlockInfo', _blk, finalBlockHandle) <- + liftIO $ executeBlockTransaction parent block blockEnv blockHandle + put finalBlockHandle + liftIO $ fakeBlockInfo' & P.equals fakeBlockInfo + return ((), fakeBlockInfo) )) ((parentBlockCtx, fakeBlockInfo, block') :) <$> loop (_bctxParentRankedBlockHash childBlockCtx) blocks loop _ [] = return [] @@ -239,7 +246,7 @@ runBlocks sql rootBlockCtx blks = do -- is consistent with us executing that block with `readFrom` assertBlock :: SQLiteEnv -> BlockCtx -> (BlockHash, BlockPayloadHash) -> DbBlock Identity -> IO () assertBlock sql blockCtx expectedBlockInfo blk = do - fakeNewBlockCtx <- Checkpointer.mkFakeNewBlockCtx + fakeNewBlockCtx <- Checkpointer.mkFakeParentCreationTime logger <- getTestLogger hist <- Checkpointer.readFrom logger testVer cid sql fakeNewBlockCtx (_bctxParentRankedBlockHash blockCtx) $ \blockEnv startHandle -> do ((), _endHandle) <- doChainwebPactDbTransaction (_psBlockDbEnv blockEnv) startHandle Nothing $ \txdb _spv -> do @@ -272,7 +279,7 @@ tests = testGroup "Pact5 Checkpointer tests" ChainwebPactDb.initSchema sql Checkpointer.setConsensusState sql $ genesisConsensusState testVer cid logger <- getTestLogger - fakeNewBlockCtx <- Checkpointer.mkFakeNewBlockCtx + fakeNewBlockCtx <- Checkpointer.mkFakeParentCreationTime ((), _handle) <- (throwIfNoHistory =<<) $ Checkpointer.readFrom logger testVer cid sql fakeNewBlockCtx genesisParentRanked $ \db blockHandle -> do @@ -290,8 +297,8 @@ tests = testGroup "Pact5 Checkpointer tests" -- extend this empty chain with the genesis block _ <- Checkpointer.restoreAndSave logger testVer cid sql $ ( - NE.singleton (blockCtxOfEvaluationCtx testVer cid (genesisEvaluationCtx testVer cid), - \_ hndl -> return ((), hndl, (view blockHash (genesisBlockHeader testVer cid), genesisBlockPayloadHash testVer cid))) + NE.singleton (blockCtxOfEvaluationCtx testVer cid (genesisEvalCtx cid), + \_ -> return ((), (view blockHash (genesisBlockHeader testVer cid), genesisBlockPayloadHash testVer cid))) ) handle @_ @SomeException (\ex -> putStrLn (displayException ex) >> throw ex) @@ -303,6 +310,19 @@ tests = testGroup "Pact5 Checkpointer tests" evalIO $ forM_ finishedBlocks $ \(parent, blockInfo, block) -> do assertBlock sql parent blockInfo block ] + where + genesisEvalCtx c = EvaluationCtx + { _evaluationCtxParentCreationTime = Parent $ testVer ^?! versionGenesis . genesisTime . atChain c + , _evaluationCtxParentHash = genesisParentBlockHash testVer c + , _evaluationCtxParentHeight = Parent $ genesisHeight testVer c + -- should not be used + , _evaluationCtxMinerReward = MinerReward 0 + , _evaluationCtxPayload = ConsensusPayload + { _consensusPayloadHash = genesisBlockPayloadHash testVer c + , _consensusPayloadData = Just $ EncodedPayloadData $ Chainweb.encodePayloadData $ + Chainweb.payloadWithOutputsToPayloadData emptyPayload + } + } testVer :: ChainwebVersion diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index b71cb55477..de1b926447 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -55,8 +55,6 @@ import Chainweb.CutDB import Chainweb.Logger import Chainweb.Mempool.Mempool (MempoolBackend) import Chainweb.Miner.Pact -import Chainweb.Pact.PactService.Pact4.ExecBlock () -import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Payload @@ -71,7 +69,6 @@ import Chainweb.Utils.Serialization (runGetS, runPutS) import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService import Control.Concurrent.STM as STM import Control.Lens hiding (elements, only) import Control.Monad diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index 621a762da0..f01fab742a 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -14,31 +14,25 @@ , TupleSections #-} -{-# options_ghc -fno-warn-gadt-mono-local-binds #-} +{-# LANGUAGE BangPatterns #-} module Chainweb.Test.Pact.PactServiceTest ( tests ) where -import Data.List qualified as List -import "pact" Pact.Types.Command qualified as Pact -import "pact" Pact.Types.Hash qualified as Pact import Chainweb.BlockHeader import Chainweb.ChainId import Chainweb.Chainweb import Chainweb.Cut import Chainweb.Graph (singletonChainGraph) import Chainweb.Logger -import Chainweb.Mempool.InMem -import Chainweb.Mempool.Mempool (InsertType (..), MempoolBackend (..)) -import Chainweb.Miner.Pact -import Chainweb.Pact.PactService +import Chainweb.Mempool.InMem qualified as Mempool +import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Pact.Types import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Pact.Transaction qualified as Pact5 import Chainweb.Payload import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb, _bdbWebBlockHeaderDb), addTestBlockDb, getCutTestBlockDb, getParentTestBlockDb, mkTestBlockDb, setCutTestBlockDb) +import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb), addTestBlockDb, getCutTestBlockDb, getParentTestBlockDb, mkTestBlockDb, setCutTestBlockDb) import Chainweb.Test.Pact.CmdBuilder import Chainweb.Test.Pact.Utils hiding (withTempSQLiteResource) import Chainweb.Test.TestVersions @@ -46,49 +40,51 @@ import Chainweb.Test.Utils import Chainweb.Time import Chainweb.Utils import Chainweb.Version -import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) -import Control.Concurrent hiding (throwTo) + import Control.Concurrent.Async (forConcurrently) -import Control.Exception (AsyncException (..)) -import Control.Exception.Safe import Control.Lens hiding (only) import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Resource -import Control.Monad.Trans.Resource qualified as Resource + import Data.ByteString.Lazy qualified as LBS import Data.Decimal -import Data.HashMap.Strict qualified as HashMap import Data.HashSet qualified as HashSet -import Data.Maybe (fromMaybe) import Data.Text qualified as T import Data.Text.IO qualified as Text import Data.Vector (Vector) import Data.Vector qualified as Vector + import Pact.Core.Capabilities -import Pact.Core.ChainData hiding (ChainId, _chainId) import Pact.Core.Command.Types import Pact.Core.Gas.Types -import Pact.Core.Hash qualified as Pact5 import Pact.Core.Names import Pact.Core.PactValue -import Pact.Types.Gas qualified as Pact import PropertyMatchers ((?)) import PropertyMatchers qualified as P import Test.Tasty -import Test.Tasty.HUnit (assertBool, assertEqual, assertFailure, testCase) +import Test.Tasty.HUnit (assertBool, assertEqual, testCase) import Text.Printf (printf) import qualified Data.Pool as Pool -import qualified Data.Pool as Pool -import Chainweb.Pact.Types (testPactServiceConfig, psMempoolAccess) import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 import Chainweb.PayloadProvider.Pact (pactMemPoolAccess) -import Chainweb.Pact.PactService (execPreInsertCheckReq) +import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer +import Chainweb.Pact.Backend.Types (throwIfNoHistory) +import qualified Chainweb.Pact.PactService as PactService +import qualified Chainweb.Pact.PactService.ExecBlock as PactService +import Chainweb.Parent +import Chainweb.PayloadProvider +import Chainweb.Core.Brief +import Data.Foldable (toList) +import Pact.Core.ChainData (TxCreationTime (..)) +import Pact.Core.Hash qualified as Pact +import qualified Data.List as List data Fixture = Fixture { _fixtureBlockDb :: TestBlockDb - , _fixtureMempools :: ChainMap (MempoolBackend Pact.Transaction) + , _fixtureLogger :: GenericLogger + , _fixtureMempools :: ChainMap (Mempool.MempoolBackend Pact.Transaction) , _fixturePacts :: ChainMap (ServiceEnv RocksDbTable) } @@ -98,32 +94,35 @@ v = instantCpmTestVersion singletonChainGraph mkFixtureWith :: (ChainId -> PactServiceConfig) -> RocksDb -> ResourceT IO Fixture mkFixtureWith pactServiceConfig baseRdb = do tdb <- mkTestBlockDb v baseRdb + logLevel <- liftIO getTestLogLevel + let logger = genericLogger logLevel Text.putStrLn perChain <- iforM (HashSet.toMap (chainIds v)) $ \chain () -> do sqlite <- withTempSQLiteResource let pdb = _bdbPayloadDb tdb - logLevel <- liftIO getTestLogLevel - let logger = genericLogger logLevel Text.putStrLn fakeRoSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sqlite) (\_ -> return ()) 10 10) - serviceEnv <- withPactService v chain Nothing mempty logger Nothing pdb fakeRoSqlPool sqlite (pactServiceConfig chain) + serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing pdb fakeRoSqlPool sqlite (pactServiceConfig chain) + liftIO $ PactService.initialPayloadState logger serviceEnv let mempoolCfg = validatingMempoolConfig chain v (GasLimit (Gas 150_000)) (GasPrice 1e-8) - (execPreInsertCheckReq logger serviceEnv) - mempool <- liftIO $ startInMemoryMempoolTest mempoolCfg + (PactService.execPreInsertCheckReq logger serviceEnv) + mempool <- liftIO $ Mempool.startInMemoryMempoolTest mempoolCfg let mempoolAccess = pactMemPoolAccess mempool logger return (mempool, serviceEnv & psMempoolAccess .~ mempoolAccess) let fixture = Fixture { _fixtureBlockDb = tdb + , _fixtureLogger = logger , _fixtureMempools = OnChains $ fst <$> perChain , _fixturePacts = OnChains $ snd <$> perChain } -- The mempool expires txs based on current time, but newBlock expires txs based on parent creation time. -- So by running an empty block with the creationTime set to the current time, we get these goals to align -- for future blocks we run. - -- _ <- liftIO $ advanceAllChains fixture $ onChains [] + _ <- liftIO $ advanceAllChains fixture $ onChains [] return fixture +genesisPayload :: ChainId -> PayloadWithOutputs genesisPayload chain = if chain == unsafeChainId 0 then IN0.payloadBlock @@ -135,282 +134,287 @@ mkFixture baseRdb = do tests :: RocksDb -> TestTree tests baseRdb = testGroup "Pact5 PactServiceTest" - [] - -- [ testCase "simple end to end" (simpleEndToEnd baseRdb) - -- , testCase "continue block spec" (continueBlockSpec baseRdb) - -- , testCase "new block empty" (newBlockEmpty baseRdb) - -- , testCase "new block timeout spec" (newBlockTimeoutSpec baseRdb) - -- , testCase "new block excludes invalid transactions" (testNewBlockExcludesInvalid baseRdb) + -- [] + [ testCase "simple end to end" (simpleEndToEnd baseRdb) + , testCase "continue block spec" (continueBlockSpec baseRdb) + , testCase "new block empty" (newBlockEmpty baseRdb) + , testCase "new block timeout spec" (newBlockTimeoutSpec baseRdb) + , testCase "new block excludes invalid transactions" (testNewBlockExcludesInvalid baseRdb) -- , testCase "lookup pact txs spec" (lookupPactTxsSpec baseRdb) -- , testCase "failed txs should go into blocks" (failedTxsShouldGoIntoBlocks baseRdb) -- , testCase "modules with higher level transitive dependencies (simple)" (modulesWithHigherLevelTransitiveDependenciesSimple baseRdb) -- , testCase "modules with higher level transitive dependencies (complex)" (modulesWithHigherLevelTransitiveDependenciesComplex baseRdb) - -- ] - --- simpleEndToEnd :: RocksDb -> IO () --- simpleEndToEnd baseRdb = runResourceT $ do --- fixture <- mkFixture baseRdb --- liftIO $ do --- cmd1 <- buildCwCmd v (transferCmd 1.0) --- cmd2 <- buildCwCmd v (transferCmd 2.0) - --- results <- advanceAllChainsWithTxs fixture $ onChain chain0 [cmd1, cmd2] - --- -- we only care that they succeed; specifics regarding their outputs are in TransactionExecTest --- results & --- P.alignExact ? onChain chain0 ? --- P.alignExact ? Vector.replicate 2 successfulTx - --- newBlockEmpty :: RocksDb -> IO () --- newBlockEmpty baseRdb = runResourceT $ do --- fixture <- mkFixture baseRdb --- liftIO $ do --- cmd <- buildCwCmd v (transferCmd 1.0) --- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsertPact5 mempool CheckedInsert [cmd] --- -- -- Test that NewBlockEmpty ignores the mempool --- emptyBip <- throwIfNotPact5 =<< throwIfNoHistory =<< --- newBlock noMiner NewBlockEmpty (ParentHeader ph) pactQueue --- let emptyPwo = finalizeBlock emptyBip --- assertEqual "empty block has no transactions" 0 (Vector.length $ _payloadWithOutputsTransactions emptyPwo) --- return emptyPwo - --- results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue _ -> do --- nonEmptyBip <- throwIfNotPact5 =<< throwIfNoHistory =<< --- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- return $ finalizeBlock nonEmptyBip - --- results & --- P.alignExact ? onChain chain0 ? --- P.alignExact ? Vector.replicate 1 successfulTx - --- continueBlockSpec :: RocksDb -> IO () --- continueBlockSpec baseRdb = runResourceT $ do --- fixture <- mkFixture baseRdb --- liftIO $ do --- startCut <- getCut fixture - --- -- construct some transactions that we plan to put into the block --- cmd1 <- buildCwCmd v (transferCmd 1.0) --- cmd2 <- buildCwCmd v (transferCmd 2.0) --- cmd3 <- buildCwCmd v (transferCmd 3.0) - --- allAtOnceResults <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- -- insert all transactions --- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3] --- -- construct a new block with all of said transactions --- bipAllAtOnce <- throwIfNotPact5 =<< throwIfNoHistory =<< --- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- return $ finalizeBlock bipAllAtOnce --- -- assert that 3 successful txs are in the block --- allAtOnceResults & --- P.alignExact ? onChain chain0 ? --- P.alignExact ? Vector.replicate 3 successfulTx - --- -- reset back to the empty block for the next phase --- -- next, produce the same block by repeatedly extending a block --- -- with the same transactions as were included in the original. --- -- note that this will reinsert all txs in the full block into the --- -- mempool, so we need to clear it after, or else the block will --- -- contain all of the transactions before we extend it. --- revert fixture startCut --- continuedResults <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolClear mempool --- mempoolInsertPact5 mempool CheckedInsert [cmd3] --- bipStart <- throwIfNotPact5 =<< throwIfNoHistory =<< --- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue - --- mempoolInsertPact5 mempool CheckedInsert [cmd2] --- bipContinued <- throwIfNoHistory =<< continueBlock bipStart pactQueue - --- mempoolInsertPact5 mempool CheckedInsert [cmd1] --- bipFinal <- throwIfNoHistory =<< continueBlock bipContinued pactQueue - --- -- We must make progress on the same parent header --- assertEqual "same parent header after continuing block" --- (_blockInProgressParentHeader bipStart) --- (_blockInProgressParentHeader bipContinued) --- assertBool "made progress (1)" --- (bipStart /= bipContinued) --- assertEqual "same parent header after finishing block" --- (_blockInProgressParentHeader bipContinued) --- (_blockInProgressParentHeader bipFinal) --- assertBool "made progress (2)" --- (bipContinued /= bipFinal) - --- return $ finalizeBlock bipFinal - --- -- assert that the continued results are equal to doing it all at once --- continuedResults & P.equals allAtOnceResults + ] + +-- TODO PP: +-- test: +-- 1. block refreshing (maybe just wait on the tvar) +-- 2. multiple-block play +-- 3. pure rewinds + +simpleEndToEnd :: RocksDb -> IO () +simpleEndToEnd baseRdb = runResourceT $ do + fixture <- mkFixture baseRdb + liftIO $ do + cmd1 <- buildCwCmd v (transferCmd 1.0) + cmd2 <- buildCwCmd v (transferCmd 2.0) + + results <- advanceAllChainsWithTxs fixture $ onChain chain0 [cmd1, cmd2] + + -- we only care that they succeed; specifics regarding their outputs are in TransactionExecTest + results & + P.alignExact ? onChain chain0 ? + P.alignExact ? Vector.replicate 2 successfulTx + +newBlockEmpty :: RocksDb -> IO () +newBlockEmpty baseRdb = runResourceT $ do + fixture <- mkFixture baseRdb + liftIO $ do + cmd <- buildCwCmd v (transferCmd 1.0) + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd] + _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + finalizeBlock fixture <$> makeEmptyBlock fixture ph + + results <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + block <- continueBlock fixture =<< makeEmptyBlock fixture ph + return $ finalizeBlock fixture block + + results & + P.alignExact ? onChain chain0 ? + P.alignExact ? Vector.replicate 1 successfulTx + +continueBlockSpec :: RocksDb -> IO () +continueBlockSpec baseRdb = runResourceT $ do + fixture <- mkFixture baseRdb + liftIO $ do + startCut <- getCut fixture + + -- construct some transactions that we plan to put into the block + cmd1 <- buildCwCmd v (transferCmd 1.0) + cmd2 <- buildCwCmd v (transferCmd 2.0) + cmd3 <- buildCwCmd v (transferCmd 3.0) + + -- insert all transactions + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd1, cmd2, cmd3] + allAtOnceResults <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + -- construct a new block with all of said transactions + bipAllAtOnce <- continueBlock fixture =<< makeEmptyBlock fixture ph + return $ finalizeBlock fixture bipAllAtOnce + -- assert that 3 successful txs are in the block + allAtOnceResults & + P.alignExact ? onChain chain0 ? + P.alignExact ? Vector.replicate 3 successfulTx + + -- reset back to the empty block for the next phase + -- next, produce the same block by repeatedly extending a block + -- with the same transactions as were included in the original. + -- note that this will reinsert all txs in the full block into the + -- mempool, so we need to clear it after, or else the block will + -- contain all of the transactions before we extend it. + revert fixture startCut + mempoolClear fixture chain0 + continuedResults <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd3] + bipStart <- continueBlock fixture =<< makeEmptyBlock fixture ph + + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd2] + bipContinued <- continueBlock fixture bipStart + + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd1] + bipFinal <- continueBlock fixture bipContinued + + -- We must make progress on the same parent header + assertEqual "same block context after continuing block" + (_blockInProgressBlockCtx bipStart) + (_blockInProgressBlockCtx bipContinued) + assertBool "made progress (1)" + (bipStart /= bipContinued) + assertEqual "same block context after finishing block" + (_blockInProgressBlockCtx bipContinued) + (_blockInProgressBlockCtx bipFinal) + assertBool "made progress (2)" + (bipContinued /= bipFinal) + + return $ finalizeBlock fixture bipFinal + + -- assert that the continued results are equal to doing it all at once + continuedResults & P.equals allAtOnceResults -- -- * test that the NewBlock timeout works properly and doesn't leave any extra state from a timed-out transaction --- newBlockTimeoutSpec :: RocksDb -> IO () --- newBlockTimeoutSpec baseRdb = runResourceT $ do --- let pactServiceConfig = testPactServiceConfig --- { _pactTxTimeLimit = Just (Micros 35_000) --- -- this may need to be tweaked for CI. --- -- it should be long enough that `timeoutTx` times out --- -- but neither `tx1` nor `tx2` time out. --- } --- fixture <- mkFixtureWith pactServiceConfig baseRdb - --- liftIO $ do --- tx1 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "1" --- , _cbGasPrice = GasPrice 1.0 --- , _cbGasLimit = GasLimit (Gas 400) --- } --- tx2 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "2" --- , _cbGasPrice = GasPrice 2.0 --- , _cbGasLimit = GasLimit (Gas 400) --- } --- timeoutTx <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' $ "(fold + 0 (enumerate 1 500000))" --- , _cbGasPrice = GasPrice 1.5 --- , _cbGasLimit = GasLimit (Gas 5000) --- } - --- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsertPact5 mempool CheckedInsert [tx2, timeoutTx, tx1] --- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< --- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- -- Mempool orders by GasPrice. 'buildCwCmd' sets the gas price to the transfer amount. --- -- We hope for 'timeoutTx' to fail, meaning that only 'txTransfer2' is in the block. --- bip & P.fun _blockInProgressTransactions ? P.fun _transactionPairs --- ? P.alignExact ? Vector.fromList --- [ P.pair --- (P.fun _cmdHash ? P.equals (_cmdHash tx2)) --- successfulTx --- ] --- return $ finalizeBlock bip - --- pure () - --- testNewBlockExcludesInvalid :: RocksDb -> IO () --- testNewBlockExcludesInvalid baseRdb = runResourceT $ do --- fixture <- mkFixture baseRdb --- liftIO $ do --- -- The mempool should reject a tx that doesn't parse as valid pact. --- badParse <- buildCwCmdNoParse v (defaultCmd chain0) --- { _cbRPC = mkExec' "(not a valid pact tx" --- } - --- regularTx1 <- buildCwCmd v $ transferCmd 1.0 --- -- The mempool checks that a tx does not already exist in the chain before adding it. --- let badUnique = regularTx1 - --- -- The mempool checks that a tx does not have a creation time too far into the future. --- badFuture <- buildCwCmd v $ (transferCmd 1.0) --- { _cbCreationTime = Just $ TxCreationTime (2 ^ (32 :: Word)) --- } - --- -- The mempool checks that a tx does not have a creation time too far into the past. --- badPast <- buildCwCmd v $ (transferCmd 1.0) --- { _cbCreationTime = Just $ TxCreationTime 0 --- } - --- regularTx2 <- buildCwCmd v $ transferCmd 1.0 --- -- The mempool checks that a tx has a valid hash. --- let badTxHash = regularTx2 --- { _cmdHash = Pact5.Hash "bad hash" --- } - --- badSigs <- buildCwCmdNoParse v (defaultCmd chain0) --- { _cbSigners = --- [ CmdSigner --- { _csSigner = Signer --- { _siScheme = Nothing --- , _siPubKey = fst sender00 --- , _siAddress = Nothing --- , _siCapList = [] --- } --- , _csPrivKey = snd sender01 --- } --- ] --- } - --- badChain <- buildCwCmd v $ transferCmd 1.0 & set cbChainId (chainIdToText $ unsafeChainId 1) - --- let pact4Hash = Pact5.Hash . Pact4.unHash . Pact4.toUntypedHash . Pact4._cmdHash --- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsertPact5 mempool CheckedInsert [regularTx1] --- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- return $ finalizeBlock bip - --- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsert mempool UncheckedInsert $ Vector.fromList [badParse, badSigs] --- mempoolInsertPact5 mempool UncheckedInsert [badChain, badUnique, badFuture, badPast, badTxHash] --- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- let expectedTxs = [] --- let actualTxs = Vector.toList $ Vector.map (unRequestKey . _crReqKey . snd) $ _transactionPairs $ _blockInProgressTransactions bip --- assertEqual "block has excluded all invalid transactions" expectedTxs actualTxs --- return $ finalizeBlock bip - --- -- we need to wait until this above block is validate for `badUnique` --- -- to disappear, because only the parent block is used to find txs to --- -- delete from the mempool --- let mempool = _fixtureMempools fixture ^?! atChain chain0 --- mempoolInsertPact5 mempool CheckedInsert [badUnique, badFuture, badPast, badTxHash] - --- let badTxHashes = --- [ pact4Hash badParse --- , _cmdHash badUnique --- , _cmdHash badFuture --- , _cmdHash badPast --- , _cmdHash badTxHash --- , pact4Hash badSigs --- ] --- inMempool <- mempoolLookupPact5 mempool (Vector.fromList badTxHashes) --- forM_ (zip [0 :: Word ..] badTxHashes) $ \(i, badHash) -> do --- assertBool ("bad tx [index = " <> sshow i <> ", hash = " <> sshow badTxHash <> "] should have been evicted from the mempool") $ not $ HashSet.member badHash inMempool - --- return () - --- lookupPactTxsSpec :: RocksDb -> IO () --- lookupPactTxsSpec baseRdb = runResourceT $ do --- fixture <- mkFixture baseRdb --- liftIO $ do --- cmd1 <- buildCwCmd v (transferCmd 1.0) --- cmd2 <- buildCwCmd v (transferCmd 2.0) - --- -- Depth 0 --- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2] --- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- return $ finalizeBlock bip - --- let rks = List.sort $ List.map (Pact5.unHash . _cmdHash) [cmd1, cmd2] - --- let lookupExpect :: Maybe Word -> IO () --- lookupExpect depth = do --- txs <- lookupPactTxs (fmap (ConfirmationDepth . fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) --- assertEqual ("all txs should be available with depth=" ++ show depth) (HashSet.fromList rks) (HashMap.keysSet txs) --- let lookupDontExpect :: Maybe Word -> IO () --- lookupDontExpect depth = do --- txs <- lookupPactTxs (fmap (ConfirmationDepth. fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) --- assertEqual ("no txs should be available with depth=" ++ show depth) HashSet.empty (HashMap.keysSet txs) +newBlockTimeoutSpec :: RocksDb -> IO () +newBlockTimeoutSpec baseRdb = runResourceT $ do + let pactServiceConfig cid = (testPactServiceConfig (genesisPayload cid)) + { _pactTxTimeLimit = Just (Micros 35_000) + -- this may need to be tweaked for CI. + -- it should be long enough that `timeoutTx` times out + -- but neither `tx1` nor `tx2` time out. + , _pactNewBlockGasLimit = GasLimit (Gas 2000000) + } + fixture <- mkFixtureWith pactServiceConfig baseRdb --- lookupExpect Nothing --- lookupExpect (Just 0) --- lookupDontExpect (Just 1) + liftIO $ do + tx1 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "1" + , _cbGasPrice = GasPrice 1.0 + , _cbGasLimit = GasLimit (Gas 400) + } + tx2 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "2" + , _cbGasPrice = GasPrice 2.0 + , _cbGasLimit = GasLimit (Gas 400) + } + timeoutTx <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' $ "(fold + 0 (enumerate 1 10000000))" + , _cbGasPrice = GasPrice 1.5 + , _cbGasLimit = GasLimit (Gas 130000) + } --- -- Depth 1 --- _ <- advanceAllChains fixture $ onChains [] + results <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + mempoolInsert fixture chain0 Mempool.CheckedInsert [tx2, timeoutTx, tx1] + bip <- continueBlock fixture =<< makeEmptyBlock fixture ph + return $ finalizeBlock fixture bip + results + & P.alignExact ? onAllChains v (\cid -> + if cid == chain0 + then P.alignExact ? Vector.singleton ? + -- Mempool orders by GasPrice. 'buildCwCmd' sets the gas price to the transfer amount. + -- We hope for 'timeoutTx' to fail, meaning that only 'txTransfer2' is in the block. + P.checkAll + [ P.fun _crReqKey ? P.equals (cmdToRequestKey tx2) + , successfulTx + ] + else P.equals Vector.empty + ) + pure () + +testNewBlockExcludesInvalid :: RocksDb -> IO () +testNewBlockExcludesInvalid baseRdb = runResourceT $ do + fixture <- mkFixture baseRdb + liftIO $ do + -- The mempool should reject a tx that doesn't parse as valid pact. + -- TODO PP: let's test this in the mempool + pact REST API tests + -- badParse <- buildCwCmd v (defaultCmd chain0) + -- { _cbRPC = mkExec' "(not a valid pact tx" + -- } + + regularTx1 <- buildCwCmd v $ transferCmd 1.0 + -- The mempool checks that a tx does not already exist in the chain before adding it. + let badUnique = regularTx1 + + -- The mempool checks that a tx does not have a creation time too far into the future. + badFuture <- buildCwCmd v $ (transferCmd 1.0) + { _cbCreationTime = Just $ TxCreationTime (2 ^ (32 :: Word)) + } --- lookupExpect Nothing --- lookupExpect (Just 0) --- lookupExpect (Just 1) --- lookupDontExpect (Just 2) + -- The mempool checks that a tx does not have a creation time too far into the past. + badPast <- buildCwCmd v $ (transferCmd 1.0) + { _cbCreationTime = Just $ TxCreationTime 0 + } --- -- Depth 2 --- _ <- advanceAllChains fixture $ onChains [] + regularTx2 <- buildCwCmd v $ transferCmd 1.0 + -- The mempool checks that a tx has a valid hash. + let badTxHash = regularTx2 + { _cmdHash = Pact.hash "wrong string" + } + + badSigs <- buildCwCmdNoSigCheck v (defaultCmd chain0) + { _cbSigners = + [ CmdSigner + { _csSigner = Signer + { _siScheme = Nothing + , _siPubKey = fst sender00 + , _siAddress = Nothing + , _siCapList = [] + } + , _csPrivKey = snd sender01 + } + ] + } --- lookupExpect Nothing --- lookupExpect (Just 0) --- lookupExpect (Just 1) --- lookupExpect (Just 2) --- lookupDontExpect (Just 3) + badChain <- buildCwCmd v $ transferCmd 1.0 & set cbChainId (chainIdToText $ unsafeChainId 1) + + _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + mempoolInsert fixture chain0 Mempool.CheckedInsert [regularTx1] + finalizeBlock fixture <$> makeFilledBlock fixture ph + + _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + mempoolInsert fixture chain0 Mempool.UncheckedInsert [badSigs] + mempoolInsert fixture chain0 Mempool.UncheckedInsert [badChain, badUnique, badFuture, badPast, badTxHash] + bip <- makeFilledBlock fixture ph + let expectedTxs = [] + let actualTxs = + Vector.toList $ + Vector.map (unRequestKey . _crReqKey . ssnd) $ + _transactionPairs (_blockInProgressTransactions bip) + assertEqual "block has excluded all invalid transactions" expectedTxs actualTxs + return $ finalizeBlock fixture bip + + -- we need to wait until this above block is validate for `badUnique` + -- to disappear, because only the parent block is used to find txs to + -- delete from the mempool + let mempool = _fixtureMempools fixture ^?! atChain chain0 + mempoolInsert fixture chain0 Mempool.CheckedInsert [badUnique, badFuture, badPast, badTxHash] + + let badTxHashes = Vector.fromList $ fmap Mempool.pactRequestKeyToTransactionHash + [ cmdToRequestKey badUnique + , cmdToRequestKey badFuture + , cmdToRequestKey badPast + , cmdToRequestKey badTxHash + , cmdToRequestKey badSigs + ] + + inMempool <- Mempool.mempoolLookup mempool badTxHashes + let badTxsInMempool = + [ i + | (i, Mempool.Pending _) <- zip [0 :: Word ..] (Vector.toList inMempool) + ] + badTxsInMempool & P.equals [] + return () + +lookupPactTxsSpec :: RocksDb -> IO () +lookupPactTxsSpec baseRdb = runResourceT $ do + fixture <- mkFixture baseRdb + liftIO $ do + cmd1 <- buildCwCmd v (transferCmd 1.0) + cmd2 <- buildCwCmd v (transferCmd 2.0) + + -- Depth 0 + _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd1, cmd2] + bip <- makeFilledBlock fixture ph + return $ finalizeBlock fixture bip + + let rks = List.sort $ List.map (Pact.unHash . _cmdHash) [cmd1, cmd2] + + let lookupExpect :: Maybe Word -> IO () + lookupExpect depth = do + txs <- PactService.execLookupPactTxs logger (fmap (ConfirmationDepth . fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) + assertEqual ("all txs should be available with depth=" ++ show depth) (HashSet.fromList rks) (HashMap.keysSet txs) + let lookupDontExpect :: Maybe Word -> IO () + lookupDontExpect depth = do + txs <- PactService.execLookupPactTxs (fmap (ConfirmationDepth. fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) + assertEqual ("no txs should be available with depth=" ++ show depth) HashSet.empty (HashMap.keysSet txs) + + lookupExpect Nothing + lookupExpect (Just 0) + lookupDontExpect (Just 1) + + -- Depth 1 + _ <- advanceAllChains fixture $ onChains [] + + lookupExpect Nothing + lookupExpect (Just 0) + lookupExpect (Just 1) + lookupDontExpect (Just 2) + + -- Depth 2 + _ <- advanceAllChains fixture $ onChains [] + + lookupExpect Nothing + lookupExpect (Just 0) + lookupExpect (Just 1) + lookupExpect (Just 2) + lookupDontExpect (Just 3) -- failedTxsShouldGoIntoBlocks :: RocksDb -> IO () -- failedTxsShouldGoIntoBlocks baseRdb = runResourceT $ do @@ -427,7 +431,7 @@ tests baseRdb = testGroup "Pact5 PactServiceTest" -- -- Depth 0 -- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2] +-- mempoolInsert fixture chain0 CheckedInsert [cmd1, cmd2] -- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue -- let block = finalizeBlock bip -- assertEqual "block has 2 txs even though one of them failed" 2 (Vector.length $ _payloadWithOutputsTransactions block) @@ -471,7 +475,7 @@ tests baseRdb = testGroup "Pact5 PactServiceTest" -- } -- results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] +-- mempoolInsert fixture chain0 CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] -- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue -- let block = finalizeBlock bip -- return block @@ -548,7 +552,7 @@ tests baseRdb = testGroup "Pact5 PactServiceTest" -- } -- results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsertPact5 mempool CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] +-- mempoolInsert fixture chain0 CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] -- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue -- let block = finalizeBlock bip -- return block @@ -595,37 +599,75 @@ tests baseRdb = testGroup "Pact5 PactServiceTest" chain0 :: ChainId chain0 = unsafeChainId 0 --- advanceAllChainsWithTxs :: Fixture -> ChainMap [Pact5.Transaction] -> IO (ChainMap (Vector TestPact5CommandResult)) --- advanceAllChainsWithTxs fixture txsPerChain = --- advanceAllChains fixture $ --- txsPerChain <&> \txs ph pactQueue mempool -> do --- mempoolClear mempool --- mempoolInsertPact5 mempool CheckedInsert txs --- nb <- throwIfNotPact5 =<< throwIfNoHistory =<< --- newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- return $ finalizeBlock nb +finalizeBlock :: Fixture -> BlockInProgress -> PayloadWithOutputs +finalizeBlock Fixture{..} bip = + toPayloadWithOutputs + (fromJuste $ _psMiner $ _fixturePacts ^?! atChain (_chainId bip)) + (_blockInProgressTransactions bip) + +makeEmptyBlock :: Fixture -> Parent BlockHeader -> IO BlockInProgress +makeEmptyBlock Fixture{..} ph = do + Pool.withResource (_psReadSqlPool serviceEnv) $ \roSql -> do + (throwIfNoHistory =<<) $ + Checkpointer.readFrom _fixtureLogger v cid roSql (view blockCreationTime <$> ph) (view rankedBlockHash <$> ph) $ + \blockEnv initialBlockHandle -> PactService.makeEmptyBlock _fixtureLogger serviceEnv blockEnv initialBlockHandle + where + cid = _chainId ph + serviceEnv = _fixturePacts ^?! atChain cid + +continueBlock :: Fixture -> BlockInProgress -> IO BlockInProgress +continueBlock Fixture{..} bip = do + Pool.withResource (_psReadSqlPool serviceEnv) $ \roSql -> do + (throwIfNoHistory =<<) $ + Checkpointer.readFrom _fixtureLogger v cid roSql parentCreationTime parentRankedHash $ + \blockEnv _initialBlockHandle -> PactService.continueBlock _fixtureLogger serviceEnv (_psBlockDbEnv blockEnv) bip + where + parentCreationTime = (_bctxParentCreationTime $ _blockInProgressBlockCtx bip) + parentRankedHash = (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx bip) + cid = _chainId bip + serviceEnv = _fixturePacts ^?! atChain cid + +makeFilledBlock :: Fixture -> Parent BlockHeader -> IO BlockInProgress +makeFilledBlock fixture ph = continueBlock fixture =<< makeEmptyBlock fixture ph + +lookupPactTxs :: Fixture -> ChainId -> Maybe ConfirmationDepth -> Vector Pact.Hash -> IO (Historical (HM.HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash))) +lookupPactTxs Fixture{..} chain depth hashes = + PactService.execLookupPactTxs _fixtureLogger + +mempoolInsert :: Foldable f => Fixture -> ChainId -> Mempool.InsertType -> f Pact.Transaction -> IO () +mempoolInsert Fixture{..} cid insertType txs = + Mempool.mempoolInsert (_fixtureMempools ^?! atChain cid) insertType (Vector.fromList $ toList txs) + +mempoolClear :: Fixture -> ChainId -> IO () +mempoolClear Fixture{..} cid = + Mempool.mempoolClear (_fixtureMempools ^?! atChain cid) + +advanceAllChainsWithTxs :: Fixture -> ChainMap [Pact.Transaction] -> IO (ChainMap (Vector TestPact5CommandResult)) +advanceAllChainsWithTxs fixture txsPerChain = do + advanceAllChains fixture $ onAllChains v $ \cid ph -> do + let txs = txsPerChain ^?! atChain cid + mempoolClear fixture cid + mempoolInsert fixture cid Mempool.CheckedInsert txs + filledBlock <- makeFilledBlock fixture ph + return $ finalizeBlock fixture filledBlock -- this mines a block on *all chains*. if you don't specify a payload on a chain, -- it adds empty blocks! advanceAllChains :: () => Fixture - -> ChainMap (BlockHeader -> ServiceEnv RocksDbTable -> MempoolBackend Pact.Transaction -> IO PayloadWithOutputs) + -> ChainMap (Parent BlockHeader -> IO PayloadWithOutputs) -> IO (ChainMap (Vector TestPact5CommandResult)) -advanceAllChains Fixture{..} blocks = do +advanceAllChains fixture@Fixture{..} blocks = do commandResults <- forConcurrently (HashSet.toList (chainIds v)) $ \c -> do ph <- getParentTestBlockDb _fixtureBlockDb c creationTime <- getCurrentTimeIntegral let serviceEnv = _fixturePacts ^?! atChain c - let mempool = _fixtureMempools ^?! atChain c - let makeEmptyBlock p _ _ = do - bip <- throwIfNotPact5 =<< throwIfNoHistory =<< - newBlock noMiner NewBlockEmpty (ParentHeader p) pactQueue - return $! finalizeBlock bip - - payload <- fromMaybe makeEmptyBlock (blocks ^? atChain c) ph pactQueue mempool + payload <- case blocks ^? atChain c of + Nothing -> finalizeBlock fixture <$> makeEmptyBlock fixture ph + Just mkBlockOn -> mkBlockOn ph added <- addTestBlockDb _fixtureBlockDb - (succ $ view blockHeight ph) + (childBlockHeight v c $ view rankedBlockHash <$> ph) (Nonce 0) (\_ _ -> creationTime) c @@ -633,11 +675,14 @@ advanceAllChains Fixture{..} blocks = do when (not added) $ error "failed to mine block" ph' <- getParentTestBlockDb _fixtureBlockDb c - payload' <- validateBlock ph' (CheckablePayloadWithOutputs payload) pactQueue - assertEqual "payloads must not be altered by validateBlock" payload payload' + let forkInfo = blockToForkInfo (unwrapParent ph') ph Nothing + cs' <- PactService.syncToFork _fixtureLogger serviceEnv Nothing forkInfo + -- we always want the sync to succeed + brief cs' + & P.equals (brief (_forkInfoTargetState forkInfo)) commandResults :: Vector TestPact5CommandResult <- forM - (_payloadWithOutputsTransactions payload') + (_payloadWithOutputsTransactions payload) (decodeOrThrow' . LBS.fromStrict . _transactionOutputBytes @@ -647,6 +692,18 @@ advanceAllChains Fixture{..} blocks = do return (onChains commandResults) +blockToForkInfo :: BlockHeader -> Parent BlockHeader -> Maybe NewBlockCtx -> ForkInfo +blockToForkInfo bh ph newBlockCtx = ForkInfo + { _forkInfoTrace = + [ConsensusPayload (view blockPayloadHash bh) Nothing <$ + blockHeaderToEvaluationCtx ph] + , _forkInfoBasePayloadHash = view blockPayloadHash <$> ph + , _forkInfoTargetState = ConsensusState syncState syncState syncState + , _forkInfoNewBlockCtx = newBlockCtx + } + where + syncState = syncStateOfBlockHeader bh + getCut :: Fixture -> IO Cut getCut Fixture{..} = getCutTestBlockDb _fixtureBlockDb @@ -655,7 +712,16 @@ revert Fixture{..} c = do setCutTestBlockDb _fixtureBlockDb c forM_ (HashSet.toList (chainIds v)) $ \chain -> do ph <- getParentTestBlockDb _fixtureBlockDb chain - pactSyncToBlock ph (_fixturePactQueues ^?! atChain chain) + let syncState = syncStateOfBlockHeader (unwrapParent ph) + let serviceEnv = _fixturePacts ^?! atChain chain + let consensusState = ConsensusState syncState syncState syncState + cs' <- PactService.syncToFork _fixtureLogger serviceEnv Nothing ForkInfo + { _forkInfoTrace = [] + , _forkInfoBasePayloadHash = view blockPayloadHash <$> ph + , _forkInfoTargetState = consensusState + , _forkInfoNewBlockCtx = Nothing + } + cs' & P.equals consensusState transferCmd :: Decimal -> CmdBuilder transferCmd transferAmount = (defaultCmd chain0) From 370bfda9da4695dcf4983659e0fd312403cdb7ca Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 23 Apr 2025 12:25:34 -0400 Subject: [PATCH 115/378] Better error message when EVM fails to connect the first time Change-Id: Id000000064b083db166528e665df655d954c9827 --- src/Chainweb/PayloadProvider/EVM.hs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index ad433ac6db..6c77c57714 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -82,10 +82,9 @@ import Configuration.Utils hiding (Error) import Control.Concurrent import Control.Concurrent.Async import Control.Concurrent.STM -import Control.Exception +import Control.Exception.Safe import Control.Lens hiding ((.=)) import Control.Monad -import Control.Monad.Catch (MonadThrow, throwM) import Data.ByteString.Short qualified as BS import Data.List qualified as L import Data.LogMessage @@ -489,7 +488,7 @@ withEvmPayloadProvider logger v c rdb mgr conf f SomeChainIdT @c _ <- return $ someChainIdVal c let pldCli h = Rest.payloadClient @v @c @p h - genPld <- checkExecutionClient v c engineCtx (EVM.ChainId (fromSNat ecid)) + genPld <- checkExecutionClient logger v c engineCtx (EVM.ChainId (fromSNat ecid)) logFunctionText logger Info $ "genesis payload block hash: " <> sshow (EVM._hdrPayloadHash genPld) logFunctionText logger Debug $ "genesis payload from execution client: " <> sshow genPld pdb <- initPayloadDb $ payloadDbConfiguration v c rdb genPld @@ -546,16 +545,24 @@ payloadListener p = case (_evmMinerAddress p) of -- Returns the genesis header. -- checkExecutionClient - :: HasChainwebVersion v + :: (HasChainwebVersion v) + => Logger logger => HasChainId c - => v + => logger + -> v -> c -> JsonRpcHttpCtx -> EVM.ChainId -- ^ expected Ethereum Network ID -> IO Payload -checkExecutionClient v c ctx expectedEcid = do - ecid <- callMethodHttp @Eth_ChainId ctx Nothing +checkExecutionClient logger v c ctx expectedEcid = do + ecid <- try @_ @SomeException (callMethodHttp @Eth_ChainId ctx Nothing) >>= \case + Left err -> do + logFunctionText logger Error + $ "Exception while initially connecting to EVM on chain " <> toText (_chainId c) <> ": " + <> T.pack (displayException err) + error "Error connecting to EVM" + Right ecid -> return ecid unless (expectedEcid == ecid) $ throwM $ EvmChainIdMissmatch (Expected expectedEcid) (Actual ecid) callMethodHttp @Eth_GetBlockByNumber ctx (DefaultBlockNumber 0, False) >>= \case @@ -613,7 +620,7 @@ forkchoiceUpdate p t fcs attr = go t throwM $ ForkchoiceUpdatedTimeoutException t | otherwise = do lf Info $ briefJson (ForkchoiceUpdatedV3Request fcs attr) - r <- try @(RPC.Error EngineServerErrors EngineErrors) $ + r <- try @_ @(RPC.Error EngineServerErrors EngineErrors) $ RPC.callMethodHttp @Engine_ForkchoiceUpdatedV3 (_evmEngineCtx p) (ForkchoiceUpdatedV3Request fcs attr) case r of From 4332c45f60b14e76b3cb07692944f135945496ff Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 23 Apr 2025 12:25:34 -0400 Subject: [PATCH 116/378] test changes --- bench/Chainweb/Pact/Backend/ApplyCmd.hs | 2 +- bench/Chainweb/Pact/Backend/ForkingBench.hs | 2 +- bench/Chainweb/Pact/Backend/PactService.hs | 2 +- chainweb.cabal | 4 +- cwtools/ea/Ea.hs | 4 +- src/Chainweb/Chainweb.hs | 54 +-- src/Chainweb/Chainweb/ChainResources.hs | 28 +- src/Chainweb/Chainweb/Configuration.hs | 16 +- src/Chainweb/Chainweb/CutResources.hs | 2 +- src/Chainweb/Chainweb/MinerResources.hs | 5 +- src/Chainweb/CutDB.hs | 12 +- src/Chainweb/Miner/Coordinator.hs | 20 +- src/Chainweb/Pact/PactService.hs | 12 +- src/Chainweb/Pact/Types.hs | 22 +- src/Chainweb/PayloadProvider.hs | 52 +-- src/Chainweb/PayloadProvider/Pact.hs | 5 +- src/Chainweb/SPV/RestAPI/Server.hs | 20 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 150 +++---- src/Chainweb/WebBlockHeaderDB.hs | 15 +- test/lib/Chainweb/Test/Pact/Utils.hs | 5 - test/unit/Chainweb/Test/CutDB.hs | 261 +++++------- test/unit/Chainweb/Test/Pact/CutFixture.hs | 388 +++++++++--------- .../Chainweb/Test/Pact/PactServiceTest.hs | 356 ++++++++-------- .../unit/Chainweb/Test/Pact/RemotePactTest.hs | 2 +- test/unit/Chainweb/Test/Pact/SPVTest.hs | 4 +- .../Chainweb/Test/Pact/TransactionExecTest.hs | 2 +- 26 files changed, 683 insertions(+), 762 deletions(-) diff --git a/bench/Chainweb/Pact/Backend/ApplyCmd.hs b/bench/Chainweb/Pact/Backend/ApplyCmd.hs index 9bf5542f74..3422fbc1c9 100644 --- a/bench/Chainweb/Pact/Backend/ApplyCmd.hs +++ b/bench/Chainweb/Pact/Backend/ApplyCmd.hs @@ -100,7 +100,7 @@ benchApplyCmd ver rdb act = lgr <- testLogger psEnvVar <- newEmptyMVar - tid <- forkIO $ void $ withPactService ver chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql testPactServiceConfig $ do + tid <- forkIO $ void $ withPactService ver chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql defaultPactServiceConfig $ do initialPayloadState ver chain0 psEnv <- ask liftIO $ putMVar psEnvVar psEnv diff --git a/bench/Chainweb/Pact/Backend/ForkingBench.hs b/bench/Chainweb/Pact/Backend/ForkingBench.hs index fe856f69af..38d702f887 100644 --- a/bench/Chainweb/Pact/Backend/ForkingBench.hs +++ b/bench/Chainweb/Pact/Backend/ForkingBench.hs @@ -248,7 +248,7 @@ withResources rdb trunkLength logLevel f = C.envWithCleanup create destroy unwra startPact version l bhdb pdb mempool sqlEnv = do reqQ <- newPactQueue pactQueueSize - a <- async $ runPactService version cid l Nothing reqQ mempool bhdb pdb sqlEnv testPactServiceConfig + a <- async $ runPactService version cid l Nothing reqQ mempool bhdb pdb sqlEnv defaultPactServiceConfig { _pactNewBlockGasLimit = 180_000 , _pactPersistIntraBlockWrites = p } diff --git a/bench/Chainweb/Pact/Backend/PactService.hs b/bench/Chainweb/Pact/Backend/PactService.hs index c9cd7d1656..4d75c4d0f3 100644 --- a/bench/Chainweb/Pact/Backend/PactService.hs +++ b/bench/Chainweb/Pact/Backend/PactService.hs @@ -166,7 +166,7 @@ destroyFixture fx = do oneBlock :: ChainwebVersion -> RocksDb -> Word -> C.Benchmarkable oneBlock v rdb numTxs = let cid = unsafeChainId 0 - cfg = testPactServiceConfig + cfg = defaultPactServiceConfig setupEnv _ = do fx <- createFixture v rdb cfg diff --git a/chainweb.cabal b/chainweb.cabal index fc918ee07a..bcd17dc4ae 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -611,9 +611,9 @@ test-suite chainweb-tests Chainweb.Test.BlockHeaderDB Chainweb.Test.BlockHeaderDB.PruneForks Chainweb.Test.Chainweb.Utils.Paging - -- Chainweb.Test.CutDB + Chainweb.Test.CutDB Chainweb.Test.Difficulty - -- Chainweb.Test.Mempool + Chainweb.Test.Mempool Chainweb.Test.Mempool.Consensus Chainweb.Test.Mempool.InMem Chainweb.Test.Mempool.RestAPI diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index efe97b24cb..63818fa804 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -33,7 +33,7 @@ import Chainweb.Logger (genericLogger) import Chainweb.Miner.Pact (noMiner) import Chainweb.Pact.Backend.Utils import Chainweb.Pact.PactService -import Chainweb.Pact.Types (testPactServiceConfig) +import Chainweb.Pact.Types (defaultPactServiceConfig) import Chainweb.Pact.Utils (toTxCreationTime, emptyPayload) import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Validations (defaultMaxTTLSeconds) @@ -160,7 +160,7 @@ genPayloadModule v tag cid cwTxs = do withSystemTempDirectory "ea-pact-db" $ \pactDbDir -> do payloadWO <- withSqliteDb cid logger pactDbDir False $ \readWriteSql -> do roPool <- Pool.newPool $ Pool.defaultPoolConfig (startReadSqliteDb cid logger pactDbDir) stopSqliteDb 10 10 - withPactService v cid Nothing mempty logger Nothing pdb roPool readWriteSql (testPactServiceConfig emptyPayload) $ \serviceEnv -> + withPactService v cid Nothing mempty logger Nothing pdb roPool readWriteSql (defaultPactServiceConfig emptyPayload) $ \serviceEnv -> execNewGenesisBlock logger serviceEnv (V.fromList cwTxs) return $ TL.toStrict $ TB.toLazyText $ payloadModuleCode tag payloadWO diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 1f7568202a..084b6f8813 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -182,7 +182,7 @@ data Chainweb logger = Chainweb , _chainwebCoordinator :: !(Maybe (MiningCoordination logger)) , _chainwebLogger :: !logger , _chainwebPeer :: !(PeerResources logger) - , _chainwebPayloadProviders :: !PayloadProviders + , _chainwebPayloadProviders :: !(ChainMap ConfiguredPayloadProvider) , _chainwebManager :: !HTTP.Manager -- , _chainwebPactData :: ![(ChainId, PactServerData logger tbl)] , _chainwebThrottler :: !(Throttle Address) @@ -317,15 +317,15 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir myInfo peerDb (_configPayloadProviders conf) - x + (\cr -> x cr) ) -- initialize global resources after all chain resources are initialized (\cs -> do logg Debug "finished initializing chain resources" - global (HM.fromList $ zip cidsList cs) + global cs ) - cidsList + (onChains [(cid, cid) | cid <- cidsList]) where v = _configChainwebVersion conf @@ -354,22 +354,20 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir p2pConfig = _peerResConfig peerRes pactConfig chain maxGasLimit = PactServiceConfig - { _pactReorgLimit = _configReorgLimit conf - , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf - , _pactQueueSize = _configPactQueueSize conf - , _pactAllowReadsInLocal = _configAllowReadsInLocal conf - , _pactUnlimitedInitialRewind = - isJust (_cutDbParamsInitialHeightLimit cutDbParams) || - isJust (_cutDbParamsInitialCutFile cutDbParams) - , _pactNewBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) - , _pactLogGas = _configLogGas conf - , _pactEnableLocalTimeout = _configEnableLocalTimeout conf - , _pactFullHistoryRequired = _configFullHistoricPactState conf - , _pactTxTimeLimit = Nothing - -- FIXME - , _pactMiner = Nothing - , _pactGenesisPayload = Pact.genesisPayload v ^?! atChain chain - } + { _pactReorgLimit = _configReorgLimit conf + , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf + , _pactAllowReadsInLocal = _configAllowReadsInLocal conf + , _pactUnlimitedInitialRewind = + isJust (_cutDbParamsInitialHeightLimit cutDbParams) || + isJust (_cutDbParamsInitialCutFile cutDbParams) + , _pactNewBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) + , _pactLogGas = _configLogGas conf + , _pactEnableLocalTimeout = _configEnableLocalTimeout conf + , _pactFullHistoryRequired = _configFullHistoricPactState conf + , _pactTxTimeLimit = Nothing + -- FIXME + , _pactMiner = Nothing + } -- FIXME: make this configurable cutDbParams :: CutDbParams @@ -413,10 +411,10 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir -- avoid excessive indentation? global - :: HM.HashMap ChainId (ChainResources logger) + :: ChainMap (ChainResources logger) -> IO () global cs = do - let !webchain = mkWebBlockHeaderDb v (HM.map _chainResBlockHeaderDb cs) + let !webchain = mkWebBlockHeaderDb v (fmap _chainResBlockHeaderDb cs) -- !pact = mkWebPactExecutionService (HM.map _chainResPact cs) !providers = payloadProvidersForAllChains cs !cutLogger = setComponent "cut" logger @@ -447,10 +445,12 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir -- height) we want this to happen before we go online. -- let - pactSyncChains = - case _configSyncPactChains conf of - Just syncChains | _configOnlySyncPact conf || _configReadOnlyReplay conf -> HM.filterWithKey (\k _ -> elem k syncChains) cs - _ -> cs + -- pactSyncChains = + -- case _configSyncPactChains conf of + -- Just syncChains + -- | _configOnlySyncPact conf || _configReadOnlyReplay conf + -- -> HM.filterWithKey (\k _ -> elem k syncChains) cs + -- _ -> cs if _configReadOnlyReplay conf then do @@ -557,7 +557,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir } } - synchronizeProviders :: WebBlockHeaderDb -> PayloadProviders -> Cut -> IO () + synchronizeProviders :: WebBlockHeaderDb -> ChainMap ConfiguredPayloadProvider -> Cut -> IO () synchronizeProviders wbh providers c = do mapConcurrently_ syncOne (_cutHeaders c) where diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 26d4d800c5..251a56c4e0 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -87,6 +87,7 @@ import P2P.Peer (PeerInfo) import P2P.Session import P2P.TaskQueue import Prelude hiding (log) +import qualified Data.HashSet as HS -- -------------------------------------------------------------------------- -- -- Payload P2P Network Resources @@ -193,21 +194,13 @@ payloadServiceApiResources config pdb = PayloadServiceApiResources -- | Payload Provider Resources -- data ProviderResources = ProviderResources - { _providerResPayloadProvider :: !SomePayloadProvider + { _providerResPayloadProvider :: !ConfiguredPayloadProvider , _providerResServiceApi :: !(Maybe PayloadServiceApiResources) , _providerResP2pApiResources :: !(Maybe PayloadP2pResources) } makeLenses ''ProviderResources -instance HasChainwebVersion ProviderResources where - _chainwebVersion = _chainwebVersion . _providerResPayloadProvider - {-# INLINE _chainwebVersion #-} - -instance HasChainId ProviderResources where - _chainId = _chainId . _providerResPayloadProvider - {-# INLINE _chainId #-} - withPayloadProviderResources :: Logger logger => HasChainwebVersion v @@ -226,7 +219,9 @@ withPayloadProviderResources withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs inner = do SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal v SomeChainIdT @c' _ <- return $ someChainIdVal c - withSomeSing provider $ \case + if HS.member (_chainId c) (_payloadProviderConfigDisabled configs) + then inner $ ProviderResources DisabledPayloadProvider Nothing Nothing + else withSomeSing provider $ \case SMinimalProvider -> do -- FIXME this should be better abstracted. @@ -244,7 +239,7 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs p2pRes <- payloadP2pResources @v' @c' @'MinimalProvider logger p2pConfig myInfo peerDb pdb queue mgr inner ProviderResources - { _providerResPayloadProvider = SomePayloadProvider p + { _providerResPayloadProvider = ConfiguredPayloadProvider p , _providerResServiceApi = Nothing , _providerResP2pApiResources = Just p2pRes } @@ -253,6 +248,7 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs _config <- case HM.lookup cid (_payloadProviderConfigPact configs) of Just x -> return x Nothing -> error $ "Chainweb.Chainweb.ChainResources.withPayloadProviderResources: missing payload provider configuration for chain " <> sshow cid + -- , _pactGenesisPayload = Pact.genesisPayload v ^?! atChain chain error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for Pact" SEvmProvider @n _ -> do @@ -270,7 +266,7 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs p2pRes <- payloadP2pResources @v' @c' @('EvmProvider n) logger p2pConfig myInfo peerDb pdb queue mgr inner ProviderResources - { _providerResPayloadProvider = SomePayloadProvider p + { _providerResPayloadProvider = ConfiguredPayloadProvider p , _providerResServiceApi = Nothing , _providerResP2pApiResources = Just p2pRes } @@ -370,10 +366,10 @@ payloadsToServeOnServiceApi chains = catMaybes -- | Return the payload providers for all chains -- payloadProvidersForAllChains - :: HM.HashMap ChainId (ChainResources logger) - -> PayloadProviders -payloadProvidersForAllChains chains = PayloadProviders - $ (_providerResPayloadProvider . _chainResPayloadProvider) + :: ChainMap (ChainResources logger) + -> ChainMap ConfiguredPayloadProvider +payloadProvidersForAllChains chains = + (_providerResPayloadProvider . _chainResPayloadProvider) <$> chains -- | Returns actions for running the P2P nodes for all chains. diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index c05fc2392f..44cff8dbd1 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -31,6 +31,7 @@ module Chainweb.Chainweb.Configuration , payloadProviderConfigMinimal , payloadProviderConfigPact , payloadProviderConfigEvm +, payloadProviderConfigDisabled , defaultPayloadProviderConfig , minimalPayloadProviderConfig , validatePayloadProviderConfig @@ -93,7 +94,7 @@ import Chainweb.HostAddress import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Config -import Chainweb.Pact.Types (RewindLimit(..)) +import Chainweb.Pact.Types (RewindLimit(..), PactServiceConfig, defaultPactServiceConfig) import Chainweb.Pact.Types (defaultReorgLimit, defaultPreInsertCheckTimeout) import Chainweb.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) import Chainweb.PayloadProvider.EVM (EvmProviderConfig, defaultEvmProviderConfig, pEvmProviderConfig) @@ -159,6 +160,7 @@ data PayloadProviderConfig = PayloadProviderConfig { _payloadProviderConfigMinimal :: !MinimalProviderConfig , _payloadProviderConfigPact :: !(HM.HashMap ChainId PactProviderConfig) , _payloadProviderConfigEvm :: !(HM.HashMap ChainId EvmProviderConfig) + , _payloadProviderConfigDisabled :: !(HS.HashSet ChainId) } deriving (Show, Eq, Generic) @@ -171,6 +173,7 @@ defaultPayloadProviderConfig v = PayloadProviderConfig { _payloadProviderConfigMinimal = defaultMinimalProviderConfig , _payloadProviderConfigPact = pacts , _payloadProviderConfigEvm = evms + , _payloadProviderConfigDisabled = mempty } where (pacts, evms) = go (toList $ chainIds v) @@ -187,6 +190,7 @@ minimalPayloadProviderConfig m = PayloadProviderConfig { _payloadProviderConfigMinimal = m , _payloadProviderConfigPact = mempty , _payloadProviderConfigEvm = mempty + , _payloadProviderConfigDisabled = mempty } validatePayloadProviderConfig :: ChainwebVersion -> ConfigValidation PayloadProviderConfig [] @@ -196,18 +200,18 @@ validatePayloadProviderConfig v conf = do go [] = return () go (c:t) = go t <* case payloadProviderTypeForChain v c of PactProvider -> unless (HM.member c (_payloadProviderConfigPact conf)) $ - if (HM.member c (_payloadProviderConfigEvm conf)) + if HM.member c (_payloadProviderConfigEvm conf) || HS.member c (_payloadProviderConfigDisabled conf) then throwError $ mconcat $ - [ "Wrong payload provdider type configuration for chain " <> sshow c + [ "Wrong payload provider type configuration for chain " <> sshow c , ". Expected Pact but found EVM" ] <> msg else throwError $ mconcat $ "Missing Pact payload provider configuration for chain " <> sshow c : msg EvmProvider _ -> unless (HM.member c (_payloadProviderConfigEvm conf)) $ - if (HM.member c (_payloadProviderConfigPact conf)) + if HM.member c (_payloadProviderConfigPact conf) || HS.member c (_payloadProviderConfigDisabled conf) then throwError $ mconcat $ - [ "Wrong payload provdider type configuration for chain " <> sshow c + [ "Wrong payload provider type configuration for chain " <> sshow c , ". Expected EVM but found Pact" ] <> msg else throwError $ mconcat @@ -216,7 +220,7 @@ validatePayloadProviderConfig v conf = do MinimalProvider -> return () msg = [ ". In order to use chainweb-node with chainweb version " <> sshow v - , " you must provide a configuraton for all payload providers except" + , " you must provide a configuraton for all enabled payload providers except" , " the chains that use the default payload provider." , " The following is the default payload provider configuration for" , " chainweb version " <> sshow v diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 20f181dc96..89ab9547f0 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -84,7 +84,7 @@ withCutResources -> PeerDb -> RocksDb -> WebBlockHeaderDb - -> PayloadProviders + -> ChainMap ConfiguredPayloadProvider -> HTTP.Manager -> (CutResources -> IO a) -> IO a diff --git a/src/Chainweb/Chainweb/MinerResources.hs b/src/Chainweb/Chainweb/MinerResources.hs index bf8a72a41a..3b60d56f37 100644 --- a/src/Chainweb/Chainweb/MinerResources.hs +++ b/src/Chainweb/Chainweb/MinerResources.hs @@ -42,7 +42,6 @@ import Chainweb.Miner.Miners import Chainweb.Version import Control.Concurrent.Async import Control.Lens -import Data.HashMap.Strict (HashMap) import Data.LogMessage (LogFunction) import System.Random.MWC qualified as MWC @@ -87,7 +86,7 @@ withMiningCoordination logger conf cdb inner data MinerResources logger = MinerResources { _minerResLogger :: !logger , _minerResCutDb :: !CutDb - , _minerChainResources :: !(HashMap ChainId (ChainResources logger)) + , _minerChainResources :: !(ChainMap (ChainResources logger)) , _minerResConfig :: !NodeMiningConfig , _minerResCoordination :: !(Maybe (MiningCoordination logger)) -- ^ The primed work cache. This is Nothing when coordination is @@ -98,7 +97,7 @@ data MinerResources logger = MinerResources withMinerResources :: logger -> NodeMiningConfig - -> HashMap ChainId (ChainResources logger) + -> ChainMap (ChainResources logger) -> CutDb -> Maybe (MiningCoordination logger) -> (Maybe (MinerResources logger) -> IO a) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index a63ae532e2..46ae303210 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -261,7 +261,7 @@ data CutDb = CutDb , _cutDbAsync :: !(Async ()) , _cutDbLogFunction :: !LogFunction , _cutDbHeaderStore :: !WebBlockHeaderStore - , _cutDbPayloadProviders :: !PayloadProviders + , _cutDbPayloadProviders :: !(ChainMap ConfiguredPayloadProvider) , _cutDbCutStore :: !(Casify RocksDbTable CutHashes) , _cutDbQueueSize :: !Natural , _cutDbReadOnly :: !Bool @@ -272,7 +272,7 @@ instance HasChainwebVersion CutDb where _chainwebVersion = _chainwebVersion . _cutDbHeaderStore {-# INLINE _chainwebVersion #-} -cutDbPayloadProviders :: Getter CutDb PayloadProviders +cutDbPayloadProviders :: Getter CutDb (ChainMap ConfiguredPayloadProvider) cutDbPayloadProviders = to _cutDbPayloadProviders {-# INLINE cutDbPayloadProviders #-} @@ -396,7 +396,7 @@ withCutDb :: CutDbParams -> LogFunction -> WebBlockHeaderStore - -> PayloadProviders + -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes -> (CutDb -> IO a) -> IO a @@ -418,7 +418,7 @@ startCutDb :: CutDbParams -> LogFunction -> WebBlockHeaderStore - -> PayloadProviders + -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes -> IO CutDb startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do @@ -541,7 +541,7 @@ processCuts :: CutDbParams -> LogFunction -> WebBlockHeaderStore - -> PayloadProviders + -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes -> PQueue (Down CutHashes) -> TVar Cut @@ -755,7 +755,7 @@ cutHashesToBlockHeaderMap :: CutDbParams -> LogFunction -> WebBlockHeaderStore - -> PayloadProviders + -> ChainMap ConfiguredPayloadProvider -> CutHashes -> IO (Maybe (HM.HashMap ChainId BlockHeader)) -- ^ The 'Left' value holds missing hashes, the 'Right' value holds diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index e38307a0ba..609840a7e2 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -608,15 +608,21 @@ runCoordination mr = do cdb = _coordCutDb mr providers = view cutDbPayloadProviders $ _coordCutDb mr + withProvider :: ChainId -> (forall p. PayloadProvider p => p -> a) -> a + withProvider cid k = case providers ^?! atChain cid of + ConfiguredPayloadProvider p -> k p + DisabledPayloadProvider -> + error $ "payload provider disabled on chain " <> sshow cid <> ", which is illegal for miners" -- Update the payload cache with the latest payloads from the the provider -- - updateCache (cid, cache) = runForever lf label $ do - withPayloadProvider providers cid $ \provider -> do - payloadStream provider - & S.chain (\_ -> lf Info $ "update cache on chain " <> toText cid) - & S.mapM_ (insertIO cache) - where + updateCache (cid, cache) = + withProvider cid $ \provider -> + runForever lf label $ do + payloadStream provider + & S.chain (\_ -> lf Info $ "update cache on chain " <> toText cid) + & S.mapM_ (insertIO cache) + where label = "miningCoordination.updateCache." <> toText cid -- Update the work state @@ -637,7 +643,7 @@ runCoordination mr = do lf Info $ "initialize mining state" forConcurrently_ (HM.toList caches) $ \(cid, cache) -> do lf Info $ "initialize mining state for chain " <> brief cid - pld <- withPayloadProvider providers cid latestPayloadIO + pld <- withProvider cid latestPayloadIO lf Info $ "got latest payload for chain " <> brief cid insertIO cache pld curRh <- _workRankedHash <$> readTVarIO (_miningState state ^?! ix cid) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index a6c9f393e9..d29c677070 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -136,8 +136,9 @@ withPactService -> Pool SQLiteEnv -> SQLiteEnv -> PactServiceConfig + -> Maybe PayloadWithOutputs -> ResourceT IO (ServiceEnv tbl) -withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config = do +withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config maybeGenesisPayload = do SomeChainwebVersionT @v _ <- pure $ someChainwebVersionVal ver SomeChainIdT @c _ <- pure $ someChainIdVal cid let payloadClient = Rest.payloadClient @v @c @'PactProvider @@ -169,7 +170,7 @@ withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb , _psMiner = _pactMiner config , _psNewBlockGasLimit = _pactNewBlockGasLimit config , _psMiningPayloadVar = miningPayloadVar - , _psGenesisPayload = _pactGenesisPayload config + , _psGenesisPayload = maybeGenesisPayload } return pse @@ -198,12 +199,15 @@ runGenesisIfNeeded logger serviceEnv = do let targetSyncState = genesisConsensusState v cid let evalCtx = genesisEvaluationCtx serviceEnv let blockCtx = blockCtxOfEvaluationCtx v cid evalCtx + let !genesisPayload = case _psGenesisPayload serviceEnv of + Nothing -> error "genesis needs to be run, but the genesis payload is missing!" + Just p -> p maybeErr <- runExceptT $ Checkpointer.restoreAndSave logger v cid (_psReadWriteSql serviceEnv) $ NEL.singleton $ (blockCtx, \blockEnv -> do _ <- Pact.execExistingBlock logger serviceEnv blockEnv - (CheckablePayloadWithOutputs (_psGenesisPayload serviceEnv)) + (CheckablePayloadWithOutputs genesisPayload) return ((), (genesisBlockHash, genesisPayloadHash)) ) case maybeErr of @@ -212,7 +216,7 @@ runGenesisIfNeeded logger serviceEnv = do addNewPayload (_payloadStoreTable $ _psPdb serviceEnv) (genesisHeight v cid) - (_psGenesisPayload serviceEnv) + genesisPayload Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState where diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index bdc9863502..ab0b6a72b7 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -49,7 +49,7 @@ module Chainweb.Pact.Types , genesisEvaluationCtx , PactServiceConfig(..) - , testPactServiceConfig + , defaultPactServiceConfig , BlockEnv(..) , psBlockDbEnv , psBlockCtx @@ -146,7 +146,6 @@ import Data.Text.Encoding qualified as T import Data.Vector (Vector) import Data.Word import GHC.Generics (Generic) -import Numeric.Natural import Pact.Core.Builtin qualified as Pact import Pact.Core.Capabilities qualified as Pact import Pact.Core.ChainData qualified as Pact @@ -422,8 +421,6 @@ data PactServiceConfig = PactServiceConfig -- ^ Maximum allowed execution time for the transactions validation. , _pactAllowReadsInLocal :: !Bool -- ^ Allow direct database reads in local mode - , _pactQueueSize :: !Natural - -- ^ max size of pact internal queue. , _pactUnlimitedInitialRewind :: !Bool -- ^ disable initial rewind limit , _pactNewBlockGasLimit :: !Pact.GasLimit @@ -441,24 +438,20 @@ data PactServiceConfig = PactServiceConfig -- If 'Nothing', it's a function of the BlockGasLimit. , _pactMiner :: !(Maybe Miner) -- ^ The miner used to make new blocks. - , _pactGenesisPayload :: !Chainweb.PayloadWithOutputs - -- ^ The genesis payload for this chain. } deriving (Eq,Show) -testPactServiceConfig :: Chainweb.PayloadWithOutputs -> PactServiceConfig -testPactServiceConfig genesisPayload = PactServiceConfig +defaultPactServiceConfig :: PactServiceConfig +defaultPactServiceConfig = PactServiceConfig { _pactReorgLimit = defaultReorgLimit , _pactPreInsertCheckTimeout = defaultPreInsertCheckTimeout - , _pactQueueSize = 1000 , _pactAllowReadsInLocal = False , _pactUnlimitedInitialRewind = False , _pactNewBlockGasLimit = testBlockGasLimit , _pactLogGas = False , _pactFullHistoryRequired = False - , _pactEnableLocalTimeout = False + , _pactEnableLocalTimeout = True , _pactTxTimeLimit = Nothing , _pactMiner = Just noMiner - , _pactGenesisPayload = genesisPayload } -- | This default value is only relevant for testing. In a chainweb-node the @GasLimit@ @@ -530,7 +523,7 @@ data ServiceEnv tbl = ServiceEnv -- ^ Latest mining payload produced, and block continuation thread. , _psNewBlockGasLimit :: Pact.GasLimit -- ^ Block gas limit in newly produced blocks. - , _psGenesisPayload :: !Chainweb.PayloadWithOutputs + , _psGenesisPayload :: !(Maybe Chainweb.PayloadWithOutputs) -- ^ The genesis payload for this chain. } @@ -562,8 +555,9 @@ genesisEvaluationCtx serviceEnv = EvaluationCtx , _evaluationCtxMinerReward = MinerReward 0 , _evaluationCtxPayload = ConsensusPayload { _consensusPayloadHash = genesisBlockPayloadHash v cid - , _consensusPayloadData = Just $ EncodedPayloadData $ Chainweb.encodePayloadData $ - Chainweb.payloadWithOutputsToPayloadData (_psGenesisPayload serviceEnv) + , _consensusPayloadData = + EncodedPayloadData . Chainweb.encodePayloadData . Chainweb.payloadWithOutputsToPayloadData + <$> _psGenesisPayload serviceEnv } } where diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 73602746d0..5facb949d5 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -67,13 +67,8 @@ module Chainweb.PayloadProvider , nextPayloadStm , payloadStream --- * Some PayloadProvider -, SomePayloadProvider(..) - --- * Payload Providers for all Chains -, PayloadProviders(..) -, payloadProviders -, withPayloadProvider +-- * PayloadProvider +, ConfiguredPayloadProvider(..) -- * SPV , PayloadSpvException(..) @@ -116,10 +111,8 @@ import Control.Monad.Catch import Control.Monad.IO.Class import Data.Aeson import Data.ByteString qualified as B -import Data.HashMap.Strict qualified as HM import Data.Text qualified as T import GHC.Generics (Generic) -import GHC.Stack (HasCallStack) import Numeric.Natural import P2P.Peer import Streaming.Prelude qualified as S @@ -958,44 +951,9 @@ newtype SpvProof = SpvProof Value -- -------------------------------------------------------------------------- -- -- Some Payload Provider -data SomePayloadProvider where - SomePayloadProvider :: PayloadProvider p => p -> SomePayloadProvider - -instance HasChainwebVersion SomePayloadProvider where - _chainwebVersion (SomePayloadProvider p) = _chainwebVersion p - -instance HasChainId SomePayloadProvider where - _chainId (SomePayloadProvider p) = _chainId p - --- -------------------------------------------------------------------------- -- --- Payload Providers - -newtype PayloadProviders = PayloadProviders - { _payloadProviders :: HM.HashMap ChainId SomePayloadProvider - } - -payloadProviders :: Getter PayloadProviders (HM.HashMap ChainId SomePayloadProvider) -payloadProviders = to _payloadProviders - -type instance Index PayloadProviders = ChainId -type instance IxValue PayloadProviders = SomePayloadProvider - -instance IxedGet PayloadProviders where - ixg i = to _payloadProviders . ix i - -withPayloadProvider - :: HasCallStack - => HasChainId c - => PayloadProviders - -> c - -> (forall p . PayloadProvider p => p -> a) - -> a -withPayloadProvider (PayloadProviders ps) c f = case HM.lookup cid ps of - Just (SomePayloadProvider p) -> f p - Nothing -> error $ - "PayloadProviders: unknown ChainId " <> sshow cid <> ". This is a bug" - where - cid = _chainId c +data ConfiguredPayloadProvider where + ConfiguredPayloadProvider :: PayloadProvider p => p -> ConfiguredPayloadProvider + DisabledPayloadProvider :: ConfiguredPayloadProvider -- -------------------------------------------------------------------------- -- -- Utils diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index d34190422a..698b0b57b0 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -104,8 +104,9 @@ withPactPayloadProvider -> PayloadDb tbl -> FilePath -> PactServiceConfig + -> Maybe PayloadWithOutputs -> ResourceT IO (PactPayloadProvider logger tbl) -withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir config = do +withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir config maybeGenesisPayload = do readWriteSqlenv <- withSqliteDb cid logger pactDbDir False (_, readOnlySqlPool) <- allocate (Pool.newPool $ Pool.defaultPoolConfig @@ -116,7 +117,7 @@ withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir c & Pool.setNumStripes (Just 2) -- two stripes, one connection per stripe ) Pool.destroyAllResources - PactPayloadProvider logger <$> PactService.withPactService ver cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config + PactPayloadProvider logger <$> PactService.withPactService ver cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config maybeGenesisPayload where mpa = pactMemPoolAccess mp $ addLabel ("sub-component", "MempoolAccess") logger diff --git a/src/Chainweb/SPV/RestAPI/Server.hs b/src/Chainweb/SPV/RestAPI/Server.hs index 8f9ebca71d..fa3d0b0d67 100644 --- a/src/Chainweb/SPV/RestAPI/Server.hs +++ b/src/Chainweb/SPV/RestAPI/Server.hs @@ -25,8 +25,9 @@ module Chainweb.SPV.RestAPI.Server , someSpvServers ) where +import Control.Lens ((^?!)) import Control.Monad.IO.Class -import Control.Exception (try, Exception(..)) +import Control.Exception.Safe (try, Exception(..)) import Data.Foldable import Chainweb.BlockHeight import Chainweb.ChainId @@ -98,7 +99,7 @@ spvGetEventProofHandler -- ^ The event index in the transaction -> Handler FakeEventProof spvGetEventProofHandler db tcid scid bh i e = do - liftIO (try getProof) >>= \case + (try getProof) >>= \case Left err -> do let msg = BL8.pack (displayException err) throwError $ case err of @@ -112,12 +113,15 @@ spvGetEventProofHandler db tcid scid bh i e = do Right v -> return $ FakeEventProof tcid v where - getProof = withPayloadProvider providers scid $ \p -> do - -- trgHeader <- minimumTrgHeader headerDb tcid scid bh - -- TODO: check through block height whether a proof can possibly - -- already by available before we do any expensive computations. - (SpvProof v) <- eventProof p xevent - return v + getProof = case providers ^?! atChain scid of + ConfiguredPayloadProvider p -> do + -- trgHeader <- minimumTrgHeader headerDb tcid scid bh + -- TODO: check through block height whether a proof can possibly + -- already by available before we do any expensive computations. + (SpvProof v) <- liftIO $ eventProof p xevent + return v + DisabledPayloadProvider -> + throwError err404 { errBody = BL8.pack "disabled payload provider on chain" } providers = view cutDbPayloadProviders db xevent = XEventId diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 7c2e0bf0da..67e8a97e28 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -389,7 +389,7 @@ getBlockHeaderInternal -> candidateHeaderCas -- ^ Ephemeral store for block headers under consideration -> candidatePldTbl - -> PayloadProviders + -> ChainMap ConfiguredPayloadProvider -> Maybe (BlockPayloadHash, EncodedPayloadOutputs) -- ^ Payload and Header data for the block, in case that it is -- available. @@ -487,89 +487,93 @@ getBlockHeaderInternal validateInductiveChainM (tableLookup chainDb) header -- Get the Payload Provider and - withPayloadProvider providers cid $ \provider -> do - let hints = Hints <$> maybeOrigin' - pld <- tableLookup candidatePldTbl (view blockPayloadHash header) - finfo <- forkInfoForHeader wdb header pld - - runConcurrently - -- instruct the payload provider to fetch payload data and prepare - -- validation. - $ Concurrently - (prefetchPayloads provider hints finfo - -- TODO (_rankedBlockPayloadHash header) - ) - - -- query parent (recursively) - -- - <* queryParent (view blockParent <$> chainValue header) - - -- query adjacent parents (recursively) - <* mconcat (queryAdjacentParent <$> adjParents header) - - -- TODO Above recursive calls are potentially long running - -- computations. In particular pact validation can take significant - -- amounts of time. We may try make these calls tail recursive by - -- providing a continuation. This would allow earlier garbage - -- collection of some stack resources. - -- - -- This requires to provide a CPS version of memoInsert. - - logg Debug $ taskMsg k $ "getBlockHeaderInternal got pre-requesites for " <> sshow h - - -- ------------------------------------------------------------------ -- - -- Validation - - -- 1. Validate Parents and Adjacent Parents + let hints = Hints <$> maybeOrigin' + pld <- tableLookup candidatePldTbl (view blockPayloadHash header) + finfo <- forkInfoForHeader wdb header pld + let prefetchProviderPayloads = case providers ^?! atChain cid of + ConfiguredPayloadProvider provider -> + prefetchPayloads provider hints finfo + -- TODO (_rankedBlockPayloadHash header) + DisabledPayloadProvider -> return () + + runConcurrently + -- instruct the payload provider to fetch payload data and prepare + -- validation. + $ Concurrently prefetchProviderPayloads + + -- query parent (recursively) -- - -- Existence and validity of parents and adjacent parents is guaranteed - -- in the dependency resolution code above. + <* queryParent (view blockParent <$> chainValue header) - -- 2. Validate BlockHeader - -- - -- Single chain properties are currently validated when the block header - -- is inserted into the block header db. + -- query adjacent parents (recursively) + <* mconcat (queryAdjacentParent <$> adjParents header) - -- 3. Validate Braiding - -- - -- Currently, we allow blocks here that are not part of a valid - -- braiding. However, those block won't make it into cuts, because the - -- cut processor uses 'joinIntoHeavier' to combine an external cut with - -- the local cut, which guarantees that only blocks with valid braiding - -- are referenced by local cuts. + -- TODO Above recursive calls are potentially long running + -- computations. In particular pact validation can take significant + -- amounts of time. We may try make these calls tail recursive by + -- providing a continuation. This would allow earlier garbage + -- collection of some stack resources. -- - -- TODO: check braiding and reject blocks without valid braiding here. + -- This requires to provide a CPS version of memoInsert. - -- 4. Validate block payload - -- - -- Pact validation is done in the context of a particular header. Just - -- because the payload does already exist in the store doesn't mean that - -- validation succeeds in the context of a particular block header. - -- - -- If we reach this point in the code we are certain that the header - -- isn't yet in the block header database and thus we still must - -- validate the payload for this block header. - -- + logg Debug $ taskMsg k $ "getBlockHeaderInternal got pre-requesites for " <> sshow h + + -- ------------------------------------------------------------------ -- + -- Validation + + -- 1. Validate Parents and Adjacent Parents + -- + -- Existence and validity of parents and adjacent parents is guaranteed + -- in the dependency resolution code above. + + -- 2. Validate BlockHeader + -- + -- Single chain properties are currently validated when the block header + -- is inserted into the block header db. - logg Debug $ taskMsg k $ - "getBlockHeaderInternal validate payload for " <> sshow h + -- 3. Validate Braiding + -- + -- Currently, we allow blocks here that are not part of a valid + -- braiding. However, those block won't make it into cuts, because the + -- cut processor uses 'joinIntoHeavier' to combine an external cut with + -- the local cut, which guarantees that only blocks with valid braiding + -- are referenced by local cuts. + -- + -- TODO: check braiding and reject blocks without valid braiding here. - r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do - logg Warn $ taskMsg k $ "getBlockHeaderInternal pact validation for " <> sshow h <> " failed with :" <> sshow e - throwM e - unless (r == _forkInfoTargetState finfo) $ do - throwM $ GetBlockHeaderFailure $ "unexpected result state" - <> "; expected: " <> sshow (_forkInfoTargetState finfo) - <> "; actual: " <> sshow r + -- 4. Validate block payload + -- + -- Pact validation is done in the context of a particular header. Just + -- because the payload does already exist in the store doesn't mean that + -- validation succeeds in the context of a particular block header. + -- + -- If we reach this point in the code we are certain that the header + -- isn't yet in the block header database and thus we still must + -- validate the payload for this block header. + -- + + logg Debug $ taskMsg k $ + "getBlockHeaderInternal validate payload for " <> sshow h + case providers ^?! atChain cid of + ConfiguredPayloadProvider provider -> do + r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do + logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation for " <> sshow h <> " failed with :" <> sshow e + throwM e + unless (r == _forkInfoTargetState finfo) $ do + throwM $ GetBlockHeaderFailure $ "unexpected result state" + <> "; expected: " <> sshow (_forkInfoTargetState finfo) + <> "; actual: " <> sshow r + DisabledPayloadProvider -> do + logg Debug $ taskMsg k $ "getBlockHeaderInternal payload provider disabled" - logg Debug $ taskMsg k "getBlockHeaderInternal pact validation succeeded" + logg Debug $ taskMsg k "getBlockHeaderInternal pact validation succeeded" - logg Debug $ taskMsg k $ "getBlockHeaderInternal return header " <> sshow h - return $! chainValue header + logg Debug $ taskMsg k $ "getBlockHeaderInternal return header " <> sshow h + return $! chainValue header logg Debug $ "getBlockHeaderInternal: got block header for " <> sshow h return bh - where + where mgr = _webBlockHeaderStoreMgr headerStore cas = WebBlockHeaderCas $ _webBlockHeaderStoreCas headerStore @@ -683,7 +687,7 @@ getBlockHeader => WebBlockHeaderStore -> candidateHeaderCas -> candidatePldTbl - -> PayloadProviders + -> ChainMap ConfiguredPayloadProvider -> Maybe (BlockPayloadHash, EncodedPayloadOutputs) -> ChainId -> Priority diff --git a/src/Chainweb/WebBlockHeaderDB.hs b/src/Chainweb/WebBlockHeaderDB.hs index 3601fe33cd..7c15c2ce6e 100644 --- a/src/Chainweb/WebBlockHeaderDB.hs +++ b/src/Chainweb/WebBlockHeaderDB.hs @@ -47,7 +47,6 @@ import Control.Monad.Catch import Data.Foldable import Data.Functor.Of import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS import qualified Data.List as L import qualified Streaming.Prelude as S @@ -85,7 +84,7 @@ import Chainweb.Storage.Table.RocksDB -- the dbs must be guarded see issue #123. -- data WebBlockHeaderDb = WebBlockHeaderDb - { _webBlockHeaderDb :: !(HM.HashMap ChainId BlockHeaderDb) + { _webBlockHeaderDb :: !(ChainMap BlockHeaderDb) , _webChainwebVersion :: !ChainwebVersion } @@ -97,13 +96,13 @@ instance HasChainwebVersion WebBlockHeaderDb where _chainwebVersion = _webChainwebVersion {-# INLINE _chainwebVersion #-} -webBlockHeaderDb :: Getter WebBlockHeaderDb (HM.HashMap ChainId BlockHeaderDb) +webBlockHeaderDb :: Getter WebBlockHeaderDb (ChainMap BlockHeaderDb) webBlockHeaderDb = to _webBlockHeaderDb -- | Returns all blocks in all block header databases. -- webEntries :: WebBlockHeaderDb -> (S.Stream (Of BlockHeader) IO () -> IO a) -> IO a -webEntries db f = go (view (webBlockHeaderDb . to HM.elems) db) mempty +webEntries db f = go (view (webBlockHeaderDb . to toList) db) mempty where go [] s = f s go (h:t) s = entries h Nothing Nothing Nothing Nothing $ \x -> @@ -114,7 +113,7 @@ type instance Index WebBlockHeaderDb = ChainId type instance IxValue WebBlockHeaderDb = BlockHeaderDb instance IxedGet WebBlockHeaderDb where - ixg i = webBlockHeaderDb . ix i + ixg i = webBlockHeaderDb . ixg i {-# INLINE ixg #-} instance (k ~ CasKeyType (ChainValue BlockHeader)) => ReadableTable WebBlockHeaderDb k (ChainValue BlockHeader) where @@ -128,7 +127,7 @@ initWebBlockHeaderDb -> ChainwebVersion -> IO WebBlockHeaderDb initWebBlockHeaderDb db v = WebBlockHeaderDb - <$!> itraverse (\cid _ -> initBlockHeaderDb (conf cid db)) (HS.toMap $ chainIds v) + <$!> onAllChainsM v (\cid -> initBlockHeaderDb (conf cid db)) <*> pure v where conf cid = Configuration (genesisBlockHeader v cid) @@ -137,7 +136,7 @@ initWebBlockHeaderDb db v = WebBlockHeaderDb -- mkWebBlockHeaderDb :: ChainwebVersion - -> HM.HashMap ChainId BlockHeaderDb + -> ChainMap BlockHeaderDb -> WebBlockHeaderDb mkWebBlockHeaderDb v m = WebBlockHeaderDb m v @@ -149,7 +148,7 @@ getWebBlockHeaderDb -> m BlockHeaderDb getWebBlockHeaderDb db p = do checkWebChainId graph p - return $! _webBlockHeaderDb db HM.! _chainId p + return $! _webBlockHeaderDb db ^?! atChain (_chainId p) where v = _chainwebVersion db graph = chainGraphAt v maxBound diff --git a/test/lib/Chainweb/Test/Pact/Utils.hs b/test/lib/Chainweb/Test/Pact/Utils.hs index e0faccf445..3d9e8021bd 100644 --- a/test/lib/Chainweb/Test/Pact/Utils.hs +++ b/test/lib/Chainweb/Test/Pact/Utils.hs @@ -22,7 +22,6 @@ module Chainweb.Test.Pact.Utils -- , mempoolLookupPact5 -- * Resources - , withTempSQLiteResource , withInMemSQLiteResource -- , withPactQueue , withMempool @@ -100,10 +99,6 @@ withSQLiteResource file = snd <$> allocate (openSQLiteConnection file chainwebPragmas) closeSQLiteConnection --- | Open a temporary file-backed SQLite database. -withTempSQLiteResource :: ResourceT IO SQLiteEnv -withTempSQLiteResource = withSQLiteResource "" - -- | Open a temporary in-memory SQLite database. withInMemSQLiteResource :: ResourceT IO SQLiteEnv withInMemSQLiteResource = withSQLiteResource ":memory:" diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index dd8741f26d..4d844392db 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -18,16 +18,15 @@ -- module Chainweb.Test.CutDB ( withTestCutDb -, extendTestCutDb -, syncPact +-- , extendTestCutDb , withTestCutDbWithoutPact , withTestPayloadResource , awaitCut , awaitBlockHeight -, extendAwait +-- , extendAwait , randomTransaction , randomBlockHeader -, fakePact +-- , fakePact , tests ) where @@ -90,6 +89,7 @@ import Data.LogMessage import Data.TaskMap import Test.Tasty.HUnit +import Chainweb.PayloadProvider (ConfiguredPayloadProvider(..)) -- -------------------------------------------------------------------------- -- -- Create a random Cut DB with the respective Payload Store @@ -114,7 +114,7 @@ withTestCutDb -- ^ any alterations to the CutDB's configuration -> Int -- ^ number of blocks in the chainweb in addition to the genesis blocks - -> (WebBlockHeaderDb -> PayloadDb RocksDbTable -> IO WebPactExecutionService) + -> (ChainMap ConfiguredPayloadProvider) -- ^ a pact execution service. -- -- When transaction don't matter you can use 'fakePact' from this module. @@ -125,67 +125,45 @@ withTestCutDb -- -> LogFunction -- ^ a logg function (use @\_ _ -> return ()@ turn of logging) - -> (forall tbl . CanReadablePayloadCas tbl => Casify RocksDbTable CutHashes -> CutDb tbl -> IO a) + -> (forall tbl . CanReadablePayloadCas tbl => Casify RocksDbTable CutHashes -> CutDb -> IO a) -> IO a -withTestCutDb rdb v conf n pactIO logfun f = do +withTestCutDb rdb v conf n providers logfun f = do rocksDb <- testRocksDb "withTestCutDb" rdb - let payloadDb = newPayloadDb rocksDb - cutHashesDb = cutHashesTable rocksDb + let cutHashesDb = cutHashesTable rocksDb webDb <- initWebBlockHeaderDb rocksDb v mgr <- HTTP.newManager HTTP.defaultManagerSettings - pact <- pactIO webDb payloadDb withLocalWebBlockHeaderStore mgr webDb $ \headerStore -> - withLocalPayloadStore mgr payloadDb pact $ \payloadStore -> - withCutDb (conf $ defaultCutDbParams v cutFetchTimeout) logfun headerStore payloadStore cutHashesDb $ \cutDb -> do - foldM_ (\c _ -> view _1 <$> mine defaultMiner pact cutDb c) (genesisCut v) [1..n] - f cutHashesDb cutDb - --- | Adds the requested number of new blocks to the given 'CutDb'. --- --- It is assumed that the 'WebPactExecutionService' is synced with the 'CutDb'. --- This can be done by calling 'syncPact'. The 'WebPactExecutionService' that --- was used to generate the given CutDb is already synced. --- --- If the 'WebPactExecutionService' is not synced with the 'CutDb', this --- function will result in an exception @PactInternalError --- "InMemoryCheckpointer: Restore not found"@. --- -extendTestCutDb - :: CanReadablePayloadCas tbl - => CutDb tbl - -> WebPactExecutionService - -> Natural - -> S.Stream (S.Of (Cut, ChainId, PayloadWithOutputs)) IO () -extendTestCutDb cutDb pact n = S.scanM - (\(c, _, _) _ -> mine defaultMiner pact cutDb c) - (mine defaultMiner pact cutDb =<< _cut cutDb) - return - (S.each [0..n-1]) - --- | Synchronize the a 'WebPactExecutionService' with a 'CutDb' by replaying all --- transactions of the payloads of all blocks in the 'CutDb'. --- -syncPact - :: CanReadablePayloadCas tbl - => CutDb tbl - -> WebPactExecutionService - -> IO () -syncPact cutDb pact = - void $ webEntries bhdb $ \s -> s - & S.filter ((/= 0) . view blockHeight) - & S.mapM_ (\h -> payload h >>= _webPactValidateBlock pact h . CheckablePayload) - where - bhdb = view cutDbWebBlockHeaderDb cutDb - pdb = view cutDbPayloadDb cutDb - payload h = lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) >>= \case - Nothing -> error $ "Corrupted database: failed to load payload data for block header " <> sshow h - Just p -> return $ payloadWithOutputsToPayloadData p + withCutDb (conf $ defaultCutDbParams v cutFetchTimeout) logfun headerStore providers cutHashesDb $ \cutDb -> do + foldM_ (\c _ -> view _1 <$> mine defaultMiner providers cutDb c) (genesisCut v) [1..n] + f cutHashesDb cutDb + +-- -- | Adds the requested number of new blocks to the given 'CutDb'. +-- -- +-- -- It is assumed that the 'WebPactExecutionService' is synced with the 'CutDb'. +-- -- This can be done by calling 'syncPact'. The 'WebPactExecutionService' that +-- -- was used to generate the given CutDb is already synced. +-- -- +-- -- If the 'WebPactExecutionService' is not synced with the 'CutDb', this +-- -- function will result in an exception @PactInternalError +-- -- "InMemoryCheckpointer: Restore not found"@. +-- -- +-- extendTestCutDb +-- :: CanReadablePayloadCas tbl +-- => CutDb +-- -> WebPactExecutionService +-- -> Natural +-- -> S.Stream (S.Of (Cut, ChainId, PayloadWithOutputs)) IO () +-- extendTestCutDb cutDb pact n = S.scanM +-- (\(c, _, _) _ -> mine defaultMiner pact cutDb c) +-- (mine defaultMiner pact cutDb =<< _cut cutDb) +-- return +-- (S.each [0..n-1]) -- | Atomically await for a 'CutDb' instance to synchronize cuts according to some -- predicate for a given 'Cut' and the results of '_cutStm'. -- awaitCut - :: CutDb tbl + :: CutDb -> (Cut -> Bool) -> IO Cut awaitCut cdb k = atomically $ do @@ -193,42 +171,42 @@ awaitCut cdb k = atomically $ do STM.check $ k c pure c --- | Extend the cut db until either a cut that meets some condition is --- encountered or the given number of cuts is mined. In the former case just the --- cut that fullfills the condition is returned. In the latter case 'Nothing' is --- returned. --- --- Note that the this function may skip over some cuts when waiting for a cut that satisfies the predicate. --- So, for instance, instead of checking for a particular cut height, one should --- check for a cut height that is larger or equal than the expected height. --- -extendAwait - :: CanReadablePayloadCas tbl - => CutDb tbl - -> WebPactExecutionService - -> Natural - -> (Cut -> Bool) - -> IO (Maybe Cut) -extendAwait cdb pact i p = race gen (awaitCut cdb p) >>= \case - Left _ -> return Nothing - Right c -> return (Just c) - where - gen = S.foldM_ checkCut (return 0) return - $ S.map (view (_1 . cutHeight)) - $ extendTestCutDb cdb pact i - - checkCut prev cur = do - unless (prev < cur) $ throwM $ InternalInvariantViolation $ unexpectedMsg - "New cut is not larger than the previous one. This is bug in Chainweb.Test.CutDB" - (Expected prev) - (Actual cur) - return cur +-- -- | Extend the cut db until either a cut that meets some condition is +-- -- encountered or the given number of cuts is mined. In the former case just the +-- -- cut that fullfills the condition is returned. In the latter case 'Nothing' is +-- -- returned. +-- -- +-- -- Note that the this function may skip over some cuts when waiting for a cut that satisfies the predicate. +-- -- So, for instance, instead of checking for a particular cut height, one should +-- -- check for a cut height that is larger or equal than the expected height. +-- -- +-- extendAwait +-- :: CanReadablePayloadCas tbl +-- => CutDb +-- -> WebPactExecutionService +-- -> Natural +-- -> (Cut -> Bool) +-- -> IO (Maybe Cut) +-- extendAwait cdb pact i p = race gen (awaitCut cdb p) >>= \case +-- Left _ -> return Nothing +-- Right c -> return (Just c) +-- where +-- gen = S.foldM_ checkCut (return 0) return +-- $ S.map (view (_1 . cutHeight)) +-- $ extendTestCutDb cdb pact i + +-- checkCut prev cur = do +-- unless (prev < cur) $ throwM $ InternalInvariantViolation $ unexpectedMsg +-- "New cut is not larger than the previous one. This is bug in Chainweb.Test.CutDB" +-- (Expected prev) +-- (Actual cur) +-- return cur -- | Wait for the cutdb to synchronize on a given blockheight for a given chain -- id -- awaitBlockHeight - :: CutDb tbl + :: CutDb -> BlockHeight -> ChainId -> IO Cut @@ -254,10 +232,10 @@ withTestCutDbWithoutPact -- ^ number of blocks in the chainweb in addition to the genesis blocks -> LogFunction -- ^ a logg function (use @\_ _ -> return ()@ turn of logging) - -> (forall tbl . CanReadablePayloadCas tbl => Casify RocksDbTable CutHashes -> CutDb tbl -> IO a) + -> (forall tbl . CanReadablePayloadCas tbl => Casify RocksDbTable CutHashes -> CutDb -> IO a) -> IO a withTestCutDbWithoutPact rdb v conf n = - withTestCutDb rdb v conf n (const $ const $ return fakePact) + withTestCutDb rdb v conf n (onAllChains v (\_ -> DisabledPayloadProvider)) -- | A version of withTestCutDb that can be used as a Tasty TestTree resource. -- @@ -266,7 +244,7 @@ withTestPayloadResource -> ChainwebVersion -> Int -> LogFunction - -> ResourceT IO (CutDb RocksDbTable) + -> ResourceT IO CutDb withTestPayloadResource rdb v n logfun = view _3 . snd <$> allocate start stopTestPayload where @@ -280,22 +258,20 @@ startTestPayload -> ChainwebVersion -> LogFunction -> Int - -> IO (Async (), Async (), CutDb RocksDbTable) + -> IO (Async (), CutDb) startTestPayload rdb v logfun n = do rocksDb <- testRocksDb "startTestPayload" rdb - let payloadDb = newPayloadDb rocksDb - cutHashesDb = cutHashesTable rocksDb - initializePayloadDb v payloadDb + let cutHashesDb = cutHashesTable rocksDb webDb <- initWebBlockHeaderDb rocksDb v mgr <- HTTP.newManager HTTP.defaultManagerSettings - (pserver, pstore) <- startLocalPayloadStore mgr payloadDb (hserver, hstore) <- startLocalWebBlockHeaderStore mgr webDb - cutDb <- startCutDb (defaultCutDbParams v cutFetchTimeout) logfun hstore pstore cutHashesDb - foldM_ (\c _ -> view _1 <$> mine defaultMiner fakePact cutDb c) (genesisCut v) [0..n] - return (pserver, hserver, cutDb) + let disabledPayloadProviders = onAllChains v $ \_ -> DisabledPayloadProvider + cutDb <- startCutDb (defaultCutDbParams v cutFetchTimeout) logfun hstore disabledPayloadProviders cutHashesDb + foldM_ (\c _ -> view _1 <$> mine defaultMiner disabledPayloadProviders cutDb c) (genesisCut v) [0..n] + return (hserver, cutDb) -stopTestPayload :: (Async (), Async (), CutDb tbl) -> IO () +stopTestPayload :: (Async (), Async (), CutDb) -> IO () stopTestPayload (pserver, hserver, cutDb) = do stopCutDb cutDb cancel hserver @@ -319,25 +295,6 @@ startLocalWebBlockHeaderStore mgr webDb = do mem <- new return (server, WebBlockHeaderStore webDb mem queue (\_ _ -> return ()) mgr) -withLocalPayloadStore - :: HTTP.Manager - -> PayloadDb tbl - -> WebPactExecutionService - -> (WebBlockPayloadStore tbl -> IO a) - -> IO a -withLocalPayloadStore mgr payloadDb pact inner = withNoopQueueServer $ \queue -> do - mem <- new - inner $ WebBlockPayloadStore payloadDb mem queue (\_ _ -> return ()) mgr pact - -startLocalPayloadStore - :: HTTP.Manager - -> PayloadDb tbl - -> IO (Async (), WebBlockPayloadStore tbl) -startLocalPayloadStore mgr payloadDb = do - (server, queue) <- startNoopQueueServer - mem <- new - return $ (server, WebBlockPayloadStore payloadDb mem queue (\_ _ -> return ()) mgr fakePact) - -- | Build a linear chainweb (no forks, assuming single threaded use of the -- cutDb). No POW or poison delay is applied. Block times are real times. -- @@ -346,10 +303,10 @@ mine => CanReadablePayloadCas tbl => Miner -- ^ The miner. For testing you may use 'defaultMiner'. - -> WebPactExecutionService + -> ChainMap ConfiguredPayloadProvider -- ^ only the new-block generator is used. For testing you may use -- 'fakePact'. - -> CutDb tbl + -> CutDb -> Cut -> IO (Cut, ChainId, PayloadWithOutputs) mine miner pact cutDb c = do @@ -396,10 +353,10 @@ tryMineForChain => Miner -- ^ The miner. For testing you may use 'defaultMiner'. -- miner. - -> WebPactExecutionService + -> ChainMap ConfiguredPayloadProvider -- ^ only the new-block generator is used. For testing you may use -- 'fakePact'. - -> CutDb tbl + -> CutDb -> Cut -> ChainId -> IO (Either MineFailure (Cut, ChainId, PayloadWithOutputs)) @@ -428,7 +385,7 @@ tryMineForChain miner webPact cutDb c cid = do -- randomBlockHeader :: HasCallStack - => CutDb tbl + => CutDb -> IO BlockHeader randomBlockHeader cutDb = do curCut <- _cut cutDb @@ -446,7 +403,7 @@ randomBlockHeader cutDb = do randomTransaction :: HasCallStack => CanReadablePayloadCas tbl - => CutDb tbl + => CutDb -> IO (BlockHeader, Int, Transaction, TransactionOutput) randomTransaction cutDb = do bh <- randomBlockHeader cutDb @@ -482,34 +439,34 @@ randomTransaction cutDb = do -- * The generated outputs are just the transaction bytes themself. -- * The coinbase is 'noCoinbase' -- -fakePact :: WebPactExecutionService -fakePact = WebPactExecutionService $ PactExecutionService - { _pactValidateBlock = - \_ p -> do - let d = checkablePayloadToPayloadData p - return - $ payloadWithOutputs d coinbase - $ getFakeOutput <$> view payloadDataTransactions d - , _pactNewBlock = \_ _ _ ph -> do - payloadDat <- generate $ V.fromList . getNonEmpty <$> arbitrary - return $ Historical - $ NewBlockPayload ph - $ newPayloadWithOutputs fakeMiner coinbase - $ (\x -> (x, getFakeOutput x)) <$> payloadDat - , _pactContinueBlock = \_ -> error "Unimplemented" - - , _pactLocal = \_t -> error "Unimplemented" - , _pactLookup = \_ _ -> error "Unimplemented" - , _pactPreInsertCheck = \_ _ -> error "Unimplemented" - , _pactBlockTxHistory = \_ _ -> error "Unimplemented" - , _pactHistoricalLookup = \_ _ _ -> error "Unimplemented" - , _pactSyncToBlock = \_ -> error "Unimplemented" - , _pactReadOnlyReplay = \_ _ -> error "Unimplemented" - } - where - getFakeOutput (Transaction txBytes) = TransactionOutput txBytes - coinbase = noCoinbaseOutput - fakeMiner = MinerData "fakeMiner" +-- fakePact :: WebPactExecutionService +-- fakePact = WebPactExecutionService $ PactExecutionService +-- { _pactValidateBlock = +-- \_ p -> do +-- let d = checkablePayloadToPayloadData p +-- return +-- $ payloadWithOutputs d coinbase +-- $ getFakeOutput <$> view payloadDataTransactions d +-- , _pactNewBlock = \_ _ _ ph -> do +-- payloadDat <- generate $ V.fromList . getNonEmpty <$> arbitrary +-- return $ Historical +-- $ NewBlockPayload ph +-- $ newPayloadWithOutputs fakeMiner coinbase +-- $ (\x -> (x, getFakeOutput x)) <$> payloadDat +-- , _pactContinueBlock = \_ -> error "Unimplemented" + +-- , _pactLocal = \_t -> error "Unimplemented" +-- , _pactLookup = \_ _ -> error "Unimplemented" +-- , _pactPreInsertCheck = \_ _ -> error "Unimplemented" +-- , _pactBlockTxHistory = \_ _ -> error "Unimplemented" +-- , _pactHistoricalLookup = \_ _ _ -> error "Unimplemented" +-- , _pactSyncToBlock = \_ -> error "Unimplemented" +-- , _pactReadOnlyReplay = \_ _ -> error "Unimplemented" +-- } +-- where +-- getFakeOutput (Transaction txBytes) = TransactionOutput txBytes +-- coinbase = noCoinbaseOutput +-- fakeMiner = MinerData "fakeMiner" tests :: RocksDb -> TestTree tests rdb = testGroup "CutDB" diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index de1b926447..1372a1dcf2 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -32,12 +32,10 @@ module Chainweb.Test.Pact.CutFixture , fixtureCutDb , fixturePayloadDb , fixtureWebBlockHeaderDb - , fixtureWebPactExecutionService , fixtureMempools - , fixturePactQueues - , advanceAllChains - , advanceAllChains_ - , withTestCutDb + -- , advanceAllChains + -- , advanceAllChains_ + -- , withTestCutDb ) where @@ -85,14 +83,18 @@ import Data.Text qualified as Text import Data.Vector (Vector) import GHC.Stack import Network.HTTP.Client qualified as HTTP +import qualified Data.Pool as Pool +import qualified Chainweb.Pact.PactService as PactService +import Chainweb.PayloadProvider.Pact (pactMemPoolAccess) +import Chainweb.Test.CutDB (withTestCutDb) data Fixture = Fixture - { _fixtureCutDb :: CutDb RocksDbTable + { _fixtureCutDb :: CutDb , _fixturePayloadDb :: PayloadDb RocksDbTable , _fixtureWebBlockHeaderDb :: WebBlockHeaderDb - , _fixtureWebPactExecutionService :: WebPactExecutionService - , _fixtureMempools :: ChainMap (MempoolBackend Pact4.UnparsedTransaction) - , _fixturePactQueues :: ChainMap PactQueue + , _fixtureLogger :: GenericLogger + , _fixturePacts :: ChainMap (ServiceEnv RocksDbTable) + , _fixtureMempools :: ChainMap (MempoolBackend Pact.Transaction) } makeLenses ''Fixture @@ -103,17 +105,20 @@ instance HasFixture Fixture where instance HasFixture a => HasFixture (IO a) where cutFixture = (>>= cutFixture) -mkFixture :: ChainwebVersion -> PactServiceConfig -> RocksDb -> ResourceT IO Fixture -mkFixture v pactServiceConfig baseRdb = do +mkFixture :: ChainwebVersion -> (ChainId -> PayloadWithOutputs) -> (PactServiceConfig) -> RocksDb -> ResourceT IO Fixture +mkFixture v genesisPayloadFor pactServiceConfig baseRdb = do logger <- liftIO getTestLogger testRdb <- liftIO $ testRocksDb "withBlockDbs" baseRdb (payloadDb, webBHDb) <- withBlockDbs v testRdb - perChain <- iforM (HashSet.toMap (chainIds v)) $ \chain () -> do - pactQueue <- liftIO $ newPactQueue 2_000 - mempool <- withMempool v chain pactQueue - withRunPactService logger v chain pactQueue mempool webBHDb payloadDb pactServiceConfig - return (mempool, pactQueue) - let webPact = mkWebPactExecutionService $ HashMap.map (mkPactExecutionService . snd) perChain + perChain <- fmap OnChains $ iforM (HashSet.toMap (chainIds v)) $ \chain () -> do + sqlite <- withTempSQLiteResource + fakeRoSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sqlite) (\_ -> return ()) 10 10) + serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing payloadDb fakeRoSqlPool sqlite pactServiceConfig (Just $ genesisPayloadFor chain) + mempool <- withMempool logger serviceEnv + let serviceEnv' = serviceEnv { _psMempoolAccess = pactMemPoolAccess mempool logger } + return (mempool, serviceEnv') + let pacts = snd <$> perChain + let mempools = fst <$> perChain let cutHashesStore = cutHashesTable testRdb cutDb <- withTestCutDb logger v webBHDb payloadDb cutHashesStore webPact @@ -121,205 +126,198 @@ mkFixture v pactServiceConfig baseRdb = do { _fixtureCutDb = cutDb , _fixturePayloadDb = payloadDb , _fixtureWebBlockHeaderDb = webBHDb - , _fixtureWebPactExecutionService = webPact , _fixtureMempools = OnChains $ fst <$> perChain - , _fixturePactQueues = OnChains $ snd <$> perChain } -- we create the first block to avoid rejecting txs based on genesis -- block creation time being from the past _ <- liftIO $ advanceAllChains fixture return fixture --- | Advance all chains by one block, filling that block with whatever is in --- their mempools at the time. --- -advanceAllChains - :: (HasCallStack, HasFixture a) - => a - -> IO (Cut, ChainMap (Vector TestPact5CommandResult)) -advanceAllChains fx = do - Fixture{..} <- cutFixture fx - let v = _chainwebVersion _fixtureCutDb - latestCut <- liftIO $ _fixtureCutDb ^. cut - let blockHeights = fmap (view blockHeight) $ latestCut ^. cutMap - let latestBlockHeight = maximum blockHeights +-- -- | Advance all chains by one block, filling that block with whatever is in +-- -- their mempools at the time. +-- -- +-- advanceAllChains +-- :: (HasCallStack, HasFixture a) +-- => a +-- -> IO (Cut, ChainMap (Vector TestPact5CommandResult)) +-- advanceAllChains fx = do +-- Fixture{..} <- cutFixture fx +-- let v = _chainwebVersion _fixtureCutDb +-- latestCut <- liftIO $ _fixtureCutDb ^. cut +-- let blockHeights = fmap (view blockHeight) $ latestCut ^. cutMap +-- let latestBlockHeight = maximum blockHeights - -- TODO: rejig this to do parallel mining. - (finalCut, perChainCommandResults) <- foldM - (\ (prevCut, !acc) cid -> do - (newCut, _minedChain, pwo) <- - mine cid noMiner NewBlockFill _fixtureWebPactExecutionService _fixtureCutDb prevCut - commandResults <- forM (_payloadWithOutputsTransactions pwo) $ \(_, txOut) -> do - decodeOrThrow' $ LBS.fromStrict $ _transactionOutputBytes txOut +-- -- TODO: rejig this to do parallel mining. +-- (finalCut, perChainCommandResults) <- foldM +-- (\ (prevCut, !acc) cid -> do +-- (newCut, _minedChain, pwo) <- +-- mine cid noMiner _fixtureWebPactExecutionService _fixtureCutDb prevCut +-- commandResults <- forM (_payloadWithOutputsTransactions pwo) $ \(_, txOut) -> do +-- decodeOrThrow' $ LBS.fromStrict $ _transactionOutputBytes txOut - addNewPayload _fixturePayloadDb latestBlockHeight pwo +-- addNewPayload _fixturePayloadDb latestBlockHeight pwo - return $ (newCut, (cid, commandResults) : acc) - ) - (latestCut, []) - (HashSet.toList (chainIdsAt v (latestBlockHeight + 1))) +-- return $ (newCut, (cid, commandResults) : acc) +-- ) +-- (latestCut, []) +-- (HashSet.toList (chainIdsAt v (latestBlockHeight + 1))) - return (finalCut, onChains perChainCommandResults) +-- return (finalCut, onChains perChainCommandResults) -advanceAllChains_ - :: (HasCallStack, HasFixture a) - => a - -> IO () -advanceAllChains_ = void . advanceAllChains +-- advanceAllChains_ +-- :: (HasCallStack, HasFixture a) +-- => a +-- -> IO () +-- advanceAllChains_ = void . advanceAllChains -withTestCutDb :: (Logger logger) - => logger - -> ChainwebVersion - -> WebBlockHeaderDb - -> PayloadDb RocksDbTable - -> Casify RocksDbTable CutHashes - -> WebPactExecutionService - -> ResourceT IO (CutDb RocksDbTable) -withTestCutDb logger v webBHDb payloadDb cutHashesStore webPact = snd <$> allocate create destroy - where - create :: IO (CutDb RocksDbTable) - create = do - initializePayloadDb v payloadDb - httpManager <- HTTP.newManager HTTP.defaultManagerSettings +-- withTestCutDb :: (Logger logger) +-- => logger +-- -> ChainwebVersion +-- -> WebBlockHeaderDb +-- -> PayloadDb RocksDbTable +-- -> Casify RocksDbTable CutHashes +-- -> ResourceT IO CutDb +-- withTestCutDb logger v webBHDb payloadDb cutHashesStore webPact = snd <$> allocate create destroy +-- where +-- create :: IO CutDb +-- create = do +-- initializePayloadDb v payloadDb +-- httpManager <- HTTP.newManager HTTP.defaultManagerSettings - headerStore <- newWebBlockHeaderStore httpManager webBHDb (logFunction logger) - payloadStore <- newWebPayloadStore httpManager webPact payloadDb (logFunction logger) +-- headerStore <- newWebBlockHeaderStore httpManager webBHDb (logFunction logger) +-- payloadStore <- newWebPayloadStore httpManager webPact payloadDb (logFunction logger) - let cutFetchTimeout = 3_000_000 - cutDb <- startCutDb (defaultCutDbParams v cutFetchTimeout) (logFunction logger) headerStore payloadStore cutHashesStore - return cutDb +-- let cutFetchTimeout = 3_000_000 +-- cutDb <- startCutDb (defaultCutDbParams v cutFetchTimeout) (logFunction logger) headerStore payloadStore cutHashesStore +-- return cutDb - destroy :: CutDb RocksDbTable -> IO () - destroy = stopCutDb +-- destroy :: CutDb -> IO () +-- destroy = stopCutDb --- | Build a linear chainweb (no forks, assuming single threaded use of the --- cutDb). No POW or poison delay is applied. Block times are real times. -mine - :: (HasCallStack, CanReadablePayloadCas tbl) - => ChainId - -> Miner - -- ^ The miner. For testing you may use 'defaultMiner' or 'noMiner'. - -> NewBlockFill - -> WebPactExecutionService - -- ^ only the new-block generator is used. For testing you may use - -- 'fakePact'. - -> CutDb tbl - -> Cut - -> IO (Cut, ChainId, PayloadWithOutputs) -mine cid miner newBlockStrat pact cutDb c = do - tryMineForChain miner newBlockStrat pact cutDb c cid >>= \case - Left _ -> throwM $ InternalInvariantViolation - $ "Failed to create new cut on chain " <> toText cid <> "." - <> "This is a bug in Chainweb.Test.Pact.CutFixture or one of its users; check that this chain's adjacent chains aren't too far behind." - <> "\nCut: \n" - <> Text.unlines (cutToTextShort c) - Right x -> do - void $ awaitCut cutDb $ ((<=) `on` _cutHeight) (view _1 x) - return x +-- -- | Build a linear chainweb (no forks, assuming single threaded use of the +-- -- cutDb). No POW or poison delay is applied. Block times are real times. +-- mine +-- :: (HasCallStack, CanReadablePayloadCas tbl) +-- => ChainId +-- -> Miner +-- -- ^ The miner. For testing you may use 'defaultMiner' or 'noMiner'. +-- -- ^ only the new-block generator is used. For testing you may use +-- -- 'fakePact'. +-- -> CutDb +-- -> Cut +-- -> IO (Cut, ChainId, PayloadWithOutputs) +-- mine cid miner newBlockStrat pact cutDb c = do +-- tryMineForChain miner newBlockStrat pact cutDb c cid >>= \case +-- Left _ -> throwM $ InternalInvariantViolation +-- $ "Failed to create new cut on chain " <> toText cid <> "." +-- <> "This is a bug in Chainweb.Test.Pact.CutFixture or one of its users; check that this chain's adjacent chains aren't too far behind." +-- <> "\nCut: \n" +-- <> Text.unlines (cutToTextShort c) +-- Right x -> do +-- void $ awaitCut cutDb $ ((<=) `on` _cutHeight) (view _1 x) +-- return x --- | Atomically await for a 'CutDb' instance to synchronize cuts according to some --- predicate for a given 'Cut' and the results of '_cutStm'. -awaitCut - :: CutDb tbl - -> (Cut -> Bool) - -> IO Cut -awaitCut cdb k = atomically $ do - c <- _cutStm cdb - STM.check $ k c - pure c +-- -- | Atomically await for a 'CutDb' instance to synchronize cuts according to some +-- -- predicate for a given 'Cut' and the results of '_cutStm'. +-- awaitCut +-- :: CutDb +-- -> (Cut -> Bool) +-- -> IO Cut +-- awaitCut cdb k = atomically $ do +-- c <- _cutStm cdb +-- STM.check $ k c +-- pure c -testMineWithPayloadHash - :: forall cid hdb. (HasChainId cid, ChainValueCasLookup hdb BlockHeader) - => hdb - -> Nonce - -> Time Micros - -> BlockPayloadHash - -> cid - -> Cut - -> IO (Either MineFailure (T2 BlockHeader Cut)) -testMineWithPayloadHash db n t ph cid c = try $ createNewCut (chainLookupM db) n t ph cid c +-- testMineWithPayloadHash +-- :: forall cid hdb. (HasChainId cid, ChainValueCasLookup hdb BlockHeader) +-- => hdb +-- -> Nonce +-- -> Time Micros +-- -> BlockPayloadHash +-- -> cid +-- -> Cut +-- -> IO (Either MineFailure (T2 BlockHeader Cut)) +-- testMineWithPayloadHash db n t ph cid c = try $ createNewCut (chainLookupM db) n t ph cid c --- | Create a new block. Only produces a new cut but doesn't insert it into the --- chain database. --- --- The creation time isn't checked. --- -createNewCut - :: (HasCallStack, MonadCatch m, MonadIO m, HasChainId cid) - => (ChainValue BlockHash -> m BlockHeader) - -> Nonce - -> Time Micros - -> BlockPayloadHash - -> cid - -> Cut - -> m (T2 BlockHeader Cut) -createNewCut hdb n t pay i c = do - extension <- fromMaybeM BadAdjacents $ getCutExtension c i - work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay - (h, mc') <- extendCut c pay (solveWork work n t) - `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg - c' <- fromMaybeM BadAdjacents mc' - return $ T2 h c' +-- -- | Create a new block. Only produces a new cut but doesn't insert it into the +-- -- chain database. +-- -- +-- -- The creation time isn't checked. +-- -- +-- createNewCut +-- :: (HasCallStack, MonadCatch m, MonadIO m, HasChainId cid) +-- => (ChainValue BlockHash -> m BlockHeader) +-- -> Nonce +-- -> Time Micros +-- -> BlockPayloadHash +-- -> cid +-- -> Cut +-- -> m (T2 BlockHeader Cut) +-- createNewCut hdb n t pay i c = do +-- extension <- fromMaybeM BadAdjacents $ getCutExtension c i +-- work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay +-- (h, mc') <- extendCut c pay (solveWork work n t) +-- `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg +-- c' <- fromMaybeM BadAdjacents mc' +-- return $ T2 h c' --- | Solve Work. Doesn't check that the nonce and the time are valid. --- -solveWork :: HasCallStack => WorkHeader -> Nonce -> Time Micros -> SolvedWork -solveWork w n t = - case runGetS decodeBlockHeaderWithoutHash $ SBS.fromShort $ _workHeaderBytes w of - Nothing -> error "Chainwb.Test.Cut.solveWork: Invalid work header bytes" - Just hdr -> SolvedWork - $ fromJuste - $ runGetS decodeBlockHeaderWithoutHash - $ runPutS - $ encodeBlockHeaderWithoutHash - -- After injecting the nonce and the creation time will have to do a - -- serialization roundtrip to update the Merkle hash. - -- - -- A "real" miner would inject the nonce and time without first - -- decoding the header and would hand over the header in serialized - -- form. +-- -- | Solve Work. Doesn't check that the nonce and the time are valid. +-- -- +-- solveWork :: HasCallStack => WorkHeader -> Nonce -> Time Micros -> SolvedWork +-- solveWork w n t = +-- case runGetS decodeBlockHeaderWithoutHash $ SBS.fromShort $ _workHeaderBytes w of +-- Nothing -> error "Chainwb.Test.Cut.solveWork: Invalid work header bytes" +-- Just hdr -> SolvedWork +-- $ fromJuste +-- $ runGetS decodeBlockHeaderWithoutHash +-- $ runPutS +-- $ encodeBlockHeaderWithoutHash +-- -- After injecting the nonce and the creation time will have to do a +-- -- serialization roundtrip to update the Merkle hash. +-- -- +-- -- A "real" miner would inject the nonce and time without first +-- -- decoding the header and would hand over the header in serialized +-- -- form. - $ set blockCreationTime (BlockCreationTime t) - $ set blockNonce n - $ hdr +-- $ set blockCreationTime (BlockCreationTime t) +-- $ set blockNonce n +-- $ hdr --- | Build a linear chainweb (no forks). No POW or poison delay is applied. --- Block times are real times. --- -tryMineForChain - :: forall tbl. (HasCallStack, CanReadablePayloadCas tbl) - => Miner - -- ^ The miner. For testing you may use 'defaultMiner'. - -> NewBlockFill - -> WebPactExecutionService - -> CutDb tbl - -> Cut - -> ChainId - -> IO (Either MineFailure (Cut, ChainId, PayloadWithOutputs)) -tryMineForChain miner newBlockStrat webPact cutDb c cid = do - newBlock <- throwIfNoHistory =<< _webPactNewBlock webPact cid miner newBlockStrat parent - let outputs = newBlockToPayloadWithOutputs newBlock - let payloadHash = _payloadWithOutputsPayloadHash outputs - t <- getCurrentTimeIntegral - x <- testMineWithPayloadHash wdb (Nonce 0) t payloadHash cid c - case x of - Right (T2 h c') -> do - addCutHashes cutDb (cutToCutHashes Nothing c') - { _cutHashesHeaders = HashMap.singleton (view blockHash h) h - , _cutHashesPayloads = HashMap.singleton (view blockPayloadHash h) (payloadWithOutputsToPayloadData outputs) - } - return $ Right (c', cid, outputs) - Left e -> return $ Left e - where - parent = ParentHeader $ c ^?! ixg cid -- parent to mine on - wdb = view cutDbWebBlockHeaderDb cutDb +-- -- | Build a linear chainweb (no forks). No POW or poison delay is applied. +-- -- Block times are real times. +-- -- +-- tryMineForChain +-- :: forall tbl. (HasCallStack, CanReadablePayloadCas tbl) +-- => Miner +-- -- ^ The miner. For testing you may use 'defaultMiner'. +-- -> CutDb +-- -> Cut +-- -> ChainId +-- -> IO (Either MineFailure (Cut, ChainId, PayloadWithOutputs)) +-- tryMineForChain miner newBlockStrat webPact cutDb c cid = do +-- newBlock <- throwIfNoHistory =<< _webPactNewBlock webPact cid miner newBlockStrat parent +-- let outputs = newBlockToPayloadWithOutputs newBlock +-- let payloadHash = _payloadWithOutputsPayloadHash outputs +-- t <- getCurrentTimeIntegral +-- x <- testMineWithPayloadHash wdb (Nonce 0) t payloadHash cid c +-- case x of +-- Right (T2 h c') -> do +-- addCutHashes cutDb (cutToCutHashes Nothing c') +-- { _cutHashesHeaders = HashMap.singleton (view blockHash h) h +-- , _cutHashesPayloads = HashMap.singleton (view blockPayloadHash h) (payloadWithOutputsToPayloadData outputs) +-- } +-- return $ Right (c', cid, outputs) +-- Left e -> return $ Left e +-- where +-- parent = ParentHeader $ c ^?! ixg cid -- parent to mine on +-- wdb = view cutDbWebBlockHeaderDb cutDb -data MineFailure - = InvalidHeader Text - -- ^ The header is invalid, e.g. because of a bad nonce or creation time. - | MissingParentHeader - -- ^ A parent header is missing in the chain db - | BadAdjacents - -- ^ This could mean that the chain is blocked. - deriving stock (Show) - deriving anyclass (Exception) +-- data MineFailure +-- = InvalidHeader Text +-- -- ^ The header is invalid, e.g. because of a bad nonce or creation time. +-- | MissingParentHeader +-- -- ^ A parent header is missing in the chain db +-- | BadAdjacents +-- -- ^ This could mean that the chain is blocked. +-- deriving stock (Show) +-- deriving anyclass (Exception) diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index f01fab742a..05b11c1f68 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -70,7 +70,7 @@ import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 import Chainweb.PayloadProvider.Pact (pactMemPoolAccess) import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer -import Chainweb.Pact.Backend.Types (throwIfNoHistory) +import Chainweb.Pact.Backend.Types (throwIfNoHistory, Historical) import qualified Chainweb.Pact.PactService as PactService import qualified Chainweb.Pact.PactService.ExecBlock as PactService import Chainweb.Parent @@ -80,6 +80,11 @@ import Data.Foldable (toList) import Pact.Core.ChainData (TxCreationTime (..)) import Pact.Core.Hash qualified as Pact import qualified Data.List as List +import Data.HashMap.Strict (HashMap) +import qualified Data.ByteString.Short as SB +import Chainweb.BlockHash (BlockHash) +import Chainweb.BlockHeight (BlockHeight) +import qualified Data.HashMap.Strict as HashMap data Fixture = Fixture { _fixtureBlockDb :: TestBlockDb @@ -91,7 +96,7 @@ data Fixture = Fixture v :: ChainwebVersion v = instantCpmTestVersion singletonChainGraph -mkFixtureWith :: (ChainId -> PactServiceConfig) -> RocksDb -> ResourceT IO Fixture +mkFixtureWith :: PactServiceConfig -> RocksDb -> ResourceT IO Fixture mkFixtureWith pactServiceConfig baseRdb = do tdb <- mkTestBlockDb v baseRdb logLevel <- liftIO getTestLogLevel @@ -100,7 +105,7 @@ mkFixtureWith pactServiceConfig baseRdb = do sqlite <- withTempSQLiteResource let pdb = _bdbPayloadDb tdb fakeRoSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sqlite) (\_ -> return ()) 10 10) - serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing pdb fakeRoSqlPool sqlite (pactServiceConfig chain) + serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing pdb fakeRoSqlPool sqlite pactServiceConfig (Just $ genesisPayload chain) liftIO $ PactService.initialPayloadState logger serviceEnv let mempoolCfg = validatingMempoolConfig chain v @@ -130,20 +135,19 @@ genesisPayload chain = mkFixture :: RocksDb -> ResourceT IO Fixture mkFixture baseRdb = do - mkFixtureWith (testPactServiceConfig . genesisPayload) baseRdb + mkFixtureWith defaultPactServiceConfig baseRdb tests :: RocksDb -> TestTree tests baseRdb = testGroup "Pact5 PactServiceTest" - -- [] [ testCase "simple end to end" (simpleEndToEnd baseRdb) , testCase "continue block spec" (continueBlockSpec baseRdb) , testCase "new block empty" (newBlockEmpty baseRdb) , testCase "new block timeout spec" (newBlockTimeoutSpec baseRdb) , testCase "new block excludes invalid transactions" (testNewBlockExcludesInvalid baseRdb) - -- , testCase "lookup pact txs spec" (lookupPactTxsSpec baseRdb) - -- , testCase "failed txs should go into blocks" (failedTxsShouldGoIntoBlocks baseRdb) - -- , testCase "modules with higher level transitive dependencies (simple)" (modulesWithHigherLevelTransitiveDependenciesSimple baseRdb) - -- , testCase "modules with higher level transitive dependencies (complex)" (modulesWithHigherLevelTransitiveDependenciesComplex baseRdb) + , testCase "lookup pact txs spec" (lookupPactTxsSpec baseRdb) + , testCase "failed txs should go into blocks" (failedTxsShouldGoIntoBlocks baseRdb) + , testCase "modules with higher level transitive dependencies (simple)" (modulesWithHigherLevelTransitiveDependenciesSimple baseRdb) + , testCase "modules with higher level transitive dependencies (complex)" (modulesWithHigherLevelTransitiveDependenciesComplex baseRdb) ] -- TODO PP: @@ -243,7 +247,7 @@ continueBlockSpec baseRdb = runResourceT $ do -- -- * test that the NewBlock timeout works properly and doesn't leave any extra state from a timed-out transaction newBlockTimeoutSpec :: RocksDb -> IO () newBlockTimeoutSpec baseRdb = runResourceT $ do - let pactServiceConfig cid = (testPactServiceConfig (genesisPayload cid)) + let pactServiceConfig = defaultPactServiceConfig { _pactTxTimeLimit = Just (Micros 35_000) -- this may need to be tweaked for CI. -- it should be long enough that `timeoutTx` times out @@ -384,16 +388,18 @@ lookupPactTxsSpec baseRdb = runResourceT $ do bip <- makeFilledBlock fixture ph return $ finalizeBlock fixture bip - let rks = List.sort $ List.map (Pact.unHash . _cmdHash) [cmd1, cmd2] + let rks = List.sort $ List.map _cmdHash [cmd1, cmd2] let lookupExpect :: Maybe Word -> IO () lookupExpect depth = do - txs <- PactService.execLookupPactTxs logger (fmap (ConfirmationDepth . fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) - assertEqual ("all txs should be available with depth=" ++ show depth) (HashSet.fromList rks) (HashMap.keysSet txs) + txs <- throwIfNoHistory =<< lookupPactTxs fixture chain0 (fmap (ConfirmationDepth . fromIntegral) depth) (Vector.fromList rks) + assertEqual ("all txs should be available with depth=" ++ show depth) + (HashSet.fromList (Pact.unHash <$> rks)) (HashMap.keysSet txs) let lookupDontExpect :: Maybe Word -> IO () lookupDontExpect depth = do - txs <- PactService.execLookupPactTxs (fmap (ConfirmationDepth. fromIntegral) depth) (Vector.fromList rks) (_fixturePactQueues fixture ^?! atChain chain0) - assertEqual ("no txs should be available with depth=" ++ show depth) HashSet.empty (HashMap.keysSet txs) + txs <- throwIfNoHistory =<< lookupPactTxs fixture chain0 (fmap (ConfirmationDepth. fromIntegral) depth) (Vector.fromList rks) + assertEqual ("no txs should be available with depth=" ++ show depth) + HashSet.empty (HashMap.keysSet txs) lookupExpect Nothing lookupExpect (Just 0) @@ -416,162 +422,162 @@ lookupPactTxsSpec baseRdb = runResourceT $ do lookupExpect (Just 2) lookupDontExpect (Just 3) --- failedTxsShouldGoIntoBlocks :: RocksDb -> IO () --- failedTxsShouldGoIntoBlocks baseRdb = runResourceT $ do --- fixture <- mkFixture baseRdb - --- liftIO $ do --- cmd1 <- buildCwCmd v (transferCmd 1.0) --- cmd2 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (module mod G (defcap G () true) (defun f () true)) (describe-module \"free.mod\")" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.1 --- , _cbGasLimit = GasLimit (Gas 1000) --- } - --- -- Depth 0 --- _ <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsert fixture chain0 CheckedInsert [cmd1, cmd2] --- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- let block = finalizeBlock bip --- assertEqual "block has 2 txs even though one of them failed" 2 (Vector.length $ _payloadWithOutputsTransactions block) --- return block - --- return () - --- modulesWithHigherLevelTransitiveDependenciesSimple :: RocksDb -> IO () --- modulesWithHigherLevelTransitiveDependenciesSimple baseRdb = runResourceT $ do --- fixture <- mkFixture baseRdb --- liftIO $ do --- cmd1 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.9 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- cmd2 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (module foo g (defcap g () true) (defun calls-foo () barbar.FOO_CONST))" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.8 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- cmd3 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (foo.calls-foo)))" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.7 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- cmd4 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.6 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- cmd5 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.5 --- , _cbGasLimit = GasLimit (Gas 1000) --- } - --- results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsert fixture chain0 CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] --- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- let block = finalizeBlock bip --- return block - --- results & --- P.alignExact ? onChain chain0 ? --- P.alignExact ? Vector.replicate 5 successfulTx - --- results & --- P.alignExact ? onChain chain0 ? --- P.alignExact ? Vector.fromList --- [ P.fun _crGas ? P.equals (Gas 173) --- , P.fun _crGas ? P.equals (Gas 305) --- , P.fun _crGas ? P.equals (Gas 348) --- , P.fun _crGas ? P.equals (Gas 389) --- , P.fun _crGas ? P.equals (Gas 81) --- ] - --- return () - --- modulesWithHigherLevelTransitiveDependenciesComplex :: RocksDb -> IO () --- modulesWithHigherLevelTransitiveDependenciesComplex baseRdb = runResourceT $ do --- fixture <- mkFixture baseRdb --- liftIO $ do --- cmd1 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.9 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- cmd2 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' $ T.unlines --- [ "(namespace 'free)" --- , "(module foo g" --- , " (defcap g () true)" --- , " (defun calls-foo (sender:string amount:integer)" --- , " (with-capability (FOO_MANAGED sender amount)" --- , " (with-capability (FOO_CAP)" --- , " barbar.FOO_CONST" --- , " )" --- , " )" --- , " )" --- , " (defcap FOO_CAP () true)" --- , "" --- , " (defun foo-mgr (a:integer b:integer) (+ a b))" --- , "" --- , " (defcap FOO_MANAGED (sender:string a:integer)" --- , " @managed a foo-mgr" --- , " true" --- , " )" --- , " )" --- ] --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.8 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- cmd3 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (install-capability (foo.FOO_MANAGED \"bob\" 100)) (foo.calls-foo \"bob\" 100)))" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.7 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- cmd4 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.6 --- , _cbGasLimit = GasLimit (Gas 1000) --- } --- cmd5 <- buildCwCmd v (defaultCmd chain0) --- { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" --- -- for ordering the transactions as they appear in the block --- , _cbGasPrice = GasPrice 0.5 --- , _cbGasLimit = GasLimit (Gas 1000) --- } - --- results <- advanceAllChains fixture $ onChain chain0 $ \ph pactQueue mempool -> do --- mempoolInsert fixture chain0 CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] --- bip <- throwIfNotPact5 =<< throwIfNoHistory =<< newBlock noMiner NewBlockFill (ParentHeader ph) pactQueue --- let block = finalizeBlock bip --- return block - --- results & --- P.alignExact ? onChain chain0 ? --- P.alignExact ? Vector.replicate 5 successfulTx - --- results & --- P.alignExact ? onChain chain0 ? --- P.alignExact ? Vector.fromList --- [ P.fun _crGas ? P.equals (Gas 173) --- , P.fun _crGas ? P.equals (Gas 715) --- , P.fun _crGas ? P.equals (Gas 648) --- , P.fun _crGas ? P.equals (Gas 618) --- , P.fun _crGas ? P.equals (Gas 82) --- ] - --- return () +failedTxsShouldGoIntoBlocks :: RocksDb -> IO () +failedTxsShouldGoIntoBlocks baseRdb = runResourceT $ do + fixture <- mkFixture baseRdb + + liftIO $ do + cmd1 <- buildCwCmd v (transferCmd 1.0) + cmd2 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (module mod G (defcap G () true) (defun f () true)) (describe-module \"free.mod\")" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.1 + , _cbGasLimit = GasLimit (Gas 1000) + } + + -- Depth 0 + _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd1, cmd2] + bip <- makeFilledBlock fixture ph + let block = finalizeBlock fixture bip + assertEqual "block has 2 txs even though one of them failed" 2 (Vector.length $ _payloadWithOutputsTransactions block) + return block + + return () + +modulesWithHigherLevelTransitiveDependenciesSimple :: RocksDb -> IO () +modulesWithHigherLevelTransitiveDependenciesSimple baseRdb = runResourceT $ do + fixture <- mkFixture baseRdb + liftIO $ do + cmd1 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.9 + , _cbGasLimit = GasLimit (Gas 1000) + } + cmd2 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (module foo g (defcap g () true) (defun calls-foo () barbar.FOO_CONST))" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.8 + , _cbGasLimit = GasLimit (Gas 1000) + } + cmd3 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (foo.calls-foo)))" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.7 + , _cbGasLimit = GasLimit (Gas 1000) + } + cmd4 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.6 + , _cbGasLimit = GasLimit (Gas 1000) + } + cmd5 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.5 + , _cbGasLimit = GasLimit (Gas 1000) + } + + results <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] + bip <- makeFilledBlock fixture ph + let block = finalizeBlock fixture bip + return block + + results & + P.alignExact ? onChain chain0 ? + P.alignExact ? Vector.replicate 5 successfulTx + + results & + P.alignExact ? onChain chain0 ? + P.alignExact ? Vector.fromList + [ P.fun _crGas ? P.equals (Gas 173) + , P.fun _crGas ? P.equals (Gas 305) + , P.fun _crGas ? P.equals (Gas 348) + , P.fun _crGas ? P.equals (Gas 389) + , P.fun _crGas ? P.equals (Gas 81) + ] + + return () + +modulesWithHigherLevelTransitiveDependenciesComplex :: RocksDb -> IO () +modulesWithHigherLevelTransitiveDependenciesComplex baseRdb = runResourceT $ do + fixture <- mkFixture baseRdb + liftIO $ do + cmd1 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.9 + , _cbGasLimit = GasLimit (Gas 1000) + } + cmd2 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' $ T.unlines + [ "(namespace 'free)" + , "(module foo g" + , " (defcap g () true)" + , " (defun calls-foo (sender:string amount:integer)" + , " (with-capability (FOO_MANAGED sender amount)" + , " (with-capability (FOO_CAP)" + , " barbar.FOO_CONST" + , " )" + , " )" + , " )" + , " (defcap FOO_CAP () true)" + , "" + , " (defun foo-mgr (a:integer b:integer) (+ a b))" + , "" + , " (defcap FOO_MANAGED (sender:string a:integer)" + , " @managed a foo-mgr" + , " true" + , " )" + , " )" + ] + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.8 + , _cbGasLimit = GasLimit (Gas 1000) + } + cmd3 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (install-capability (foo.FOO_MANAGED \"bob\" 100)) (foo.calls-foo \"bob\" 100)))" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.7 + , _cbGasLimit = GasLimit (Gas 1000) + } + cmd4 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.6 + , _cbGasLimit = GasLimit (Gas 1000) + } + cmd5 <- buildCwCmd v (defaultCmd chain0) + { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" + -- for ordering the transactions as they appear in the block + , _cbGasPrice = GasPrice 0.5 + , _cbGasLimit = GasLimit (Gas 1000) + } + + results <- advanceAllChains fixture $ onChain chain0 $ \ph -> do + mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd1, cmd2, cmd3, cmd4, cmd5] + bip <- makeFilledBlock fixture ph + let block = finalizeBlock fixture bip + return block + + results & + P.alignExact ? onChain chain0 ? + P.alignExact ? Vector.replicate 5 successfulTx + + results & + P.alignExact ? onChain chain0 ? + P.alignExact ? Vector.fromList + [ P.fun _crGas ? P.equals (Gas 173) + , P.fun _crGas ? P.equals (Gas 715) + , P.fun _crGas ? P.equals (Gas 648) + , P.fun _crGas ? P.equals (Gas 618) + , P.fun _crGas ? P.equals (Gas 82) + ] + + return () -- {- -- tests = do @@ -590,10 +596,6 @@ lookupPactTxsSpec baseRdb = runResourceT $ do -- -- * test that the mempool only gives valid transactions -- -- * test that blocks fit the block gas limit always -- -- * test that blocks can include txs even if their gas limits together exceed that block gas limit --- -- pact5 upgrade tests: --- -- * test that a defpact can straddle the pact5 upgrade --- -- * test that pact5 can load pact4 modules --- -- * test that rewinding past the pact5 boundary is possible -- -} chain0 :: ChainId @@ -630,9 +632,9 @@ continueBlock Fixture{..} bip = do makeFilledBlock :: Fixture -> Parent BlockHeader -> IO BlockInProgress makeFilledBlock fixture ph = continueBlock fixture =<< makeEmptyBlock fixture ph -lookupPactTxs :: Fixture -> ChainId -> Maybe ConfirmationDepth -> Vector Pact.Hash -> IO (Historical (HM.HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash))) +lookupPactTxs :: Fixture -> ChainId -> Maybe ConfirmationDepth -> Vector Pact.Hash -> IO (Historical (HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash))) lookupPactTxs Fixture{..} chain depth hashes = - PactService.execLookupPactTxs _fixtureLogger + PactService.execLookupPactTxs _fixtureLogger (_fixturePacts ^?! atChain chain) depth (Pact.unHash <$> hashes) mempoolInsert :: Foldable f => Fixture -> ChainId -> Mempool.InsertType -> f Pact.Transaction -> IO () mempoolInsert Fixture{..} cid insertType txs = diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index 7f592e474b..938f391ba0 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -1186,7 +1186,7 @@ instance HasFixture a => HasFixture (IO a) where mkFixture :: ChainwebVersion -> RocksDb -> ResourceT IO Fixture mkFixture v baseRdb = do - fx <- CutFixture.mkFixture v testPactServiceConfig baseRdb + fx <- CutFixture.mkFixture v defaultPactServiceConfig baseRdb logger <- liftIO getTestLogger let mkSomePactServerData cid = PactServerData diff --git a/test/unit/Chainweb/Test/Pact/SPVTest.hs b/test/unit/Chainweb/Test/Pact/SPVTest.hs index 9281d0dd41..a36879befa 100644 --- a/test/unit/Chainweb/Test/Pact/SPVTest.hs +++ b/test/unit/Chainweb/Test/Pact/SPVTest.hs @@ -76,7 +76,7 @@ import Chainweb.Payload (PayloadWithOutputs_ (_payloadWithOutputsPayloadHash), T import Chainweb.Payload.PayloadStore import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb, _bdbWebBlockHeaderDb), addTestBlockDb, getCutTestBlockDb, getParentTestBlockDb, mkTestBlockDb, setCutTestBlockDb) -import Chainweb.Test.Pact4.Utils (stdoutDummyLogger, testPactServiceConfig, withBlockHeaderDb) +import Chainweb.Test.Pact4.Utils (stdoutDummyLogger, defaultPactServiceConfig, withBlockHeaderDb) import Chainweb.Test.Pact.CmdBuilder import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions @@ -201,7 +201,7 @@ roundtrip' roundtrip' v sid0 tid0 burn create step = withTestBlockDb v $ \bdb -> do tg <- newMVar mempty let logger = hunitDummyLogger step - withWebPactExecutionService logger v testPactServiceConfig bdb (chainToMPA' tg) $ \(pact,_) -> do + withWebPactExecutionService logger v defaultPactServiceConfig bdb (chainToMPA' tg) $ \(pact,_) -> do sid <- mkChainId v maxBound sid0 tid <- mkChainId v maxBound tid0 diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index 44eefbd19e..8952741cc5 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -111,7 +111,7 @@ readFromAfterGenesis ver rdb act = runResourceT $ do -- fake ro-sql pool, assuming we're using this single-threaded roSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sql) (\_ -> return ()) 10 10) logger <- liftIO $ testLogger - serviceEnv <- withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (testPactServiceConfig PIN0.payloadBlock) + serviceEnv <- withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (defaultPactServiceConfig PIN0.payloadBlock) liftIO $ do initialPayloadState logger serviceEnv fakeParentCreationTime <- mkFakeParentCreationTime From 65eba60656663789613a0431c002d6f56e1e85fe Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 29 Apr 2025 18:55:16 -0700 Subject: [PATCH 117/378] include full evm execution header in evm-genesis output --- cwtools/evm-genesis/Main.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 0ef84fb05c..526d8f87f3 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -54,6 +54,7 @@ main = do [ "chainId" .= cid , "blockPayloadHash" .= E._hdrPayloadHash hdr , "blockPayload" .= encodeB64UrlNoPaddingText (E.putRlpByteString hdr) + , "evmPayloadHeader" .= hdr ] | (cid, hdr) <- hdrs ] From 86d4387279cb4969a9d10980efcfc1ff47b42344 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 29 Apr 2025 18:56:12 -0700 Subject: [PATCH 118/378] update EVM header for Pectra hard fork --- src/Chainweb/MerkleUniverse.hs | 9 +++ src/Chainweb/PayloadProvider/EVM.hs | 17 ++-- src/Chainweb/PayloadProvider/EVM/EngineAPI.hs | 81 ++++++++++++++++++- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 44 +++++----- src/Chainweb/PayloadProvider/EVM/Header.hs | 68 +++++++++++++++- src/Chainweb/PayloadProvider/EVM/Utils.hs | 21 +++++ src/Chainweb/Version/EvmDevelopment.hs | 40 ++++----- 7 files changed, 225 insertions(+), 55 deletions(-) diff --git a/src/Chainweb/MerkleUniverse.hs b/src/Chainweb/MerkleUniverse.hs index d37448e280..b08c87d729 100644 --- a/src/Chainweb/MerkleUniverse.hs +++ b/src/Chainweb/MerkleUniverse.hs @@ -150,6 +150,7 @@ data ChainwebHashTag | EthExcessBlobGasTag | EthParentBeaconBlockRootTag | EthReceiptTag + | EthRequestsHashTag deriving (Show, Eq, Bounded, Enum) instance MerkleUniverse ChainwebHashTag where @@ -205,6 +206,7 @@ instance MerkleUniverse ChainwebHashTag where type MerkleTagVal ChainwebHashTag 'EthExcessBlobGasTag = 0x0052 type MerkleTagVal ChainwebHashTag 'EthParentBeaconBlockRootTag = 0x0053 type MerkleTagVal ChainwebHashTag 'EthReceiptTag = 0x0054 + type MerkleTagVal ChainwebHashTag 'EthRequestsHashTag = 0x0055 instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag Void where type Tag Void = 'VoidTag @@ -398,6 +400,9 @@ data instance Sing :: ChainwebHashTag -> Type where SEthReceiptTag :: SNat (MerkleTagVal ChainwebHashTag EthReceiptTag) -> Sing 'EthReceiptTag + SEthRequestsHashTag + :: SNat (MerkleTagVal ChainwebHashTag EthRequestsHashTag) + -> Sing 'EthRequestsHashTag deriving instance Show (Sing (a :: ChainwebHashTag)) @@ -457,6 +462,7 @@ sTagVal (SEthBlobGasUsedTag n) = n sTagVal (SEthExcessBlobGasTag n) = n sTagVal (SEthParentBeaconBlockRootTag n) = n sTagVal (SEthReceiptTag n) = n +sTagVal (SEthRequestsHashTag n) = n pattern STagVal :: forall (a :: ChainwebHashTag) @@ -511,6 +517,7 @@ instance SingI 'EthBlobGasUsedTag where sing = SEthBlobGasUsedTag SNat instance SingI 'EthExcessBlobGasTag where sing = SEthExcessBlobGasTag SNat instance SingI 'EthParentBeaconBlockRootTag where sing = SEthParentBeaconBlockRootTag SNat instance SingI 'EthReceiptTag where sing = SEthReceiptTag SNat +instance SingI 'EthRequestsHashTag where sing = SEthRequestsHashTag SNat instance SingKind ChainwebHashTag where type Demote ChainwebHashTag = ChainwebHashTag @@ -560,6 +567,7 @@ instance SingKind ChainwebHashTag where fromSing (SEthExcessBlobGasTag SNat) = EthExcessBlobGasTag fromSing (SEthParentBeaconBlockRootTag SNat) = EthParentBeaconBlockRootTag fromSing (SEthReceiptTag SNat) = EthReceiptTag + fromSing (SEthRequestsHashTag SNat) = EthRequestsHashTag toSing VoidTag = SomeSing (SVoidTag SNat) toSing MerkleRootTag = SomeSing (SMerkleRootTag SNat) @@ -607,6 +615,7 @@ instance SingKind ChainwebHashTag where toSing EthExcessBlobGasTag = SomeSing (SEthExcessBlobGasTag SNat) toSing EthParentBeaconBlockRootTag = SomeSing (SEthParentBeaconBlockRootTag SNat) toSing EthReceiptTag = SomeSing (SEthReceiptTag SNat) + toSing EthRequestsHashTag = SomeSing (SEthRequestsHashTag SNat) tagList :: [ChainwebHashTag] tagList = [minBound .. maxBound] diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 170ae89713..7e6ca7a74f 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -877,7 +877,7 @@ awaitNewPayload p = do go pid = do lf Debug $ "got payload ID " <> encodeToText [pid] - resp <- RPC.callMethodHttp @Engine_GetPayloadV3 ctx [pid] + resp <- RPC.callMethodHttp @Engine_GetPayloadV4 ctx [pid] lf Debug $ "got execution payload for payload ID " <> toText pid -- FIXME if this fails with unknown payload, we need to issues a new @@ -885,11 +885,12 @@ awaitNewPayload p = do -- stuck. -- Response data - let v3 = _getPayloadV3ResponseExecutionPayload resp + let v3 = _getPayloadV4ResponseExecutionPayload resp v2 = _executionPayloadV2 v3 v1 = _executionPayloadV1 v2 h = EVM.numberToHeight $ _executionPayloadV1BlockNumber v1 newEvmBlockHash = _executionPayloadV1BlockHash v1 + reqs = _getjPayloadV4ResponseExecutionRequests resp -- check that this is a new payload atomically (tryReadTMVar (_evmPayloadVar p)) >>= \case @@ -907,7 +908,7 @@ awaitNewPayload p = do let n = maybe 0 (succ . _newPayloadNumber . ssnd) x -- check that Blobs bundle is empty - unless (null $ _blobsBundleV1Blobs $ _getPayloadV3ResponseBlobsBundle resp) $ + unless (null $ _blobsBundleV1Blobs $ _getPayloadV4ResponseBlobsBundle resp) $ throwM BlobsNotSupported -- Check that the block height matches the expected height @@ -917,13 +918,13 @@ awaitNewPayload p = do -- Check that the fees of the execution paylod match the block -- value of the response. -- FIXME FIXME FIXME - -- unless (EVM._blockValueStu (_getPayloadV3ResponseBlockValue resp) == fees v1) $ + -- unless (EVM._blockValueStu (_getPayloadV4ResponseBlockValue resp) == fees v1) $ -- throwM InconsistentNewPayloadFees - -- { _inconsistentPayloadBlockValue = _getPayloadV3ResponseBlockValue resp + -- { _inconsistentPayloadBlockValue = _getPayloadV4ResponseBlockValue resp -- , _inconsistentPayloadFees = fees v1 -- } - let pld = executionPayloadV3ToHeader (_syncStateBlockHash sstate) v3 + let pld = executionPayloadV3ToHeader (_syncStateBlockHash sstate) v3 reqs -- Check that the computed block hash matches the hash from the -- response @@ -958,8 +959,9 @@ awaitNewPayload p = do executionPayloadV3ToHeader :: Chainweb.BlockHash -> ExecutionPayloadV3 + -> [Utils.ExecutionRequest] -> Payload -executionPayloadV3ToHeader phdr v3 = hdr +executionPayloadV3ToHeader phdr v3 reqs = hdr { EVM._hdrHash = EVM.computeBlockHash hdr , EVM._hdrPayloadHash = EVM.computeBlockPayloadHash hdr } @@ -987,6 +989,7 @@ executionPayloadV3ToHeader phdr v3 = hdr , _hdrBlobGasUsed = _executionPayloadV3BlobGasUsed v3 , _hdrExcessBlobGas = _executionPayloadV3ExcessBlobGas v3 , _hdrParentBeaconBlockRoot = EVM.chainwebBlockHashToBeaconBlockRoot phdr + , _hdrRequestsHash = EVM.requestsHash reqs , _hdrHash = error "Chainweb.PayloadProvider.EVM.executionPayloadV3ToHeader: _hdrHash" , _hdrPayloadHash = error "Chainweb.PayloadProvider.executionPayloadV3ToHeader: _hdrPayloadHash" } diff --git a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs index 6a3fb01f46..7b5a2cad09 100644 --- a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs +++ b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs @@ -103,6 +103,7 @@ module Chainweb.PayloadProvider.EVM.EngineAPI , BlockValue(..) , GetPayloadV2Response(..) , GetPayloadV3Response(..) +, GetPayloadV4Response(..) -- * Authentication and Client Context , JwtSecret(..) @@ -114,6 +115,7 @@ module Chainweb.PayloadProvider.EVM.EngineAPI -- * Engine API Methods , type Engine_GetPayloadV2 , type Engine_GetPayloadV3 +, type Engine_GetPayloadV4 , type Engine_ForkchoiceUpdatedV3 ) where @@ -596,7 +598,7 @@ instance FromJSON ExecutionPayloadV2 where -- -------------------------------------------------------------------------- -- -- Execution Payload V3 --- | Execution Payload V3a +-- | Execution Payload V3 -- -- This structure has the syntax of ExecutionPayloadV2 and appends the new -- fields: blobGasUsed and excessBlobGas. @@ -1067,7 +1069,7 @@ instance JsonRpcMethod "engine_getPayloadV3" where , ApplicationError UnsupportedFork ] --- | Engine Get Payload V2 Response +-- | Engine Get Payload V3 Response -- -- error: code and message set in case an exception happens while getting the -- payload. @@ -1119,6 +1121,81 @@ instance FromJSON GetPayloadV3Response where <*> o .: "shouldOverrideBuilder" {-# INLINE parseJSON #-} +-- -------------------------------------------------------------------------- -- +-- Engine Get Payload V4 (Prague / Pectra) + +type Engine_GetPayloadV4 = "engine_getPayloadV4" + +-- | Engine Get Payload V4 +-- +-- The response of this method is extended with the executionRequests field. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#engine_getpayloadv4 +-- +instance JsonRpcMethod "engine_getPayloadV4" where + type MethodRequest "engine_getPayloadV4" = [PayloadId] + type MethodResponse "engine_getPayloadV4" = GetPayloadV4Response + type ServerErrors "engine_getPayloadV4" = EngineServerErrors + type ApplicationErrors "engine_getPayloadV4" = EngineErrors + responseTimeoutMs = Just 1000 + methodErrors = + [ ApplicationError UnknownPayload + , ApplicationError UnsupportedFork + ] + +-- | Engine Get Payload V4 Response +-- +-- New fields: +-- +-- * executionRequests +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#response-1 +-- +data GetPayloadV4Response = GetPayloadV4Response + { _getPayloadV4ResponseExecutionPayload :: !ExecutionPayloadV3 + -- ^ executionPayload: ExecutionPayloadV2 + , _getPayloadV4ResponseBlockValue :: !BlockValue + -- ^ blockValue : QUANTITY, 256 Bits - The expected value to be received + -- by the feeRecipient in wei + , _getPayloadV4ResponseBlobsBundle :: !BlobsBundleV1 + -- ^ blobsBundle: BlobsBundleV1 - Bundle with data corresponding to blob + -- transactions included into executionPayload + , _getPayloadV4ResponseShouldOverrideBuilder :: !Bool + -- ^ shouldOverrideBuilder : BOOLEAN - Suggestion from the execution layer + -- to use this executionPayload instead of an externally provided one + , _getjPayloadV4ResponseExecutionRequests :: ![ExecutionRequest] + -- ^ executionRequests: Array of DATA - Execution layer triggered requests + -- obtained from the executionPayload transaction execution. + } + deriving (Show, Eq, Generic) + +getPayloadV4ResponseProperties + :: KeyValue e kv + => GetPayloadV4Response + -> [kv] +getPayloadV4ResponseProperties o = + [ "executionPayload" .= _getPayloadV4ResponseExecutionPayload o + , "blockValue" .= _getPayloadV4ResponseBlockValue o + , "blobsBundle" .= _getPayloadV4ResponseBlobsBundle o + , "shouldOverrideBuilder" .= _getPayloadV4ResponseShouldOverrideBuilder o + , "executionRequests" .= _getjPayloadV4ResponseExecutionRequests o + ] + +instance ToJSON GetPayloadV4Response where + toEncoding = pairs . mconcat . getPayloadV4ResponseProperties + toJSON = object . getPayloadV4ResponseProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON GetPayloadV4Response where + parseJSON = withObject "GetPayloadV4Response" $ \o -> GetPayloadV4Response + <$> o .: "executionPayload" + <*> o .: "blockValue" + <*> o .: "blobsBundle" + <*> o .: "shouldOverrideBuilder" + <*> o .: "executionRequests" + {-# INLINE parseJSON #-} + -- -------------------------------------------------------------------------- -- -- Engine New Payload V1 (Paris) diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 13b6169d03..5aa6d085a8 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -65,7 +65,7 @@ import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) -- 1. Query the EVM genesis header and compute block payload hash and header: -- -- @ --- cabal run cwtools:exe:evm-genesis +-- cabal run cwtools:exe:evm-genesis -- @ -- genesisBlocks @@ -76,48 +76,46 @@ genesisBlocks -> Header genesisBlocks v c = go (_chainwebVersion v) (_chainId c) where - -- Ethereum NetworkID 1789 go EvmDevelopment (ChainId 20) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGnV3kPaffv02pFz2XaK1abwEUqbp-L5K03hpucRJqlToFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - -- Ethereum NetworkID 1790 + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGnV3kPaffv02pFz2XaK1abwEUqbp-L5K03hpucRJqlToFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 21) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJSFmb3gp1R0kVepD6Cb9wKqd4mvZhjGCfLWOJYhfOtXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJSFmb3gp1R0kVepD6Cb9wKqd4mvZhjGCfLWOJYhfOtXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 22) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIHnBqDOlLrBUgs1mmDAGcyoZGAiI4MDWIm5TSP2DfXHoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIHnBqDOlLrBUgs1mmDAGcyoZGAiI4MDWIm5TSP2DfXHoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 23) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOG8PeZNPmM3lrR8_XYZiV77h9JISltItpJrMg3uWzISoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOG8PeZNPmM3lrR8_XYZiV77h9JISltItpJrMg3uWzISoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 24) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoL_zw0Kq1MfRVs38rgQLmxq1zFk8ac976rfmBmNiPvc0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoL_zw0Kq1MfRVs38rgQLmxq1zFk8ac976rfmBmNiPvc0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 25) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAR82Mmf_nzgqOuk-JuK2c6y4cUsnjj_mthufymqwKl4oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAR82Mmf_nzgqOuk-JuK2c6y4cUsnjj_mthufymqwKl4oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 26) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoLIoqmCAur1Y7jpeSGyRpx71kbUqXT-R5dTjIswNvgyXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoLIoqmCAur1Y7jpeSGyRpx71kbUqXT-R5dTjIswNvgyXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 27) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoEa3cdjo4w54WX1slUf3N6RgIFddjDWTfYNUg4mMF_ExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoEa3cdjo4w54WX1slUf3N6RgIFddjDWTfYNUg4mMF_ExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 28) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJGErv8Pw9R_vy2jSuU8h22F9iPtZMzBDYhF33EA0PGhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJGErv8Pw9R_vy2jSuU8h22F9iPtZMzBDYhF33EA0PGhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 29) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMh5sX_kYQkGl1nZ-RHbBlbBwbp-VcznAOB0nAVnXOzroFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMh5sX_kYQkGl1nZ-RHbBlbBwbp-VcznAOB0nAVnXOzroFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 30) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGcOIoUzg5FTvy0MC0EZJsxo030YmtHVbISpQi0iaD3poFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGcOIoUzg5FTvy0MC0EZJsxo030YmtHVbISpQi0iaD3poFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 31) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNkhLvaaRHuHwXutFpApXpQMuKIUXeN0riiBvHCjpezvoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNkhLvaaRHuHwXutFpApXpQMuKIUXeN0riiBvHCjpezvoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 32) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAZn-r-AVUqdftFL0aAYgnXg3Ihh3TpvNHF1Lk2f5dO7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAZn-r-AVUqdftFL0aAYgnXg3Ihh3TpvNHF1Lk2f5dO7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 33) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoI5MZQFQY_vcdKf1l4cilyj0fjzDsAGNw5ZFew7mLNB7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoI5MZQFQY_vcdKf1l4cilyj0fjzDsAGNw5ZFew7mLNB7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 34) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFIGyZq62efyMMlN-YowvgItKWe3F4BY3CG2g_9q7jfRoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFIGyZq62efyMMlN-YowvgItKWe3F4BY3CG2g_9q7jfRoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 35) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBV-hCyAvRGG3t7rNKZd1fKF1ZuQjEBKezJpX2508UDioFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBV-hCyAvRGG3t7rNKZd1fKF1ZuQjEBKezJpX2508UDioFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 36) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFH9aUrJ9ByuptHRBLi9Qz5ZfxB9PHQo49SlwaBIgb6ZoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFH9aUrJ9ByuptHRBLi9Qz5ZfxB9PHQo49SlwaBIgb6ZoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 37) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNMYg9Jdt7H6aQ8qvbWwbcqKrAYx8dFuK4ZQjkUPo-jNoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNMYg9Jdt7H6aQ8qvbWwbcqKrAYx8dFuK4ZQjkUPo-jNoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 38) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoArbi8-FGeh8uGxs7MxDXJUwVMznEnD_W-imVRojX9PnoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoArbi8-FGeh8uGxs7MxDXJUwVMznEnD_W-imVRojX9PnoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go EvmDevelopment (ChainId 39) = f - "-QI-oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoPitbBNoKjvjSCImGCqCmwST-kMGThKeiK5WSDMw-CfqoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoPitbBNoKjvjSCImGCqCmwST-kMGThKeiK5WSDMw-CfqoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" go _ _ = error "requested genesis block for unsupported chain" f t = case decodeB64UrlNoPaddingText t >>= decodeRlpM of diff --git a/src/Chainweb/PayloadProvider/EVM/Header.hs b/src/Chainweb/PayloadProvider/EVM/Header.hs index 991e3b11ba..0549c958a4 100644 --- a/src/Chainweb/PayloadProvider/EVM/Header.hs +++ b/src/Chainweb/PayloadProvider/EVM/Header.hs @@ -57,6 +57,8 @@ module Chainweb.PayloadProvider.EVM.Header , beaconBlockRootToChainwebBlockHash , computeBlockHash , computeBlockPayloadHash +, RequestsHash(..) +, requestsHash -- * Misc @@ -85,6 +87,10 @@ module Chainweb.PayloadProvider.EVM.Header , hdrBaseFeePerGas , hdrWithdrawalsRoot , hdrHeight +, hdrBlobGasUsed +, hdrExcessBlobGas +, hdrParentBeaconBlockRoot +, hdrRequestsHash , hdrHash , hdrPayloadHash ) where @@ -96,7 +102,7 @@ import Chainweb.BlockPayloadHash import Chainweb.Crypto.MerkleLog import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse -import Chainweb.PayloadProvider.EVM.Receipt (Receipt) +import Chainweb.PayloadProvider.EVM.Receipt ({- IsMerkleLogEntry ReceiptsRoot -}) import Chainweb.PayloadProvider.EVM.Utils import Chainweb.Storage.Table import Chainweb.Time @@ -112,16 +118,16 @@ import Data.ByteString.Short qualified as SBS import Data.Function import Data.Hashable (Hashable(..)) import Data.MerkleLog qualified as V2 -import Data.MerkleLog.Common import Data.Ratio ((%)) -import Data.Text qualified as T import Data.Void +import Data.Hash.SHA2 import Ethereum.Misc import Ethereum.RLP import Ethereum.Utils import Foreign.Storable import GHC.Generics (Generic) import GHC.TypeNats +import Data.Coerce -- -------------------------------------------------------------------------- -- Utils @@ -237,6 +243,27 @@ newtype ExcessBlobGas = ExcessBlobGas Natural deriving ToJSON via (HexQuantity Natural) deriving FromJSON via (HexQuantity Natural) +-- -- -------------------------------------------------------------------------- +-- Header fields for Pectra (Prague + Electra) Hardfork + +-- | Requests Hash +-- +-- Since Pectra Hardfork (cf. EIP-7685) +-- +newtype RequestsHash = RequestsHash (BytesN 32) + deriving (Show, Eq) + deriving newtype (RLP, Bytes, Storable, Hashable) + deriving ToJSON via (HexBytes (BytesN 32)) + deriving FromJSON via (HexBytes (BytesN 32)) + +requestsHash :: [ExecutionRequest] -> RequestsHash +requestsHash = RequestsHash + . unsafeBytesN + . coerce + . hashShortByteString_ @Sha2_256 + . mconcat + . fmap (coerce . hashShortByteString_ @Sha2_256 . coerce) + -- -------------------------------------------------------------------------- -- -- Header @@ -325,6 +352,13 @@ data Header = Header , _hdrExcessBlobGas :: !ExcessBlobGas , _hdrParentBeaconBlockRoot :: !ParentBeaconBlockRoot + -- The following field was introduced in the Pectra hard fork. It is ignored + -- in legacy headers. + , _hdrRequestsHash :: !RequestsHash + -- ^ The requests hash is the SHA256 hash of the SHA256 hashes of all + -- non-empty hashes in the block ordered by request type. For blocks + -- with no requests this is the empty hash. (cf. EIP-7685) + -- synthetic fields , _hdrHash :: {- Lazy -} BlockHash -- ^ Ethereum Execution Header Block Hash @@ -458,6 +492,7 @@ headerProperties o = , "blobGasUsed" .= _hdrBlobGasUsed o , "excessBlobGas" .= _hdrExcessBlobGas o , "parentBeaconBlockRoot" .= _hdrParentBeaconBlockRoot o + , "requestsHash" .= _hdrRequestsHash o , "hash" .= _hdrHash o ] {-# INLINE headerProperties #-} @@ -493,6 +528,7 @@ instance FromJSON Header where <*> o .: "blobGasUsed" <*> o .: "excessBlobGas" <*> o .: "parentBeaconBlockRoot" + <*> o .: "requestsHash" <*> pure (error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrHash JSON during deserialization") <*> pure (error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrPayloadHash JSON during deserialization") return hdr @@ -536,6 +572,9 @@ instance RLP Header where , putRlp $ _hdrBlobGasUsed hdr , putRlp $ _hdrExcessBlobGas hdr , putRlp $ _hdrParentBeaconBlockRoot hdr + + -- Pectra Hardfork + , putRlp $ _hdrRequestsHash hdr ] getRlp = label "Header" $ do @@ -562,6 +601,10 @@ instance RLP Header where <*> getRlp -- blob gas used <*> getRlp -- excess blob gas <*> getRlp -- parent beacon block root + + -- Pectra Hardfork + <*> getRlp -- requests hash + <*> pure (error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrHash during RLP deserialization") <*> pure (error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrPayloadHash during RLP deserialization") return hdr @@ -632,6 +675,9 @@ deriving via (RlpMerkleLogEntry 'EthExcessBlobGasTag ExcessBlobGas) deriving via (RlpMerkleLogEntry 'EthParentBeaconBlockRootTag ParentBeaconBlockRoot) instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ParentBeaconBlockRoot +deriving via (RlpMerkleLogEntry 'EthRequestsHashTag RequestsHash) + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag RequestsHash + -- -------------------------------------------------------------------------- -- -- MerkleLog Instance @@ -659,6 +705,7 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag Header where , BlobGasUsed , ExcessBlobGas , ParentBeaconBlockRoot + , RequestsHash ] type MerkleLogBody Header = Void @@ -685,6 +732,7 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag Header where :+: _hdrBlobGasUsed h :+: _hdrExcessBlobGas h :+: _hdrParentBeaconBlockRoot h + :+: _hdrRequestsHash h :+: emptyBody fromLog l = hdr @@ -713,6 +761,7 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag Header where , _hdrBlobGasUsed = hBlobGasUsed , _hdrExcessBlobGas = hExcessBlobGas , _hdrParentBeaconBlockRoot = hParentBeaconBlockRoot + , _hdrRequestsHash = hRequestsHash , _hdrHash = error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrHash during MerkleLog deserialization" , _hdrPayloadHash = error "Chainweb.PayloadProvider.EVM.Header: attempt to force _hdrPayloadHash during MerkleLog deserialization" } @@ -736,6 +785,7 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag Header where :+: hBlobGasUsed :+: hExcessBlobGas :+: hParentBeaconBlockRoot + :+: hRequestsHash :+: _ ) = _merkleLogEntries l @@ -796,6 +846,18 @@ hdrWithdrawalsRoot = to _hdrWithdrawalsRoot hdrHeight :: Getter Header Chainweb.BlockHeight hdrHeight = to _hdrHeight +hdrBlobGasUsed :: Getter Header BlobGasUsed +hdrBlobGasUsed = to _hdrBlobGasUsed + +hdrExcessBlobGas :: Getter Header ExcessBlobGas +hdrExcessBlobGas = to _hdrExcessBlobGas + +hdrParentBeaconBlockRoot :: Getter Header ParentBeaconBlockRoot +hdrParentBeaconBlockRoot = to _hdrParentBeaconBlockRoot + +hdrRequestsHash :: Getter Header RequestsHash +hdrRequestsHash = to _hdrRequestsHash + hdrHash :: Getter Header BlockHash hdrHash = to _hdrHash diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs index 11b0a0c0ec..f8eba6cedb 100644 --- a/src/Chainweb/PayloadProvider/EVM/Utils.hs +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -35,6 +35,7 @@ module Chainweb.PayloadProvider.EVM.Utils , toAddress32 , _blockValueStu , DefaultBlockParameter(..) +, ExecutionRequest(..) -- * Misc Utils , fromHexQuanity @@ -73,6 +74,7 @@ import Text.Printf import Chainweb.MerkleUniverse import Chainweb.Crypto.MerkleLog import Data.MerkleLog (MerkleNodeType(..)) +import qualified Data.ByteString.Short as SB -- -------------------------------------------------------------------------- -- -- Utils (should be moved to the ethereum package) @@ -253,6 +255,25 @@ newtype BlockValue = BlockValue { _blockValue :: Wei } _blockValueStu :: BlockValue -> Stu _blockValueStu (BlockValue (Wei v)) = Stu (int v) +-- ---------------------------------------------------------------------------- +-- Execution Request + +-- | Execution Requests are described in EIP-7685 and introduced in the Pectra +-- hard fork. +-- +-- They let the execution layer request certain actions from the consensus that +-- are usually related to managing stake. +-- +-- In chainweb consensus we ignore these requests at the moment. However, it is +-- possible that in the future we will use this mechanism for custom requests +-- that are specific to chainweb consensus. +-- +newtype ExecutionRequest = ExecutionRequest BS.ShortByteString + deriving (Show, Eq, Ord) + deriving newtype (RLP, E.Bytes, Hashable) + deriving ToJSON via (HexBytes (SB.ShortByteString)) + deriving FromJSON via (HexBytes (SB.ShortByteString)) + -- -------------------------------------------------------------------------- -- -- Default Block Parameter diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 281424ed04..efea9c61c4 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -95,26 +95,26 @@ evmDevnet = ChainwebVersion , (unsafeChainId 18, unsafeFromText "gMs-6QtsYgghAZqTNZ7DBlyOHR2XryNIE4n9rpn-h_I") , (unsafeChainId 19, unsafeFromText "Evv6R7db1V3t2mrDTosjpL4fjrN9Cn2vdr8tE34hPUY") -- EVM Payload Provider - , (unsafeChainId 20, unsafeFromText "fSrq8wiBgto56jF8jWWmQ4BCMoR2XzTsFh8RYuFyvJM") - , (unsafeChainId 21, unsafeFromText "_Wv_1kmEUCWjtDvnhyfcWoZMsml2ezuR1YwwUQOZqH0") - , (unsafeChainId 22, unsafeFromText "pKDYEr-_RxOla6FgAosDSGjNXIDnksAiNxKOAiRbDOE") - , (unsafeChainId 23, unsafeFromText "p0ivYZAy5SAVh5QH0loiORXgcZqwyOv1yvE5Wb0Eh1w") - , (unsafeChainId 24, unsafeFromText "xqi3q2Ycy_7QfU80739ok6C2jSHZNBqshVh45mLVcdw") - , (unsafeChainId 25, unsafeFromText "JIZ16mtTtCjH47cX6tiA_sS0DdvHN3N0s8d9-LIJbNk") - , (unsafeChainId 26, unsafeFromText "8Pm1VpSzWLpcQ7T4Hf4TWJ2oeztescGg_U95FdZq6Vg") - , (unsafeChainId 27, unsafeFromText "Ac24HD57R_4W8qrAAqe3tdwoLlaR-3L0ES2p1_2JFLE") - , (unsafeChainId 28, unsafeFromText "-lLFm9Lu6eDgyFaNI9msdaHazUqCWJP44PIZTmfGUf0") - , (unsafeChainId 29, unsafeFromText "uguTxEXodT8r8ICE6gZmi7l9aD_9nB-cBal_-JBpZ_M") - , (unsafeChainId 30, unsafeFromText "GlmowvGyANjcmlWsD0LJHki8E71H-4Yo8YGBMPYWhfw") - , (unsafeChainId 31, unsafeFromText "RpbZ_CmoCL7ZEdzRWjAoLCR2kbLUMMzxuwu-UndxR7o") - , (unsafeChainId 32, unsafeFromText "n0FR2nBGo4HbyxLoIClmwzZT0RznxxbJcaCkkTd3X7Y") - , (unsafeChainId 33, unsafeFromText "Of2R_J3W8i6-H4JgjmVmsIp3bH0bm1ZXyg_5FzYV6c8") - , (unsafeChainId 34, unsafeFromText "eeun0LRSyrinygZmVeUfqBzKJ4K2LoCRPQ7255cE7mg") - , (unsafeChainId 35, unsafeFromText "FM_h3zF-BIdMhsKoDsFU_xJOnF2WXp4ue6tMCG25oF8") - , (unsafeChainId 36, unsafeFromText "gveQfdrWNSERjopJqzEPVygNfOddVTp9NRDHcS3KXa0") - , (unsafeChainId 37, unsafeFromText "3_8fKJHVlJTKrmlYeLe5BE7gVpj4wjPg_jBxHsAkV24") - , (unsafeChainId 38, unsafeFromText "oEbS9GxnyOsx509ujJqfp1iYtQzDmMspqaXJUrFxaqY") - , (unsafeChainId 39, unsafeFromText "RV9VQgd5sMMK0flHPFUAhS804gz7z1tN39g4H9lkpfg") + , (unsafeChainId 20, unsafeFromText "FAxLDjtb8r_0S0Rfr8rD47EQwO-Ma-fmEynZccHvn5o") + , (unsafeChainId 21, unsafeFromText "RYPcKnqXKzSneT9zLC6OSGpQah48AeRWIVrSMbEYfcE") + , (unsafeChainId 22, unsafeFromText "IQLMke3si3QrlqKRyesUJr0iOdYFawl0UhPVXHYc6-M") + , (unsafeChainId 23, unsafeFromText "-dc_2udXDNRodCsLAX02kKVsnI-gQMeBZdsZHjxEkbw") + , (unsafeChainId 24, unsafeFromText "nWj_l1UK6k9hdMRV53WfNPEIHmUW2NFDpv0-iI2SnPQ") + , (unsafeChainId 25, unsafeFromText "8OH3La_FkKuK91jQZETYp_QnE2UhQHJnlyZdSql6nhs") + , (unsafeChainId 26, unsafeFromText "tHw2yo16N5wEyz2jsd53kplg2xeIi-5PwdzY0KlzzSM") + , (unsafeChainId 27, unsafeFromText "20Rw_Wl_AZl0BmsYPYkv6ghIL8jqGCUeOpUiLhCuS84") + , (unsafeChainId 28, unsafeFromText "_ThaCzgNd-zBRfzz3l-ggZT_XWPwR0OTrolGSUexdsA") + , (unsafeChainId 29, unsafeFromText "vi1Pgfd1Uyio0OUi1RHCHvRNNYIjEX9Z4-YY9Hkrjo4") + , (unsafeChainId 30, unsafeFromText "a0cPOU3F0WTHWrQXPJIGToEpVETRetRM4-FabZ3WhfU") + , (unsafeChainId 31, unsafeFromText "gRs2a2_sBlxwVABhjLkPqdBGY4jSOI-9FsYeLYZX42s") + , (unsafeChainId 32, unsafeFromText "-IFOzOxVR2-yusLt_W9ns_eURYgFsEYTmWBeqCiWowo") + , (unsafeChainId 33, unsafeFromText "_yCbWuqwwYEX_YbGxH8XJ5ZmCWoobO7WUyyMt1MGgxE") + , (unsafeChainId 34, unsafeFromText "cv9ZuWQvqVkPZAyaaVX-NUPpgrwxg23_K7vtD3CRqB8") + , (unsafeChainId 35, unsafeFromText "iNZJV9TWAEOB9W_4bCrEB0tpvSOcEz63K3NfSFbiDXw") + , (unsafeChainId 36, unsafeFromText "e4PE6KrZkxtncGRGS4sscjuq75JZ1S798-TJHja__Kg") + , (unsafeChainId 37, unsafeFromText "gj4cGxxI_maEK2yIXTE1JW-s10W8291mAZiEQQHevcs") + , (unsafeChainId 38, unsafeFromText "miWz2MqGFUUx_KsbYUHWmJ6HMEP0w5UlT83m6r7onLY") + , (unsafeChainId 39, unsafeFromText "KfnCJ-BsVoG7ae42M9STk2Y6FO8LKdsijDklbDhyUfo") -- Minimal Payload Provider , (unsafeChainId 40, unsafeFromText "H3VBsNGh-SQE-0d_qlYSHnS2obzUeo6Zi1XDDvhndYo") , (unsafeChainId 41, unsafeFromText "N6hVHz6vo0frpS3eyqvtMeZg1eFbAMJ1CS315M-JpWw") From 99326a63e122df25c56dca24a7f30af474061512 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 24 Apr 2025 12:45:58 -0400 Subject: [PATCH 119/378] Test fixes Change-Id: Id000000019ea536b97ac0605949da2686c003318 --- chainweb.cabal | 1 + src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 16 +- src/Chainweb/ChainId.hs | 46 +-- src/Chainweb/Chainweb.hs | 131 ++---- src/Chainweb/Chainweb/Configuration.hs | 2 +- src/Chainweb/Chainweb/CutResources.hs | 31 +- src/Chainweb/CutDB.hs | 11 +- src/Chainweb/Mempool/Mempool.hs | 2 +- src/Chainweb/Mempool/RestAPI/Server.hs | 7 +- src/Chainweb/Miner/Config.hs | 12 - src/Chainweb/Pact/Backend/Utils.hs | 42 +- src/Chainweb/Pact/PactService.hs | 58 +-- src/Chainweb/Pact/PactService/ExecBlock.hs | 2 + src/Chainweb/Pact/RestAPI/Server.hs | 40 +- src/Chainweb/Pact/Types.hs | 24 +- src/Chainweb/PayloadProvider.hs | 16 +- src/Chainweb/PayloadProvider/Pact.hs | 15 +- src/Chainweb/PayloadProvider/Pact/Genesis.hs | 64 +-- src/Chainweb/RestAPI.hs | 89 ++--- src/Chainweb/Utils.hs | 14 +- src/Chainweb/Utils/Rule.hs | 7 +- src/Chainweb/Version.hs | 29 +- src/Chainweb/Version/Development.hs | 16 +- src/Chainweb/Version/EvmDevelopment.hs | 12 +- src/Chainweb/Version/Mainnet.hs | 72 ++-- src/Chainweb/Version/RecapDevelopment.hs | 76 ++-- src/Chainweb/Version/Registry.hs | 3 +- src/Chainweb/Version/Testnet04.hs | 74 ++-- src/Chainweb/Version/Testnet05.hs | 80 ++-- src/Chainweb/WebBlockHeaderDB.hs | 2 +- test/lib/Chainweb/Test/Orphans/Internal.hs | 1 - test/lib/Chainweb/Test/RestAPI/Utils.hs | 8 +- test/lib/Chainweb/Test/TestVersions.hs | 90 ++--- test/lib/Chainweb/Test/Utils.hs | 54 +-- .../Chainweb/Test/BlockHeader/Validation.hs | 17 +- .../Chainweb/Test/BlockHeaderDB/PruneForks.hs | 2 +- test/unit/Chainweb/Test/CutDB.hs | 148 +++---- test/unit/Chainweb/Test/Mempool.hs | 4 +- test/unit/Chainweb/Test/Mempool/Consensus.hs | 2 +- test/unit/Chainweb/Test/Pact/CutFixture.hs | 341 +++++++--------- .../Chainweb/Test/Pact/PactServiceTest.hs | 14 +- .../unit/Chainweb/Test/Pact/RemotePactTest.hs | 377 +++++++----------- .../Chainweb/Test/Pact/TransactionExecTest.hs | 7 +- test/unit/Chainweb/Test/Roundtrips.hs | 6 +- test/unit/Chainweb/Test/SPV/EventProof.hs | 7 +- .../Chainweb/Test/Sync/WebBlockHeaderStore.hs | 11 +- test/unit/Chainweb/Test/TreeDB.hs | 1 + test/unit/ChainwebTests.hs | 16 - 48 files changed, 975 insertions(+), 1125 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index bcd17dc4ae..13ef82c715 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -550,6 +550,7 @@ library chainweb-test-utils , quickcheck-instances >= 0.3 , random >= 1.2 , resourcet >= 1.3 + , resource-pool >= 0.4 , retry >= 0.7 , rocksdb-haskell-kadena >= 1.1.0 , safe-exceptions >= 0.1 diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index e9579e4526..368c21590d 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -302,20 +302,19 @@ someBlockHeaderDbServer (SomeBlockHeaderDb (db :: BlockHeaderDb_ v c)) someBlockHeaderDbServers :: ChainwebVersion - -> [(ChainId, BlockHeaderDb)] + -> ChainMap BlockHeaderDb -> SomeServer -someBlockHeaderDbServers v cdbs = mconcat - [ someBlockHeaderDbServer (someBlockHeaderDbVal v cid cdb) - | (cid, cdb) <- Map.toList $ (Map.fromList cdbs) - ] +someBlockHeaderDbServers v cdbs = ifoldMap + (\cid cdb -> someBlockHeaderDbServer (someBlockHeaderDbVal v cid cdb)) + cdbs someP2pBlockHeaderDbServer :: SomeBlockHeaderDb -> SomeServer someP2pBlockHeaderDbServer (SomeBlockHeaderDb (db :: BlockHeaderDb_ v c)) = SomeServer (Proxy @(P2pBlockHeaderDbApi v c)) (p2pBlockHeaderDbServer db) -someP2pBlockHeaderDbServers :: ChainwebVersion -> [(ChainId, BlockHeaderDb)] -> SomeServer -someP2pBlockHeaderDbServers v = mconcat - . fmap (someP2pBlockHeaderDbServer . uncurry (someBlockHeaderDbVal v)) +someP2pBlockHeaderDbServers :: ChainwebVersion -> ChainMap BlockHeaderDb -> SomeServer +someP2pBlockHeaderDbServers v = ifoldMap + (\cid bhdb -> someP2pBlockHeaderDbServer (someBlockHeaderDbVal v cid bhdb)) -- -------------------------------------------------------------------------- -- -- BlockHeader Event Stream @@ -344,4 +343,3 @@ blockStreamHandler db = Tagged $ \req resp -> do f :: HeaderUpdate -> ServerEvent f hu = ServerEvent (Just $ fromByteString "BlockHeader") Nothing [ fromLazyByteString . encode $ toJSON hu ] - diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index be3e8c613c..0d979a93b6 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -18,6 +18,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module: Chainweb.ChainId @@ -67,7 +68,6 @@ module Chainweb.ChainId , chainZip ) where -import Control.Applicative import Control.DeepSeq import Control.Lens hiding ((.=)) import Control.Monad.Catch (Exception, MonadThrow) @@ -81,7 +81,6 @@ import Data.Kind import Data.Proxy import Data.Semialign import qualified Data.Text as T -import Data.These import Data.Word (Word32) import GHC.Generics (Generic) @@ -283,50 +282,45 @@ chainIdInt (ChainId' cid) = int cid -- exists a value for each chain? -- | Values keyed by `ChainId`s, or a single value that applies for all chains. -data ChainMap a = AllChains a | OnChains (HashMap ChainId a) - deriving stock (Eq, Functor, Foldable, Traversable, Generic, Ord, Show) - deriving anyclass (Hashable, NFData) +newtype ChainMap a = ChainMap (HashMap ChainId a) + deriving stock (Traversable, Generic) + deriving newtype (Eq, Hashable, Functor, Foldable, NFData, Ord, Show) +instance Semigroup (ChainMap a) where + (<>) = chainZip (\f _s -> f) +instance Monoid (ChainMap a) where + mempty = ChainMap mempty + +instance FoldableWithIndex ChainId ChainMap where + ifoldMap f (ChainMap a) = ifoldMap f a --- TODO: fix this. This is not a legal instance, because `align` can change the --- shape from `AllChains` to `OnChains`. This breaks the "alignedness" law. instance Semialign ChainMap where - align (OnChains l) (OnChains r) = OnChains $ align l r - align (OnChains l) (AllChains r) = OnChains $ fmap (`These` r) l - align (AllChains l) (OnChains r) = OnChains $ fmap (l `These`) r - align (AllChains l) (AllChains r) = AllChains $ These l r + align (ChainMap l) (ChainMap r) = ChainMap $ align l r --- | A smart constructor, @onChains = OnChains . HM.fromList@. +-- | A smart constructor, @onChains = ChainMap . HM.fromList@. onChains :: [(ChainId, a)] -> ChainMap a -onChains = OnChains . HM.fromList +onChains = ChainMap . HM.fromList --- | A smart constructor, @onChain c a = OnChains (HM.singleton c a)@. +-- | A smart constructor, @onChain c a = ChainMap (HM.singleton c a)@. onChain :: ChainId -> a -> ChainMap a -onChain c a = OnChains (HM.singleton c a) +onChain c a = ChainMap (HM.singleton c a) -- | Zips two `ChainMap`s on their chain IDs. chainZip :: (a -> a -> a) -> ChainMap a -> ChainMap a -> ChainMap a -chainZip f (OnChains l) (OnChains r) = OnChains $ HM.unionWith f l r -chainZip f (OnChains l) (AllChains r) = OnChains $ fmap (`f` r) l -chainZip f (AllChains l) (OnChains r) = OnChains $ fmap (l `f`) r -chainZip f (AllChains l) (AllChains r) = AllChains $ f l r +chainZip f (ChainMap l) (ChainMap r) = ChainMap $ HM.unionWith f l r instance ToJSON a => ToJSON (ChainMap a) where - toJSON (AllChains a) = object - [ "allChains" .= a - ] - toJSON (OnChains m) = toJSON m + toJSON (ChainMap m) = toJSON m instance FromJSON a => FromJSON (ChainMap a) where parseJSON = withObject "ChainMap" $ \o -> - (AllChains <$> o .: "allChains") <|> OnChains <$> parseJSON (Object o) + ChainMap <$> parseJSON (Object o) makePrisms ''ChainMap -- | Provides access to the value at a `ChainId`, if it exists. atChain :: HasChainId cid => cid -> Fold (ChainMap a) a atChain cid = folding $ \case - OnChains m -> m ^. at (_chainId cid) - AllChains a -> Just a + ChainMap m -> m ^. at (_chainId cid) type instance Index (ChainMap a) = ChainId type instance IxValue (ChainMap a) = a diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 084b6f8813..1b7c5ddf55 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -55,7 +55,6 @@ module Chainweb.Chainweb , chainwebLogger , chainwebSocket , chainwebPeer -, chainwebPayloadProviders , chainwebThrottler , chainwebPutPeerThrottler , chainwebMempoolThrottler @@ -102,6 +101,8 @@ import Control.Exception import Control.Lens hiding ((.=), (<.>)) import Control.Monad import Control.Monad.Catch (MonadThrow (throwM)) +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource import Data.Foldable import Data.HashMap.Strict qualified as HM @@ -159,6 +160,7 @@ import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID import Chainweb.Storage.Table.RocksDB import Chainweb.Sync.WebBlockHeaderStore +import Chainweb.Time import Chainweb.Utils import Chainweb.Utils.RequestLog import Chainweb.Version @@ -176,13 +178,12 @@ import qualified Chainweb.PayloadProvider.Pact.Genesis as Pact data Chainweb logger = Chainweb { _chainwebHostAddress :: !HostAddress - , _chainwebChains :: !(HM.HashMap ChainId (ChainResources logger)) + , _chainwebChains :: !(ChainMap (ChainResources logger)) , _chainwebCutResources :: !CutResources , _chainwebMiner :: !(Maybe (MinerResources logger)) , _chainwebCoordinator :: !(Maybe (MiningCoordination logger)) , _chainwebLogger :: !logger , _chainwebPeer :: !(PeerResources logger) - , _chainwebPayloadProviders :: !(ChainMap ConfiguredPayloadProvider) , _chainwebManager :: !HTTP.Manager -- , _chainwebPactData :: ![(ChainId, PactServerData logger tbl)] , _chainwebThrottler :: !(Throttle Address) @@ -312,7 +313,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir rocksDb (_peerResManager peerRes) pactDbDir - (pactConfig cid maxGasLimit) + (pactConfig maxGasLimit) (_peerResConfig peerRes) myInfo peerDb @@ -338,9 +339,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir mgr :: HTTP.Manager mgr = _peerResManager peerRes - payloadDb :: PayloadDb RocksDbTable - payloadDb = newPayloadDb rocksDb - peer :: Peer peer = _peerResPeer peerRes @@ -353,7 +351,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir p2pConfig :: P2pConfiguration p2pConfig = _peerResConfig peerRes - pactConfig chain maxGasLimit = PactServiceConfig + pactConfig maxGasLimit = PactServiceConfig { _pactReorgLimit = _configReorgLimit conf , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf , _pactAllowReadsInLocal = _configAllowReadsInLocal conf @@ -367,6 +365,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir , _pactTxTimeLimit = Nothing -- FIXME , _pactMiner = Nothing + , _pactBlockRefreshInterval = Micros 5_000_000 } -- FIXME: make this configurable @@ -386,26 +385,19 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir backupLogger :: logger backupLogger = addLabel ("component", "backup") logger - pruningLogger :: T.Text -> logger - pruningLogger l = addLabel ("sub-component", l) - $ setComponent "database-pruning" logger - chainLogger :: HasChainId c => c -> logger chainLogger cid = addLabel ("chain", toText (_chainId cid)) logger initLogger :: logger initLogger = setComponent "init" logger - providerLogger :: HasChainId p => HasPayloadProviderType p => p -> logger - providerLogger p = chainLogger p - & addLabel ("provider", toText (_payloadProviderType p)) + providerLogger :: HasPayloadProviderType p => p -> logger -> logger + providerLogger p = + addLabel ("provider", toText (_payloadProviderType p)) logg :: LogFunctionText logg = logFunctionText initLogger - providerLogg :: HasChainId p => HasPayloadProviderType p => p -> LogFunctionText - providerLogg = logFunctionText . providerLogger - -- Initialize global resources -- TODO: Can this be moved to a top-level function or broken down a bit to -- avoid excessive indentation? @@ -413,16 +405,17 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir global :: ChainMap (ChainResources logger) -> IO () - global cs = do + global cs = runResourceT $ do let !webchain = mkWebBlockHeaderDb v (fmap _chainResBlockHeaderDb cs) -- !pact = mkWebPactExecutionService (HM.map _chainResPact cs) !providers = payloadProvidersForAllChains cs !cutLogger = setComponent "cut" logger - logg Debug "start initializing cut resources" - logFunctionJson logger Info InitializingCutResources + liftIO $ logg Debug "start initializing cut resources" + liftIO $ logFunctionJson logger Info InitializingCutResources - withCutResources cutLogger cutDbParams p2pConfig myInfo peerDb rocksDb webchain providers mgr $ \cuts -> do + cuts <- withCutResources cutLogger cutDbParams p2pConfig myInfo peerDb rocksDb webchain providers mgr + liftIO $ do logg Debug "finished initializing cut resources" let !mLogger = setComponent "miner" logger @@ -540,7 +533,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir , _chainwebCoordinator = mc , _chainwebLogger = logger , _chainwebPeer = peerRes - , _chainwebPayloadProviders = providers , _chainwebManager = mgr -- , _chainwebPactData = pactData , _chainwebThrottler = throttler @@ -561,20 +553,25 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir synchronizeProviders wbh providers c = do mapConcurrently_ syncOne (_cutHeaders c) where - syncOne hdr = withPayloadProvider providers hdr $ \provider -> do - providerLogg provider Info $ - "sync payload provider to " - <> sshow (view blockHeight hdr) - <> ":" <> sshow (view blockHash hdr) - finfo <- forkInfoForHeader wbh hdr Nothing - providerLogg provider Debug $ "syncToBlock with fork info " <> sshow finfo - r <- syncToBlock provider Nothing finfo `catch` \(e :: SomeException) -> do - providerLogg provider Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e - throwM e - unless (r == _forkInfoTargetState finfo) $ do - providerLogg provider Error $ "unexpectedResult" - error "Chainweb.Chainweb.synchronizeProviders: unexpected result state" - -- FIXME + syncOne hdr = forM_ (providers ^? atChain (_chainId hdr)) $ \case + ConfiguredPayloadProvider provider -> do + let loggr = (providerLogger provider(chainLogger hdr)) + logFunctionText loggr Info $ + "sync payload provider to " + <> sshow (view blockHeight hdr) + <> ":" <> sshow (view blockHash hdr) + finfo <- forkInfoForHeader wbh hdr Nothing + logFunctionText loggr Debug $ "syncToBlock with fork info " <> sshow finfo + r <- syncToBlock provider Nothing finfo `catch` \(e :: SomeException) -> do + logFunctionText loggr Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e + throwM e + unless (r == _forkInfoTargetState finfo) $ do + logFunctionText loggr Error $ "unexpectedResult" + error "Chainweb.Chainweb.synchronizeProviders: unexpected result state" + -- FIXME + DisabledPayloadProvider -> do + logFunctionText logger Info $ + "payload provider disabled, not synced, on chain: " <> toText (_chainId hdr) -- synchronizePactDb :: HM.HashMap ChainId (ChainResources logger) -> Cut -> IO () -- synchronizePactDb cs targetCut = do @@ -713,49 +710,17 @@ runChainweb cw nowServing = do concurrentlies_ $ concat [ miner , cutNetworks (_chainwebCutResources cw) - , runP2pNodesOfAllChains chainVals + , runP2pNodesOfAllChains (_chainwebChains cw) , mpClients ] logg :: LogFunctionText logg = logFunctionText $ _chainwebLogger cw - -- chains - chains :: [(ChainId, ChainResources logger)] - chains = HM.toList (_chainwebChains cw) - - chainVals :: [ChainResources logger] - chainVals = map snd chains - - -- collect server resources - proj :: forall a . (ChainResources logger -> a) -> [(ChainId, a)] - proj f = chains & mapped . _2 %~ f - - peerDb = _peerResDb (_chainwebPeer cw) - - -- FIXME export the SomeServer instead of DBs? - -- I.e. the handler would be created in the chain resource. - -- Similar to how it is done with the payload provider APIs. - -- - chainDbsToServe :: [(ChainId, BlockHeaderDb)] - chainDbsToServe = proj _chainResBlockHeaderDb - - mempoolsToServe :: [(ChainId, Mempool.MempoolBackend Pact.Transaction)] - -- mempoolsToServe = proj _chainResMempool - mempoolsToServe = [] - - pactDbsToServe :: [(ChainId, PactServerData logger tbl)] - -- pactDbsToServe = _chainwebPactData cw - pactDbsToServe = [] - - memP2pPeersToServe :: [(NetworkId, PeerDb)] - -- memP2pToServe = (\(i, _) -> (MempoolNetwork i, peerDb)) <$> chains - memP2pPeersToServe = [] - -- TODO use the peerDbs from the respective chains -- (even though those are currently all the same) - payloadP2pPeersToServe :: [(NetworkId, PeerDb)] - payloadP2pPeersToServe = (\(i, _) -> (ChainNetwork i, peerDb)) <$> chains + -- payloadP2pPeersToServe :: [(NetworkId, PeerDb)] + -- payloadP2pPeersToServe = (\(i, _) -> (ChainNetwork i, peerDb)) <$> chains loggServerError msg (Just r) e = "HTTP server error (" <> msg <> "): " <> sshow e <> ". Request: " <> sshow r @@ -790,16 +755,11 @@ runChainweb cw nowServing = do logFunctionCounter (_chainwebLogger cw) Info . (:[]) =<< roll clientClosedConnectionsCounter - chainwebServerDbs :: ChainwebServerDbs Pact.Transaction + chainwebServerDbs :: ChainwebServerDbs chainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Just cutDb - , _chainwebServerBlockHeaderDbs = chainDbsToServe - , _chainwebServerMempools = mempoolsToServe - , _chainwebServerPayloads = payloadsToServeOnP2pApi chains - , _chainwebServerPeerDbs - = (CutNetwork, cutPeerDb) - : memP2pPeersToServe - <> payloadP2pPeersToServe + , _chainwebServerBlockHeaderDbs = _chainResBlockHeaderDb <$> _chainwebChains cw + , _chainwebServerPeerDbs = [(CutNetwork, cutPeerDb)] } serve :: Middleware -> IO () @@ -881,9 +841,7 @@ runChainweb cw nowServing = do (_chainwebVersion cw) ChainwebServerDbs { _chainwebServerCutDb = Just cutDb - , _chainwebServerBlockHeaderDbs = chainDbsToServe - , _chainwebServerMempools = mempoolsToServe - , _chainwebServerPayloads = payloadsToServeOnServiceApi chains + , _chainwebServerBlockHeaderDbs = _chainResBlockHeaderDb <$> _chainwebChains cw -- We do not want to serve peer APIs on the service API. -- If at all we could serve the GET endpoints. @@ -911,11 +869,6 @@ runChainweb cw nowServing = do miner :: [IO ()] miner = maybe [] (\m -> [ runMiner (_chainwebVersion cw) m ]) $ _chainwebMiner cw - -- Configure Clients - - mgr :: HTTP.Manager - mgr = view chainwebManager cw - -- Mempool mempoolP2pConfig :: EnableConfig MempoolP2pConfig @@ -929,7 +882,7 @@ runChainweb cw nowServing = do Just c | cw ^. chainwebVersion . versionDefaults . disableMempoolSync -> disabled | otherwise -> enabled c - where + where disabled = do logg Info "Mempool p2p sync disabled" return [] diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 44cff8dbd1..51cf5fc063 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -759,7 +759,7 @@ parseVersion = constructVersion & versionForks %~ HM.filterWithKey (\fork _ -> fork <= fromMaybe maxBound fub) & versionUpgrades .~ maybe (_versionUpgrades winningVersion) (\fub' -> - OnChains $ HM.mapWithKey + ChainMap $ HM.mapWithKey (\cid _ -> case winningVersion ^?! versionForks . at fub' . _Just . atChain cid of ForkNever -> error "Chainweb.Chainweb.Configuration.parseVersion: the fork upper bound never occurs in this version." diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 89ab9547f0..a7fcd8cf55 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -31,6 +31,8 @@ module Chainweb.Chainweb.CutResources ) where import Control.Lens +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource import Prelude hiding (log) @@ -86,27 +88,26 @@ withCutResources -> WebBlockHeaderDb -> ChainMap ConfiguredPayloadProvider -> HTTP.Manager - -> (CutResources -> IO a) - -> IO a -withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain providers mgr f = do + -> ResourceT IO CutResources +withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain providers mgr = do -- initialize blockheader store - headerStore <- newWebBlockHeaderStore mgr webchain (logFunction logger) + headerStore <- liftIO $ newWebBlockHeaderStore mgr webchain (logFunction logger) -- initialize cutHashes store let cutHashesStore = cutHashesTable rdb - withCutDb cutDbParams (logFunction logger) headerStore providers cutHashesStore $ \cutDb -> do - cutP2pNode <- mkP2pNode True "cut" $ - C.syncSession myInfo cutDb - headerP2pNode <- mkP2pNode False "header" $ - session 10 (_webBlockHeaderStoreQueue headerStore) - f $ CutResources - { _cutResPeerDb = peerDb - , _cutResCutDb = cutDb - , _cutResCutP2pNode = cutP2pNode - , _cutResHeaderP2pNode = headerP2pNode - } + cutDb <- withCutDb cutDbParams (logFunction logger) headerStore providers cutHashesStore + cutP2pNode <- liftIO $ mkP2pNode True "cut" $ + C.syncSession myInfo cutDb + headerP2pNode <- liftIO $ mkP2pNode False "header" $ + session 10 (_webBlockHeaderStoreQueue headerStore) + return CutResources + { _cutResPeerDb = peerDb + , _cutResCutDb = cutDb + , _cutResCutP2pNode = cutP2pNode + , _cutResHeaderP2pNode = headerP2pNode + } where syncLogger = addLabel ("sub-component", "sync") logger diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 46ae303210..227a5289d8 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -102,6 +102,7 @@ import Control.Monad.Catch (throwM) import Control.Monad.IO.Class import Control.Monad.Morph import Control.Monad.STM +import Control.Monad.Trans.Resource import Data.Aeson (ToJSON) import Data.Foldable @@ -398,12 +399,11 @@ withCutDb -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes - -> (CutDb -> IO a) - -> IO a -withCutDb config logfun headerStore providers cutHashesStore a - = bracket + -> ResourceT IO CutDb +withCutDb config logfun headerStore providers cutHashesStore + = snd <$> allocate (startCutDb config logfun headerStore providers cutHashesStore) - stopCutDb a + stopCutDb -- | Start a CutDB. This loads the initial cut from the database (falling back -- to the configured initial cut loading fails) and starts the cut validation @@ -434,7 +434,6 @@ startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do "got initial cut:" : [" " <> block | block <- cutToTextShort c] queue <- newEmptyPQueue cutAsync <- asyncWithUnmask $ \u -> u $ processor queue cutVar - logg Debug "CutDB started" return CutDb { _cutDbCut = cutVar , _cutDbQueue = queue diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Mempool/Mempool.hs index 95bc7b809c..c37d3478c9 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Mempool/Mempool.hs @@ -255,7 +255,7 @@ instance Show InsertError where InsertErrorBadlisted -> "Transaction is badlisted because it previously failed to validate." InsertErrorMetadataMismatch -> "Transaction metadata (chain id, chainweb version) conflicts with this endpoint" InsertErrorTransactionsDisabled -> "Transactions are disabled until 2019 Dec 5" - InsertErrorBuyGas msg -> "Attempt to buy gas failed with: " <> T.unpack msg + InsertErrorBuyGas msg -> T.unpack msg InsertErrorCompilationFailed msg -> "Transaction compilation failed: " <> T.unpack msg InsertErrorOther m -> "insert error: " <> T.unpack m InsertErrorInvalidHash -> "Invalid transaction hash" diff --git a/src/Chainweb/Mempool/RestAPI/Server.hs b/src/Chainweb/Mempool/RestAPI/Server.hs index c4a950cfc6..5092c25fc9 100644 --- a/src/Chainweb/Mempool/RestAPI/Server.hs +++ b/src/Chainweb/Mempool/RestAPI/Server.hs @@ -13,6 +13,7 @@ module Chainweb.Mempool.RestAPI.Server ------------------------------------------------------------------------------ import Control.DeepSeq (NFData) +import Control.Lens import Control.Monad.Catch hiding (Handler) import Control.Monad.IO.Class import qualified Data.DList as D @@ -119,9 +120,9 @@ someMempoolServer ver (SomeMempool (mempool :: Mempool_ v c t)) someMempoolServers :: (Show t) - => ChainwebVersion -> [(ChainId, MempoolBackend t)] -> SomeServer -someMempoolServers v = mconcat - . fmap (someMempoolServer v . uncurry (someMempoolVal v)) + => ChainwebVersion -> ChainMap (MempoolBackend t) -> SomeServer +someMempoolServers v = ifoldMap + (\cid mempool -> someMempoolServer v (someMempoolVal v cid mempool)) mempoolServer :: Show t => ChainwebVersion -> Mempool_ v c t -> Server (MempoolApi v c) diff --git a/src/Chainweb/Miner/Config.hs b/src/Chainweb/Miner/Config.hs index 91bf146c0d..595745e004 100644 --- a/src/Chainweb/Miner/Config.hs +++ b/src/Chainweb/Miner/Config.hs @@ -166,8 +166,6 @@ data CoordinationConfig = CoordinationConfig -- present on the node. , _coordinationUpdateStreamTimeout :: !Seconds -- ^ the duration that an update stream is kept open in seconds - , _coordinationPayloadRefreshDelay :: !(TimeSpan Micros) - -- ^ the duration between payload refreshes in microseconds } deriving stock (Eq, Show, Generic) coordinationEnabled :: Lens' CoordinationConfig Bool @@ -177,28 +175,21 @@ coordinationUpdateStreamTimeout :: Lens' CoordinationConfig Seconds coordinationUpdateStreamTimeout = lens _coordinationUpdateStreamTimeout (\m c -> m { _coordinationUpdateStreamTimeout = c }) -coordinationPayloadRefreshDelay :: Lens' CoordinationConfig (TimeSpan Micros) -coordinationPayloadRefreshDelay = - lens _coordinationPayloadRefreshDelay (\m c -> m { _coordinationPayloadRefreshDelay = c }) - instance ToJSON CoordinationConfig where toJSON o = object [ "enabled" .= _coordinationEnabled o , "updateStreamTimeout" .= _coordinationUpdateStreamTimeout o - , "payloadRefreshDelay" .= _coordinationPayloadRefreshDelay o ] instance FromJSON (CoordinationConfig -> CoordinationConfig) where parseJSON = withObject "CoordinationConfig" $ \o -> id <$< coordinationEnabled ..: "enabled" % o <*< coordinationUpdateStreamTimeout ..: "updateStreamTimeout" % o - <*< coordinationPayloadRefreshDelay ..: "payloadRefreshDelay" % o defaultCoordination :: CoordinationConfig defaultCoordination = CoordinationConfig { _coordinationEnabled = False , _coordinationUpdateStreamTimeout = 240 - , _coordinationPayloadRefreshDelay = TimeSpan (Micros 15_000_000) } pCoordinationConfig :: MParser CoordinationConfig @@ -209,9 +200,6 @@ pCoordinationConfig = id <*< coordinationUpdateStreamTimeout .:: jsonOption % long "mining-update-stream-timeout" <> help "duration that an update stream is kept open in seconds" - <*< coordinationPayloadRefreshDelay .:: jsonOption - % long "mining-payload-refresh-delay" - <> help "frequency that the mining payload is refreshed" pMiner :: String -> Parser Miner pMiner prefix = pkToMiner <$> pPk diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 7a982f66aa..a3d0fa2fbd 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -15,6 +15,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Pact.ChainwebPactDb @@ -51,6 +52,7 @@ module Chainweb.Pact.Backend.Utils -- * SQLite runners , withSqliteDb , withReadSqliteDb + , withReadSqlitePool , startSqliteDb , startReadSqliteDb , stopSqliteDb @@ -85,9 +87,10 @@ import Control.Monad.Trans.Resource (ResourceT, allocate) import Data.Bits import Data.Foldable import Data.String -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import qualified Database.SQLite3.Direct as SQ3 +import Data.Pool qualified as Pool +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import Database.SQLite3.Direct qualified as SQ3 import Prelude hiding (log) @@ -153,7 +156,7 @@ asStringUtf8 = toUtf8 . asString -- withSavepoint - :: (MonadMask m, MonadIO m) + :: (HasCallStack, MonadMask m, MonadIO m) => SQLiteEnv -> SavepointName -> m a @@ -258,6 +261,26 @@ withSqliteDb cid logger dbDir resetDb = snd <$> allocate (startSqliteDb cid logger dbDir resetDb) stopSqliteDb +withReadSqliteDb + :: Logger logger + => ChainId + -> logger + -> FilePath + -> ResourceT IO SQLiteEnv +withReadSqliteDb cid logger dbDir = snd <$> allocate + (startReadSqliteDb cid logger dbDir) + stopSqliteDb + +withReadSqlitePool :: FilePath -> ResourceT IO (Pool.Pool SQLiteEnv) +withReadSqlitePool pactDbDir = snd <$> allocate + (Pool.newPool $ Pool.defaultPoolConfig + (openSQLiteConnection pactDbDir chainwebPragmas) + stopSqliteDb + 30 -- seconds to keep them around unused + 2 -- connections at most + & Pool.setNumStripes (Just 2) -- two stripes, one connection per stripe + ) (Pool.destroyAllResources) + startSqliteDb :: Logger logger => ChainId @@ -286,17 +309,6 @@ startReadSqliteDb cid logger dbDir = do where sqliteFile = dbDir chainDbFileName cid -withReadSqliteDb - :: Logger logger - => ChainId - -> logger - -> FilePath - -> (SQLiteEnv -> IO a) - -> IO a -withReadSqliteDb cid logger dbDir = bracket - (startReadSqliteDb cid logger dbDir) - stopSqliteDb - chainDbFileName :: ChainId -> FilePath chainDbFileName cid = fold [ "pact-v1-chain-" diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index d29c677070..af42e3f795 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -55,7 +55,6 @@ import qualified Data.HashMap.Strict as HM import Data.Maybe import Data.Monoid import Data.Pool (Pool) -import Data.Text (Text) import qualified Data.Text as Text import Data.Vector (Vector) import qualified Data.Vector as V @@ -125,7 +124,7 @@ import qualified Chainweb.Pact.NoCoinbase as Pact import Chainweb.Core.Brief withPactService - :: (Logger logger, CanReadablePayloadCas tbl) + :: (Logger logger, CanPayloadCas tbl) => ChainwebVersion -> ChainId -> Maybe HTTP.Manager @@ -156,7 +155,8 @@ withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb { _psVersion = ver , _psChainId = cid -- TODO: PPgaslog - , _psGasLogger = undefined <$ guard (_pactLogGas config) + -- , _psGasLogger = undefined <$ guard (_pactLogGas config) + , _psGasLogger = Nothing , _psReadSqlPool = readSqlPool , _psReadWriteSql = readWriteSqlenv , _psPdb = payloadStore @@ -171,8 +171,10 @@ withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb , _psNewBlockGasLimit = _pactNewBlockGasLimit config , _psMiningPayloadVar = miningPayloadVar , _psGenesisPayload = maybeGenesisPayload + , _psBlockRefreshInterval = _pactBlockRefreshInterval config } + liftIO $ initialPayloadState chainwebLogger pse return pse initialPayloadState @@ -194,9 +196,12 @@ runGenesisIfNeeded runGenesisIfNeeded logger serviceEnv = do latestBlock <- fmap _consensusStateLatest <$> Checkpointer.getConsensusState (_psReadWriteSql serviceEnv) when (maybe True (isGenesisBlockHeader' v cid . Parent . _syncStateBlockHash) latestBlock) $ do + logFunctionText logger Debug "running genesis" let genesisBlockHash = genesisBlockHeader v cid ^. blockHash let genesisPayloadHash = genesisBlockPayloadHash v cid + let gTime = v ^?! versionGenesis . genesisTime . atChain cid let targetSyncState = genesisConsensusState v cid + let genesisRankedBlockHash = RankedBlockHash (genesisHeight v cid) genesisBlockHash let evalCtx = genesisEvaluationCtx serviceEnv let blockCtx = blockCtxOfEvaluationCtx v cid evalCtx let !genesisPayload = case _psGenesisPayload serviceEnv of @@ -218,6 +223,16 @@ runGenesisIfNeeded logger serviceEnv = do (genesisHeight v cid) genesisPayload Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState + -- we have to kick off payload refreshing here + emptyBlock <- (throwIfNoHistory =<<) $ + Checkpointer.readFrom logger v cid + (_psReadWriteSql serviceEnv) + (Parent gTime) + (Parent genesisRankedBlockHash) $ + \blockEnv blockHandle -> makeEmptyBlock logger serviceEnv blockEnv blockHandle + refresherThread <- liftIO $ async (refreshPayloads logger serviceEnv) + liftIO $ + atomically $ writeTMVar (_psMiningPayloadVar serviceEnv) (refresherThread, emptyBlock) where v = _chainwebVersion serviceEnv @@ -368,8 +383,8 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do blockCtx (TxBlockIdx 0) spvSupport initialGas (view Pact.payloadObj <$> cwtx) ) commandResult <- case applyCmdResult of - Left _err -> - earlyReturn $ LocalResultWithWarns (J.encodeJsonText Pact.CommandResult + Left err -> + earlyReturn $ LocalResultWithWarns (Pact.CommandResult { _crReqKey = requestKey , _crTxId = Nothing , _crResult = Pact.PactResultErr $ @@ -381,7 +396,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do (Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin (Pact.LineInfo 0)) , _crGas = cwtx ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmGasLimit . Pact._GasLimit - , _crLogs = Nothing :: Maybe Text + , _crLogs = Nothing , _crContinuation = Nothing , _crMetaData = Nothing , _crEvents = [] @@ -393,7 +408,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do let commandResult' = hashPactTxLogs $ set Pact.crMetaData (Just metadata) commandResult -- TODO: once Pact 5 has warnings, include them here. pure $ LocalResultWithWarns - (J.encodeJsonText $ Pact.pactErrorToOnChainError <$> commandResult') + (Pact.pactErrorToOnChainError <$> commandResult') [] _ -> lift $ do -- default is legacy mode: use applyLocal, don't buy gas, don't do any @@ -401,7 +416,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do cr <- flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \dbEnv spvSupport -> do -- TODO: PPgaslog fmap Pact.pactErrorToOnChainError <$> Pact.applyLocal logger Nothing dbEnv blockCtx spvSupport (view Pact.payloadObj <$> cwtx) - pure $ LocalResultLegacy $ J.encodeJsonText (hashPactTxLogs cr) + pure $ LocalResultLegacy $ hashPactTxLogs cr gasLogger = view psGasLogger serviceEnv enableLocalTimeout = view psEnableLocalTimeout serviceEnv @@ -621,32 +636,15 @@ syncToFork logger serviceEnv hints forkInfo = do (fmap (fromRight (error "invalid payload in database")) . runExceptT . pact5TransactionsFromPayload) rewoundPayloads --- runBlock --- :: (CanReadablePayloadCas tbl, Logger logger) --- => logger --- -> ServiceEnv tbl --- -> BlockEnv --- -> BlockPayloadHash --- -> PayloadData --- -> BlockHandle --- -> StateT BlockHandle --- (ExceptT BlockInvalidError IO) --- (DList (Pact.Gas, PayloadWithOutputs, Vector Pact.Transaction)) --- runBlock logger serviceEnv blockEnv payload = do --- (outputs, finalBlockHandle) <- --- Pact.execExistingBlock logger serviceEnv blockEnv expectedPayloadHash (CheckablePayload payload) --- return (DList.singleton outputs, finalBlockHandle, blockHashes) --- where --- expectedPayloadHash = _consensusPayloadHash $ _evaluationCtxPayload evalCtx --- v = _chainwebVersion serviceEnv --- cid = _chainId serviceEnv - refreshPayloads :: Logger logger => logger -> ServiceEnv tbl -> IO () refreshPayloads logger serviceEnv = do -- note that if this is empty, we wait; taking from it is the way to make us stop let logOutraced = liftIO $ logFunctionText logger Debug $ "Refresher outraced by new block" (_, blockInProgress) <- liftIO $ atomically $ readTMVar payloadVar + logFunctionText logger Debug $ + "refreshing payloads for " <> + brief (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) maybeRefreshedBlockInProgress <- Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> Checkpointer.readFrom logger v cid sql (_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress) (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) $ \blockEnv _bh -> do let dbEnv = view psBlockDbEnv blockEnv @@ -665,7 +663,9 @@ refreshPayloads logger serviceEnv = do return False if outraced then logOutraced - else refreshPayloads logger serviceEnv + else do + approximateThreadDelay (int $ _psBlockRefreshInterval serviceEnv) + refreshPayloads logger serviceEnv where payloadVar = _psMiningPayloadVar serviceEnv diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index 04dfa8981f..a0c27671fd 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -169,6 +169,8 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do startTxs <> V.fromList (concat valids) & blockInProgressRemainingGasLimit .~ finalGasLimit + & blockInProgressNumber %~ + succ liftIO $ logFunctionText logger Debug $ "block with new transactions: " <> sshow (fmap (Pact.unRequestKey . Pact._crReqKey . ssnd) $ _transactionPairs (_blockInProgressTransactions blockInProgress')) diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index e0bfd9058c..b506fd7444 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -148,7 +148,7 @@ import Control.Concurrent (threadDelay) data PactServerData logger tbl = PactServerData { _pactServerDataMempool :: !(MempoolBackend Pact.Transaction) , _pactServerDataLogger :: !logger - , _pactServerDataPact :: !(PactPayloadProvider logger tbl) + , _pactServerDataPact :: !(ServiceEnv tbl) } newtype PactServerData_ (v :: ChainwebVersionT) (c :: ChainIdT) logger tbl @@ -191,7 +191,7 @@ pactServer d = where mempool = _pactServerDataMempool d logger = _pactServerDataLogger d - PactPayloadProvider _ pact = _pactServerDataPact d + pact = _pactServerDataPact d pactApiHandlers = sendHandler logger mempool @@ -258,16 +258,14 @@ sendHandler -> Handler Pact.SendResponse sendHandler logger mempool (Pact.SendRequest (Pact.SubmitBatch cmds)) = Handler $ do liftIO $ logg Info (PactCmdLogSend cmds) - case traverse (liftError NEL.singleton . Pact.parseCommand) cmds of - Success cmdsWithParsedPayloads -> do - let cmdsWithParsedPayloadsV = V.fromList $ NEL.toList cmdsWithParsedPayloads - -- If any of the txs in the batch fail validation, we reject them all. - liftIO (mempoolInsertCheckVerbose mempool cmdsWithParsedPayloadsV) >>= checkResult - liftIO (mempoolInsert mempool UncheckedInsert cmdsWithParsedPayloadsV) - return $! Pact.SendResponse $ Pact.RequestKeys $ NEL.map Pact.cmdToRequestKey cmdsWithParsedPayloads - Failure errs -> throwError $ setErrJSONPact (J.array $ fmap convertError errs) err400 + cmdsWithParsedPayloads <- parsedCommands + let cmdsWithParsedPayloadsV = V.fromList $ NEL.toList cmdsWithParsedPayloads + -- If any of the txs in the batch fail validation, we reject them all. + liftIO (mempoolInsertCheckVerbose mempool cmdsWithParsedPayloadsV) >>= checkResult + liftIO (mempoolInsert mempool UncheckedInsert cmdsWithParsedPayloadsV) + return $! Pact.SendResponse $ Pact.RequestKeys $ fmap Pact.cmdToRequestKey cmdsWithParsedPayloads where - convertError = Pact.pactErrorToOnChainError . fmap Pact.spanInfoToLineInfo + convertError = Pact._boundedText . Pact._peMsg . Pact.pactErrorToOnChainError . fmap Pact.spanInfoToLineInfo failWith :: Text -> ExceptT ServerError IO a failWith err = do liftIO $ logFunctionText logger Info err @@ -275,6 +273,24 @@ sendHandler logger mempool (Pact.SendRequest (Pact.SubmitBatch cmds)) = Handler logg = logFunctionJson (setComponent "send-handler" logger) + parsedCommands :: ExceptT ServerError IO (NonEmpty Pact.Transaction) + parsedCommands = do + let + parsedWithErrs = + flip traverse (NEL.zip (NEL.iterate succ (0 :: Word)) cmds) $ \(i, tx) -> + case Pact.parseCommand tx of + Left err -> + Failure $ NEL.singleton $ + "Transaction at index " <> sshow i <> + " has invalid Pact code: " <> convertError err + Right cmd -> + pure cmd + case parsedWithErrs of + Failure errs -> + failWith $ "One or more transactions has invalid Pact code: " <> T.intercalate ", " (toList errs) + Success parsedCmds -> + return parsedCmds + checkResult :: Vector (T2 TransactionHash (Either InsertError Pact.Transaction)) -> ExceptT ServerError IO () checkResult vec | V.null vec = return () @@ -657,7 +673,7 @@ internalPoll logger mempool pact confDepth requestKeys0 = do err = Pact.PactOnChainError -- the only legal error type, once chainweaver is really gone, we -- can use a real error type - (Pact.ErrorType "TxFailure") + (Pact.ErrorType "EvalError") (Pact.mkBoundedText "Transaction is badlisted because it previously failed to validate.") (Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin Pact.noInfo) !cr = Pact.CommandResult rk Nothing res (mempty :: Pact.Gas) Nothing Nothing Nothing [] diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index ab0b6a72b7..158ee379a4 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -18,6 +18,7 @@ {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE NumericUnderscores #-} module Chainweb.Pact.Types ( ServiceEnv(..) @@ -38,6 +39,7 @@ module Chainweb.Pact.Types , psMiningPayloadVar , psNewBlockGasLimit , psGenesisPayload + , psBlockRefreshInterval , BlockCtx(..) , blockCtxOfEvaluationCtx @@ -128,7 +130,6 @@ module Chainweb.Pact.Types import Control.Applicative ((<|>)) import Control.DeepSeq -import Control.Exception.Safe import Control.Lens import Control.Monad.IO.Class import Control.Monad.State.Strict @@ -296,14 +297,11 @@ data LocalPreflightSimulation -- | The type of local results (used in /local endpoint) --- Internally contains a JsonText instead of a CommandResult for compatibility across Pact versions, --- but the constructors are hidden. --- This can be undone in the 2.28 release. -- data LocalResult = MetadataValidationFailure !(NE.NonEmpty Text) - | LocalResultLegacy !J.JsonText - | LocalResultWithWarns !J.JsonText ![Text] + | LocalResultLegacy !(Pact.CommandResult Pact.Hash Pact.PactOnChainError) + | LocalResultWithWarns !(Pact.CommandResult Pact.Hash Pact.PactOnChainError) ![Text] | LocalTimeout deriving stock (Generic, Show) @@ -332,16 +330,16 @@ instance FromJSON LocalResult where (\o -> metaFailureParser o <|> localWithWarnParser o - <|> pure (legacyFallbackParser o) + <|> legacyFallbackParser o ) v where metaFailureParser o = MetadataValidationFailure <$> o .: "preflightValidationFailure" localWithWarnParser o = LocalResultWithWarns - <$> (J.encodeJsonText @Value <$> o .: "preflightResult") + <$> o .: "preflightResult" <*> o .: "preflightWarnings" - legacyFallbackParser _ = LocalResultLegacy $ J.encodeJsonText v + legacyFallbackParser _ = LocalResultLegacy <$> parseJSON v type OnChainCommandResult = Pact.CommandResult Pact.Hash Pact.PactOnChainError @@ -438,6 +436,8 @@ data PactServiceConfig = PactServiceConfig -- If 'Nothing', it's a function of the BlockGasLimit. , _pactMiner :: !(Maybe Miner) -- ^ The miner used to make new blocks. + , _pactBlockRefreshInterval :: !Micros + -- ^ How often to refresh blocks. } deriving (Eq,Show) defaultPactServiceConfig :: PactServiceConfig @@ -452,6 +452,7 @@ defaultPactServiceConfig = PactServiceConfig , _pactEnableLocalTimeout = True , _pactTxTimeLimit = Nothing , _pactMiner = Just noMiner + , _pactBlockRefreshInterval = Micros 1_000_000 } -- | This default value is only relevant for testing. In a chainweb-node the @GasLimit@ @@ -523,8 +524,9 @@ data ServiceEnv tbl = ServiceEnv -- ^ Latest mining payload produced, and block continuation thread. , _psNewBlockGasLimit :: Pact.GasLimit -- ^ Block gas limit in newly produced blocks. - , _psGenesisPayload :: !(Maybe Chainweb.PayloadWithOutputs) + , _psGenesisPayload :: Maybe Chainweb.PayloadWithOutputs -- ^ The genesis payload for this chain. + , _psBlockRefreshInterval :: Micros } instance HasChainwebVersion (ServiceEnv tbl) where @@ -749,7 +751,7 @@ toPayloadWithOutputs mi ts = data PactTxFailureLog = PactTxFailureLog !Pact.RequestKey !Text deriving stock (Generic) - deriving anyclass (NFData, Typeable) + deriving anyclass (NFData) instance LogMessage PactTxFailureLog where logText (PactTxFailureLog rk msg) = "Failed tx " <> sshow rk <> ": " <> msg diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 5facb949d5..67325cae10 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -65,6 +65,7 @@ module Chainweb.PayloadProvider , blockHeaderToEvaluationCtx , nextPayload , nextPayloadStm +, waitForChangedPayload , payloadStream -- * PayloadProvider @@ -809,12 +810,12 @@ class (HasChainwebVersion p, HasChainId p) => PayloadProvider p where -> ForkInfo -> IO ConsensusState - -- | Asynchronoulsly yield new block payloads on top of the latest block. + -- | Asynchronously yield new block payloads on top of the latest block. -- -- The new payload is identified by the block payload hash. -- -- New payloads only depend on there respective evaluation context and new - -- block contenxt and are only valid within this context. The context is + -- block context and are only valid within this context. The context is -- fully determined by the parent header, which is always the block of the -- latest block of the current consensus state. It is represented by its -- respective height and block hash. @@ -824,10 +825,10 @@ class (HasChainwebVersion p, HasChainId p) => PayloadProvider p where -- optimizations in payload synchronizations (e.g. by piggybacking the new -- payloads onto the respective new cuts in the P2P gossip network). Beside -- of being a sequence of bytes the content of the those payloads are - -- completely parametetric outside the context of the payload provider. + -- completely parametric outside the context of the payload provider. -- - -- Payload provider should cache new payloads internally as long they have - -- not either been integrated into the longest chain or definitely + -- Payload providers should cache new payloads internally as long they + -- are not either integrated into the longest chain or definitely -- abandoned. Payload providers may also cache the validation result. -- latestPayloadSTM :: p -> STM NewPayload @@ -850,6 +851,11 @@ nextPayloadStm p cur = do nextPayload :: PayloadProvider p => p -> NewPayload -> IO NewPayload nextPayload p = atomically . nextPayloadStm p +waitForChangedPayload :: PayloadProvider p => p -> IO NewPayload +waitForChangedPayload p = do + old <- latestPayloadIO p + nextPayload p old + payloadStream :: PayloadProvider p => p -> S.Stream (S.Of NewPayload) IO () payloadStream p = do cur <- liftIO $ latestPayloadIO p diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 698b0b57b0..535366e4e3 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -8,14 +8,18 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TypeApplications #-} module Chainweb.PayloadProvider.Pact ( PactPayloadProvider(..) , withPactPayloadProvider , pactMemPoolAccess + , decodeNewPayload ) where import Control.Concurrent.STM +import Control.Exception.Safe import Data.LogMessage import Data.Vector (Vector) import System.LogLevel @@ -27,6 +31,7 @@ import Chainweb.ChainId import Chainweb.Counter import Chainweb.Logger import Chainweb.Mempool.Mempool +import Chainweb.MerkleUniverse import qualified Chainweb.MinerReward as MinerReward import Chainweb.Pact.Backend.Utils import qualified Chainweb.Pact.PactService as PactService @@ -91,9 +96,17 @@ instance (Logger logger, CanPayloadCas tbl) => PayloadProvider (PactPayloadProvi eventProof :: Logger logger => PactPayloadProvider logger tbl -> XEventId -> IO SpvProof eventProof = error "not figured out yet" +decodeNewPayload :: MonadThrow m => NewPayload -> m PayloadWithOutputs +decodeNewPayload NewPayload{..} = do + pd <- decodePayloadData @_ @ChainwebMerkleHashAlgorithm $ + _encodedPayloadData $ fromJuste _newPayloadEncodedPayloadData + bo <- decodeBlockOutputs @_ @ChainwebMerkleHashAlgorithm $ + _encodedPayloadOutputs $ fromJuste _newPayloadEncodedPayloadOutputs + return $ payloadWithOutputs pd (_blockCoinbaseOutput bo) (_blockOutputs bo) + -- | Initialization for Pact (in process) Api withPactPayloadProvider - :: CanReadablePayloadCas tbl + :: CanPayloadCas tbl => Logger logger => ChainwebVersion -> ChainId diff --git a/src/Chainweb/PayloadProvider/Pact/Genesis.hs b/src/Chainweb/PayloadProvider/Pact/Genesis.hs index 2ec2de7b65..47803b5586 100644 --- a/src/Chainweb/PayloadProvider/Pact/Genesis.hs +++ b/src/Chainweb/PayloadProvider/Pact/Genesis.hs @@ -43,36 +43,38 @@ import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload as R import Chainweb.Utils genesisPayload :: ChainwebVersion -> ChainMap PayloadWithOutputs -genesisPayload Mainnet01 = OnChains $ HM.fromList $ concat - [ [ (unsafeChainId 0, MN0.payloadBlock) - , (unsafeChainId 1, MN1.payloadBlock) - , (unsafeChainId 2, MN2.payloadBlock) - , (unsafeChainId 3, MN3.payloadBlock) - , (unsafeChainId 4, MN4.payloadBlock) - , (unsafeChainId 5, MN5.payloadBlock) - , (unsafeChainId 6, MN6.payloadBlock) - , (unsafeChainId 7, MN7.payloadBlock) - , (unsafeChainId 8, MN8.payloadBlock) - , (unsafeChainId 9, MN9.payloadBlock) - ] - , [(unsafeChainId i, MNKAD.payloadBlock) | i <- [10..19]] - ] -genesisPayload Testnet04 = OnChains $ HM.fromList $ concat - [ [(unsafeChainId 0, T04N0.payloadBlock)] - , [(unsafeChainId i, T04NN.payloadBlock) | i <- [1..19]] - ] -genesisPayload Testnet05 = OnChains $ HM.fromList $ concat - [ [(unsafeChainId 0, T05N0.payloadBlock)] - , [(unsafeChainId i, T05NN.payloadBlock) | i <- [1..19]] - ] -genesisPayload Development = OnChains $ HM.fromList $ concat - [ [(unsafeChainId 0, DN0.payloadBlock)] - , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] - ] -genesisPayload RecapDevelopment = OnChains $ HM.fromList $ concat - [ [(unsafeChainId 0, RDN0.payloadBlock)] - , [(unsafeChainId i, RDNN.payloadBlock) | i <- [1..9]] - , [(unsafeChainId i, RDNKAD.payloadBlock) | i <- [10..19]] - ] +genesisPayload v + | _versionCode v == _versionCode Mainnet01 = ChainMap $ HM.fromList $ concat + [ + [ (unsafeChainId 0, MN0.payloadBlock) + , (unsafeChainId 1, MN1.payloadBlock) + , (unsafeChainId 2, MN2.payloadBlock) + , (unsafeChainId 3, MN3.payloadBlock) + , (unsafeChainId 4, MN4.payloadBlock) + , (unsafeChainId 5, MN5.payloadBlock) + , (unsafeChainId 6, MN6.payloadBlock) + , (unsafeChainId 7, MN7.payloadBlock) + , (unsafeChainId 8, MN8.payloadBlock) + , (unsafeChainId 9, MN9.payloadBlock) + ] + , [(unsafeChainId i, MNKAD.payloadBlock) | i <- [10..19]] + ] + | _versionCode v == _versionCode Testnet04 = ChainMap $ HM.fromList $ concat + [ [(unsafeChainId 0, T04N0.payloadBlock)] + , [(unsafeChainId i, T04NN.payloadBlock) | i <- [1..19]] + ] + | _versionCode v == _versionCode Testnet05 = ChainMap $ HM.fromList $ concat + [ [(unsafeChainId 0, T05N0.payloadBlock)] + , [(unsafeChainId i, T05NN.payloadBlock) | i <- [1..19]] + ] + | _versionCode v == _versionCode Development = ChainMap $ HM.fromList $ concat + [ [(unsafeChainId 0, DN0.payloadBlock)] + , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] + ] + | _versionCode v == _versionCode RecapDevelopment = ChainMap $ HM.fromList $ concat + [ [(unsafeChainId 0, RDN0.payloadBlock)] + , [(unsafeChainId i, RDNN.payloadBlock) | i <- [1..9]] + , [(unsafeChainId i, RDNKAD.payloadBlock) | i <- [10..19]] + ] genesisPayload v = error $ "Chainweb.PayloadProvider.Pact.Genesis.genesisPayload: unsupported chainweb version: " <> sshow v diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index add005a8a4..c1ec66c5b7 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -54,6 +54,7 @@ module Chainweb.RestAPI import Control.Monad (guard) import Data.Bool (bool) +import Data.Foldable import GHC.Generics (Generic) @@ -133,22 +134,18 @@ serveSocketTls settings certChain key = runTLSSocket tlsSettings settings -- | Datatype for collectively passing all storage backends to -- functions that run a chainweb server. -- -data ChainwebServerDbs t = ChainwebServerDbs +data ChainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb :: !(Maybe CutDb) - , _chainwebServerBlockHeaderDbs :: ![(ChainId, BlockHeaderDb)] - , _chainwebServerMempools :: ![(ChainId, MempoolBackend t)] - , _chainwebServerPayloads :: ![(ChainId, SomeServer)] + , _chainwebServerBlockHeaderDbs :: !(ChainMap BlockHeaderDb) , _chainwebServerPeerDbs :: ![(NetworkId, PeerDb)] } deriving (Generic) -emptyChainwebServerDbs :: ChainwebServerDbs t +emptyChainwebServerDbs :: ChainwebServerDbs emptyChainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Nothing - , _chainwebServerBlockHeaderDbs = [] - , _chainwebServerMempools = [] - , _chainwebServerPayloads = [] - , _chainwebServerPeerDbs = [] + , _chainwebServerBlockHeaderDbs = mempty + , _chainwebServerPeerDbs = mempty } -- -------------------------------------------------------------------------- -- @@ -198,23 +195,22 @@ chainwebServiceMiddlewares -- Chainweb Peer Server someChainwebServer - :: Show t - => ChainwebConfiguration - -> ChainwebServerDbs t + :: ChainwebConfiguration + -> ChainwebServerDbs -> SomeServer someChainwebServer config dbs = maybe mempty (someCutServer v cutPeerDb) cuts - <> mconcat (snd <$> payloads) + -- <> fold payloads <> someP2pBlockHeaderDbServers v blocks - <> Mempool.someMempoolServers v mempools + -- <> Mempool.someMempoolServers v mempools <> someP2pServers v peers <> someGetConfigServer config where - payloads = _chainwebServerPayloads dbs + -- payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs cuts = _chainwebServerCutDb dbs peers = _chainwebServerPeerDbs dbs - mempools = _chainwebServerMempools dbs + -- mempools = _chainwebServerMempools dbs cutPeerDb = fromJuste $ lookup CutNetwork peers v = _configChainwebVersion config @@ -223,24 +219,23 @@ someChainwebServer config dbs = -- When we have comprehensive testing for the service API we can remove this -- someChainwebServerWithHashesAndSpvApi - :: Show t - => ChainwebConfiguration - -> ChainwebServerDbs t + :: ChainwebConfiguration + -> ChainwebServerDbs -> SomeServer someChainwebServerWithHashesAndSpvApi config dbs = maybe mempty (someCutServer v cutPeerDb) cuts - <> mconcat (snd <$> payloads) + -- <> foldMap snd payloads <> someBlockHeaderDbServers v blocks - <> Mempool.someMempoolServers v mempools + -- <> Mempool.someMempoolServers v mempools <> someP2pServers v peers <> someGetConfigServer config <> maybe mempty (someSpvServers v) cuts where - payloads = _chainwebServerPayloads dbs + -- payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs cuts = _chainwebServerCutDb dbs peers = _chainwebServerPeerDbs dbs - mempools = _chainwebServerMempools dbs + -- mempools = _chainwebServerMempools dbs cutPeerDb = fromJuste $ lookup CutNetwork peers v = _configChainwebVersion config @@ -248,9 +243,8 @@ someChainwebServerWithHashesAndSpvApi config dbs = -- Chainweb P2P API Application chainwebApplication - :: Show t - => ChainwebConfiguration - -> ChainwebServerDbs t + :: ChainwebConfiguration + -> ChainwebServerDbs -> Application chainwebApplication config dbs = chainwebP2pMiddlewares @@ -262,9 +256,8 @@ chainwebApplication config dbs -- When we have comprehensive testing for the service API we can remove this -- chainwebApplicationWithHashesAndSpvApi - :: Show t - => ChainwebConfiguration - -> ChainwebServerDbs t + :: ChainwebConfiguration + -> ChainwebServerDbs -> Application chainwebApplicationWithHashesAndSpvApi config dbs = chainwebP2pMiddlewares @@ -272,40 +265,36 @@ chainwebApplicationWithHashesAndSpvApi config dbs $ someChainwebServerWithHashesAndSpvApi config dbs serveChainwebOnPort - :: Show t - => Port + :: Port -> ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs -> IO () serveChainwebOnPort p c dbs = run (int p) $ chainwebApplication c dbs serveChainweb - :: Show t - => Settings + :: Settings -> ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs -> IO () serveChainweb s c dbs = runSettings s $ chainwebApplication c dbs serveChainwebSocket - :: Show t - => Settings + :: Settings -> Socket -> ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs -> Middleware -> IO () serveChainwebSocket settings sock c dbs m = runSettingsSocket settings sock $ m $ chainwebApplication c dbs serveChainwebSocketTls - :: Show t - => Settings + :: Settings -> X509CertChainPem -> X509KeyPem -> Socket -> ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs -> Middleware -> IO () serveChainwebSocketTls settings certChain key sock c dbs m = @@ -336,10 +325,9 @@ servePeerDbSocketTls settings certChain key sock v nid pdb m = -- Chainweb Service API Application someServiceApiServer - :: Show t - => Logger logger + :: Logger logger => ChainwebVersion - -> ChainwebServerDbs t + -> ChainwebServerDbs -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) @@ -355,19 +343,17 @@ someServiceApiServer v dbs mr (HeaderStream hs) backupEnv pbl = -- GET Cut, Payload, and Headers endpoints <> maybe mempty (someCutGetServer v) cuts - <> mconcat (snd <$> payloads) + -- <> mconcat (snd <$> payloads) <> someBlockHeaderDbServers v blocks <> maybe mempty (someBlockStreamServer v) (bool Nothing cuts hs) where cuts = _chainwebServerCutDb dbs - payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs serviceApiApplication - :: Show t - => Logger logger + :: Logger logger => ChainwebVersion - -> ChainwebServerDbs t + -> ChainwebServerDbs -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) @@ -379,12 +365,11 @@ serviceApiApplication v dbs mr hs be pbl $ someServiceApiServer v dbs mr hs be pbl serveServiceApiSocket - :: Show t - => Logger logger + :: Logger logger => Settings -> Socket -> ChainwebVersion - -> ChainwebServerDbs t + -> ChainwebServerDbs -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index bb6bf9ca09..c3aed128fb 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -114,6 +114,7 @@ module Chainweb.Utils , decodeB64UrlText , encodeB64UrlNoPadding , encodeB64UrlNoPaddingText +, b64UrlNoPaddingPrism , b64UrlNoPaddingTextEncoding , decodeB64UrlNoPaddingText @@ -194,6 +195,7 @@ module Chainweb.Utils -- * Resource Management , concurrentWith , withLink +, withAsyncR , concurrentlies , concurrentlies_ @@ -269,6 +271,7 @@ import Control.Monad.Cont import Control.Monad.IO.Class import Control.Monad.Primitive import Control.Monad.Reader as Reader +import Control.Monad.Trans.Resource import Data.Aeson.Text (encodeToLazyText) import Data.Aeson.Types qualified as Aeson @@ -287,7 +290,7 @@ import Data.ByteString.Lazy qualified as BL import Data.ByteString.Short qualified as BS import Data.Coerce import Data.Csv qualified as CSV -import Data.Decimal +import Data.Decimal hiding (allocate) import Data.DoubleWord import Data.Functor.Of import Data.HashMap.Strict qualified as HM @@ -742,6 +745,11 @@ encodeB64UrlNoPaddingText :: B.ByteString -> T.Text encodeB64UrlNoPaddingText = T.dropWhileEnd (== '=') . T.decodeUtf8 . B64U.encode {-# INLINE encodeB64UrlNoPaddingText #-} +-- | A prism for encoding/decoding unpadded base64-url as/from text. +-- +b64UrlNoPaddingPrism :: Prism' T.Text B.ByteString +b64UrlNoPaddingPrism = prism' encodeB64UrlNoPaddingText decodeB64UrlNoPaddingText + -- | Encode a binary value to a textual base64-url without padding -- representation. -- @@ -1358,6 +1366,10 @@ withLink act = do link a return a +-- | Spawn a thread to do the input action and kill it on exit. +withAsyncR :: IO a -> ResourceT IO (Async a) +withAsyncR act = snd <$> allocate (async act) uninterruptibleCancel + -- | Like `sequence` for IO but concurrent concurrentlies :: forall a. [IO a] -> IO [a] concurrentlies = runConcurrently . traverse Concurrently diff --git a/src/Chainweb/Utils/Rule.hs b/src/Chainweb/Utils/Rule.hs index 9499749dbf..13ab60080e 100644 --- a/src/Chainweb/Utils/Rule.hs +++ b/src/Chainweb/Utils/Rule.hs @@ -5,6 +5,8 @@ {-# language InstanceSigs #-} {-# language LambdaCase #-} {-# language TupleSections #-} +{-# language IncoherentInstances #-} +{-# LANGUAGE StandaloneDeriving #-} module Chainweb.Utils.Rule ( Rule(..) @@ -44,8 +46,9 @@ import GHC.Generics -- latest occurrence. -- data Rule h a = Above (h, a) (Rule h a) | Bottom (h, a) - deriving stock (Eq, Ord, Show, Foldable, Functor, Generic, Generic1, Traversable) + deriving stock (Eq, Ord, Foldable, Functor, Generic, Generic1, Traversable) deriving anyclass (Hashable, NFData) +deriving stock instance (Show h, Show a) => Show (Rule h a) instance Bifunctor Rule where bimap :: (h -> h') -> (a -> a') -> Rule h a -> Rule h' a' @@ -91,7 +94,7 @@ ruleDropWhile _ t = t -- Leftmost fields are "below", rightmost fields are "above". data RuleZipper h a = BetweenZipper (Rule h a) [(h, a)] - deriving Show +deriving stock instance (Show h, Show a) => Show (RuleZipper h a) ruleZipperHere :: RuleZipper h a -> (h, a) ruleZipperHere (BetweenZipper r _) = ruleHead r diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 0278b15e3a..9da0150066 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -94,7 +94,6 @@ module Chainweb.Version , someChainwebVersionVal -- ** Singletons - , Sing(SChainwebVersion) , SChainwebVersion , pattern FromSingChainwebVersion @@ -102,7 +101,7 @@ module Chainweb.Version , PayloadProviderType(..) , HasPayloadProviderType(..) , payloadProviderTypeForChain - , Sing(SMinimalProvider, SPactProvider, SEvmProvider) + , Sing(..) -- * HasChainwebVersion , HasChainwebVersion(..) @@ -139,7 +138,8 @@ module Chainweb.Version , indexByForkHeights , latestBehaviorAt , onAllChains - , onAllChainsM + , tabulateChains + , tabulateChainsM , domainAddr2PeerInfo -- * Internal. Don't use. Exported only for testing @@ -398,11 +398,6 @@ data VersionQuirks = VersionQuirks deriving stock (Show, Eq, Ord, Generic) deriving anyclass (NFData) -noQuirks :: VersionQuirks -noQuirks = VersionQuirks - { _quirkGasFees = AllChains HM.empty - } - -- -------------------------------------------------------------------------- -- -- Payload Provider Type @@ -772,7 +767,7 @@ indexByForkHeights :: ChainwebVersion -> [(Fork, ChainMap a)] -> ChainMap (HashMap BlockHeight a) -indexByForkHeights v = OnChains . foldl' go (HM.empty <$ HS.toMap (chainIds v)) +indexByForkHeights v = ChainMap . foldl' go (HM.empty <$ HS.toMap (chainIds v)) where conflictError fork h = error $ "conflicting behavior at block height " <> show h <> " when adding behavior for fork " <> show fork @@ -800,13 +795,21 @@ latestBehaviorAt v = foldlOf' behaviorChanges max 0 v + 1 , versionGraphs . to ruleHead . _1 ] -onAllChains :: ChainwebVersion -> (ChainId -> a) -> ChainMap a -onAllChains v f = runIdentity $ onAllChainsM v (Identity . f) +onAllChains :: ChainwebVersion -> a -> ChainMap a +onAllChains v a = tabulateChains v (\_ -> a) + +tabulateChains :: ChainwebVersion -> (ChainId -> a) -> ChainMap a +tabulateChains v f = runIdentity $ tabulateChainsM v (Identity . f) -- | Easy construction of a `ChainMap` with entries for every chain -- in a `ChainwebVersion`. -onAllChainsM :: Applicative m => ChainwebVersion -> (ChainId -> m a) -> m (ChainMap a) -onAllChainsM v f = OnChains <$> +tabulateChainsM :: Applicative m => ChainwebVersion -> (ChainId -> m a) -> m (ChainMap a) +tabulateChainsM v f = ChainMap <$> HM.traverseWithKey (\cid () -> f cid) (HS.toMap (chainIds v)) + +noQuirks :: ChainwebVersion -> VersionQuirks +noQuirks v = VersionQuirks + { _quirkGasFees = onAllChains v HM.empty + } diff --git a/src/Chainweb/Version/Development.hs b/src/Chainweb/Version/Development.hs index f2eefcc292..854e920b5f 100644 --- a/src/Chainweb/Version/Development.hs +++ b/src/Chainweb/Version/Development.hs @@ -28,17 +28,17 @@ devnet :: ChainwebVersion devnet = ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000002 , _versionName = ChainwebVersionName "development" - , _versionForks = tabulateHashMap $ \case - _ -> AllChains ForkAtGenesis - , _versionUpgrades = AllChains mempty + , _versionForks = tabulateHashMap $ + \_ -> onAllChains devnet ForkAtGenesis + , _versionUpgrades = onAllChains devnet mempty , _versionGraphs = Bottom (minBound, twentyChainGraph) , _versionBlockDelay = BlockDelay 30_000_000 , _versionWindow = WindowWidth 120 , _versionHeaderBaseSizeBytes = 318 - 110 , _versionBootstraps = [] , _versionGenesis = VersionGenesis - { _genesisBlockTarget = AllChains $ HashTarget (maxBound `div` 100_000) - , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] + { _genesisBlockTarget = onAllChains devnet $ HashTarget (maxBound `div` 100_000) + , _genesisTime = onAllChains devnet $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] , _genesisBlockPayload = onChains [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") , (unsafeChainId 1, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") @@ -75,9 +75,9 @@ devnet = ChainwebVersion { _disablePeerValidation = True , _disableMempoolSync = False } - , _versionVerifierPluginNames = AllChains $ Bottom + , _versionVerifierPluginNames = onAllChains devnet $ Bottom (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) - , _versionQuirks = noQuirks + , _versionQuirks = noQuirks devnet , _versionServiceDate = Nothing - , _versionPayloadProviderTypes = AllChains PactProvider + , _versionPayloadProviderTypes = onAllChains devnet PactProvider } diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 413893e2f5..485f9fbe50 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -34,16 +34,16 @@ evmDevnet = ChainwebVersion , _versionForks = tabulateHashMap $ \case -- TODO: for now, Pact 5 is never enabled on EVM devnet. -- this will change as it stabilizes. - Pact5Fork -> AllChains ForkNever - _ -> AllChains ForkAtGenesis - , _versionUpgrades = AllChains mempty + Pact5Fork -> onAllChains evmDevnet ForkNever + _ -> onAllChains evmDevnet ForkAtGenesis + , _versionUpgrades = onAllChains evmDevnet mempty , _versionGraphs = Bottom (minBound, twentyChainGraph) , _versionBlockDelay = BlockDelay 30_000_000 , _versionWindow = WindowWidth 120 , _versionHeaderBaseSizeBytes = 318 - 110 , _versionBootstraps = [] , _versionGenesis = VersionGenesis - { _genesisBlockTarget = AllChains $ HashTarget (maxBound `div` 500_000) + { _genesisBlockTarget = onAllChains evmDevnet $ HashTarget (maxBound `div` 500_000) , _genesisTime = onChains $ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) : (unsafeChainId 1, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) @@ -84,9 +84,9 @@ evmDevnet = ChainwebVersion { _disablePeerValidation = True , _disableMempoolSync = False } - , _versionVerifierPluginNames = AllChains $ Bottom + , _versionVerifierPluginNames = onAllChains evmDevnet $ Bottom (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) - , _versionQuirks = noQuirks + , _versionQuirks = noQuirks evmDevnet , _versionServiceDate = Nothing -- FIXME make this safe for graph changes diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index ca63c92655..8a282cc2ef 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -71,7 +71,7 @@ mainnet = ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000005 , _versionName = ChainwebVersionName "mainnet01" , _versionForks = tabulateHashMap $ \case - SlowEpoch -> AllChains (ForkAtBlockHeight $ BlockHeight 80_000) + SlowEpoch -> onAllChains mainnet (ForkAtBlockHeight $ BlockHeight 80_000) Vuln797Fix -> onChains $ [ (unsafeChainId 0, ForkAtBlockHeight $ BlockHeight 121_452) -- 2019-12-10T21:00:00.0 , (unsafeChainId 1, ForkAtBlockHeight $ BlockHeight 121_452) @@ -96,37 +96,37 @@ mainnet = ChainwebVersion , (unsafeChainId 8, ForkAtBlockHeight $ BlockHeight 140_808) , (unsafeChainId 9, ForkAtBlockHeight $ BlockHeight 140_808) ] <> [(unsafeChainId i, ForkAtGenesis) | i <- [10..19]] - PactBackCompat_v16 -> AllChains (ForkAtBlockHeight $ BlockHeight 328_000) - ModuleNameFix -> AllChains (ForkAtBlockHeight $ BlockHeight 448_501) - SkipTxTimingValidation -> AllChains (ForkAtBlockHeight $ BlockHeight 449_940) - OldTargetGuard -> AllChains (ForkAtBlockHeight $ BlockHeight 452_820) -- ~ 2020-04-04T00:00:00Z - SkipFeatureFlagValidation -> AllChains (ForkAtBlockHeight $ BlockHeight 530_500) -- ~ 2020-05-01T00:00:xxZ - ModuleNameFix2 -> AllChains (ForkAtBlockHeight $ BlockHeight 752_214) - OldDAGuard -> AllChains (ForkAtBlockHeight $ BlockHeight 771_414) -- ~ 2020-07-23 16:00:00 - PactEvents -> AllChains (ForkAtBlockHeight $ BlockHeight 1_138_000) - SPVBridge -> AllChains (ForkAtBlockHeight $ BlockHeight 1_275_000) - Pact4Coin3 -> AllChains (ForkAtBlockHeight $ BlockHeight 1_722_500) -- 2021-06-19T03:34:05+00:00 - EnforceKeysetFormats -> AllChains (ForkAtBlockHeight $ BlockHeight 2_162_000) -- 2022-01-17T17:51:12 - Pact42 -> AllChains (ForkAtBlockHeight $ BlockHeight 2_334_500) -- 2022-01-17T17:51:12+00:00 - CheckTxHash -> AllChains (ForkAtBlockHeight $ BlockHeight 2_349_800) -- 2022-01-23T02:53:38 - Chainweb213Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 2_447_315) -- 2022-02-26T00:00:00+00:00 - Chainweb214Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 2_605_663) -- 2022-04-22T00:00:00+00:00 - Chainweb215Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 2_766_630) -- 2022-06-17T00:00:00+00:00 - Pact44NewTrans -> AllChains (ForkAtBlockHeight $ BlockHeight 2_939_323) -- Todo: add date - Chainweb216Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 2_988_324) -- 2022-09-02T00:00:00+00:00 - Chainweb217Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 3_250_348) -- 2022-12-02T00:00:00+00:00 - Chainweb218Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 3_512_363) -- 2023-03-03 00:00:00+00:00 - Chainweb219Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 3_774_423) -- 2023-06-02 00:00:00+00:00 - Chainweb220Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 4_056_499) -- 2023-09-08 00:00:00+00:00 - Chainweb221Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 4_177_889) -- 2023-10-20 00:00:00+00:00 - Chainweb222Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 4_335_753) -- 2023-12-14 00:00:00+00:00 - Chainweb223Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 4_577_530) -- 2024-03-07 00:00:00+00:00 - Chainweb224Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 4_819_246) -- 2024-05-30 00:00:00+00:00 - Chainweb225Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 5_060_924) -- 2024-08-22 00:00:00+00:00 - Chainweb226Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 5_302_559) -- 2024-11-14 00:00:00+00:00 - Pact5Fork -> AllChains (ForkAtBlockHeight $ BlockHeight 5_555_698) -- 2025-02-10 00:00:00+00:00 - Chainweb228Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 5_659_280) -- 2025-03-18 00:00:00+00:00 - Chainweb229Pact -> AllChains ForkNever + PactBackCompat_v16 -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 328_000 + ModuleNameFix -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 448_501 + SkipTxTimingValidation -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 449_940 + OldTargetGuard -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 452_820 -- ~ 2020-04-04T00:00:00Z + SkipFeatureFlagValidation -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 530_500 -- ~ 2020-05-01T00:00:xxZ + ModuleNameFix2 -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 752_214 + OldDAGuard -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 771_414 -- ~ 2020-07-23 16:00:00 + PactEvents -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 1_138_000 + SPVBridge -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 1_275_000 + Pact4Coin3 -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 1_722_500 -- 2021-06-19T03:34:05+00:00 + EnforceKeysetFormats -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_162_000 -- 2022-01-17T17:51:12 + Pact42 -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_334_500 -- 2022-01-17T17:51:12+00:00 + CheckTxHash -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_349_800 -- 2022-01-23T02:53:38 + Chainweb213Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_447_315 -- 2022-02-26T00:00:00+00:00 + Chainweb214Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_605_663 -- 2022-04-22T00:00:00+00:00 + Chainweb215Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_766_630 -- 2022-06-17T00:00:00+00:00 + Pact44NewTrans -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_939_323 -- Todo: add date + Chainweb216Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_988_324 -- 2022-09-02T00:00:00+00:00 + Chainweb217Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 3_250_348 -- 2022-12-02T00:00:00+00:00 + Chainweb218Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 3_512_363 -- 2023-03-03 00:00:00+00:00 + Chainweb219Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 3_774_423 -- 2023-06-02 00:00:00+00:00 + Chainweb220Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_056_499 -- 2023-09-08 00:00:00+00:00 + Chainweb221Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_177_889 -- 2023-10-20 00:00:00+00:00 + Chainweb222Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_335_753 -- 2023-12-14 00:00:00+00:00 + Chainweb223Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_577_530 -- 2024-03-07 00:00:00+00:00 + Chainweb224Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_819_246 -- 2024-05-30 00:00:00+00:00 + Chainweb225Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_060_924 -- 2024-08-22 00:00:00+00:00 + Chainweb226Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_302_559 -- 2024-11-14 00:00:00+00:00 + Pact5Fork -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_555_698 -- 2025-02-10 00:00:00+00:00 + Chainweb228Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_659_280 -- 2025-03-18 00:00:00+00:00 + Chainweb229Pact -> onAllChains mainnet $ ForkNever , _versionGraphs = (to20ChainsMainnet, twentyChainGraph) `Above` @@ -139,11 +139,11 @@ mainnet = ChainwebVersion Bottom (minBound, Nothing) , _versionBootstraps = domainAddr2PeerInfo mainnetBootstrapHosts , _versionGenesis = VersionGenesis - { _genesisBlockTarget = OnChains $ HM.fromList $ concat + { _genesisBlockTarget = ChainMap $ HM.fromList $ concat [ [(unsafeChainId i, maxTarget) | i <- [0..9]] , [(unsafeChainId i, mainnet20InitialHashTarget) | i <- [10..19]] ] - , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-10-30T00:01:00.0 |] + , _genesisTime = onAllChains mainnet $ BlockCreationTime [timeMicrosQQ| 2019-10-30T00:01:00.0 |] , _genesisBlockPayload = onChains [ ( unsafeChainId 0, unsafeFromText "k1H3DsInAPvJ0W_zPxnrpkeSNdPUT0S9U8bqDLG739o") , ( unsafeChainId 1, unsafeFromText "kClp_Tw7keCLXMfaCyjH-gToAGmLvRQqiNRmhWUCbxs") @@ -177,7 +177,7 @@ mainnet = ChainwebVersion { _disablePeerValidation = False , _disableMempoolSync = False } - , _versionVerifierPluginNames = AllChains $ + , _versionVerifierPluginNames = onAllChains mainnet $ (4_577_530, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) `Above` Bottom (minBound, mempty) , _versionQuirks = VersionQuirks @@ -187,5 +187,5 @@ mainnet = ChainwebVersion ] } , _versionServiceDate = Just "2025-04-30T00:00:00Z" - , _versionPayloadProviderTypes = AllChains PactProvider + , _versionPayloadProviderTypes = onAllChains mainnet PactProvider } diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index 58e4faac44..b89ad315bd 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -34,40 +34,40 @@ recapDevnet = ChainwebVersion , _versionName = ChainwebVersionName "recap-development" , _versionForks = tabulateHashMap $ \case - SlowEpoch -> AllChains ForkAtGenesis - Vuln797Fix -> AllChains ForkAtGenesis - CoinV2 -> AllChains ForkAtGenesis - PactBackCompat_v16 -> AllChains ForkAtGenesis - SkipTxTimingValidation -> AllChains ForkAtGenesis - OldTargetGuard -> AllChains ForkAtGenesis - SkipFeatureFlagValidation -> AllChains ForkAtGenesis - ModuleNameFix -> AllChains ForkAtGenesis - ModuleNameFix2 -> AllChains ForkAtGenesis - OldDAGuard -> AllChains ForkAtGenesis - PactEvents -> AllChains ForkAtGenesis - SPVBridge -> AllChains ForkAtGenesis - Pact4Coin3 -> AllChains ForkAtGenesis - EnforceKeysetFormats -> AllChains ForkAtGenesis - Pact42 -> AllChains ForkAtGenesis - CheckTxHash -> AllChains ForkAtGenesis - Chainweb213Pact -> AllChains ForkAtGenesis - Chainweb214Pact -> AllChains ForkAtGenesis - Chainweb215Pact -> AllChains ForkAtGenesis - Pact44NewTrans -> AllChains ForkAtGenesis - Chainweb216Pact -> AllChains ForkAtGenesis - Chainweb217Pact -> AllChains ForkAtGenesis - Chainweb218Pact -> AllChains ForkAtGenesis - Chainweb219Pact -> AllChains ForkAtGenesis - Chainweb220Pact -> AllChains ForkAtGenesis - Chainweb221Pact -> AllChains ForkAtGenesis - Chainweb222Pact -> AllChains ForkAtGenesis - Chainweb223Pact -> AllChains ForkAtGenesis - Chainweb224Pact -> AllChains ForkAtGenesis - Chainweb225Pact -> AllChains ForkAtGenesis - Chainweb226Pact -> AllChains ForkAtGenesis - Pact5Fork -> AllChains $ ForkAtGenesis - Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 10 - Chainweb229Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 + SlowEpoch -> onAllChains recapDevnet ForkAtGenesis + Vuln797Fix -> onAllChains recapDevnet ForkAtGenesis + CoinV2 -> onAllChains recapDevnet ForkAtGenesis + PactBackCompat_v16 -> onAllChains recapDevnet ForkAtGenesis + SkipTxTimingValidation -> onAllChains recapDevnet ForkAtGenesis + OldTargetGuard -> onAllChains recapDevnet ForkAtGenesis + SkipFeatureFlagValidation -> onAllChains recapDevnet ForkAtGenesis + ModuleNameFix -> onAllChains recapDevnet ForkAtGenesis + ModuleNameFix2 -> onAllChains recapDevnet ForkAtGenesis + OldDAGuard -> onAllChains recapDevnet ForkAtGenesis + PactEvents -> onAllChains recapDevnet ForkAtGenesis + SPVBridge -> onAllChains recapDevnet ForkAtGenesis + Pact4Coin3 -> onAllChains recapDevnet ForkAtGenesis + EnforceKeysetFormats -> onAllChains recapDevnet ForkAtGenesis + Pact42 -> onAllChains recapDevnet ForkAtGenesis + CheckTxHash -> onAllChains recapDevnet ForkAtGenesis + Chainweb213Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb214Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb215Pact -> onAllChains recapDevnet ForkAtGenesis + Pact44NewTrans -> onAllChains recapDevnet ForkAtGenesis + Chainweb216Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb217Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb218Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb219Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb220Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb221Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb222Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb223Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb224Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb225Pact -> onAllChains recapDevnet ForkAtGenesis + Chainweb226Pact -> onAllChains recapDevnet ForkAtGenesis + Pact5Fork -> onAllChains recapDevnet $ ForkAtGenesis + Chainweb228Pact -> onAllChains recapDevnet $ ForkAtBlockHeight $ BlockHeight 10 + Chainweb229Pact -> onAllChains recapDevnet $ ForkAtBlockHeight $ BlockHeight 20 , _versionUpgrades = onChains [] @@ -84,7 +84,7 @@ recapDevnet = ChainwebVersion [ [(unsafeChainId i, HashTarget $ maxBound `div` 100_000) | i <- [0..9]] , [(unsafeChainId i, HashTarget 0x0000088f99632cadf39b0db7655be62cb7dbc84ebbd9a90e5b5756d3e7d9196c) | i <- [10..19]] ] - , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] + , _genesisTime = onAllChains recapDevnet $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] , _genesisBlockPayload = onChains [ (unsafeChainId 0, unsafeFromText "5TWTF5R6Vc85vWHqcklTY91ljkV6mJ1wYfDJShooTCw") , (unsafeChainId 1, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") @@ -119,10 +119,10 @@ recapDevnet = ChainwebVersion { _disablePeerValidation = True , _disableMempoolSync = False } - , _versionVerifierPluginNames = AllChains $ + , _versionVerifierPluginNames = onAllChains recapDevnet $ (600, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) `Above` Bottom (minBound, mempty) - , _versionQuirks = noQuirks + , _versionQuirks = noQuirks recapDevnet , _versionServiceDate = Nothing - , _versionPayloadProviderTypes = AllChains PactProvider + , _versionPayloadProviderTypes = onAllChains recapDevnet PactProvider } diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index d8c0daa742..5e753dd8f2 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -85,8 +85,7 @@ validateVersion v = do evaluate (rnf v) let hasAllChains :: ChainMap a -> Bool - hasAllChains (AllChains _) = True - hasAllChains (OnChains m) = HS.fromMap (void m) == chainIds v + hasAllChains (ChainMap m) = HS.fromMap (void m) == chainIds v errors = concat [ [ "validateVersion: version does not have heights for all forks" | not (HS.fromMap (void $ _versionForks v) == HS.fromList [minBound :: Fork .. maxBound :: Fork]) ] diff --git a/src/Chainweb/Version/Testnet04.hs b/src/Chainweb/Version/Testnet04.hs index b7c3de1543..e88be4ae9a 100644 --- a/src/Chainweb/Version/Testnet04.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -79,43 +79,43 @@ testnet04 = ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000007 , _versionName = ChainwebVersionName "testnet04" , _versionForks = tabulateHashMap $ \case - SlowEpoch -> AllChains ForkAtGenesis - Vuln797Fix -> AllChains ForkAtGenesis + SlowEpoch -> onAllChains testnet04 ForkAtGenesis + Vuln797Fix -> onAllChains testnet04 ForkAtGenesis CoinV2 -> onChains $ concat [ [(unsafeChainId i, ForkAtBlockHeight $ BlockHeight 1) | i <- [0..9]] , [(unsafeChainId i, ForkAtBlockHeight $ BlockHeight 337_000) | i <- [10..19]] ] - PactBackCompat_v16 -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - ModuleNameFix -> AllChains $ ForkAtBlockHeight $ BlockHeight 2 - SkipTxTimingValidation -> AllChains $ ForkAtBlockHeight $ BlockHeight 1 - OldTargetGuard -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - SkipFeatureFlagValidation -> AllChains $ ForkAtBlockHeight $ BlockHeight 0 - ModuleNameFix2 -> AllChains $ ForkAtBlockHeight $ BlockHeight 289_966 -- ~ 2020-07-13 - OldDAGuard -> AllChains $ ForkAtBlockHeight $ BlockHeight 318_204 -- ~ 2020-07-23 16:00:00 - PactEvents -> AllChains $ ForkAtBlockHeight $ BlockHeight 660_000 - SPVBridge -> AllChains $ ForkAtBlockHeight $ BlockHeight 820_000 -- 2021-01-14T17:12:02 - Pact4Coin3 -> AllChains $ ForkAtBlockHeight $ BlockHeight 1_261_000 -- 2021-06-17T15:54:14 - EnforceKeysetFormats -> AllChains $ ForkAtBlockHeight $ BlockHeight 1_701_000 -- 2021-11-18T17:54:36 - Pact42 -> AllChains $ ForkAtBlockHeight $ BlockHeight 1_862_000 -- 2021-06-19T03:34:05 - CheckTxHash -> AllChains $ ForkAtBlockHeight $ BlockHeight 1_889_000 -- 2022-01-24T04:19:24 - Chainweb213Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 1_974_556 -- 2022-02-25 00:00:00 - Chainweb214Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 2_134_331 -- 2022-04-21T12:00:00Z - Chainweb215Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 2_295_437 -- 2022-06-16T12:00:00+00:00 - Pact44NewTrans -> AllChains $ ForkAtBlockHeight $ BlockHeight 2_500_369 -- Todo: add date - Chainweb216Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 2_516_739 -- 2022-09-01 12:00:00+00:00 - Chainweb217Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 2_777_367 -- 2022-12-01 12:00:00+00:00 - Chainweb218Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 3_038_343 -- 2023-03-02 12:00:00+00:00 - Chainweb219Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 3_299_753 -- 2023-06-01 12:00:00+00:00 - Chainweb220Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 3_580_964 -- 2023-09-08 12:00:00+00:00 - Chainweb221Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 3_702_250 -- 2023-10-19 12:00:00+00:00 - Chainweb222Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 3_859_808 -- 2023-12-13 12:00:00+00:00 - Chainweb223Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 4_100_681 -- 2024-03-06 12:00:00+00:00 - Chainweb224Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 4_333_587 -- 2024-05-29 12:00:00+00:00 - Chainweb225Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 4_575_072 -- 2024-08-21 12:00:00+00:00 - Chainweb226Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 4_816_925 -- 2024-11-13 12:00:00+00:00 - Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_058_738 -- 2025-02-05 12:00:00+00:00 - Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_155_146 -- 2025-03-11 00:00:00+00:00 - Chainweb229Pact -> AllChains ForkNever + PactBackCompat_v16 -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 0 + ModuleNameFix -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2 + SkipTxTimingValidation -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1 + OldTargetGuard -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 0 + SkipFeatureFlagValidation -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 0 + ModuleNameFix2 -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 289_966 -- ~ 2020-07-13 + OldDAGuard -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 318_204 -- ~ 2020-07-23 16:00:00 + PactEvents -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 660_000 + SPVBridge -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 820_000 -- 2021-01-14T17:12:02 + Pact4Coin3 -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_261_000 -- 2021-06-17T15:54:14 + EnforceKeysetFormats -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_701_000 -- 2021-11-18T17:54:36 + Pact42 -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_862_000 -- 2021-06-19T03:34:05 + CheckTxHash -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_889_000 -- 2022-01-24T04:19:24 + Chainweb213Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_974_556 -- 2022-02-25 00:00:00 + Chainweb214Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_134_331 -- 2022-04-21T12:00:00Z + Chainweb215Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_295_437 -- 2022-06-16T12:00:00+00:00 + Pact44NewTrans -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_500_369 -- Todo: add date + Chainweb216Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_516_739 -- 2022-09-01 12:00:00+00:00 + Chainweb217Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_777_367 -- 2022-12-01 12:00:00+00:00 + Chainweb218Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_038_343 -- 2023-03-02 12:00:00+00:00 + Chainweb219Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_299_753 -- 2023-06-01 12:00:00+00:00 + Chainweb220Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_580_964 -- 2023-09-08 12:00:00+00:00 + Chainweb221Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_702_250 -- 2023-10-19 12:00:00+00:00 + Chainweb222Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_859_808 -- 2023-12-13 12:00:00+00:00 + Chainweb223Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 4_100_681 -- 2024-03-06 12:00:00+00:00 + Chainweb224Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 4_333_587 -- 2024-05-29 12:00:00+00:00 + Chainweb225Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 4_575_072 -- 2024-08-21 12:00:00+00:00 + Chainweb226Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 4_816_925 -- 2024-11-13 12:00:00+00:00 + Pact5Fork -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 5_058_738 -- 2025-02-05 12:00:00+00:00 + Chainweb228Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 5_155_146 -- 2025-03-11 00:00:00+00:00 + Chainweb229Pact -> onAllChains testnet04 ForkNever , _versionGraphs = (to20ChainsTestnet, twentyChainGraph) `Above` @@ -128,11 +128,11 @@ testnet04 = ChainwebVersion Bottom (minBound, Nothing) , _versionBootstraps = domainAddr2PeerInfo testnet04BootstrapHosts , _versionGenesis = VersionGenesis - { _genesisBlockTarget = OnChains $ HM.fromList $ concat + { _genesisBlockTarget = ChainMap $ HM.fromList $ concat [ [(unsafeChainId i, maxTarget) | i <- [0..9]] , [(unsafeChainId i, testnet20InitialHashTarget) | i <- [10..19]] ] - , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] + , _genesisTime = onAllChains testnet04 $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] , _genesisBlockPayload = onChains [ (unsafeChainId 0, unsafeFromText "nfYm3e_fk2ICws0Uowos6OMuqfFg5Nrl_zqXVx9v_ZQ") , (unsafeChainId 1, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") @@ -166,7 +166,7 @@ testnet04 = ChainwebVersion { _disablePeerValidation = False , _disableMempoolSync = False } - , _versionVerifierPluginNames = AllChains $ (4_100_681, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) `Above` + , _versionVerifierPluginNames = onAllChains testnet04 $ (4_100_681, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) `Above` Bottom (minBound, mempty) , _versionQuirks = VersionQuirks { _quirkGasFees = onChains @@ -175,5 +175,5 @@ testnet04 = ChainwebVersion ] } , _versionServiceDate = Just "2025-04-30T00:00:00Z" - , _versionPayloadProviderTypes = AllChains PactProvider + , _versionPayloadProviderTypes = onAllChains testnet04 PactProvider } diff --git a/src/Chainweb/Version/Testnet05.hs b/src/Chainweb/Version/Testnet05.hs index 1f6107ed8c..aa33cb8288 100644 --- a/src/Chainweb/Version/Testnet05.hs +++ b/src/Chainweb/Version/Testnet05.hs @@ -31,40 +31,40 @@ testnet05 = ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000009 , _versionName = ChainwebVersionName "testnet05" , _versionForks = tabulateHashMap $ \case - SlowEpoch -> AllChains ForkAtGenesis - Vuln797Fix -> AllChains ForkAtGenesis - CoinV2 -> AllChains ForkAtGenesis - PactBackCompat_v16 -> AllChains ForkAtGenesis - ModuleNameFix -> AllChains ForkAtGenesis - SkipTxTimingValidation -> AllChains ForkAtGenesis - OldTargetGuard -> AllChains ForkAtGenesis - SkipFeatureFlagValidation -> AllChains ForkAtGenesis - ModuleNameFix2 -> AllChains ForkAtGenesis - OldDAGuard -> AllChains ForkAtGenesis - PactEvents -> AllChains ForkAtGenesis - SPVBridge -> AllChains ForkAtGenesis - Pact4Coin3 -> AllChains ForkAtGenesis - EnforceKeysetFormats -> AllChains ForkAtGenesis - Pact42 -> AllChains ForkAtGenesis - CheckTxHash -> AllChains ForkAtGenesis - Chainweb213Pact -> AllChains ForkAtGenesis - Chainweb214Pact -> AllChains ForkAtGenesis - Chainweb215Pact -> AllChains ForkAtGenesis - Pact44NewTrans -> AllChains ForkAtGenesis - Chainweb216Pact -> AllChains ForkAtGenesis - Chainweb217Pact -> AllChains ForkAtGenesis - Chainweb218Pact -> AllChains ForkAtGenesis - Chainweb219Pact -> AllChains ForkAtGenesis - Chainweb220Pact -> AllChains ForkAtGenesis - Chainweb221Pact -> AllChains ForkAtGenesis - Chainweb222Pact -> AllChains ForkAtGenesis - Chainweb223Pact -> AllChains ForkAtGenesis - Chainweb224Pact -> AllChains ForkAtGenesis - Chainweb225Pact -> AllChains ForkAtGenesis - Chainweb226Pact -> AllChains ForkAtGenesis - Pact5Fork -> AllChains ForkAtGenesis - Chainweb228Pact -> AllChains ForkAtGenesis - Chainweb229Pact -> AllChains ForkNever + SlowEpoch -> onAllChains testnet05 ForkAtGenesis + Vuln797Fix -> onAllChains testnet05 ForkAtGenesis + CoinV2 -> onAllChains testnet05 ForkAtGenesis + PactBackCompat_v16 -> onAllChains testnet05 ForkAtGenesis + ModuleNameFix -> onAllChains testnet05 ForkAtGenesis + SkipTxTimingValidation -> onAllChains testnet05 ForkAtGenesis + OldTargetGuard -> onAllChains testnet05 ForkAtGenesis + SkipFeatureFlagValidation -> onAllChains testnet05 ForkAtGenesis + ModuleNameFix2 -> onAllChains testnet05 ForkAtGenesis + OldDAGuard -> onAllChains testnet05 ForkAtGenesis + PactEvents -> onAllChains testnet05 ForkAtGenesis + SPVBridge -> onAllChains testnet05 ForkAtGenesis + Pact4Coin3 -> onAllChains testnet05 ForkAtGenesis + EnforceKeysetFormats -> onAllChains testnet05 ForkAtGenesis + Pact42 -> onAllChains testnet05 ForkAtGenesis + CheckTxHash -> onAllChains testnet05 ForkAtGenesis + Chainweb213Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb214Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb215Pact -> onAllChains testnet05 ForkAtGenesis + Pact44NewTrans -> onAllChains testnet05 ForkAtGenesis + Chainweb216Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb217Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb218Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb219Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb220Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb221Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb222Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb223Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb224Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb225Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb226Pact -> onAllChains testnet05 ForkAtGenesis + Pact5Fork -> onAllChains testnet05 ForkAtGenesis + Chainweb228Pact -> onAllChains testnet05 ForkAtGenesis + Chainweb229Pact -> onAllChains testnet05 ForkNever , _versionGraphs = Bottom (minBound, twentyChainGraph) @@ -75,10 +75,10 @@ testnet05 = ChainwebVersion Bottom (minBound, Just 180_000) , _versionBootstraps = domainAddr2PeerInfo testnet05BootstrapHosts , _versionGenesis = VersionGenesis - { _genesisBlockTarget = OnChains $ HM.fromList $ concat + { _genesisBlockTarget = ChainMap $ HM.fromList $ concat [ [(unsafeChainId i, maxTarget) | i <- [0..19]] ] - , _genesisTime = AllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] + , _genesisTime = onAllChains testnet05 $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] , _genesisBlockPayload = onChains [ (unsafeChainId 0, unsafeFromText "Gbu_Tf-PJP2VyptN3m0AnTsXRfiFpnxV8iWZcimPZq4") , (unsafeChainId 1, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") @@ -102,7 +102,7 @@ testnet05 = ChainwebVersion , (unsafeChainId 19, unsafeFromText "c33AN8j0AKMwQO9BoCGCtinIQT_3JWyNc-fsqdt41Go") ] } - , _versionUpgrades = AllChains mempty + , _versionUpgrades = onAllChains testnet05 mempty , _versionCheats = VersionCheats { _disablePow = False , _fakeFirstEpochStart = False @@ -112,9 +112,9 @@ testnet05 = ChainwebVersion { _disablePeerValidation = False , _disableMempoolSync = False } - , _versionVerifierPluginNames = AllChains $ + , _versionVerifierPluginNames = onAllChains testnet05 $ Bottom (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) - , _versionQuirks = noQuirks + , _versionQuirks = noQuirks testnet05 , _versionServiceDate = Nothing - , _versionPayloadProviderTypes = AllChains PactProvider + , _versionPayloadProviderTypes = onAllChains testnet05 PactProvider } diff --git a/src/Chainweb/WebBlockHeaderDB.hs b/src/Chainweb/WebBlockHeaderDB.hs index 7c15c2ce6e..919ada2acf 100644 --- a/src/Chainweb/WebBlockHeaderDB.hs +++ b/src/Chainweb/WebBlockHeaderDB.hs @@ -127,7 +127,7 @@ initWebBlockHeaderDb -> ChainwebVersion -> IO WebBlockHeaderDb initWebBlockHeaderDb db v = WebBlockHeaderDb - <$!> onAllChainsM v (\cid -> initBlockHeaderDb (conf cid db)) + <$!> tabulateChainsM v (\cid -> initBlockHeaderDb (conf cid db)) <*> pure v where conf cid = Configuration (genesisBlockHeader v cid) diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index 68975c2aea..5f19f67f98 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -828,7 +828,6 @@ instance Arbitrary CoordinationConfig where arbitrary = CoordinationConfig <$> arbitrary <*> arbitrary - <*> arbitrary instance Arbitrary NodeMiningConfig where arbitrary = NodeMiningConfig diff --git a/test/lib/Chainweb/Test/RestAPI/Utils.hs b/test/lib/Chainweb/Test/RestAPI/Utils.hs index f5acd235bf..de56c08bb6 100644 --- a/test/lib/Chainweb/Test/RestAPI/Utils.hs +++ b/test/lib/Chainweb/Test/RestAPI/Utils.hs @@ -58,13 +58,9 @@ import Chainweb.Test.Utils import qualified Pact.JSON.Encode as J import qualified Pact.Core.Command.Server as Pact import qualified Pact.Core.Command.Types as Pact -import qualified Data.Aeson as Aeson -import qualified Data.Text.Lazy.Encoding as TL -import qualified Data.Text.Lazy as TL import qualified Pact.Core.Hash as Pact import qualified Pact.Core.Command.Client as Pact import qualified Pact.Core.Errors as Pact -import Chainweb.Utils -- ------------------------------------------------------------------ -- -- Defaults @@ -127,9 +123,7 @@ local local v sid cenv cmd = do Just cr <- preview _LocalResultLegacy <$> localWithQueryParams v sid cenv Nothing Nothing Nothing cmd - let !result = fromJuste $ - Aeson.decode (TL.encodeUtf8 $ TL.fromStrict $ J.getJsonText cr) - pure result + return cr -- | Request an SPV proof using exponential retry logic -- diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index fffd4bb878..7e8b5d4bfc 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -132,7 +132,7 @@ testVersionTemplate v = v & versionWindow .~ WindowWidth 120 & versionMaxBlockGasLimit .~ Bottom (minBound, Just 2_000_000) & versionBootstraps .~ [testBootstrapPeerInfos] - & versionVerifierPluginNames .~ AllChains (Bottom (minBound, mempty)) + & versionVerifierPluginNames .~ onAllChains v (Bottom (minBound, mempty)) & versionServiceDate .~ Nothing -- | A test version without Pact or PoW, with only one chain graph. @@ -153,13 +153,13 @@ barebonesTestVersion g = buildTestVersion $ \v -> , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = AllChains $ _payloadWithOutputsPayloadHash emptyPayload - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch + { _genesisBlockPayload = onAllChains v $ _payloadWithOutputsPayloadHash emptyPayload + , _genesisBlockTarget = onAllChains v maxTarget + , _genesisTime = onAllChains v $ BlockCreationTime epoch } - & versionForks .~ HM.fromList [ (f, AllChains ForkAtGenesis) | f <- [minBound..maxBound] ] - & versionQuirks .~ noQuirks - & versionUpgrades .~ AllChains HM.empty + & versionForks .~ HM.fromList [ (f, onAllChains v ForkAtGenesis) | f <- [minBound..maxBound] ] + & versionQuirks .~ noQuirks v + & versionUpgrades .~ onAllChains v HM.empty -- | A test version without Pact or PoW, with a chain graph upgrade at block height 8. timedConsensusVersion :: ChainGraph -> ChainGraph -> ChainwebVersion @@ -169,12 +169,12 @@ timedConsensusVersion g1 g2 = buildTestVersion $ \v -> v & versionBlockDelay .~ BlockDelay 1_000_000 & versionWindow .~ WindowWidth 120 & versionForks .~ tabulateHashMap (\case - SkipTxTimingValidation -> AllChains $ ForkAtBlockHeight (BlockHeight 2) + SkipTxTimingValidation -> onAllChains v $ ForkAtBlockHeight (BlockHeight 2) -- pact is disabled, we don't care about pact forks - _ -> AllChains ForkAtGenesis + _ -> onAllChains v ForkAtGenesis ) - & versionQuirks .~ noQuirks - & versionUpgrades .~ AllChains HM.empty + & versionQuirks .~ noQuirks v + & versionUpgrades .~ onAllChains v HM.empty & versionGraphs .~ (BlockHeight 8, g2) `Above` Bottom (minBound, g1) & versionCheats .~ VersionCheats { _disablePow = True @@ -189,8 +189,8 @@ timedConsensusVersion g1 g2 = buildTestVersion $ \v -> v { _genesisBlockPayload = onChains $ [] -- TODO: PP -- (unsafeChainId 0, TN0.payloadBlock) : -- [(n, TNN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains v maxTarget + , _genesisTime = onAllChains v $ BlockCreationTime epoch } -- | A test version without Pact or PoW. @@ -201,12 +201,12 @@ checkpointerTestVersion g1 = buildTestVersion $ \v -> v & versionBlockDelay .~ BlockDelay 1_000_000 & versionWindow .~ WindowWidth 120 & versionForks .~ tabulateHashMap (\case - SkipTxTimingValidation -> AllChains $ ForkAtBlockHeight (BlockHeight 2) + SkipTxTimingValidation -> onAllChains v $ ForkAtBlockHeight (BlockHeight 2) -- pact is disabled, we don't care about pact forks - _ -> AllChains ForkAtGenesis + _ -> onAllChains v ForkAtGenesis ) - & versionQuirks .~ noQuirks - & versionUpgrades .~ AllChains HM.empty + & versionQuirks .~ noQuirks v + & versionUpgrades .~ onAllChains v HM.empty & versionGraphs .~ Bottom (minBound, g1) & versionCheats .~ VersionCheats { _disablePow = True @@ -219,10 +219,10 @@ checkpointerTestVersion g1 = buildTestVersion $ \v -> v } & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains [ (n, _payloadWithOutputsPayloadHash emptyPayload) | n <- HS.toList (chainIds v) ] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains v maxTarget + , _genesisTime = onAllChains v $ BlockCreationTime epoch } - & versionPayloadProviderTypes .~ AllChains PactProvider + & versionPayloadProviderTypes .~ onAllChains v PactProvider -- | A family of versions each with Pact enabled and PoW disabled. cpmTestVersion :: ChainGraph -> VersionBuilder @@ -240,8 +240,8 @@ cpmTestVersion g v = v { _disableMempoolSync = False , _disablePeerValidation = True } - & versionUpgrades .~ AllChains mempty - & versionPayloadProviderTypes .~ AllChains PactProvider + & versionUpgrades .~ onAllChains v mempty + & versionPayloadProviderTypes .~ onAllChains v PactProvider -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled, -- and with a gas fee quirk. @@ -250,7 +250,7 @@ quirkedGasInstantCpmTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("quirked-instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> AllChains ForkAtGenesis) + _ -> onAllChains v ForkAtGenesis) & versionQuirks .~ VersionQuirks { _quirkGasFees = onChain (unsafeChainId 0) $ HM.singleton (BlockHeight 2, TxBlockIdx 0) (Pact.Gas 1) @@ -259,11 +259,11 @@ quirkedGasInstantCpmTestVersion g = buildTestVersion $ \v -> v { _genesisBlockPayload = onChains $ [] -- TODO: PP -- (unsafeChainId 0, IN0.payloadBlock) : -- [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains v maxTarget + , _genesisTime = onAllChains v $ BlockCreationTime epoch } - & versionUpgrades .~ AllChains mempty - & versionVerifierPluginNames .~ AllChains (Bottom (minBound, mempty)) + & versionUpgrades .~ onAllChains v mempty + & versionVerifierPluginNames .~ onAllChains v (Bottom (minBound, mempty)) -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled, -- and with a gas fee quirk. @@ -272,7 +272,7 @@ quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("quirked-pact5-instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> AllChains ForkAtGenesis) + _ -> onAllChains v ForkAtGenesis) & versionQuirks .~ VersionQuirks { _quirkGasFees = onChain (unsafeChainId 0) $ HM.singleton (BlockHeight 1, TxBlockIdx 0) (Pact.Gas 1) @@ -281,11 +281,11 @@ quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ \v -> v { _genesisBlockPayload = onChains $ (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : [(n, _payloadWithOutputsPayloadHash INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains v maxTarget + , _genesisTime = onAllChains v $ BlockCreationTime epoch } - & versionUpgrades .~ AllChains mempty - & versionVerifierPluginNames .~ AllChains (Bottom (minBound, mempty)) + & versionUpgrades .~ onAllChains v mempty + & versionVerifierPluginNames .~ onAllChains v (Bottom (minBound, mempty)) -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled -- at genesis EXCEPT Pact 5. @@ -294,18 +294,18 @@ instantCpmTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> AllChains ForkAtGenesis + _ -> onAllChains v ForkAtGenesis ) - & versionQuirks .~ noQuirks + & versionQuirks .~ noQuirks v & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : [(n, _payloadWithOutputsPayloadHash INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains v maxTarget + , _genesisTime = onAllChains v $ BlockCreationTime epoch } - & versionUpgrades .~ AllChains mempty - & versionVerifierPluginNames .~ AllChains + & versionUpgrades .~ onAllChains v mempty + & versionVerifierPluginNames .~ onAllChains v (Bottom ( minBound , Set.fromList $ map Pact.VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] @@ -323,23 +323,23 @@ instantCpmTestVersion g = buildTestVersion $ \v -> v -- -- genesis blocks are not ever run with Pact 5 -- Pact5Fork -> onChains [ (cid, ForkAtBlockHeight (succ $ genesisBlockHeight v cid)) | cid <- HS.toList $ graphChainIds g ] -- -- SPV Bridge is not in effect for Pact 5 yet. --- SPVBridge -> AllChains ForkNever --- _ -> AllChains ForkAtGenesis +-- SPVBridge -> onAllChains v ForkNever +-- _ -> onAllChains v ForkAtGenesis -- ) --- & versionQuirks .~ noQuirks +-- & versionQuirks .~ noQuirks v -- & versionGenesis .~ VersionGenesis -- { _genesisBlockPayload = onChains $ [] -- TODO: PP -- -- (unsafeChainId 0, IN0.payloadBlock) : -- -- [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] --- , _genesisBlockTarget = AllChains maxTarget --- , _genesisTime = AllChains $ BlockCreationTime epoch +-- , _genesisBlockTarget = onAllChains v maxTarget +-- , _genesisTime = onAllChains v $ BlockCreationTime epoch -- } -- & versionUpgrades .~ indexByForkHeights v -- -- TODO: PP --- -- [ (Pact5Fork, AllChains (Pact5Upgrade (List.map pactTxFrom4To5 CoinV6.transactions))) +-- -- [ (Pact5Fork, onAllChains v (Pact5Upgrade (List.map pactTxFrom4To5 CoinV6.transactions))) -- [ -- ] --- & versionVerifierPluginNames .~ AllChains +-- & versionVerifierPluginNames .~ onAllChains v -- (Bottom -- ( minBound -- , Set.fromList $ map Pact.VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index ed7e43fd0c..e6a5662547 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -128,6 +128,7 @@ module Chainweb.Test.Utils , withPactDir , NodeDbDirs(..) , withTempDir +, withTempFile ) where import Control.Concurrent @@ -147,6 +148,7 @@ import Data.Coerce (coerce) import Data.Foldable import Data.IORef import Data.List (sortOn, isInfixOf) +import Data.Pool (Pool) import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Tree @@ -167,10 +169,10 @@ import Numeric.Natural import Servant.Client (BaseUrl(..), ClientEnv, Scheme(..), mkClientEnv, runClientM) -import System.Directory (removeDirectoryRecursive) +import System.Directory (removeDirectoryRecursive, removeFile) import System.Environment (withArgs) import System.IO -import System.IO.Temp +import System.IO.Temp qualified as Temp import System.LogLevel import System.Random (randomIO) @@ -211,7 +213,7 @@ import Chainweb.Mempool.Mempool (MempoolBackend(..), TransactionHash(..), BlockF import Chainweb.MerkleUniverse import Chainweb.Miner.Config import Chainweb.Pact.Backend.Types(SQLiteEnv) -import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) +import Chainweb.Pact.Backend.Utils import Chainweb.Parent import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID @@ -309,8 +311,8 @@ withRocksResource :: ResourceT IO RocksDb withRocksResource = view _2 . snd <$> allocate create destroy where create = do - sysdir <- getCanonicalTemporaryDirectory - dir <- createTempDirectory sysdir "chainweb-rocksdb-tmp" + sysdir <- Temp.getCanonicalTemporaryDirectory + dir <- Temp.createTempDirectory sysdir "chainweb-rocksdb-tmp" opts@(R.Options' opts_ptr _ _) <- R.mkOpts modernDefaultOptions rocks <- openRocksDb dir opts_ptr return (dir, rocks, opts) @@ -333,9 +335,12 @@ withSQLiteResource file = snd <$> allocate (openSQLiteConnection file chainwebPragmas) closeSQLiteConnection --- | Open a temporary file-backed SQLite database. -withTempSQLiteResource :: ResourceT IO SQLiteEnv -withTempSQLiteResource = withSQLiteResource "" +-- | Open a temporary file-backed SQLite database, and return a writable +-- connection and a read-only pool of connections. +withTempSQLiteResource :: ResourceT IO (SQLiteEnv, Pool SQLiteEnv) +withTempSQLiteResource = do + fp <- withTempFile "sqlite-tmp" + (,) <$> withSQLiteResource fp <*> withReadSqlitePool fp withInMemSQLiteResource :: ResourceT IO SQLiteEnv withInMemSQLiteResource = withSQLiteResource ":memory:" @@ -564,19 +569,18 @@ testHost = "localhost" data TestClientEnv t = TestClientEnv { _envClientEnv :: !ClientEnv , _envCutDb :: !(Maybe CutDb) - , _envBlockHeaderDbs :: ![(ChainId, BlockHeaderDb)] - , _envMempools :: ![(ChainId, MempoolBackend t)] + , _envBlockHeaderDbs :: !(ChainMap BlockHeaderDb) , _envPeerDbs :: ![(NetworkId, P2P.PeerDb)] , _envVersion :: !ChainwebVersion } pattern BlockHeaderDbsTestClientEnv :: ClientEnv - -> [(ChainId, BlockHeaderDb)] + -> ChainMap BlockHeaderDb -> ChainwebVersion -> TestClientEnv t pattern BlockHeaderDbsTestClientEnv { _cdbEnvClientEnv, _cdbEnvBlockHeaderDbs, _cdbEnvVersion } - = TestClientEnv _cdbEnvClientEnv Nothing _cdbEnvBlockHeaderDbs [] [] _cdbEnvVersion + <- TestClientEnv _cdbEnvClientEnv Nothing _cdbEnvBlockHeaderDbs [] _cdbEnvVersion pattern PeerDbsTestClientEnv :: ClientEnv @@ -584,7 +588,7 @@ pattern PeerDbsTestClientEnv -> ChainwebVersion -> TestClientEnv t pattern PeerDbsTestClientEnv { _pdbEnvClientEnv, _pdbEnvPeerDbs, _pdbEnvVersion } - = TestClientEnv _pdbEnvClientEnv Nothing [] [] _pdbEnvPeerDbs _pdbEnvVersion + <- TestClientEnv _pdbEnvClientEnv Nothing _ _pdbEnvPeerDbs _pdbEnvVersion pattern PayloadTestClientEnv :: ClientEnv @@ -592,7 +596,7 @@ pattern PayloadTestClientEnv -> ChainwebVersion -> TestClientEnv t pattern PayloadTestClientEnv { _pEnvClientEnv, _pEnvCutDb, _eEnvVersion } - = TestClientEnv _pEnvClientEnv (Just _pEnvCutDb) [] [] [] _eEnvVersion + <- TestClientEnv _pEnvClientEnv (Just _pEnvCutDb) _ [] _eEnvVersion withTestAppServer :: Bool @@ -681,7 +685,7 @@ clientEnvWithChainwebTestServer => ShouldValidateSpec -> Bool -> ChainwebVersion - -> ChainwebServerDbs t + -> ChainwebServerDbs -> ResourceT IO (TestClientEnv t) clientEnvWithChainwebTestServer shouldValidateSpec tls v dbs = do -- FIXME: Hashes API got removed from the P2P API. We use an application that @@ -698,7 +702,6 @@ clientEnvWithChainwebTestServer shouldValidateSpec tls v dbs = do (mkClientEnv mgr (BaseUrl (if tls then Https else Http) testHost port "")) (_chainwebServerCutDb dbs) (_chainwebServerBlockHeaderDbs dbs) - (_chainwebServerMempools dbs) (_chainwebServerPeerDbs dbs) v @@ -739,14 +742,12 @@ withBlockHeaderDbsServer => ShouldValidateSpec -> Bool -> ChainwebVersion - -> [(ChainId, BlockHeaderDb)] - -> [(ChainId, MempoolBackend t)] + -> ChainMap BlockHeaderDb -> ResourceT IO (TestClientEnv t) -withBlockHeaderDbsServer shouldValidateSpec tls v chainDbs mempools = +withBlockHeaderDbsServer shouldValidateSpec tls v chainDbs = clientEnvWithChainwebTestServer shouldValidateSpec tls v emptyChainwebServerDbs { _chainwebServerBlockHeaderDbs = chainDbs - , _chainwebServerMempools = mempools } -- -------------------------------------------------------------------------- -- @@ -989,9 +990,6 @@ awaitBlockHeight v cenv i = do checkRetry _ (Right c) = return $ any (\bh -> _bhwhHeight bh < i) (_cutHashes c) -withAsyncR :: IO a -> ResourceT IO (Async a) -withAsyncR action = snd <$> allocate (async action) uninterruptibleCancel - runTestNodes :: Logger logger => logger -> ChainwebVersion @@ -1067,12 +1065,18 @@ data NodeDbDirs = NodeDbDirs withPactDir :: Word -> ResourceT IO FilePath withPactDir nid = withTempDir $ "pactdb-dir-" ++ show nid +withTempFile :: FilePath -> ResourceT IO FilePath +withTempFile template = snd <$> allocate create destroy + where + create = Temp.emptySystemTempFile template + destroy = removeFile + withTempDir :: FilePath -> ResourceT IO FilePath withTempDir template = snd <$> allocate create destroy where create = do - targetDir <- getCanonicalTemporaryDirectory - createTempDirectory targetDir template + targetDir <- Temp.getCanonicalTemporaryDirectory + Temp.createTempDirectory targetDir template destroy = removeDirectoryRecursive withNodeDbDirs :: RocksDb -> Word -> ResourceT IO [NodeDbDirs] diff --git a/test/unit/Chainweb/Test/BlockHeader/Validation.hs b/test/unit/Chainweb/Test/BlockHeader/Validation.hs index 6ae7228fdf..c16b8d7505 100644 --- a/test/unit/Chainweb/Test/BlockHeader/Validation.hs +++ b/test/unit/Chainweb/Test/BlockHeader/Validation.hs @@ -47,6 +47,7 @@ import Chainweb.BlockHeader.Validation import Chainweb.BlockHeight import Chainweb.Difficulty import Chainweb.Graph hiding (AdjacentChainMismatch) +import Chainweb.Parent import Chainweb.Test.Orphans.Internal () import Chainweb.Test.Utils.TestHeader import Chainweb.Test.TestVersions @@ -225,7 +226,7 @@ validationFailures = , ( hdr & testHeaderParent . coerced . blockHeight .~ 318359 , [IncorrectHeight, IncorrectEpoch, IncorrectTarget] ) - , ( hdr & testHeaderHdr . blockParent %~ messWords encodeBlockHash decodeBlockHash (flip complementBit 0) + , ( hdr & testHeaderHdr . blockParent %~ messWords (encodeBlockHash . unwrapParent) (Parent <$> decodeBlockHash) (flip complementBit 0) , [MissingParent] ) , ( hdr & testHeaderHdr . blockPayloadHash %~ messWords encodeBlockPayloadHash decodeBlockPayloadHash (flip complementBit 0) @@ -273,10 +274,10 @@ validationFailures = , ( hdr & testHeaderHdr . blockAdjacentHashes .~ BlockHashRecord mempty , [IncorrectHash, IncorrectPow, AdjacentChainMismatch] ) - , ( hdr & testHeaderAdjs . each . parentHeader . blockChainwebVersion .~ _versionCode RecapDevelopment + , ( hdr & testHeaderAdjs . each . _Parent . blockChainwebVersion .~ _versionCode RecapDevelopment , [InvalidAdjacentVersion] ) - , ( hdr & testHeaderAdjs . ix 0 . parentHeader . blockChainId .~ unsafeChainId 0 + , ( hdr & testHeaderAdjs . ix 0 . _Parent . blockChainId .~ unsafeChainId 0 , [AdjacentParentChainMismatch] ) ] @@ -348,9 +349,9 @@ daValidation = where h, p :: Lens' TestHeader BlockHeader h = testHeaderHdr - p = testHeaderParent . parentHeader + p = testHeaderParent . _Parent - a = testHeaderAdjs . each . parentHeader + a = testHeaderAdjs . each . _Parent expected = [IncorrectHash, IncorrectPow, AdjacentChainMismatch] @@ -401,7 +402,7 @@ legacyDaValidation = where h, p :: Lens' TestHeader BlockHeader h = testHeaderHdr - p = testHeaderParent . parentHeader + p = testHeaderParent . _Parent -- From mainnet height 600000 hdr = testHeader @@ -446,11 +447,11 @@ testnet04Headers :: [TestHeader] testnet04Headers = genesisTestHeaders Testnet04 -- TODO replace these by "interesting headers" form mainnet (e.g. fork block heights) -_testnet04InvalidHeaders :: [(ParentHeader, BlockHeader, [ValidationFailureType])] +_testnet04InvalidHeaders :: [(Parent BlockHeader, BlockHeader, [ValidationFailureType])] _testnet04InvalidHeaders = some where f (p, h, e) = (,,) - <$> (fmap ParentHeader . decode . wrap) p + <$> (fmap Parent . decode . wrap) p <*> (decode . wrap) h <*> pure e diff --git a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs index b8a886b384..00d8f57bed 100644 --- a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs +++ b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs @@ -37,7 +37,7 @@ import Chainweb.BlockHeader.Validation import Chainweb.BlockHeaderDB import Chainweb.BlockHeaderDB.Internal import Chainweb.BlockHeaderDB.PruneForks -import Chainweb.Chainweb.PruneChainDatabase +-- import Chainweb.Chainweb.PruneChainDatabase import Chainweb.Logger import Chainweb.Payload import Chainweb.Payload.PayloadStore diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 4d844392db..88c7472dd2 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -8,6 +8,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeApplications #-} -- | -- Module: Chainweb.Test.CutDB @@ -35,12 +36,14 @@ import Control.Concurrent.STM as STM import Control.Lens hiding (elements) import Control.Monad import Control.Monad.Catch +import Control.Monad.IO.Class import Control.Monad.Trans.Resource import Data.Foldable import Data.Function import qualified Data.HashMap.Strict as HM import Data.Semigroup +import Data.Text(Text) import qualified Data.Vector as V import GHC.Stack @@ -67,9 +70,11 @@ import Chainweb.CutDB import Chainweb.CutDB.RestAPI.Server import Chainweb.Miner.Pact import Chainweb.Pact.Types +import Chainweb.Parent import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.PayloadProvider import Chainweb.Sync.WebBlockHeaderStore import Chainweb.Test.Orphans.Internal () import Chainweb.Test.Sync.WebBlockHeaderStore @@ -89,7 +94,8 @@ import Data.LogMessage import Data.TaskMap import Test.Tasty.HUnit -import Chainweb.PayloadProvider (ConfiguredPayloadProvider(..)) +import Chainweb.Logger +import System.LogLevel -- -------------------------------------------------------------------------- -- -- Create a random Cut DB with the respective Payload Store @@ -105,8 +111,7 @@ cutFetchTimeout = 3_000_000 -- inserted. -- withTestCutDb - :: forall a - . HasCallStack + :: HasCallStack => RocksDb -> ChainwebVersion -- ^ the chainweb version @@ -114,7 +119,7 @@ withTestCutDb -- ^ any alterations to the CutDB's configuration -> Int -- ^ number of blocks in the chainweb in addition to the genesis blocks - -> (ChainMap ConfiguredPayloadProvider) + -> ChainMap ConfiguredPayloadProvider -- ^ a pact execution service. -- -- When transaction don't matter you can use 'fakePact' from this module. @@ -125,17 +130,17 @@ withTestCutDb -- -> LogFunction -- ^ a logg function (use @\_ _ -> return ()@ turn of logging) - -> (forall tbl . CanReadablePayloadCas tbl => Casify RocksDbTable CutHashes -> CutDb -> IO a) - -> IO a -withTestCutDb rdb v conf n providers logfun f = do - rocksDb <- testRocksDb "withTestCutDb" rdb + -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb) +withTestCutDb rdb v conf n providers logfun = do + rocksDb <- liftIO $ testRocksDb "withTestCutDb" rdb let cutHashesDb = cutHashesTable rocksDb - webDb <- initWebBlockHeaderDb rocksDb v - mgr <- HTTP.newManager HTTP.defaultManagerSettings - withLocalWebBlockHeaderStore mgr webDb $ \headerStore -> - withCutDb (conf $ defaultCutDbParams v cutFetchTimeout) logfun headerStore providers cutHashesDb $ \cutDb -> do - foldM_ (\c _ -> view _1 <$> mine defaultMiner providers cutDb c) (genesisCut v) [1..n] - f cutHashesDb cutDb + webDb <- liftIO $ initWebBlockHeaderDb rocksDb v + mgr <- liftIO $ HTTP.newManager HTTP.defaultManagerSettings + headerStore <- withLocalWebBlockHeaderStore mgr webDb + cutDb <- withCutDb (conf $ defaultCutDbParams v cutFetchTimeout) logfun headerStore providers cutHashesDb + liftIO $ logfun @Text Debug "GOING TO MINE AT THE START" + liftIO $ foldM_ (\c _ -> view _1 <$> mine providers cutDb c) (genesisCut v) [1..n] + return (cutHashesDb, cutDb) -- -- | Adds the requested number of new blocks to the given 'CutDb'. -- -- @@ -221,8 +226,7 @@ awaitBlockHeight cdb bh cid = atomically $ do -- important. -- withTestCutDbWithoutPact - :: forall a - . HasCallStack + :: HasCallStack => RocksDb -> ChainwebVersion -- ^ the chainweb version @@ -232,10 +236,9 @@ withTestCutDbWithoutPact -- ^ number of blocks in the chainweb in addition to the genesis blocks -> LogFunction -- ^ a logg function (use @\_ _ -> return ()@ turn of logging) - -> (forall tbl . CanReadablePayloadCas tbl => Casify RocksDbTable CutHashes -> CutDb -> IO a) - -> IO a + -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb) withTestCutDbWithoutPact rdb v conf n = - withTestCutDb rdb v conf n (onAllChains v (\_ -> DisabledPayloadProvider)) + withTestCutDb rdb v conf n (onAllChains v DisabledPayloadProvider) -- | A version of withTestCutDb that can be used as a Tasty TestTree resource. -- @@ -246,7 +249,7 @@ withTestPayloadResource -> LogFunction -> ResourceT IO CutDb withTestPayloadResource rdb v n logfun - = view _3 . snd <$> allocate start stopTestPayload + = view _2 . snd <$> allocate start stopTestPayload where start = startTestPayload rdb v logfun n @@ -265,26 +268,25 @@ startTestPayload rdb v logfun n = do webDb <- initWebBlockHeaderDb rocksDb v mgr <- HTTP.newManager HTTP.defaultManagerSettings (hserver, hstore) <- startLocalWebBlockHeaderStore mgr webDb - let disabledPayloadProviders = onAllChains v $ \_ -> DisabledPayloadProvider + let disabledPayloadProviders = onAllChains v DisabledPayloadProvider cutDb <- startCutDb (defaultCutDbParams v cutFetchTimeout) logfun hstore disabledPayloadProviders cutHashesDb - foldM_ (\c _ -> view _1 <$> mine defaultMiner disabledPayloadProviders cutDb c) (genesisCut v) [0..n] + foldM_ (\c _ -> view _1 <$> mine disabledPayloadProviders cutDb c) (genesisCut v) [0..n] return (hserver, cutDb) -stopTestPayload :: (Async (), Async (), CutDb) -> IO () -stopTestPayload (pserver, hserver, cutDb) = do +stopTestPayload :: (Async (), CutDb) -> IO () +stopTestPayload (hserver, cutDb) = do stopCutDb cutDb cancel hserver - cancel pserver withLocalWebBlockHeaderStore :: HTTP.Manager -> WebBlockHeaderDb - -> (WebBlockHeaderStore -> IO a) - -> IO a -withLocalWebBlockHeaderStore mgr webDb inner = withNoopQueueServer $ \queue -> do - mem <- new - inner $ WebBlockHeaderStore webDb mem queue (\_ _ -> return ()) mgr + -> ResourceT IO WebBlockHeaderStore +withLocalWebBlockHeaderStore mgr webDb = do + queue <- withNoopQueueServer + mem <- liftIO new + return $ WebBlockHeaderStore webDb mem queue (\_ _ -> return ()) mgr startLocalWebBlockHeaderStore :: HTTP.Manager @@ -300,16 +302,14 @@ startLocalWebBlockHeaderStore mgr webDb = do -- mine :: HasCallStack - => CanReadablePayloadCas tbl - => Miner -- ^ The miner. For testing you may use 'defaultMiner'. - -> ChainMap ConfiguredPayloadProvider + => ChainMap ConfiguredPayloadProvider -- ^ only the new-block generator is used. For testing you may use -- 'fakePact'. -> CutDb -> Cut - -> IO (Cut, ChainId, PayloadWithOutputs) -mine miner pact cutDb c = do + -> IO (Cut, ChainId, NewPayload) +mine pact cutDb c = do -- Pick a chain that isn't blocked. With that mining is guaranteed to -- succeed if @@ -320,7 +320,7 @@ mine miner pact cutDb c = do -- - the transaction generator produces valid blocks. cid <- getRandomUnblockedChain c - tryMineForChain miner pact cutDb c cid >>= \case + tryMineForChain pact cutDb c cid >>= \case Left _ -> throwM $ InternalInvariantViolation "Failed to create new cut. This is a bug in Chainweb.Test.CutDB or one of it's users" Right x -> do @@ -347,35 +347,35 @@ getRandomUnblockedChain c = do -- Block times are real times. -- tryMineForChain - :: forall tbl - . HasCallStack - => CanReadablePayloadCas tbl - => Miner + :: HasCallStack -- ^ The miner. For testing you may use 'defaultMiner'. -- miner. - -> ChainMap ConfiguredPayloadProvider + => ChainMap ConfiguredPayloadProvider -- ^ only the new-block generator is used. For testing you may use -- 'fakePact'. -> CutDb -> Cut -> ChainId - -> IO (Either MineFailure (Cut, ChainId, PayloadWithOutputs)) -tryMineForChain miner webPact cutDb c cid = do - newBlock <- throwIfNoHistory =<< _webPactNewBlock webPact cid miner NewBlockFill parent - let outputs = newBlockToPayloadWithOutputs newBlock - let payloadHash = _payloadWithOutputsPayloadHash outputs + -> IO (Either MineFailure (Cut, ChainId, NewPayload)) +tryMineForChain providers cutDb c cid = do + newPayload <- case providers ^?! atChain cid of + ConfiguredPayloadProvider p -> latestPayloadIO p + DisabledPayloadProvider -> error "missing payload provider, cannot mine for chain" + let payloadHash = _newPayloadBlockPayloadHash newPayload t <- getCurrentTimeIntegral x <- testMineWithPayloadHash wdb (Nonce 0) t payloadHash cid c case x of Right (T2 h c') -> do addCutHashes cutDb (cutToCutHashes Nothing c') - { _cutHashesHeaders = HM.singleton (view blockHash h) h - , _cutHashesPayloads = HM.singleton (view blockPayloadHash h) (payloadWithOutputsToPayloadData outputs) + { _cutHashesHeaders = + HM.singleton (view blockHash h) h + , _cutHashesPayloads = + HM.singleton (view blockPayloadHash h) (fromJuste $ _newPayloadEncodedPayloadData newPayload) } - return $ Right (c', cid, outputs) + return $ Right (c', cid, newPayload) Left e -> return $ Left e where - parent = ParentHeader $ c ^?! ixg cid -- parent to mine on + parent = Parent $ c ^?! ixg cid -- parent to mine on wdb = view cutDbWebBlockHeaderDb cutDb -- | picks a random block header from a web chain. The result header is @@ -403,9 +403,10 @@ randomBlockHeader cutDb = do randomTransaction :: HasCallStack => CanReadablePayloadCas tbl - => CutDb + => PayloadDb tbl + -> CutDb -> IO (BlockHeader, Int, Transaction, TransactionOutput) -randomTransaction cutDb = do +randomTransaction payloadDb cutDb = do bh <- randomBlockHeader cutDb Just pd <- lookupPayloadDataWithHeight payloadDb (Just $ view blockHeight bh) (view blockPayloadHash bh) let pay = BlockPayload @@ -429,8 +430,6 @@ randomTransaction cutDb = do , _blockTransactions btxs V.! txIx , _blockOutputs outs V.! txIx ) - where - payloadDb = view cutDbPayloadDb cutDb -- | FAKE pact execution service. -- @@ -475,24 +474,24 @@ tests rdb = testGroup "CutDB" ] testCutPruning :: RocksDb -> TestTree -testCutPruning rdb = testCase "cut pruning" $ do +testCutPruning rdb = testCase "cut pruning" $ runResourceT $ do -- initialize cut DB and mine enough to trigger pruning let v = barebonesTestVersion pairChainGraph - withTestCutDbWithoutPact rdb v alterPruningSettings + (cutHashesStore, _) <- withTestCutDbWithoutPact rdb v alterPruningSettings (int $ avgCutHeightAt v minedBlockHeight) (\_ _ -> return ()) - $ \cutHashesStore _ -> do - -- peek inside the cut DB's store to find the oldest and newest cuts - let table = unCasify cutHashesStore - Just (leastCutHeight, _, _) <- tableMinKey table - Just (mostCutHeight, _, _) <- tableMaxKey table - let fuzz = 10 :: Integer - -- we must have pruned the older cuts - assertBool "oldest cuts are too old" $ - round (avgBlockHeightAtCutHeight v leastCutHeight) >= fuzz - -- we must keep the latest cut - assertBool "newest cut is too old" $ - round (avgBlockHeightAtCutHeight v mostCutHeight) >= int minedBlockHeight - fuzz + liftIO $ do + -- peek inside the cut DB's store to find the oldest and newest cuts + let table = unCasify cutHashesStore + Just (leastCutHeight, _, _) <- tableMinKey table + Just (mostCutHeight, _, _) <- tableMaxKey table + let fuzz = 10 :: Integer + -- we must have pruned the older cuts + assertBool "oldest cuts are too old" $ + round (avgBlockHeightAtCutHeight v leastCutHeight) >= fuzz + -- we must keep the latest cut + assertBool "newest cut is too old" $ + round (avgBlockHeightAtCutHeight v mostCutHeight) >= int minedBlockHeight - fuzz where alterPruningSettings = set cutDbParamsAvgBlockHeightPruningDepth 50 . @@ -500,14 +499,15 @@ testCutPruning rdb = testCase "cut pruning" $ do minedBlockHeight = 300 testCutGet :: RocksDb -> TestTree -testCutGet rdb = testCase "cut get" $ do +testCutGet rdb = testCase "cut get" $ runResourceT $ do let v = barebonesTestVersion pairChainGraph let bh = BlockHeight 300 let ch = avgCutHeightAt v bh let halfCh = ch `div` 2 - withTestCutDbWithoutPact rdb v id (2 * int ch) (\_ _ -> return ()) $ \_ cutDb -> do - curHeight <- _cutHeight <$> _cut cutDb - assertGe "cut height is large enough" (Actual curHeight) (Expected $ 2 * int ch) - retCut <- cutGetHandler cutDb (Just $ MaxRank (Max $ int halfCh)) - assertLe "cut hashes are too high" (Actual (_cutHashesHeight retCut)) (Expected halfCh) + (_, cutDb) <- withTestCutDbWithoutPact rdb v id (2 * int ch) (\_ _ -> return ()) + liftIO $ do + curHeight <- _cutHeight <$> _cut cutDb + assertGe "cut height is large enough" (Actual curHeight) (Expected $ 2 * int ch) + retCut <- cutGetHandler cutDb (Just $ MaxRank (Max $ int halfCh)) + assertLe "cut hashes are too high" (Actual (_cutHashesHeight retCut)) (Expected halfCh) diff --git a/test/unit/Chainweb/Test/Mempool.hs b/test/unit/Chainweb/Test/Mempool.hs index bc2b06929d..71df03deaa 100644 --- a/test/unit/Chainweb/Test/Mempool.hs +++ b/test/unit/Chainweb/Test/Mempool.hs @@ -116,7 +116,7 @@ arbitraryDecimal = do return $! Decimal places mantissa arbitraryGasPrice :: Gen GasPrice -arbitraryGasPrice = GasPrice . ParsedDecimal . abs <$> arbitraryDecimal +arbitraryGasPrice = GasPrice . abs <$> arbitraryDecimal instance Arbitrary MockTx where arbitrary = MockTx @@ -202,7 +202,7 @@ propBadlistPreblock (txs, badTxs) _ mempool = runExceptT $ do liftIO (lookup badTxs) >>= V.mapM_ lookupIsPending -- once we call mempoolGetBlock, the bad txs should be badlisted - liftIO $ void $ mempoolGetBlock mempool mockBlockFill preblockCheck 1 nullBlockHash + liftIO $ void $ mempoolGetBlock mempool mockBlockFill preblockCheck (EvaluationCtx 1 nullBlockHash) liftIO (lookup txs) >>= V.mapM_ lookupIsPending liftIO (lookup badTxs) >>= V.mapM_ lookupIsMissing liftIO $ insert badTxs diff --git a/test/unit/Chainweb/Test/Mempool/Consensus.hs b/test/unit/Chainweb/Test/Mempool/Consensus.hs index aaa57d76a7..70b6c42552 100644 --- a/test/unit/Chainweb/Test/Mempool/Consensus.hs +++ b/test/unit/Chainweb/Test/Mempool/Consensus.hs @@ -44,7 +44,7 @@ import Chainweb.BlockWeight import Chainweb.ChainId import Chainweb.Crypto.MerkleLog hiding (header) import Chainweb.Difficulty (targetToDifficulty) -import Chainweb.Mempool.Consensus +-- import Chainweb.Mempool.Consensus import Chainweb.Mempool.Mempool import Chainweb.MerkleUniverse import Chainweb.Payload diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index 1372a1dcf2..a5862ceaa0 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -33,9 +33,10 @@ module Chainweb.Test.Pact.CutFixture , fixturePayloadDb , fixtureWebBlockHeaderDb , fixtureMempools - -- , advanceAllChains - -- , advanceAllChains_ - -- , withTestCutDb + , fixturePacts + , advanceAllChains + , advanceAllChains_ + , withTestCutDb ) where @@ -59,6 +60,7 @@ import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Storage.Table.RocksDB import Chainweb.Sync.WebBlockHeaderStore +import Chainweb.Test.CutDB import Chainweb.Test.Pact.Utils import Chainweb.Test.Utils import Chainweb.Time @@ -85,8 +87,8 @@ import GHC.Stack import Network.HTTP.Client qualified as HTTP import qualified Data.Pool as Pool import qualified Chainweb.Pact.PactService as PactService -import Chainweb.PayloadProvider.Pact (pactMemPoolAccess) -import Chainweb.Test.CutDB (withTestCutDb) +import Chainweb.PayloadProvider.Pact +import Chainweb.PayloadProvider data Fixture = Fixture { _fixtureCutDb :: CutDb @@ -105,219 +107,182 @@ instance HasFixture Fixture where instance HasFixture a => HasFixture (IO a) where cutFixture = (>>= cutFixture) -mkFixture :: ChainwebVersion -> (ChainId -> PayloadWithOutputs) -> (PactServiceConfig) -> RocksDb -> ResourceT IO Fixture +mkFixture :: ChainwebVersion -> (ChainId -> PayloadWithOutputs) -> PactServiceConfig -> RocksDb -> ResourceT IO Fixture mkFixture v genesisPayloadFor pactServiceConfig baseRdb = do logger <- liftIO getTestLogger testRdb <- liftIO $ testRocksDb "withBlockDbs" baseRdb (payloadDb, webBHDb) <- withBlockDbs v testRdb - perChain <- fmap OnChains $ iforM (HashSet.toMap (chainIds v)) $ \chain () -> do - sqlite <- withTempSQLiteResource - fakeRoSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sqlite) (\_ -> return ()) 10 10) - serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing payloadDb fakeRoSqlPool sqlite pactServiceConfig (Just $ genesisPayloadFor chain) + perChain <- fmap ChainMap $ iforM (HashSet.toMap (chainIds v)) $ \chain () -> do + (writeSqlite, readPool) <- withTempSQLiteResource + serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing payloadDb readPool writeSqlite pactServiceConfig (Just $ genesisPayloadFor chain) mempool <- withMempool logger serviceEnv let serviceEnv' = serviceEnv { _psMempoolAccess = pactMemPoolAccess mempool logger } return (mempool, serviceEnv') let pacts = snd <$> perChain let mempools = fst <$> perChain - let cutHashesStore = cutHashesTable testRdb - cutDb <- withTestCutDb logger v webBHDb payloadDb cutHashesStore webPact - + let providers = ConfiguredPayloadProvider . PactPayloadProvider logger <$> pacts + (_, cutDb) <- withTestCutDb testRdb v id 0 providers (logFunction logger) let fixture = Fixture { _fixtureCutDb = cutDb + , _fixtureLogger = logger + , _fixturePacts = pacts , _fixturePayloadDb = payloadDb , _fixtureWebBlockHeaderDb = webBHDb - , _fixtureMempools = OnChains $ fst <$> perChain + , _fixtureMempools = mempools } -- we create the first block to avoid rejecting txs based on genesis -- block creation time being from the past _ <- liftIO $ advanceAllChains fixture return fixture --- -- | Advance all chains by one block, filling that block with whatever is in --- -- their mempools at the time. --- -- --- advanceAllChains --- :: (HasCallStack, HasFixture a) --- => a --- -> IO (Cut, ChainMap (Vector TestPact5CommandResult)) --- advanceAllChains fx = do --- Fixture{..} <- cutFixture fx --- let v = _chainwebVersion _fixtureCutDb --- latestCut <- liftIO $ _fixtureCutDb ^. cut --- let blockHeights = fmap (view blockHeight) $ latestCut ^. cutMap --- let latestBlockHeight = maximum blockHeights - --- -- TODO: rejig this to do parallel mining. --- (finalCut, perChainCommandResults) <- foldM --- (\ (prevCut, !acc) cid -> do --- (newCut, _minedChain, pwo) <- --- mine cid noMiner _fixtureWebPactExecutionService _fixtureCutDb prevCut --- commandResults <- forM (_payloadWithOutputsTransactions pwo) $ \(_, txOut) -> do --- decodeOrThrow' $ LBS.fromStrict $ _transactionOutputBytes txOut - --- addNewPayload _fixturePayloadDb latestBlockHeight pwo +-- | Advance all chains by one block, filling that block with whatever is in +-- their mempools at the time. +-- +advanceAllChains + :: (HasCallStack, HasFixture a) + => a + -> IO (Cut, ChainMap (Vector TestPact5CommandResult)) +advanceAllChains fx = do + Fixture{..} <- cutFixture fx + let v = _chainwebVersion _fixtureCutDb + latestCut <- liftIO $ _fixtureCutDb ^. cut + let blockHeights = fmap (view blockHeight) $ latestCut ^. cutMap + let latestBlockHeight = maximum blockHeights --- return $ (newCut, (cid, commandResults) : acc) --- ) --- (latestCut, []) --- (HashSet.toList (chainIdsAt v (latestBlockHeight + 1))) + -- TODO: rejig this to do parallel mining. + (finalCut, perChainCommandResults) <- foldM + (\ (prevCut, !acc) cid -> do + (newCut, _minedChain, newPayload) <- + mine cid _fixtureCutDb prevCut --- return (finalCut, onChains perChainCommandResults) + pwo <- decodeNewPayload newPayload --- advanceAllChains_ --- :: (HasCallStack, HasFixture a) --- => a --- -> IO () --- advanceAllChains_ = void . advanceAllChains + commandResults <- forM (_payloadWithOutputsTransactions pwo) $ \(_, txOut) -> do + decodeOrThrow' $ LBS.fromStrict $ _transactionOutputBytes txOut --- withTestCutDb :: (Logger logger) --- => logger --- -> ChainwebVersion --- -> WebBlockHeaderDb --- -> PayloadDb RocksDbTable --- -> Casify RocksDbTable CutHashes --- -> ResourceT IO CutDb --- withTestCutDb logger v webBHDb payloadDb cutHashesStore webPact = snd <$> allocate create destroy --- where --- create :: IO CutDb --- create = do --- initializePayloadDb v payloadDb --- httpManager <- HTTP.newManager HTTP.defaultManagerSettings + addNewPayload _fixturePayloadDb latestBlockHeight pwo --- headerStore <- newWebBlockHeaderStore httpManager webBHDb (logFunction logger) --- payloadStore <- newWebPayloadStore httpManager webPact payloadDb (logFunction logger) + return $ (newCut, (cid, commandResults) : acc) + ) + (latestCut, []) + (HashSet.toList (chainIdsAt v (latestBlockHeight + 1))) --- let cutFetchTimeout = 3_000_000 --- cutDb <- startCutDb (defaultCutDbParams v cutFetchTimeout) (logFunction logger) headerStore payloadStore cutHashesStore --- return cutDb + return (finalCut, onChains perChainCommandResults) --- destroy :: CutDb -> IO () --- destroy = stopCutDb +advanceAllChains_ + :: (HasCallStack, HasFixture a) + => a + -> IO () +advanceAllChains_ = void . advanceAllChains --- -- | Build a linear chainweb (no forks, assuming single threaded use of the --- -- cutDb). No POW or poison delay is applied. Block times are real times. --- mine --- :: (HasCallStack, CanReadablePayloadCas tbl) --- => ChainId --- -> Miner --- -- ^ The miner. For testing you may use 'defaultMiner' or 'noMiner'. --- -- ^ only the new-block generator is used. For testing you may use --- -- 'fakePact'. --- -> CutDb --- -> Cut --- -> IO (Cut, ChainId, PayloadWithOutputs) --- mine cid miner newBlockStrat pact cutDb c = do --- tryMineForChain miner newBlockStrat pact cutDb c cid >>= \case --- Left _ -> throwM $ InternalInvariantViolation --- $ "Failed to create new cut on chain " <> toText cid <> "." --- <> "This is a bug in Chainweb.Test.Pact.CutFixture or one of its users; check that this chain's adjacent chains aren't too far behind." --- <> "\nCut: \n" --- <> Text.unlines (cutToTextShort c) --- Right x -> do --- void $ awaitCut cutDb $ ((<=) `on` _cutHeight) (view _1 x) --- return x +-- | Build a linear chainweb (no forks, assuming single threaded use of the +-- cutDb). No POW or poison delay is applied. Block times are real times. +mine + :: HasCallStack + => ChainId + -> CutDb + -> Cut + -> IO (Cut, ChainId, NewPayload) +mine cid cutDb c = do + tryMineForChain cutDb c cid >>= \case + Left _ -> throwM $ InternalInvariantViolation + $ "Failed to create new cut on chain " <> toText cid <> "." + <> "This is a bug in Chainweb.Test.Pact.CutFixture or one of its users; check that this chain's adjacent chains aren't too far behind." + <> "\nCut: \n" + <> Text.unlines (cutToTextShort c) + Right x -> do + void $ awaitCut cutDb $ ((<=) `on` _cutHeight) (view _1 x) + return x --- -- | Atomically await for a 'CutDb' instance to synchronize cuts according to some --- -- predicate for a given 'Cut' and the results of '_cutStm'. --- awaitCut --- :: CutDb --- -> (Cut -> Bool) --- -> IO Cut --- awaitCut cdb k = atomically $ do --- c <- _cutStm cdb --- STM.check $ k c --- pure c +testMineWithPayloadHash + :: forall cid hdb. (HasChainId cid, ChainValueCasLookup hdb BlockHeader) + => hdb + -> Nonce + -> Time Micros + -> BlockPayloadHash + -> cid + -> Cut + -> IO (Either MineFailure (T2 BlockHeader Cut)) +testMineWithPayloadHash db n t ph cid c = try $ createNewCut (chainLookupM db) n t ph cid c --- testMineWithPayloadHash --- :: forall cid hdb. (HasChainId cid, ChainValueCasLookup hdb BlockHeader) --- => hdb --- -> Nonce --- -> Time Micros --- -> BlockPayloadHash --- -> cid --- -> Cut --- -> IO (Either MineFailure (T2 BlockHeader Cut)) --- testMineWithPayloadHash db n t ph cid c = try $ createNewCut (chainLookupM db) n t ph cid c +-- | Create a new block. Only produces a new cut but doesn't insert it into the +-- chain database. +-- +-- The creation time isn't checked. +-- +createNewCut + :: (HasCallStack, MonadCatch m, MonadIO m, HasChainId cid) + => (ChainValue BlockHash -> m BlockHeader) + -> Nonce + -> Time Micros + -> BlockPayloadHash + -> cid + -> Cut + -> m (T2 BlockHeader Cut) +createNewCut hdb n t pay i c = do + extension <- fromMaybeM BadAdjacents $ getCutExtension c i + work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay + (h, mc') <- extendCut c (solveWork work n t) + `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg + c' <- fromMaybeM BadAdjacents mc' + return $ T2 h c' --- -- | Create a new block. Only produces a new cut but doesn't insert it into the --- -- chain database. --- -- --- -- The creation time isn't checked. --- -- --- createNewCut --- :: (HasCallStack, MonadCatch m, MonadIO m, HasChainId cid) --- => (ChainValue BlockHash -> m BlockHeader) --- -> Nonce --- -> Time Micros --- -> BlockPayloadHash --- -> cid --- -> Cut --- -> m (T2 BlockHeader Cut) --- createNewCut hdb n t pay i c = do --- extension <- fromMaybeM BadAdjacents $ getCutExtension c i --- work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay --- (h, mc') <- extendCut c pay (solveWork work n t) --- `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg --- c' <- fromMaybeM BadAdjacents mc' --- return $ T2 h c' +-- | Solve Work. Doesn't check that the nonce and the time are valid. +-- +solveWork :: HasCallStack => WorkHeader -> Nonce -> Time Micros -> SolvedWork +solveWork w n t = + case runGetS decodeBlockHeaderWithoutHash $ SBS.fromShort $ _workHeaderBytes w of + Nothing -> error "Chainwb.Test.Cut.solveWork: Invalid work header bytes" + Just hdr -> SolvedWork + $ fromJuste + $ runGetS decodeBlockHeaderWithoutHash + $ runPutS + $ encodeBlockHeaderWithoutHash + -- After injecting the nonce and the creation time will have to do a + -- serialization roundtrip to update the Merkle hash. + -- + -- A "real" miner would inject the nonce and time without first + -- decoding the header and would hand over the header in serialized + -- form. --- -- | Solve Work. Doesn't check that the nonce and the time are valid. --- -- --- solveWork :: HasCallStack => WorkHeader -> Nonce -> Time Micros -> SolvedWork --- solveWork w n t = --- case runGetS decodeBlockHeaderWithoutHash $ SBS.fromShort $ _workHeaderBytes w of --- Nothing -> error "Chainwb.Test.Cut.solveWork: Invalid work header bytes" --- Just hdr -> SolvedWork --- $ fromJuste --- $ runGetS decodeBlockHeaderWithoutHash --- $ runPutS --- $ encodeBlockHeaderWithoutHash --- -- After injecting the nonce and the creation time will have to do a --- -- serialization roundtrip to update the Merkle hash. --- -- --- -- A "real" miner would inject the nonce and time without first --- -- decoding the header and would hand over the header in serialized --- -- form. + $ set blockCreationTime (BlockCreationTime t) + $ set blockNonce n + $ hdr --- $ set blockCreationTime (BlockCreationTime t) --- $ set blockNonce n --- $ hdr - --- -- | Build a linear chainweb (no forks). No POW or poison delay is applied. --- -- Block times are real times. --- -- --- tryMineForChain --- :: forall tbl. (HasCallStack, CanReadablePayloadCas tbl) --- => Miner --- -- ^ The miner. For testing you may use 'defaultMiner'. --- -> CutDb --- -> Cut --- -> ChainId --- -> IO (Either MineFailure (Cut, ChainId, PayloadWithOutputs)) --- tryMineForChain miner newBlockStrat webPact cutDb c cid = do --- newBlock <- throwIfNoHistory =<< _webPactNewBlock webPact cid miner newBlockStrat parent --- let outputs = newBlockToPayloadWithOutputs newBlock --- let payloadHash = _payloadWithOutputsPayloadHash outputs --- t <- getCurrentTimeIntegral --- x <- testMineWithPayloadHash wdb (Nonce 0) t payloadHash cid c --- case x of --- Right (T2 h c') -> do --- addCutHashes cutDb (cutToCutHashes Nothing c') --- { _cutHashesHeaders = HashMap.singleton (view blockHash h) h --- , _cutHashesPayloads = HashMap.singleton (view blockPayloadHash h) (payloadWithOutputsToPayloadData outputs) --- } --- return $ Right (c', cid, outputs) --- Left e -> return $ Left e --- where --- parent = ParentHeader $ c ^?! ixg cid -- parent to mine on --- wdb = view cutDbWebBlockHeaderDb cutDb +-- | Build a linear chainweb (no forks). No POW or poison delay is applied. +-- Block times are real times. +-- +tryMineForChain + :: HasCallStack + => CutDb + -> Cut + -> ChainId + -> IO (Either MineFailure (Cut, ChainId, NewPayload)) +tryMineForChain cutDb c cid = do + newBlock <- case cutDb ^?! cutDbPayloadProviders . atChain cid of + ConfiguredPayloadProvider p -> waitForChangedPayload p + DisabledPayloadProvider -> error $ "missing payload provider on chain " <> show cid + let payloadHash = _newPayloadBlockPayloadHash newBlock + t <- getCurrentTimeIntegral + x <- testMineWithPayloadHash wdb (Nonce 0) t payloadHash cid c + case x of + Right (T2 h c') -> do + addCutHashes cutDb (cutToCutHashes Nothing c') + { _cutHashesHeaders = HashMap.singleton (view blockHash h) h + , _cutHashesPayloads = + HashMap.singleton (view blockPayloadHash h) (fromJuste $ _newPayloadEncodedPayloadData newBlock) + } + return $ Right (c', cid, newBlock) + Left e -> return $ Left e + where + wdb = view cutDbWebBlockHeaderDb cutDb --- data MineFailure --- = InvalidHeader Text --- -- ^ The header is invalid, e.g. because of a bad nonce or creation time. --- | MissingParentHeader --- -- ^ A parent header is missing in the chain db --- | BadAdjacents --- -- ^ This could mean that the chain is blocked. --- deriving stock (Show) --- deriving anyclass (Exception) +data MineFailure + = InvalidHeader Text + -- ^ The header is invalid, e.g. because of a bad nonce or creation time. + | MissingParentHeader + -- ^ A parent header is missing in the chain db + | BadAdjacents + -- ^ This could mean that the chain is blocked. + deriving stock (Show) + deriving anyclass (Exception) diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index 05b11c1f68..1a4b65698d 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -102,11 +102,9 @@ mkFixtureWith pactServiceConfig baseRdb = do logLevel <- liftIO getTestLogLevel let logger = genericLogger logLevel Text.putStrLn perChain <- iforM (HashSet.toMap (chainIds v)) $ \chain () -> do - sqlite <- withTempSQLiteResource + (writeSqlite, readPool) <- withTempSQLiteResource let pdb = _bdbPayloadDb tdb - fakeRoSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sqlite) (\_ -> return ()) 10 10) - serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing pdb fakeRoSqlPool sqlite pactServiceConfig (Just $ genesisPayload chain) - liftIO $ PactService.initialPayloadState logger serviceEnv + serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing pdb readPool writeSqlite pactServiceConfig (Just $ genesisPayload chain) let mempoolCfg = validatingMempoolConfig chain v (GasLimit (Gas 150_000)) @@ -118,8 +116,8 @@ mkFixtureWith pactServiceConfig baseRdb = do let fixture = Fixture { _fixtureBlockDb = tdb , _fixtureLogger = logger - , _fixtureMempools = OnChains $ fst <$> perChain - , _fixturePacts = OnChains $ snd <$> perChain + , _fixtureMempools = ChainMap $ fst <$> perChain + , _fixturePacts = ChainMap $ snd <$> perChain } -- The mempool expires txs based on current time, but newBlock expires txs based on parent creation time. -- So by running an empty block with the creationTime set to the current time, we get these goals to align @@ -278,7 +276,7 @@ newBlockTimeoutSpec baseRdb = runResourceT $ do bip <- continueBlock fixture =<< makeEmptyBlock fixture ph return $ finalizeBlock fixture bip results - & P.alignExact ? onAllChains v (\cid -> + & P.alignExact ? tabulateChains v (\cid -> if cid == chain0 then P.alignExact ? Vector.singleton ? -- Mempool orders by GasPrice. 'buildCwCmd' sets the gas price to the transfer amount. @@ -646,7 +644,7 @@ mempoolClear Fixture{..} cid = advanceAllChainsWithTxs :: Fixture -> ChainMap [Pact.Transaction] -> IO (ChainMap (Vector TestPact5CommandResult)) advanceAllChainsWithTxs fixture txsPerChain = do - advanceAllChains fixture $ onAllChains v $ \cid ph -> do + advanceAllChains fixture $ tabulateChains v $ \cid ph -> do let txs = txsPerChain ^?! atChain cid mempoolClear fixture cid mempoolInsert fixture cid Mempool.CheckedInsert txs diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index 938f391ba0..ea58ff1c66 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -30,17 +30,17 @@ module Chainweb.Test.Pact.RemotePactTest ( tests - , mkFixture - , Fixture(..) - , HasFixture(..) - , poll - , pollWithDepth - , PollException(..) - , ClientException(..) - , _FailureResponse - , send - , local - , textContains + -- , mkFixture + -- , Fixture(..) + -- , HasFixture(..) + -- , poll + -- , pollWithDepth + -- , PollException(..) + -- , ClientException(..) + -- , _FailureResponse + -- , send + -- , local + -- , textContains ) where import Control.Concurrent.Async hiding (poll) @@ -64,7 +64,6 @@ import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO qualified as T -import Pact.JSON.Encode (getJsonText) import Data.Text.Encoding qualified as T import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Encoding qualified as TL @@ -88,10 +87,11 @@ import Test.Tasty import Test.Tasty.HUnit (testCaseSteps, testCase, assertFailure) import Pact.Core.Capabilities -import Pact.Core.ChainData (TxCreationTime(..)) +import Pact.Core.ChainData qualified as Pact +import Pact.Core.Command.Client (SubmitBatch(..)) import Pact.Core.Command.Crypto (signEd25519, exportEd25519Signature, importEd25519KeyPair, PrivateKeyBS (..)) import Pact.Core.Command.RPC (ContMsg (..)) -import Pact.Core.Command.Server qualified as Pact5 +import Pact.Core.Command.Server import Pact.Core.Command.Types import Pact.Core.DefPacts.Types import Pact.Core.Errors @@ -101,8 +101,6 @@ import Pact.Core.Hash import Pact.Core.Names import Pact.Core.PactValue import Pact.Core.SPV -import Pact.Types.API qualified as Pact -import Pact.Types.ChainId qualified as Pact import Chainweb.ChainId import Chainweb.CutDB.RestAPI.Server (someCutGetServer) @@ -111,6 +109,8 @@ import Chainweb.Mempool.Mempool (TransactionHash (..)) import Chainweb.Pact.RestAPI.Client import Chainweb.Pact.RestAPI.Server import Chainweb.Pact.Types +import Chainweb.Payload +import Chainweb.PayloadProvider.Pact import Chainweb.RestAPI.Utils (someServerApplication) import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Pact.CmdBuilder @@ -123,6 +123,9 @@ import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Mainnet (mainnet) +import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 +import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN + -- generating this cert and making an HTTP manager take quite a while relative -- to the rest of the tests, so they're shared globally. @@ -138,8 +141,8 @@ httpManager = unsafePerformIO $ HTTP.newTlsManagerWith (HTTP.mkManagerSettings d tests :: RocksDb -> TestTree tests rdb = withResource' (evaluate httpManager >> evaluate cert) $ \_ -> testGroup "Pact5 RemotePactTest" - [ testCaseSteps "crosschainTest" (crosschainTest rdb) - , sendInvalidTxsTest rdb + -- [ testCaseSteps "crosschainTest" (crosschainTest rdb) + [ sendInvalidTxsTest rdb , testCaseSteps "caplistTest" (caplistTest rdb) , testCaseSteps "pollingInvalidRequestKeyTest" (pollingInvalidRequestKeyTest rdb) , testCaseSteps "pollingConfirmationDepthTest" (pollingConfirmationDepthTest rdb) @@ -147,15 +150,13 @@ tests rdb = withResource' (evaluate httpManager >> evaluate cert) $ \_ -> , testCaseSteps "allocationTest" (allocationTest rdb) , testCaseSteps "webAuthnSignatureTest" (webAuthnSignatureTest rdb) , testCaseSteps "gasPurchaseFailureMessages" (gasPurchaseFailureMessages rdb) - , testCaseSteps "transition occurs" (transitionOccurs rdb) - , testCaseSteps "transition crosschain" (transitionCrosschain rdb) , testCaseSteps "upgradeNamespaceTests" (upgradeNamespaceTests rdb) , localTests rdb ] pollingInvalidRequestKeyTest :: RocksDb -> Step -> IO () pollingInvalidRequestKeyTest baseRdb _step = runResourceT $ do - let v = pact5InstantCpmTestVersion singletonChainGraph + let v = instantCpmTestVersion singletonChainGraph let cid = unsafeChainId 0 fx <- mkFixture v baseRdb @@ -165,7 +166,7 @@ pollingInvalidRequestKeyTest baseRdb _step = runResourceT $ do pollingConfirmationDepthTest :: RocksDb -> Step -> IO () pollingConfirmationDepthTest baseRdb _step = runResourceT $ do - let v = pact5InstantCpmTestVersion singletonChainGraph + let v = instantCpmTestVersion singletonChainGraph let cid = unsafeChainId 0 fx <- mkFixture v baseRdb @@ -185,7 +186,7 @@ pollingConfirmationDepthTest baseRdb _step = runResourceT $ do , P.match _Just ? P.fun _crResult ? P.equals (PactResultOk (PInteger 43)) ] - let expectEmpty :: (HasCallStack, Foldable t, Eq a) => t (Maybe a) -> IO () + let expectEmpty :: (HasCallStack, Foldable t, Eq a, Show a) => t (Maybe a) -> IO () expectEmpty = traverse_ (P.equals Nothing) send fx v cid [cmd1, cmd2] @@ -232,7 +233,7 @@ pollingConfirmationDepthTest baseRdb _step = runResourceT $ do crosschainTest :: RocksDb -> Step -> IO () crosschainTest baseRdb step = runResourceT $ do - let v = pact5InstantCpmTestVersion petersonChainGraph + let v = instantCpmTestVersion petersonChainGraph fx <- mkFixture v baseRdb let srcChain = unsafeChainId 0 @@ -260,7 +261,7 @@ crosschainTest baseRdb step = runResourceT $ do -- what if the source chain hasn't got the xchain transfer in a block yet? spvTxOutProof fx v targetChain srcChain initiatorReqKey - & P.fails ? P.match _FailureResponse ? P.fun responseBody + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? P.equals ("Transaction hash not found: " <> sshow initiatorReqKey) advanceAllChains_ fx @@ -269,7 +270,7 @@ crosschainTest baseRdb step = runResourceT $ do -- what if the target chain isn't aware of the source xchain transfer yet? spvTxOutProof fx v targetChain srcChain initiatorReqKey - & P.fails ? P.match _FailureResponse ? P.fun responseBody + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? P.equals "SPV target not reachable: target chain not reachable. Chainweb instance is too young" step "waiting" @@ -341,7 +342,7 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> $ set cbRPC (mkExec' "(+ 1") $ defaultCmd cid send fx v cid [cmdParseFailure] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains "Pact parse error: Expected: [')']" + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains "ParseError: Expected: [')']" , testCase "invalid hash" $ do cmdInvalidPayloadHash <- do @@ -352,7 +353,7 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> { _cmdHash = hash "fakehash" } send fx v cid [cmdInvalidPayloadHash] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdInvalidPayloadHash "Invalid transaction hash") , testCase "signature length mismatch" $ do @@ -364,7 +365,7 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> { _cmdSigs = [] } send fx v cid [cmdSignersSigsLengthMismatch1] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdSignersSigsLengthMismatch1 "Invalid transaction sigs: The number of signers and signatures do not match. Number of signers: 1. Number of signatures: 0.") cmdSignersSigsLengthMismatch2 <- do @@ -379,13 +380,13 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> _cmdSigs = [ED25519Sig "fakeSig"] } send fx v cid [cmdSignersSigsLengthMismatch2] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdSignersSigsLengthMismatch2 "Invalid transaction sigs: The number of signers and signatures do not match. Number of signers: 0. Number of signatures: 1.") , testCase "invalid signatures" $ do cmdInvalidUserSig <- mkCmdInvalidUserSig send fx v cid [cmdInvalidUserSig] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") , testCase "batches are rejected with any invalid txs" $ do @@ -394,15 +395,17 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> -- Test that [badCmd, goodCmd] fails on badCmd, and the batch is rejected. -- We just re-use a previously built bad cmd. send fx v cid [cmdInvalidUserSig, cmdGood] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains - (validationFailed 0 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains + (validationFailed 0 cmdInvalidUserSig + "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") -- Test that [goodCmd, badCmd] fails on badCmd, and the batch is rejected. -- Order matters, and the error message also indicates the position of the -- failing tx. -- We just re-use a previously built bad cmd. send fx v cid [cmdGood, cmdInvalidUserSig] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains - (validationFailed 1 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains + (validationFailed 1 cmdInvalidUserSig + "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") , testCase "multiple bad txs in batch" $ do cmdGood <- mkCmdGood @@ -410,20 +413,23 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> cmdParseFailure <- buildTextCmd v $ set cbRPC (mkExec' "(+ 1") $ defaultCmd cid + -- if any tx fails parsing, no txs even get validated send fx v cid [cmdInvalidUserSig, cmdGood, cmdParseFailure] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? P.checkAll - [ textContains (validationFailed 0 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") - , textContains (validationFailed 2 cmdParseFailure "Pact parse error: Expected: [')']") + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? P.checkAll + [ textContains (validationFailed 0 cmdInvalidUserSig + "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") + , textContains (parseFailed 2 cmdParseFailure "Expected: [')']") ] , testCase "invalid metadata" $ do cmdGood <- mkCmdGood send fx v wrongChain [cmdGood] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains - (validationFailed 0 cmdGood "Transaction metadata (chain id, chainweb version) conflicts with this endpoint") + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains + (validationFailed 0 cmdGood + "Transaction metadata (chain id, chainweb version) conflicts with this endpoint") send fx wrongV cid [cmdGood] - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals notFound404 , P.fun responseBody ? P.equals "" ] @@ -431,22 +437,25 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> let invalidCid = "invalid chain ID" cmdInvalidChain <- buildTextCmd v (defaultCmd cid & set cbChainId invalidCid) send fx v wrongChain [cmdInvalidChain] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains - (validationFailed 0 cmdInvalidChain "insert error: Unparsable ChainId") + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains + (validationFailed 0 cmdInvalidChain + "Transaction metadata (chain id, chainweb version) conflicts with this endpoint") cmdWrongV <- buildTextCmd wrongV $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) $ defaultCmd cid send fx v cid [cmdWrongV] - & P.fails ? P.match _FailureResponse ? P.fun responseBody ? textContains - (validationFailed 0 cmdWrongV "Transaction metadata (chain id, chainweb version) conflicts with this endpoint") + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains + (validationFailed 0 cmdWrongV + "Transaction metadata (chain id, chainweb version) conflicts with this endpoint") - cmdExpiredTTL <- buildTextCmd v (defaultCmd cid & cbCreationTime .~ Just (TxCreationTime 0)) + cmdExpiredTTL <- buildTextCmd v (defaultCmd cid & cbCreationTime .~ Just (Pact.TxCreationTime 0)) send fx v cid [cmdExpiredTTL] - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains - (validationFailed 0 cmdExpiredTTL "Transaction time-to-live is expired") + (validationFailed 0 cmdExpiredTTL + "Transaction time-to-live is expired") ] , testCase "cannot buy gas" $ do @@ -454,20 +463,22 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> $ set cbGasLimit (GasLimit $ Gas 100000000000000) $ defaultCmd cid send fx v cid [cmdExcessiveGasLimit] - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains - (validationFailed 0 cmdExcessiveGasLimit "Transaction gas limit exceeds block gas limit") + (validationFailed 0 cmdExcessiveGasLimit + "Transaction gas limit exceeds block gas limit") ] cmdGasPriceTooPrecise <- buildTextCmd v $ set cbGasPrice (GasPrice 0.00000000000000001) $ defaultCmd cid send fx v cid [cmdGasPriceTooPrecise] - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains - (validationFailed 0 cmdGasPriceTooPrecise "insert error: This transaction's gas price: 0.00000000000000001 is not correctly rounded. It should be rounded to at most 12 decimal places.") + (validationFailed 0 cmdGasPriceTooPrecise + "This transaction's gas price (0.00000000000000001) is not correctly rounded. It should be rounded to at most 12 decimal places.") ] cmdNotEnoughGasFunds <- buildTextCmd v @@ -475,22 +486,22 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> $ set cbGasLimit (GasLimit (Gas 10_000)) $ defaultCmd cid send fx v cid [cmdNotEnoughGasFunds] - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains - (validationFailed 0 cmdNotEnoughGasFunds "Attempt to buy gas failed with: " <> sshow (_cmdHash cmdNotEnoughGasFunds) <> " Failed to buy gas: Insufficient funds") + (validationFailed 0 cmdNotEnoughGasFunds + "Failed to buy gas: Insufficient funds") ] cmdInvalidSender <- buildTextCmd v $ set cbSender "invalid-sender" $ defaultCmd cid send fx v cid [cmdInvalidSender] - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains - -- TODO: the full error is far more verbose than this, - -- perhaps that's something we should fix. - (validationFailed 0 cmdInvalidSender "Attempt to buy gas failed") + (validationFailed 0 cmdInvalidSender + "Failed to buy gas: No value found in table coin_coin-table for key") ] ] @@ -503,13 +514,14 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> ] where - v = pact5InstantCpmTestVersion petersonChainGraph - wrongV = pact5InstantCpmTestVersion twentyChainGraph + v = instantCpmTestVersion petersonChainGraph + wrongV = instantCpmTestVersion twentyChainGraph cid = unsafeChainId 0 wrongChain = unsafeChainId 1 validationFailed i cmd msg = "Transaction " <> sshow (_cmdHash cmd) <> " at index " <> sshow @Int i <> " failed with: " <> msg + parseFailed i cmd msg = "Transaction " <> sshow (_cmdHash cmd) <> " at index " <> sshow @Int i <> " has invalid Pact code: " <> msg mkCmdInvalidUserSig = mkCmdGood <&> set cmdSigs [ED25519Sig "fakeSig"] @@ -520,7 +532,7 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> caplistTest :: RocksDb -> Step -> IO () caplistTest baseRdb step = runResourceT $ do - let v = pact5InstantCpmTestVersion petersonChainGraph + let v = instantCpmTestVersion petersonChainGraph fx <- mkFixture v baseRdb let cid = unsafeChainId 0 @@ -555,7 +567,9 @@ caplistTest baseRdb step = runResourceT $ do >>= P.alignExact ? List.singleton ? P.match _Just ? P.checkAll [ P.fun _crResult ? P.match (_PactResultOk . _PString) ? P.equals "Write succeeded" - , P.fun _crMetaData ? P.match (_Just . A._Object . at "blockHash") ? P.match _Just P.succeed + , P.fun _crMetaData + ? P.match (_Just . A._Object . at "blockHash") + ? P.match (_Just . A._String . b64UrlNoPaddingPrism) P.succeed ] @@ -579,7 +593,7 @@ allocation02KeyPair' = allocationTest :: RocksDb -> (String -> IO ()) -> IO () allocationTest rdb step = runResourceT $ do - let v = pact5InstantCpmTestVersion petersonChainGraph + let v = instantCpmTestVersion petersonChainGraph let cid = unsafeChainId 0 fx <- mkFixture v rdb @@ -596,7 +610,7 @@ allocationTest rdb step = runResourceT $ do P.alignExact [ P.match _Just ? P.checkAll [ P.fun _crResult ? P.match _PactResultOk ? P.succeed - , P.fun _crEvents ? P.startingWith + , P.fun _crEvents ? P.match _head ? (event (P.equals "RELEASE_ALLOCATION") (P.equals [PString "allocation00", PDecimal 1000000]) @@ -607,7 +621,7 @@ allocationTest rdb step = runResourceT $ do buildTextCmd v (set cbRPC (mkExec' "(coin.details \"allocation00\")") $ defaultCmd cid) >>= local fx v cid Nothing Nothing Nothing - >>= P.match _Pact5LocalResultLegacy + >>= P.match _LocalResultLegacy ? P.fun _crResult ? P.match _PactResultOk ? P.match _PObject @@ -625,7 +639,7 @@ allocationTest rdb step = runResourceT $ do $ set cbSigners [mkEd25519Signer' allocation01KeyPair [], mkEd25519Signer' sender00 []] $ defaultCmd cid) >>= local fx v cid Nothing Nothing Nothing - >>= P.match _Pact5LocalResultLegacy + >>= P.match _LocalResultLegacy ? P.fun _crResult ? P.match _PactResultErr ? P.checkAll @@ -639,7 +653,7 @@ allocationTest rdb step = runResourceT $ do $ set cbSigners [mkEd25519Signer' allocation01KeyPair [], mkEd25519Signer' sender00 []] $ defaultCmd cid) >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - >>= P.match (_Pact5LocalResultWithWarns . _1) ? + >>= P.match (_LocalResultWithWarns . _1) ? P.fun _crResult ? P.match _PactResultErr ? P.checkAll [ P.fun _peType ? P.equals ? ErrorType "TxFailure" @@ -673,7 +687,7 @@ allocationTest rdb step = runResourceT $ do buildTextCmd v (set cbRPC (mkExec' "(coin.details \"allocation02\")") $ defaultCmd cid) >>= local fx v cid Nothing Nothing Nothing - >>= P.match _Pact5LocalResultLegacy + >>= P.match _LocalResultLegacy ? P.fun _crResult ? P.match _PactResultOk ? P.match _PObject @@ -685,7 +699,7 @@ allocationTest rdb step = runResourceT $ do gasPurchaseFailureMessages :: RocksDb -> Step -> IO () gasPurchaseFailureMessages rdb _step = runResourceT $ do - let v = pact5InstantCpmTestVersion petersonChainGraph + let v = instantCpmTestVersion petersonChainGraph let cid = unsafeChainId 0 fx <- mkFixture v rdb @@ -704,17 +718,17 @@ gasPurchaseFailureMessages rdb _step = runResourceT $ do $ defaultCmd cid local fx v cid (Just PreflightSimulation) Nothing Nothing cmd - >>= P.match _Pact5LocalResultWithWarns + >>= P.match _LocalResultWithWarns ? P.fun fst ? P.fun _crResult ? P.match _PactResultErr ? P.checkAll - [ P.fun _peType ? P.equals ? ErrorType "EvalError" + [ P.fun _peType ? P.equals ? ErrorType "TxFailure" , P.fun _peMsg ? P.fun _boundedText ? textContains "Failed to buy gas: Insufficient funds" ] send fx v cid [cmd] - & P.fails + & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains "Failed to buy gas: Insufficient funds" @@ -734,113 +748,21 @@ gasPurchaseFailureMessages rdb _step = runResourceT $ do $ defaultCmd cid local fx v cid (Just PreflightSimulation) Nothing Nothing cmd - >>= P.match _Pact5LocalResultWithWarns + >>= P.match _LocalResultWithWarns ? P.fun fst ? P.fun _crResult ? P.match _PactResultErr ? P.checkAll [ P.fun _peType ? P.equals ? ErrorType "EvalError" - , P.fun _peMsg ? P.fun _boundedText ? textContains "Failed to buy gas: Multiple gas payer capabilities" + , P.fun _peMsg ? P.fun _boundedText + ? textContains "Failed to buy gas: multiple gas payer capabilities in signers list" ] send fx v cid [cmd] - & P.fails + & P.throws ? P.match _FailureResponse ? P.fun responseBody - ? textContains "Failed to buy gas: Multiple gas payer capabilities" - -transitionOccurs :: RocksDb -> Step -> IO () -transitionOccurs rdb _step = runResourceT $ do - let v = instantCpmTransitionTestVersion petersonChainGraph - let cid = unsafeChainId 0 - fx <- mkFixture v rdb - - liftIO $ do - checkPactVersion fx v cid >>= P.equals Pact4 - forM_ @_ @_ @Word [1..17] $ \i -> do - advanceAllChains_ fx - -- index trick to show which iteration fails, if any - (i,) <$> checkPactVersion fx v cid >>= P.equals (i, Pact4) - advanceAllChains_ fx - checkPactVersion fx v cid >>= P.equals Pact5 - --- | Test that xchains work across the Pact4->Pact4 transition boundary. --- This is mostly the same as 'spvTest', except it waits for the transition. -transitionCrosschain :: RocksDb -> Step -> IO () -transitionCrosschain rdb step = runResourceT $ do - let v = instantCpmTransitionTestVersion petersonChainGraph - let srcChain = unsafeChainId 0 - let targetChain = unsafeChainId 9 - fx <- mkFixture v rdb - - let checkIsVersion pv = do - checkPactVersion fx v srcChain >>= P.equals pv - checkPactVersion fx v targetChain >>= P.equals pv - - liftIO $ do - checkIsVersion Pact4 - - step "xchain initiate" - initiator <- buildTextCmd v - $ set cbSigners - [ mkEd25519Signer' sender00 - [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] - , CapToken (QualifiedName "TRANSFER_XCHAIN" (ModuleName "coin" Nothing)) - [ PString "sender00" - , PString "sender01" - , PDecimal 1.0 - , PString (chainIdToText targetChain) - ] - ] - ] - $ set cbRPC (mkExec ("(coin.transfer-crosschain \"sender00\" \"sender01\" (read-keyset 'k) \"" <> chainIdToText targetChain <> "\" 1.0)") (mkKeySetData "k" [sender01])) - $ defaultCmd srcChain - - send fx v srcChain [initiator] - let initiatorReqKey = cmdToRequestKey initiator - advanceAllChains_ fx - [Just sendCr] <- pollWithDepth fx v srcChain [initiatorReqKey] (Just (ConfirmationDepth 0)) - let cont = fromMaybe (error "missing continuation") (_crContinuation sendCr) - - step "waiting until pact5 transition" - - step "... performing transition" - replicateM_ 16 $ advanceAllChains_ fx - checkIsVersion Pact4 - advanceAllChains_ fx - checkIsVersion Pact5 - - TransactionOutputProofB64 spvProof <- spvTxOutProof fx v targetChain srcChain initiatorReqKey - let contMsg = ContMsg - { _cmPactId = _peDefPactId cont - , _cmStep = succ $ _peStep cont - , _cmRollback = _peStepHasRollback cont - , _cmData = PUnit - , _cmProof = Just (ContProof (T.encodeUtf8 spvProof)) - } - step "xchain recv" - - recv <- buildTextCmd v - $ set cbRPC (mkCont contMsg) - $ defaultCmd targetChain - send fx v targetChain [recv] - let recvReqKey = cmdToRequestKey recv - advanceAllChains_ fx - poll fx v targetChain [recvReqKey] - >>= P.match (_head . _Just) - ? P.checkAll - [ P.fun _crResult ? P.match _PactResultOk P.succeed - , P.fun _crEvents ? P.alignExact - [ P.succeed - , P.checkAll - [ P.fun _peName ? P.equals "TRANSFER_XCHAIN_RECD" - , P.fun _peArgs ? P.equals - [PString "", PString "sender01", PDecimal 1.0, PString (chainIdToText srcChain)] - ] - , P.fun _peName ? P.equals "X_RESUME" - , P.succeed - ] - ] + ? textContains "Failed to buy gas: multiple gas payer capabilities in signers list" return () @@ -848,7 +770,7 @@ transitionCrosschain rdb step = runResourceT $ do -- by the pact service. webAuthnSignatureTest :: RocksDb -> Step -> IO () webAuthnSignatureTest rdb _step = runResourceT $ do - let v = pact5InstantCpmTestVersion petersonChainGraph + let v = instantCpmTestVersion petersonChainGraph let cid = unsafeChainId 0 fx <- mkFixture v rdb liftIO $ do @@ -863,7 +785,7 @@ webAuthnSignatureTest rdb _step = runResourceT $ do localTests :: RocksDb -> TestTree localTests baseRdb = let - v = pact5InstantCpmTestVersion petersonChainGraph + v = instantCpmTestVersion petersonChainGraph cid = unsafeChainId 0 in testGroup "tests for local" [ testCase "ordinary txs" $ runResourceT $ do @@ -887,11 +809,11 @@ localTests baseRdb = let ] buildTextCmd v (defaultCmd cid) >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - >>= P.match _Pact5LocalResultWithWarns ? P.fun fst ? expectation + >>= P.match _LocalResultWithWarns ? P.fun fst ? expectation buildTextCmd v (defaultCmd cid) >>= local fx v cid (Just PreflightSimulation) (Just NoVerify) Nothing - >>= P.match _Pact5LocalResultWithWarns ? P.fun fst ? expectation + >>= P.match _LocalResultWithWarns ? P.fun fst ? expectation , testCase "signature with the wrong key" $ runResourceT $ do fx <- mkFixture v baseRdb @@ -908,7 +830,7 @@ localTests baseRdb = let <&> set cmdSigs [sender01Sig] -- preflight mode, verify signatures >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"The signature at position 0 is invalid: invalid ed25519 signature.\"]" ] @@ -923,7 +845,7 @@ localTests baseRdb = let <&> set cmdSigs [sender01Sig] -- non-preflight mode, verify signatures >>= local fx v cid Nothing Nothing Nothing - & P.fails ? P.match _FailureResponse + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"The signature at position 0 is invalid: invalid ed25519 signature.\"]" @@ -942,7 +864,7 @@ localTests baseRdb = let let wrongChain = unsafeChainId maxBound buildTextCmd v (defaultCmd wrongChain) >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Chain id mismatch\"]" ] @@ -955,7 +877,7 @@ localTests baseRdb = let let invalidChain = "not a real chain" buildTextCmd v (defaultCmd cid & set cbChainId invalidChain) >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Chain id mismatch\"]" ] @@ -966,21 +888,21 @@ localTests baseRdb = let buildTextCmd v (set cbGasLimit (GasLimit $ Gas 100000000000000) $ defaultCmd cid) >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Transaction Gas limit exceeds block gas limit\"]" ] buildTextCmd v (set cbGasPrice (GasPrice 0.00000000000000001) $ defaultCmd cid) >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Gas price decimal precision too high\"]" ] buildTextCmd mainnet (defaultCmd cid) >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Network id mismatch\"]" ] @@ -988,7 +910,7 @@ localTests baseRdb = let let sigs' = replicate 101 $ mkEd25519Signer' sender00 [] buildTextCmd v (defaultCmd cid & set cbSigners sigs') >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - & P.fails ? P.match _FailureResponse ? P.checkAll + & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Signature list size too big\"]" ] @@ -998,7 +920,7 @@ localTests baseRdb = let & set cbGasPrice (GasPrice 10_000_000_000) & set cbGasLimit (GasLimit (Gas 10_000))) >>= local fx v cid (Just PreflightSimulation) Nothing Nothing - >>= P.match (_Pact5LocalResultWithWarns . _1) + >>= P.match (_LocalResultWithWarns . _1) ? P.fun _crResult -- TODO: a more detailed check here than "is an error" might be nice ? P.match _PactResultErr P.succeed @@ -1007,7 +929,7 @@ localTests baseRdb = let startBalance <- buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) >>= local fx v cid Nothing Nothing (Just (RewindDepth 0)) <&> unsafeHeadOf - ? _Pact5LocalResultLegacy + ? _LocalResultLegacy . to _crResult . _PactResultOk . _PObject . at "balance" . _Just @@ -1036,7 +958,7 @@ localTests baseRdb = let buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) >>= local fx v cid Nothing Nothing Nothing - >>= P.match _Pact5LocalResultLegacy + >>= P.match _LocalResultLegacy ? P.checkAll [ hasBalance ? P.lt startBalance , hasBlockHeight (P.equals 3) @@ -1044,7 +966,7 @@ localTests baseRdb = let buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) >>= local fx v cid Nothing Nothing (Just (RewindDepth 0)) - >>= P.match _Pact5LocalResultLegacy + >>= P.match _LocalResultLegacy ? P.checkAll [ hasBalance ? P.lt startBalance , hasBlockHeight (P.equals 3) @@ -1052,7 +974,7 @@ localTests baseRdb = let buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) >>= local fx v cid Nothing Nothing (Just (RewindDepth 1)) - >>= P.match _Pact5LocalResultLegacy + >>= P.match _LocalResultLegacy ? P.checkAll [ hasBalance ? P.equals startBalance , hasBlockHeight (P.equals 2) @@ -1060,7 +982,7 @@ localTests baseRdb = let buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) >>= local fx v cid Nothing Nothing (Just (RewindDepth 2)) - >>= P.match _Pact5LocalResultLegacy + >>= P.match _LocalResultLegacy ? P.checkAll [ hasBalance ? P.equals startBalance , hasBlockHeight (P.equals 1) @@ -1068,11 +990,10 @@ localTests baseRdb = let buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) >>= local fx v cid Nothing Nothing (Just (RewindDepth 3)) - >>= P.match _Pact5LocalResultLegacy - ? P.checkAll - [ hasBalance ? P.equals startBalance - , hasBlockHeight (P.equals 1) - ] + & P.throws + ? P.match _FailureResponse + ? P.fun responseBody + ? textContains "No block exists at the given rewind depth" , withResourceT (mkFixture v baseRdb) $ \fx -> testCase "local continuation" $ do let code = "(namespace 'free)(module m G (defcap G () true) (defpact p () (step (yield { \"a\" : (+ 1 1) })) (step (resume { \"a\" := a } a))))(free.m.p)" @@ -1088,15 +1009,16 @@ localTests baseRdb = let $ set cbRPC (mkCont (mkContMsg defPactId 1)) $ defaultCmd cid local fx v cid Nothing Nothing Nothing continuer - >>= P.match _Pact5LocalResultLegacy ? P.fun _crResult + >>= P.match _LocalResultLegacy ? P.fun _crResult ? P.match _PactResultOk ? P.equals (PInteger 2) ] + pollingMetadataTest :: RocksDb -> Step -> IO () pollingMetadataTest baseRdb _step = runResourceT $ do - let v = pact5InstantCpmTestVersion singletonChainGraph + let v = instantCpmTestVersion singletonChainGraph let cid = unsafeChainId 0 fx <- mkFixture v baseRdb @@ -1115,16 +1037,15 @@ pollingMetadataTest baseRdb _step = runResourceT $ do poll fx v cid [cmdToRequestKey cmd] >>= P.alignExact [ P.match _Just ? P.fun _crMetaData ? P.match _Just ? P.match A._Object ? P.alignExact ? A.KeyMap.fromList - [ ("blockHash", P.match A._String P.succeed) + [ ("blockHash", P.match (A._String . b64UrlNoPaddingPrism) P.succeed) , ("blockHeight", P.equals (A.Number 2)) - , ("blockTime", P.match A._Number P.succeed) - , ("prevBlockHash", P.match A._String P.succeed) + , ("blockPayloadHash", P.match (A._String . b64UrlNoPaddingPrism) P.succeed) ] ] upgradeNamespaceTests :: RocksDb -> Step -> IO () upgradeNamespaceTests baseRdb _step = runResourceT $ do - let v = pact5InstantCpmTestVersion singletonChainGraph + let v = instantCpmTestVersion singletonChainGraph let cid = unsafeChainId 0 fx <- mkFixture v baseRdb @@ -1166,7 +1087,7 @@ upgradeNamespaceTests baseRdb _step = runResourceT $ do ? textContains "Loaded module ns" ----------------------------------------------------- +-- ---------------------------------------------------- data Fixture = Fixture { _cutFixture :: CutFixture.Fixture @@ -1184,16 +1105,20 @@ instance HasFixture Fixture where instance HasFixture a => HasFixture (IO a) where remotePactTestFixture = (>>= remotePactTestFixture) +instantCpmTestVersionGenesis :: ChainId -> PayloadWithOutputs +instantCpmTestVersionGenesis chain + | chain == unsafeChainId 0 = IN0.payloadBlock + | otherwise = INN.payloadBlock + mkFixture :: ChainwebVersion -> RocksDb -> ResourceT IO Fixture mkFixture v baseRdb = do - fx <- CutFixture.mkFixture v defaultPactServiceConfig baseRdb + fx <- CutFixture.mkFixture v instantCpmTestVersionGenesis defaultPactServiceConfig { _pactBlockRefreshInterval = 10_000 } baseRdb logger <- liftIO getTestLogger let mkSomePactServerData cid = PactServerData - { _pactServerDataCutDb = fx ^. CutFixture.fixtureCutDb - , _pactServerDataMempool = fx ^. CutFixture.fixtureMempools ^?! atChain cid + { _pactServerDataMempool = fx ^?! CutFixture.fixtureMempools . atChain cid , _pactServerDataLogger = logger - , _pactServerDataPact = mkPactExecutionService (fx ^. CutFixture.fixturePactQueues ^?! atChain cid) + , _pactServerDataPact = fx ^?! CutFixture.fixturePacts . atChain cid } let pactServer = somePactServers v $ List.map (\cid -> (cid, mkSomePactServerData cid)) (HashSet.toList (chainIds v)) let cutGetServer = someCutGetServer v (fx ^. CutFixture.fixtureCutDb) @@ -1243,14 +1168,18 @@ pollWithDepth pollWithDepth fx v cid rks mConfirmationDepth = do clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx let rksNel = NE.fromList rks - pollResult <- runClientM (pactPollApiClient v cid mConfirmationDepth (Pact5.PollRequest rksNel)) clientEnv + pollResult <- runClientM (pactPollApiClient v cid mConfirmationDepth (PollRequest rksNel)) clientEnv case pollResult of Left e -> do throwM (PollException (show e)) - Right (Pact5.PollResponse response) -> do + Right (PollResponse response) -> do -- the poll should only return results for commands -- that were polled for - response & P.fun HashMap.keys ? traverse_ ? P.fun (\rk -> elem rk rks) ? P.bool + response + & P.fun HashMap.keys + ? traverse_ + ? P.fun (\rk -> elem rk rks) + ? P.equals True return (rks <&> (\rk -> HashMap.lookup rk response)) @@ -1273,13 +1202,13 @@ send :: (HasCallStack, HasFixture fx) -> IO () send fx v cid cmds = do let commands = NE.fromList $ toListOf each cmds - let batch = Pact4.SubmitBatch (fmap toPact4Command commands) + let batch = SendRequest (SubmitBatch commands) clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx sendResult <- runClientM (pactSendApiClient v cid batch) clientEnv case sendResult of Left e -> do throwM (ClientException callStack e) - Right (Pact4.RequestKeys (fmap toPact5RequestKey -> response)) -> do + Right (SendResponse (RequestKeys response)) -> do -- the returned request keys should always be exactly the hashes -- of the commands response & P.equals (cmdToRequestKey <$> commands) @@ -1297,7 +1226,9 @@ local fx v cid preflight sigVerify depth cmd = do -- send a single local request and return the result -- clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx - r <- runClientM (pactLocalWithQueryApiClient v cid preflight sigVerify depth (toPact4Command cmd)) clientEnv + r <- runClientM + (pactLocalApiClient v cid preflight sigVerify depth cmd) + clientEnv either (throwM . ClientException callStack) return r spvTxOutProof :: (HasCallStack, HasFixture fx) @@ -1309,9 +1240,10 @@ spvTxOutProof :: (HasCallStack, HasFixture fx) -> IO TransactionOutputProofB64 spvTxOutProof fx v trgChain srcChain reqKey = do clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx - let pact4TrgChain = Pact4.ChainId $ toText trgChain - let pact4ReqKey = toPact4RequestKey reqKey - r <- runClientM (pactSpvApiClient v srcChain (SpvRequest pact4ReqKey pact4TrgChain)) clientEnv + let pactTrgChain = Pact.ChainId $ toText trgChain + r <- runClientM + (pactSpvApiClient v srcChain (SpvRequest reqKey pactTrgChain)) + clientEnv either (throwM . ClientException callStack) return r pactDeadBeef :: RequestKey @@ -1323,24 +1255,3 @@ textContains expectedStr actualStr | expectedStr `T.isInfixOf` actualStr = P.succeed actualStr | otherwise = P.fail ("String containing: " <> PP.pretty expectedStr) actualStr - -checkPactVersion :: Fixture -> ChainwebVersion -> ChainId -> IO PactVersion -checkPactVersion fx v cid = do - cmd <- buildTextCmd v - $ set cbRPC (mkExec' "(do 1)") - $ defaultCmd cid - r <- local fx v cid (Just PreflightSimulation) Nothing Nothing cmd - case r of - LocalResultLegacy (getJsonText -> txt) -> do - if extractError txt == Just "Cannot resolve do" - then return Pact4 - else return Pact5 - LocalResultWithWarns (getJsonText -> txt) _warns -> do - if extractError txt == Just "Cannot resolve do" - then return Pact4 - else return Pact5 - anythingElse -> do - assertFailure $ "checkPactVersion: Unexpected result: " ++ show anythingElse - where - extractError :: Text -> Maybe Text - extractError json = json ^? A.key "result" . A.key "error" . A.key "message" . A._String diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index 8952741cc5..1897bea99a 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -106,17 +106,16 @@ tests baseRdb = testGroup "Pact5 TransactionExecTest" -- PactServiceTest or RemotePactTest. readFromAfterGenesis :: ChainwebVersion -> RocksDb -> (BlockEnv -> BlockHandle -> IO a) -> IO a readFromAfterGenesis ver rdb act = runResourceT $ do - sql <- withTempSQLiteResource + (writeSql, readPool) <- withTempSQLiteResource tdb <- mkTestBlockDb ver rdb -- fake ro-sql pool, assuming we're using this single-threaded - roSqlPool <- liftIO $ Pool.newPool (Pool.defaultPoolConfig (return sql) (\_ -> return ()) 10 10) logger <- liftIO $ testLogger - serviceEnv <- withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) roSqlPool sql (defaultPactServiceConfig PIN0.payloadBlock) + serviceEnv <- withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) readPool writeSql defaultPactServiceConfig (Just PIN0.payloadBlock) liftIO $ do initialPayloadState logger serviceEnv fakeParentCreationTime <- mkFakeParentCreationTime throwIfNoHistory =<< - readFrom logger ver cid sql fakeParentCreationTime + readFrom logger ver cid writeSql fakeParentCreationTime (Parent (gh ver cid ^. rankedBlockHash)) act diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index 3a08a751dc..31111eccc6 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -276,7 +276,7 @@ jsonTestCases f = , testProperty "PeerInfo" $ f @PeerInfo , testProperty "Peer" $ f @Peer , testProperty "NetworkId" $ f @NetworkId - , testProperty "ChainDatabaseGcConfig" $ f @ChainDatabaseGcConfig + -- , testProperty "ChainDatabaseGcConfig" $ f @ChainDatabaseGcConfig , testProperty "MerkleRootType" $ f @MerkleRootType , testProperty "ChainwebConfiguration" $ f @ChainwebConfiguration , testProperty "Probability" $ f @Probability @@ -299,7 +299,7 @@ jsonTestCases f = , testProperty "SpvSubjectType" $ f @SpvAlgorithm , testProperty "SpvSubjectIdentifier" $ f @SpvSubjectIdentifier , testProperty "Spv2Request" $ f @Spv2Request - , testProperty "TransactionProof" $ f @(TransactionProof ChainwebMerkleHashAlgorithm) + -- , testProperty "TransactionProof" $ f @(TransactionProof ChainwebMerkleHashAlgorithm) , testProperty "TransactionOutputProof" $ f @(TransactionOutputProof ChainwebMerkleHashAlgorithm) , testProperty "PayloadProof" $ f @(PayloadProof ChainwebMerkleHashAlgorithm) , testProperty "SomePayloadProof" $ f @(SomePayloadProof) @@ -478,7 +478,7 @@ hasTextRepresentationTests = testGroup "HasTextRepresentation roundtrips" , testProperty "P2pNetworkId" $ prop_iso' @_ @NetworkId fromText toText , testProperty "Transaction" $ prop_iso' @_ @Transaction fromText toText , testProperty "TransactionOutput" $ prop_iso' @_ @TransactionOutput fromText toText - , testProperty "ChainDatabaseGcConfig" $ prop_iso' @_ @ChainDatabaseGcConfig fromText toText + -- , testProperty "ChainDatabaseGcConfig" $ prop_iso' @_ @ChainDatabaseGcConfig fromText toText , testProperty "MerkleRootType" $ prop_iso' @_ @MerkleRootType fromText toText , testProperty "Fork" $ prop_iso' @_ @Fork fromText toText ] diff --git a/test/unit/Chainweb/Test/SPV/EventProof.hs b/test/unit/Chainweb/Test/SPV/EventProof.hs index cd2752b678..320bb38813 100644 --- a/test/unit/Chainweb/Test/SPV/EventProof.hs +++ b/test/unit/Chainweb/Test/SPV/EventProof.hs @@ -5,6 +5,7 @@ {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Test.SPV.EventProof @@ -39,6 +40,7 @@ import Data.Aeson.Types import Data.Bifunctor import Data.Decimal import Data.MerkleLog +import Data.MerkleLog.V1 qualified as V1 import qualified Data.Text as T import qualified Data.Vector as V @@ -179,14 +181,14 @@ proof_properties = testGroup "merkle proof properties" ] prop_merkleProof_run :: MerkleHashAlgorithm a => MerkleProof a -> Bool -prop_merkleProof_run p = case runMerkleProof p of !_ -> True +prop_merkleProof_run p = case V1.runMerkleProof p of !_ -> True prop_eventsProof_run :: forall a . MerkleHashAlgorithm a => Property prop_eventsProof_run = forAll (arbitraryEventsProof @a) $ \p -> - case runMerkleProof (_payloadProofBlob p) of !_ -> True + case V1.runMerkleProof (_payloadProofBlob p) of !_ -> True prop_eventsProof_run2 :: forall a @@ -294,4 +296,3 @@ eventProof_0 = [aesonQQ| "rootType": "blockEvents" } |] - diff --git a/test/unit/Chainweb/Test/Sync/WebBlockHeaderStore.hs b/test/unit/Chainweb/Test/Sync/WebBlockHeaderStore.hs index 653a6b7b09..0575c6561a 100644 --- a/test/unit/Chainweb/Test/Sync/WebBlockHeaderStore.hs +++ b/test/unit/Chainweb/Test/Sync/WebBlockHeaderStore.hs @@ -27,6 +27,7 @@ import Control.Concurrent import Control.Concurrent.Async import Control.Monad import Control.Monad.IO.Class +import Control.Monad.Trans.Resource import Data.Hashable import Data.IORef @@ -50,6 +51,7 @@ import Data.TaskMap import Chainweb.Storage.Table import qualified Chainweb.Storage.Table.HashMap as HashMapTable +import Chainweb.Utils import P2P.TaskQueue @@ -128,13 +130,14 @@ testQueueServer limit q = forM_ [0..] $ session_ limit q (\_ _ -> return ()) -- TODO provide test with actual block header db -withNoopQueueServer :: (PQueue (Task env a) -> IO b) -> IO b -withNoopQueueServer a = do - q <- newEmptyPQueue +withNoopQueueServer :: ResourceT IO (PQueue (Task env a)) +withNoopQueueServer = do + q <- liftIO newEmptyPQueue let failTask = do task <- pQueueRemove q putIVar (_taskResult task) $ Left $ [] - withAsync (forever failTask) $ const $ a q + _ <- withAsyncR (forever failTask) + return q startNoopQueueServer :: IO (Async (), PQueue (Task env a)) startNoopQueueServer = do diff --git a/test/unit/Chainweb/Test/TreeDB.hs b/test/unit/Chainweb/Test/TreeDB.hs index 19425ce508..f22fb9338f 100644 --- a/test/unit/Chainweb/Test/TreeDB.hs +++ b/test/unit/Chainweb/Test/TreeDB.hs @@ -43,6 +43,7 @@ import Test.Tasty.QuickCheck import Chainweb.BlockHeader.Internal import Chainweb.BlockHeader.Validation +import Chainweb.Parent import Chainweb.Test.Utils import Chainweb.Test.Utils.BlockHeader import Chainweb.TreeDB diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index 318ff72e09..27a83aab9f 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -56,22 +56,6 @@ import qualified Chainweb.Test.Mempool.Sync (tests) import qualified Chainweb.Test.MinerReward (tests) import qualified Chainweb.Test.Mining (tests) import qualified Chainweb.Test.Misc (tests) -import qualified Chainweb.Test.Pact4.Checkpointer (tests) -import qualified Chainweb.Test.Pact4.DbCacheTest (tests) -import qualified Chainweb.Test.Pact4.GrandHash (tests) -import qualified Chainweb.Test.Pact4.ModuleCacheOnRestart (tests) -import qualified Chainweb.Test.Pact4.NoCoinbase (tests) -import qualified Chainweb.Test.Pact4.PactExec (tests) -import qualified Chainweb.Test.Pact4.PactMultiChainTest (tests) -import qualified Chainweb.Test.Pact4.PactReplay (tests) -import qualified Chainweb.Test.Pact4.PactSingleChainTest (tests) -import qualified Chainweb.Test.Pact4.RemotePactTest (tests) -import qualified Chainweb.Test.Pact4.RewardsTest (tests) -import qualified Chainweb.Test.Pact4.SPV (tests) -import qualified Chainweb.Test.Pact4.SQLite (tests) -import qualified Chainweb.Test.Pact4.TTL (tests) -import qualified Chainweb.Test.Pact4.TransactionTests (tests) -import qualified Chainweb.Test.Pact4.VerifierPluginTest (tests) import qualified Chainweb.Test.Pact.CheckpointerTest import qualified Chainweb.Test.Pact.HyperlanePluginTests import qualified Chainweb.Test.Pact.PactServiceTest From 526050f4fb70f49e69dee1966da738c16ee86d74 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 24 Apr 2025 12:45:58 -0400 Subject: [PATCH 120/378] Update Pact pin --- cabal.project | 2 +- src/Chainweb/Pact/TransactionExec.hs | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 6e8d992ae6..cccbed7eb5 100644 --- a/cabal.project +++ b/cabal.project @@ -101,7 +101,7 @@ source-repository-package source-repository-package type: git location: https://github.com/kadena-io/pact-5.git - tag: 166fdba5e6b50a5528eba72fc8e1ca835538899d + tag: 18a8a1c192f30c9f22561a834478a7a664fcf4a3 --sha256: 15bzwmhjh9733ngbqp83n95fxsncscsx6l144y964ydii47ri3i8 source-repository-package diff --git a/src/Chainweb/Pact/TransactionExec.hs b/src/Chainweb/Pact/TransactionExec.hs index b052d94a08..561c23146f 100644 --- a/src/Chainweb/Pact/TransactionExec.hs +++ b/src/Chainweb/Pact/TransactionExec.hs @@ -531,7 +531,11 @@ runGenesisPayload logger db spv ctx cmd = do (runTransactionM (runPayload Transactional - (Set.singleton FlagDisableRuntimeRTC) + (Set.unions + [ Set.singleton FlagDisableRuntimeRTC + , guardDisablePact51Flags ctx + , guardDisablePact52Flags ctx + ]) db spv [ CapToken (QualifiedName "GENESIS" (ModuleName "coin" Nothing)) [] @@ -951,3 +955,9 @@ guardDisablePact51Flags :: BlockCtx -> Set ExecutionFlag guardDisablePact51Flags txCtx | guardCtx chainweb228Pact txCtx = Set.empty | otherwise = Set.singleton FlagDisablePact51 + +-- TODO: PP, make sure this is right +guardDisablePact52Flags :: BlockCtx -> Set ExecutionFlag +guardDisablePact52Flags txCtx + | guardCtx chainweb229Pact txCtx = Set.empty + | otherwise = Set.singleton FlagDisablePact52 From da0c963c2c221136e3ba0387218779ca928a88eb Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 24 Apr 2025 12:45:58 -0400 Subject: [PATCH 121/378] Add txInvalidErrorToOnChainError Change-Id: Id0000000c973c316245ddcb50f7aa0a68c4856e4 --- src/Chainweb/Pact/PactService.hs | 14 +++++-------- src/Chainweb/Pact/Types.hs | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index af42e3f795..b1eb807097 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -388,12 +388,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do { _crReqKey = requestKey , _crTxId = Nothing , _crResult = Pact.PactResultErr $ - Pact.PactOnChainError - -- the only legal error type, once chainweaver is really gone, we - -- can use a real error type - (Pact.ErrorType "EvalError") - (Pact.mkBoundedText $ undefined) -- TODO: PP prettyPact5GasPurchaseFailure err) - (Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin (Pact.LineInfo 0)) + txInvalidErrorToOnChainPactError err , _crGas = cwtx ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmGasLimit . Pact._GasLimit , _crLogs = Nothing @@ -749,9 +744,10 @@ execPreInsertCheckReq logger serviceEnv txs = do gasEnv <- Pact.mkTableGasEnv (Pact.MilliGasLimit mempty) Pact.GasLogsDisabled Pact.buyGas logger' gasEnv pactDb noMiner bctx (view Pact.payloadObj <$> tx) case result of - Left _err -> do - -- TODO: PP - throwError $ InsertErrorBuyGas $ undefined -- _prettyGasPurchaseFailure $ BuyGasError (Pact.cmdToRequestKey tx) err + Left err -> do + -- note that this is not on-chain + throwError $ InsertErrorBuyGas $ Pact._boundedText $ Pact._peMsg $ + txInvalidErrorToOnChainPactError (BuyGasError err) Right (_ :: Pact.EvalResult) -> return () execLookupPactTxs diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 158ee379a4..f16bb4bd82 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -105,6 +105,7 @@ module Chainweb.Pact.Types , TransactionOutputProofB64(..) , TxInvalidError(..) + , txInvalidErrorToOnChainPactError , _BuyGasError , _RedeemGasError , _PurchaseGasTxTooBigForGasLimit @@ -664,6 +665,39 @@ data TxInvalidError | TxExceedsBlockGasLimit !Int deriving stock (Show, Eq, Generic) +-- | Convert an error that would make a transaction invalid, into an error that can be +-- returned to the user. Note that outputs from here do not make it on-chain ever. +txInvalidErrorToOnChainPactError :: TxInvalidError -> Pact.PactOnChainError +txInvalidErrorToOnChainPactError = \case + BuyGasError err -> + prefixMsg "Failed to buy gas:" $ + buyGasErrorToOnChainPactError err + RedeemGasError (RedeemGasPactError err) -> + prefixMsg "Failed to redeem gas:" $ + Pact.pactErrorToOnChainError err + PurchaseGasTxTooBigForGasLimit -> Pact.PactOnChainError + { _peType = Pact.ErrorType "EvalError" + , _peMsg = "gas limit too low to even begin evaluation" + , _peInfo = Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin (Pact.LineInfo 0) + } + TxExceedsBlockGasLimit {} -> Pact.PactOnChainError + { _peType = Pact.ErrorType "EvalError" + , _peMsg = "tx gas limit taken with other block txs exceeds block gas limit" + , _peInfo = Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin (Pact.LineInfo 0) + } + where + buyGasErrorToOnChainPactError (BuyGasPactError err) = Pact.pactErrorToOnChainError err + buyGasErrorToOnChainPactError BuyGasMultipleGasPayerCaps = Pact.PactOnChainError + { _peType = Pact.ErrorType "EvalError" + , _peMsg = "multiple gas payer capabilities in signers list" + , _peInfo = Pact.LocatedErrorInfo Pact.TopLevelErrorOrigin (Pact.LineInfo 0) + } + prefixMsg p err = + err + { Pact._peMsg = Pact.mkBoundedText $ + T.unwords [p, Pact._boundedText (Pact._peMsg err) ] + } + makePrisms ''TxInvalidError data BlockInvalidError From 47e302f416b0527275bc90644fe7f5d0b7ed62c5 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 24 Apr 2025 12:45:58 -0400 Subject: [PATCH 122/378] Change gas price error in mempool Change-Id: Id0000000ca504d32a414a1d97b0262ebf368a90d --- src/Chainweb/Mempool/InMem.hs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 4a5a298b9c..7bf4337d17 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -437,13 +437,14 @@ validateOne cfg badmap curTxIdx now t h = -- prop_tx_gas_rounding gasPriceRoundingCheck :: Either InsertError () gasPriceRoundingCheck = - ebool_ (InsertErrorOther msg) (f (txGasPrice txcfg t)) + ebool_ (InsertErrorOther msg) f where - f (GasPrice d) = decimalPlaces d <= defaultMaxCoinDecimalPlaces - msg = T.unwords - [ "This transaction's gas price:" - , sshow (txGasPrice txcfg t) - , "is not correctly rounded." + GasPrice d = txGasPrice txcfg t + f = decimalPlaces d <= defaultMaxCoinDecimalPlaces + msg = T.concat + [ "This transaction's gas price (" + , sshow d + , ") is not correctly rounded. " , "It should be rounded to at most 12 decimal places." ] From 85174729517eebd1fa295b0fe20f9058fcc37330 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 24 Apr 2025 12:45:58 -0400 Subject: [PATCH 123/378] Delete unused TxInsertError case and delete argument from TxExceedsBlockGasLimit --- src/Chainweb/Pact/PactService/ExecBlock.hs | 2 +- src/Chainweb/Pact/Types.hs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index a0c27671fd..1bc40c62cd 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -323,7 +323,7 @@ applyCmdInBlock logger serviceEnv blockEnv miner txIdxInBlock tx = StateT $ \(bl , txGasLimit > blockGas -- then the transaction is not allowed to fail, or it would consume more gas than remains in the block , Pact.PactResultErr _ <- Pact._crResult result - -> throwError $ TxExceedsBlockGasLimit (txGasLimit ^. Pact._GasLimit . to Pact._gas . to fromIntegral) + -> throwError $ TxExceedsBlockGasLimit | otherwise -> do let subtractGasLimit limit subtrahend = let limitGas = limit ^. Pact._GasLimit diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index f16bb4bd82..22ad39661b 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -109,7 +109,6 @@ module Chainweb.Pact.Types , _BuyGasError , _RedeemGasError , _PurchaseGasTxTooBigForGasLimit - , _TxInsertError , _TxExceedsBlockGasLimit , BlockInvalidError(..) , BlockOutputMismatchError(..) @@ -661,8 +660,7 @@ data TxInvalidError = BuyGasError !BuyGasError | RedeemGasError !RedeemGasError | PurchaseGasTxTooBigForGasLimit - | TxInsertError !InsertError - | TxExceedsBlockGasLimit !Int + | TxExceedsBlockGasLimit deriving stock (Show, Eq, Generic) -- | Convert an error that would make a transaction invalid, into an error that can be From e3279ff5f3d0e1e75e68d03bc364bcda45cef001 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 30 Apr 2025 11:39:14 -0400 Subject: [PATCH 124/378] jsonify BlockOutputMismatchError --- src/Chainweb/Pact/Types.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 22ad39661b..d1d55feba8 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -711,7 +711,9 @@ data BlockOutputMismatchError = BlockOutputMismatchError , blockOutputMismatchActualPayload :: !Chainweb.PayloadWithOutputs , blockOutputMismatchExpectedPayload :: !Chainweb.CheckablePayload } - deriving Show + +instance Show BlockOutputMismatchError where + show = T.unpack . J.encodeText instance J.Encode BlockOutputMismatchError where build bvf = J.object From 2fb05d7eab58710c1a4d67362c4164080fa94ebc Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 30 Apr 2025 11:55:45 -0400 Subject: [PATCH 125/378] fix ea --- cwtools/cwtools.cabal | 2 ++ cwtools/ea/Ea.hs | 13 ++++++++----- cwtools/ea/Ea/Genesis.hs | 2 +- src/Chainweb/Pact/Backend/Utils.hs | 15 +++------------ test/lib/Chainweb/Test/Pact/Utils.hs | 11 ----------- test/lib/Chainweb/Test/Utils.hs | 28 +++++++--------------------- 6 files changed, 21 insertions(+), 50 deletions(-) diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index 1186db69d7..3563fe6b1a 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -174,12 +174,14 @@ executable ea , async , base , chainweb-storage + , filepath , lens , loglevel , pact-json , pact-tng:pact-request-api , pact-tng , resource-pool + , resourcet , temporary , text , vector diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index 63818fa804..c0ba2bbc35 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -48,6 +48,8 @@ import Chainweb.Version.RecapDevelopment (pattern RecapDevelopment) import Chainweb.Version.Registry (registerVersion) import Control.Concurrent.Async import Control.Exception +import Control.Monad.Trans.Resource +import Control.Monad.IO.Class import Control.Lens import Data.Aeson qualified as Aeson import Data.Foldable @@ -63,6 +65,7 @@ import Data.Traversable import Data.Vector qualified as V import Ea.Genesis import GHC.Exts(the) +import System.FilePath import System.LogLevel import System.IO.Temp import Text.Printf @@ -157,11 +160,11 @@ genPayloadModule :: ChainwebVersion -> Text -> Chainweb.ChainId -> [Pact.Transac genPayloadModule v tag cid cwTxs = do let logger = genericLogger Warn TIO.putStrLn pdb <- newPayloadDb - withSystemTempDirectory "ea-pact-db" $ \pactDbDir -> do - payloadWO <- withSqliteDb cid logger pactDbDir False $ \readWriteSql -> do - roPool <- Pool.newPool $ Pool.defaultPoolConfig (startReadSqliteDb cid logger pactDbDir) stopSqliteDb 10 10 - withPactService v cid Nothing mempty logger Nothing pdb roPool readWriteSql (defaultPactServiceConfig emptyPayload) $ \serviceEnv -> - execNewGenesisBlock logger serviceEnv (V.fromList cwTxs) + withSystemTempDirectory "ea-pact-db" $ \pactDbDir -> runResourceT $ do + readWriteSql <- withSqliteDb cid logger pactDbDir False + roPool <- withReadSqlitePool cid pactDbDir + serviceEnv <- withPactService v cid Nothing mempty logger Nothing pdb roPool readWriteSql defaultPactServiceConfig Nothing + payloadWO <- liftIO $ execNewGenesisBlock logger serviceEnv (V.fromList cwTxs) return $ TL.toStrict $ TB.toLazyText $ payloadModuleCode tag payloadWO mkChainwebTxs :: [FilePath] -> IO [Pact.Transaction] diff --git a/cwtools/ea/Ea/Genesis.hs b/cwtools/ea/Ea/Genesis.hs index 1a78791ff2..8b4251b6da 100644 --- a/cwtools/ea/Ea/Genesis.hs +++ b/cwtools/ea/Ea/Genesis.hs @@ -63,7 +63,7 @@ module Ea.Genesis import Control.Lens import Control.Monad -import Data.Text +import Data.Text (Text) import Data.Word import Chainweb.Graph diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index a3d0fa2fbd..9781a681a2 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -60,7 +60,6 @@ module Chainweb.Pact.Backend.Utils , openSQLiteConnection , closeSQLiteConnection , withTempSQLiteConnection - , withInMemSQLiteConnection -- * SQLite , chainwebPragmas , LocatedSQ3Error(..) @@ -271,10 +270,10 @@ withReadSqliteDb cid logger dbDir = snd <$> allocate (startReadSqliteDb cid logger dbDir) stopSqliteDb -withReadSqlitePool :: FilePath -> ResourceT IO (Pool.Pool SQLiteEnv) -withReadSqlitePool pactDbDir = snd <$> allocate +withReadSqlitePool :: ChainId -> FilePath -> ResourceT IO (Pool.Pool SQLiteEnv) +withReadSqlitePool cid pactDbDir = snd <$> allocate (Pool.newPool $ Pool.defaultPoolConfig - (openSQLiteConnection pactDbDir chainwebPragmas) + (openSQLiteConnection (pactDbDir chainDbFileName cid) chainwebPragmas) stopSqliteDb 30 -- seconds to keep them around unused 2 -- connections at most @@ -345,14 +344,6 @@ closeSQLiteConnection c = void $ close_v2 c withTempSQLiteConnection :: [Pact4.Pragma] -> (SQLiteEnv -> IO c) -> IO c withTempSQLiteConnection = withSQLiteConnection "" --- Using the special file name @:memory:@ causes sqlite to create a temporary in-memory --- database. --- --- Cf. https://www.sqlite.org/inmemorydb.html --- -withInMemSQLiteConnection :: [Pact4.Pragma] -> (SQLiteEnv -> IO c) -> IO c -withInMemSQLiteConnection = withSQLiteConnection ":memory:" - open2 :: String -> IO (Either (SQ3.Error, SQ3.Utf8) SQ3.Database) open2 file = open_v2 (fromString file) diff --git a/test/lib/Chainweb/Test/Pact/Utils.hs b/test/lib/Chainweb/Test/Pact/Utils.hs index 3d9e8021bd..e68c20f760 100644 --- a/test/lib/Chainweb/Test/Pact/Utils.hs +++ b/test/lib/Chainweb/Test/Pact/Utils.hs @@ -17,15 +17,8 @@ module Chainweb.Test.Pact.Utils , testLogFn , getTestLogger - -- * Mempool - -- , mempoolInsertPact5 - -- , mempoolLookupPact5 - -- * Resources - , withInMemSQLiteResource - -- , withPactQueue , withMempool - -- , withRunPactService , withBlockDbs -- * Properties @@ -99,10 +92,6 @@ withSQLiteResource file = snd <$> allocate (openSQLiteConnection file chainwebPragmas) closeSQLiteConnection --- | Open a temporary in-memory SQLite database. -withInMemSQLiteResource :: ResourceT IO SQLiteEnv -withInMemSQLiteResource = withSQLiteResource ":memory:" - withMempool :: (Logger logger) => logger diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index e6a5662547..574f273bc1 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -41,8 +41,7 @@ module Chainweb.Test.Utils , withTestBlockHeaderDb -- * SQLite Database Test Resource -, withTempSQLiteResource -, withInMemSQLiteResource +, withTempChainSqlite -- * Data Generation , toyBlockHeaderDb @@ -217,7 +216,7 @@ import Chainweb.Pact.Backend.Utils import Chainweb.Parent import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID -import Chainweb.Test.Pact.Utils (getTestLogLevel) +import Chainweb.Test.Pact.Utils (getTestLogLevel, getTestLogger) import Chainweb.Test.P2P.Peer.BootstrapConfig (testBootstrapCertificate, testBootstrapKey, testBootstrapPeerConfig) import Chainweb.Test.Utils.BlockHeader @@ -324,26 +323,13 @@ withRocksResource = view _2 . snd <$> allocate create destroy -- -------------------------------------------------------------------------- -- -- SQLite DB Test Resource --- | This function doesn't delete the database file after use. --- --- You should use 'withTempSQLiteResource' or 'withInMemSQLiteResource' instead. --- -withSQLiteResource - :: String - -> ResourceT IO SQLiteEnv -withSQLiteResource file = snd <$> allocate - (openSQLiteConnection file chainwebPragmas) - closeSQLiteConnection - -- | Open a temporary file-backed SQLite database, and return a writable -- connection and a read-only pool of connections. -withTempSQLiteResource :: ResourceT IO (SQLiteEnv, Pool SQLiteEnv) -withTempSQLiteResource = do - fp <- withTempFile "sqlite-tmp" - (,) <$> withSQLiteResource fp <*> withReadSqlitePool fp - -withInMemSQLiteResource :: ResourceT IO SQLiteEnv -withInMemSQLiteResource = withSQLiteResource ":memory:" +withTempChainSqlite :: ChainId -> ResourceT IO (SQLiteEnv, Pool SQLiteEnv) +withTempChainSqlite cid = do + logger <- liftIO $ getTestLogger + fp <- withTempDir "sqlite-tmp" + (,) <$> withSqliteDb cid logger fp False <*> withReadSqlitePool cid fp -- -------------------------------------------------------------------------- -- -- Toy Values From 79942df66293033dcc760e8ea339f1d9f0ec0833 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 30 Apr 2025 12:17:26 -0400 Subject: [PATCH 126/378] Fix chainweb-node exe --- node/Setup.hs | 13 +++++---- node/src/ChainwebNode.hs | 63 ++++++++++++++-------------------------- 2 files changed, 28 insertions(+), 48 deletions(-) diff --git a/node/Setup.hs b/node/Setup.hs index 74302cb25e..92e1379682 100644 --- a/node/Setup.hs +++ b/node/Setup.hs @@ -8,6 +8,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -133,7 +134,7 @@ import System.Directory doesFileExist, getCurrentDirectory) import System.Environment (lookupEnv) import System.Exit (ExitCode(ExitSuccess)) -import System.FilePath (isDrive, takeDirectory, ()) +import System.FilePath (isDrive, takeDirectory) -- | Include this function when your setup doesn't contain any -- extra functionality. @@ -183,7 +184,7 @@ mkPkgInfoModulesPostConf hook args flags pkgDesc bInfo = do updatePkgInfoModule :: PackageDescription -> LocalBuildInfo -> ComponentLocalBuildInfo -> IO () updatePkgInfoModule pkgDesc bInfo clbInfo = do - createDirectoryIfMissing True dirName + createDirectoryIfMissing True $ interpretSymbolicPathCWD dirName moduleBytes <- pkgInfoModule moduleName cName pkgDesc bInfo updateFile fileName moduleBytes @@ -196,10 +197,10 @@ updatePkgInfoModule pkgDesc bInfo clbInfo = do cName = unUnqualComponentName <$> componentNameString (componentLocalName clbInfo) moduleName = pkgInfoModuleName - fileName = dirName ++ "/" ++ moduleName ++ ".hs" + fileName = dirName unsafeMakeSymbolicPath moduleName <.> ".hs" legacyModuleName = legacyPkgInfoModuleName cName - legacyFileName = dirName ++ "/" ++ legacyModuleName ++ ".hs" + legacyFileName = dirName unsafeMakeSymbolicPath legacyModuleName <.> ".hs" -- -------------------------------------------------------------------------- -- -- Generate PkgInfo Module @@ -207,8 +208,8 @@ updatePkgInfoModule pkgDesc bInfo clbInfo = do pkgInfoModuleName :: String pkgInfoModuleName = "PkgInfo" -updateFile :: FilePath -> B.ByteString -> IO () -updateFile fileName content = do +updateFile :: SymbolicPath from to -> B.ByteString -> IO () +updateFile (interpretSymbolicPathCWD -> fileName) content = do x <- doesFileExist fileName if | not x -> update | otherwise -> do diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 1dce10d360..548b751eb6 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -68,8 +68,6 @@ import GHC.Stats import qualified Network.HTTP.Client as HTTP import qualified Network.HTTP.Client.TLS as HTTPS -import qualified Streaming.Prelude as S - import System.Directory import System.FilePath import System.IO @@ -86,18 +84,14 @@ import Chainweb.Chainweb.CutResources import Chainweb.Counter import Chainweb.Cut.CutHashes import Chainweb.CutDB -import Chainweb.Difficulty import Chainweb.Logger import Chainweb.Logging.Config import Chainweb.Logging.Miner -import Chainweb.Mempool.Consensus (ReintroducedTxsLog) import Chainweb.Mempool.InMemTypes (MempoolStats(..)) -- import Chainweb.Miner.Coordinator (MiningStats) import Chainweb.Pact.Backend.DbCache (DbCacheStats) import Chainweb.Pact.RestAPI.Server (PactCmdLog(..)) import Chainweb.Pact.Types -import Chainweb.Payload -import Chainweb.Payload.PayloadStore import Chainweb.Time import Data.Time.Format.ISO8601 import Chainweb.Utils @@ -170,9 +164,6 @@ pChainwebNodeConfiguration = id <*< nodeConfigDatabaseDirectory .:: fmap Just % textOption % long "database-directory" <> help "directory where the databases are persisted" - <*< nodeConfigResetChainDbs .:: enableDisableFlag - % long "reset-chain-databases" - <> help "Reset the chain databases for all chains on startup" getRocksDbDir :: HasCallStack => ChainwebNodeConfiguration -> IO FilePath getRocksDbDir conf = (\base -> base "0" "rocksDb") <$> getDbBaseDir conf @@ -239,31 +230,24 @@ instance ToJSON BlockUpdate where {-# INLINE toEncoding #-} {-# INLINE toJSON #-} -runBlockUpdateMonitor :: Logger logger => logger -> CutDb -> IO () -runBlockUpdateMonitor logger db = L.withLoggerLabel ("component", "block-update-monitor") logger $ \l -> - runMonitorLoop "ChainwebNode.runBlockUpdateMonitor" l $ do - blockDiffStream db - & S.mapM toUpdate - & S.mapM_ (logFunctionJson l Info) - where - txCount :: BlockHeader -> IO Int - txCount bh = return (-1) - -- bp <- lookupPayloadDataWithHeight (Just $ view blockHeight bh) (view blockPayloadHash bh) >>= \case - -- Nothing -> error "block payload not found" - -- Just x -> return x - -- return $ length $ view payloadDataTransactions bp - - toUpdate :: Either BlockHeader BlockHeader -> IO BlockUpdate - toUpdate (Right bh) = BlockUpdate - <$> pure (ObjectEncoded bh) -- _blockUpdateBlockHeader - <*> pure False -- _blockUpdateOrphaned - <*> txCount bh -- _blockUpdateTxCount - <*> pure (difficultyToDouble (targetToDifficulty (view blockTarget bh))) -- _blockUpdateDifficultyDouble - toUpdate (Left bh) = BlockUpdate - <$> pure (ObjectEncoded bh) -- _blockUpdateBlockHeader - <*> pure True -- _blockUpdateOrphaned - <*> ((0 -) <$> txCount bh) -- _blockUpdateTxCount - <*> pure (difficultyToDouble (targetToDifficulty (view blockTarget bh))) -- _blockUpdateDifficultyDouble +-- runBlockUpdateMonitor :: Logger logger => logger -> CutDb -> IO () +-- runBlockUpdateMonitor logger db = L.withLoggerLabel ("component", "block-update-monitor") logger $ \l -> +-- runMonitorLoop "ChainwebNode.runBlockUpdateMonitor" l $ do +-- blockDiffStream db +-- & S.mapM toUpdate +-- & S.mapM_ (logFunctionJson l Info) +-- where +-- toUpdate :: Either BlockHeader BlockHeader -> IO BlockUpdate +-- toUpdate (Right bh) = BlockUpdate +-- <$> pure (ObjectEncoded bh) -- _blockUpdateBlockHeader +-- <*> pure False -- _blockUpdateOrphaned +-- <*> txCount bh -- _blockUpdateTxCount +-- <*> pure (difficultyToDouble (targetToDifficulty (view blockTarget bh))) -- _blockUpdateDifficultyDouble +-- toUpdate (Left bh) = BlockUpdate +-- <$> pure (ObjectEncoded bh) -- _blockUpdateBlockHeader +-- <*> pure True -- _blockUpdateOrphaned +-- <*> ((0 -) <$> txCount bh) -- _blockUpdateTxCount +-- <*> pure (difficultyToDouble (targetToDifficulty (view blockTarget bh))) -- _blockUpdateDifficultyDouble -- type CutLog = HM.HashMap ChainId (ObjectEncoded BlockHeader) @@ -327,8 +311,6 @@ runDatabaseMonitor logger rocksDbDir pactDbDir = L.withLoggerLabel ("component", node :: HasCallStack => Logger logger => ChainwebNodeConfiguration -> logger -> IO () node conf logger = do - dbBaseDir <- getDbBaseDir conf - when (_nodeConfigResetChainDbs conf) $ removeDirectoryRecursive dbBaseDir rocksDbDir <- getRocksDbDir conf pactDbDir <- getPactDbDir conf dbBackupsDir <- getBackupsDir conf @@ -355,8 +337,8 @@ node conf logger = do runQueueMonitor (_chainwebLogger cw) (_cutResCutDb $ _chainwebCutResources cw) , when telemetryEnabled $ runRtsMonitor (_chainwebLogger cw) - , when telemetryEnabled $ - runBlockUpdateMonitor (_chainwebLogger cw) (_cutResCutDb $ _chainwebCutResources cw) + -- , when telemetryEnabled $ + -- runBlockUpdateMonitor (_chainwebLogger cw) (_cutResCutDb $ _chainwebCutResources cw) , when telemetryEnabled $ runDatabaseMonitor (_chainwebLogger cw) rocksDbDir pactDbDir ] @@ -381,7 +363,7 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do -- we don't log tx failures in replay let !txFailureHandler = if _configOnlySyncPact chainwebCfg || _configReadOnlyReplay chainwebCfg - then [dropLogHandler (Proxy :: Proxy Pact4TxFailureLog), dropLogHandler (Proxy :: Proxy Pact5TxFailureLog)] + then [dropLogHandler (Proxy :: Proxy PactTxFailureLog)] else [] -- Telemetry Backends @@ -406,8 +388,6 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do $ mkTelemetryLogger @RequestResponseLog mgr teleLogConfig queueStatsBackend <- managed $ mkTelemetryLogger @QueueStats mgr teleLogConfig - reintroBackend <- managed - $ mkTelemetryLogger @ReintroducedTxsLog mgr teleLogConfig traceBackend <- managed $ mkTelemetryLogger @Trace mgr teleLogConfig mempoolStatsBackend <- managed @@ -439,7 +419,6 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do -- , logHandler miningStatsBackend , logHandler requestLogBackend , logHandler queueStatsBackend - , logHandler reintroBackend , logHandler traceBackend , logHandler mempoolStatsBackend , logHandler blockUpdateBackend From 87eed017c7801ad6cd362c6a1cbd73f8f8d137fd Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 30 Apr 2025 14:08:54 -0400 Subject: [PATCH 127/378] Configure Pact to generate genesis Change-Id: Id0000000f80230cb19be9a04712e0c49fa79893c --- cwtools/ea/Ea.hs | 4 ++-- src/Chainweb/Pact/PactService.hs | 14 +++++++++----- src/Chainweb/Pact/Types.hs | 6 ++++++ src/Chainweb/PayloadProvider/Pact.hs | 4 +++- test/unit/Chainweb/Test/Pact/CutFixture.hs | 4 ++-- test/unit/Chainweb/Test/Pact/PactServiceTest.hs | 4 ++-- .../unit/Chainweb/Test/Pact/TransactionExecTest.hs | 4 ++-- 7 files changed, 26 insertions(+), 14 deletions(-) diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index c0ba2bbc35..bde79cc6f6 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -33,7 +33,7 @@ import Chainweb.Logger (genericLogger) import Chainweb.Miner.Pact (noMiner) import Chainweb.Pact.Backend.Utils import Chainweb.Pact.PactService -import Chainweb.Pact.Types (defaultPactServiceConfig) +import Chainweb.Pact.Types (defaultPactServiceConfig, GenesisConfig(..)) import Chainweb.Pact.Utils (toTxCreationTime, emptyPayload) import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Validations (defaultMaxTTLSeconds) @@ -163,7 +163,7 @@ genPayloadModule v tag cid cwTxs = do withSystemTempDirectory "ea-pact-db" $ \pactDbDir -> runResourceT $ do readWriteSql <- withSqliteDb cid logger pactDbDir False roPool <- withReadSqlitePool cid pactDbDir - serviceEnv <- withPactService v cid Nothing mempty logger Nothing pdb roPool readWriteSql defaultPactServiceConfig Nothing + serviceEnv <- withPactService v cid Nothing mempty logger Nothing pdb roPool readWriteSql defaultPactServiceConfig GeneratingGenesis payloadWO <- liftIO $ execNewGenesisBlock logger serviceEnv (V.fromList cwTxs) return $ TL.toStrict $ TB.toLazyText $ payloadModuleCode tag payloadWO diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index b1eb807097..9f978b5946 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -67,7 +67,6 @@ import Prelude hiding (lookup) import qualified Pact.JSON.Encode as J import qualified Pact.Core.Gas as Pact -import qualified Pact.Core.Info as Pact import qualified Chainweb.Pact.TransactionExec as Pact import qualified Chainweb.Pact.Validations as Pact @@ -135,9 +134,9 @@ withPactService -> Pool SQLiteEnv -> SQLiteEnv -> PactServiceConfig - -> Maybe PayloadWithOutputs + -> GenesisConfig -> ResourceT IO (ServiceEnv tbl) -withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config maybeGenesisPayload = do +withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config pactGenesis = do SomeChainwebVersionT @v _ <- pure $ someChainwebVersionVal ver SomeChainIdT @c _ <- pure $ someChainIdVal cid let payloadClient = Rest.payloadClient @v @c @'PactProvider @@ -170,11 +169,16 @@ withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb , _psMiner = _pactMiner config , _psNewBlockGasLimit = _pactNewBlockGasLimit config , _psMiningPayloadVar = miningPayloadVar - , _psGenesisPayload = maybeGenesisPayload + , _psGenesisPayload = case pactGenesis of + GeneratingGenesis -> Nothing + GenesisPayload p -> Just p + GenesisNotNeeded -> Nothing , _psBlockRefreshInterval = _pactBlockRefreshInterval config } - liftIO $ initialPayloadState chainwebLogger pse + case pactGenesis of + GeneratingGenesis -> return () + _ -> liftIO $ initialPayloadState chainwebLogger pse return pse initialPayloadState diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index d1d55feba8..caeb4f4bb7 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -125,6 +125,7 @@ module Chainweb.Pact.Types , logError_ , PactTxFailureLog(..) + , GenesisConfig(..) ) where @@ -813,3 +814,8 @@ deriving newtype instance ToHttpApiData RewindDepth deriving newtype instance FromHttpApiData ConfirmationDepth deriving newtype instance ToHttpApiData ConfirmationDepth + +data GenesisConfig + = GeneratingGenesis + | GenesisNotNeeded + | GenesisPayload Chainweb.PayloadWithOutputs diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 535366e4e3..b1b2240d56 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -130,7 +130,9 @@ withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir c & Pool.setNumStripes (Just 2) -- two stripes, one connection per stripe ) Pool.destroyAllResources - PactPayloadProvider logger <$> PactService.withPactService ver cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config maybeGenesisPayload + PactPayloadProvider logger <$> + PactService.withPactService ver cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config + (maybe GenesisNotNeeded GenesisPayload maybeGenesisPayload) where mpa = pactMemPoolAccess mp $ addLabel ("sub-component", "MempoolAccess") logger diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index a5862ceaa0..a7e0b5fa2a 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -113,8 +113,8 @@ mkFixture v genesisPayloadFor pactServiceConfig baseRdb = do testRdb <- liftIO $ testRocksDb "withBlockDbs" baseRdb (payloadDb, webBHDb) <- withBlockDbs v testRdb perChain <- fmap ChainMap $ iforM (HashSet.toMap (chainIds v)) $ \chain () -> do - (writeSqlite, readPool) <- withTempSQLiteResource - serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing payloadDb readPool writeSqlite pactServiceConfig (Just $ genesisPayloadFor chain) + (writeSqlite, readPool) <- withTempChainSqlite chain + serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing payloadDb readPool writeSqlite pactServiceConfig (GenesisPayload $ genesisPayloadFor chain) mempool <- withMempool logger serviceEnv let serviceEnv' = serviceEnv { _psMempoolAccess = pactMemPoolAccess mempool logger } return (mempool, serviceEnv') diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index 1a4b65698d..69a3179952 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -102,9 +102,9 @@ mkFixtureWith pactServiceConfig baseRdb = do logLevel <- liftIO getTestLogLevel let logger = genericLogger logLevel Text.putStrLn perChain <- iforM (HashSet.toMap (chainIds v)) $ \chain () -> do - (writeSqlite, readPool) <- withTempSQLiteResource + (writeSqlite, readPool) <- withTempChainSqlite chain let pdb = _bdbPayloadDb tdb - serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing pdb readPool writeSqlite pactServiceConfig (Just $ genesisPayload chain) + serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing pdb readPool writeSqlite pactServiceConfig (GenesisPayload $ genesisPayload chain) let mempoolCfg = validatingMempoolConfig chain v (GasLimit (Gas 150_000)) diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index 1897bea99a..d87a7bc7a0 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -106,11 +106,11 @@ tests baseRdb = testGroup "Pact5 TransactionExecTest" -- PactServiceTest or RemotePactTest. readFromAfterGenesis :: ChainwebVersion -> RocksDb -> (BlockEnv -> BlockHandle -> IO a) -> IO a readFromAfterGenesis ver rdb act = runResourceT $ do - (writeSql, readPool) <- withTempSQLiteResource + (writeSql, readPool) <- withTempChainSqlite cid tdb <- mkTestBlockDb ver rdb -- fake ro-sql pool, assuming we're using this single-threaded logger <- liftIO $ testLogger - serviceEnv <- withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) readPool writeSql defaultPactServiceConfig (Just PIN0.payloadBlock) + serviceEnv <- withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) readPool writeSql defaultPactServiceConfig (GenesisPayload PIN0.payloadBlock) liftIO $ do initialPayloadState logger serviceEnv fakeParentCreationTime <- mkFakeParentCreationTime From ad80af7c31079e2fb630305fe4a6dc99cb10ae65 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 30 Apr 2025 14:08:54 -0400 Subject: [PATCH 128/378] Regenerate genesis payloads --- src/Chainweb/BlockHeader/Genesis/Development0Payload.hs | 6 +++--- src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs | 6 +++--- src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs | 6 +++--- .../BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs | 6 +++--- .../Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs | 6 +++--- .../Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs | 6 +++--- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs index d8c22e6109..8df4b1c015 100644 --- a/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "oq5QZGFJNhFZDMmrLCWVp_-gBcp1_DBqHYimdL7v468" + expectedHash = unsafeFromText "1I4CRQ2BGFZEFr7_X_U1hMPPZI4YMbfxEr6cyrRYlsA" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiejJtR1ZxektqeHp5bUdHLWNpWjBCSEpGM1EzeGtvOFYyNEE4ejBxcEl4ayIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJ6Mm1HVnF6S2p4enltR0ctY2laMEJISkYzUTN4a284VjI0QTh6MHFwSXhrIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs index cebc2270e6..975d6d246a 100644 --- a/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "gMX0oIeFRI4dKlraTBi2pLwfvVDqBH-5qEbcWAkk1jg" + expectedHash = unsafeFromText "jrfVQ_AVbtnWRVXeUhyNurrz1XXl37sofUfEBh7EBG0" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoibWd3Z3lCdTRGS3NEQ042Q3dWOEJNcFdNcnBwb2UtVkJKYTFYRjktLTFQRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJtZ3dneUJ1NEZLc0RDTjZDd1Y4Qk1wV01ycHBvZS1WQkphMVhGOS0tMVBFIiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs index bd649126f1..f61601e7b9 100644 --- a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "oq5QZGFJNhFZDMmrLCWVp_-gBcp1_DBqHYimdL7v468" + expectedHash = unsafeFromText "1I4CRQ2BGFZEFr7_X_U1hMPPZI4YMbfxEr6cyrRYlsA" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiejJtR1ZxektqeHp5bUdHLWNpWjBCSEpGM1EzeGtvOFYyNEE4ejBxcEl4ayIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJ6Mm1HVnF6S2p4enltR0ctY2laMEJISkYzUTN4a284VjI0QTh6MHFwSXhrIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs index 397d3878f3..b151e35709 100644 --- a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "gMX0oIeFRI4dKlraTBi2pLwfvVDqBH-5qEbcWAkk1jg" + expectedHash = unsafeFromText "jrfVQ_AVbtnWRVXeUhyNurrz1XXl37sofUfEBh7EBG0" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoibWd3Z3lCdTRGS3NEQ042Q3dWOEJNcFdNcnBwb2UtVkJKYTFYRjktLTFQRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJtZ3dneUJ1NEZLc0RDTjZDd1Y4Qk1wV01ycHBvZS1WQkphMVhGOS0tMVBFIiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs index 009ae28c5d..7322e7f259 100644 --- a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "oq5QZGFJNhFZDMmrLCWVp_-gBcp1_DBqHYimdL7v468" + expectedHash = unsafeFromText "1I4CRQ2BGFZEFr7_X_U1hMPPZI4YMbfxEr6cyrRYlsA" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiejJtR1ZxektqeHp5bUdHLWNpWjBCSEpGM1EzeGtvOFYyNEE4ejBxcEl4ayIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJ6Mm1HVnF6S2p4enltR0ctY2laMEJISkYzUTN4a284VjI0QTh6MHFwSXhrIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs index 6ef59ceb1a..5675ade959 100644 --- a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "gMX0oIeFRI4dKlraTBi2pLwfvVDqBH-5qEbcWAkk1jg" + expectedHash = unsafeFromText "jrfVQ_AVbtnWRVXeUhyNurrz1XXl37sofUfEBh7EBG0" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoibWd3Z3lCdTRGS3NEQ042Q3dWOEJNcFdNcnBwb2UtVkJKYTFYRjktLTFQRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIixcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJtZ3dneUJ1NEZLc0RDTjZDd1Y4Qk1wV01ycHBvZS1WQkphMVhGOS0tMVBFIiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") From 7afbd4ec3288bcf5a721e415724af61197b824b6 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 1 May 2025 13:50:34 -0400 Subject: [PATCH 129/378] Finish fixing tests --- src/Chainweb/Mempool/InMem.hs | 10 ++-------- src/Chainweb/Mempool/InMem/ValidatingConfig.hs | 7 ++++--- src/Chainweb/Mempool/Mempool.hs | 11 +++++++++-- test/unit/Chainweb/Test/Pact/RemotePactTest.hs | 13 ++++++++----- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 7bf4337d17..2374584a83 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -437,16 +437,10 @@ validateOne cfg badmap curTxIdx now t h = -- prop_tx_gas_rounding gasPriceRoundingCheck :: Either InsertError () gasPriceRoundingCheck = - ebool_ (InsertErrorOther msg) f + ebool_ (InsertErrorBadGasPrice gp) f where - GasPrice d = txGasPrice txcfg t + gp@(GasPrice d) = txGasPrice txcfg t f = decimalPlaces d <= defaultMaxCoinDecimalPlaces - msg = T.concat - [ "This transaction's gas price (" - , sshow d - , ") is not correctly rounded. " - , "It should be rounded to at most 12 decimal places." - ] -- prop_tx_ttl_arrival ttlCheck :: Either InsertError () diff --git a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs index a35145d1d7..52a68f875f 100644 --- a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs +++ b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs @@ -60,7 +60,7 @@ validatingMempoolConfig cid v gl gp preInsertCheck = InMemConfig sigs = Pact._cmdSigs tx ver = Pact._pNetworkId pay if | not $ assertChainId cid pcid -> Left InsertErrorMetadataMismatch - | not $ assertSigSize sigs -> Left $ InsertErrorOther "Too many signatures" + | not $ assertSigSize sigs -> Left InsertErrorTooManySigs | not $ assertNetworkId v ver -> Left InsertErrorMetadataMismatch | otherwise -> Right tx @@ -86,5 +86,6 @@ validatingMempoolConfig cid v gl gp preInsertCheck = InMemConfig f (These r (T2 h t)) = case r of Just e -> Left (T2 h e) Nothing -> Right (T2 h t) - f (That (T2 h _)) = Left (T2 h $ InsertErrorOther "preInsertBatch: align mismatch 0") - f (This _) = Left (T2 (TransactionHash "") (InsertErrorOther "preInsertBatch: align mismatch 1")) + f (That _) = alignmentError + f (This _) = alignmentError + alignmentError = error "internal error: mismatch between input and output length of pre-insert check" diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Mempool/Mempool.hs index c37d3478c9..79e8b0f8f5 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Mempool/Mempool.hs @@ -237,9 +237,10 @@ data InsertError | InsertErrorTransactionsDisabled | InsertErrorBuyGas Text | InsertErrorCompilationFailed Text - | InsertErrorOther Text + | InsertErrorBadGasPrice GasPrice | InsertErrorInvalidHash | InsertErrorInvalidSigs Text + | InsertErrorTooManySigs | InsertErrorTimedOut | InsertErrorPactParseError Text | InsertErrorWrongChain Text Text @@ -257,12 +258,18 @@ instance Show InsertError where InsertErrorTransactionsDisabled -> "Transactions are disabled until 2019 Dec 5" InsertErrorBuyGas msg -> T.unpack msg InsertErrorCompilationFailed msg -> "Transaction compilation failed: " <> T.unpack msg - InsertErrorOther m -> "insert error: " <> T.unpack m InsertErrorInvalidHash -> "Invalid transaction hash" InsertErrorInvalidSigs msg -> "Invalid transaction sigs: " <> T.unpack msg InsertErrorTimedOut -> "Transaction validation timed out" InsertErrorPactParseError msg -> "Pact parse error: " <> T.unpack msg InsertErrorWrongChain expected actual -> "Wrong chain, expected: " <> T.unpack expected <> ", actual: " <> T.unpack actual + InsertErrorBadGasPrice (GasPrice d) -> concat + [ "This transaction's gas price (" + , sshow d + , ") is not correctly rounded. " + , "It should be rounded to at most 12 decimal places." + ] + InsertErrorTooManySigs -> "Too many signatures" instance Exception InsertError diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index ea58ff1c66..6568782074 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -415,11 +415,14 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> $ defaultCmd cid -- if any tx fails parsing, no txs even get validated send fx v cid [cmdInvalidUserSig, cmdGood, cmdParseFailure] - & P.throws ? P.match _FailureResponse ? P.fun responseBody ? P.checkAll - [ textContains (validationFailed 0 cmdInvalidUserSig + & P.throws ? P.match _FailureResponse ? P.fun responseBody + ? textContains (parseFailed 2 "ParseError: Expected: [')']") + + -- without parse failures, all txs get validated + send fx v cid [cmdInvalidUserSig, cmdGood] + & P.throws ? P.match _FailureResponse ? P.fun responseBody + ? textContains (validationFailed 0 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") - , textContains (parseFailed 2 cmdParseFailure "Expected: [')']") - ] , testCase "invalid metadata" $ do cmdGood <- mkCmdGood @@ -521,7 +524,7 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> wrongChain = unsafeChainId 1 validationFailed i cmd msg = "Transaction " <> sshow (_cmdHash cmd) <> " at index " <> sshow @Int i <> " failed with: " <> msg - parseFailed i cmd msg = "Transaction " <> sshow (_cmdHash cmd) <> " at index " <> sshow @Int i <> " has invalid Pact code: " <> msg + parseFailed i msg = "Transaction at index " <> sshow @Int i <> " has invalid Pact code: " <> msg mkCmdInvalidUserSig = mkCmdGood <&> set cmdSigs [ED25519Sig "fakeSig"] From a28c3f4cbd1ba806c384834dc74a86fee45cb474 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 2 May 2025 08:28:21 -0700 Subject: [PATCH 130/378] Start hashing adjacent hash records in work headers as of some fork --- chainweb.cabal | 1 + src/Chainweb/BlockHash.hs | 66 ++++- src/Chainweb/BlockHeader.hs | 1 + src/Chainweb/BlockHeader/Internal.hs | 43 ++- src/Chainweb/Core/Brief.hs | 44 ++- src/Chainweb/Core/CryptoHash.hs | 135 +++++++++ src/Chainweb/Cut/Create.hs | 236 ++++++++++------ src/Chainweb/Logging/Miner.hs | 5 +- src/Chainweb/Miner/Coordinator.hs | 261 +++++++++++------- src/Chainweb/Miner/Core.hs | 6 +- src/Chainweb/Miner/Miners.hs | 27 +- src/Chainweb/Miner/RestAPI/Server.hs | 7 +- src/Chainweb/Utils/Serialization.hs | 5 + src/Chainweb/Version.hs | 3 + src/Chainweb/Version/Guards.hs | 3 + src/Chainweb/Version/Mainnet.hs | 1 + src/Chainweb/Version/RecapDevelopment.hs | 1 + src/Chainweb/Version/Testnet04.hs | 1 + test/lib/Chainweb/Test/Cut.hs | 39 ++- test/lib/Chainweb/Test/Orphans/Internal.hs | 14 +- test/lib/Chainweb/Test/TestVersions.hs | 1 + .../Test/Chainweb/SPV/Argument.hs | 4 +- test/unit/Chainweb/Test/Misc.hs | 108 ++++++-- test/unit/Chainweb/Test/Pact/CutFixture.hs | 45 ++- test/unit/Chainweb/Test/Roundtrips.hs | 10 +- 25 files changed, 766 insertions(+), 301 deletions(-) create mode 100644 src/Chainweb/Core/CryptoHash.hs diff --git a/chainweb.cabal b/chainweb.cabal index 86c1fbf0d4..397380c82f 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -176,6 +176,7 @@ library , Chainweb.Chainweb.MinerResources , Chainweb.Chainweb.PeerResources , Chainweb.Core.Brief + , Chainweb.Core.CryptoHash , Chainweb.Counter , Chainweb.Crypto.MerkleLog , Chainweb.Cut diff --git a/src/Chainweb/BlockHash.hs b/src/Chainweb/BlockHash.hs index 74d45de315..417239e873 100644 --- a/src/Chainweb/BlockHash.hs +++ b/src/Chainweb/BlockHash.hs @@ -1,16 +1,20 @@ {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PackageImports #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} @@ -18,8 +22,6 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE PatternSynonyms #-} -{-# LANGUAGE DerivingVia #-} -- | -- Module: Chainweb.BlockHash @@ -62,6 +64,15 @@ module Chainweb.BlockHash , encodeRankedBlockHash , decodeRankedBlockHash +-- * AdjacentsHash +, AdjacentsHash(..) +, adjacentsHash +, adjacentsHashBytes +, encodeAdjacentsHash +, decodeAdjacentsHash +, AdjacentsHashAlgorithm +, AdjacentsHashSize + -- * Exceptions ) where @@ -74,12 +85,14 @@ import Data.Aeson (FromJSON(..), FromJSONKey(..), ToJSON(..), ToJSONKey(..), withText) import Data.Aeson.Types (FromJSONKeyFunction(..), toJSONKeyText) import Data.Bifoldable +import Data.ByteString.Short qualified as SB import Data.Foldable +import Data.Hash.SHA2 +import Data.HashMap.Strict qualified as HM import Data.Hashable (Hashable(..)) -import qualified Data.HashMap.Strict as HM -import qualified Data.List as L -import qualified Data.Text as T -import qualified Data.Vector as V +import Data.List qualified as L +import Data.Text qualified as T +import Data.Vector qualified as V import GHC.Generics hiding (to) @@ -97,6 +110,7 @@ import Chainweb.Parent import Chainweb.Ranked import Chainweb.Utils import Chainweb.Utils.Serialization +import Chainweb.Core.CryptoHash -- -------------------------------------------------------------------------- -- -- BlockHash @@ -267,6 +281,46 @@ blockHashRecordFromVector g cid = BlockHashRecord . zip (L.sort $ toList $ adjacentChainIds (_chainGraph g) cid) . toList +-- ---------------------------------------------------------------------------- +-- | BlockHashRecord Hash for MiningWork + +type AdjacentsHashAlgorithm = Sha2_512_256 +type AdjacentsHashSize = DigestSize AdjacentsHashAlgorithm + +-- | The MiningWork includes an (aggregate) hash of the adjacent block hashes +-- that is calculate from the BlockHashRecord. +-- +newtype AdjacentsHash = AdjacentsHash (CryptoHash Sha2_512_256) + deriving stock (Show, Generic) + deriving anyclass (NFData) + deriving newtype (Eq, Ord, Hashable, ToJSON, FromJSON) + +instance HasTextRepresentation AdjacentsHash where + toText (AdjacentsHash h) = toText h + fromText = fmap AdjacentsHash . fromText + {-# INLINE toText #-} + {-# INLINE fromText #-} + +encodeAdjacentsHash :: AdjacentsHash -> Put +encodeAdjacentsHash (AdjacentsHash w) = encodeCryptoHash w +{-# INLINE encodeAdjacentsHash #-} + +adjacentsHashBytes :: AdjacentsHash -> SB.ShortByteString +adjacentsHashBytes (AdjacentsHash bytes) = cryptoHashBytes bytes +{-# INLINE adjacentsHashBytes #-} + +decodeAdjacentsHash :: Get AdjacentsHash +decodeAdjacentsHash = AdjacentsHash <$> decodeCryptoHash +{-# INLINE decodeAdjacentsHash #-} + +-- | Compute the AdjacentsHash from a BlockHashRecord. +-- +adjacentsHash :: BlockHashRecord -> AdjacentsHash +adjacentsHash = AdjacentsHash + . hashByteString_ @(CryptoHash AdjacentsHashAlgorithm) + . foldMap (runPutS . encodeBlockHash . view _Parent) + . blockHashRecordToVector + -- -------------------------------------------------------------------------- -- -- Ranked Block Hash diff --git a/src/Chainweb/BlockHeader.hs b/src/Chainweb/BlockHeader.hs index 58fb5016d7..acbf434544 100644 --- a/src/Chainweb/BlockHeader.hs +++ b/src/Chainweb/BlockHeader.hs @@ -62,6 +62,7 @@ module Chainweb.BlockHeader , I._rankedBlockPayloadHash , I._blockAdjacentChainIds , I.blockAdjacentChainIds +, I.encodeAsMiningWork , I.encodeBlockHeader , I.encodeBlockHeaderWithoutHash , I.decodeBlockHeader diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index 96f9b90d05..981a986fab 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -97,6 +97,7 @@ module Chainweb.BlockHeader.Internal , rankedBlockHash , _rankedBlockPayloadHash , rankedBlockPayloadHash +, encodeAsMiningWork , encodeBlockHeader , encodeBlockHeaderWithoutHash , decodeBlockHeader @@ -188,6 +189,7 @@ import Numeric.AffineSpace import Numeric.Natural import System.IO.Unsafe import Text.Read (readEither) +import Control.Monad -- -------------------------------------------------------------------------- -- -- Nonce @@ -844,6 +846,37 @@ encodeBlockHeader b = do encodeBlockHeaderWithoutHash b encodeBlockHash (_blockHash b) +-- | Encode the block header as a mining work value. +-- +-- NOTE: The legacy format of this is just the block header without the block +-- hash. With the legacy format the size and layout of the mining work depended +-- on the chain graph. The new format has a fixed size and layout that matches +-- the format that has been used in Mainnet01 and Testnet04 since genesis. +-- +encodeAsMiningWork :: BlockHeader -> Put +encodeAsMiningWork b = do + encodeFeatureFlags (_blockFlags b) + encodeBlockCreationTime (_blockCreationTime b) + encodeBlockHash (unwrapParent $ _blockParent b) + + if hashedAdjacentRecord (_chainwebVersion b) (_chainId b) (_blockHeight b) + then do + encodeWordLe @Word16 0xf0f0 + replicateM_ 3 $ do + encodeWordLe @Word32 0 + encodeAdjacentsHash (adjacentsHash $ _blockAdjacentHashes b) + else do + encodeBlockHashRecord (_blockAdjacentHashes b) + + encodeHashTarget (_blockTarget b) + encodeBlockPayloadHash (_blockPayloadHash b) + encodeChainId (_blockChainId b) + encodeBlockWeight (_blockWeight b) + encodeBlockHeight (_blockHeight b) + encodeChainwebVersionCode (_blockChainwebVersion b) + encodeEpochStartTime (_blockEpochStart b) + encodeNonce (Nonce 0) + -- | Decode and check that -- -- 1. chain id is in graph @@ -961,7 +994,7 @@ computeBlockHash h = BlockHash $ MerkleLogHash $ computeMerkleLogRoot h -- _blockPow :: BlockHeader -> PowHash _blockPow h = cryptoHash @Blake2s_256 - $ runPutS $ encodeBlockHeaderWithoutHash h + $ runPutS $ encodeAsMiningWork h blockPow :: Getter BlockHeader PowHash blockPow = to _blockPow @@ -1174,12 +1207,18 @@ headerSizeBytes v cid h = snd -- given chainweb version. This would only ever change as part of the -- introduction of new block header format. -- +-- TODO: we should probably just make this a global constant, fix all tests and +-- call it a day. +-- workSizeBytes :: HasCallStack => ChainwebVersion -> BlockHeight -> Natural -workSizeBytes v h = headerSizeBytes v (unsafeChainId 0) h - 32 +workSizeBytes v h + | hashedAdjacentRecord (_chainwebVersion v) (unsafeChainId 0) h = + headerSizeBytes Mainnet01 (unsafeChainId 0) 0 - 32 + | otherwise = headerSizeBytes v (unsafeChainId 0) h - 32 _rankedBlockHash :: BlockHeader -> RankedBlockHash _rankedBlockHash h = RankedBlockHash diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index 324537e806..8164bb94ea 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -1,8 +1,12 @@ +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeSynonymInstances #-} +{-# LANGUAGE UndecidableInstances #-} -- | -- Module: Chainweb.Core.Brief @@ -25,18 +29,24 @@ import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainId +import Chainweb.Core.CryptoHash import Chainweb.Cut import Chainweb.Cut.CutHashes -import Chainweb.PayloadProvider import Chainweb.Parent +import Chainweb.PayloadProvider import Chainweb.Ranked import Chainweb.Utils import Control.Lens +import Data.Aeson +import Data.ByteString qualified as B +import Data.ByteString.Short qualified as BS +import Data.Coerce +import Data.Hash.Class.Mutable +import Data.Hash.SHA2 (Sha2_512_256(..) {- Coercible AdjacentsHashAlgorithm BS.ShortByteString -}) import Data.HashMap.Strict qualified as HM import Data.List qualified as L import Data.Text qualified as T import Numeric.Natural -import Data.Aeson -- -------------------------------------------------------------------------- -- -- Adhoc class for brief logging output @@ -81,6 +91,13 @@ instance Brief BlockHash where brief = toTextShort instance Brief BlockPayloadHash where brief = toTextShort instance Brief BlockHeader where brief = brief . view blockHash instance Brief (Parent BlockHeader) where brief = brief . unwrapParent +deriving + via (CryptoHash AdjacentsHashAlgorithm) + instance Brief AdjacentsHash + +deriving + via (BriefText (CryptoHash a)) + instance (IncrementalHash a, Coercible a BS.ShortByteString) => Brief (CryptoHash a) instance Brief BlockHashWithHeight where brief a = brief (_bhwhHeight a) <> ":" <> brief (_bhwhHash a) @@ -129,3 +146,26 @@ briefValue (Object o) = Object (briefValue <$> o) briefValue (Array a) = Array (briefValue <$> a) briefValue (String t) = String (toTextShort t) briefValue n = n + +-- -------------------------------------------------------------------------- -- +-- Tools for Deriving Via + +newtype ShowBrief a = ShowBrief a +instance Show a => Brief (ShowBrief a) where + brief (ShowBrief a) = T.take 6 $ T.pack $ show a + +newtype BriefBase64ByteString = BriefBase64ByteString B.ByteString +instance Brief BriefBase64ByteString where + brief (BriefBase64ByteString bytes) = T.take 6 + $ encodeB64UrlNoPaddingText + $ bytes + +newtype BriefBase64ShortByteString = BriefBase64ShortByteString BS.ShortByteString +instance Brief BriefBase64ShortByteString where + brief (BriefBase64ShortByteString bytes) = T.take 6 + $ encodeB64UrlNoPaddingText + $ BS.fromShort bytes + +newtype BriefText a = BriefText a +instance HasTextRepresentation a => Brief (BriefText a) where + brief (BriefText t) = toTextShort t diff --git a/src/Chainweb/Core/CryptoHash.hs b/src/Chainweb/Core/CryptoHash.hs new file mode 100644 index 0000000000..fd3fc4d320 --- /dev/null +++ b/src/Chainweb/Core/CryptoHash.hs @@ -0,0 +1,135 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +-- | +-- Module: Chainweb.Core.CryptoHash +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- Base type for cryptographic hashes in Chainweb +-- +module Chainweb.Core.CryptoHash +( CryptoHash(..) +, unsafeMkCryptoHash +, encodeCryptoHash +, decodeCryptoHash +, cryptoHashBytes +, cryptoHashToText +, cryptoHashFromText +) where + +import Data.ByteString.Short qualified as BS +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Control.DeepSeq (NFData, rnf) +import Control.Monad.Catch +import Data.Aeson +import Data.ByteString qualified as B +import Data.Coerce +import Data.Hash.Class.Mutable +import Data.Hashable (Hashable(..)) +import Data.String (IsString) +import Data.Text qualified as T +import GHC.Exts (Int(I#), indexIntArray#) +import GHC.Generics (Generic) +import GHC.Stack + +-- ----------------------------------------------------------------------------- +-- | Base Type for Cryptographic Hashes in Chainweb. +-- +-- This is mostly useful for use with deriving Via. +-- +-- It also provides a few convenience functions for working with cryptographic +-- hashes. +-- +newtype CryptoHash a = CryptoHash a + deriving (Show, Generic) + deriving newtype (Eq, Ord, IncrementalHash, Hash, ResetableHash, IsString) + +unsafeMkCryptoHash + :: HasCallStack + => Coercible a BS.ShortByteString + => IncrementalHash a + => B.ByteString + -> CryptoHash a +unsafeMkCryptoHash = fromJuste . runGetS decodeCryptoHash +{-# INLINE unsafeMkCryptoHash #-} + +encodeCryptoHash :: Coercible a BS.ShortByteString => CryptoHash a -> Put +encodeCryptoHash (CryptoHash w) = putByteString $ BS.fromShort $ coerce w +{-# INLINE encodeCryptoHash #-} + +cryptoHashBytes + :: Coercible a BS.ShortByteString + => CryptoHash a + -> BS.ShortByteString +cryptoHashBytes = coerce +{-# INLINE cryptoHashBytes #-} + +decodeCryptoHash + :: forall a + . Coercible a BS.ShortByteString + => IncrementalHash a + => Get (CryptoHash a) +decodeCryptoHash = coerce . BS.toShort + <$> getByteString (digestSize @(CryptoHash a)) +{-# INLINE decodeCryptoHash #-} + +cryptoHashToText + :: Coercible a BS.ShortByteString + => CryptoHash a + -> T.Text +cryptoHashToText = encodeB64UrlNoPaddingText . runPutS . encodeCryptoHash +{-# INLINE cryptoHashToText #-} + +cryptoHashFromText + :: Coercible a BS.ShortByteString + => IncrementalHash a + => MonadThrow m + => T.Text + -> m (CryptoHash a) +cryptoHashFromText t = either (throwM . TextFormatException . sshow) return + $ runGetS decodeCryptoHash =<< decodeB64UrlNoPaddingText t +{-# INLINE cryptoHashFromText #-} + +instance Coercible a BS.ShortByteString => NFData (CryptoHash a) where + rnf = rnf . cryptoHashBytes + {-# INLINE rnf #-} + +instance (Coercible a BS.ShortByteString, Eq a) => Hashable (CryptoHash a) where + -- CryptoHashs are already cryptographically strong hashes + -- that include the chain id. + hashWithSalt s h = + hashWithSalt s $ I# $ indexIntArray# arr# 0# + where + !(BS.SBS arr#) = coerce h + {-# INLINE hashWithSalt #-} + +instance Coercible a BS.ShortByteString => ToJSON (CryptoHash a) where + toJSON = toJSON . encodeB64UrlNoPaddingText . runPutS . encodeCryptoHash + toEncoding = b64UrlNoPaddingTextEncoding . runPutS . encodeCryptoHash + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance (Coercible a BS.ShortByteString, IncrementalHash a) => FromJSON (CryptoHash a) where + parseJSON = withText "CryptoHash" $ \t -> + either (fail . show) return + $ runGetS decodeCryptoHash =<< decodeB64UrlNoPaddingText t + {-# INLINE parseJSON #-} + +instance (IncrementalHash a, Coercible a BS.ShortByteString) => HasTextRepresentation (CryptoHash a) where + toText = encodeB64UrlNoPaddingText . runPutS . encodeCryptoHash + fromText = fmap unsafeMkCryptoHash . decodeB64UrlNoPaddingText + {-# INLINE toText #-} + {-# INLINE fromText #-} + diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 8b02e2c6fd..2c31a9caf7 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} @@ -5,13 +7,13 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MagicHash #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE BangPatterns #-} -- | -- Module: Chainweb.Cut.Create @@ -49,25 +51,23 @@ module Chainweb.Cut.Create , getCutExtension -- * WorkParents -, WorkParents +, WorkParents(..) , workParents , _workParent , workParent , _workParentsAdjacentHashes , workParentsAdjacentHashes , newWork +, getAdjacentParentHeaders -- * Work -, WorkHeader(..) -, encodeWorkHeader -, decodeWorkHeader -, newWorkHeader -, newWorkHeaderPure +, MiningWork(..) +, encodeMiningWork +, decodeMiningWork +, newMiningWorkPure -- * Solved Work , SolvedWork(..) -, solvedWorkHeight -, encodeSolvedWork , decodeSolvedWork , extend , InvalidSolvedHeader(..) @@ -99,13 +99,12 @@ import Chainweb.Core.Brief import Chainweb.Cut import Chainweb.Cut.CutHashes import Chainweb.Difficulty +import Chainweb.Parent import Chainweb.PayloadProvider(EncodedPayloadData(..), EncodedPayloadOutputs) -import Chainweb.Time import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Utils -import Chainweb.Parent -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes @@ -274,18 +273,24 @@ getCutExtension c cid = do $ "Chainweb.Miner.Coordinator.getAdjacentParents: internal code invariant violation" -- -------------------------------------------------------------------------- -- --- Mining Work Header +-- Mining Work --- | A work header has a preliminary creation time and an invalid nonce of 0. +-- | Mining work has a preliminary creation time and an invalid nonce of 0. +-- +-- The work is derived from a block header, however it is not obtain the +-- original block header from the work header. In order to obtain the original +-- header one has to extract the nonce and the creation from the mining work and +-- inject it into the original block header. +-- -- It is the job of the miner to update the creation time and find a valid nonce -- that satisfies the target of the header. -- --- The updated header has type 'SolvedHeader'. +-- The result of mining has type 'SolvedMiningWork'. -- -data WorkHeader = WorkHeader - { _workHeaderChainId :: !ChainId - , _workHeaderTarget :: !HashTarget - , _workHeaderBytes :: !SB.ShortByteString +data MiningWork = MiningWork + { _miningWorkChainId :: !ChainId + , _miningWorkTarget :: !HashTarget + , _miningWorkBytes :: !SB.ShortByteString -- ^ 286 work bytes. -- The last 8 bytes are the nonce -- The creation time is encoded in bytes 8-15 @@ -293,43 +298,32 @@ data WorkHeader = WorkHeader deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) -encodeWorkHeader :: WorkHeader -> Put -encodeWorkHeader wh = do - encodeChainId $ _workHeaderChainId wh - encodeHashTarget $ _workHeaderTarget wh - putByteString $ SB.fromShort $ _workHeaderBytes wh +encodeMiningWork :: MiningWork -> Put +encodeMiningWork wh = do + encodeChainId $ _miningWorkChainId wh + encodeHashTarget $ _miningWorkTarget wh + putByteString $ SB.fromShort $ _miningWorkBytes wh -- FIXME: We really want this indepenent of the block height. For production -- chainweb version this is actually the case. -- -decodeWorkHeader :: ChainwebVersion -> BlockHeight -> Get WorkHeader -decodeWorkHeader ver h = WorkHeader +decodeMiningWork :: ChainwebVersion -> BlockHeight -> Get MiningWork +decodeMiningWork ver h = MiningWork <$> decodeChainId <*> decodeHashTarget <*> (SB.toShort <$> getByteString (int $ workSizeBytes ver h)) --- | Create work header for cut --- -newWorkHeader - :: ChainValueCasLookup hdb BlockHeader - => hdb - -> CutExtension - -> BlockPayloadHash - -> IO WorkHeader -newWorkHeader hdb e h = do - creationTime <- BlockCreationTime <$> getCurrentTimeIntegral - newWorkHeaderPure (chainLookupM hdb) creationTime e h - --- | A pure version of 'newWorkHeader' that is useful in testing. +-- | A pure version of 'newWorkHeader' that is useful in testing. It is not used +-- in production code. -- -newWorkHeaderPure +newMiningWorkPure :: Applicative m => (ChainValue BlockHash -> m BlockHeader) -> BlockCreationTime -> CutExtension -> BlockPayloadHash - -> m WorkHeader -newWorkHeaderPure hdb creationTime extension phash = do + -> m MiningWork +newMiningWorkPure hdb creationTime extension phash = do -- Collect block headers for adjacent parents, some of which may be -- available in the cut. createWithParents <$> getAdjacentParentHeaders hdb extension @@ -342,10 +336,10 @@ newWorkHeaderPure hdb creationTime extension phash = do let nh = newBlockHeader parents phash (Nonce 0) creationTime $! _cutExtensionParent extension -- FIXME: check that parents also include hashes on new chains! - in WorkHeader - { _workHeaderBytes = SB.toShort $ runPutS $ encodeBlockHeaderWithoutHash nh - , _workHeaderTarget = view blockTarget nh - , _workHeaderChainId = _chainId nh + in MiningWork + { _miningWorkBytes = SB.toShort $ runPutS $ encodeAsMiningWork nh + , _miningWorkTarget = view blockTarget nh + , _miningWorkChainId = _chainId nh } -- | Get all adjacent parent headers for a new block header for a given cut. @@ -388,8 +382,11 @@ getAdjacentParentHeaders hdb extension <> ". CutHashes: " <> encodeToText (cutToCutHashes Nothing c) -- -------------------------------------------------------------------------- -- --- +-- Work Parents +-- | The direct parents and adjacent new parents onto which a new block is +-- created. +-- data WorkParents = WorkParents { _workParent' :: !(Parent BlockHeader) -- ^ The header onto which the new block is created. @@ -403,6 +400,11 @@ data WorkParents = WorkParents } deriving (Show, Eq, Generic) +instance Brief WorkParents where + brief ps = brief (view _Parent $ _workParent ps) + <> ":" + <> brief (HM.toList $ view _Parent <$> _workAdjacentParents' ps) + _workParent :: WorkParents -> Parent BlockHeader _workParent = _workParent' @@ -439,55 +441,108 @@ newWork :: BlockCreationTime -> WorkParents -> BlockPayloadHash - -> WorkHeader -newWork creationTime parents pldHash = WorkHeader - { _workHeaderBytes = SB.toShort $ runPutS $ encodeBlockHeaderWithoutHash nh - , _workHeaderTarget = view blockTarget nh - , _workHeaderChainId = _chainId nh + -> MiningWork +newWork creationTime parents pldHash = MiningWork + { _miningWorkBytes = SB.toShort $ runPutS $ encodeAsMiningWork nh + , _miningWorkTarget = view blockTarget nh + , _miningWorkChainId = _chainId nh } where adjParents = _workAdjacentParents' parents parent = _workParent' parents nh = newBlockHeader adjParents pldHash (Nonce 0) creationTime parent --- -------------------------------------------------------------------------- -- --- Solved Header +-- | TODO: do we have to verify that the solved for matches the work parents? -- +newHeader + :: WorkParents + -> SolvedWork + -> BlockHeader +newHeader parents solved = + newBlockHeader adjParents pldHash nonce creationTime parent + where + pldHash = _solvedPayloadHash solved + adjParents = _workAdjacentParents' parents + parent = _workParent' parents + nonce = _solvedWorkNonce solved + creationTime = _solvedWorkCreationTime solved --- | A solved header is the result of mining. It has the final creation time and --- a valid nonce. The binary representation doesn't yet contain a Merkle Hash. --- Also, the block header is not yet validated. --- --- This is an internal data type. On the client/miner side only the serialized --- representation is used. --- -newtype SolvedWork = SolvedWork BlockHeader +-- -------------------------------------------------------------------------- -- +-- Solved Work + +-- | A solution for mining work. It provides final creation time and +-- a valid nonce for the respective new header that is identfied by the parent +-- hashes, and the payload hash. +-- +-- With the currently mining API, mining clients actually return a solved mining +-- work value from which the solution is extracted. Future version of the API +-- will most like return SolvedWork values directly. +-- +data SolvedWork = SolvedWork + { _solvedChainId :: !ChainId + , _solvedParentHash :: !(Parent BlockHash) + , _solvedAdjacentHash :: !AdjacentsHash + , _solvedPayloadHash :: !BlockPayloadHash + , _solvedWorkCreationTime :: !BlockCreationTime + , _solvedWorkNonce :: !Nonce + } deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) -encodeSolvedWork :: SolvedWork -> Put -encodeSolvedWork (SolvedWork hdr) = encodeBlockHeaderWithoutHash hdr - -decodeSolvedWork :: Get SolvedWork -decodeSolvedWork = SolvedWork <$> decodeBlockHeaderWithoutHash - -solvedWorkHeight :: Getter SolvedWork BlockHeight -solvedWorkHeight = to (\(SolvedWork hdr) -> hdr) . blockHeight - instance HasChainId SolvedWork where - _chainId (SolvedWork hdr) = _chainId hdr + _chainId = _solvedChainId + {-# INLINE _chainId #-} -instance HasChainwebVersion SolvedWork where - _chainwebVersion (SolvedWork hdr) = _chainwebVersion hdr +-- | This is a special decoding function that decode solved work from the +-- mining work bytes that are minter returns as solution. +-- +decodeSolvedWork :: Get SolvedWork +decodeSolvedWork = do + -- creation time: [8:15] + skip 8 + time <- decodeBlockCreationTime + + -- parenthash: [16:47] + parent <- decodeBlockHash + + -- third adjacent hash: [126:157] + skip 78 + adj <- decodeAdjacentsHash + + -- payload hash: [190:221] + skip 32 + pldHash <- decodeBlockPayloadHash + + -- chainId [222:225] + cid <- decodeChainId + + -- nonce: [278:285] + skip 52 + nonce <- decodeNonce + + return $ SolvedWork + { _solvedChainId = cid + , _solvedParentHash = Parent parent + , _solvedAdjacentHash = adj + , _solvedPayloadHash = pldHash + , _solvedWorkCreationTime = time + , _solvedWorkNonce = nonce + } -data InvalidSolvedHeader = InvalidSolvedHeader BlockHeader T.Text +data InvalidSolvedHeader = InvalidSolvedHeader T.Text deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) instance Exception InvalidSolvedHeader instance Brief SolvedWork where - brief (SolvedWork hdr) = "SolvedWork" <> ":" <> brief hdr + brief (SolvedWork cid p a pld t n) = "SolvedWork" + <> ": chain " <> brief cid + <> ", parent " <> brief p + <> ", adj " <> brief a + <> ", payload " <> brief pld + <> ", time " <> sshow t + <> ", nonce " <> sshow n -- | Extend a Cut with a solved work value. -- @@ -501,18 +556,16 @@ instance Brief SolvedWork where -- The result is 'Nothing' if the given cut can't be extended with the solved -- work. -- --- FIXME: this should be the only function that can pattern match the Header out --- of a 'SolvedWork' value. --- extend :: MonadThrow m => Cut -> Maybe EncodedPayloadData -> Maybe EncodedPayloadOutputs + -> WorkParents -> SolvedWork -> m (BlockHeader, Maybe CutHashes) -extend c pld pwo s = do - (bh, mc) <- extendCut c s +extend c pld pwo ps s = do + (bh, mc) <- extendCut c ps s return (bh, toCutHashes bh <$> mc) where toCutHashes bh c' = cutToCutHashes Nothing c' @@ -528,17 +581,20 @@ extend c pld pwo s = do extendCut :: MonadThrow m => Cut + -> WorkParents -> SolvedWork -> m (BlockHeader, Maybe Cut) -extendCut c (SolvedWork bh) = do +extendCut c ps s = do + -- Fail Early: If a `BlockHeader`'s injected Nonce (and thus its POW + -- Hash) is trivially incorrect, reject it. + -- + unless (prop_block_pow bh) + $ throwM $ InvalidSolvedHeader "Invalid POW hash" - -- Fail Early: If a `BlockHeader`'s injected Nonce (and thus its POW - -- Hash) is trivially incorrect, reject it. - -- - unless (prop_block_pow bh) - $ throwM $ InvalidSolvedHeader bh "Invalid POW hash" + -- If the `BlockHeader` is already stale and can't be appended to the + -- best `Cut`, Nothing is returned + -- + (bh,) <$> tryMonotonicCutExtension c bh + where + bh = newHeader ps s - -- If the `BlockHeader` is already stale and can't be appended to the - -- best `Cut`, Nothing is returned - -- - (bh,) <$> tryMonotonicCutExtension c bh diff --git a/src/Chainweb/Logging/Miner.hs b/src/Chainweb/Logging/Miner.hs index 7b3e771220..795e36c39e 100644 --- a/src/Chainweb/Logging/Miner.hs +++ b/src/Chainweb/Logging/Miner.hs @@ -29,6 +29,8 @@ import Chainweb.BlockHeader import Chainweb.Time import Chainweb.MinerReward import Numeric.Natural +import Chainweb.Parent +import Chainweb.BlockHash data NewMinedBlock = NewMinedBlock { _minedBlockHeader :: !(ObjectEncoded BlockHeader) @@ -42,7 +44,8 @@ data NewMinedBlock = NewMinedBlock deriving anyclass (ToJSON, NFData) data OrphanedBlock = OrphanedBlock - { _orphanedHeader :: !(ObjectEncoded BlockHeader) + { _orphanedParent :: !(Parent BlockHash) + , _orphanedPayloadHash :: !BlockPayloadHash , _orphanedBestOnCut :: !(ObjectEncoded BlockHeader) , _orphanedDiscoveredAt :: !(Time Micros) , _orphanedReason :: !Text diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 2b84c3679a..ca4d98053f 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -122,6 +122,20 @@ lookupInCut c cid <> " Chain: " <> sshow (_chainId cid) <> "." <> " Cut Hashes: " <> encodeToText (cutToCutHashes Nothing c) <> "." +type WorkParentsId = (Parent BlockHash, AdjacentsHash) + +parentsId :: WorkParents -> WorkParentsId +parentsId ps = + ( view blockHash <$> _workParent ps + , adjacentsHash (_workParentsAdjacentHashes ps) + ) + +solvedId :: SolvedWork -> WorkParentsId +solvedId s = + ( _solvedParentHash s + , _solvedAdjacentHash s + ) + -- -------------------------------------------------------------------------- -- -- Payload Caches @@ -244,14 +258,14 @@ data WorkState -- -- Invariant: The payload must match the ranked block hash. - | WorkReady !(Parent RankedBlockHash) !NewPayload !WorkParents !WorkHeader + | WorkReady !(Parent RankedBlockHash) !NewPayload !WorkParents -- ^ The chain is ready for mining -- -- Invariant: The payload, parents, work header must match the ranked block -- hash. | WorkSolved !(Parent RankedBlockHash) !NewPayload !WorkParents - -- ^ A block with this parent has already been solved and submitted to the + -- ^ A block with these parents has already been solved and submitted to the -- cut pipeline - we don't want to mine it again. So we wait until the -- current cut is updated with a new parent header for this chain. @@ -261,7 +275,7 @@ _workRankedHash :: WorkState -> Parent RankedBlockHash _workRankedHash (WorkNotReady rh) = rh _workRankedHash (WorkStale rh _) = rh _workRankedHash (WorkBlocked rh _) = rh -_workRankedHash (WorkReady rh _ _ _) = rh +_workRankedHash (WorkReady rh _ _) = rh _workRankedHash (WorkSolved rh _ _) = rh _workStateHash :: WorkState -> Parent BlockHash @@ -271,14 +285,11 @@ _workStateHeight :: WorkState -> Parent BlockHeight _workStateHeight = fmap _rankedBlockHashHeight . _workRankedHash workReady - :: BlockCreationTime - -> Parent RankedBlockHash + :: Parent RankedBlockHash -> NewPayload -> WorkParents -> WorkState -workReady t rbh pld ps' = WorkReady rbh pld ps' - $ newWork t ps' - $ _newPayloadBlockPayloadHash pld +workReady rbh pld ps' = WorkReady rbh pld ps' _newPayloadRankedHash :: NewPayload -> RankedBlockHash _newPayloadRankedHash p = @@ -288,7 +299,7 @@ instance Brief WorkState where brief (WorkNotReady rh) = "WorkNotReady" <> ":" <> brief rh brief (WorkStale rh _) = "WorkStale" <> ":" <> brief rh brief (WorkBlocked rh _) = "WorkBlocked" <> ":" <> brief rh - brief (WorkReady rh _ _ _) = "WorkReady" <> ":" <> brief rh + brief (WorkReady rh _ _) = "WorkReady" <> ":" <> brief rh brief (WorkSolved rh _ _) = "WorkSolved" <> ":" <> brief rh -- -------------------------------------------------------------------------- -- @@ -311,31 +322,30 @@ onHeader rh cur -- payload available for the new header. -- onParents - :: BlockCreationTime - -> Maybe WorkParents + :: Maybe WorkParents -- Just the work parents or 'Nothing' when the chain is blocked. -> WorkState -> Maybe WorkState -onParents t (Just ps) cur | psRankedHash /= _workRankedHash cur = - onHeader psRankedHash cur >>= onParents t (Just ps) +onParents (Just ps) cur | psRankedHash /= _workRankedHash cur = + onHeader psRankedHash cur >>= onParents (Just ps) where psRankedHash = _rankedBlockHash <$> _workParent ps -onParents _ (Just ps') (WorkNotReady rh) = Just $ WorkStale rh ps' -onParents t (Just ps') (WorkBlocked rh pld) = Just $ workReady t rh pld ps' -onParents _ (Just ps') (WorkStale rh ps) +onParents (Just ps') (WorkNotReady rh) = Just $ WorkStale rh ps' +onParents (Just ps') (WorkBlocked rh pld) = Just $ workReady rh pld ps' +onParents (Just ps') (WorkStale rh ps) | ps /= ps' = Just $ WorkStale rh ps' | otherwise = Nothing -onParents t (Just ps') (WorkReady rh pld ps _) - | ps /= ps' = Just $ workReady t rh pld ps' +onParents (Just ps') (WorkReady rh pld ps) + | ps /= ps' = Just $ workReady rh pld ps' | otherwise = Nothing -onParents t (Just ps') (WorkSolved rh pld ps) - | ps /= ps' = Just $ workReady t rh pld ps' +onParents (Just ps') (WorkSolved rh pld ps) + | ps /= ps' = Just $ workReady rh pld ps' | otherwise = Nothing -onParents _ Nothing WorkNotReady{} = Nothing -onParents _ Nothing WorkBlocked{} = Nothing -onParents _ Nothing (WorkStale rh _) = Just $ WorkNotReady rh -onParents _ Nothing (WorkReady rh pld _ _) = Just $ WorkBlocked rh pld -onParents _ Nothing (WorkSolved rh pld _) = Just $ WorkBlocked rh pld +onParents Nothing WorkNotReady{} = Nothing +onParents Nothing WorkBlocked{} = Nothing +onParents Nothing (WorkStale rh _) = Just $ WorkNotReady rh +onParents Nothing (WorkReady rh pld _) = Just $ WorkBlocked rh pld +onParents Nothing (WorkSolved rh pld _) = Just $ WorkBlocked rh pld -- | Called on a new payload event. -- @@ -344,20 +354,19 @@ onParents _ Nothing (WorkSolved rh pld _) = Just $ WorkBlocked rh pld -- parent header. -- onPayload - :: BlockCreationTime - -> NewPayload + :: NewPayload -> WorkState -> Maybe WorkState -onPayload _ pld' cur | _newPayloadRankedParentHash pld' /= _workRankedHash cur = Nothing -onPayload _ pld' (WorkNotReady rh) = Just $ WorkBlocked rh pld' -onPayload t pld' (WorkStale rh ps) = Just $ workReady t rh pld' ps -onPayload _ pld' (WorkBlocked rh pld) +onPayload pld' cur | _newPayloadRankedParentHash pld' /= _workRankedHash cur = Nothing +onPayload pld' (WorkNotReady rh) = Just $ WorkBlocked rh pld' +onPayload pld' (WorkStale rh ps) = Just $ workReady rh pld' ps +onPayload pld' (WorkBlocked rh pld) | pld /= pld' = Just $ WorkBlocked rh pld' | otherwise = Nothing -onPayload t pld' (WorkReady rh pld ps _) - | pld /= pld' = Just $ workReady t rh pld' ps +onPayload pld' (WorkReady rh pld ps) + | pld /= pld' = Just $ workReady rh pld' ps | otherwise = Nothing -onPayload _ _ WorkSolved{} = Nothing +onPayload _ WorkSolved{} = Nothing -- | Called when work is solved for the chain. -- @@ -365,18 +374,15 @@ onSolved :: SolvedWork -> WorkState -> Maybe WorkState -onSolved (SolvedWork hdr) (WorkSolved rh _ ps) - -- If we solved this header in this cut before we do not change the work - -- state, even if the payload differs. - -- TODO: this might be wrong. `hdr` is the newly made work header, but `rh` looks to be the parent header of that. - | view blockParent hdr == fmap _rankedBlockHashHash rh - && view blockAdjacentHashes hdr /= _workParentsAdjacentHashes ps = Nothing -onSolved (SolvedWork hdr) (WorkReady rh pld ps _) +onSolved s (WorkReady rh pld ps) -- If work is currently ready for this header in this cut, we mark it solved. - | view blockParent hdr == fmap _rankedBlockHashHash rh - && view blockAdjacentHashes hdr /= _workParentsAdjacentHashes ps = - Just $ WorkSolved rh pld ps - -- otherwise do not change the state. + -- We checked that before, when we processed the solution. But we have to do + -- it again, since the state is updated asynchronously and there could be a + -- race. + | solvedId s == parentsId ps = Just $ WorkSolved rh pld ps +-- otherwise do not change the state. +-- If we solved this header in this cut before we do not change the work +-- state, even if the payload differs. onSolved _ _ = Nothing -- -------------------------------------------------------------------------- -- @@ -425,14 +431,12 @@ newMiningState c = do updateStateVar :: LogFunctionText -> ChainId -> TVar WorkState -> WorkState -> IO () updateStateVar lf cid var new = do - -- Logging. This can race, but we don't care cur <- readTVarIO var lf Debug $ "update work state" <> "; chain: " <> toText cid <> "; cur: " <> brief cur <> "; new: " <> brief new - atomically $ writeTVar var new -- TODO: consider storing the mining state more efficiently: @@ -452,11 +456,10 @@ updateForCut -> Cut -> IO () updateForCut lf hdb caches ms c = do - t <- BlockCreationTime <$> getCurrentTimeIntegral forM_ (M.toList $ _miningState ms) $ \(cid, var) -> - forChain t cid var (caches ^?! ix cid) + forChain cid var (caches ^?! ix cid) where - forChain t cid var cache = do + forChain cid var cache = do ps <- workParents hdb c cid cur <- readTVarIO var @@ -473,7 +476,7 @@ updateForCut lf hdb caches ms c = do -- <> "; cache latest: " <> brief cl -- <> "; cache hashes: " <> brief ch - case onParents t ps cur of + case onParents ps cur of Nothing -> return () Just !new @@ -484,20 +487,17 @@ updateForCut lf hdb caches ms c = do -- if the parent header changed, check if a payload is available | otherwise -> getLatestIO cache (_workRankedHash new) >>= \case Nothing -> updateStateVar lf cid var new - Just pld -> case onPayload t pld new of + Just pld -> case onPayload pld new of Nothing -> updateStateVar lf cid var new Just !newnew -> updateStateVar lf cid var newnew updateForPayload :: LogFunctionText -> MiningState -> NewPayload -> IO () updateForPayload lf ms pld = do - t <- BlockCreationTime <$> getCurrentTimeIntegral cur <- readTVarIO var - -- lf @T.Text Debug $ "updateForPayload on chain: " <> toText cid -- <> "; cur: " <> brief cur -- <> "; new payload: " <> brief pld - - case onPayload t pld cur of + case onPayload pld cur of Nothing -> return () Just !new -> updateStateVar lf cid var new where @@ -507,24 +507,23 @@ updateForPayload lf ms pld = do updateForSolved :: LogFunctionText -> MiningState -> SolvedWork -> IO () updateForSolved lf ms sw = do cur <- readTVarIO var - -- lf @T.Text Debug $ "updateForSolved on chain: " <> toText cid -- <> "; cur: " <> brief cur -- <> "; sw: " <> brief sw - case onSolved sw cur of Nothing -> return () - Just !new -> updateStateVar lf cid var new + Just !new -> do + updateStateVar lf cid var new where cid = _chainId sw var = ms ^?! ixg cid -awaitAnyReady :: MiningState -> STM WorkHeader +awaitAnyReady :: MiningState -> STM (WorkParents, NewPayload) awaitAnyReady s = msum $ awaitWorkReady <$> _miningState s where - awaitWorkReady :: TVar WorkState -> STM WorkHeader + awaitWorkReady :: TVar WorkState -> STM (WorkParents, NewPayload) awaitWorkReady var = readTVar var >>= \case - WorkReady _ _ _ w -> return w + WorkReady _ pld ps -> return (ps, pld) _ -> retry -- -------------------------------------------------------------------------- -- @@ -578,7 +577,6 @@ runCoordination => MiningCoordination l -> IO () runCoordination mr = do - -- Initialize Work State for provider caches, without this isolated networks -- fail to start mining. initializeState @@ -721,7 +719,7 @@ awaitEvent cdb caches c p = -- 3. some payload providers are deadlocked, or -- 4. some payload providers are very slow in producing new payloads. -- -randomWork :: LogFunction -> MiningState -> IO WorkHeader +randomWork :: LogFunction -> MiningState -> IO MiningWork randomWork logFun state = do -- Pick a random chain. @@ -788,13 +786,16 @@ randomWork logFun state = do w <- atomically $ Right <$> awaitAnyReady state <|> awaitTimeout timeoutVar case w of - Right x -> return x + Right (ps, npld) -> do + ct <- BlockCreationTime <$> getCurrentTimeIntegral + return $ newWork ct ps (_newPayloadBlockPayloadHash npld) Left e -> error e -- FIXME: throw a proper exception and log what is going on go ((cid, var):t) = readTVarIO var >>= \case - WorkReady _ _ _ wh -> do + WorkReady _ npld ps -> do logFun @T.Text Debug $ "randomWork: picked chain " <> brief cid - return wh + ct <- BlockCreationTime <$> getCurrentTimeIntegral + return $ newWork ct ps (_newPayloadBlockPayloadHash npld) e -> do logFun @T.Text Info $ "randomWork: not ready for " <> brief cid <> "; state: " <> brief e @@ -826,7 +827,7 @@ work :: forall l . Logger l => MiningCoordination l - -> IO WorkHeader + -> IO MiningWork work mr = randomWork lf (_coordState mr) where lf :: LogFunction @@ -853,59 +854,107 @@ instance Exception NoAsscociatedPayload -- There are a number of "fail fast" conditions which will kill the candidate -- `BlockHeader` before it enters the Cut pipeline. -- +-- NEW DOC: +-- +-- First we check wether the state is still up to date. If the parents in the +-- mining state for the chain do not match the parents of the solved work, we +-- can't build a header for it and reject it. +-- +-- Next we lookup the payload in the cache. This is expected to succeed. It is +-- an exceptional case if it does not. +-- +-- We then try to extend the current cut. This can fail if the current cut +-- changed asynchronously. If it fails we log an orphaned block. +-- +-- If the cut is extended successfully we publish the new cut and updated +-- the mining state. This can race with other updates to the mining state. +-- However, at this point we don't care. +-- (Should we remove the solved state all together?) +-- solve :: forall l . Logger l => MiningCoordination l -> SolvedWork -> IO () -solve mr solved@(SolvedWork hdr) = - lookupIO cache cacheKey (view blockPayloadHash hdr) >>= \case - - Nothing -> do - ch <- payloadHashesIO cache - lf Error $ "solve: no payload for " <> brief hdr - <> "; cache key: " <> brief cacheKey - <> "; cache content: " <> brief ch - throwM NoAsscociatedPayload - -- FIXME Do we really need to restart the coordinator? - - Just np -> do - c <- _cut cdb - now <- getCurrentTimeIntegral - let pld = _newPayloadEncodedPayloadData np - let pwo = _newPayloadEncodedPayloadOutputs np - - try (extend c pld pwo solved) >>= \case - - -- Publish CutHashes to CutDb and log success - Right (bh, Just ch) -> do - updateForSolved lf (_coordState mr) solved - publish cdb ch - logMinedBlock lf bh np - - -- Log Orphaned Block - Right (bh, Nothing) -> do - let !p = lookupInCut c bh - lf Info $ orphandMsg now p bh "orphaned solution" - - -- Log failure and rethrow - Left e@(InvalidSolvedHeader bh msg) -> do - let !p = lookupInCut c bh - lf Info $ orphandMsg now p bh msg - throwM e +solve mr solved = (readTVarIO $ _coordState mr ^?! ixg cid) >>= \case + + WorkSolved{} -> + -- The solved work is already in the mining state + lf Info $ "solve: ignoring solution for mining state that was already solved before" + <> ". solved " <> brief solved + -- TODO check and log whether the solved state matches the solution + + WorkReady rh npld ps -> do + -- The solved work is still valid and the payload is available + + let pldh = _newPayloadBlockPayloadHash npld + + -- Check that the solved parents match the current mining state parents. + -- We can be lenient on the payloads, but the parents must match the + -- current cut. + if solvedId solved /= parentsId ps + then do + -- ignore solved work + lf Info $ "solve: solved work does not match the current mining state" + <> "; solved: " <> brief solved + <> "; current work parents: " <> brief ps + + -- check that we have the payload for the solution in the cache. + else lookupIO cache rh pldh >>= \case + + Nothing -> do + ch <- payloadHashesIO cache + lf Error $ "solve: no payload for " <> brief solved + <> "; cache key: " <> brief rh + <> "; cache content: " <> brief ch + throwM NoAsscociatedPayload + -- FIXME Do we really need to restart the coordinator? + + Just np -> do + c <- _cut cdb + now <- getCurrentTimeIntegral + let pld = _newPayloadEncodedPayloadData np + let pwo = _newPayloadEncodedPayloadOutputs np + + try (extend c pld pwo ps solved) >>= \case + + -- Publish CutHashes to CutDb and log success + Right (bh, Just ch) -> do + updateForSolved lf (_coordState mr) solved + publish cdb ch + logMinedBlock lf bh np + + -- Log Orphaned Block + Right (_, Nothing) -> do + let !p = lookupInCut c cid + lf Info $ orphandMsg now p solved "orphaned solution" + + -- Log failure and rethrow + Left e@(InvalidSolvedHeader msg) -> do + let !p = lookupInCut c cid + lf Info $ orphandMsg now p solved msg + throwM e + _ -> do + -- The solved work is orphaned. + lf Info $ "solve: ignoring outdated solution for. Mining state is not ready or solved" + <> ". solved: " <> brief solved + c <- _cut cdb + now <- getCurrentTimeIntegral + let !p = lookupInCut c cid + lf Info $ orphandMsg now p solved "orphaned solution" where cid = _chainId solved cdb = _coordCutDb mr caches = _coordPayloadCache mr cache = caches HM.! cid - cacheKey = Parent $ RankedBlockHash (view blockHeight hdr - 1) (unwrapParent $ view blockParent hdr) lf :: LogFunction lf = logFunction $ _coordLogger mr - orphandMsg now p bh msg = JsonLog OrphanedBlock - { _orphanedHeader = ObjectEncoded bh + orphandMsg now p s msg = JsonLog OrphanedBlock + { _orphanedParent = _solvedParentHash s + , _orphanedPayloadHash = _solvedPayloadHash s , _orphanedBestOnCut = ObjectEncoded p , _orphanedDiscoveredAt = now , _orphanedReason = msg diff --git a/src/Chainweb/Miner/Core.hs b/src/Chainweb/Miner/Core.hs index f775f105e7..3e06741a1f 100644 --- a/src/Chainweb/Miner/Core.hs +++ b/src/Chainweb/Miner/Core.hs @@ -95,7 +95,7 @@ mine :: forall a . HashAlgorithm a => Nonce - -> WorkHeader + -> MiningWork -> IO SolvedWork mine orig work = do when (bufSize < noncePosition + sizeOf (0 :: Word64)) $ @@ -137,8 +137,8 @@ mine orig work = do go0 100000 t orig runGetS decodeSolvedWork new where - tbytes = runPutS $ encodeHashTarget (_workHeaderTarget work) - hbytes = BS.fromShort $ _workHeaderBytes work + tbytes = runPutS $ encodeHashTarget (_miningWorkTarget work) + hbytes = BS.fromShort $ _miningWorkBytes work bufSize :: Int !bufSize = B.length hbytes diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index 186b764956..0f1aa8a538 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -96,9 +96,9 @@ localTest lf v coord cdb gen miners = runForever lf "Chainweb.Miner.Miners.localTest" $ do c <- _cut cdb wh <- work coord - let height = c ^?! ixg (_workHeaderChainId wh) . blockHeight + let height = c ^?! ixg (_miningWorkChainId wh) . blockHeight - race (awaitNewCutByChainId cdb (_workHeaderChainId wh) c) (go height wh) >>= \case + race (awaitNewCutByChainId cdb (_miningWorkChainId wh) c) (go height wh) >>= \case Left _ -> return () Right new -> do solve coord new @@ -107,10 +107,10 @@ localTest lf v coord cdb gen miners = meanBlockTime :: Double meanBlockTime = int (_getBlockDelay (_versionBlockDelay v)) / 1_000_000 - go :: BlockHeight -> WorkHeader -> IO SolvedWork + go :: BlockHeight -> MiningWork -> IO SolvedWork go height w = do MWC.geometric1 t gen >>= threadDelay - runGetS decodeSolvedWork $ BS.fromShort $ _workHeaderBytes w + runGetS decodeSolvedWork $ BS.fromShort $ _miningWorkBytes w where t :: Double t = int graphOrder / (int (_minerCount miners) * meanBlockTime * 1_000_000) @@ -145,13 +145,13 @@ localPOW lf coord cdb = runForever lf "Chainweb.Miner.Miners.localPOW" $ do c <- _cut cdb lf Debug "request new work for localPOW miner" wh <- work coord - let cid = _workHeaderChainId wh + let cid = _miningWorkChainId wh lf Debug $ "run localPOW miner on chain " <> toText cid race (awaitNewCutByChainId cdb cid c) (go wh) >>= \case Left _ -> do lf Debug "abondond work due to chain update" return () - Right new -> do + Right (new, h) -> do lf Debug $ "solved work on chain " <> toText cid solve coord new @@ -161,12 +161,21 @@ localPOW lf coord cdb = runForever lf "Chainweb.Miner.Miners.localPOW" $ do -- chain is at least as high as the solved work. -- This can still dead-lock if for some reason the solved work is -- invalid. - awaitHeight (_chainId new) (view solvedWorkHeight new) + awaitHeight (_chainId new) h where - go :: WorkHeader -> IO SolvedWork - go = mine @Blake2s_256 (Nonce 0) + go :: MiningWork -> IO (SolvedWork, BlockHeight) + go w = do + h <- workHeight w + s <- mine @Blake2s_256 (Nonce 0) w + return (s, h) + + workHeight w = runGetS (skip 258 >> decodeBlockHeight) + $ BS.fromShort + $ _miningWorkBytes w awaitHeight cid h = atomically $ do c <- _cutStm cdb let h' = view blockHeight $ c ^?! ixg cid guard (h <= h') + + diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index 42f2ff3508..2ed4db047e 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -65,7 +65,7 @@ workHandler -> Handler WorkBytes workHandler mr = do wh <- liftIO $ work mr - return $ WorkBytes $ runPutS $ encodeWorkHeader wh + return $ WorkBytes $ runPutS $ encodeMiningWork wh -- -------------------------------------------------------------------------- -- -- Solved Handler @@ -86,7 +86,7 @@ solvedHandler mr (HeaderBytes bytes) = do result <- liftIO $ catches (Right () <$ solve mr solved) [ E.Handler $ \NoAsscociatedPayload -> return $ Left $ setErrText "No associated Payload" err404 - , E.Handler $ \(InvalidSolvedHeader _ msg) -> + , E.Handler $ \(InvalidSolvedHeader msg) -> return $ Left $ setErrText ("Invalid solved work: " <> msg) err400 ] case result of @@ -178,11 +178,10 @@ awaitWorkChange ms cid timer prevVar = go -- check result case r of Nothing -> return Nothing - Just (WorkReady prh ppld pps pwh, WorkReady crh cpld cps cwh) + Just (WorkReady prh ppld pps, WorkReady crh cpld cps) | prh /= crh -> return $ Just WorkOutdated | pps /= cps -> return $ Just WorkOutdated | ppld /= cpld -> return $ Just WorkRefreshed - | pwh /= cwh -> return $ Just WorkRefreshed | otherwise -> go Just (WorkReady{}, _) -> return $ Just WorkOutdated _ -> go diff --git a/src/Chainweb/Utils/Serialization.hs b/src/Chainweb/Utils/Serialization.hs index 243ca6d0d4..30d0726ac7 100644 --- a/src/Chainweb/Utils/Serialization.hs +++ b/src/Chainweb/Utils/Serialization.hs @@ -41,6 +41,7 @@ module Chainweb.Utils.Serialization , getShortByteString , putRawByteString , getRemainingLazyByteString + , skip -- abstract encoders and decoders , WordEncoding(..) @@ -181,6 +182,10 @@ putRawByteString = coerce (Binary.putBuilder . Builder.fromByteString) getRemainingLazyByteString :: Get BL.ByteString getRemainingLazyByteString = coerce Binary.getRemainingLazyByteString +skip :: Int -> Get () +skip = coerce . Binary.skip +{-# INLINE skip #-} + -------------------- -- Abstract encoders/decoders -------------------- diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 2ffa28e9c8..87b4ca97e0 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -233,6 +233,7 @@ data Fork | Pact5Fork | Chainweb228Pact | Chainweb229Pact + | HashedAdjacentRecord | Chainweb230Pact -- always add new forks at the end, not in the middle of the constructors. deriving stock (Bounded, Generic, Eq, Enum, Ord, Show) @@ -273,6 +274,7 @@ instance HasTextRepresentation Fork where toText Pact5Fork = "pact5" toText Chainweb228Pact = "chainweb228Pact" toText Chainweb229Pact = "chainweb229Pact" + toText HashedAdjacentRecord = "hashedAdjacentRecord" toText Chainweb230Pact = "chainweb230Pact" fromText "slowEpoch" = return SlowEpoch @@ -309,6 +311,7 @@ instance HasTextRepresentation Fork where fromText "pact5" = return Pact5Fork fromText "chainweb228Pact" = return Chainweb228Pact fromText "chainweb229Pact" = return Chainweb229Pact + fromText "hashedAdjacentRecord" = return HashedAdjacentRecord fromText "chainweb230Pact" = return Chainweb230Pact fromText t = throwM . TextFormatException $ "Unknown Chainweb fork: " <> t diff --git a/src/Chainweb/Version/Guards.hs b/src/Chainweb/Version/Guards.hs index fdf986bfb5..441f2ec3fb 100644 --- a/src/Chainweb/Version/Guards.hs +++ b/src/Chainweb/Version/Guards.hs @@ -63,6 +63,7 @@ module Chainweb.Version.Guards , oldTargetGuard , skipFeatureFlagValidationGuard , oldDaGuard + , hashedAdjacentRecord ) where import Chainweb.BlockHeight @@ -286,6 +287,8 @@ maxBlockGasLimit :: ChainwebVersion -> BlockHeight -> Maybe Natural maxBlockGasLimit v bh = snd $ ruleZipperHere $ snd $ ruleSeek (\h _ -> bh >= h) (_versionMaxBlockGasLimit v) +hashedAdjacentRecord :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +hashedAdjacentRecord = checkFork atOrAfter HashedAdjacentRecord -- | Different versions of Chainweb allow different PPKSchemes. -- diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index 07cf91cae5..10e04aa9a4 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -127,6 +127,7 @@ mainnet = ChainwebVersion Pact5Fork -> AllChains (ForkAtBlockHeight $ BlockHeight 5_555_698) -- 2025-02-10 00:00:00+00:00 Chainweb228Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 5_659_280) -- 2025-03-18 00:00:00+00:00 Chainweb229Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 5_785_923) -- 2025-05-01 00:00:00+00:00 + HashedAdjacentRecord -> AllChains (ForkAtBlockHeight $ BlockHeight 5_785_923) -- 2025-05-01 00:00:00+00:00 Chainweb230Pact -> AllChains ForkNever , _versionGraphs = diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index 75c9549475..b615edfbee 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -69,6 +69,7 @@ recapDevnet = ChainwebVersion Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 10 Chainweb229Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 Chainweb230Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 30 + HashedAdjacentRecord -> AllChains $ ForkAtBlockHeight $ BlockHeight 40 , _versionUpgrades = onChains [] diff --git a/src/Chainweb/Version/Testnet04.hs b/src/Chainweb/Version/Testnet04.hs index 5eb213005c..9cabb0a9eb 100644 --- a/src/Chainweb/Version/Testnet04.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -116,6 +116,7 @@ testnet04 = ChainwebVersion Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_058_738 -- 2025-02-05 12:00:00+00:00 Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_155_146 -- 2025-03-11 00:00:00+00:00 Chainweb229Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_300_466 -- 2025-04-30 12:00:00+00:00 + HashedAdjacentRecord -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_300_466 -- 2025-04-30 12:00:00+00:00 Chainweb230Pact -> AllChains ForkNever , _versionGraphs = diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index 565069574e..54121d443f 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -146,25 +146,16 @@ arbitraryBlockTimeOffset lower upper = do -- | Solve Work. Doesn't check that the nonce and the time are valid. -- -solveWork :: HasCallStack => WorkHeader -> Nonce -> Time Micros -> SolvedWork -solveWork w n t = - case runGetS decodeBlockHeaderWithoutHash $ BS.fromShort $ _workHeaderBytes w of - Nothing -> error "Chainwb.Test.Cut.solveWork: Invalid work header bytes" - Just hdr -> SolvedWork - $ fromJuste - $ runGetS decodeBlockHeaderWithoutHash - $ runPutS - $ encodeBlockHeaderWithoutHash - -- After injecting the nonce and the creation time will have to do a - -- serialization roundtrip to update the Merkle hash. - -- - -- A "real" miner would inject the nonce and time without first - -- decoding the header and would hand over the header in serialized - -- form. - - $ set blockCreationTime (BlockCreationTime t) - $ set blockNonce n - $ hdr +solveWork :: HasCallStack => MiningWork -> Nonce -> Time Micros -> SolvedWork +solveWork work n t = s + { _solvedWorkCreationTime = BlockCreationTime t + , _solvedWorkNonce = n + } + where + s = fromJuste + $ runGetS decodeSolvedWork + $ BS.fromShort + $ _miningWorkBytes work -- -------------------------------------------------------------------------- -- -- Test Mining @@ -244,11 +235,13 @@ createNewCut -> m (T2 BlockHeader Cut) createNewCut hdb n t pay i c = do extension <- fromMaybeM BadAdjacents $ getCutExtension c i - work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay - (h, mc') <- extendCut c (solveWork work n t) - `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg + wp <- WorkParents (_cutExtensionParent extension) + <$> getAdjacentParentHeaders hdb extension + work <- newMiningWorkPure hdb (BlockCreationTime t) extension pay + (solvedHeader, mc') <- extendCut c wp (solveWork work n t) + `catch` \(InvalidSolvedHeader msg) -> throwM $ InvalidHeader msg c' <- fromMaybeM BadAdjacents mc' - return $ T2 h c' + return $ T2 solvedHeader c' -- | Create a new cut where the new block has a creation time of one second -- after its parent. diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index c313a3e8d5..538a479176 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -428,17 +428,19 @@ instance Arbitrary CutHeight where -- -------------------------------------------------------------------------- -- -- Mining Work -instance Arbitrary WorkHeader where +instance Arbitrary MiningWork where arbitrary = do hdr <- arbitrary - return $ WorkHeader - { _workHeaderChainId = _chainId hdr - , _workHeaderTarget = view blockTarget hdr - , _workHeaderBytes = BS.toShort $ runPutS $ encodeBlockHeaderWithoutHash hdr + return $ MiningWork + { _miningWorkChainId = _chainId hdr + , _miningWorkTarget = view blockTarget hdr + , _miningWorkBytes = BS.toShort $ runPutS $ encodeAsMiningWork hdr } instance Arbitrary SolvedWork where - arbitrary = SolvedWork <$> arbitrary + arbitrary = fromJuste . runGetS decodeSolvedWork . BS.fromShort . work <$> arbitrary + where + work hdr = BS.toShort $ runPutS $ encodeAsMiningWork hdr -- -------------------------------------------------------------------------- -- -- Payload over arbitrary bytesstrings diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index b272ffb048..40664ad868 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -17,6 +17,7 @@ module Chainweb.Test.TestVersions , timedConsensusVersion , instantCpmTestVersion , checkpointerTestVersion + , testVersions ) where import Control.Lens hiding (elements) diff --git a/test/payload-provider/Test/Chainweb/SPV/Argument.hs b/test/payload-provider/Test/Chainweb/SPV/Argument.hs index 05f47b82f2..0a567206eb 100644 --- a/test/payload-provider/Test/Chainweb/SPV/Argument.hs +++ b/test/payload-provider/Test/Chainweb/SPV/Argument.hs @@ -408,7 +408,7 @@ testPayload = case eitherDecodeStrictText payloadStr of "parentBeaconBlockRoot": "0x80af8b91b32cae2e3c3b17ef0b6ce9124e01176bf953f192633a6cc718f129ba", "hash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f" } - |] + |] testHeader :: BlockHeader testHeader = case eitherDecodeStrictText headerStr of @@ -436,7 +436,7 @@ testHeader = case eitherDecodeStrictText headerStr of "featureFlags": 0, "hash": "9NDFjt_VVE2_fJD54eLcybRfbp_BAOR2T2HxavWe7ho" } - |] + |] -- EVM Development block (chain 0, height 354) diff --git a/test/unit/Chainweb/Test/Misc.hs b/test/unit/Chainweb/Test/Misc.hs index 67bd17b28c..6d652615e9 100644 --- a/test/unit/Chainweb/Test/Misc.hs +++ b/test/unit/Chainweb/Test/Misc.hs @@ -1,4 +1,6 @@ {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedStrings #-} -- | -- Module: Chainweb.Test.Misc @@ -22,21 +24,40 @@ import Control.Scheduler (Comp(..), scheduleWork, terminateWith, withScheduler) import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck +import Chainweb.BlockHash +import Data.HashMap.Strict qualified as HM +import Chainweb.ChainId + +import PropertyMatchers ((?)) +import PropertyMatchers qualified as P +-- import Chainweb.MerkleLogHash (unsafeMerkleLogHash) +import Data.Function ((&)) +-- import qualified Data.ByteString as BS +import Chainweb.Utils (HasTextRepresentation(..)) +import Chainweb.MerkleLogHash (unsafeMerkleLogHash) +import qualified Data.ByteString as BS +import Control.Monad --- tests :: TestTree tests = testGroup "Misc. Unit Tests" [ testGroup "scheduler" - [ testCase "early termination result order" terminateOrder - ] + [ testCase "early termination result order" terminateOrder + ] , testGroup "binary encoding" - [ testProperty "BlockPayload" propPayloadBinaryEncoding - , testProperty "BlockTransactions" propBlockTransactionsEncoding - , testProperty "BlockOutputs" propBlockOutputsEncoding - , testProperty "PayloadData" propPayloadDataEncoding - , testProperty "PayloadWithOutputs" propPayloadWithOutputsEncoding - ] + [ testProperty "BlockPayload" propPayloadBinaryEncoding + , testProperty "BlockTransactions" propBlockTransactionsEncoding + , testProperty "BlockOutputs" propBlockOutputsEncoding + , testProperty "PayloadData" propPayloadDataEncoding + , testProperty "PayloadWithOutputs" propPayloadWithOutputsEncoding + ] + , testGroup "hashed adjacent block hash record" + [ testCase "smoke test" hashedAdjacentBlockHashRecordSmokeTest + , testCase "null padding" hashedAdjacentBlockHashRecordNullPadding + , testCase "truncation" hashedAdjacentBlockHashRecordTruncation + , testCase "bit flipping" hashedAdjacentBlockHashRecordBitFlipping + ] ] -- | Guarantee that `terminateWith` makes the scheduler's "head" return value be @@ -51,25 +72,76 @@ terminateOrder = do propPayloadBinaryEncoding :: BlockPayload -> Bool propPayloadBinaryEncoding bp - | Right x <- decodeBlockPayloads (encodeBlockPayloads bp) = x == bp - | otherwise = False + | Right x <- decodeBlockPayloads (encodeBlockPayloads bp) = x == bp + | otherwise = False propBlockTransactionsEncoding :: BlockTransactions -> Bool propBlockTransactionsEncoding bt - | Right x <- decodeBlockTransactions (encodeBlockTransactions bt) = x == bt - | otherwise = False + | Right x <- decodeBlockTransactions (encodeBlockTransactions bt) = x == bt + | otherwise = False propBlockOutputsEncoding :: BlockOutputs -> Bool propBlockOutputsEncoding bo - | Right x <- decodeBlockOutputs (encodeBlockOutputs bo) = x == bo - | otherwise = False + | Right x <- decodeBlockOutputs (encodeBlockOutputs bo) = x == bo + | otherwise = False propPayloadDataEncoding :: PayloadData -> Bool propPayloadDataEncoding pd - | Right x <- decodePayloadData (encodePayloadData pd) = x == pd - | otherwise = False + | Right x <- decodePayloadData (encodePayloadData pd) = x == pd + | otherwise = False propPayloadWithOutputsEncoding :: PayloadWithOutputs -> Bool propPayloadWithOutputsEncoding pwo - | Right x <- decodePayloadWithOutputs (encodePayloadWithOutputs pwo) = x == pwo - | otherwise = False + | Right x <- decodePayloadWithOutputs (encodePayloadWithOutputs pwo) = x == pwo + | otherwise = False + +hashedAdjacentBlockHashRecordSmokeTest :: IO () +hashedAdjacentBlockHashRecordSmokeTest = do + -- smoke test + blockHashHash <- fromText "rxPASJkSJKXkxmREa2iKr0j7VFbbNilgGwDsFgx05VQ" + convertBlockHashRecordForMining + (BlockHashRecord (HM.fromList [(unsafeChainId 0, nullBlockHash)])) + & P.equals + ? BlockHashRecord + ? HM.fromList [(unsafeChainId 0, BlockHash blockHashHash)] + +hashedAdjacentBlockHashRecordNullPadding :: IO () +hashedAdjacentBlockHashRecordNullPadding = do + -- check that the first chain has a null block hash with a multi-chain record + blockHashHash <- fromText "iu7PoLnyrHgYhjsTYiQeTzLQaxAK6dHA-8xO1huRsXo" + convertBlockHashRecordForMining + (BlockHashRecord (HM.fromList [(unsafeChainId 0, nullBlockHash), (unsafeChainId 1, nullBlockHash)])) + & P.equals + ? BlockHashRecord + ? HM.fromList [(unsafeChainId 0, nullBlockHash), (unsafeChainId 1, BlockHash blockHashHash)] + +hashedAdjacentBlockHashRecordTruncation :: IO () +hashedAdjacentBlockHashRecordTruncation = do + -- check that more than 3 chains gets truncated + blockHashHash <- fromText "tnka1yzuG3ury0o80ccea3EiVWDwOL0pYITwsA2du7Q" + let nullBlockHashRecord = HM.fromList $ [(unsafeChainId cid, nullBlockHash) | cid <- [0..5]] + convertBlockHashRecordForMining (BlockHashRecord nullBlockHashRecord) + & P.equals + ? BlockHashRecord + ? HM.fromList ([(unsafeChainId cid, nullBlockHash) | cid <- [0..1]] ++ [(unsafeChainId 2, blockHashHash)]) + +hashedAdjacentBlockHashRecordBitFlipping :: IO () +hashedAdjacentBlockHashRecordBitFlipping = do + -- these two must be the same as previous test + blockHashHash <- fromText "tnka1yzuG3ury0o80ccea3EiVWDwOL0pYITwsA2du7Q" + let nullBlockHashRecord = HM.fromList $ [(unsafeChainId cid, nullBlockHash) | cid <- [0..5]] + -- check that flipping a bit relative to the previous test in *any* hash in + -- the record changes the output hash + let flippedBitNullBlockHash = + BlockHash $ unsafeMerkleLogHash $ BS.replicate 31 0x00 <> BS.singleton 0x01 + forM_ [0..5] $ \flippedCid -> do + let flippedRecord = + BlockHashRecord (HM.insert (unsafeChainId flippedCid) flippedBitNullBlockHash nullBlockHashRecord) + convertBlockHashRecordForMining flippedRecord + -- TODO: replace when property-matchers has notEquals check + & (\expected actual -> + if expected /= actual + then P.succeed actual + else P.fail "equal, should not be" actual) + ? BlockHashRecord + ? HM.fromList ([(unsafeChainId cid, nullBlockHash) | cid <- [0..1]] ++ [(unsafeChainId 2, blockHashHash)]) diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index b71cb55477..396261782f 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -49,7 +49,7 @@ import Chainweb.BlockHeader.Internal (blockCreationTime, blockNonce) import Chainweb.ChainId import Chainweb.ChainValue (ChainValue(..), ChainValueCasLookup, chainLookupM) import Chainweb.Cut -import Chainweb.Cut.Create (InvalidSolvedHeader(..), WorkHeader(..), SolvedWork(..), extendCut, getCutExtension, newWorkHeaderPure) +import Chainweb.Cut.Create (InvalidSolvedHeader(..), SolvedWork, extendCut, getCutExtension, newHeaderForPayloadPure, makeSolvedWork) import Chainweb.Cut.CutHashes import Chainweb.CutDB import Chainweb.Logger @@ -79,7 +79,6 @@ import Control.Monad.Catch hiding (finally) import Control.Monad.IO.Class import Control.Monad.Trans.Resource (ResourceT, allocate) import Data.ByteString.Lazy qualified as LBS -import Data.ByteString.Short qualified as SBS import Data.Function import Data.HashMap.Strict qualified as HashMap import Data.HashSet qualified as HashSet @@ -258,33 +257,31 @@ createNewCut -> m (T2 BlockHeader Cut) createNewCut hdb n t pay i c = do extension <- fromMaybeM BadAdjacents $ getCutExtension c i - work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay - (h, mc') <- extendCut c pay (solveWork work n t) - `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg + bh <- newHeaderForPayloadPure hdb (BlockCreationTime t) extension pay + (solvedHeader, mc') <- extendCut c pay (solveWork bh n t) bh + `catch` \(InvalidSolvedHeader msg) -> throwM $ InvalidHeader msg c' <- fromMaybeM BadAdjacents mc' - return $ T2 h c' + return $ T2 solvedHeader c' -- | Solve Work. Doesn't check that the nonce and the time are valid. -- -solveWork :: HasCallStack => WorkHeader -> Nonce -> Time Micros -> SolvedWork -solveWork w n t = - case runGetS decodeBlockHeaderWithoutHash $ SBS.fromShort $ _workHeaderBytes w of - Nothing -> error "Chainwb.Test.Cut.solveWork: Invalid work header bytes" - Just hdr -> SolvedWork - $ fromJuste - $ runGetS decodeBlockHeaderWithoutHash - $ runPutS - $ encodeBlockHeaderWithoutHash - -- After injecting the nonce and the creation time will have to do a - -- serialization roundtrip to update the Merkle hash. - -- - -- A "real" miner would inject the nonce and time without first - -- decoding the header and would hand over the header in serialized - -- form. +solveWork :: HasCallStack => BlockHeader -> Nonce -> Time Micros -> SolvedWork +solveWork bh n t = + makeSolvedWork + $ fromJuste + $ runGetS decodeBlockHeaderWithoutHash + $ runPutS + $ encodeAsWorkHeader + -- After injecting the nonce and the creation time will have to do a + -- serialization roundtrip to update the Merkle hash. + -- + -- A "real" miner would inject the nonce and time without first + -- decoding the header and would hand over the header in serialized + -- form. - $ set blockCreationTime (BlockCreationTime t) - $ set blockNonce n - $ hdr + $ set blockCreationTime (BlockCreationTime t) + $ set blockNonce n + $ bh -- | Build a linear chainweb (no forks). No POW or poison delay is applied. -- Block times are real times. diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index 3a08a751dc..07a3d4e795 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -67,7 +67,7 @@ import Chainweb.RestAPI.NodeInfo import Chainweb.SPV import Chainweb.SPV.EventProof import Chainweb.SPV.PayloadProof -import Chainweb.Test.Orphans.Internal (EventPactValue(..), ProofPactEvent(..)) +import Chainweb.Test.Orphans.Internal (EventPactValue(..), ProofPactEvent(..), arbitraryBlockHeaderVersion) import Chainweb.Test.SPV.EventProof hiding (tests) import Chainweb.Test.Utils import Chainweb.Time @@ -83,6 +83,7 @@ import P2P.Peer import P2P.Test.Orphans () import Utils.Logging +import Control.Lens (view) -- -------------------------------------------------------------------------- -- -- Roundrip Tests @@ -204,10 +205,9 @@ encodeDecodeTests = testGroup "Encode-Decode roundtrips" , testProperty "SolvedWork" $ prop_encodeDecode decodeSolvedWork encodeSolvedWork - -- FIXME: decoding depends on version and block height (which is something - -- that we should fix) - -- , testProperty "WorkHeader" - -- $ prop_encodeDecode decodeWorkHeader encodeWorkHeader + , testProperty "WorkHeader" + $ forAll arbitrary $ \v -> forAll (arbitraryBlockHeaderVersion v) $ \bh -> + prop_encodeDecode (decodeWorkHeader v (view blockHeight bh)) encodeWorkHeader (workOnHeader bh) -- TODO Fix this! -- The following doesn't hold: From 0ece8fcae5a45305da23d6df074c0e9c48655d2b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 3 May 2025 21:19:29 -0700 Subject: [PATCH 131/378] Prerelease version of optparse-applicate with support for option groups --- cabal.project | 5 +++++ node/src/ChainwebNode.hs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cabal.project b/cabal.project index effae884d6..fd792a619a 100644 --- a/cabal.project +++ b/cabal.project @@ -156,6 +156,11 @@ source-repository-package tag: 90247042ab3b8662809210af2a78e6dee0f9b4ac --sha256: 0dqsrjxm0cm35xcihm49dhwdvmz79vsv4sd5qs2izc4sbnd0d8n6 +source-repository-package + type: git + location: https://github.com/pcapriotti/optparse-applicative/ + tag: 7861d08b5d0ae542c82dfc109ca85e61745dc4eb + -- -------------------------------------------------------------------------- -- -- Relaxed Bounds diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 548b751eb6..2c06059bed 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -160,7 +160,7 @@ instance FromJSON (ChainwebNodeConfiguration -> ChainwebNodeConfiguration) where pChainwebNodeConfiguration :: MParser ChainwebNodeConfiguration pChainwebNodeConfiguration = id <$< nodeConfigChainweb %:: pChainwebConfiguration - <*< nodeConfigLog %:: pLogConfig + <*< parserOptionGroup "Logging" (nodeConfigLog %:: pLogConfig) <*< nodeConfigDatabaseDirectory .:: fmap Just % textOption % long "database-directory" <> help "directory where the databases are persisted" From 799e18eee4743c8e8163f9b918fd6f7bd5843f7d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 3 May 2025 21:23:01 -0700 Subject: [PATCH 132/378] Utils for chain specific configurations --- src/Chainweb/ChainId.hs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index 0d979a93b6..f9f3399476 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -66,6 +66,12 @@ module Chainweb.ChainId , onChains , onChain , chainZip + +-- * Configuration Utils +, prefixLongCid +, suffixHelpCid +, helpCid +, pEnableConfigCid ) where import Control.DeepSeq @@ -84,7 +90,7 @@ import qualified Data.Text as T import Data.Word (Word32) import GHC.Generics (Generic) -import GHC.TypeLits +import GHC.TypeLits hiding (Mod) -- internal imports @@ -94,6 +100,7 @@ import Chainweb.Utils import Chainweb.Utils.Serialization import Data.Singletons hiding (Index) +import Configuration.Utils -- -------------------------------------------------------------------------- -- -- Exceptions @@ -327,3 +334,33 @@ type instance IxValue (ChainMap a) = a instance IxedGet (ChainMap a) where ixg i = atChain i + +-- -------------------------------------------------------------------------- -- +-- Configuration Utils + +prefixLongCid :: HasName f => ChainId -> String -> Mod f a +prefixLongCid cid = prefixLong (Just p) + where + p = "chain-" <> T.unpack (toText cid) + +suffixHelpCid :: ChainId -> String -> Mod f a +suffixHelpCid cid = suffixHelp (Just s) + where + s = "chain-" <> T.unpack (toText cid) + +helpCid :: ChainId -> String -> (Mod f a) +helpCid cid t = suffixHelp (Just "the respective chain") t + <> mconcat [ hidden <> internal | chainIdInt @Int cid /= 0 ] + +pEnableConfigCid + :: String + -> ChainId + -> (ChainId -> MParser a) + -> MParser (EnableConfig a) +pEnableConfigCid comp cid pConfig = id + <$< enableConfigEnabled .:: enableDisableFlag + % prefixLongCid cid comp + <> help ("whether " <> comp <> " is enabled or disabled for the respective chain") + <> mconcat [ hidden <> internal | chainIdInt @Int cid /= 0 ] + <*< enableConfigConfig %:: pConfig cid + From e284f417e9069f3d8c1156167d3967e99980d29d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 3 May 2025 21:41:12 -0700 Subject: [PATCH 133/378] Leaner payload provider configuration --- chainweb.cabal | 1 + node/src/ChainwebNode.hs | 5 +- src/Chainweb/Chainweb.hs | 80 ++-- src/Chainweb/Chainweb/ChainResources.hs | 107 +++-- src/Chainweb/Chainweb/Configuration.hs | 374 +++++++----------- src/Chainweb/Mempool/P2pConfig.hs | 19 +- src/Chainweb/Miner/Config.hs | 40 +- src/Chainweb/Pact/Types.hs | 13 + src/Chainweb/PayloadProvider/EVM.hs | 49 ++- src/Chainweb/PayloadProvider/Minimal.hs | 4 +- .../PayloadProvider/Pact/Configuration.hs | 172 ++++++++ src/Chainweb/RestAPI/Config.hs | 9 +- test/lib/Chainweb/Test/MultiNode.hs | 7 +- test/lib/Chainweb/Test/Orphans/Internal.hs | 2 +- test/lib/Chainweb/Test/Utils.hs | 10 +- 15 files changed, 496 insertions(+), 396 deletions(-) create mode 100644 src/Chainweb/PayloadProvider/Pact/Configuration.hs diff --git a/chainweb.cabal b/chainweb.cabal index 8c7c496c19..3e44fb7cf7 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -240,6 +240,7 @@ library , Chainweb.PayloadProvider.Minimal.Payload , Chainweb.PayloadProvider.Minimal.PayloadDB , Chainweb.PayloadProvider.Pact + , Chainweb.PayloadProvider.Pact.Configuration , Chainweb.PayloadProvider.Pact.Genesis , Chainweb.PayloadProvider.P2P , Chainweb.PayloadProvider.P2P.RestAPI diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 2c06059bed..36febaa0f2 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -88,7 +88,6 @@ import Chainweb.Logger import Chainweb.Logging.Config import Chainweb.Logging.Miner import Chainweb.Mempool.InMemTypes (MempoolStats(..)) --- import Chainweb.Miner.Coordinator (MiningStats) import Chainweb.Pact.Backend.DbCache (DbCacheStats) import Chainweb.Pact.RestAPI.Server (PactCmdLog(..)) import Chainweb.Pact.Types @@ -315,7 +314,7 @@ node conf logger = do pactDbDir <- getPactDbDir conf dbBackupsDir <- getBackupsDir conf withRocksDb' <- - if _configOnlySyncPact cwConf || _configReadOnlyReplay cwConf + if _configOnlySync cwConf || _configReadOnlyReplay cwConf then withReadOnlyRocksDb <$ logFunctionText logger Info "Opening RocksDB in read-only mode" else @@ -362,7 +361,7 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do -- we don't log tx failures in replay let !txFailureHandler = - if _configOnlySyncPact chainwebCfg || _configReadOnlyReplay chainwebCfg + if _configOnlySync chainwebCfg || _configReadOnlyReplay chainwebCfg then [dropLogHandler (Proxy :: Proxy PactTxFailureLog)] else [] diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 1b7c5ddf55..baf7014d1e 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -291,20 +291,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir concurrentWith -- initialize chains concurrently (\cid x -> do - -- let mcfg = validatingMempoolConfig cid v (_configBlockGasLimit conf) (_configMinGasPrice conf) - - -- FIXME: shouldn't this be done in a configuration validation? - -- NOTE: the gas limit may be set based on block height in future, so this approach may not be valid. - let maxGasLimit = Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit v maxBound - case maxGasLimit of - Just maxGasLimit' - | _configBlockGasLimit conf > maxGasLimit' -> - logg Warn $ T.unwords - [ "configured block gas limit is greater than the" - , "maximum for this chain; the maximum will be used instead" - ] - _ -> return () - -- Initialize all chain resources, including payload providers withChainResources (chainLogger cid) @@ -313,10 +299,11 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir rocksDb (_peerResManager peerRes) pactDbDir - (pactConfig maxGasLimit) (_peerResConfig peerRes) myInfo peerDb + (_configReorgLimit conf) + initialUnlimitedRewind (_configPayloadProviders conf) (\cr -> x cr) ) @@ -351,23 +338,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir p2pConfig :: P2pConfiguration p2pConfig = _peerResConfig peerRes - pactConfig maxGasLimit = PactServiceConfig - { _pactReorgLimit = _configReorgLimit conf - , _pactPreInsertCheckTimeout = _configPreInsertCheckTimeout conf - , _pactAllowReadsInLocal = _configAllowReadsInLocal conf - , _pactUnlimitedInitialRewind = - isJust (_cutDbParamsInitialHeightLimit cutDbParams) || - isJust (_cutDbParamsInitialCutFile cutDbParams) - , _pactNewBlockGasLimit = maybe id min maxGasLimit (_configBlockGasLimit conf) - , _pactLogGas = _configLogGas conf - , _pactEnableLocalTimeout = _configEnableLocalTimeout conf - , _pactFullHistoryRequired = _configFullHistoricPactState conf - , _pactTxTimeLimit = Nothing - -- FIXME - , _pactMiner = Nothing - , _pactBlockRefreshInterval = Micros 5_000_000 - } - -- FIXME: make this configurable cutDbParams :: CutDbParams cutDbParams = (defaultCutDbParams v $ _cutFetchTimeout cutConf) @@ -375,11 +345,15 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir , _cutDbParamsTelemetryLevel = Info , _cutDbParamsInitialHeightLimit = _cutInitialBlockHeightLimit cutConf , _cutDbParamsFastForwardHeightLimit = _cutFastForwardBlockHeightLimit cutConf - , _cutDbParamsReadOnly = _configOnlySyncPact conf || _configReadOnlyReplay conf + , _cutDbParamsReadOnly = _configOnlySync conf || _configReadOnlyReplay conf } where cutConf = _configCuts conf + initialUnlimitedRewind = + isJust (_cutDbParamsInitialHeightLimit cutDbParams) || + isJust (_cutDbParamsInitialCutFile cutDbParams) + -- Logger backupLogger :: logger @@ -480,7 +454,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir -- logFunctionJson logger Info PactReplaySuccessful -- inner $ Replayed lowerBoundCut upperBoundCut else - if _configOnlySyncPact conf + if _configOnlySync conf then do error "Chainweb.Chainweb.withChainwebInternal: pact replay is not supported" -- initialCut <- _cut mCutDb @@ -872,22 +846,30 @@ runChainweb cw nowServing = do -- Mempool mempoolP2pConfig :: EnableConfig MempoolP2pConfig - mempoolP2pConfig = _configMempoolP2p $ _chainwebConfig cw + -- mempoolP2pConfig = _configMempoolP2p $ _chainwebConfig cw + -- FIXME + mempoolP2pConfig = error "Chainweb.Chainweb.runChainweb: FIXME Pact mempoolP2pConfig moved into Pact and is not yet implemented" -- | Decide whether to enable the mempool sync clients. -- + -- FIXME: Pact mempools are now part of the pact payload provider and are + -- configured there. + -- mempoolSyncClients :: IO [IO ()] - mempoolSyncClients = case enabledConfig mempoolP2pConfig of - Nothing -> disabled - Just c - | cw ^. chainwebVersion . versionDefaults . disableMempoolSync -> disabled - | otherwise -> enabled c - where - disabled = do - logg Info "Mempool p2p sync disabled" - return [] - enabled conf = do - logg Info "Mempool p2p sync enabled" - -- return $ map (runMempoolSyncClient mgr conf (_chainwebPeer cw)) chainVals - logg Warn "Overwriting mempool p2p sync client configuration. It is currently not supported" - return [] + mempoolSyncClients = return [] + -- mempoolSyncClients = case enabledConfig mempoolP2pConfig of + -- Nothing -> disabled + -- Just c + -- | cw ^. chainwebVersion . versionDefaults . disableMempoolSync -> disabled + -- | otherwise -> enabled c + -- where + -- disabled = do + -- logg Info "Mempool p2p sync disabled" + -- return [] + -- enabled conf = do + -- logg Info "Mempool p2p sync enabled" + -- -- FIXME FIXME FIXME + -- -- return $ map (runMempoolSyncClient mgr conf (_chainwebPeer cw)) chainVals + -- logg Warn "Overwriting mempool p2p sync client configuration. It is currently not supported" + -- logg Warn "Chainweb.Chainweb.runChainweb.mempoolSyncClient: Pact mempool synchronization is currently disabled" + -- return [] diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 251a56c4e0..d39520eb7f 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -14,6 +14,7 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE NumericUnderscores #-} -- | -- Module: Chainweb.Chainweb.ChainResources @@ -66,6 +67,7 @@ import Chainweb.PayloadProvider.EVM import Chainweb.PayloadProvider.Minimal import Chainweb.PayloadProvider.P2P.RestAPI import Chainweb.PayloadProvider.P2P.RestAPI.Server +import Chainweb.PayloadProvider.Pact.Configuration import Chainweb.RestAPI.NetworkID import Chainweb.RestAPI.Utils import Chainweb.Storage.Table @@ -87,7 +89,10 @@ import P2P.Peer (PeerInfo) import P2P.Session import P2P.TaskQueue import Prelude hiding (log) -import qualified Data.HashSet as HS +import Chainweb.Version.Guards (maxBlockGasLimit) +import Pact.Core.Gas qualified as Pact +import System.LogLevel +import Chainweb.Time -- -------------------------------------------------------------------------- -- -- Payload P2P Network Resources @@ -213,15 +218,17 @@ withPayloadProviderResources -> PeerDb -> RocksDb -> HTTP.Manager + -> RewindLimit + -- ^ the reorg limit for the payload providers + -> Bool + -- ^ whether to allow unlimited rewind on startup -> PayloadProviderConfig -> (ProviderResources -> IO a) -> IO a -withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs inner = do +withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs inner = do SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal v SomeChainIdT @c' _ <- return $ someChainIdVal c - if HS.member (_chainId c) (_payloadProviderConfigDisabled configs) - then inner $ ProviderResources DisabledPayloadProvider Nothing Nothing - else withSomeSing provider $ \case + withSomeSing provider $ \case SMinimalProvider -> do -- FIXME this should be better abstracted. @@ -244,33 +251,62 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr configs , _providerResP2pApiResources = Just p2pRes } - SPactProvider -> do - _config <- case HM.lookup cid (_payloadProviderConfigPact configs) of - Just x -> return x - Nothing -> error $ "Chainweb.Chainweb.ChainResources.withPayloadProviderResources: missing payload provider configuration for chain " <> sshow cid - -- , _pactGenesisPayload = Pact.genesisPayload v ^?! atChain chain - error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for Pact" - - SEvmProvider @n _ -> do - config <- case HM.lookup cid (_payloadProviderConfigEvm configs) of - Just x -> return x - Nothing -> error $ "Chainweb.Chainweb.ChainResources.withPayloadProviderResources: missing payload provider configuration for chain " <> sshow cid - - -- This assumes that the respective execution client is available - -- and answering API requests. - -- It also starts to awaiting and devlivering new payloads if mining - -- is enabled. - withEvmPayloadProvider logger v c rdb (Just mgr) config $ \p -> do - let pdb = view evmPayloadDb p - let queue = view evmPayloadQueue p - p2pRes <- payloadP2pResources @v' @c' @('EvmProvider n) - logger p2pConfig myInfo peerDb pdb queue mgr - inner ProviderResources - { _providerResPayloadProvider = ConfiguredPayloadProvider p - , _providerResServiceApi = Nothing - , _providerResP2pApiResources = Just p2pRes - } + SPactProvider -> case HM.lookup cid (_payloadProviderConfigPact configs) of + Just conf -> do + -- , _pactGenesisPayload = Pact.genesisPayload v ^?! atChain chain + + -- let mcfg = validatingMempoolConfig cid v (_configBlockGasLimit conf) (_configMinGasPrice conf) + + -- FIXME move the following to the pact provider initialization + + let maxGasLimit = Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit ver maxBound + case maxGasLimit of + Just maxGasLimit' + | _pactConfigBlockGasLimit conf > maxGasLimit' -> + logFunction logger Warn $ T.unwords + [ "configured block gas limit is greater than the" + , "maximum for this chain; the maximum will be used instead" + ] + _ -> return () + + let pactConfig = PactServiceConfig + { _pactReorgLimit = rewindLimit + , _pactPreInsertCheckTimeout = _pactConfigPreInsertCheckTimeout conf + , _pactAllowReadsInLocal = _pactConfigAllowReadsInLocal conf + , _pactUnlimitedInitialRewind = initialUnlimitedRewind + , _pactNewBlockGasLimit = maybe id min maxGasLimit (_pactConfigBlockGasLimit conf) + , _pactLogGas = _pactConfigLogGas conf + , _pactEnableLocalTimeout = _pactConfigEnableLocalTimeout conf + , _pactFullHistoryRequired = _pactConfigFullHistoricPactState conf + , _pactTxTimeLimit = Nothing + , _pactMiner = _pactConfigMiner conf + , _pactBlockRefreshInterval = Micros 5_000_000 + } + + error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for Pact" + + _ -> inner $ ProviderResources DisabledPayloadProvider Nothing Nothing + + SEvmProvider @n _ -> case HM.lookup cid (_payloadProviderConfigEvm configs) of + Just config -> do + -- This assumes that the respective execution client is available + -- and answering API requests. + -- It also starts to awaiting and devlivering new payloads if mining + -- is enabled. + withEvmPayloadProvider logger v c rdb (Just mgr) config $ \p -> do + let pdb = view evmPayloadDb p + let queue = view evmPayloadQueue p + p2pRes <- payloadP2pResources @v' @c' @('EvmProvider n) + logger p2pConfig myInfo peerDb pdb queue mgr + inner ProviderResources + { _providerResPayloadProvider = ConfiguredPayloadProvider p + , _providerResServiceApi = Nothing + , _providerResP2pApiResources = Just p2pRes + } + _ -> inner $ ProviderResources DisabledPayloadProvider Nothing Nothing + where + ver = _chainwebVersion v cid = _chainId c provider :: PayloadProviderType provider = payloadProviderTypeForChain v c @@ -317,14 +353,17 @@ withChainResources -> HTTP.Manager -> FilePath -- ^ database directory for pact databases - -> PactServiceConfig -> P2pConfiguration -> PeerInfo -> PeerDb + -> RewindLimit + -- ^ the reorg limit for the payload providers + -> Bool + -- ^ whether to allow unlimited rewind on startup -> PayloadProviderConfig -> (ChainResources logger -> IO a) -> IO a -withChainResources logger v c rdb mgr _pactDbDir _pConf p2pConf myInfo peerDb configs inner = +withChainResources logger v c rdb mgr _pactDbDir p2pConf myInfo peerDb rewindLimit initialUnlimitedRewind configs inner = -- This uses the the CutNetwork for fetching block headers. withBlockHeaderDb rdb (_chainwebVersion v) (_chainId c) $ \cdb -> do @@ -332,7 +371,7 @@ withChainResources logger v c rdb mgr _pactDbDir _pConf p2pConf myInfo peerDb co -- Payload Providers are using per chain payload networks for fetching -- block headers. withPayloadProviderResources - providerLogger v c p2pConf myInfo peerDb rdb mgr configs $ \provider -> do + providerLogger v c p2pConf myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs $ \provider -> do inner ChainResources { _chainResBlockHeaderDb = cdb diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 51cf5fc063..0d50746612 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -21,19 +21,12 @@ -- module Chainweb.Chainweb.Configuration ( - --- * Placeholder for Pact Provider Config - PactProviderConfig(..) -, defaultPactProviderConfig - -- * Payload Provider Config -, PayloadProviderConfig(..) + PayloadProviderConfig(..) , payloadProviderConfigMinimal , payloadProviderConfigPact , payloadProviderConfigEvm -, payloadProviderConfigDisabled , defaultPayloadProviderConfig -, minimalPayloadProviderConfig , validatePayloadProviderConfig -- * Throttling Configuration @@ -70,18 +63,15 @@ module Chainweb.Chainweb.Configuration , configCuts , configMining , configHeaderStream -, configReintroTxs , configP2p -, configBlockGasLimit -, configMinGasPrice , configThrottling , configReorgLimit -, configFullHistoricPactState , configBackup , configServiceApi -, configOnlySyncPact -, configSyncPactChains -, configEnableLocalTimeout +, configOnlySync +, configReadOnlyReplay +, configSyncChains +, configPayloadProviders , defaultChainwebConfiguration , pChainwebConfiguration , validateChainwebConfiguration @@ -91,15 +81,13 @@ module Chainweb.Chainweb.Configuration import Chainweb.BlockHeight import Chainweb.Difficulty import Chainweb.HostAddress -import Chainweb.Mempool.Mempool qualified as Mempool -import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Config -import Chainweb.Pact.Types (RewindLimit(..), PactServiceConfig, defaultPactServiceConfig) -import Chainweb.Pact.Types (defaultReorgLimit, defaultPreInsertCheckTimeout) +import Chainweb.Pact.Types (RewindLimit) +import Chainweb.Pact.Types (defaultReorgLimit) import Chainweb.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) -import Chainweb.PayloadProvider.EVM (EvmProviderConfig, defaultEvmProviderConfig, pEvmProviderConfig) +import Chainweb.PayloadProvider.EVM (EvmProviderConfig, pEvmProviderConfig, defaultEvmProviderConfig, validateEvmProviderConfig) import Chainweb.PayloadProvider.Minimal (MinimalProviderConfig, defaultMinimalProviderConfig, pMinimalProviderConfig) -import Chainweb.Time hiding (second) +import Chainweb.PayloadProvider.Pact.Configuration import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Development @@ -114,7 +102,6 @@ import Control.Monad.Except import Control.Monad.Writer import Data.Aeson.Key qualified as K import Data.Aeson.KeyMap qualified as KM -import Data.Bifunctor import Data.Foldable import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS @@ -124,108 +111,66 @@ import Data.Text qualified as T import Data.Text.Read qualified as T import GHC.Generics hiding (from, to) import Network.Wai.Handler.Warp hiding (Port) -import Numeric.Natural (Natural) import P2P.Node.Configuration -import Pact.JSON.Encode qualified as J import Prelude hiding (log) import System.Directory -import qualified Pact.Core.Gas as Pact -import Pact.Core.StableEncoding -- -------------------------------------------------------------------------- -- -- Payload Provider Configuration --- | Placeholder for PactProviderConfig --- -data PactProviderConfig = PactProviderConfig - deriving (Show, Eq, Generic) - -instance ToJSON PactProviderConfig where - toJSON PactProviderConfig = object [] - -instance FromJSON PactProviderConfig where - parseJSON = withObject "PactProviderConfig" $ \_ -> pure PactProviderConfig - -instance FromJSON (PactProviderConfig -> PactProviderConfig) where - parseJSON = withObject "PactProviderConfig" $ \_ -> pure id - -defaultPactProviderConfig :: PactProviderConfig -defaultPactProviderConfig = PactProviderConfig - -- | Payload Provider Configurations -- -- There is only a single Default Minimal Payload Provider Configuration. +-- Minimal payload provider cannot be disabled. +-- +-- The default configuration for the pact and evm payload providers is to be +-- disabled. +-- +-- When a payload provider is enabled for a chain it must match the payload +-- provider type for the respective chain. -- data PayloadProviderConfig = PayloadProviderConfig { _payloadProviderConfigMinimal :: !MinimalProviderConfig , _payloadProviderConfigPact :: !(HM.HashMap ChainId PactProviderConfig) , _payloadProviderConfigEvm :: !(HM.HashMap ChainId EvmProviderConfig) - , _payloadProviderConfigDisabled :: !(HS.HashSet ChainId) } deriving (Show, Eq, Generic) makeLenses ''PayloadProviderConfig --- | This has to depend on the Chainweb Version +-- | By default only the minimal payload provider is enabled. For the pact and +-- evm chains the payload providers are disabled by default. -- -defaultPayloadProviderConfig :: ChainwebVersion -> PayloadProviderConfig -defaultPayloadProviderConfig v = PayloadProviderConfig +defaultPayloadProviderConfig :: PayloadProviderConfig +defaultPayloadProviderConfig = PayloadProviderConfig { _payloadProviderConfigMinimal = defaultMinimalProviderConfig - , _payloadProviderConfigPact = pacts - , _payloadProviderConfigEvm = evms - , _payloadProviderConfigDisabled = mempty - } - where - (pacts, evms) = go (toList $ chainIds v) - go :: [ChainId] -> (HM.HashMap ChainId PactProviderConfig, HM.HashMap ChainId EvmProviderConfig) - go [] = (mempty, mempty) - go (h:t) = case payloadProviderTypeForChain v h of - MinimalProvider -> go t - PactProvider -> first (HM.insert h defaultPactProviderConfig) $ go t - EvmProvider _ -> second (HM.insert h defaultEvmProviderConfig) $ go t - - -minimalPayloadProviderConfig :: MinimalProviderConfig -> PayloadProviderConfig -minimalPayloadProviderConfig m = PayloadProviderConfig - { _payloadProviderConfigMinimal = m , _payloadProviderConfigPact = mempty , _payloadProviderConfigEvm = mempty - , _payloadProviderConfigDisabled = mempty } validatePayloadProviderConfig :: ChainwebVersion -> ConfigValidation PayloadProviderConfig [] validatePayloadProviderConfig v conf = do - go (toList $ chainIds v) + void $ HM.traverseWithKey checkPactProvider $ _payloadProviderConfigPact conf + void $ HM.traverseWithKey checkEvmProvider $ _payloadProviderConfigEvm conf where - go [] = return () - go (c:t) = go t <* case payloadProviderTypeForChain v c of - PactProvider -> unless (HM.member c (_payloadProviderConfigPact conf)) $ - if HM.member c (_payloadProviderConfigEvm conf) || HS.member c (_payloadProviderConfigDisabled conf) - then throwError $ mconcat $ - [ "Wrong payload provider type configuration for chain " <> sshow c - , ". Expected Pact but found EVM" - ] <> msg - else throwError $ mconcat - $ "Missing Pact payload provider configuration for chain " <> sshow c - : msg - EvmProvider _ -> unless (HM.member c (_payloadProviderConfigEvm conf)) $ - if HM.member c (_payloadProviderConfigPact conf) || HS.member c (_payloadProviderConfigDisabled conf) - then throwError $ mconcat $ - [ "Wrong payload provider type configuration for chain " <> sshow c - , ". Expected EVM but found Pact" - ] <> msg - else throwError $ mconcat - $ "Missing EVM payload provider configuration for chain " <> sshow c - : msg - MinimalProvider -> return () - msg = - [ ". In order to use chainweb-node with chainweb version " <> sshow v - , " you must provide a configuraton for all enabled payload providers except" - , " the chains that use the default payload provider." - , " The following is the default payload provider configuration for" - , " chainweb version " <> sshow v - , ": " <> encodeToText (defaultPayloadProviderConfig v) - ] + checkPactProvider cid _conf = case payloadProviderTypeForChain v cid of + PactProvider -> return () -- FIXME implement validation + e -> do + tell [ "Pact provider configured for chain " <> sshow cid <> ": " <> sshow conf ] + throwError $ mconcat $ + [ "Wrong payload provider type configuration for chain " <> sshow cid + , ". Expected " <> sshow e <> " but found Pact" + ] + + checkEvmProvider cid _conf = case payloadProviderTypeForChain v cid of + EvmProvider _ -> validateEvmProviderConfig v cid _conf + e -> do + tell [ "EVM provider configured for chain " <> sshow cid <> ": " <> sshow conf ] + throwError $ mconcat $ + [ "Wrong payload provider type configuration for chain " <> sshow cid + , ". Expected " <> sshow e <> " but found EVM" + ] + instance ToJSON PayloadProviderConfig where toJSON o = object @@ -250,34 +195,13 @@ instance ToJSON PayloadProviderConfig where key :: ChainId -> Key key cid = K.fromText $ "chain-" <> toText cid --- | NOTE: This creates unsafe ChainIds. The result should only be used after --- validation against the chainweb version. +-- | Configuration parser for the payload provider configuration. -- -instance FromJSON PayloadProviderConfig where - parseJSON = withObject "PayloadProviderConfig" $ \o -> do - minimal <- o .: "default" - ifoldlM go (minimalPayloadProviderConfig minimal) (KM.toMapText o) - where - parseKey k = case T.stripPrefix "chain-" k of - Nothing -> fail $ "failed to parse chain key: " <> sshow k - Just x -> case T.decimal x of - Left e -> fail $ "failed to parse chain value: " <> e - Right (n, "") -> return $ unsafeChainId n - Right _ -> fail $ "trailng garabage when parsing chain value: " <> sshow x - - go "default" c = const (return c) - go k c = withObject "ProviderConfig for Chain" $ \o -> do - cid <- parseKey k - (o .: "type") >>= \case - "pact" -> do - x <- parseJSON (Object o) - return $ c & payloadProviderConfigPact . at cid .~ Just x - "evm" -> do - x <- parseJSON (Object o) - return $ c & payloadProviderConfigEvm . at cid .~ Just x - (x :: T.Text) -> fail $ "unknown payload provider type: " <> sshow x - --- | FIXME: test this instance. +-- * if the value for a chain is Null, then the provider is disabled +-- * if the value is an object, then +-- * the provider is enabled +-- * the value updates a given value or the default value if no +-- previous value is present -- instance FromJSON (PayloadProviderConfig -> PayloadProviderConfig) where parseJSON = withObject "PayloadProviderConfig" $ \o -> do @@ -291,32 +215,87 @@ instance FromJSON (PayloadProviderConfig -> PayloadProviderConfig) where Right (n, "") -> return $ unsafeChainId n Right _ -> fail $ "trailng garabage when parsing chain value: " <> sshow x - go "default" c = const (return c) - go k c = withObject "ProviderConfig for Chain" $ \o -> do + go "default" c _ = return c + go k c v = do cid <- parseKey k + go2 cid c v + + -- disable provider: + go2 cid c Null = return $ (payloadProviderConfigPact . at cid .~ Nothing) . c + + -- enabled provider: + go2 cid c v = flip (withObject ("ProviderConfig for chain " <> sshow cid)) v $ \o -> do (o .: "type") >>= \case "pact" -> do x <- parseJSON (Object o) - return $ (payloadProviderConfigPact . at cid %~ x) . c + let f Nothing = Just (x defaultPactProviderConfig) + f (Just y) = Just (x y) + return $ (payloadProviderConfigPact . at cid %~ f) . c "evm" -> do x <- parseJSON (Object o) - return $ (payloadProviderConfigEvm . at cid %~ x) . c + let f Nothing = Just (x defaultEvmProviderConfig) + f (Just y) = Just (x y) + return $ (payloadProviderConfigEvm . at cid %~ f) . c (x :: T.Text) -> fail $ "unknown payload provider type: " <> sshow x +-- | Command line option parser for the payload provider configuration. +-- +-- * if --disable-chain-X is set: disable provider +-- * otherwise parse update function and return a with function +-- f Nothing = Just (x defaultValue), if --enable-chain-X is set +-- f Nothing = Nothing, if --enable-chain-X is not set +-- f (Just y) = Just (x y), i.e. if the payload provider is already enabled +-- +-- Note, that --disable-chain-X takes precendence over --enable-chain-X. +-- pPayloadProviderConfig :: MParser PayloadProviderConfig pPayloadProviderConfig = id <$< payloadProviderConfigMinimal %:: pMinimalProviderConfig <*< pevm where - cids = [ unsafeChainId i | i <- [0..20]] - -- FIXME this is is really ugly and also clutters the help message. - -- At least use the largest know graph. Ideally, we would use the - -- chainweb version -- but we don't know it yet. - -- - -- FIXME: at the very least we should hide the all but the first options - -- from the help message! + cids = [ unsafeChainId i | i <- [0..100]] + -- FIXME this is is ugly. At least use the largest know graph. Ideally, + -- we would use the chainweb version -- but we don't know it yet. + -- For the help message we just display options for chain 0. pevm = foldr (\a b -> a . b) id <$> traverse go cids - go cid = (payloadProviderConfigEvm . ix cid %~) <$> (pEvmProviderConfig cid) + go cid + = parserOptionGroup "EVM [only options for chain 0 are shown]" evmChains + <|> parserOptionGroup "Pact [only for chain 0 are shown]" pactChains + where + evmChains = (payloadProviderConfigEvm . at cid %~) + <$> providerOpt "evm" cid defaultEvmProviderConfig pEvmProviderConfig + pactChains = (payloadProviderConfigPact . at cid %~) + <$> providerOpt "pact" cid defaultPactProviderConfig pPactProviderConfig + +-- | Utility for parsing payload provider options: +-- +providerOpt + :: forall a + . String + -- ^ name of provider + -> ChainId + -> a + -- default value + -> (ChainId -> MParser a) + -- option parser for the payload provider + -> MParser (Maybe a) +providerOpt prov cid a p = f <$> dis <*> ena <*> p cid + where + f :: Bool -> Bool -> (a -> a) -> Maybe a -> Maybe a + f True _ _ _ = Nothing + f False False _ Nothing = Nothing + f False True update Nothing = Just (update a) + f False _ update (Just y) = Just (update y) + + ena = switch + % long ("enable-chain-" <> T.unpack (toText cid) <> "-" <> prov) + <> help ("enable the payload provider for this chain") + <> mconcat [ hidden <> internal | chainIdInt @Int cid /= 0 ] + + dis = switch + % long ("disable-chain-" <> T.unpack (toText cid) <> "-" <> prov) + <> help ("disabled the payload provider for this chain") + <> mconcat [ hidden <> internal | chainIdInt @Int cid /= 0 ] -- -------------------------------------------------------------------------- -- -- Throttling Configuration @@ -526,29 +505,25 @@ data ChainwebConfiguration = ChainwebConfiguration , _configCuts :: !CutConfig , _configMining :: !MiningConfig , _configHeaderStream :: !Bool - , _configReintroTxs :: !Bool , _configP2p :: !P2pConfiguration , _configThrottling :: !ThrottlingConfig - , _configMempoolP2p :: !(EnableConfig MempoolP2pConfig) - , _configBlockGasLimit :: !Mempool.GasLimit - , _configLogGas :: !Bool - , _configMinGasPrice :: !Mempool.GasPrice - , _configPactQueueSize :: !Natural , _configReorgLimit :: !RewindLimit - , _configPreInsertCheckTimeout :: !Micros - , _configAllowReadsInLocal :: !Bool - , _configFullHistoricPactState :: !Bool , _configBackup :: !BackupConfig , _configServiceApi :: !ServiceApiConfig + , _configPayloadProviders :: PayloadProviderConfig + + -- The following properties are deprecated: history replay should not be + -- part of normal operation mode. It should probably use a completely + -- separate configuration. + , _configReadOnlyReplay :: !Bool -- ^ do a read-only replay using the cut db params for the block heights - , _configOnlySyncPact :: !Bool + , _configOnlySync :: !Bool -- ^ exit after synchronizing pact dbs to the latest cut - , _configSyncPactChains :: !(Maybe [ChainId]) + , _configSyncChains :: !(Maybe [ChainId]) -- ^ the only chains to be synchronized on startup to the latest cut. -- if unset, all chains will be synchronized. - , _configEnableLocalTimeout :: !Bool - , _configPayloadProviders :: PayloadProviderConfig + } deriving (Show, Eq, Generic) makeLenses ''ChainwebConfiguration @@ -591,39 +566,15 @@ defaultChainwebConfiguration v = ChainwebConfiguration , _configCuts = defaultCutConfig , _configMining = defaultMining , _configHeaderStream = False - , _configReintroTxs = True , _configP2p = defaultP2pConfiguration , _configThrottling = defaultThrottlingConfig - , _configMempoolP2p = defaultEnableConfig defaultMempoolP2pConfig - , _configBlockGasLimit = Pact.GasLimit (Pact.Gas 150_000) - , _configLogGas = False - , _configMinGasPrice = Pact.GasPrice 1e-8 - , _configPactQueueSize = 2000 , _configReorgLimit = defaultReorgLimit - , _configPreInsertCheckTimeout = defaultPreInsertCheckTimeout - , _configAllowReadsInLocal = False - , _configFullHistoricPactState = True , _configServiceApi = defaultServiceApiConfig - , _configOnlySyncPact = False + , _configOnlySync = False , _configReadOnlyReplay = False - , _configSyncPactChains = Nothing + , _configSyncChains = Nothing , _configBackup = defaultBackupConfig - , _configEnableLocalTimeout = False - , _configPayloadProviders = minimalPayloadProviderConfig defaultMinimalProviderConfig - -- Similar to bootstrap-peers, there is no default configuration that - -- is valid accross chainweb versions. - -- - -- We have to options: - -- 1. require that users explicitely configure all payload providers - -- that they want to use (FIXME: implement support for opting out of - -- payload providers for some chains, and force miners to keep them - -- all) - -- 2. use the default value for mainnet. That configuration will simply - -- fail validation on other networks. - -- - -- However, even on mainnet users will most likely have to provide an - -- explicit configuration once we have external providers on mainet. At - -- that point it probably makes sense to use the first option. + , _configPayloadProviders = defaultPayloadProviderConfig } instance ToJSON ChainwebConfiguration where @@ -632,24 +583,14 @@ instance ToJSON ChainwebConfiguration where , "cuts" .= _configCuts o , "mining" .= _configMining o , "headerStream" .= _configHeaderStream o - , "reintroTxs" .= _configReintroTxs o , "p2p" .= _configP2p o , "throttling" .= _configThrottling o - , "mempoolP2p" .= _configMempoolP2p o - , "gasLimitOfBlock" .= J.toJsonViaEncode (StableEncoding $ _configBlockGasLimit o) - , "logGas" .= _configLogGas o - , "minGasPrice" .= J.toJsonViaEncode (StableEncoding $ _configMinGasPrice o) - , "pactQueueSize" .= _configPactQueueSize o , "reorgLimit" .= _configReorgLimit o - , "preInsertCheckTimeout" .= _configPreInsertCheckTimeout o - , "allowReadsInLocal" .= _configAllowReadsInLocal o - , "fullHistoricPactState" .= _configFullHistoricPactState o , "serviceApi" .= _configServiceApi o - , "onlySyncPact" .= _configOnlySyncPact o + , "onlySync" .= _configOnlySync o , "readOnlyReplay" .= _configReadOnlyReplay o - , "syncPactChains" .= _configSyncPactChains o + , "syncChains" .= _configSyncChains o , "backup" .= _configBackup o - , "enableLocalTimeout" .= _configEnableLocalTimeout o , "payloadProviders" .= _configPayloadProviders o ] @@ -663,24 +604,14 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where <*< configCuts %.: "cuts" % o <*< configMining %.: "mining" % o <*< configHeaderStream ..: "headerStream" % o - <*< configReintroTxs ..: "reintroTxs" % o <*< configP2p %.: "p2p" % o <*< configThrottling %.: "throttling" % o - <*< configMempoolP2p %.: "mempoolP2p" % o - <*< configBlockGasLimit . iso StableEncoding _stableEncoding ..: "gasLimitOfBlock" % o - <*< configLogGas ..: "logGas" % o - <*< configMinGasPrice . iso StableEncoding _stableEncoding ..: "minGasPrice" % o - <*< configPactQueueSize ..: "pactQueueSize" % o <*< configReorgLimit ..: "reorgLimit" % o - <*< configAllowReadsInLocal ..: "allowReadsInLocal" % o - <*< configPreInsertCheckTimeout ..: "preInsertCheckTimeout" % o - <*< configFullHistoricPactState ..: "fullHistoricPactState" % o <*< configServiceApi %.: "serviceApi" % o - <*< configOnlySyncPact ..: "onlySyncPact" % o + <*< configOnlySync ..: "onlySync" % o <*< configReadOnlyReplay ..: "readOnlyReplay" % o - <*< configSyncPactChains ..: "syncPactChains" % o + <*< configSyncChains ..: "syncChains" % o <*< configBackup %.: "backup" % o - <*< configEnableLocalTimeout ..: "enableLocalTimeout" % o <*< configPayloadProviders %.: "payloadProviders" % o pChainwebConfiguration :: MParser ChainwebConfiguration @@ -689,55 +620,26 @@ pChainwebConfiguration = id <*< configHeaderStream .:: boolOption_ % long "header-stream" <> help "whether to enable an endpoint for streaming block updates" - <*< configReintroTxs .:: enableDisableFlag - % long "tx-reintro" - <> help "whether to enable transaction reintroduction from losing forks" - <*< configP2p %:: pP2pConfiguration - <*< configMempoolP2p %:: - pEnableConfig "mempool-p2p" pMempoolP2pConfig - <*< configBlockGasLimit . iso StableEncoding _stableEncoding .:: jsonOption - % long "block-gas-limit" - <> help "the sum of all transaction gas fees in a block must not exceed this number" - <*< configLogGas .:: boolOption_ - % long "log-gas" - <> help "log gas consumed by Pact commands" - <*< configMinGasPrice . iso StableEncoding _stableEncoding .:: jsonOption - % long "min-gas-price" - <> help "the gas price of an individual transaction in a block must not be beneath this number" - <*< configPactQueueSize .:: jsonOption - % long "pact-queue-size" - <> help "max size of pact internal queue" + <*< parserOptionGroup "P2P" (configP2p %:: pP2pConfiguration) <*< configReorgLimit .:: jsonOption % long "reorg-limit" <> help "Max allowed reorg depth.\ \ Consult https://github.com/kadena-io/chainweb-node/blob/master/docs/RecoveringFromDeepForks.md for\ \ more information. " - <*< configPreInsertCheckTimeout .:: jsonOption - % long "pre-insert-check-timeout" - <> help "Max allowed time in microseconds for the transactions validation in the PreInsertCheck command." - <*< configAllowReadsInLocal .:: boolOption_ - % long "allowReadsInLocal" - <> help "Enable direct database reads of smart contract tables in local queries." - <*< configFullHistoricPactState .:: boolOption_ - % long "full-historic-pact-state" - <> help "Write full historic Pact state; only enable for custodial or archival nodes." - <*< configCuts %:: pCutConfig - <*< configServiceApi %:: pServiceApiConfig - <*< configMining %:: pMiningConfig - <*< configOnlySyncPact .:: boolOption_ - % long "only-sync-pact" + <*< parserOptionGroup "Cut Processing" (configCuts %:: pCutConfig) + <*< parserOptionGroup "Service API" (configServiceApi %:: pServiceApiConfig) + <*< parserOptionGroup "Mining Coordination" (configMining %:: pMiningConfig) + <*< configOnlySync .:: boolOption_ + % long "only-sync" <> help "Terminate after synchronizing the pact databases to the latest cut" <*< configReadOnlyReplay .:: boolOption_ % long "read-only-replay" <> help "Replay the block history non-destructively" - <*< configSyncPactChains .:: fmap Just % jsonOption - % long "sync-pact-chains" + <*< configSyncChains .:: fmap Just % jsonOption + % long "sync-chains" <> help "The only Pact databases to synchronize. If empty or unset, all chains will be synchronized." <> metavar "JSON list of chain ids" - <*< configBackup %:: pBackupConfig - <*< configEnableLocalTimeout .:: option auto - % long "enable-local-timeout" - <> help "Enable timeout support on /local endpoints" + <*< parserOptionGroup "Backup" (configBackup %:: pBackupConfig) -- FIXME support payload providers <*< configPayloadProviders %:: pPayloadProviderConfig diff --git a/src/Chainweb/Mempool/P2pConfig.hs b/src/Chainweb/Mempool/P2pConfig.hs index b4e24fd893..e9c4a541b5 100644 --- a/src/Chainweb/Mempool/P2pConfig.hs +++ b/src/Chainweb/Mempool/P2pConfig.hs @@ -4,6 +4,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} -- | -- Module: Chainweb.Mempool.P2pConfig @@ -35,6 +36,7 @@ import Numeric.Natural import Chainweb.Time import Chainweb.Utils +import Chainweb.ChainId data MempoolP2pConfig = MempoolP2pConfig { _mempoolP2pConfigMaxSessionCount :: !Natural @@ -73,14 +75,15 @@ instance FromJSON MempoolP2pConfig where <*> o .: "sessionTimeout" <*> o .: "pollInterval" -pMempoolP2pConfig :: MParser MempoolP2pConfig -pMempoolP2pConfig = id +pMempoolP2pConfig :: ChainId -> MParser MempoolP2pConfig +pMempoolP2pConfig cid = id <$< mempoolP2pConfigMaxSessionCount .:: option auto - % long "mempool-p2p-max-session-count" - <> help "maximum number of sessions that are active at any time" + % prefixLongCid cid "mempool-p2p-max-session-count" + <> helpCid cid "maximum number of sessions that are active at any time" <*< mempoolP2pConfigSessionTimeout .:: textOption - % long "mempool-p2p-session-timeout" - <> help "timeout for sessions in seconds" + % prefixLongCid cid "mempool-p2p-session-timeout" + <> helpCid cid "timeout for sessions in seconds" <*< mempoolP2pConfigPollInterval .:: textOption - % long "mempool-p2p-poll-interval" - <> help "poll interval for synchronizing mempools in seconds" + % prefixLongCid cid "mempool-p2p-poll-interval" + <> helpCid cid "poll interval for synchronizing mempools in seconds" + diff --git a/src/Chainweb/Miner/Config.hs b/src/Chainweb/Miner/Config.hs index 595745e004..78f53abb5f 100644 --- a/src/Chainweb/Miner/Config.hs +++ b/src/Chainweb/Miner/Config.hs @@ -23,8 +23,6 @@ module Chainweb.Miner.Config , pMiningConfig , miningCoordination , miningInNode -, miningMiner -, invalidMiner , validateMinerConfig , CoordinationConfig(..) , pCoordinationConfig @@ -38,27 +36,19 @@ module Chainweb.Miner.Config import Configuration.Utils -import Control.Lens (lens, view) +import Control.Lens (lens) import Control.Monad (when) import Control.Monad.Except (throwError) import Control.Monad.Writer (tell) -import Data.Set qualified as Set import GHC.Generics (Generic) import Numeric.Natural (Natural) -import Options.Applicative - -import Pact.JSON.Encode qualified as J - -- internal modules -import Pact.Core.Guards qualified as Pact - -import Chainweb.Miner.Pact (Miner(..), MinerGuard(..), MinerId(..), minerId) import Chainweb.Time -import Chainweb.Utils (hostArch, sshow, textOption) +import Chainweb.Utils (hostArch, sshow) import Chainweb.Version import Chainweb.Version.Mainnet import Chainweb.Version.Testnet04 @@ -83,17 +73,12 @@ validateMinerConfig v c = do ] when (not (_coordinationEnabled cc)) $ throwError "In-node mining is enabled but mining coordination is disabled" - when (view minerId (_miningMiner c) == "") - $ throwError "In-node Mining is enabled but no miner id is configured" - when (_coordinationEnabled cc && isProd) $ do when (hostArch `notElem` supportedArchs) $ do throwError $ mconcat [ "Unsupported host architecture for mining on production networks: " <> sshow hostArch <> "." , " Supported architectures are " <> sshow supportedArchs ] - when (view minerId (_miningMiner c) == "") - $ throwError "Mining is enabled but no miner id is configured" where nmc = _miningInNode c cc = _miningCoordination c @@ -109,7 +94,6 @@ validateMinerConfig v c = do data MiningConfig = MiningConfig { _miningCoordination :: !CoordinationConfig , _miningInNode :: !NodeMiningConfig - , _miningMiner :: !Miner } deriving stock (Eq, Show) @@ -119,21 +103,16 @@ miningCoordination = lens _miningCoordination (\m c -> m { _miningCoordination = miningInNode :: Lens' MiningConfig NodeMiningConfig miningInNode = lens _miningInNode (\m c -> m { _miningInNode = c }) -miningMiner :: Lens' MiningConfig Miner -miningMiner = lens _miningMiner (\m c -> m { _miningMiner = c }) - instance ToJSON MiningConfig where toJSON o = object [ "coordination" .= _miningCoordination o , "nodeMining" .= _miningInNode o - , "miner" .= J.toJsonViaEncode (_miningMiner o) ] instance FromJSON (MiningConfig -> MiningConfig) where parseJSON = withObject "MiningConfig" $ \o -> id <$< miningCoordination %.: "coordination" % o <*< miningInNode %.: "nodeMining" % o - <*< miningMiner ..: "miner" % o instance FromJSON MiningConfig where parseJSON v = do @@ -144,18 +123,13 @@ pMiningConfig :: MParser MiningConfig pMiningConfig = id <$< miningCoordination %:: pCoordinationConfig <*< miningInNode %:: pNodeMiningConfig - <*< miningMiner .:: pMiner "" defaultMining :: MiningConfig defaultMining = MiningConfig { _miningCoordination = defaultCoordination , _miningInNode = defaultNodeMining - , _miningMiner = invalidMiner } -invalidMiner :: Miner -invalidMiner = Miner "" . MinerGuard $ Pact.GKeyset (Pact.KeySet mempty Pact.KeysAll) - -- -------------------------------------------------------------------------- -- -- Mining Coordination Config @@ -201,16 +175,6 @@ pCoordinationConfig = id % long "mining-update-stream-timeout" <> help "duration that an update stream is kept open in seconds" -pMiner :: String -> Parser Miner -pMiner prefix = pkToMiner <$> pPk - where - pkToMiner pk = Miner - (MinerId $ "k:" <> Pact._pubKey pk) - (MinerGuard $ Pact.GKeyset $ Pact.KeySet (Set.singleton pk) Pact.KeysAll) - pPk = Pact.PublicKeyText <$> textOption - % long (prefix <> "mining-public-key") - <> help "public key of a miner in hex decimal encoding. The account name is the public key prefix by 'k:'. (This option can be provided multiple times.)" - -- -------------------------------------------------------------------------- -- -- Node Mining Config diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index caeb4f4bb7..7efb426633 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -416,12 +416,25 @@ data PactServiceConfig = PactServiceConfig { _pactReorgLimit :: !RewindLimit -- ^ Maximum allowed reorg depth, implemented as a rewind limit in validate. New block -- hardcodes this to 8 currently. + -- + -- TODO: This is a property of consensus that is currently implemented on + -- payload provider level. It should be moved to consensus level. + -- + -- Payload providers only need to know how much history to keep, which must + -- be enough to satisfy the reorg limit. + , _pactPreInsertCheckTimeout :: !Micros -- ^ Maximum allowed execution time for the transactions validation. , _pactAllowReadsInLocal :: !Bool -- ^ Allow direct database reads in local mode + , _pactUnlimitedInitialRewind :: !Bool -- ^ disable initial rewind limit + -- + -- TODO: this property is only used for history replays, which should in the + -- future be separated from normal operation. So this should be not about + -- initial rewinds but rewind limits in general. + , _pactNewBlockGasLimit :: !Pact.GasLimit -- ^ the gas limit for new block creation, not for validation , _pactLogGas :: !Bool diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 55674060da..692ba48e56 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -25,6 +25,7 @@ {-# OPTIONS_GHC -Wprepositive-qualified-module #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} +{-# LANGUAGE RankNTypes #-} -- | -- Module: Chainweb.PayloadProvider.EVM @@ -35,8 +36,12 @@ -- module Chainweb.PayloadProvider.EVM ( EvmProviderConfig(..) +, evmConfEngineUri +, evmConfEngineJwtSecret +, evmConfMinerAddress , defaultEvmProviderConfig , pEvmProviderConfig +, validateEvmProviderConfig -- * EVM Payload Provider Implementation , EvmPayloadProvider(..) @@ -85,6 +90,7 @@ import Control.Concurrent.STM import Control.Exception.Safe import Control.Lens hiding ((.=)) import Control.Monad +import Control.Monad.Writer import Data.ByteString.Short qualified as BS import Data.List qualified as L import Data.LogMessage @@ -105,7 +111,6 @@ import Network.URI.Static import P2P.Session (ClientEnv) import P2P.TaskQueue import System.LogLevel -import Data.Function -- -------------------------------------------------------------------------- -- -- Types (to keep the code clean and avoid confusion) @@ -138,25 +143,15 @@ payloadDbConfiguration = EvmDB.configuration -- -------------------------------------------------------------------------- -- -- Configuration -prefixLongCid :: HasName f => ChainId -> String -> Mod f a -prefixLongCid cid = prefixLong (Just p) - where - p = "chain-" <> T.unpack (toText cid) - -suffixHelpCid :: ChainId -> String -> Mod f a -suffixHelpCid cid = suffixHelp (Just s) - where - s = "chain-" <> T.unpack (toText cid) - pJwtSecret :: ChainId -> OptionParser JwtSecret pJwtSecret cid = textOption - % prefixLongCid cid "jwt-secret" - <> suffixHelpCid cid "JWT secret for the EVM Engine API" + % prefixLongCid cid "evm-jwt-secret" + <> helpCid cid "JWT secret for the EVM Engine API" pMinerAddress :: ChainId -> OptionParser EVM.Address pMinerAddress cid = textOption - % prefixLongCid cid "miner-address" - <> suffixHelpCid cid "Miner address for new EVM blocks" + % prefixLongCid cid "evm-miner-address" + <> helpCid cid "Miner address for new EVM blocks" newtype EngineUri = EngineUri { _engineUri :: URI } deriving (Show, Eq, Generic) @@ -168,8 +163,8 @@ instance HasTextRepresentation EngineUri where pEngineUri :: ChainId -> OptionParser EngineUri pEngineUri cid = textOption - % prefixLongCid cid "uri" - <> suffixHelpCid cid "EVM Engine URI" + % prefixLongCid cid "evm-uri" + <> helpCid cid "EVM Engine URI" data EvmProviderConfig = EvmProviderConfig { _evmConfEngineUri :: !EngineUri @@ -187,6 +182,26 @@ defaultEvmProviderConfig = EvmProviderConfig , _evmConfMinerAddress = Nothing } +validateEvmProviderConfig :: ChainwebVersion -> ChainId -> ConfigValidation EvmProviderConfig [] +validateEvmProviderConfig _ cid conf = do + when (euri == _evmConfEngineUri defaultEvmProviderConfig) $ tell + [ "EVM Engine URI for " <> sshow cid + <> " is to the default value: " <> sshow euri + <> ". This is likely to fail if more than one EVM chain is configured" + ] + when (jwt == _evmConfEngineJwtSecret defaultEvmProviderConfig) $ tell + [ "EVM Engine JWT secret for " <> sshow cid + <> " is to the default value: " <> sshow jwt + <> ". This value is not a secure JWT secret." + ] + when (isNothing $ _evmConfMinerAddress conf) $ tell + [ "EVM miner address is not set for " <> sshow cid + <> ". This means that payload production is disabled." + ] + where + euri = _evmConfEngineUri conf + jwt = _evmConfEngineJwtSecret conf + instance ToJSON EvmProviderConfig where toJSON o = object [ "engineUri" .= _evmConfEngineUri o diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index f383bf91f2..02e4dd9ba6 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -14,11 +14,11 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE TypeAbstractions #-} -- | -- Module: Chainweb.IdleProvider @@ -73,6 +73,8 @@ -- module Chainweb.PayloadProvider.Minimal ( MinimalProviderConfig(..) +, mpcRedeemChain +, mpcRedeemAccount , defaultMinimalProviderConfig , validateMinimalProviderConfig , pMinimalProviderConfig diff --git a/src/Chainweb/PayloadProvider/Pact/Configuration.hs b/src/Chainweb/PayloadProvider/Pact/Configuration.hs new file mode 100644 index 0000000000..c0d32b8d19 --- /dev/null +++ b/src/Chainweb/PayloadProvider/Pact/Configuration.hs @@ -0,0 +1,172 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} + +-- | +-- Module: Chainweb.PayloadProvider.Pact.Configuration +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.Pact.Configuration +( PactProviderConfig(..) +, pactConfigReintroTxs +, pactConfigMempoolP2p +, pactConfigBlockGasLimit +, pactConfigLogGas +, pactConfigMinGasPrice +, pactConfigPactQueueSize +, pactConfigPreInsertCheckTimeout +, pactConfigAllowReadsInLocal +, pactConfigFullHistoricPactState +, pactConfigEnableLocalTimeout +, pactConfigMiner +, defaultPactProviderConfig +, pPactProviderConfig +, pMiner +, invalidMiner +) where + +import Chainweb.Mempool.Mempool qualified as Mempool +import Chainweb.Mempool.P2pConfig +import Chainweb.Miner.Pact (Miner(..), MinerGuard(..), MinerId(..)) +import Chainweb.Pact.Types (defaultPreInsertCheckTimeout) +import Chainweb.Time hiding (second) +import Chainweb.Utils +import Chainweb.Version +import Configuration.Utils hiding (Error, Lens', disabled) +import Control.Lens hiding ((.=), (<.>)) +import Control.Monad +import Data.Maybe +import Data.Set qualified as Set +import GHC.Generics hiding (from, to) +import Numeric.Natural (Natural) +import Pact.Core.Gas qualified as Pact +import Pact.Core.Guards qualified as Pact +import Pact.Core.StableEncoding +import Pact.JSON.Encode qualified as J +import Prelude hiding (log) + +-- -------------------------------------------------------------------------- -- + +invalidMiner :: Miner +invalidMiner = Miner "" + $ MinerGuard + $ Pact.GKeyset (Pact.KeySet mempty Pact.KeysAll) + +pMiner :: ChainId -> OptionParser Miner +pMiner cid = pkToMiner <$> pPk + where + pkToMiner pk = Miner + (MinerId $ "k:" <> Pact._pubKey pk) + (MinerGuard $ Pact.GKeyset $ Pact.KeySet (Set.singleton pk) Pact.KeysAll) + pPk = Pact.PublicKeyText <$> textOption + % prefixLongCid cid "pact-mining-public-key" + <> helpCid cid + "Public key of a miner in hex decimal encoding. The account name is the public key prefix by 'k:'. Payload production is disabled if not set." + +-- -------------------------------------------------------------------------- -- +-- Payload Provider Configuration + +-- | Placeholder for PactProviderConfig +-- +data PactProviderConfig = PactProviderConfig + { _pactConfigReintroTxs :: !Bool + , _pactConfigMempoolP2p :: !(EnableConfig MempoolP2pConfig) + , _pactConfigBlockGasLimit :: !Mempool.GasLimit + , _pactConfigLogGas :: !Bool + , _pactConfigMinGasPrice :: !Mempool.GasPrice + , _pactConfigPactQueueSize :: !Natural + , _pactConfigPreInsertCheckTimeout :: !Micros + , _pactConfigAllowReadsInLocal :: !Bool + + -- For shallow nodes this should be a history depth parameter + , _pactConfigFullHistoricPactState :: !Bool + + , _pactConfigEnableLocalTimeout :: !Bool + , _pactConfigMiner :: !(Maybe Miner) + } + deriving (Show, Eq, Generic) + +makeLenses ''PactProviderConfig + +instance ToJSON PactProviderConfig where + toJSON o = object + [ "reintroTxs" .= _pactConfigReintroTxs o + , "mempoolP2p" .= _pactConfigMempoolP2p o + , "gasLimitOfBlock" .= J.toJsonViaEncode (StableEncoding $ _pactConfigBlockGasLimit o) + , "logGas" .= _pactConfigLogGas o + , "minGasPrice" .= J.toJsonViaEncode (StableEncoding $ _pactConfigMinGasPrice o) + , "pactQueueSize" .= _pactConfigPactQueueSize o + , "preInsertCheckTimeout" .= _pactConfigPreInsertCheckTimeout o + , "allowReadsInLocal" .= _pactConfigAllowReadsInLocal o + , "fullHistoricPactState" .= _pactConfigFullHistoricPactState o + , "enableLocalTimeout" .= _pactConfigEnableLocalTimeout o + , "miner" .= J.toJsonViaEncode (_pactConfigMiner o) + ] + +instance FromJSON (PactProviderConfig -> PactProviderConfig) where + parseJSON = withObject "PactProviderConfig" $ \o -> id + <$< pactConfigReintroTxs ..: "reintroTxs" % o + <*< pactConfigMempoolP2p %.: "mempoolP2p" % o + <*< pactConfigBlockGasLimit . iso StableEncoding _stableEncoding ..: "gasLimitOfBlock" % o + <*< pactConfigLogGas ..: "logGas" % o + <*< pactConfigMinGasPrice . iso StableEncoding _stableEncoding ..: "minGasPrice" % o + <*< pactConfigPactQueueSize ..: "pactQueueSize" % o + <*< pactConfigPreInsertCheckTimeout ..: "preInsertCheckTimeout" % o + <*< pactConfigAllowReadsInLocal ..: "allowReadsInLocal" % o + <*< pactConfigFullHistoricPactState ..: "fullHistoricPactState" % o + <*< pactConfigEnableLocalTimeout ..: "enableLocalTimeout" % o + <*< pactConfigMiner ..: "miner" % o + +defaultPactProviderConfig :: PactProviderConfig +defaultPactProviderConfig = PactProviderConfig + { _pactConfigReintroTxs = True + , _pactConfigMempoolP2p = defaultEnableConfig defaultMempoolP2pConfig + , _pactConfigBlockGasLimit = Pact.GasLimit (Pact.Gas 150_000) + , _pactConfigLogGas = False + , _pactConfigMinGasPrice = Pact.GasPrice 1e-8 + , _pactConfigPactQueueSize = 2000 + , _pactConfigPreInsertCheckTimeout = defaultPreInsertCheckTimeout + , _pactConfigAllowReadsInLocal = False + , _pactConfigFullHistoricPactState = True + , _pactConfigEnableLocalTimeout = False + , _pactConfigMiner = Nothing + } + +pPactProviderConfig :: ChainId -> MParser PactProviderConfig +pPactProviderConfig cid = id + <$< pactConfigReintroTxs .:: enableDisableFlag + % prefixLongCid cid "pact-tx-reintro" + <> helpCid cid "whether to enable transaction reintroduction from losing forks" + <*< pactConfigMempoolP2p %:: pEnableConfigCid "pact-mempool-p2p" cid pMempoolP2pConfig + <*< pactConfigBlockGasLimit . iso StableEncoding _stableEncoding .:: jsonOption + % prefixLongCid cid "pact-block-gas-limit" + <> helpCid cid "the sum of all transaction gas fees in a block must not exceed this number" + <*< pactConfigLogGas .:: boolOption_ + % prefixLongCid cid "pact-log-gas" + <> helpCid cid "log gas consumed by Pact commands" + <*< pactConfigMinGasPrice . iso StableEncoding _stableEncoding .:: jsonOption + % prefixLongCid cid "pact-min-gas-price" + <> helpCid cid "the gas price of an individual transaction in a block must not be beneath this number" + <*< pactConfigPactQueueSize .:: jsonOption + % prefixLongCid cid "pact-queue-size" + <> helpCid cid "max size of pact internal queue" + <*< pactConfigPreInsertCheckTimeout .:: jsonOption + % prefixLongCid cid "pact-pre-insert-check-timeout" + <> helpCid cid "Max allowed time in microseconds for the transactions validation in the PreInsertCheck command." + <*< pactConfigAllowReadsInLocal .:: boolOption_ + % prefixLongCid cid "pact-allowReadsInLocal" + <> helpCid cid "Enable direct database reads of smart contract tables in local queries." + <*< pactConfigFullHistoricPactState .:: boolOption_ + % prefixLongCid cid "pact-full-historic-pact-state" + <> helpCid cid "Write full historic Pact state; only enable for custodial or archival nodes." + <*< pactConfigEnableLocalTimeout .:: option auto + % prefixLongCid cid "pact-enable-local-timeout" + <> helpCid cid "Enable timeout support on /local endpoints" + <*< pactConfigMiner .:: fmap Just % pMiner cid + diff --git a/src/Chainweb/RestAPI/Config.hs b/src/Chainweb/RestAPI/Config.hs index 457be75065..e885bd82f2 100644 --- a/src/Chainweb/RestAPI/Config.hs +++ b/src/Chainweb/RestAPI/Config.hs @@ -28,7 +28,10 @@ import Servant -- internal modules import Chainweb.Chainweb.Configuration -import Chainweb.Miner.Config +import Chainweb.PayloadProvider.EVM (evmConfMinerAddress) +import Chainweb.PayloadProvider.Minimal (mpcRedeemAccount) +import Chainweb.PayloadProvider.Minimal.Payload (invalidAccount) +import Chainweb.PayloadProvider.Pact.Configuration (pactConfigMiner) import Chainweb.RestAPI.Utils import P2P.Node.Configuration @@ -54,7 +57,9 @@ someGetConfigServer config = SomeServer (Proxy @GetConfigApi) $ return $ set (configP2p . p2pConfigPeer . peerConfigKeyFile) Nothing -- Miner Info - $ set (configMining . miningMiner) invalidMiner + $ set (configPayloadProviders . payloadProviderConfigMinimal . mpcRedeemAccount) invalidAccount + $ set (configPayloadProviders . payloadProviderConfigPact . each . pactConfigMiner) Nothing + $ set (configPayloadProviders . payloadProviderConfigEvm . each . evmConfMinerAddress) Nothing -- Service API port $ set (configServiceApi . serviceApiConfigPort) 0 diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index 8326a1ec64..7d384b8ad6 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -157,9 +157,6 @@ multiConfig v n = defaultChainwebConfiguration v & set (configMining . miningCoordination . coordinationEnabled) True & set (configMining . miningInNode) miner - & set configReintroTxs True - -- enable transaction re-introduction - & set configThrottling throttling -- throttling is effectively disabled to not slow down the test nodes @@ -577,7 +574,7 @@ replayTest loglevel v n rdb pactDbDir step = do runNodesForSeconds loglevel logFun (multiConfig v n & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight) - & set configOnlySyncPact True) + & set configOnlySync True) n (Seconds 20) rdb pactDbDir $ \nid cw -> case cw of Replayed l (Just u) -> do writeIORef firstReplayCompleteRef True @@ -599,7 +596,7 @@ replayTest loglevel v n rdb pactDbDir step = do (multiConfig v n & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight) & set (configCuts . cutFastForwardBlockHeightLimit) (Just fastForwardHeight) - & set configOnlySyncPact True) + & set configOnlySync True) n (Seconds 20) rdb pactDbDir $ \_ cw -> case cw of Replayed l (Just u) -> do writeIORef secondReplayCompleteRef True diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index f5d3c21c5d..2986257089 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -836,7 +836,7 @@ instance Arbitrary NodeMiningConfig where <$> arbitrary <*> pure (MinerCount 10) instance Arbitrary MiningConfig where - arbitrary = MiningConfig <$> arbitrary <*> arbitrary <*> arbitrary + arbitrary = MiningConfig <$> arbitrary <*> arbitrary -- -------------------------------------------------------------------------- -- -- Chainweb.SPV.EventProof diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index 24f558187f..b9cb3a830f 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -214,6 +214,7 @@ import Chainweb.Miner.Config import Chainweb.Pact.Backend.Types(SQLiteEnv) import Chainweb.Pact.Backend.Utils import Chainweb.Parent +import Chainweb.PayloadProvider.Pact.Configuration import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID import Chainweb.Test.Pact.Utils (getTestLogLevel, getTestLogger) @@ -1089,8 +1090,13 @@ config ver n = defaultChainwebConfiguration ver & set (configP2p . p2pConfigMaxSessionCount) 4 & set (configP2p . p2pConfigSessionTimeout) 60 & set (configMining . miningInNode) miner - & set configReintroTxs True - & set configBlockGasLimit (Pact.GasLimit $ Pact.Gas 1_000_000) + & set + ( configPayloadProviders + . payloadProviderConfigPact + . each + . pactConfigBlockGasLimit + ) + (Pact.GasLimit $ Pact.Gas 1_000_000) & set (configMining . miningCoordination . coordinationEnabled) True & set (configServiceApi . serviceApiConfigPort) 0 & set (configServiceApi . serviceApiConfigInterface) interface From b02128f7b63a3deb293a7ea755193599aae20b0e Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Sun, 4 May 2025 19:28:10 -0400 Subject: [PATCH 134/378] Simplified miner matchmaking --- src/Chainweb/ChainId.hs | 7 +- src/Chainweb/Logging/Miner.hs | 1 - src/Chainweb/Miner/Coordinator.hs | 525 ++++++++------------------- src/Chainweb/Miner/PayloadCache.hs | 1 - src/Chainweb/Miner/RestAPI/Server.hs | 57 +-- src/Chainweb/PayloadProvider.hs | 10 +- 6 files changed, 187 insertions(+), 414 deletions(-) diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index f9f3399476..7854979d4f 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -66,6 +66,7 @@ module Chainweb.ChainId , onChains , onChain , chainZip +, chainIntersect -- * Configuration Utils , prefixLongCid @@ -296,6 +297,8 @@ instance Semigroup (ChainMap a) where (<>) = chainZip (\f _s -> f) instance Monoid (ChainMap a) where mempty = ChainMap mempty +instance FunctorWithIndex ChainId ChainMap where + imap f (ChainMap a) = ChainMap $ imap f a instance FoldableWithIndex ChainId ChainMap where ifoldMap f (ChainMap a) = ifoldMap f a @@ -315,6 +318,9 @@ onChain c a = ChainMap (HM.singleton c a) chainZip :: (a -> a -> a) -> ChainMap a -> ChainMap a -> ChainMap a chainZip f (ChainMap l) (ChainMap r) = ChainMap $ HM.unionWith f l r +chainIntersect :: (a -> b -> c) -> ChainMap a -> ChainMap b -> ChainMap c +chainIntersect f (ChainMap l) (ChainMap r) = ChainMap $ HM.intersectionWith f l r + instance ToJSON a => ToJSON (ChainMap a) where toJSON (ChainMap m) = toJSON m @@ -363,4 +369,3 @@ pEnableConfigCid comp cid pConfig = id <> help ("whether " <> comp <> " is enabled or disabled for the respective chain") <> mconcat [ hidden <> internal | chainIdInt @Int cid /= 0 ] <*< enableConfigConfig %:: pConfig cid - diff --git a/src/Chainweb/Logging/Miner.hs b/src/Chainweb/Logging/Miner.hs index 795e36c39e..d408382b76 100644 --- a/src/Chainweb/Logging/Miner.hs +++ b/src/Chainweb/Logging/Miner.hs @@ -46,7 +46,6 @@ data NewMinedBlock = NewMinedBlock data OrphanedBlock = OrphanedBlock { _orphanedParent :: !(Parent BlockHash) , _orphanedPayloadHash :: !BlockPayloadHash - , _orphanedBestOnCut :: !(ObjectEncoded BlockHeader) , _orphanedDiscoveredAt :: !(Time Micros) , _orphanedReason :: !Text } diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index fade43b1c7..71ac67fc2a 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -21,6 +21,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE BlockArguments #-} +{-# LANGUAGE RecordWildCards #-} -- | -- Module: Chainweb.Miner.Coordinator @@ -42,6 +43,7 @@ module Chainweb.Miner.Coordinator -- * WorkState , WorkState(..) +, awaitLatestPayloadForWorkStateSTM -- ** Payload Caches , type PayloadCaches @@ -49,9 +51,7 @@ module Chainweb.Miner.Coordinator , awaitPayloadsNext -- ** MiningState -, MiningState , updateForCut -, updateForPayload , updateForSolved -- ** Delivery @@ -79,15 +79,20 @@ import Chainweb.Storage.Table import Chainweb.Time (Micros(..), getCurrentTimeIntegral) import Chainweb.Utils hiding (check) import Chainweb.Version +import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB import Control.Applicative import Control.Concurrent.Async import Control.Concurrent.STM (atomically, STM, retry) import Control.Concurrent.STM.TVar +import Control.Concurrent.STM.TMVar +import Control.Exception.Safe import Control.Lens import Control.Monad -import Control.Monad.Catch +import Control.Monad.Except import Control.Monad.IO.Class +import Control.Monad.Trans.Class +import Data.Foldable import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS import Data.Hashable @@ -106,22 +111,6 @@ import System.Random (randomRIO) -- -------------------------------------------------------------------------- -- -- Utils --- | Lookup a 'BlockHeader' for a 'ChainId' in a cut and raise a meaningfull --- error if the lookup fails. --- --- Generally, failing lookup in a cut is a code invariant violation. In almost --- all circumstances there should be a invariant in scope that guarantees that --- the lookup succeeds. This function is useful when debugging corner cases of --- new code logic, like graph changes. --- -lookupInCut :: HasCallStack => HasChainId cid => Cut -> cid -> BlockHeader -lookupInCut c cid - | Just x <- lookupCutM cid c = x - | otherwise = error $ T.unpack - $ "Chainweb.Miner.Coordinator.lookupInCut: failed to lookup chain in cut." - <> " Chain: " <> sshow (_chainId cid) <> "." - <> " Cut Hashes: " <> encodeToText (cutToCutHashes Nothing c) <> "." - type WorkParentsId = (Parent BlockHash, AdjacentsHash) parentsId :: WorkParents -> WorkParentsId @@ -139,18 +128,15 @@ solvedId s = -- -------------------------------------------------------------------------- -- -- Payload Caches -type PayloadCaches = HM.HashMap ChainId PayloadCache +type PayloadCaches = ChainMap PayloadCache newPayloadCaches :: HasChainwebVersion v => HasChainGraph v => v -> IO PayloadCaches -newPayloadCaches v = mapM (const (newIO depth)) cids +newPayloadCaches v = tabulateChainsM (_chainwebVersion v) (\_ -> newIO depth) where - cids :: HM.HashMap ChainId () - cids = HS.toMap (chainIds v) - -- FIXME: Make this configurable? depth :: Natural depth = diameter (_chainGraph v) @@ -167,10 +153,10 @@ awaitPayloadsNext -> V.Vector Int -- ^ The hash/fingerprint of the previous value for each chain. -> STM NewPayload -awaitPayloadsNext caches c prevs = msum (awaitChain <$> HM.toList xs) +awaitPayloadsNext caches c prevs = msum (awaitChain <$> itoList xs) where - xs = HM.intersectionWith (,) caches rhs - rhs = Parent . _rankedBlockHash <$> _cutHeaders c + xs = chainIntersect (,) caches rhs + rhs = ChainMap $ Parent . _rankedBlockHash <$> _cutHeaders c awaitChain (ChainId cid, (ca, rh)) = do x <- awaitLatestSTM ca rh @@ -244,201 +230,41 @@ awaitPayloadsNext caches c prevs = msum (awaitChain <$> HM.toList xs) -- Solved -> Solved [label=payload]; -- @ -- -data WorkState - = WorkNotReady !(Parent RankedBlockHash) - -- ^ Chain is blocked and no payload has yet been produced - - | WorkStale !(Parent RankedBlockHash) !WorkParents - -- ^ The chain is unblocked but no payload has produced yet. - -- - -- Invariant: The work parents must match the ranked block hash. - - | WorkBlocked !(Parent RankedBlockHash) !NewPayload - -- ^ A payload is ready but the chain is still blocked - -- - -- Invariant: The payload must match the ranked block hash. - - | WorkReady !(Parent RankedBlockHash) !NewPayload !WorkParents - -- ^ The chain is ready for mining - -- - -- Invariant: The payload, parents, work header must match the ranked block - -- hash. - - | WorkSolved !(Parent RankedBlockHash) !NewPayload !WorkParents +data WorkState = WorkState + { workStateParents :: !WorkParents + -- ^ Invariant: The work parents must match the ranked block hash. + , workStateSolved :: !(Maybe SolvedWork) -- ^ A block with these parents has already been solved and submitted to the -- cut pipeline - we don't want to mine it again. So we wait until the -- current cut is updated with a new parent header for this chain. + } + deriving stock (Show, Eq, Generic) - deriving stock (Show, Eq, Generic) - -_workRankedHash :: WorkState -> Parent RankedBlockHash -_workRankedHash (WorkNotReady rh) = rh -_workRankedHash (WorkStale rh _) = rh -_workRankedHash (WorkBlocked rh _) = rh -_workRankedHash (WorkReady rh _ _) = rh -_workRankedHash (WorkSolved rh _ _) = rh - -_workStateHash :: WorkState -> Parent BlockHash -_workStateHash = fmap _rankedBlockHashHash . _workRankedHash - -_workStateHeight :: WorkState -> Parent BlockHeight -_workStateHeight = fmap _rankedBlockHashHeight . _workRankedHash - -workReady - :: Parent RankedBlockHash - -> NewPayload - -> WorkParents - -> WorkState -workReady rbh pld ps' = WorkReady rbh pld ps' - -_newPayloadRankedHash :: NewPayload -> RankedBlockHash -_newPayloadRankedHash p = - RankedBlockHash (unwrapParent $ _newPayloadParentHeight p) (unwrapParent $ _newPayloadParentHash p) +awaitLatestPayloadForWorkStateSTM :: PayloadCache -> WorkState -> STM NewPayload +awaitLatestPayloadForWorkStateSTM payloadCache workState = do + let parent = fmap (view rankedBlockHash) $ _workParent' $ workStateParents workState + awaitLatestSTM payloadCache parent instance Brief WorkState where - brief (WorkNotReady rh) = "WorkNotReady" <> ":" <> brief rh - brief (WorkStale rh _) = "WorkStale" <> ":" <> brief rh - brief (WorkBlocked rh _) = "WorkBlocked" <> ":" <> brief rh - brief (WorkReady rh _ _) = "WorkReady" <> ":" <> brief rh - brief (WorkSolved rh _ _) = "WorkSolved" <> ":" <> brief rh - --- -------------------------------------------------------------------------- -- --- WorkState Transition Function - --- | Called on headers event --- -onHeader - :: Parent RankedBlockHash - -> WorkState - -> Maybe WorkState -onHeader rh cur - | rh == _workRankedHash cur = Nothing - | otherwise = Just $ WorkNotReady rh - --- | Called on a work headers event. --- --- If the parent header changes, 'onHeader' is called first, which also resets --- the payload. For that reason it should be also checked whether there is a --- payload available for the new header. --- -onParents - :: Maybe WorkParents - -- Just the work parents or 'Nothing' when the chain is blocked. - -> WorkState - -> Maybe WorkState -onParents (Just ps) cur | psRankedHash /= _workRankedHash cur = - onHeader psRankedHash cur >>= onParents (Just ps) - where - psRankedHash = _rankedBlockHash <$> _workParent ps -onParents (Just ps') (WorkNotReady rh) = Just $ WorkStale rh ps' -onParents (Just ps') (WorkBlocked rh pld) = Just $ workReady rh pld ps' -onParents (Just ps') (WorkStale rh ps) - | ps /= ps' = Just $ WorkStale rh ps' - | otherwise = Nothing -onParents (Just ps') (WorkReady rh pld ps) - | ps /= ps' = Just $ workReady rh pld ps' - | otherwise = Nothing -onParents (Just ps') (WorkSolved rh pld ps) - | ps /= ps' = Just $ workReady rh pld ps' - | otherwise = Nothing -onParents Nothing WorkNotReady{} = Nothing -onParents Nothing WorkBlocked{} = Nothing -onParents Nothing (WorkStale rh _) = Just $ WorkNotReady rh -onParents Nothing (WorkReady rh pld _) = Just $ WorkBlocked rh pld -onParents Nothing (WorkSolved rh pld _) = Just $ WorkBlocked rh pld - --- | Called on a new payload event. --- --- If the parent header of the new payload does not match the current parent --- header, it is ignored. Only an 'onParents' event can update the current --- parent header. --- -onPayload - :: NewPayload - -> WorkState - -> Maybe WorkState -onPayload pld' cur | _newPayloadRankedParentHash pld' /= _workRankedHash cur = Nothing -onPayload pld' (WorkNotReady rh) = Just $ WorkBlocked rh pld' -onPayload pld' (WorkStale rh ps) = Just $ workReady rh pld' ps -onPayload pld' (WorkBlocked rh pld) - | pld /= pld' = Just $ WorkBlocked rh pld' - | otherwise = Nothing -onPayload pld' (WorkReady rh pld ps) - | pld /= pld' = Just $ workReady rh pld' ps - | otherwise = Nothing -onPayload _ WorkSolved{} = Nothing - --- | Called when work is solved for the chain. --- -onSolved - :: SolvedWork - -> WorkState - -> Maybe WorkState -onSolved s (WorkReady rh pld ps) - -- If work is currently ready for this header in this cut, we mark it solved. - -- We checked that before, when we processed the solution. But we have to do - -- it again, since the state is updated asynchronously and there could be a - -- race. - | solvedId s == parentsId ps = Just $ WorkSolved rh pld ps --- otherwise do not change the state. --- If we solved this header in this cut before we do not change the work --- state, even if the payload differs. -onSolved _ _ = Nothing + brief WorkState{..} = + "WorkState:" <> brief workStateParents + <> ":solved=" <> brief workStateSolved -- -------------------------------------------------------------------------- -- -- Mining State --- | Current Mining State on each chain. It is updated --- --- 1. each time a new cut is received --- 2. each time a block is solved --- 3. each time a new payload is received for a chain --- -newtype MiningState = MiningState - { _miningState :: M.Map ChainId (TVar WorkState) - } - deriving stock (Generic) - deriving newtype (Semigroup, Monoid) - -makeLenses ''MiningState - -type instance Index MiningState = ChainId -type instance IxValue MiningState = TVar WorkState - -instance Ixed MiningState where - ix i = miningState . ix i - -instance IxedGet MiningState - -instance Each MiningState MiningState (TVar WorkState) (TVar WorkState) where - each f = fmap MiningState . each f . _miningState - -newMiningState :: Cut -> IO MiningState +newMiningState :: Cut -> IO (ChainMap (TVar (Maybe WorkState))) newMiningState c = do states <- forM cids $ \cid -> do - var <- newTVarIO - $ WorkNotReady - $ Parent - $ _rankedBlockHash - $ fromMaybe (genesisBlockHeader v cid) (HM.lookup cid (_cutMap c)) + var <- newTVarIO Nothing return (cid, var) - return $ MiningState $! M.fromList states + return $! onChains states where v = _chainwebVersion c cids :: [ChainId] cids = HS.toList (chainIds v) -updateStateVar :: LogFunctionText -> ChainId -> TVar WorkState -> WorkState -> IO () -updateStateVar lf cid var new = do - -- Logging. This can race, but we don't care - cur <- readTVarIO var - lf Debug $ "update work state" - <> "; chain: " <> toText cid - <> "; cur: " <> brief cur - <> "; new: " <> brief new - atomically $ writeTVar var new - -- TODO: consider storing the mining state more efficiently: -- -- Do not recompute cut extensions more often than needed. @@ -451,80 +277,81 @@ updateStateVar lf cid var new = do updateForCut :: LogFunctionText -> (ChainValue BlockHash -> IO BlockHeader) - -> PayloadCaches - -> MiningState + -> (ChainMap (TVar (Maybe WorkState))) -> Cut -> IO () -updateForCut lf hdb caches ms c = do - forM_ (M.toList $ _miningState ms) $ \(cid, var) -> - forChain cid var (caches ^?! ix cid) - where - forChain cid var cache = do - ps <- workParents hdb c cid - cur <- readTVarIO var - - -- logging - -- cs <- sizeIO cache - -- cl <- getLatestIO cache (_workRankedHash cur) - -- ch <- payloadHashesIO cache - -- lf @T.Text Debug $ "updateForCut for chain: " <> brief cid - -- <> "; cur: " <> brief cur - -- <> "; cut: " <> brief (c ^?! ixg cid) - -- <> "; parent: " <> brief (_workParent <$> ps) - -- <> "; cache size: " <> sshow cs - -- <> "; cache depth: " <> sshow (_payloadCacheDepth cache) - -- <> "; cache latest: " <> brief cl - -- <> "; cache hashes: " <> brief ch - - case onParents ps cur of - Nothing -> return () - Just !new - - -- Check whether the parent header is still the same - | _workRankedHash new == _workRankedHash cur -> - updateStateVar lf cid var new - - -- if the parent header changed, check if a payload is available - | otherwise -> getLatestIO cache (_workRankedHash new) >>= \case - Nothing -> updateStateVar lf cid var new - Just pld -> case onPayload pld new of - Nothing -> updateStateVar lf cid var new - Just !newnew -> updateStateVar lf cid var newnew - -updateForPayload :: LogFunctionText -> MiningState -> NewPayload -> IO () -updateForPayload lf ms pld = do - cur <- readTVarIO var - -- lf @T.Text Debug $ "updateForPayload on chain: " <> toText cid - -- <> "; cur: " <> brief cur - -- <> "; new payload: " <> brief pld - case onPayload pld cur of - Nothing -> return () - Just !new -> updateStateVar lf cid var new +updateForCut lf hdb ms c = do + iforM_ ms $ \cid var -> + forChain cid var where - cid = _chainId pld - var = ms ^?! ixg cid - -updateForSolved :: LogFunctionText -> MiningState -> SolvedWork -> IO () -updateForSolved lf ms sw = do - cur <- readTVarIO var - -- lf @T.Text Debug $ "updateForSolved on chain: " <> toText cid - -- <> "; cur: " <> brief cur - -- <> "; sw: " <> brief sw - case onSolved sw cur of - Nothing -> return () - Just !new -> do - updateStateVar lf cid var new - where - cid = _chainId sw - var = ms ^?! ixg cid + forChain cid var = do + maybeNewParents <- workParents hdb c cid + atomically $ do + maybeOldWorkState <- readTVar var + case (maybeOldWorkState, maybeNewParents) of + (_, Nothing) -> + writeTVar var Nothing + (Just oldWorkState, Just newParents) + | workStateParents oldWorkState == newParents + -> return () + (_, Just newParents) -> + writeTVar var $ Just + WorkState + { workStateParents = newParents + , workStateSolved = Nothing + } + +updateForSolved :: LogFunction -> CutDb -> PayloadCache -> TVar (Maybe WorkState) -> SolvedWork -> IO () +updateForSolved lf cdb payloadCache var sw = do + stateOrErr <- runExceptT $ do + solvedWorkState <- mapExceptT atomically $ do + lift (readTVar var) >>= \case + Just workState + | solvedId sw /= parentsId (workStateParents workState) -> + throwError (Info, "orphaned; this block is for an older parent set") + | Just _ <- workStateSolved workState -> + throwError (Debug, "this block has already been solved") + | otherwise -> do + let newState = workState { workStateSolved = Just sw } + lift (writeTVar var (Just newState)) + return newState + -- orphaned by parent disappearance + Nothing -> + throwError (Info, "orphaned; this block is for a blocked chain") + + c <- lift $ _cut cdb + lift (lookupIO payloadCache (fmap (view rankedBlockHash) $ _workParent $ workStateParents solvedWorkState) (_solvedPayloadHash sw)) >>= \case + Nothing -> throwError (Warn, "updateForSolved: missing payload in cache: " <> brief solvedWorkState) + Just payload -> do + let pld = _newPayloadEncodedPayloadData payload + let pwo = _newPayloadEncodedPayloadOutputs payload + + extend c pld pwo (workStateParents solvedWorkState) sw >>= \case -awaitAnyReady :: MiningState -> STM (WorkParents, NewPayload) -awaitAnyReady s = msum $ awaitWorkReady <$> _miningState s - where - awaitWorkReady :: TVar WorkState -> STM (WorkParents, NewPayload) - awaitWorkReady var = readTVar var >>= \case - WorkReady _ pld ps -> return (ps, pld) - _ -> retry + -- Publish CutHashes to CutDb and log success + (bh, Just ch) -> do + lift $ publish cdb ch + lift $ logMinedBlock lf bh payload + return solvedWorkState + + -- Log Orphaned Block + (_, Nothing) -> do + throwError (Info, "orphaned; this block is for an older parent set") + + case stateOrErr of + Right s' -> lf Info $ "updateForSolved: new block solved: " <> brief s' + Left (level, err) -> do + lf level $ "updateForSolved: block not solved: " <> err + now <- getCurrentTimeIntegral + lf Info $ orphandMsg now err + where + + orphandMsg now msg = JsonLog OrphanedBlock + { _orphanedParent = _solvedParentHash sw + , _orphanedPayloadHash = _solvedPayloadHash sw + , _orphanedDiscoveredAt = now + , _orphanedReason = msg + } -- -------------------------------------------------------------------------- -- -- MiningCoordination @@ -535,10 +362,12 @@ awaitAnyReady s = msum $ awaitWorkReady <$> _miningState s data MiningCoordination logger = MiningCoordination { _coordLogger :: !logger , _coordCutDb :: !CutDb - , _coordState :: !MiningState + , _coordState :: !(ChainMap (TVar (Maybe WorkState))) , _coordConf :: !CoordinationConfig , _coordPayloadCache :: !PayloadCaches } +instance HasChainwebVersion (MiningCoordination logger) where + _chainwebVersion = _chainwebVersion . _coordCutDb newMiningCoordination :: Logger logger @@ -583,7 +412,7 @@ runCoordination mr = do concurrentlies_ $ updateWork - : (updateCache <$> HM.toList caches) + : toList (imap updateCache caches) where lf :: LogFunctionText lf = logFunction $ _coordLogger mr @@ -608,7 +437,7 @@ runCoordination mr = do -- Update the payload cache with the latest payloads from the the provider -- - updateCache (cid, cache) = + updateCache cid cache = withProvider cid $ \provider -> runForever lf label $ do payloadStream provider @@ -624,27 +453,23 @@ runCoordination mr = do eventStream cdb caches & S.chain (\e -> lf Info $ "coordination event: " <> brief e) & S.mapM_ \case - CutEvent c -> updateForCut lf f caches state c - NewPayloadEvent c -> updateForPayload lf state c + CutEvent c -> updateForCut lf f state c + NewPayloadEvent _ -> return () -- There is a race with solved events. Does it matter? -- We could synchronize those by delivering those via an -- STM variable, too. + -- TODO: is there still? -- FIXME: this is probably more aggressive than needed initializeState = do lf Info $ "initialize mining state" - forConcurrently_ (HM.toList caches) $ \(cid, cache) -> do + forConcurrently_ (itoList caches) $ \(cid, cache) -> do lf Info $ "initialize mining state for chain " <> brief cid pld <- withProvider cid latestPayloadIO lf Info $ "got latest payload for chain " <> brief cid insertIO cache pld - curRh <- _workRankedHash <$> readTVarIO (_miningState state ^?! ix cid) - lf Info $ "got current rh for chain " <> brief cid - l <- awaitLatestIO cache curRh - lf Info $ "got new payload for chain " <> brief cid - updateForPayload lf state l curCut <- _cut $ cdb - updateForCut lf f caches state curCut + updateForCut lf f state curCut lf Info "done initializing mining state for all chains" -- | Note that this stream is lossy. It always delivers the latest available @@ -725,8 +550,8 @@ awaitEvent cdb caches c p = -- 3. some payload providers are deadlocked, or -- 4. some payload providers are very slow in producing new payloads. -- -randomWork :: LogFunction -> MiningState -> IO MiningWork -randomWork logFun state = do +randomWork :: LogFunction -> PayloadCaches -> ChainMap (TVar (Maybe WorkState)) -> IO MiningWork +randomWork logFun caches state = do -- Pick a random chain. -- @@ -740,7 +565,7 @@ randomWork logFun state = do -- that has work ready. We could prune the random range and search space, -- but at this, point the slight, temporary overhead seems acceptable. -- - n <- randomRIO (0, M.size m) + n <- randomRIO (0, length state) -- NOTE: it is tempting to search for a matching chain within a single STM -- transaction. However, that is problematic: the search restarts when the @@ -753,10 +578,14 @@ randomWork logFun state = do -- towards chains for which block are produced more quickly, but we think, -- that it is negligible. -- - let (s0, s1) = M.splitAt n m - go (M.toList s1 <> M.toList s0) + let (s0, s1) = splitAt n (itoList state) + go (s1 <> s0) where - m = _miningState state + awaitWorkReady :: ChainId -> TVar (Maybe WorkState) -> STM (WorkParents, NewPayload) + awaitWorkReady cid var = do + workState <- maybe retry return =<< readTVar var + payload <- awaitLatestPayloadForWorkStateSTM (caches ^?! atChain cid) workState + return (workStateParents workState, payload) go [] = do @@ -765,7 +594,7 @@ randomWork logFun state = do -- We shall check for the following conditions: -- -- 1. No chain is ready and we haven't received neither new cuts nor - -- new payloads. This is some sort deadlock in the system. + -- new payloads. This is some sort of deadlock in the system. -- -- 2. We get new cuts (i.e. chains become unblocked and blocked), but -- no chain becomes ready. Either payload providers are @@ -790,22 +619,33 @@ randomWork logFun state = do -- timeoutVar <- registerDelay (int staleMiningStateDelay) w <- atomically $ - Right <$> awaitAnyReady state <|> awaitTimeout timeoutVar + Right <$> msum (imap awaitWorkReady state) <|> awaitTimeout timeoutVar case w of Right (ps, npld) -> do ct <- BlockCreationTime <$> getCurrentTimeIntegral return $ newWork ct ps (_newPayloadBlockPayloadHash npld) Left e -> error e -- FIXME: throw a proper exception and log what is going on - go ((cid, var):t) = readTVarIO var >>= \case - WorkReady _ npld ps -> do - logFun @T.Text Debug $ "randomWork: picked chain " <> brief cid - ct <- BlockCreationTime <$> getCurrentTimeIntegral - return $ newWork ct ps (_newPayloadBlockPayloadHash npld) - e -> do - logFun @T.Text Info $ "randomWork: not ready for " <> brief cid - <> "; state: " <> brief e - go t + go ((cid, var):t) = do + readyCheck <- atomically $ + (do + workState <- readTVar var >>= \case + Just workState + | Nothing <- workStateSolved workState -> + -- ^ solved chains are not ready to mine on yet + return workState + _ -> retry + payload <- awaitLatestPayloadForWorkStateSTM (caches ^?! atChain cid) workState + return $ Just (workStateParents workState, payload) + ) <|> pure Nothing + case readyCheck of + Just (parents, payload) -> do + ct <- BlockCreationTime <$> getCurrentTimeIntegral + logFun @T.Text Debug $ "randomWork: picked chain " <> brief cid + return $ newWork ct parents (_newPayloadBlockPayloadHash payload) + Nothing -> do + logFun @T.Text Info $ "randomWork: not ready for " <> brief cid + go t awaitTimeout var = do @@ -834,7 +674,7 @@ work . Logger l => MiningCoordination l -> IO MiningWork -work mr = randomWork lf (_coordState mr) +work mr = randomWork lf (_coordPayloadCache mr) (_coordState mr) where lf :: LogFunction lf = logFunction $ _coordLogger mr @@ -883,89 +723,14 @@ solve => MiningCoordination l -> SolvedWork -> IO () -solve mr solved = (readTVarIO $ _coordState mr ^?! ixg cid) >>= \case - - WorkSolved{} -> - -- The solved work is already in the mining state - lf Info $ "solve: ignoring solution for mining state that was already solved before" - <> ". solved " <> brief solved - -- TODO check and log whether the solved state matches the solution - - WorkReady rh npld ps -> do - -- The solved work is still valid and the payload is available - - let pldh = _newPayloadBlockPayloadHash npld - - -- Check that the solved parents match the current mining state parents. - -- We can be lenient on the payloads, but the parents must match the - -- current cut. - if solvedId solved /= parentsId ps - then do - -- ignore solved work - lf Info $ "solve: solved work does not match the current mining state" - <> "; solved: " <> brief solved - <> "; current work parents: " <> brief ps - - -- check that we have the payload for the solution in the cache. - else lookupIO cache rh pldh >>= \case - - Nothing -> do - ch <- payloadHashesIO cache - lf Error $ "solve: no payload for " <> brief solved - <> "; cache key: " <> brief rh - <> "; cache content: " <> brief ch - throwM NoAsscociatedPayload - -- FIXME Do we really need to restart the coordinator? - - Just np -> do - c <- _cut cdb - now <- getCurrentTimeIntegral - let pld = _newPayloadEncodedPayloadData np - let pwo = _newPayloadEncodedPayloadOutputs np - - try (extend c pld pwo ps solved) >>= \case - - -- Publish CutHashes to CutDb and log success - Right (bh, Just ch) -> do - updateForSolved lf (_coordState mr) solved - publish cdb ch - logMinedBlock lf bh np - - -- Log Orphaned Block - Right (_, Nothing) -> do - let !p = lookupInCut c cid - lf Info $ orphandMsg now p solved "orphaned solution" - - -- Log failure and rethrow - Left e@(InvalidSolvedHeader msg) -> do - let !p = lookupInCut c cid - lf Info $ orphandMsg now p solved msg - throwM e - _ -> do - -- The solved work is orphaned. - lf Info $ "solve: ignoring outdated solution for. Mining state is not ready or solved" - <> ". solved: " <> brief solved - c <- _cut cdb - now <- getCurrentTimeIntegral - let !p = lookupInCut c cid - lf Info $ orphandMsg now p solved "orphaned solution" +solve mr solved = + updateForSolved lf (_coordCutDb mr) (_coordPayloadCache mr ^?! atChain cid) (_coordState mr ^?! atChain cid) solved where cid = _chainId solved - cdb = _coordCutDb mr - caches = _coordPayloadCache mr - cache = caches HM.! cid lf :: LogFunction lf = logFunction $ _coordLogger mr - orphandMsg now p s msg = JsonLog OrphanedBlock - { _orphanedParent = _solvedParentHash s - , _orphanedPayloadHash = _solvedPayloadHash s - , _orphanedBestOnCut = ObjectEncoded p - , _orphanedDiscoveredAt = now - , _orphanedReason = msg - } - logMinedBlock :: LogFunction -> BlockHeader diff --git a/src/Chainweb/Miner/PayloadCache.hs b/src/Chainweb/Miner/PayloadCache.hs index 09f731b85e..f1b8e51ca3 100644 --- a/src/Chainweb/Miner/PayloadCache.hs +++ b/src/Chainweb/Miner/PayloadCache.hs @@ -69,7 +69,6 @@ import Data.List qualified as L import Data.Map.Strict qualified as M import Numeric.Natural import Chainweb.BlockHeight -import Chainweb.BlockHeader import Chainweb.Parent -- | A new payload cache for a chain. diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index 2ed4db047e..b457d3b24c 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -30,7 +30,9 @@ import Chainweb.Logger import Chainweb.Miner.Config import Chainweb.Miner.Coordinator import Chainweb.Miner.Core +import Chainweb.Miner.PayloadCache import Chainweb.Miner.RestAPI (MiningApi) +import Chainweb.PayloadProvider import Chainweb.RestAPI.Utils import Chainweb.Utils import Chainweb.Utils.Serialization @@ -152,39 +154,37 @@ data WorkChange -- | This event triggers when the previous work got outdated -- awaitWorkChange - :: MiningState - -> ChainId + :: TVar (Maybe WorkState) + -> PayloadCache -> TVar Bool -- ^ Timer - -> TVar WorkState - -- ^ Previous Work State + -> TVar (Maybe (WorkState, NewPayload)) -> IO (Maybe WorkChange) -awaitWorkChange ms cid timer prevVar = go +awaitWorkChange var payloadCache timer prevVar = go where go = do -- await a change in the work state of the chain - r <- atomically $ readTVar timer >>= \case + atomically $ readTVar timer >>= \case True -> return Nothing False -> do prev <- readTVar prevVar - cur <- readTVar (ms ^?! ixg cid) - -- TODO: this guard is potentially somewhat expense. However, - -- ideally in most cases it should be possible to establish - -- equality by pointer equality. - guard (prev /= cur) - writeTVar prevVar cur - return $ Just (prev, cur) + cur <- readTVar var + (workChange, curPayload) <- case (prev, cur) of + (Just _, Nothing) -> + return (WorkOutdated, Nothing) + (old, Just newWorkState) -> do + pload <- awaitLatestPayloadForWorkStateSTM payloadCache newWorkState + case old of + Just (oldWorkState, oldPayload) + | oldWorkState == newWorkState -> do + guard (pload /= oldPayload) + return (WorkRefreshed, Just pload) + _ -> return (WorkOutdated, Just pload) + (Nothing, Nothing) -> + retry + writeTVar prevVar ((,) <$> cur <*> curPayload) - -- check result - case r of - Nothing -> return Nothing - Just (WorkReady prh ppld pps, WorkReady crh cpld cps) - | prh /= crh -> return $ Just WorkOutdated - | pps /= cps -> return $ Just WorkOutdated - | ppld /= cpld -> return $ Just WorkRefreshed - | otherwise -> go - Just (WorkReady{}, _) -> return $ Just WorkOutdated - _ -> go + return $ Just workChange -- | -- @@ -221,16 +221,21 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> do jitter <- randomRIO @Double (0.9, 1.1) timer <- registerDelay (round $ jitter * realToFrac timeout * 1_000_000) - curWork <- readTVarIO (_coordState mr ^?! ixg cid) + curWork <- atomically $ readTVar (_coordState mr ^?! ixg cid) >>= \case + Nothing -> return Nothing + Just workState -> do + pload <- awaitLatestPayloadForWorkStateSTM (_coordPayloadCache mr ^?! ixg cid) workState + return $ Just (workState, pload) + prevVar <- newTVarIO curWork eventSourceAppIO (go timer cid prevVar) req resp where timeout = _coordinationUpdateStreamTimeout $ _coordConf mr - go :: TVar Bool -> ChainId -> TVar WorkState -> IO ServerEvent + go :: TVar Bool -> ChainId -> TVar (Maybe (WorkState, NewPayload)) -> IO ServerEvent go timer cid prevVar = do - awaitWorkChange (_coordState mr) cid timer prevVar >>= \case + awaitWorkChange (_coordState mr ^?! ixg cid) (_coordPayloadCache mr ^?! ixg cid) timer prevVar >>= \case Nothing -> do logFunctionText logger Debug $ "sent close event to miner on chain " <> toText cid diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 67325cae10..5206b50014 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -643,6 +643,11 @@ data NewPayload = NewPayload } deriving (Show, Generic) +_newPayloadRankedParentHash :: NewPayload -> Parent RankedBlockHash +_newPayloadRankedParentHash np = Parent $ RankedBlockHash + (unwrapParent $ _newPayloadParentHeight np) + (unwrapParent $ _newPayloadParentHash np) + instance Eq NewPayload where (==) = on (==) $ \x -> -- move entropy to the beginning of the comparision to fail fast @@ -682,11 +687,6 @@ instance Hashable NewPayload where hashWithSalt s = hashWithSalt s . _newPayloadBlockPayloadHash {-# INLINE hashWithSalt #-} -_newPayloadRankedParentHash :: NewPayload -> Parent RankedBlockHash -_newPayloadRankedParentHash np = Parent $ RankedBlockHash - (unwrapParent $ _newPayloadParentHeight np) - (unwrapParent $ _newPayloadParentHash np) - instance HasChainwebVersion NewPayload where _chainwebVersion = _newPayloadChainwebVersion {-# INLINE _chainwebVersion #-} From e22ac66044f11d08a9e0bec4058b8d2d28e028d1 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 17:02:21 -0400 Subject: [PATCH 135/378] safer updateForSolved --- src/Chainweb/Miner/Coordinator.hs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 71ac67fc2a..c6cb9ccdf1 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -308,20 +308,22 @@ updateForSolved lf cdb payloadCache var sw = do lift (readTVar var) >>= \case Just workState | solvedId sw /= parentsId (workStateParents workState) -> - throwError (Info, "orphaned; this block is for an older parent set") + throwError (Just workState, Info, "orphaned; this block is for an older parent set") | Just _ <- workStateSolved workState -> - throwError (Debug, "this block has already been solved") + throwError (Just workState, Debug, "this block has already been solved") | otherwise -> do let newState = workState { workStateSolved = Just sw } + -- speculatively set the work state's solved work. if we have + -- trouble integrating this block, we will revert this after. lift (writeTVar var (Just newState)) return newState -- orphaned by parent disappearance Nothing -> - throwError (Info, "orphaned; this block is for a blocked chain") + throwError (Nothing, Info, "orphaned; this block is for a blocked chain") c <- lift $ _cut cdb lift (lookupIO payloadCache (fmap (view rankedBlockHash) $ _workParent $ workStateParents solvedWorkState) (_solvedPayloadHash sw)) >>= \case - Nothing -> throwError (Warn, "updateForSolved: missing payload in cache: " <> brief solvedWorkState) + Nothing -> throwError (Just solvedWorkState, Warn, "updateForSolved: missing payload in cache: " <> brief solvedWorkState) Just payload -> do let pld = _newPayloadEncodedPayloadData payload let pwo = _newPayloadEncodedPayloadOutputs payload @@ -336,12 +338,23 @@ updateForSolved lf cdb payloadCache var sw = do -- Log Orphaned Block (_, Nothing) -> do - throwError (Info, "orphaned; this block is for an older parent set") + throwError (Just solvedWorkState, Info, "orphaned; this block is for an older parent set") case stateOrErr of Right s' -> lf Info $ "updateForSolved: new block solved: " <> brief s' - Left (level, err) -> do + Left (staleMaybeWorkState, level, err) -> do lf level $ "updateForSolved: block not solved: " <> err + -- an error has occurred when trying to integrate this block; + -- reset the work state's solved work, if the parents have not + -- changed since the failure. + forM_ staleMaybeWorkState $ \staleWorkState -> atomically $ do + latestMaybeWorkState <- readTVar var + case latestMaybeWorkState of + Just latestWorkState + | parentsId (workStateParents staleWorkState) + == parentsId (workStateParents latestWorkState) + -> writeTVar var $ Just latestWorkState { workStateSolved = Nothing } + _ -> return () now <- getCurrentTimeIntegral lf Info $ orphandMsg now err where From 8b0c7274c2c3f466627b3ad2d5dfea14b212c3e1 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 16:41:15 -0400 Subject: [PATCH 136/378] add error back for InvalidSolvedHeader --- src/Chainweb/Miner/Coordinator.hs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index c6cb9ccdf1..c6cbd4aa7a 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -328,18 +328,21 @@ updateForSolved lf cdb payloadCache var sw = do let pld = _newPayloadEncodedPayloadData payload let pwo = _newPayloadEncodedPayloadOutputs payload - extend c pld pwo (workStateParents solvedWorkState) sw >>= \case + try (extend c pld pwo (workStateParents solvedWorkState) sw) >>= \case -- Publish CutHashes to CutDb and log success - (bh, Just ch) -> do + Right (bh, Just ch) -> do lift $ publish cdb ch lift $ logMinedBlock lf bh payload return solvedWorkState -- Log Orphaned Block - (_, Nothing) -> do + Right (_, Nothing) -> do throwError (Just solvedWorkState, Info, "orphaned; this block is for an older parent set") + Left (InvalidSolvedHeader msg) -> do + throwError (Just solvedWorkState, Warn, "invalid solved header: " <> msg) + case stateOrErr of Right s' -> lf Info $ "updateForSolved: new block solved: " <> brief s' Left (staleMaybeWorkState, level, err) -> do From 1b89f034c3af83a6806e019f45c523bb1a394bd2 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 17:17:53 -0400 Subject: [PATCH 137/378] reuse awaitWorkReady in other go case --- src/Chainweb/Miner/Coordinator.hs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index c6cbd4aa7a..8ee94c29c1 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -600,6 +600,7 @@ randomWork logFun caches state = do awaitWorkReady :: ChainId -> TVar (Maybe WorkState) -> STM (WorkParents, NewPayload) awaitWorkReady cid var = do workState <- maybe retry return =<< readTVar var + guard (isNothing $ workStateSolved workState) payload <- awaitLatestPayloadForWorkStateSTM (caches ^?! atChain cid) workState return (workStateParents workState, payload) @@ -644,16 +645,7 @@ randomWork logFun caches state = do go ((cid, var):t) = do readyCheck <- atomically $ - (do - workState <- readTVar var >>= \case - Just workState - | Nothing <- workStateSolved workState -> - -- ^ solved chains are not ready to mine on yet - return workState - _ -> retry - payload <- awaitLatestPayloadForWorkStateSTM (caches ^?! atChain cid) workState - return $ Just (workStateParents workState, payload) - ) <|> pure Nothing + Just <$> awaitWorkReady cid var <|> pure Nothing case readyCheck of Just (parents, payload) -> do ct <- BlockCreationTime <$> getCurrentTimeIntegral From 22a67d25bb0758dd2e7546afd89c2f10e48f81d9 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 17:29:36 -0400 Subject: [PATCH 138/378] Format line --- src/Chainweb/Miner/Coordinator.hs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 8ee94c29c1..61cb87a43b 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -732,7 +732,12 @@ solve -> SolvedWork -> IO () solve mr solved = - updateForSolved lf (_coordCutDb mr) (_coordPayloadCache mr ^?! atChain cid) (_coordState mr ^?! atChain cid) solved + updateForSolved + lf + (_coordCutDb mr) + (_coordPayloadCache mr ^?! atChain cid) + (_coordState mr ^?! atChain cid) + solved where cid = _chainId solved From 13ff7e0f274524ebdc3e47369f579a4ad61e11e4 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 17:30:59 -0400 Subject: [PATCH 139/378] s/WorkState/ParentState Change-Id: Id000000075e199b0cd2fa975ec515a46a9f57f99 Change-Id: Id0000000ac1cd169a3bf5e1282ab141249e931a2 --- src/Chainweb/Miner/Coordinator.hs | 100 +++++++++++++-------------- src/Chainweb/Miner/RestAPI/Server.hs | 24 +++---- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 61cb87a43b..5051f8452d 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -41,9 +41,9 @@ module Chainweb.Miner.Coordinator -- * Internal --- * WorkState -, WorkState(..) -, awaitLatestPayloadForWorkStateSTM +-- * ParentState +, ParentState(..) +, awaitLatestPayloadForParentStateSTM -- ** Payload Caches , type PayloadCaches @@ -230,7 +230,7 @@ awaitPayloadsNext caches c prevs = msum (awaitChain <$> itoList xs) -- Solved -> Solved [label=payload]; -- @ -- -data WorkState = WorkState +data ParentState = ParentState { workStateParents :: !WorkParents -- ^ Invariant: The work parents must match the ranked block hash. , workStateSolved :: !(Maybe SolvedWork) @@ -240,20 +240,20 @@ data WorkState = WorkState } deriving stock (Show, Eq, Generic) -awaitLatestPayloadForWorkStateSTM :: PayloadCache -> WorkState -> STM NewPayload -awaitLatestPayloadForWorkStateSTM payloadCache workState = do - let parent = fmap (view rankedBlockHash) $ _workParent' $ workStateParents workState +awaitLatestPayloadForParentStateSTM :: PayloadCache -> ParentState -> STM NewPayload +awaitLatestPayloadForParentStateSTM payloadCache parentState = do + let parent = fmap (view rankedBlockHash) $ _workParent' $ workStateParents parentState awaitLatestSTM payloadCache parent -instance Brief WorkState where - brief WorkState{..} = - "WorkState:" <> brief workStateParents +instance Brief ParentState where + brief ParentState{..} = + "ParentState:" <> brief workStateParents <> ":solved=" <> brief workStateSolved -- -------------------------------------------------------------------------- -- -- Mining State -newMiningState :: Cut -> IO (ChainMap (TVar (Maybe WorkState))) +newMiningState :: Cut -> IO (ChainMap (TVar (Maybe ParentState))) newMiningState c = do states <- forM cids $ \cid -> do var <- newTVarIO Nothing @@ -277,7 +277,7 @@ newMiningState c = do updateForCut :: LogFunctionText -> (ChainValue BlockHash -> IO BlockHeader) - -> (ChainMap (TVar (Maybe WorkState))) + -> (ChainMap (TVar (Maybe ParentState))) -> Cut -> IO () updateForCut lf hdb ms c = do @@ -287,32 +287,32 @@ updateForCut lf hdb ms c = do forChain cid var = do maybeNewParents <- workParents hdb c cid atomically $ do - maybeOldWorkState <- readTVar var - case (maybeOldWorkState, maybeNewParents) of + maybeOldParentState <- readTVar var + case (maybeOldParentState, maybeNewParents) of (_, Nothing) -> writeTVar var Nothing - (Just oldWorkState, Just newParents) - | workStateParents oldWorkState == newParents + (Just oldParentState, Just newParents) + | workStateParents oldParentState == newParents -> return () (_, Just newParents) -> writeTVar var $ Just - WorkState + ParentState { workStateParents = newParents , workStateSolved = Nothing } -updateForSolved :: LogFunction -> CutDb -> PayloadCache -> TVar (Maybe WorkState) -> SolvedWork -> IO () +updateForSolved :: LogFunction -> CutDb -> PayloadCache -> TVar (Maybe ParentState) -> SolvedWork -> IO () updateForSolved lf cdb payloadCache var sw = do stateOrErr <- runExceptT $ do - solvedWorkState <- mapExceptT atomically $ do + solvedParentState <- mapExceptT atomically $ do lift (readTVar var) >>= \case - Just workState - | solvedId sw /= parentsId (workStateParents workState) -> - throwError (Just workState, Info, "orphaned; this block is for an older parent set") - | Just _ <- workStateSolved workState -> - throwError (Just workState, Debug, "this block has already been solved") + Just parentState + | solvedId sw /= parentsId (workStateParents parentState) -> + throwError (Just parentState, Info, "orphaned; this block is for an older parent set") + | Just _ <- workStateSolved parentState -> + throwError (Just parentState, Debug, "this block has already been solved") | otherwise -> do - let newState = workState { workStateSolved = Just sw } + let newState = parentState { workStateSolved = Just sw } -- speculatively set the work state's solved work. if we have -- trouble integrating this block, we will revert this after. lift (writeTVar var (Just newState)) @@ -322,41 +322,41 @@ updateForSolved lf cdb payloadCache var sw = do throwError (Nothing, Info, "orphaned; this block is for a blocked chain") c <- lift $ _cut cdb - lift (lookupIO payloadCache (fmap (view rankedBlockHash) $ _workParent $ workStateParents solvedWorkState) (_solvedPayloadHash sw)) >>= \case - Nothing -> throwError (Just solvedWorkState, Warn, "updateForSolved: missing payload in cache: " <> brief solvedWorkState) + lift (lookupIO payloadCache (fmap (view rankedBlockHash) $ _workParent $ workStateParents solvedParentState) (_solvedPayloadHash sw)) >>= \case + Nothing -> throwError (Just solvedParentState, Warn, "updateForSolved: missing payload in cache: " <> brief solvedParentState) Just payload -> do let pld = _newPayloadEncodedPayloadData payload let pwo = _newPayloadEncodedPayloadOutputs payload - try (extend c pld pwo (workStateParents solvedWorkState) sw) >>= \case + try (extend c pld pwo (workStateParents solvedParentState) sw) >>= \case -- Publish CutHashes to CutDb and log success Right (bh, Just ch) -> do lift $ publish cdb ch lift $ logMinedBlock lf bh payload - return solvedWorkState + return solvedParentState -- Log Orphaned Block Right (_, Nothing) -> do - throwError (Just solvedWorkState, Info, "orphaned; this block is for an older parent set") + throwError (Just solvedParentState, Info, "orphaned; this block is for an older parent set") Left (InvalidSolvedHeader msg) -> do - throwError (Just solvedWorkState, Warn, "invalid solved header: " <> msg) + throwError (Just solvedParentState, Warn, "invalid solved header: " <> msg) case stateOrErr of Right s' -> lf Info $ "updateForSolved: new block solved: " <> brief s' - Left (staleMaybeWorkState, level, err) -> do + Left (staleMaybeParentState, level, err) -> do lf level $ "updateForSolved: block not solved: " <> err -- an error has occurred when trying to integrate this block; -- reset the work state's solved work, if the parents have not -- changed since the failure. - forM_ staleMaybeWorkState $ \staleWorkState -> atomically $ do - latestMaybeWorkState <- readTVar var - case latestMaybeWorkState of - Just latestWorkState - | parentsId (workStateParents staleWorkState) - == parentsId (workStateParents latestWorkState) - -> writeTVar var $ Just latestWorkState { workStateSolved = Nothing } + forM_ staleMaybeParentState $ \staleParentState -> atomically $ do + latestMaybeParentState <- readTVar var + case latestMaybeParentState of + Just latestParentState + | parentsId (workStateParents staleParentState) + == parentsId (workStateParents latestParentState) + -> writeTVar var $ Just latestParentState { workStateSolved = Nothing } _ -> return () now <- getCurrentTimeIntegral lf Info $ orphandMsg now err @@ -378,7 +378,7 @@ updateForSolved lf cdb payloadCache var sw = do data MiningCoordination logger = MiningCoordination { _coordLogger :: !logger , _coordCutDb :: !CutDb - , _coordState :: !(ChainMap (TVar (Maybe WorkState))) + , _coordParentState :: !(ChainMap (TVar (Maybe ParentState))) , _coordConf :: !CoordinationConfig , _coordPayloadCache :: !PayloadCaches } @@ -398,7 +398,7 @@ newMiningCoordination logger conf cdb = do return $ MiningCoordination { _coordLogger = logger , _coordCutDb = cdb - , _coordState = state + , _coordParentState = state , _coordConf = conf , _coordPayloadCache = caches } @@ -434,7 +434,7 @@ runCoordination mr = do lf = logFunction $ _coordLogger mr caches = _coordPayloadCache mr - state = _coordState mr + state = _coordParentState mr hdb :: WebBlockHeaderDb hdb = view cutDbWebBlockHeaderDb (_coordCutDb mr) @@ -566,7 +566,7 @@ awaitEvent cdb caches c p = -- 3. some payload providers are deadlocked, or -- 4. some payload providers are very slow in producing new payloads. -- -randomWork :: LogFunction -> PayloadCaches -> ChainMap (TVar (Maybe WorkState)) -> IO MiningWork +randomWork :: LogFunction -> PayloadCaches -> ChainMap (TVar (Maybe ParentState)) -> IO MiningWork randomWork logFun caches state = do -- Pick a random chain. @@ -597,12 +597,12 @@ randomWork logFun caches state = do let (s0, s1) = splitAt n (itoList state) go (s1 <> s0) where - awaitWorkReady :: ChainId -> TVar (Maybe WorkState) -> STM (WorkParents, NewPayload) + awaitWorkReady :: ChainId -> TVar (Maybe ParentState) -> STM (WorkParents, NewPayload) awaitWorkReady cid var = do - workState <- maybe retry return =<< readTVar var - guard (isNothing $ workStateSolved workState) - payload <- awaitLatestPayloadForWorkStateSTM (caches ^?! atChain cid) workState - return (workStateParents workState, payload) + parentState <- maybe retry return =<< readTVar var + guard (isNothing $ workStateSolved parentState) + payload <- awaitLatestPayloadForParentStateSTM (caches ^?! atChain cid) parentState + return (workStateParents parentState, payload) go [] = do @@ -682,7 +682,7 @@ work . Logger l => MiningCoordination l -> IO MiningWork -work mr = randomWork lf (_coordPayloadCache mr) (_coordState mr) +work mr = randomWork lf (_coordPayloadCache mr) (_coordParentState mr) where lf :: LogFunction lf = logFunction $ _coordLogger mr @@ -736,7 +736,7 @@ solve mr solved = lf (_coordCutDb mr) (_coordPayloadCache mr ^?! atChain cid) - (_coordState mr ^?! atChain cid) + (_coordParentState mr ^?! atChain cid) solved where cid = _chainId solved diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index b457d3b24c..cba4b41a88 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -154,11 +154,11 @@ data WorkChange -- | This event triggers when the previous work got outdated -- awaitWorkChange - :: TVar (Maybe WorkState) + :: TVar (Maybe ParentState) -> PayloadCache -> TVar Bool -- ^ Timer - -> TVar (Maybe (WorkState, NewPayload)) + -> TVar (Maybe (ParentState, NewPayload)) -> IO (Maybe WorkChange) awaitWorkChange var payloadCache timer prevVar = go where @@ -172,11 +172,11 @@ awaitWorkChange var payloadCache timer prevVar = go (workChange, curPayload) <- case (prev, cur) of (Just _, Nothing) -> return (WorkOutdated, Nothing) - (old, Just newWorkState) -> do - pload <- awaitLatestPayloadForWorkStateSTM payloadCache newWorkState + (old, Just newParentState) -> do + pload <- awaitLatestPayloadForParentStateSTM payloadCache newParentState case old of - Just (oldWorkState, oldPayload) - | oldWorkState == newWorkState -> do + Just (oldParentState, oldPayload) + | oldParentState == newParentState -> do guard (pload /= oldPayload) return (WorkRefreshed, Just pload) _ -> return (WorkOutdated, Just pload) @@ -221,11 +221,11 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> do jitter <- randomRIO @Double (0.9, 1.1) timer <- registerDelay (round $ jitter * realToFrac timeout * 1_000_000) - curWork <- atomically $ readTVar (_coordState mr ^?! ixg cid) >>= \case + curWork <- atomically $ readTVar (_coordParentState mr ^?! ixg cid) >>= \case Nothing -> return Nothing - Just workState -> do - pload <- awaitLatestPayloadForWorkStateSTM (_coordPayloadCache mr ^?! ixg cid) workState - return $ Just (workState, pload) + Just parentState -> do + pload <- awaitLatestPayloadForParentStateSTM (_coordPayloadCache mr ^?! ixg cid) parentState + return $ Just (parentState, pload) prevVar <- newTVarIO curWork @@ -233,9 +233,9 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> do where timeout = _coordinationUpdateStreamTimeout $ _coordConf mr - go :: TVar Bool -> ChainId -> TVar (Maybe (WorkState, NewPayload)) -> IO ServerEvent + go :: TVar Bool -> ChainId -> TVar (Maybe (ParentState, NewPayload)) -> IO ServerEvent go timer cid prevVar = do - awaitWorkChange (_coordState mr ^?! ixg cid) (_coordPayloadCache mr ^?! ixg cid) timer prevVar >>= \case + awaitWorkChange (_coordParentState mr ^?! ixg cid) (_coordPayloadCache mr ^?! ixg cid) timer prevVar >>= \case Nothing -> do logFunctionText logger Debug $ "sent close event to miner on chain " <> toText cid From 5f044b0dbc205e210e29e7e2f6dc12ddcc795220 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 17:43:45 -0400 Subject: [PATCH 140/378] Fix latency bug in awaitWorkChange --- src/Chainweb/Miner/RestAPI/Server.hs | 42 ++++++++++++++++++---------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index cba4b41a88..5a8a24bd50 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -158,7 +158,7 @@ awaitWorkChange -> PayloadCache -> TVar Bool -- ^ Timer - -> TVar (Maybe (ParentState, NewPayload)) + -> TVar (Maybe ParentState, Maybe NewPayload) -> IO (Maybe WorkChange) awaitWorkChange var payloadCache timer prevVar = go where @@ -170,19 +170,31 @@ awaitWorkChange var payloadCache timer prevVar = go prev <- readTVar prevVar cur <- readTVar var (workChange, curPayload) <- case (prev, cur) of - (Just _, Nothing) -> + ((Just _, _), Nothing) -> + -- the parent state has been vacated; + -- there are no longer parents to mine on, + -- so stop mining return (WorkOutdated, Nothing) - (old, Just newParentState) -> do - pload <- awaitLatestPayloadForParentStateSTM payloadCache newParentState - case old of - Just (oldParentState, oldPayload) - | oldParentState == newParentState -> do - guard (pload /= oldPayload) - return (WorkRefreshed, Just pload) - _ -> return (WorkOutdated, Just pload) - (Nothing, Nothing) -> + ((Nothing, _), Just _) -> + -- the parent state has been populated; + -- there are now parents to mine on, + -- so start mining + return (WorkOutdated, Nothing) + ((Nothing, _), Nothing) -> + -- the parent state is still empty retry - writeTVar prevVar ((,) <$> cur <*> curPayload) + ((old, oldMaybePayload), Just newParentState) -> do + case (old, oldMaybePayload) of + (Just oldParentState, oldPayload) + | oldParentState == newParentState -> do + -- the parent state remains the same, + -- so we await a refreshed payload + newPayload <- awaitLatestPayloadForParentStateSTM payloadCache newParentState + guard (Just newPayload /= oldPayload) + return (WorkRefreshed, Just newPayload) + | otherwise -> + return (WorkOutdated, Nothing) + writeTVar prevVar (cur, curPayload) return $ Just workChange @@ -222,10 +234,10 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> do timer <- registerDelay (round $ jitter * realToFrac timeout * 1_000_000) curWork <- atomically $ readTVar (_coordParentState mr ^?! ixg cid) >>= \case - Nothing -> return Nothing + Nothing -> return (Nothing, Nothing) Just parentState -> do pload <- awaitLatestPayloadForParentStateSTM (_coordPayloadCache mr ^?! ixg cid) parentState - return $ Just (parentState, pload) + return (Just parentState, Just pload) prevVar <- newTVarIO curWork @@ -233,7 +245,7 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> do where timeout = _coordinationUpdateStreamTimeout $ _coordConf mr - go :: TVar Bool -> ChainId -> TVar (Maybe (ParentState, NewPayload)) -> IO ServerEvent + go :: TVar Bool -> ChainId -> TVar (Maybe ParentState, Maybe NewPayload) -> IO ServerEvent go timer cid prevVar = do awaitWorkChange (_coordParentState mr ^?! ixg cid) (_coordPayloadCache mr ^?! ixg cid) timer prevVar >>= \case Nothing -> do From 9e4b75314efd7597843cf01faa9a8197f7c0fbd3 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 17:59:04 -0400 Subject: [PATCH 141/378] Rename variable --- src/Chainweb/Miner/RestAPI/Server.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index 5a8a24bd50..cdf18e5a32 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -183,8 +183,8 @@ awaitWorkChange var payloadCache timer prevVar = go ((Nothing, _), Nothing) -> -- the parent state is still empty retry - ((old, oldMaybePayload), Just newParentState) -> do - case (old, oldMaybePayload) of + ((oldMaybeParentState, oldMaybePayload), Just newParentState) -> do + case (oldMaybeParentState, oldMaybePayload) of (Just oldParentState, oldPayload) | oldParentState == newParentState -> do -- the parent state remains the same, From 0e77a0b8fd5b4fb2700e1f114b643fe60ff62c3c Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 6 May 2025 01:11:16 -0700 Subject: [PATCH 142/378] Reenable P2P networking for payloads --- src/Chainweb/Chainweb.hs | 56 +++++++++++++++++++++---- src/Chainweb/Chainweb/ChainResources.hs | 16 +++++-- src/Chainweb/Mempool/RestAPI/Server.hs | 12 ++++-- src/Chainweb/RestAPI.hs | 34 ++++++++------- 4 files changed, 90 insertions(+), 28 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index baf7014d1e..c04bca1fe5 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -691,10 +691,44 @@ runChainweb cw nowServing = do logg :: LogFunctionText logg = logFunctionText $ _chainwebLogger cw - -- TODO use the peerDbs from the respective chains - -- (even though those are currently all the same) - -- payloadP2pPeersToServe :: [(NetworkId, PeerDb)] - -- payloadP2pPeersToServe = (\(i, _) -> (ChainNetwork i, peerDb)) <$> chains + -- chains + chains :: [(ChainId, ChainResources logger)] + chains = itoList (_chainwebChains cw) + + chainVals :: [ChainResources logger] + chainVals = map snd chains + + -- collect server resources + proj :: forall a . (ChainResources logger -> a) -> [(ChainId, a)] + proj f = chains & mapped . _2 %~ f + + peerDb = _peerResDb (_chainwebPeer cw) + + -- FIXME export the SomeServer instead of DBs? + -- I.e. the handler would be created in the chain resource. + -- Similar to how it is done with the payload provider APIs. + -- + -- chainDbsToServe :: [(ChainId, BlockHeaderDb)] + -- chainDbsToServe = proj _chainResBlockHeaderDb + chainDbsToServe :: ChainMap BlockHeaderDb + chainDbsToServe = _chainResBlockHeaderDb <$> _chainwebChains cw + + mempoolsToServe :: ChainMap (Mempool.MempoolBackend Pact.Transaction) + -- mempoolsToServe = proj _chainResMempool + mempoolsToServe = mempty + + pactDbsToServe :: [(ChainId, PactServerData logger tbl)] + -- pactDbsToServe = _chainwebPactData cw + pactDbsToServe = [] + + -- As long as all Pact payload providers live within the consensus node, it + -- probably makes sense to serve the P2P network for mempools through the + -- consensus node API. + -- TODO: only include the mempools for enabled payload providers + -- + memP2pPeersToServe :: [(NetworkId, PeerDb)] + -- memP2pToServe = (\(i, _) -> (MempoolNetwork i, peerDb)) <$> chains + memP2pPeersToServe = [] loggServerError msg (Just r) e = "HTTP server error (" <> msg <> "): " <> sshow e <> ". Request: " <> sshow r @@ -732,8 +766,14 @@ runChainweb cw nowServing = do chainwebServerDbs :: ChainwebServerDbs chainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Just cutDb - , _chainwebServerBlockHeaderDbs = _chainResBlockHeaderDb <$> _chainwebChains cw - , _chainwebServerPeerDbs = [(CutNetwork, cutPeerDb)] + , _chainwebServerBlockHeaderDbs = chainDbsToServe + , _chainwebServerMempools = mempoolsToServe + -- , _chainwebServerPayloads = payloadsToServeOnP2pApi chains + , _chainwebServerPayloads = ChainMap $ HM.fromList $ payloadsToServeOnP2pApi chains + , _chainwebServerPeerDbs + = (CutNetwork, cutPeerDb) + : memP2pPeersToServe + <> payloadP2pPeersToServe chains } serve :: Middleware -> IO () @@ -815,7 +855,9 @@ runChainweb cw nowServing = do (_chainwebVersion cw) ChainwebServerDbs { _chainwebServerCutDb = Just cutDb - , _chainwebServerBlockHeaderDbs = _chainResBlockHeaderDb <$> _chainwebChains cw + , _chainwebServerBlockHeaderDbs = chainDbsToServe + , _chainwebServerMempools = mempoolsToServe + , _chainwebServerPayloads = ChainMap $ HM.fromList $ payloadsToServeOnServiceApi chains -- We do not want to serve peer APIs on the service API. -- If at all we could serve the GET endpoints. diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index d39520eb7f..ba9a148372 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -35,6 +35,7 @@ module Chainweb.Chainweb.ChainResources , withChainResources , payloadsToServeOnP2pApi , payloadsToServeOnServiceApi +, payloadP2pPeersToServe , payloadProvidersForAllChains , runP2pNodesOfAllChains @@ -384,7 +385,7 @@ withChainResources logger v c rdb mgr _pactDbDir p2pConf myInfo peerDb rewindLim & setComponent "payload-provider" & addLabel ("provider", toText providerType) --- | Return P2P Payload Servers for all chains +-- | Return P2P Payload Servers for all enabled payload providers -- payloadsToServeOnP2pApi :: [(ChainId, ChainResources logger)] @@ -393,7 +394,7 @@ payloadsToServeOnP2pApi chains = catMaybes $ mapM (fmap _payloadResP2pServer . _chainResP2pApiResources) <$> chains --- | Return Service API Payload Servers for all chains +-- | Return Service API Payload Servers for all enabled payload providers -- payloadsToServeOnServiceApi :: [(ChainId, ChainResources logger)] @@ -402,7 +403,16 @@ payloadsToServeOnServiceApi chains = catMaybes $ mapM (fmap _payloadResServiceServer . _chainResServiceApiResources) <$> chains --- | Return the payload providers for all chains +payloadP2pPeersToServe + :: [(ChainId, ChainResources logger)] + -> [(NetworkId, PeerDb)] +payloadP2pPeersToServe chains = + catMaybes + $ fmap sequence + $ (\(cid, x) -> (ChainNetwork cid, _payloadResPeerDb <$> _chainResP2pApiResources x)) + <$> chains + +-- | Return the configured payload providers for all chains -- payloadProvidersForAllChains :: ChainMap (ChainResources logger) diff --git a/src/Chainweb/Mempool/RestAPI/Server.hs b/src/Chainweb/Mempool/RestAPI/Server.hs index 5092c25fc9..022583558b 100644 --- a/src/Chainweb/Mempool/RestAPI/Server.hs +++ b/src/Chainweb/Mempool/RestAPI/Server.hs @@ -117,15 +117,19 @@ someMempoolServer someMempoolServer ver (SomeMempool (mempool :: Mempool_ v c t)) = SomeServer (Proxy @(MempoolApi v c)) (mempoolServer ver mempool) - someMempoolServers :: (Show t) - => ChainwebVersion -> ChainMap (MempoolBackend t) -> SomeServer + => ChainwebVersion + -> ChainMap (MempoolBackend t) + -> SomeServer someMempoolServers v = ifoldMap (\cid mempool -> someMempoolServer v (someMempoolVal v cid mempool)) - -mempoolServer :: Show t => ChainwebVersion -> Mempool_ v c t -> Server (MempoolApi v c) +mempoolServer + :: Show t + => ChainwebVersion + -> Mempool_ v c t + -> Server (MempoolApi v c) mempoolServer _v (Mempool_ mempool) = insertHandler mempool :<|> memberHandler mempool diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index c1ec66c5b7..e3f61a279b 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -85,6 +85,7 @@ import Chainweb.Mempool.Mempool (MempoolBackend) import qualified Chainweb.Mempool.RestAPI.Server as Mempool import qualified Chainweb.Miner.RestAPI.Server as Mining -- import qualified Chainweb.Pact.RestAPI.Server as PactAPI +import qualified Chainweb.Pact.Transaction as Pact import Chainweb.Payload.RestAPI import Chainweb.RestAPI.Backup import Chainweb.RestAPI.Config @@ -137,6 +138,8 @@ serveSocketTls settings certChain key = runTLSSocket tlsSettings settings data ChainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb :: !(Maybe CutDb) , _chainwebServerBlockHeaderDbs :: !(ChainMap BlockHeaderDb) + , _chainwebServerMempools :: !(ChainMap (MempoolBackend Pact.Transaction)) + , _chainwebServerPayloads :: !(ChainMap SomeServer) , _chainwebServerPeerDbs :: ![(NetworkId, PeerDb)] } deriving (Generic) @@ -145,7 +148,9 @@ emptyChainwebServerDbs :: ChainwebServerDbs emptyChainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Nothing , _chainwebServerBlockHeaderDbs = mempty - , _chainwebServerPeerDbs = mempty + , _chainwebServerMempools = mempty + , _chainwebServerPayloads = mempty + , _chainwebServerPeerDbs = [] } -- -------------------------------------------------------------------------- -- @@ -200,17 +205,17 @@ someChainwebServer -> SomeServer someChainwebServer config dbs = maybe mempty (someCutServer v cutPeerDb) cuts - -- <> fold payloads + <> fold payloads <> someP2pBlockHeaderDbServers v blocks - -- <> Mempool.someMempoolServers v mempools + <> Mempool.someMempoolServers v mempools <> someP2pServers v peers <> someGetConfigServer config where - -- payloads = _chainwebServerPayloads dbs + payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs cuts = _chainwebServerCutDb dbs peers = _chainwebServerPeerDbs dbs - -- mempools = _chainwebServerMempools dbs + mempools = _chainwebServerMempools dbs cutPeerDb = fromJuste $ lookup CutNetwork peers v = _configChainwebVersion config @@ -224,18 +229,18 @@ someChainwebServerWithHashesAndSpvApi -> SomeServer someChainwebServerWithHashesAndSpvApi config dbs = maybe mempty (someCutServer v cutPeerDb) cuts - -- <> foldMap snd payloads + <> fold payloads <> someBlockHeaderDbServers v blocks - -- <> Mempool.someMempoolServers v mempools + <> Mempool.someMempoolServers v mempools <> someP2pServers v peers <> someGetConfigServer config <> maybe mempty (someSpvServers v) cuts where - -- payloads = _chainwebServerPayloads dbs + payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs cuts = _chainwebServerCutDb dbs peers = _chainwebServerPeerDbs dbs - -- mempools = _chainwebServerMempools dbs + mempools = _chainwebServerMempools dbs cutPeerDb = fromJuste $ lookup CutNetwork peers v = _configChainwebVersion config @@ -343,11 +348,12 @@ someServiceApiServer v dbs mr (HeaderStream hs) backupEnv pbl = -- GET Cut, Payload, and Headers endpoints <> maybe mempty (someCutGetServer v) cuts - -- <> mconcat (snd <$> payloads) + <> fold payloads <> someBlockHeaderDbServers v blocks <> maybe mempty (someBlockStreamServer v) (bool Nothing cuts hs) where cuts = _chainwebServerCutDb dbs + payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs serviceApiApplication @@ -359,10 +365,10 @@ serviceApiApplication -> Maybe (BackupEnv logger) -> PayloadBatchLimit -> Application -serviceApiApplication v dbs mr hs be pbl +serviceApiApplication v dbs mr hs benv pbl = chainwebServiceMiddlewares . someServerApplication - $ someServiceApiServer v dbs mr hs be pbl + $ someServiceApiServer v dbs mr hs benv pbl serveServiceApiSocket :: Logger logger @@ -376,5 +382,5 @@ serveServiceApiSocket -> PayloadBatchLimit -> Middleware -> IO () -serveServiceApiSocket s sock v dbs mr hs be pbl m = - runSettingsSocket s sock $ m $ serviceApiApplication v dbs mr hs be pbl +serveServiceApiSocket s sock v dbs mr hs benv pbl m = + runSettingsSocket s sock $ m $ serviceApiApplication v dbs mr hs benv pbl From 4fedd2e5cb58fbf2d4dc8574bce37540887e808f Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 17:30:59 -0400 Subject: [PATCH 143/378] Finish s/WorkState/ParentState in fields --- src/Chainweb/Miner/Coordinator.hs | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 5051f8452d..302679831c 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -231,9 +231,9 @@ awaitPayloadsNext caches c prevs = msum (awaitChain <$> itoList xs) -- @ -- data ParentState = ParentState - { workStateParents :: !WorkParents + { parentStateParents :: !WorkParents -- ^ Invariant: The work parents must match the ranked block hash. - , workStateSolved :: !(Maybe SolvedWork) + , parentStateSolved :: !(Maybe SolvedWork) -- ^ A block with these parents has already been solved and submitted to the -- cut pipeline - we don't want to mine it again. So we wait until the -- current cut is updated with a new parent header for this chain. @@ -242,13 +242,13 @@ data ParentState = ParentState awaitLatestPayloadForParentStateSTM :: PayloadCache -> ParentState -> STM NewPayload awaitLatestPayloadForParentStateSTM payloadCache parentState = do - let parent = fmap (view rankedBlockHash) $ _workParent' $ workStateParents parentState + let parent = fmap (view rankedBlockHash) $ _workParent' $ parentStateParents parentState awaitLatestSTM payloadCache parent instance Brief ParentState where brief ParentState{..} = - "ParentState:" <> brief workStateParents - <> ":solved=" <> brief workStateSolved + "ParentState:" <> brief parentStateParents + <> ":solved=" <> brief parentStateSolved -- -------------------------------------------------------------------------- -- -- Mining State @@ -292,13 +292,13 @@ updateForCut lf hdb ms c = do (_, Nothing) -> writeTVar var Nothing (Just oldParentState, Just newParents) - | workStateParents oldParentState == newParents + | parentStateParents oldParentState == newParents -> return () (_, Just newParents) -> writeTVar var $ Just ParentState - { workStateParents = newParents - , workStateSolved = Nothing + { parentStateParents = newParents + , parentStateSolved = Nothing } updateForSolved :: LogFunction -> CutDb -> PayloadCache -> TVar (Maybe ParentState) -> SolvedWork -> IO () @@ -307,12 +307,12 @@ updateForSolved lf cdb payloadCache var sw = do solvedParentState <- mapExceptT atomically $ do lift (readTVar var) >>= \case Just parentState - | solvedId sw /= parentsId (workStateParents parentState) -> + | solvedId sw /= parentsId (parentStateParents parentState) -> throwError (Just parentState, Info, "orphaned; this block is for an older parent set") - | Just _ <- workStateSolved parentState -> + | Just _ <- parentStateSolved parentState -> throwError (Just parentState, Debug, "this block has already been solved") | otherwise -> do - let newState = parentState { workStateSolved = Just sw } + let newState = parentState { parentStateSolved = Just sw } -- speculatively set the work state's solved work. if we have -- trouble integrating this block, we will revert this after. lift (writeTVar var (Just newState)) @@ -322,13 +322,13 @@ updateForSolved lf cdb payloadCache var sw = do throwError (Nothing, Info, "orphaned; this block is for a blocked chain") c <- lift $ _cut cdb - lift (lookupIO payloadCache (fmap (view rankedBlockHash) $ _workParent $ workStateParents solvedParentState) (_solvedPayloadHash sw)) >>= \case + lift (lookupIO payloadCache (fmap (view rankedBlockHash) $ _workParent $ parentStateParents solvedParentState) (_solvedPayloadHash sw)) >>= \case Nothing -> throwError (Just solvedParentState, Warn, "updateForSolved: missing payload in cache: " <> brief solvedParentState) Just payload -> do let pld = _newPayloadEncodedPayloadData payload let pwo = _newPayloadEncodedPayloadOutputs payload - try (extend c pld pwo (workStateParents solvedParentState) sw) >>= \case + try (extend c pld pwo (parentStateParents solvedParentState) sw) >>= \case -- Publish CutHashes to CutDb and log success Right (bh, Just ch) -> do @@ -354,9 +354,9 @@ updateForSolved lf cdb payloadCache var sw = do latestMaybeParentState <- readTVar var case latestMaybeParentState of Just latestParentState - | parentsId (workStateParents staleParentState) - == parentsId (workStateParents latestParentState) - -> writeTVar var $ Just latestParentState { workStateSolved = Nothing } + | parentsId (parentStateParents staleParentState) + == parentsId (parentStateParents latestParentState) + -> writeTVar var $ Just latestParentState { parentStateSolved = Nothing } _ -> return () now <- getCurrentTimeIntegral lf Info $ orphandMsg now err @@ -600,9 +600,9 @@ randomWork logFun caches state = do awaitWorkReady :: ChainId -> TVar (Maybe ParentState) -> STM (WorkParents, NewPayload) awaitWorkReady cid var = do parentState <- maybe retry return =<< readTVar var - guard (isNothing $ workStateSolved parentState) + guard (isNothing $ parentStateSolved parentState) payload <- awaitLatestPayloadForParentStateSTM (caches ^?! atChain cid) parentState - return (workStateParents parentState, payload) + return (parentStateParents parentState, payload) go [] = do From be5e491fc357754efc62d0b2b93b501b94a7edbe Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 6 May 2025 23:01:58 -0700 Subject: [PATCH 144/378] update GHC and cabal versions and freeze file --- .github/workflows/applications.yml | 18 +++++++++++------- .github/workflows/macos.yaml | 2 +- cabal.project.freeze | 24 ++++++++++++------------ 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index 93956efa92..39559686c5 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -135,13 +135,13 @@ jobs: MATRIX="$(jq -c '.' < Date: Mon, 5 May 2025 21:20:56 -0400 Subject: [PATCH 145/378] Pact payload provider initialization --- src/Chainweb/BlockHeaderDB/Internal.hs | 9 +- src/Chainweb/Chainweb.hs | 29 +-- src/Chainweb/Chainweb/ChainResources.hs | 180 +++++++++++------- src/Chainweb/Mempool/InMem.hs | 21 +- src/Chainweb/PayloadProvider/EVM.hs | 37 ++-- src/Chainweb/PayloadProvider/Pact.hs | 5 +- .../PayloadProvider/Pact/Configuration.hs | 8 +- src/Chainweb/PayloadProvider/Pact/Genesis.hs | 9 +- src/Chainweb/Version/EvmDevelopment.hs | 44 ++--- 9 files changed, 202 insertions(+), 140 deletions(-) diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index f272bb8b7a..727ec26813 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -44,11 +44,12 @@ module Chainweb.BlockHeaderDB.Internal ) where import Control.Arrow +import Control.Exception.Safe import Control.DeepSeq import Control.Lens hiding (children) import Control.Monad -import Control.Monad.Catch import Control.Monad.Trans.Maybe +import Control.Monad.Trans.Resource hiding (throwM) import Data.Aeson import Data.Function @@ -249,9 +250,8 @@ withBlockHeaderDb :: RocksDb -> ChainwebVersion -> ChainId - -> (BlockHeaderDb -> IO b) - -> IO b -withBlockHeaderDb db v cid = bracket start closeBlockHeaderDb + -> ResourceT IO BlockHeaderDb +withBlockHeaderDb db v cid = snd <$> allocate start closeBlockHeaderDb where start = initBlockHeaderDb Configuration { _configRoot = genesisBlockHeader v cid @@ -381,4 +381,3 @@ insertBlockHeaderDb db = dbAddChecked db . _validatedHeader unsafeInsertBlockHeaderDb :: BlockHeaderDb -> BlockHeader -> IO () unsafeInsertBlockHeaderDb = dbAddChecked {-# INLINE unsafeInsertBlockHeaderDb #-} - diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index c04bca1fe5..4ed9d88a0e 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -292,20 +292,21 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir -- initialize chains concurrently (\cid x -> do -- Initialize all chain resources, including payload providers - withChainResources - (chainLogger cid) - v - cid - rocksDb - (_peerResManager peerRes) - pactDbDir - (_peerResConfig peerRes) - myInfo - peerDb - (_configReorgLimit conf) - initialUnlimitedRewind - (_configPayloadProviders conf) - (\cr -> x cr) + runResourceT $ do + cr <- withChainResources + (chainLogger cid) + v + cid + rocksDb + (_peerResManager peerRes) + pactDbDir + (_peerResConfig peerRes) + myInfo + peerDb + (_configReorgLimit conf) + initialUnlimitedRewind + (_configPayloadProviders conf) + liftIO $ x cr ) -- initialize global resources after all chain resources are initialized diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index ba9a148372..bc38f38f6b 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -15,6 +15,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE RecursiveDo #-} -- | -- Module: Chainweb.Chainweb.ChainResources @@ -56,44 +57,57 @@ module Chainweb.Chainweb.ChainResources , payloadServiceApiResources ) where +import Control.Exception(evaluate) +import Control.Lens hiding ((.=), (<.>)) +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource +import Data.Foldable +import Data.HashMap.Strict qualified as HM +import Data.Maybe +import Data.PQueue (PQueue) +import Data.Singletons +import Data.Text qualified as T +import Network.HTTP.Client qualified as HTTP +import P2P.Node +import P2P.Node.Configuration +import P2P.Node.PeerDB (PeerDb) +import P2P.Peer (PeerInfo) +import P2P.Session +import P2P.TaskQueue +import Prelude hiding (log) +import System.LogLevel + import Chainweb.BlockHeaderDB import Chainweb.BlockPayloadHash import Chainweb.ChainId import Chainweb.Chainweb.Configuration -- FIXME this module should not depend on the global configuration import Chainweb.Logger +import Chainweb.Mempool.InMem qualified as Mempool +import Chainweb.Mempool.InMem.ValidatingConfig qualified as Mempool +import Chainweb.Pact.PactService qualified as Pact +import Chainweb.Pact.RestAPI qualified as Pact +import Chainweb.Pact.RestAPI.Server qualified as Pact import Chainweb.Pact.Types +import Chainweb.Payload.PayloadStore.RocksDB import Chainweb.PayloadProvider import Chainweb.PayloadProvider.EVM import Chainweb.PayloadProvider.Minimal +import Chainweb.PayloadProvider.P2P import Chainweb.PayloadProvider.P2P.RestAPI import Chainweb.PayloadProvider.P2P.RestAPI.Server +import Chainweb.PayloadProvider.Pact import Chainweb.PayloadProvider.Pact.Configuration +import Chainweb.PayloadProvider.Pact.Genesis qualified as Pact import Chainweb.RestAPI.NetworkID import Chainweb.RestAPI.Utils import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB +import Chainweb.Time import Chainweb.Utils import Chainweb.Version -import Control.Lens hiding ((.=), (<.>)) -import Data.Foldable -import Data.HashMap.Strict qualified as HM -import Data.Maybe -import Data.PQueue (PQueue) -import Data.Singletons -import Data.Text qualified as T -import Network.HTTP.Client qualified as HTTP -import P2P.Node -import P2P.Node.Configuration -import P2P.Node.PeerDB (PeerDb) -import P2P.Peer (PeerInfo) -import P2P.Session -import P2P.TaskQueue -import Prelude hiding (log) import Chainweb.Version.Guards (maxBlockGasLimit) import Pact.Core.Gas qualified as Pact -import System.LogLevel -import Chainweb.Time -- -------------------------------------------------------------------------- -- -- Payload P2P Network Resources @@ -209,11 +223,9 @@ makeLenses ''ProviderResources withPayloadProviderResources :: Logger logger - => HasChainwebVersion v - => HasChainId c => logger - -> v - -> c + -> ChainwebVersion + -> ChainId -> P2pConfiguration -> PeerInfo -> PeerDb @@ -224,11 +236,10 @@ withPayloadProviderResources -> Bool -- ^ whether to allow unlimited rewind on startup -> PayloadProviderConfig - -> (ProviderResources -> IO a) - -> IO a -withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs inner = do + -> ResourceT IO ProviderResources +withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs = do SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal v - SomeChainIdT @c' _ <- return $ someChainIdVal c + SomeChainIdT @c' _ <- return $ someChainIdVal cid withSomeSing provider $ \case SMinimalProvider -> do @@ -241,12 +252,12 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr rewindLi -- provider. let config = _payloadProviderConfigMinimal configs - p <- newMinimalPayloadProvider logger v c rdb (Just mgr) config + p <- liftIO $ newMinimalPayloadProvider logger v cid rdb (Just mgr) config let pdb = view minimalPayloadDb p let queue = view minimalPayloadQueue p - p2pRes <- payloadP2pResources @v' @c' @'MinimalProvider + p2pRes <- liftIO $ payloadP2pResources @v' @c' @'MinimalProvider logger p2pConfig myInfo peerDb pdb queue mgr - inner ProviderResources + return ProviderResources { _providerResPayloadProvider = ConfiguredPayloadProvider p , _providerResServiceApi = Nothing , _providerResP2pApiResources = Just p2pRes @@ -260,11 +271,11 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr rewindLi -- FIXME move the following to the pact provider initialization - let maxGasLimit = Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit ver maxBound + let maxGasLimit = Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit v maxBound case maxGasLimit of Just maxGasLimit' | _pactConfigBlockGasLimit conf > maxGasLimit' -> - logFunction logger Warn $ T.unwords + liftIO $ logFunction logger Warn $ T.unwords [ "configured block gas limit is greater than the" , "maximum for this chain; the maximum will be used instead" ] @@ -284,9 +295,55 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr rewindLi , _pactBlockRefreshInterval = Micros 5_000_000 } - error "Chainweb.PayloadProvider.P2P.RestAPI.somePayloadApi: providerResources not implemented for Pact" + let pdb = newPayloadDb rdb + pactDbDir <- liftIO $ evaluate $ fromJuste $ _pactConfigDatabaseDirectory conf + rec + pp <- + withPactPayloadProvider + (_chainwebVersion v) cid + (Just mgr) + logger + Nothing + mempool + pdb + pactDbDir + pactConfig + (Pact.genesisPayload (_chainwebVersion v) ^? atChain cid) + let mempoolConfig = + Mempool.validatingMempoolConfig + cid (_chainwebVersion v) + (_pactNewBlockGasLimit pactConfig) + (_pactConfigMinGasPrice conf) + (\txs -> + Pact.execPreInsertCheckReq + (pactPayloadProviderLogger pp) + (pactPayloadProviderServiceEnv pp) txs + ) + mempool <- Mempool.withInMemoryMempool (setComponent "mempool" logger) mempoolConfig v + let queue = _payloadStoreQueue $ _psPdb $ pactPayloadProviderServiceEnv pp + p2pRes <- liftIO $ payloadP2pResources @v' @c' @'PactProvider + logger p2pConfig myInfo peerDb pdb queue mgr + let pactServerData = Pact.PactServerData + { Pact._pactServerDataLogger = + pactPayloadProviderLogger pp + , Pact._pactServerDataMempool = + mempool + , Pact._pactServerDataPact = + pactPayloadProviderServiceEnv pp + } + let pactServer = Pact.somePactServer (Pact.somePactServerData v cid pactServerData) + return ProviderResources + { _providerResPayloadProvider = ConfiguredPayloadProvider pp + , _providerResServiceApi = Just $ PayloadServiceApiResources + -- TODO: I think this isn't what was in mind for this... + -- this seems to really just be for the payload API + { _payloadResServiceApi = Pact.somePactServiceApi v cid + , _payloadResServiceServer = pactServer + } + , _providerResP2pApiResources = Just p2pRes + } - _ -> inner $ ProviderResources DisabledPayloadProvider Nothing Nothing + _ -> return $ ProviderResources DisabledPayloadProvider Nothing Nothing SEvmProvider @n _ -> case HM.lookup cid (_payloadProviderConfigEvm configs) of Just config -> do @@ -294,23 +351,21 @@ withPayloadProviderResources logger v c p2pConfig myInfo peerDb rdb mgr rewindLi -- and answering API requests. -- It also starts to awaiting and devlivering new payloads if mining -- is enabled. - withEvmPayloadProvider logger v c rdb (Just mgr) config $ \p -> do - let pdb = view evmPayloadDb p - let queue = view evmPayloadQueue p - p2pRes <- payloadP2pResources @v' @c' @('EvmProvider n) - logger p2pConfig myInfo peerDb pdb queue mgr - inner ProviderResources - { _providerResPayloadProvider = ConfiguredPayloadProvider p - , _providerResServiceApi = Nothing - , _providerResP2pApiResources = Just p2pRes - } - _ -> inner $ ProviderResources DisabledPayloadProvider Nothing Nothing + p <- withEvmPayloadProvider logger v cid rdb (Just mgr) config + let pdb = view evmPayloadDb p + let queue = view evmPayloadQueue p + p2pRes <- liftIO $ payloadP2pResources @v' @c' @('EvmProvider n) + logger p2pConfig myInfo peerDb pdb queue mgr + return ProviderResources + { _providerResPayloadProvider = ConfiguredPayloadProvider p + , _providerResServiceApi = Nothing + , _providerResP2pApiResources = Just p2pRes + } + _ -> return $ ProviderResources DisabledPayloadProvider Nothing Nothing where - ver = _chainwebVersion v - cid = _chainId c provider :: PayloadProviderType - provider = payloadProviderTypeForChain v c + provider = payloadProviderTypeForChain v cid -- -------------------------------------------------------------------------- -- -- Single Chain Resources @@ -345,11 +400,9 @@ instance HasChainId (ChainResources logger) where withChainResources :: Logger logger - => HasChainwebVersion v - => HasChainId c => logger - -> v - -> c + -> ChainwebVersion + -> ChainId -> RocksDb -> HTTP.Manager -> FilePath @@ -362,25 +415,24 @@ withChainResources -> Bool -- ^ whether to allow unlimited rewind on startup -> PayloadProviderConfig - -> (ChainResources logger -> IO a) - -> IO a -withChainResources logger v c rdb mgr _pactDbDir p2pConf myInfo peerDb rewindLimit initialUnlimitedRewind configs inner = + -> ResourceT IO (ChainResources logger) +withChainResources logger v cid rdb mgr _pactDbDir p2pConf myInfo peerDb rewindLimit initialUnlimitedRewind configs = do -- This uses the the CutNetwork for fetching block headers. - withBlockHeaderDb rdb (_chainwebVersion v) (_chainId c) $ \cdb -> do + cdb <- withBlockHeaderDb rdb v cid - -- Payload Providers are using per chain payload networks for fetching - -- block headers. - withPayloadProviderResources - providerLogger v c p2pConf myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs $ \provider -> do + -- Payload Providers are using per chain payload networks for fetching + -- block headers. + provider <- withPayloadProviderResources + providerLogger v cid p2pConf myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs - inner ChainResources - { _chainResBlockHeaderDb = cdb - , _chainResPayloadProvider = provider - , _chainResLogger = logger - } + return ChainResources + { _chainResBlockHeaderDb = cdb + , _chainResPayloadProvider = provider + , _chainResLogger = logger + } where - providerType = payloadProviderTypeForChain v c + providerType = payloadProviderTypeForChain v cid providerLogger = logger & setComponent "payload-provider" & addLabel ("provider", toText providerType) diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 2374584a83..7b17a97e10 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -34,8 +34,10 @@ import Control.Concurrent.Async import Control.Concurrent.MVar import Control.DeepSeq import Control.Error.Util (hush) -import Control.Exception (evaluate, mask_, throw) +import Control.Exception (evaluate, mask_) import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource import qualified Data.ByteString.Short as SB import Data.Decimal @@ -163,17 +165,12 @@ withInMemoryMempool => logger -> InMemConfig t -> ChainwebVersion - -> (MempoolBackend t -> IO a) - -> IO a -withInMemoryMempool l cfg _v f = do - let action inMem = do - r <- race (monitor inMem) $ do - back <- toMempoolBackend l inMem - f $! back - case r of - Left () -> throw $ InternalInvariantViolation "mempool monitor exited unexpectedly" - Right result -> return result - action =<< makeInMemPool cfg + -> ResourceT IO (MempoolBackend t) +withInMemoryMempool l cfg _v = do + inMem <- liftIO $ makeInMemPool cfg + monitorAsync <- withAsyncR (monitor inMem) + liftIO $ link monitorAsync + liftIO $ toMempoolBackend l inMem where monitor m = do let lf = logFunction l diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 692ba48e56..71b045ef12 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -90,6 +90,7 @@ import Control.Concurrent.STM import Control.Exception.Safe import Control.Lens hiding ((.=)) import Control.Monad +import Control.Monad.Trans.Resource hiding (throwM) import Control.Monad.Writer import Data.ByteString.Short qualified as BS import Data.List qualified as L @@ -493,26 +494,25 @@ withEvmPayloadProvider -- -- It is /not/ used for communication with the execution engine client. -> EvmProviderConfig - -> (EvmPayloadProvider logger -> IO a) - -> IO a -withEvmPayloadProvider logger v c rdb mgr conf f + -> ResourceT IO (EvmPayloadProvider logger) +withEvmPayloadProvider logger v c rdb mgr conf | FromSing @_ @p (SEvmProvider ecid) <- payloadProviderTypeForChain v c = do - engineCtx <- mkEngineCtx (_evmConfEngineJwtSecret conf) (_engineUri $ _evmConfEngineUri conf) + engineCtx <- liftIO $ mkEngineCtx (_evmConfEngineJwtSecret conf) (_engineUri $ _evmConfEngineUri conf) SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal v SomeChainIdT @c _ <- return $ someChainIdVal c let pldCli h = Rest.payloadClient @v @c @p h - genPld <- checkExecutionClient logger v c engineCtx (EVM.ChainId (fromSNat ecid)) - logFunctionText logger Info $ "genesis payload block hash: " <> sshow (EVM._hdrPayloadHash genPld) - logFunctionText logger Debug $ "genesis payload from execution client: " <> sshow genPld - pdb <- initPayloadDb $ payloadDbConfiguration v c rdb genPld - store <- newPayloadStore mgr (logFunction pldStoreLogger) pdb pldCli - pldVar <- newEmptyTMVarIO - pldIdVar <- newEmptyTMVarIO - candidates <- emptyTable - stateVar <- newTVarIO (T2 (genesisState v c) Nothing) - lock <- newMVar () + genPld <- liftIO $ checkExecutionClient logger v c engineCtx (EVM.ChainId (fromSNat ecid)) + liftIO $ logFunctionText logger Info $ "genesis payload block hash: " <> sshow (EVM._hdrPayloadHash genPld) + liftIO $ logFunctionText logger Debug $ "genesis payload from execution client: " <> sshow genPld + pdb <- liftIO $ initPayloadDb $ payloadDbConfiguration v c rdb genPld + store <- liftIO $ newPayloadStore mgr (logFunction pldStoreLogger) pdb pldCli + pldVar <- liftIO newEmptyTMVarIO + pldIdVar <- liftIO newEmptyTMVarIO + candidates <- liftIO $ emptyTable + stateVar <- liftIO $ newTVarIO (T2 (genesisState v c) Nothing) + lock <- liftIO $ newMVar () let p = EvmPayloadProvider { _evmChainwebVersion = _chainwebVersion v , _evmChainId = _chainId c @@ -527,13 +527,12 @@ withEvmPayloadProvider logger v c rdb mgr conf f , _evmLock = lock } - result <- race (payloadListener p) $ do + listenerAsync <- withAsyncR (payloadListener p) + liftIO $ link listenerAsync + liftIO $ logg p Info $ "EVM payload provider started for Ethereum network id " <> sshow ecid - f p - case result of - Left () -> error "Chainweb.PayloadProvider.EVM.withEvmPayloadProvider: runForever (payloadListener p) exited unexpectedly" - Right x -> return x + return p | otherwise = error "Chainweb.PayloadProvider.Evm.configuration: chain does not use EVM provider" diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index b1b2240d56..9a108e29ad 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -46,7 +46,10 @@ import qualified Data.Pool as Pool import Control.Monad.Trans.Resource (ResourceT, allocate) import Chainweb.Core.Brief -data PactPayloadProvider logger tbl = PactPayloadProvider logger (ServiceEnv tbl) +data PactPayloadProvider logger tbl = PactPayloadProvider + { pactPayloadProviderLogger :: logger + , pactPayloadProviderServiceEnv :: ServiceEnv tbl + } makePrisms ''PactPayloadProvider diff --git a/src/Chainweb/PayloadProvider/Pact/Configuration.hs b/src/Chainweb/PayloadProvider/Pact/Configuration.hs index c0d32b8d19..d72e2c6ff7 100644 --- a/src/Chainweb/PayloadProvider/Pact/Configuration.hs +++ b/src/Chainweb/PayloadProvider/Pact/Configuration.hs @@ -89,6 +89,7 @@ data PactProviderConfig = PactProviderConfig , _pactConfigEnableLocalTimeout :: !Bool , _pactConfigMiner :: !(Maybe Miner) + , _pactConfigDatabaseDirectory :: !(Maybe FilePath) } deriving (Show, Eq, Generic) @@ -107,6 +108,7 @@ instance ToJSON PactProviderConfig where , "fullHistoricPactState" .= _pactConfigFullHistoricPactState o , "enableLocalTimeout" .= _pactConfigEnableLocalTimeout o , "miner" .= J.toJsonViaEncode (_pactConfigMiner o) + , "databaseDirectory" .= _pactConfigDatabaseDirectory o ] instance FromJSON (PactProviderConfig -> PactProviderConfig) where @@ -122,6 +124,7 @@ instance FromJSON (PactProviderConfig -> PactProviderConfig) where <*< pactConfigFullHistoricPactState ..: "fullHistoricPactState" % o <*< pactConfigEnableLocalTimeout ..: "enableLocalTimeout" % o <*< pactConfigMiner ..: "miner" % o + <*< pactConfigDatabaseDirectory ..: "databaseDirectory" % o defaultPactProviderConfig :: PactProviderConfig defaultPactProviderConfig = PactProviderConfig @@ -136,6 +139,7 @@ defaultPactProviderConfig = PactProviderConfig , _pactConfigFullHistoricPactState = True , _pactConfigEnableLocalTimeout = False , _pactConfigMiner = Nothing + , _pactConfigDatabaseDirectory = Nothing } pPactProviderConfig :: ChainId -> MParser PactProviderConfig @@ -169,4 +173,6 @@ pPactProviderConfig cid = id % prefixLongCid cid "pact-enable-local-timeout" <> helpCid cid "Enable timeout support on /local endpoints" <*< pactConfigMiner .:: fmap Just % pMiner cid - + <*< pactConfigDatabaseDirectory .:: fmap Just % fileOption + % prefixLongCid cid "pact-database-directory" + <> helpCid cid "the directory to store the pact database" diff --git a/src/Chainweb/PayloadProvider/Pact/Genesis.hs b/src/Chainweb/PayloadProvider/Pact/Genesis.hs index bda9c5b0e9..51c1c8cae3 100644 --- a/src/Chainweb/PayloadProvider/Pact/Genesis.hs +++ b/src/Chainweb/PayloadProvider/Pact/Genesis.hs @@ -12,10 +12,11 @@ module Chainweb.PayloadProvider.Pact.Genesis ) where import Chainweb.Version -import Chainweb.Version.Mainnet -import Chainweb.Version.Testnet04 import Chainweb.Version.Development +import Chainweb.Version.EvmDevelopment +import Chainweb.Version.Mainnet import Chainweb.Version.RecapDevelopment +import Chainweb.Version.Testnet04 import Chainweb.Payload import Data.HashMap.Strict qualified as HM @@ -69,5 +70,9 @@ genesisPayload v , [(unsafeChainId i, RDNN.payloadBlock) | i <- [1..9]] , [(unsafeChainId i, RDNKAD.payloadBlock) | i <- [10..19]] ] + | _versionCode v == _versionCode EvmDevelopment = ChainMap $ HM.fromList $ concat + [ [(unsafeChainId 0, DN0.payloadBlock)] + , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] + ] genesisPayload v = error $ "Chainweb.PayloadProvider.Pact.Genesis.genesisPayload: unsupported chainweb version: " <> sshow v diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 64756e5cff..56b743e8df 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -73,27 +73,27 @@ evmDevnet = ChainwebVersion <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..39] ] <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [40..97] ] , _genesisBlockPayload = onChains $ - -- Minimal Payload Provider - [ (unsafeChainId 0, unsafeFromText "rXG-6Jg02UjBYZt0OIwxZ3QtdnwH-C7pX4cX5FHmGE8") - , (unsafeChainId 1, unsafeFromText "xeJGd3yT9SII6Uzm7AwOjoyTSjRGJUI-hC5gBWKLGkw") - , (unsafeChainId 2, unsafeFromText "lXQTK0XTngWwc-POGHOg9_MBqiObaZCXDKR08WfWfL8") - , (unsafeChainId 3, unsafeFromText "GD4-7rjII-MGkVBnXurpn0Pb4aMolNnKDvmlKGMaPRA") - , (unsafeChainId 4, unsafeFromText "iZRTSTlKtzqpgMb0LDukvb9U55RlAJuxXVDWIVYXTIU") - , (unsafeChainId 5, unsafeFromText "B0AotFJgnzdUZo3pHBTT2E9LItJevMGKlYDK4mF9ZzQ") - , (unsafeChainId 6, unsafeFromText "X-TifEHiGkavUAbHgQ0AwtAm6ctCHTkKqg4FVFIzFHQ") - , (unsafeChainId 7, unsafeFromText "WWxH5-JZYdNgRo1KIVzts6YyW8Df1eP8fin4bx4QAM4") - , (unsafeChainId 8, unsafeFromText "TugVzxHnoOEO1nLjsd2IDTHXE1Z2E83sG-OrFgogaBE") - , (unsafeChainId 9, unsafeFromText "2k748hkHJZgRvwJAsc1bxTvWzbbo4d2oSW719NrcS9g") - , (unsafeChainId 10, unsafeFromText "TCfeZhl_v8YJr77HIbxNkWB-j0KQXwmJQx_lkJSpB0w") - , (unsafeChainId 11, unsafeFromText "ZQ7__f2OWthXBnzSWNp80HM5dEz9AyYQ_VCaNKwiX_c") - , (unsafeChainId 12, unsafeFromText "aY5Mc9L2FrkrN6-z-gDBJbuPO5I7B5Cz6giyGSiMzKk") - , (unsafeChainId 13, unsafeFromText "XtZwHbCPrs0ByLZ-1a-hvC_u8GE93tHk-e0uCPqXUz8") - , (unsafeChainId 14, unsafeFromText "A97xRHSsEO9Hn0WdVwZPVNHClzDj-4KXr4Heqf0x36A") - , (unsafeChainId 15, unsafeFromText "iU54vz0G3eiWwXiyeK9FAiqMGV0dqD7TfdgQRLVrVg0") - , (unsafeChainId 16, unsafeFromText "hh_No_CZO9S28lQvU9SQin-ZsxjfIedX_zuYABfoYGQ") - , (unsafeChainId 17, unsafeFromText "tQ0TWds1dMFX9E8EtEXndtmhEDDYtWrgpkv6NmVmJaY") - , (unsafeChainId 18, unsafeFromText "gMs-6QtsYgghAZqTNZ7DBlyOHR2XryNIE4n9rpn-h_I") - , (unsafeChainId 19, unsafeFromText "Evv6R7db1V3t2mrDTosjpL4fjrN9Cn2vdr8tE34hPUY") + -- Pact Payload Provider + [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") + , (unsafeChainId 1, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 2, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 3, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 4, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 5, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 6, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 7, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 8, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 9, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 10, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 11, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 12, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 13, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 14, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 15, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 16, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 17, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider , (unsafeChainId 20, unsafeFromText "FAxLDjtb8r_0S0Rfr8rD47EQwO-Ma-fmEynZccHvn5o") , (unsafeChainId 21, unsafeFromText "RYPcKnqXKzSneT9zLC6OSGpQah48AeRWIVrSMbEYfcE") @@ -196,7 +196,7 @@ evmDevnet = ChainwebVersion -- FIXME make this safe for graph changes , _versionPayloadProviderTypes = onChains - $ [ (unsafeChainId i, MinimalProvider) | i <- [0..19] ] + $ [ (unsafeChainId i, PactProvider) | i <- [0..19] ] <> [ (unsafeChainId i, EvmProvider (1789 - 20 + int i)) | i <- [20..39] ] <> [ (unsafeChainId i, MinimalProvider) | i <- [40..97] ] } From d0d2ec2477dd94a34d14f45d4348081a7a188aae Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 7 Feb 2025 14:37:34 -0500 Subject: [PATCH 146/378] draft: remove registry Change-Id: Id000000093fc79d7fee9f7649bad58da42b2a160 --- bench/Chainweb/MempoolBench.hs | 8 +- bench/Chainweb/Pact/Backend/ApplyCmd.hs | 38 +- bench/Chainweb/Pact/Backend/Bench.hs | 43 +- bench/Chainweb/Pact/Backend/ForkingBench.hs | 44 +- bench/Chainweb/Pact/Backend/PactService.hs | 79 +- bench/JSONEncoding.hs | 6 +- chainweb.cabal | 1 + cwtools/ea/Ea/Genesis.hs | 2 +- src/Chainweb/Backup.hs | 3 +- src/Chainweb/BlockHeader/Internal.hs | 228 +++--- src/Chainweb/BlockHeader/Validation.hs | 87 +-- src/Chainweb/BlockHeaderDB/Internal.hs | 32 +- src/Chainweb/BlockHeaderDB/PruneForks.hs | 11 +- src/Chainweb/BlockHeaderDB/RemoteDB.hs | 27 +- src/Chainweb/BlockHeaderDB/RestAPI.hs | 30 +- src/Chainweb/BlockHeaderDB/RestAPI/Client.hs | 87 ++- src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 33 +- src/Chainweb/Chainweb.hs | 88 +-- src/Chainweb/Chainweb/ChainResources.hs | 47 +- src/Chainweb/Chainweb/CheckReachability.hs | 7 +- src/Chainweb/Chainweb/Configuration.hs | 29 +- src/Chainweb/Chainweb/CutResources.hs | 8 +- src/Chainweb/Chainweb/MempoolSyncClient.hs | 4 +- src/Chainweb/Chainweb/MinerResources.hs | 12 +- src/Chainweb/Chainweb/PeerResources.hs | 42 +- src/Chainweb/Cut.hs | 117 ++- src/Chainweb/Cut/Create.hs | 37 +- src/Chainweb/Cut/CutHashes.hs | 40 +- src/Chainweb/CutDB.hs | 98 +-- src/Chainweb/CutDB/RestAPI.hs | 5 +- src/Chainweb/CutDB/RestAPI/Client.hs | 26 +- src/Chainweb/CutDB/RestAPI/Server.hs | 29 +- src/Chainweb/CutDB/Sync.hs | 19 +- src/Chainweb/Mempool/InMem.hs | 4 +- .../Mempool/InMem/ValidatingConfig.hs | 14 +- src/Chainweb/Mempool/RestAPI.hs | 7 +- src/Chainweb/Mempool/RestAPI/Client.hs | 46 +- src/Chainweb/Mempool/RestAPI/Server.hs | 28 +- src/Chainweb/Miner/Config.hs | 6 +- src/Chainweb/Miner/Coordinator.hs | 33 +- src/Chainweb/Miner/Core.hs | 3 +- src/Chainweb/Miner/Miners.hs | 11 +- src/Chainweb/Miner/RestAPI/Server.hs | 11 +- src/Chainweb/MinerReward.hs | 9 +- src/Chainweb/OpenAPIValidation.hs | 10 +- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 43 +- src/Chainweb/Pact/Backend/Compaction.hs | 23 +- src/Chainweb/Pact/Backend/PactState.hs | 6 +- src/Chainweb/Pact/Backend/PactState/Diff.hs | 105 +-- .../Pact/Backend/PactState/GrandHash/Calc.hs | 91 +-- .../Backend/PactState/GrandHash/Import.hs | 46 +- .../Pact/Backend/PactState/GrandHash/Utils.hs | 35 +- src/Chainweb/Pact/Backend/Utils.hs | 16 +- src/Chainweb/Pact/PactService.hs | 82 +- src/Chainweb/Pact/PactService/Checkpointer.hs | 51 +- src/Chainweb/Pact/PactService/ExecBlock.hs | 13 +- src/Chainweb/Pact/RestAPI.hs | 10 +- src/Chainweb/Pact/RestAPI/Client.hs | 76 +- src/Chainweb/Pact/RestAPI/Server.hs | 27 +- src/Chainweb/Pact/TransactionExec.hs | 27 +- src/Chainweb/Pact/Types.hs | 46 +- src/Chainweb/Pact/Validations.hs | 14 +- src/Chainweb/Parent.hs | 2 - src/Chainweb/Payload/PayloadStore.hs | 26 - src/Chainweb/Payload/RestAPI.hs | 19 +- src/Chainweb/Payload/RestAPI/Client.hs | 32 +- src/Chainweb/Payload/RestAPI/Server.hs | 9 +- src/Chainweb/PayloadProvider.hs | 73 +- src/Chainweb/PayloadProvider/EVM.hs | 56 +- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 96 +-- src/Chainweb/PayloadProvider/EVM/HeaderDB.hs | 31 +- src/Chainweb/PayloadProvider/EVM/SPV.hs | 9 +- src/Chainweb/PayloadProvider/Minimal.hs | 56 +- .../PayloadProvider/Minimal/Payload.hs | 26 +- .../PayloadProvider/Minimal/PayloadDB.hs | 37 +- src/Chainweb/PayloadProvider/P2P/RestAPI.hs | 15 +- src/Chainweb/PayloadProvider/Pact.hs | 17 +- src/Chainweb/PayloadProvider/Pact/Genesis.hs | 18 +- src/Chainweb/RestAPI.hs | 87 ++- src/Chainweb/RestAPI/Backup.hs | 8 +- src/Chainweb/RestAPI/NodeInfo.hs | 31 +- src/Chainweb/SPV/CreateProof.hs | 19 +- src/Chainweb/SPV/EventProof.hs | 4 + src/Chainweb/SPV/OutputProof.hs | 4 + src/Chainweb/SPV/RestAPI/Client.hs | 8 +- src/Chainweb/SPV/RestAPI/Server.hs | 19 +- src/Chainweb/SPV/VerifyProof.hs | 4 + src/Chainweb/Sync/WebBlockHeaderStore.hs | 123 +-- src/Chainweb/VerifierPlugin.hs | 7 +- .../VerifierPlugin/Hyperlane/Message.hs | 2 +- src/Chainweb/Version.hs | 120 ++- src/Chainweb/Version/Development.hs | 16 +- src/Chainweb/Version/EvmDevelopment.hs | 14 +- src/Chainweb/Version/Guards.hs | 109 +-- src/Chainweb/Version/Mainnet.hs | 76 +- src/Chainweb/Version/RecapDevelopment.hs | 82 +- src/Chainweb/Version/Registry.hs | 89 +-- src/Chainweb/Version/Testnet04.hs | 79 +- src/Chainweb/Version/Utils.hs | 239 +++--- src/Chainweb/WebBlockHeaderDB.hs | 71 +- src/P2P/Node.hs | 62 +- src/P2P/Node/PeerDB.hs | 52 +- src/P2P/Node/RestAPI/Client.hs | 30 +- src/P2P/Node/RestAPI/Server.hs | 38 +- test/lib/Chainweb/Test/Cut.hs | 152 ++-- test/lib/Chainweb/Test/Cut/TestBlockDb.hs | 26 +- test/lib/Chainweb/Test/MultiNode.hs | 132 ++-- test/lib/Chainweb/Test/Orphans/Internal.hs | 89 +-- test/lib/Chainweb/Test/Pact/CmdBuilder.hs | 20 +- test/lib/Chainweb/Test/Pact/Utils.hs | 10 +- .../VerifierPluginTest/Transaction/Utils.hs | 247 ------ test/lib/Chainweb/Test/RestAPI/Client_.hs | 71 +- test/lib/Chainweb/Test/RestAPI/Utils.hs | 64 +- test/lib/Chainweb/Test/TestVersions.hs | 131 ++-- test/lib/Chainweb/Test/Utils.hs | 169 ++-- test/lib/Chainweb/Test/Utils/APIValidation.hs | 15 +- test/lib/Chainweb/Test/Utils/BlockHeader.hs | 31 +- test/lib/Chainweb/Test/Utils/TestHeader.hs | 42 +- .../Chainweb/Test/BlockHeader/Validation.hs | 52 +- test/unit/Chainweb/Test/BlockHeaderDB.hs | 11 +- .../Chainweb/Test/BlockHeaderDB/PruneForks.hs | 58 +- test/unit/Chainweb/Test/CutDB.hs | 79 +- test/unit/Chainweb/Test/Mempool/Consensus.hs | 35 +- test/unit/Chainweb/Test/Mempool/RestAPI.hs | 18 +- test/unit/Chainweb/Test/Mempool/Sync.hs | 4 +- test/unit/Chainweb/Test/MinerReward.hs | 50 +- test/unit/Chainweb/Test/Mining.hs | 6 +- .../Chainweb/Test/Pact/CheckpointerTest.hs | 63 +- test/unit/Chainweb/Test/Pact/CutFixture.hs | 101 +-- .../Test/Pact/HyperlanePluginTests.hs | 2 +- .../Chainweb/Test/Pact/PactServiceTest.hs | 125 +-- .../unit/Chainweb/Test/Pact/RemotePactTest.hs | 463 +++++------ .../Chainweb/Test/Pact/TransactionExecTest.hs | 105 ++- test/unit/Chainweb/Test/RestAPI.hs | 735 +++++++++--------- test/unit/Chainweb/Test/Roundtrips.hs | 119 +-- test/unit/Chainweb/Test/SPV.hs | 62 +- test/unit/Chainweb/Test/TreeDB.hs | 10 +- test/unit/Chainweb/Test/Version.hs | 85 +- test/unit/ChainwebTests.hs | 7 +- 139 files changed, 3490 insertions(+), 4074 deletions(-) delete mode 100644 test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs diff --git a/bench/Chainweb/MempoolBench.hs b/bench/Chainweb/MempoolBench.hs index a690b410f0..ae79979249 100644 --- a/bench/Chainweb/MempoolBench.hs +++ b/bench/Chainweb/MempoolBench.hs @@ -56,10 +56,10 @@ setup txs = do return (NoopNFData mp) cmds :: V.Vector UnparsedTransaction -cmds = unsafePerformIO $ V.generateM 4096 $ \i -> do +cmds = withVersion v $ unsafePerformIO $ V.generateM 4096 $ \i -> do now <- getCurrentCreationTime fmap unparseTransaction - $ buildCwCmd (sshow i) v + $ buildCwCmd (sshow i) $ set cbCreationTime now $ set cbGasLimit (Mempool.GasLimit 1) $ defaultCmd @@ -68,9 +68,9 @@ txHash :: UnparsedTransaction -> Mempool.TransactionHash txHash = Mempool.txHasher txCfg expiredCmds :: V.Vector UnparsedTransaction -expiredCmds = unsafePerformIO $ V.generateM 4096 $ \i -> do +expiredCmds = withVersion v $ unsafePerformIO $ V.generateM 4096 $ \i -> do fmap unparseTransaction - $ buildCwCmd (sshow i) v + $ buildCwCmd (sshow i) $ set cbGasLimit (Mempool.GasLimit 1) $ defaultCmd diff --git a/bench/Chainweb/Pact/Backend/ApplyCmd.hs b/bench/Chainweb/Pact/Backend/ApplyCmd.hs index 3422fbc1c9..7e3385792d 100644 --- a/bench/Chainweb/Pact/Backend/ApplyCmd.hs +++ b/bench/Chainweb/Pact/Backend/ApplyCmd.hs @@ -74,8 +74,8 @@ import Pact.Types.SPV qualified as Pact bench :: RocksDb -> C.Benchmark bench rdb = C.bgroup "applyCmd" - [ C.bench "Pact5" $ benchApplyCmd pact5Version rdb (SomeBlockM $ Pair (error "Pact4") applyCmd5) - , C.bench "Pact4" $ benchApplyCmd pact4Version rdb (SomeBlockM $ Pair applyCmd4 (error "Pact5")) + [ C.bench "Pact5" $ withVersion pact5Version benchApplyCmd rdb (SomeBlockM $ Pair (error "Pact4") applyCmd5) + , C.bench "Pact4" $ withVersion pact4Version benchApplyCmd rdb (SomeBlockM $ Pair applyCmd4 (error "Pact5")) ] data Env = Env @@ -91,17 +91,24 @@ data Env = Env instance NFData Env where rnf !_ = () -benchApplyCmd :: ChainwebVersion -> RocksDb -> SomeBlockM GenericLogger RocksDbTable a -> C.Benchmarkable -benchApplyCmd ver rdb act = +benchApplyCmd :: HasVersion => RocksDb -> SomeBlockM GenericLogger RocksDbTable a -> C.Benchmarkable +benchApplyCmd rdb act = let setupEnv _ = do sql <- openSQLiteConnection "" chainwebPragmas - T2 tdb tdbRdb <- mkTestBlockDbIO ver rdb + T2 tdb tdbRdb <- mkTestBlockDbIO rdb bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb tdb) chain0 lgr <- testLogger psEnvVar <- newEmptyMVar - tid <- forkIO $ void $ withPactService ver chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql defaultPactServiceConfig $ do - initialPayloadState ver chain0 +-- <<<<<<< Conflict 1 of 1 +-- %%%%%%% Changes from base to side #1 +-- - tid <- forkIO $ void $ withPactService ver chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql testPactServiceConfig $ do +-- + tid <- forkIO $ void $ withPactService ver chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql defaultPactServiceConfig $ do +-- initialPayloadState ver chain0 +-- +++++++ Contents of side #2 +-- tid <- forkIO $ void $ withPactService chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql testPactServiceConfig $ do +-- initialPayloadState chain0 +-- >>>>>>> Conflict 1 of 1 ends psEnv <- ask liftIO $ putMVar psEnvVar psEnv psEnv <- readMVar psEnvVar @@ -125,23 +132,23 @@ benchApplyCmd ver rdb act = T2 a _finalPactState <- runPactServiceM (PactServiceState mempty) env.pactServiceEnv $ do throwIfNoHistory =<< readFrom - (Just $ ParentHeader (gh ver chain0)) + (Just $ ParentHeader (gh chain0)) act return (NoopNFData a) applyCmd4 :: Pact4.PactBlockM GenericLogger RocksDbTable (Pact4.CommandResult [Pact4.TxLogJson]) --(CommandResult [TxLog ByteString] (PactError Info)) -applyCmd4 = do +applyCmd4 = withVersion pact4Version $ do lgr <- view (psServiceEnv . psLogger) let txCtx = Pact4.TxContext - { Pact4._tcParentHeader = ParentHeader (gh pact4Version chain0) + { Pact4._tcParentHeader = ParentHeader (gh chain0) , Pact4._tcPublicMeta = Pact4.noPublicMeta , Pact4._tcMiner = noMiner } let gasModel = Pact4.getGasModel txCtx pactDbEnv <- view (psBlockDbEnv . Pact4.cpPactDbEnv) - cmd <- liftIO $ Pact4.buildCwCmd "fakeNonce" pact4Version + cmd <- liftIO $ Pact4.buildCwCmd "fakeNonce" $ set Pact4.cbSigners [ Pact4.mkEd25519Signer' Pact4.sender00 [] ] @@ -151,7 +158,6 @@ applyCmd4 = do T3 cmdResult _moduleCache _warnings <- liftIO $ Pact4.applyCmd - pact4Version lgr Nothing Nothing @@ -170,8 +176,8 @@ applyCmd4 = do {-# noinline applyCmd4 #-} applyCmd5 :: Pact5.PactBlockM GenericLogger RocksDbTable (Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info)) -applyCmd5 = do - cmd <- liftIO $ Pact5.buildCwCmd pact5Version (Pact5.defaultCmd chain0) +applyCmd5 = withVersion pact5Version $ do + cmd <- liftIO $ Pact5.buildCwCmd (Pact5.defaultCmd chain0) { Pact5._cbRPC = Pact5.mkExec' "(fold + 0 [1 2 3 4 5])" , Pact5._cbGasPrice = Pact5.GasPrice 2 , Pact5._cbGasLimit = Pact5.GasLimit (Pact5.Gas 500) @@ -179,7 +185,7 @@ applyCmd5 = do , Pact5._cbSigners = [Pact5.mkEd25519Signer' Pact5.sender00 []] } lgr <- view (psServiceEnv . psLogger) - let txCtx = Pact5.TxContext {Pact5._tcParentHeader = ParentHeader (gh pact5Version chain0), Pact5._tcMiner = noMiner} + let txCtx = Pact5.TxContext {Pact5._tcParentHeader = ParentHeader (gh chain0), Pact5._tcMiner = noMiner} Pact5.pactTransaction Nothing $ \pactDb -> do r <- Pact5.applyCmd lgr Nothing pactDb txCtx (TxBlockIdx 0) Pact5.noSPVSupport (Pact5.Gas 1) (view payloadObj <$> cmd) @@ -191,7 +197,7 @@ applyCmd5 = do chain0 :: ChainId chain0 = unsafeChainId 0 -gh :: ChainwebVersion -> ChainId -> BlockHeader +gh :: HasVersion => ChainId -> BlockHeader gh = genesisBlockHeader pact4Version :: ChainwebVersion diff --git a/bench/Chainweb/Pact/Backend/Bench.hs b/bench/Chainweb/Pact/Backend/Bench.hs index 68dc1d1f08..14774bb020 100644 --- a/bench/Chainweb/Pact/Backend/Bench.hs +++ b/bench/Chainweb/Pact/Backend/Bench.hs @@ -7,6 +7,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} +{-# LANGUAGE PartialTypeSignatures #-} module Chainweb.Pact.Backend.Bench ( bench ) @@ -73,7 +74,8 @@ testChainId = unsafeChainId 0 -- allowing a straightforward list of blocks to be passed to the API, -- and only exposing the PactDbEnv part of the block context cpRestoreAndSave - :: (Monoid q, Logger logger) + :: HasVersion + => (Monoid q, Logger logger) => Checkpointer logger -> Maybe BlockHeader -> [(BlockHeader, PactDbEnv (Pact4.BlockEnv logger) -> IO q)] @@ -82,13 +84,13 @@ cpRestoreAndSave cp pc blks = snd <$> restoreAndSave cp (ParentHeader <$> pc) (traverse Stream.yield [Pact4RunnableBlock $ \dbEnv _ -> (,bh) <$> fun (Pact4._cpPactDbEnv dbEnv) | (bh, fun) <- blks]) -- | fabricate a `BlockHeader` for a block given its hash and its parent. -childOf :: Maybe BlockHeader -> BlockHash -> BlockHeader +childOf :: HasVersion => Maybe BlockHeader -> BlockHash -> BlockHeader childOf m bhsh = case m of Just bh -> bh & blockHash .~ bhsh & blockParent .~ view blockHash bh & blockHeight .~ view blockHeight bh + 1 - Nothing -> genesisBlockHeader testVer testChainId + Nothing -> genesisBlockHeader testChainId & blockHash .~ bhsh bench :: C.Benchmark @@ -139,21 +141,23 @@ pactSqliteWithBench unsafe benchtorun = benchtorun dbEnv ] -cpWithBench :: forall logger. (Logger logger, logger ~ GenericLogger) +cpWithBench + :: forall logger. (Logger logger, logger ~ GenericLogger) => (Checkpointer logger -> C.Benchmark) -> C.Benchmark -cpWithBench torun = +cpWithBench torun = withVersion testVer $ C.envWithCleanup setup teardown $ \ ~(NoopNFData (_,e)) -> C.bgroup name (benches e) where name = "batchedCheckpointer" + setup :: HasVersion => IO (NoopNFData (SQLiteEnv, Checkpointer GenericLogger)) setup = do let dbFile = "" {- temporary SQLite db -} let neverLogger = genericLogger Error (\_ -> return ()) !sqliteEnv <- openSQLiteConnection dbFile chainwebPragmas !cenv <- - initCheckpointerResources defaultModuleCacheLimit sqliteEnv DoNotPersistIntraBlockWrites neverLogger testVer testChainId + initCheckpointerResources defaultModuleCacheLimit sqliteEnv DoNotPersistIntraBlockWrites neverLogger testChainId return $ NoopNFData (sqliteEnv, cenv) teardown (NoopNFData (sqliteEnv, _cenv)) = closeSQLiteConnection sqliteEnv @@ -163,14 +167,16 @@ cpWithBench torun = [ torun cpenv ] cpBenchNoRewindOverBlock :: Logger logger => Int -> Checkpointer logger -> C.Benchmark -cpBenchNoRewindOverBlock transactionCount cp = C.env setup' $ \ ~ut -> +cpBenchNoRewindOverBlock transactionCount cp = withVersion testVer $ C.env setup' $ \ ~ut -> C.bench name $ C.nfIO $ do mv <- newMVar (initbytestring, pc01) go mv ut where name = "noRewind/transactionCount=" ++ show transactionCount + pc01 :: HasVersion => BlockHeader pc01 = childOf Nothing hash01 + setup' :: HasVersion => IO (NoopNFData (Domain RowKey RowData)) setup' = do [usertablename] <- cpRestoreAndSave cp Nothing [(pc01, \db -> fmap (:[]) $ setupUserTable db $ \ut -> writeRow db Insert ut f k 1)] @@ -191,6 +197,9 @@ cpBenchNoRewindOverBlock transactionCount cp = C.env setup' $ \ ~ut -> where inc = B8.replicate 30 '\NUL' <> "\SOH\NUL" + go + :: HasVersion + => MVar (B8.ByteString, BlockHeader) -> NoopNFData (Domain RowKey RowData) -> IO () go mblock (NoopNFData ut) = do (bytestring, pc) <- readMVar mblock let (bytestring', hash') = nextHash bytestring @@ -205,10 +214,11 @@ cpBenchNoRewindOverBlock transactionCount cp = C.env setup' $ \ ~ut -> transaction db = incIntegerAtKey db ut f k 1 cpBenchOverBlock :: Logger logger => Int -> Checkpointer logger -> C.Benchmark -cpBenchOverBlock transactionCount cp = C.env setup' $ \ ~(ut) -> +cpBenchOverBlock transactionCount cp = withVersion testVer $ C.env setup' $ \ ~(ut) -> C.bench benchname $ C.nfIO (go ut) where benchname = "overBlock/transactionCount=" ++ show transactionCount + setup' :: HasVersion => IO (NoopNFData (Domain RowKey RowData)) setup' = do [usertablename] <- cpRestoreAndSave cp Nothing [(pc01, \db -> fmap (:[]) $ setupUserTable db $ \ut -> writeRow db Insert ut f k 1 @@ -220,9 +230,12 @@ cpBenchOverBlock transactionCount cp = C.env setup' $ \ ~(ut) -> hash01 = BlockHash $ unsafeMerkleLogHash "0000000000000000000000000000001a" hash02 = BlockHash $ unsafeMerkleLogHash "0000000000000000000000000000002a" + pc01 :: HasVersion => BlockHeader pc01 = childOf Nothing hash01 + pc02 :: HasVersion => BlockHeader pc02 = childOf (Just pc01) hash02 + go :: HasVersion => NoopNFData (Domain RowKey RowData) -> IO () go (NoopNFData ut) = do cpRestoreAndSave cp (Just pc01) [(pc02, \pactdbenv -> @@ -338,7 +351,7 @@ benchUserTableForKeys numSampleEvents dbEnv = writeRow db Update ut f rowkeyb a -_cpBenchKeys :: Logger logger => Int -> Checkpointer logger -> C.Benchmark +_cpBenchKeys :: HasVersion => Logger logger => Int -> Checkpointer logger -> C.Benchmark _cpBenchKeys numKeys cp = C.env setup' $ \ ~(ut) -> C.bench name $ C.nfIO (go ut) where @@ -372,12 +385,13 @@ _cpBenchKeys numKeys cp = incIntegerAtKey db ut f rowkey 1 cpBenchSampleKeys :: Logger logger => Int -> Checkpointer logger -> C.Benchmark -cpBenchSampleKeys numSampleEvents cp = +cpBenchSampleKeys numSampleEvents cp = withVersion testVer $ C.env setup' $ \ ~(ut) -> C.bench name $ C.nfIO (go ut) where name = "user-table-keys/sampleEvents=" ++ show numSampleEvents numberOfKeys :: Integer numberOfKeys = 10 + setup' :: HasVersion => IO (NoopNFData (Domain RowKey RowData)) setup' = do [usertablename] <- cpRestoreAndSave cp Nothing [(pc01, \db -> @@ -398,11 +412,14 @@ cpBenchSampleKeys numSampleEvents cp = hash01 = BlockHash $ unsafeMerkleLogHash "0000000000000000000000000000001a" hash02 = BlockHash $ unsafeMerkleLogHash "0000000000000000000000000000002a" + pc01 :: HasVersion => BlockHeader pc01 = childOf Nothing hash01 + pc02 :: HasVersion => BlockHeader pc02 = childOf (Just pc01) hash02 f = "f" + go :: HasVersion => NoopNFData (Domain RowKey RowData) -> IO () go (NoopNFData ut) = do cpRestoreAndSave cp (Just pc01) [(pc02, \db@(PactDbEnv pdb e) -> @@ -418,11 +435,12 @@ cpBenchSampleKeys numSampleEvents cp = cpBenchLookupProcessedTx :: Logger logger => Int -> Checkpointer logger -> C.Benchmark -cpBenchLookupProcessedTx transactionCount cp = C.env setup' $ \ ~(ut) -> +cpBenchLookupProcessedTx transactionCount cp = withVersion testVer $ C.env setup' $ \ ~(ut) -> C.bench benchname $ C.nfIO (go ut) where benchname = "lookupProcessedTx/transactionCount=" ++ show transactionCount transaction (NoopNFData ut) db = incIntegerAtKey db ut f k 1 + setup' :: HasVersion => IO (NoopNFData (Domain RowKey RowData)) setup' = do [usertablename] <- cpRestoreAndSave cp Nothing [(pc01, \db -> @@ -441,9 +459,12 @@ cpBenchLookupProcessedTx transactionCount cp = C.env setup' $ \ ~(ut) -> hash01 = BlockHash $ unsafeMerkleLogHash "0000000000000000000000000000001a" hash02 = BlockHash $ unsafeMerkleLogHash "0000000000000000000000000000002a" + pc01 :: HasVersion => BlockHeader pc01 = childOf Nothing hash01 + pc02 :: HasVersion => BlockHeader pc02 = childOf (Just pc01) hash02 + go :: HasVersion => _ go (NoopNFData _) = do readFrom cp (Just (ParentHeader pc02)) Pact4T $ \dbEnv _ -> Pact4._cpLookupProcessedTx dbEnv (V.fromList [Pact.RequestKey (Pact.toUntypedHash $ Pact.TypedHash "") | _ <- [1..transactionCount]]) diff --git a/bench/Chainweb/Pact/Backend/ForkingBench.hs b/bench/Chainweb/Pact/Backend/ForkingBench.hs index 954cb68c9c..e0d26bc38e 100644 --- a/bench/Chainweb/Pact/Backend/ForkingBench.hs +++ b/bench/Chainweb/Pact/Backend/ForkingBench.hs @@ -103,13 +103,15 @@ bench rdb = C.bgroup "ForkingBench" $ , doubleForkingBench ] where - forkingBench = withResources rdb 10 Quiet PersistIntraBlockWrites + forkingBench = withVersion testVer + $ withResources rdb 10 Quiet PersistIntraBlockWrites $ \mainLineBlocks pdb bhdb nonceCounter pactQueue _ -> C.bench "forkingBench" $ C.whnfIO $ do let (T3 _ join1 _) = mainLineBlocks !! 5 void $ playLine pdb bhdb 5 join1 pactQueue nonceCounter - doubleForkingBench = withResources rdb 10 Quiet PersistIntraBlockWrites + doubleForkingBench = withVersion testVer + $ withResources rdb 10 Quiet PersistIntraBlockWrites $ \mainLineBlocks pdb bhdb nonceCounter pactQueue _ -> C.bench "doubleForkingBench" $ C.whnfIO $ do let (T3 _ join1 _) = mainLineBlocks !! 5 @@ -122,7 +124,8 @@ bench rdb = C.bgroup "ForkingBench" $ -- Benchmark Function playLine - :: PayloadDb HashMapTable + :: HasVersion + => PayloadDb HashMapTable -> BlockHeaderDb -> Word64 -> BlockHeader @@ -149,7 +152,8 @@ playLine pdb bhdb trunkLength startingBlock pactQueue counter = do return ret mineBlock - :: ParentHeader + :: HasVersion + => ParentHeader -> Nonce -> PayloadDb HashMapTable -> BlockHeaderDb @@ -163,7 +167,8 @@ mineBlock parent nonce pdb bhdb pact = do return r createBlock - :: ParentHeader + :: HasVersion + => ParentHeader -> Nonce -> PactQueue -> IO (T3 ParentHeader BlockHeader PayloadWithOutputs) @@ -209,7 +214,8 @@ type RunPactService = -> IORef Int -> C.Benchmark -withResources :: () +withResources + :: HasVersion => RocksDb -> Word64 -> LogLevel @@ -221,6 +227,7 @@ withResources rdb trunkLength logLevel f = C.envWithCleanup create destroy unwra unwrap ~(NoopNFData (Resources {..})) = f mainTrunkBlocks payloadDb blockHeaderDb nonceCounter (snd pactService) txPerBlock + create :: HasVersion => _ create = do payloadDb <- createPayloadDb blockHeaderDb <- testBlockHeaderDb @@ -231,7 +238,7 @@ withResources rdb trunkLength logLevel f = C.envWithCleanup create destroy unwra (sqlEnv, pactService, mainTrunkBlocks) <- do sqlEnv <- openSQLiteConnection "" {- temporary SQLite db -} chainwebBenchPragmas pactService <- - startPact testVer logger blockHeaderDb payloadDb mp sqlEnv + startPact logger blockHeaderDb payloadDb mp sqlEnv mainTrunkBlocks <- playLine payloadDb blockHeaderDb trunkLength genesisBlock (snd pactService) nonceCounter pure (sqlEnv, pactService, mainTrunkBlocks) @@ -246,9 +253,15 @@ withResources rdb trunkLength logLevel f = C.envWithCleanup create destroy unwra logger = genericLogger logLevel T.putStrLn - startPact version l bhdb pdb mempool sqlEnv = do + startPact l bhdb pdb mempool sqlEnv = do reqQ <- newPactQueue pactQueueSize - a <- async $ runPactService version cid l Nothing reqQ mempool bhdb pdb sqlEnv defaultPactServiceConfig +-- <<<<<<< Conflict 1 of 1 +-- +++++++ Contents of side #1 +-- a <- async $ runPactService version cid l Nothing reqQ mempool bhdb pdb sqlEnv defaultPactServiceConfig +-- %%%%%%% Changes from base to side #2 +-- - a <- async $ runPactService version cid l Nothing reqQ mempool bhdb pdb sqlEnv testPactServiceConfig +-- + a <- async $ runPactService someChainId l Nothing reqQ mempool bhdb pdb sqlEnv testPactServiceConfig +-- >>>>>>> Conflict 1 of 1 ends { _pactNewBlockGasLimit = 180_000 , _pactPersistIntraBlockWrites = p } @@ -268,7 +281,7 @@ withResources rdb trunkLength logLevel f = C.envWithCleanup create destroy unwra ] genesisBlock :: BlockHeader - genesisBlock = genesisBlockHeader testVer cid + genesisBlock = genesisBlockHeader someChainId -- | Creates an in-memory Payload database that is managed by the garbage -- collector. @@ -288,7 +301,7 @@ withResources rdb trunkLength logLevel f = C.envWithCleanup create destroy unwra -- | Mempool Access -- -testMemPoolAccess :: IORef Int -> MVar (Map Account (NonEmpty (DynKeyPair, [SigCapability]))) -> IO MemPoolAccess +testMemPoolAccess :: HasVersion => IORef Int -> MVar (Map Account (NonEmpty (DynKeyPair, [SigCapability]))) -> IO MemPoolAccess testMemPoolAccess txsPerBlock accounts = do return $ mempty { mpaGetBlock = \bf validate bh hash bct -> do @@ -300,10 +313,10 @@ testMemPoolAccess txsPerBlock accounts = do setTime time pb = pb { _pmCreationTime = toTxCreationTime time } - getTestBlock :: _ -> _ -> MempoolPreBlockCheck Pact4.UnparsedTransaction to -> _ -> _ -> IO (V.Vector to) + getTestBlock :: HasVersion => _ -> _ -> MempoolPreBlockCheck Pact4.UnparsedTransaction to -> _ -> _ -> IO (V.Vector to) getTestBlock mVarAccounts txOrigTime validate bHeight hash | bHeight == 1 = do - meta <- setTime txOrigTime <$> makeMeta cid + meta <- setTime txOrigTime <$> makeMeta someChainId (as, kss, cmds) <- unzip3 . toList <$> createCoinAccounts testVer meta case traverse validateCommand cmds of Left err -> throwM $ userError err @@ -323,7 +336,7 @@ testMemPoolAccess txsPerBlock accounts = do txs <- forM coinReqs $ \req@(TransferRequest (SenderName sn) rcvr amt) -> do let (Account sender, ks) = mkTransferCaps rcvr amt (sn, fromJuste $ M.lookup sn accs) - meta <- setTime txOrigTime <$> makeMetaWithSender sender cid + meta <- setTime txOrigTime <$> makeMetaWithSender sender someChainId eCmd <- validateCommand <$> createTransfer testVer meta ks req case eCmd of Left e -> throwM $ userError e @@ -345,9 +358,6 @@ testMemPoolAccess txsPerBlock accounts = do -- -------------------------------------------------------------------------- -- -- Utils -cid :: ChainId -cid = someChainId testVer - testVer :: ChainwebVersion testVer = slowForkingCpmTestVersion petersenChainGraph diff --git a/bench/Chainweb/Pact/Backend/PactService.hs b/bench/Chainweb/Pact/Backend/PactService.hs index 4d75c4d0f3..30c540ac91 100644 --- a/bench/Chainweb/Pact/Backend/PactService.hs +++ b/bench/Chainweb/Pact/Backend/PactService.hs @@ -82,36 +82,35 @@ bench :: RocksDb -> C.Benchmark bench rdb = do C.bgroup "PactService" [ C.bgroup "Pact4" - [ C.bench "1 tx" $ oneBlock pact4Version rdb 1 - , C.bench "10 txs" $ oneBlock pact4Version rdb 10 - , C.bench "20 txs" $ oneBlock pact4Version rdb 20 - , C.bench "30 txs" $ oneBlock pact4Version rdb 30 - , C.bench "40 txs" $ oneBlock pact4Version rdb 40 - , C.bench "50 txs" $ oneBlock pact4Version rdb 50 - , C.bench "60 txs" $ oneBlock pact4Version rdb 60 - , C.bench "70 txs" $ oneBlock pact4Version rdb 70 - , C.bench "80 txs" $ oneBlock pact4Version rdb 80 - , C.bench "90 txs" $ oneBlock pact4Version rdb 90 - , C.bench "100 txs" $ oneBlock pact4Version rdb 100 + [ C.bench "1 tx" $ withVersion pact4Version oneBlock rdb 1 + , C.bench "10 txs" $ withVersion pact4Version oneBlock rdb 10 + , C.bench "20 txs" $ withVersion pact4Version oneBlock rdb 20 + , C.bench "30 txs" $ withVersion pact4Version oneBlock rdb 30 + , C.bench "40 txs" $ withVersion pact4Version oneBlock rdb 40 + , C.bench "50 txs" $ withVersion pact4Version oneBlock rdb 50 + , C.bench "60 txs" $ withVersion pact4Version oneBlock rdb 60 + , C.bench "70 txs" $ withVersion pact4Version oneBlock rdb 70 + , C.bench "80 txs" $ withVersion pact4Version oneBlock rdb 80 + , C.bench "90 txs" $ withVersion pact4Version oneBlock rdb 90 + , C.bench "100 txs" $ withVersion pact4Version oneBlock rdb 100 ] , C.bgroup "Pact5" - [ C.bench "1 tx" $ oneBlock pact5Version rdb 1 - , C.bench "10 txs" $ oneBlock pact5Version rdb 10 - , C.bench "20 txs" $ oneBlock pact5Version rdb 20 - , C.bench "30 txs" $ oneBlock pact5Version rdb 30 - , C.bench "40 txs" $ oneBlock pact5Version rdb 40 - , C.bench "50 txs" $ oneBlock pact5Version rdb 50 - , C.bench "60 txs" $ oneBlock pact5Version rdb 60 - , C.bench "70 txs" $ oneBlock pact5Version rdb 70 - , C.bench "80 txs" $ oneBlock pact5Version rdb 80 - , C.bench "90 txs" $ oneBlock pact5Version rdb 90 - , C.bench "100 txs" $ oneBlock pact5Version rdb 100 + [ C.bench "1 tx" $ withVersion pact5Version oneBlock rdb 1 + , C.bench "10 txs" $ withVersion pact5Version oneBlock rdb 10 + , C.bench "20 txs" $ withVersion pact5Version oneBlock rdb 20 + , C.bench "30 txs" $ withVersion pact5Version oneBlock rdb 30 + , C.bench "40 txs" $ withVersion pact5Version oneBlock rdb 40 + , C.bench "50 txs" $ withVersion pact5Version oneBlock rdb 50 + , C.bench "60 txs" $ withVersion pact5Version oneBlock rdb 60 + , C.bench "70 txs" $ withVersion pact5Version oneBlock rdb 70 + , C.bench "80 txs" $ withVersion pact5Version oneBlock rdb 80 + , C.bench "90 txs" $ withVersion pact5Version oneBlock rdb 90 + , C.bench "100 txs" $ withVersion pact5Version oneBlock rdb 100 ] ] data Fixture = Fixture - { _chainwebVersion :: !ChainwebVersion - , _fixtureBlockDb :: !TestBlockDb + { _fixtureBlockDb :: !TestBlockDb , _fixtureBlockDbRocksDb :: !RocksDb , _fixtureMempools :: !(ChainMap (MempoolBackend Pact4.UnparsedTransaction)) , _fixturePactQueues :: !(ChainMap PactQueue) @@ -122,26 +121,25 @@ data Fixture = Fixture instance NFData Fixture where rnf !_ = () -createFixture :: ChainwebVersion -> RocksDb -> PactServiceConfig -> IO Fixture -createFixture v rdb pactServiceConfig = do - T2 tdb tdbRdb <- mkTestBlockDbIO v rdb +createFixture :: HasVersion => RocksDb -> PactServiceConfig -> IO Fixture +createFixture rdb pactServiceConfig = do + T2 tdb tdbRdb <- mkTestBlockDbIO rdb logger <- testLogger - perChain <- iforM (HashSet.toMap (chainIds v)) $ \chain () -> do + perChain <- iforM (HashSet.toMap chainIds) $ \chain () -> do sql <- openSQLiteConnection "" chainwebPragmas bhdb <- liftIO $ getWebBlockHeaderDb (_bdbWebBlockHeaderDb tdb) chain pactQueue <- liftIO $ newPactQueue 2_000 pactExecutionServiceVar <- liftIO $ newMVar (mkPactExecutionService pactQueue) - let mempoolCfg = validatingMempoolConfig chain v (Pact4.GasLimit 150_000) (Pact4.GasPrice 1e-8) pactExecutionServiceVar + let mempoolCfg = validatingMempoolConfig chain (Pact4.GasLimit 150_000) (Pact4.GasPrice 1e-8) pactExecutionServiceVar mempool <- liftIO $ startInMemoryMempoolTest mempoolCfg mempoolConsensus <- liftIO $ mkMempoolConsensus mempool bhdb (Just (_bdbPayloadDb tdb)) let mempoolAccess = pactMemPoolAccess mempoolConsensus logger - tid <- forkIO $ runPactService v chain logger Nothing pactQueue mempoolAccess bhdb (_bdbPayloadDb tdb) sql pactServiceConfig + tid <- forkIO $ runPactService chain logger Nothing pactQueue mempoolAccess bhdb (_bdbPayloadDb tdb) sql pactServiceConfig return (mempool, pactQueue, tid, sql) let fixture = Fixture - { _chainwebVersion = v - , _fixtureBlockDb = tdb + { _fixtureBlockDb = tdb , _fixtureBlockDbRocksDb = tdbRdb , _fixtureMempools = OnChains $ view _1 <$> perChain , _fixturePactQueues = OnChains $ view _2 <$> perChain @@ -163,15 +161,15 @@ destroyFixture fx = do closeSQLiteConnection sql deleteNamespaceRocksDb fx._fixtureBlockDbRocksDb -oneBlock :: ChainwebVersion -> RocksDb -> Word -> C.Benchmarkable -oneBlock v rdb numTxs = +oneBlock :: HasVersion => RocksDb -> Word -> C.Benchmarkable +oneBlock rdb numTxs = let cid = unsafeChainId 0 cfg = defaultPactServiceConfig setupEnv _ = do - fx <- createFixture v rdb cfg + fx <- createFixture rdb cfg txs <- forM [1..numTxs] $ \_ -> do - buildCwCmd v (transferCmd cid 1.0) + buildCwCmd (transferCmd cid 1.0) return (fx, txs) cleanupEnv _ (fx, _) = do @@ -195,22 +193,23 @@ oneBlock v rdb numTxs = getCut :: Fixture -> IO Cut getCut Fixture{..} = getCutTestBlockDb _fixtureBlockDb -revert :: Fixture -> Cut -> IO () +revert :: HasVersion => Fixture -> Cut -> IO () revert Fixture{..} c = do setCutTestBlockDb _fixtureBlockDb c - forM_ (HashSet.toList (chainIds _chainwebVersion)) $ \chain -> do + forM_ (HashSet.toList chainIds) $ \chain -> do ph <- getParentTestBlockDb _fixtureBlockDb chain pactSyncToBlock ph (_fixturePactQueues ^?! atChain chain) -- this mines a block on *all chains*. if you don't specify a payload on a chain, -- it adds empty blocks! -advanceAllChains :: () +advanceAllChains + :: HasVersion => Fixture -> ChainMap (BlockHeader -> PactQueue -> MempoolBackend Pact4.UnparsedTransaction -> IO PayloadWithOutputs) -> IO (ChainMap (Vector TestPact5CommandResult)) advanceAllChains Fixture{..} blocks = do commandResults <- - forConcurrently (HashSet.toList (chainIds _chainwebVersion)) $ \c -> do + forConcurrently (HashSet.toList chainIds) $ \c -> do ph <- getParentTestBlockDb _fixtureBlockDb c creationTime <- getCurrentTimeIntegral let pactQueue = _fixturePactQueues ^?! atChain c diff --git a/bench/JSONEncoding.hs b/bench/JSONEncoding.hs index f0c07c2732..81e6d9c329 100644 --- a/bench/JSONEncoding.hs +++ b/bench/JSONEncoding.hs @@ -45,6 +45,7 @@ import Chainweb.RestAPI.NodeInfo import Chainweb.Test.Orphans.Internal import Chainweb.Utils.Paging import Chainweb.Version.Mainnet +import Chainweb.Version -- -------------------------------------------------------------------------- -- -- Main @@ -140,11 +141,11 @@ config :: ChainwebConfiguration config = defaultChainwebConfiguration Mainnet01 {-# NOINLINE config #-} -headerPage :: Natural -> Page BlockHash BlockHeader +headerPage :: HasVersion => Natural -> Page BlockHash BlockHeader headerPage n = unsafePerformIO $ generate $ arbitraryPage n {-# NOINLINE headerPage #-} -objHeaderPage :: Natural -> Page BlockHash (ObjectEncoded BlockHeader) +objHeaderPage :: HasVersion => Natural -> Page BlockHash (ObjectEncoded BlockHeader) objHeaderPage n = pageItems %~ fmap ObjectEncoded $ unsafePerformIO $ generate $ arbitraryPage n {-# NOINLINE objHeaderPage #-} @@ -152,4 +153,3 @@ objHeaderPage n = pageItems %~ fmap ObjectEncoded $ unsafePerformIO payloadPage :: Natural -> Page BlockHash PayloadWithOutputs payloadPage n = unsafePerformIO $ generate $ arbitraryPage n {-# NOINLINE payloadPage #-} - diff --git a/chainweb.cabal b/chainweb.cabal index 3e44fb7cf7..2edab1329a 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -532,6 +532,7 @@ library chainweb-test-utils , directory >= 1.2 , exceptions , filepath >= 1.4 + , hashable , hashes >=0.2.2.0 , http-client >= 0.5 , http-types >= 0.12 diff --git a/cwtools/ea/Ea/Genesis.hs b/cwtools/ea/Ea/Genesis.hs index 010916a7aa..c727b58c9f 100644 --- a/cwtools/ea/Ea/Genesis.hs +++ b/cwtools/ea/Ea/Genesis.hs @@ -238,7 +238,7 @@ quirkedPact5InstantCPMN = quirkedPact5InstantCPM0 -- fastTimedCPM0 :: Genesis -- fastTimedCPM0 = Genesis --- { _version = fastForkingCpmTestVersion petersonChainGraph +-- { _version = fastForkingCpmTestVersion petersenChainGraph -- , _tag = "FastTimedCPM" -- , _txChainIds = onlyChainId 0 -- , _coinbase = Just fast0Grants diff --git a/src/Chainweb/Backup.hs b/src/Chainweb/Backup.hs index 5a121d2925..4c09036274 100644 --- a/src/Chainweb/Backup.hs +++ b/src/Chainweb/Backup.hs @@ -42,6 +42,7 @@ import Chainweb.Pact.Backend.Utils(chainDbFileName, withSqliteDb) import Chainweb.Utils import Chainweb.Storage.Table.RocksDB +import Chainweb.Version (HasVersion) data BackupOptions = BackupOptions { _backupIdentifier :: !FilePath @@ -76,7 +77,7 @@ instance MimeRender PlainText BackupStatus where instance MimeUnrender PlainText BackupStatus where mimeUnrender = const (over _Left show . fromText . TL.toStrict . TL.decodeUtf8) -makeBackup :: Logger logger => BackupEnv logger -> BackupOptions -> IO () +makeBackup :: (Logger logger, HasVersion) => BackupEnv logger -> BackupOptions -> IO () makeBackup env options = do logCr Info ("making backup to " <> T.pack thisBackup) createDirectoryIfMissing True (thisBackup "0" "sqlite") diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index 981a986fab..22f52ee773 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -163,7 +163,6 @@ import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Guards import Chainweb.Version.Mainnet -import Chainweb.Version.Registry (lookupVersionByName, lookupVersionByCode) import Chainweb.Version.Testnet04 import Control.DeepSeq import Control.Exception @@ -187,9 +186,9 @@ import GHC.Generics (Generic) import GHC.Stack import Numeric.AffineSpace import Numeric.Natural -import System.IO.Unsafe import Text.Read (readEither) import Control.Monad +import System.IO.Unsafe -- -------------------------------------------------------------------------- -- -- Nonce @@ -388,11 +387,8 @@ instance Hashable BlockHeader where instance HasChainId BlockHeader where _chainId = _blockChainId -instance HasChainGraph BlockHeader where - _chainGraph h = _chainGraph (_chainwebVersion h, _blockHeight h) - -instance HasChainwebVersion BlockHeader where - _chainwebVersion = lookupVersionByCode . _blockChainwebVersion +instance HasVersion => HasChainGraph BlockHeader where + _chainGraph h = chainGraphAt (_blockHeight h) instance IsCasValue BlockHeader where type CasKeyType BlockHeader = BlockHash @@ -420,15 +416,15 @@ makeLenses ''BlockHeader -- adjustments. This is to account for rapidly changing total hash power in the -- early stages of the network. -- -effectiveWindow :: BlockHeader -> Maybe WindowWidth -effectiveWindow h = WindowWidth <$> case _versionWindow (_chainwebVersion h) of +effectiveWindow :: HasVersion => BlockHeader -> Maybe WindowWidth +effectiveWindow h = WindowWidth <$> case _versionWindow implicitVersion of WindowWidth w | int (_blockHeight h) <= w -> Just $ max 1 $ w `div` 10 | otherwise -> Just w -- | Return whether the given 'BlockHeader' is the last header in its epoch. -- -isLastInEpoch :: BlockHeader -> Bool +isLastInEpoch :: HasVersion => BlockHeader -> Bool isLastInEpoch h = case effectiveWindow h of Nothing -> False Just (WindowWidth w) -> (int (_blockHeight h) + 1) `mod` w == 0 @@ -442,13 +438,12 @@ isLastInEpoch h = case effectiveWindow h of -- all chainweb version. Emergency DAs are enabled (and have occured) only on -- mainnet01 for cut heights smaller than 80,000. -- -slowEpoch :: Parent BlockHeader -> BlockCreationTime -> Bool +slowEpoch :: HasVersion => Parent BlockHeader -> BlockCreationTime -> Bool slowEpoch (Parent p) (BlockCreationTime ct) = actual > (expected * 5) where EpochStartTime es = _blockEpochStart p - v = _chainwebVersion p - BlockDelay bd = _versionBlockDelay v - WindowWidth ww = _versionWindow v + BlockDelay bd = _versionBlockDelay implicitVersion + WindowWidth ww = _versionWindow implicitVersion expected :: Micros expected = bd * int ww @@ -466,7 +461,8 @@ slowEpoch (Parent p) (BlockCreationTime ct) = actual > (expected * 5) -- transition. -- powTarget - :: Parent BlockHeader + :: HasVersion + => Parent BlockHeader -- ^ parent header -> HM.HashMap ChainId (Parent BlockHeader) -- ^ adjacent Parents @@ -481,24 +477,23 @@ powTarget p@(Parent ph) as bct = case effectiveWindow ph of Nothing -> maxTarget Just w -- Emergency DA, legacy - | slowEpochGuard ver (_chainId ph) (_blockHeight ph) && slowEpoch p bct -> + | slowEpochGuard (_chainId ph) (_blockHeight ph) && slowEpoch p bct -> activeAdjust w | isLastInEpoch ph -> activeAdjust w | otherwise -> _blockTarget ph where - ver = _chainwebVersion ph - t = EpochStartTime $ if oldTargetGuard ver (_chainId ph) (_blockHeight ph) + t = EpochStartTime $ if oldTargetGuard (_chainId ph) (_blockHeight ph) then _bct bct else _bct (_blockCreationTime ph) activeAdjust w - | oldDaGuard ver (_chainId ph) (_blockHeight ph + 1) - = legacyAdjust (_versionBlockDelay ver) w (t .-. _blockEpochStart ph) (_blockTarget ph) + | oldDaGuard (_chainId ph) (_blockHeight ph + 1) + = legacyAdjust (_versionBlockDelay implicitVersion) w (t .-. _blockEpochStart ph) (_blockTarget ph) | otherwise = avgTarget $ adjustForParent w <$> (p : HM.elems as) adjustForParent w (Parent a) - = adjust (_versionBlockDelay ver) w (toEpochStart a .-. _blockEpochStart a) (_blockTarget a) + = adjust (_versionBlockDelay implicitVersion) w (toEpochStart a .-. _blockEpochStart a) (_blockTarget a) toEpochStart = EpochStartTime . _bct . _blockCreationTime @@ -510,7 +505,8 @@ powTarget p@(Parent ph) as bct = case effectiveWindow ph of -- | Compute the epoch start value for a new BlockHeader -- epochStart - :: Parent BlockHeader + :: HasVersion + => Parent BlockHeader -- ^ parent header -> HM.HashMap ChainId (Parent BlockHeader) -- ^ Adjacent parents of the block. It is not checked whether the @@ -528,7 +524,7 @@ epochStart ph@(Parent p) adj (BlockCreationTime bt) -- A special case for starting a new devnet, to compensate the inaccurate -- creation time of the genesis blocks. This would result in a very long -- first epoch that cause a trivial target in the second epoch. - | ver ^. versionCheats . fakeFirstEpochStart, _blockHeight p == 1 = EpochStartTime (_bct $ _blockCreationTime p) + | implicitVersion ^. versionCheats . fakeFirstEpochStart, _blockHeight p == 1 = EpochStartTime (_bct $ _blockCreationTime p) -- New Graph: the block time of the genesis block isn't accurate, we thus -- use the block time of the first block on the chain. Depending on where @@ -538,13 +534,13 @@ epochStart ph@(Parent p) adj (BlockCreationTime bt) | parentIsFirstOnNewChain = EpochStartTime (_bct $ _blockCreationTime p) -- End of epoch, DA adjustment (legacy version) - | isLastInEpoch p && oldTargetGuard ver (_chainId p) (_blockHeight p) = EpochStartTime bt + | isLastInEpoch p && oldTargetGuard (_chainId p) (_blockHeight p) = EpochStartTime bt -- End of epoch, DA adjustment | isLastInEpoch p = EpochStartTime (_bct $ _blockCreationTime p) -- Within epoch with old legacy DA - | oldDaGuard ver (_chainId p) (_blockHeight p + 1) = _blockEpochStart p + | oldDaGuard (_chainId p) (_blockHeight p + 1) = _blockEpochStart p -- Within an epoch with new DA | otherwise = _blockEpochStart p @@ -552,7 +548,6 @@ epochStart ph@(Parent p) adj (BlockCreationTime bt) -- Experimental, allow DA to support multiple hash functions -- | otherwise = _blockEpochStart p .+^ _adjustmentAvg where - ver = _chainwebVersion p cid = _chainId p -- Add a penalty for fast chains by adding the different between the @@ -610,34 +605,34 @@ epochStart ph@(Parent p) adj (BlockCreationTime bt) $ adj parentIsFirstOnNewChain - = _blockHeight p > 1 && _blockHeight p == genesisHeight ver cid + 1 + = _blockHeight p > 1 && _blockHeight p == genesisHeight cid + 1 {-# INLINE epochStart #-} -- -------------------------------------------------------------------------- -- -- Newtype wrappers for function parameters -isGenesisBlockHeader :: BlockHeader -> Bool +isGenesisBlockHeader :: HasVersion => BlockHeader -> Bool isGenesisBlockHeader b = - _blockHeight b == genesisHeight (_chainwebVersion b) (_chainId b) + _blockHeight b == genesisHeight (_chainId b) -- Alternate method of detecting a genesis block using only its parent + chain + version -isGenesisBlockHeader' :: ChainwebVersion -> ChainId -> Parent BlockHash -> Bool -isGenesisBlockHeader' v cid ph = - genesisParentBlockHash v cid == ph +isGenesisBlockHeader' :: HasVersion => ChainId -> Parent BlockHash -> Bool +isGenesisBlockHeader' cid ph = + genesisParentBlockHash cid == ph -- The height of a child of the given block. -childBlockHeight :: ChainwebVersion -> ChainId -> Parent (Ranked BlockHash) -> BlockHeight -childBlockHeight v cid (Parent rbh) - | isGenesisBlockHeader' v cid (Parent (_rankedBlockHashHash rbh)) = +childBlockHeight :: HasVersion => ChainId -> Parent (Ranked BlockHash) -> BlockHeight +childBlockHeight cid (Parent rbh) + | isGenesisBlockHeader' cid (Parent (_rankedBlockHashHash rbh)) = _rankedBlockHashHeight rbh | otherwise = succ (_rankedBlockHashHeight rbh) -- | The height of a parent of the given block. Note that you need the hash of -- the parent and the height of the child. -parentBlockHeight :: ChainwebVersion -> ChainId -> Ranked (Parent BlockHash) -> Parent (Ranked BlockHash) -parentBlockHeight v cid (Ranked height parentHash) - | isGenesisBlockHeader' v cid parentHash = +parentBlockHeight :: HasVersion => ChainId -> Ranked (Parent BlockHash) -> Parent (Ranked BlockHash) +parentBlockHeight cid (Ranked height parentHash) + | isGenesisBlockHeader' cid parentHash = Parent $ Ranked height $ unwrapParent parentHash | otherwise = Parent $ Ranked (pred height) $ unwrapParent parentHash @@ -647,18 +642,18 @@ parentBlockHeight v cid (Ranked height parentHash) -- -- It is the '_blockParent' of the genesis block -- -genesisParentBlockHash :: HasChainId p => ChainwebVersion -> p -> Parent BlockHash -genesisParentBlockHash v p = Parent $ BlockHash $ MerkleLogHash +genesisParentBlockHash :: (HasVersion, HasChainId p) => p -> Parent BlockHash +genesisParentBlockHash p = Parent $ BlockHash $ MerkleLogHash $ merkleRoot @ChainwebMerkleHashAlgorithm [ InputNode "CHAINWEB_GENESIS" - , encodeMerkleInputNode encodeChainwebVersionCode (_versionCode v) + , encodeMerkleInputNode encodeChainwebVersionCode (_versionCode implicitVersion) , encodeMerkleInputNode encodeChainId (_chainId p) ] -genesisRankedParentBlockHash :: HasChainId p => ChainwebVersion -> p -> Parent RankedBlockHash -genesisRankedParentBlockHash v p = Parent $ RankedBlockHash - (genesisHeight v (_chainId p)) - (unwrapParent $ genesisParentBlockHash v p) +genesisRankedParentBlockHash :: (HasVersion, HasChainId p) => p -> Parent RankedBlockHash +genesisRankedParentBlockHash p = Parent $ RankedBlockHash + (genesisHeight (_chainId p)) + (unwrapParent $ genesisParentBlockHash p) {-# NOINLINE genesisBlockHeaderCache #-} genesisBlockHeaderCache :: IORef (HashMap ChainwebVersionCode (HashMap ChainId BlockHeader)) @@ -677,26 +672,21 @@ genesisBlockHeaders :: ChainwebVersion -> HashMap ChainId BlockHeader genesisBlockHeaders = \v -> if _versionCode v == _versionCode mainnet then mainnetGenesisHeaders else if _versionCode v == _versionCode testnet04 then testnetGenesisHeaders - else unsafeDupablePerformIO $ - HM.lookup (_versionCode v) <$> readIORef genesisBlockHeaderCache >>= \case - Just hs -> return hs - Nothing -> do - let freshGenesisHeaders = makeGenesisBlockHeaders v - modifyIORef' genesisBlockHeaderCache $ HM.insert (_versionCode v) freshGenesisHeaders - return freshGenesisHeaders - where - mainnetGenesisHeaders = makeGenesisBlockHeaders mainnet - testnetGenesisHeaders = makeGenesisBlockHeaders testnet04 + else withVersion v makeGenesisBlockHeaders + where + mainnetGenesisHeaders = withVersion mainnet makeGenesisBlockHeaders + testnetGenesisHeaders = withVersion testnet04 makeGenesisBlockHeaders -genesisBlockHeader :: (HasCallStack, HasChainId p) => ChainwebVersion -> p -> BlockHeader -genesisBlockHeader v p = genesisBlockHeaders v ^?! at (_chainId p) . _Just +genesisBlockHeader :: (HasCallStack, HasChainId p, HasVersion) => p -> BlockHeader +genesisBlockHeader p = genesisBlockHeaders implicitVersion ^?! at (_chainId p) . _Just -makeGenesisBlockHeaders :: ChainwebVersion -> HashMap ChainId BlockHeader -makeGenesisBlockHeaders v = HM.fromList [ (cid, makeGenesisBlockHeader v cid) | cid <- HS.toList (chainIds v)] +makeGenesisBlockHeaders :: HasVersion => HashMap ChainId BlockHeader +makeGenesisBlockHeaders = + HM.fromList [ (cid, makeGenesisBlockHeader cid) | cid <- HS.toList chainIds] -makeGenesisBlockHeader :: ChainwebVersion -> ChainId -> BlockHeader -makeGenesisBlockHeader v cid = - makeGenesisBlockHeader' v cid (_genesisTime (_versionGenesis v) ^?! atChain cid) (Nonce 0) +makeGenesisBlockHeader :: HasVersion => ChainId -> BlockHeader +makeGenesisBlockHeader cid = + makeGenesisBlockHeader' cid (_genesisTime (_versionGenesis implicitVersion) ^?! atChain cid) (Nonce 0) -- | Like `genesisBlockHeader`, but with slightly more control. -- @@ -705,42 +695,41 @@ makeGenesisBlockHeader v cid = -- building the Merkle tree. -- makeGenesisBlockHeader' - :: HasChainId p - => ChainwebVersion - -> p + :: (HasChainId p, HasVersion) + => p -> BlockCreationTime -> Nonce -> BlockHeader -makeGenesisBlockHeader' v p ct@(BlockCreationTime t) n = +makeGenesisBlockHeader' p ct@(BlockCreationTime t) n = fromLog @ChainwebMerkleHashAlgorithm mlog where - (h, g) = genesisHeightAndGraph v p + (h, g) = genesisHeightAndGraph p cid = _chainId p mlog = newMerkleLog $ mkFeatureFlags :+: ct - :+: genesisParentBlockHash v cid - :+: (v ^?! versionGenesis . genesisBlockTarget . atChain cid) - :+: genesisBlockPayloadHash v cid + :+: genesisParentBlockHash cid + :+: (implicitVersion ^?! versionGenesis . genesisBlockTarget . atChain cid) + :+: genesisBlockPayloadHash cid :+: cid :+: BlockWeight 0 :+: h -- because of chain graph changes (new chains) not all chains start at 0 - :+: _versionCode v + :+: _versionCode implicitVersion :+: EpochStartTime t :+: n :+: MerkleLogBody (blockHashRecordToVector adjParents) adjParents = BlockHashRecord $ HM.fromList $ - (\c -> (c, genesisParentBlockHash v c)) <$> HS.toList (adjacentChainIds g p) + (\c -> (c, genesisParentBlockHash c)) <$> HS.toList (adjacentChainIds g p) -- | The set of genesis block headers as it exited at a particular block height -- genesisBlockHeadersAtHeight - :: ChainwebVersion - -> BlockHeight + :: HasVersion + => BlockHeight -> HashMap ChainId BlockHeader -genesisBlockHeadersAtHeight v h = - HM.filter (\hdr -> _blockHeight hdr <= h) (genesisBlockHeaders v) +genesisBlockHeadersAtHeight h = + HM.filter (\hdr -> _blockHeight hdr <= h) (genesisBlockHeaders implicitVersion) -- -- -------------------------------------------------------------------------- -- -- Genesis Height @@ -753,10 +742,10 @@ genesisBlockHeadersAtHeight v h = -- (We generally assume that this invariant holds throughout the code base. -- It is enforced via the 'mkChainId' smart constructor for ChainId.) -- -genesisHeight :: HasCallStack => ChainwebVersion -> ChainId -> BlockHeight -genesisHeight v c = _blockHeight (genesisBlockHeader v c) +genesisHeight :: (HasVersion, HasCallStack) => ChainId -> BlockHeight +genesisHeight c = _blockHeight (genesisBlockHeader c) -instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag BlockHeader where +instance HasVersion => HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag BlockHeader where -- /IMPORTANT/ a types must occur at most once in this list type MerkleLogHeader BlockHeader = @@ -820,11 +809,10 @@ instance HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag BlockHeader wh :+: nonce :+: MerkleLogBody adjParents ) = _merkleLogEntries l - cwv = lookupVersionByCode cwvc adjGraph - | height == genesisBlockHeight cwv cid = chainGraphAt cwv height - | otherwise = chainGraphAt cwv (height - 1) + | height == genesisBlockHeight cid = chainGraphAt height + | otherwise = chainGraphAt (height - 1) encodeBlockHeaderWithoutHash :: BlockHeader -> Put encodeBlockHeaderWithoutHash b = do @@ -853,13 +841,13 @@ encodeBlockHeader b = do -- on the chain graph. The new format has a fixed size and layout that matches -- the format that has been used in Mainnet01 and Testnet04 since genesis. -- -encodeAsMiningWork :: BlockHeader -> Put +encodeAsMiningWork :: HasVersion => BlockHeader -> Put encodeAsMiningWork b = do encodeFeatureFlags (_blockFlags b) encodeBlockCreationTime (_blockCreationTime b) encodeBlockHash (unwrapParent $ _blockParent b) - if hashedAdjacentRecord (_chainwebVersion b) (_chainId b) (_blockHeight b) + if hashedAdjacentRecord (_chainId b) (_blockHeight b) then do encodeWordLe @Word16 0xf0f0 replicateM_ 3 $ do @@ -882,7 +870,7 @@ encodeAsMiningWork b = do -- 1. chain id is in graph -- 2. all adjacentParent match adjacents in graph -- -decodeBlockHeaderChecked :: Get BlockHeader +decodeBlockHeaderChecked :: HasVersion => Get BlockHeader decodeBlockHeaderChecked = do !bh <- decodeBlockHeader _ <- checkAdjacentChainIds bh bh (Expected $ _blockAdjacentChainIds bh) @@ -895,7 +883,7 @@ decodeBlockHeaderChecked = do -- 3. chainId matches the expected chain id -- decodeBlockHeaderCheckedChainId - :: HasChainId p + :: (HasVersion, HasChainId p) => Expected p -> Get BlockHeader decodeBlockHeaderCheckedChainId p = do @@ -905,7 +893,7 @@ decodeBlockHeaderCheckedChainId p = do -- | Decode a BlockHeader and trust the result -- -decodeBlockHeaderWithoutHash :: Get BlockHeader +decodeBlockHeaderWithoutHash :: HasVersion => Get BlockHeader decodeBlockHeaderWithoutHash = do a0 <- decodeFeatureFlags a1 <- decodeBlockCreationTime @@ -984,7 +972,7 @@ getAdjacentHash p b = firstOf (blockAdjacentHashes . ixg (_chainId p)) b (Actual $ _blockAdjacentChainIds b) {-# INLINE getAdjacentHash #-} -computeBlockHash :: BlockHeader -> BlockHash +computeBlockHash :: HasVersion => BlockHeader -> BlockHash computeBlockHash h = BlockHash $ MerkleLogHash $ computeMerkleLogRoot h {-# INLINE computeBlockHash #-} @@ -992,11 +980,11 @@ computeBlockHash h = BlockHash $ MerkleLogHash $ computeMerkleLogRoot h -- '_blockHash'. The value (interpreted as 'BlockHashNat' must be smaller than -- the value of '_blockTarget' (interpreted as 'BlockHashNat'). -- -_blockPow :: BlockHeader -> PowHash +_blockPow :: HasVersion => BlockHeader -> PowHash _blockPow h = cryptoHash @Blake2s_256 $ runPutS $ encodeAsMiningWork h -blockPow :: Getter BlockHeader PowHash +blockPow :: HasVersion => Getter BlockHeader PowHash blockPow = to _blockPow {-# INLINE blockPow #-} @@ -1027,7 +1015,8 @@ newtype ObjectEncoded a = ObjectEncoded { _objectEncoded :: a } deriving newtype (Eq, Ord, Hashable, NFData) blockHeaderProperties - :: KeyValue e kv + :: HasVersion + => KeyValue e kv => ObjectEncoded BlockHeader -> [kv] blockHeaderProperties (ObjectEncoded b) = @@ -1040,20 +1029,20 @@ blockHeaderProperties (ObjectEncoded b) = , "chainId" .= _chainId b , "weight" .= _blockWeight b , "height" .= _blockHeight b - , "chainwebVersion" .= _versionName (_chainwebVersion b) + , "chainwebVersion" .= _versionName implicitVersion , "epochStart" .= _blockEpochStart b , "featureFlags" .= _blockFlags b , "hash" .= _blockHash b ] {-# INLINE blockHeaderProperties #-} -instance ToJSON (ObjectEncoded BlockHeader) where +instance HasVersion => ToJSON (ObjectEncoded BlockHeader) where toJSON = object . blockHeaderProperties toEncoding = pairs . mconcat . blockHeaderProperties {-# INLINE toJSON #-} {-# INLINE toEncoding #-} -parseBlockHeaderObject :: Object -> Parser BlockHeader +parseBlockHeaderObject :: HasVersion => Object -> Parser BlockHeader parseBlockHeaderObject o = BlockHeader <$> o .: "featureFlags" <*> o .: "creationTime" @@ -1066,12 +1055,20 @@ parseBlockHeaderObject o = BlockHeader <*> o .: "height" -- TODO: lookupVersionByName should probably be deprecated for performance, -- so perhaps we move this codec outside of the node proper. - <*> (_versionCode . lookupVersionByName <$> (o .: "chainwebVersion")) + <*> parseVersionCode <*> o .: "epochStart" <*> o .: "nonce" <*> o .: "hash" - -instance FromJSON (ObjectEncoded BlockHeader) where + where + parseVersionCode = do + v <- o .: "chainwebVersion" + if _versionName implicitVersion == v + then return (_versionCode implicitVersion) + else fail $ T.unpack + $ "invalid chainwebversion. expected " <> getChainwebVersionName (_versionName implicitVersion) + <> ", got " <> getChainwebVersionName v + +instance HasVersion => FromJSON (ObjectEncoded BlockHeader) where parseJSON = withObject "BlockHeader" $ fmap ObjectEncoded . parseBlockHeaderObject {-# INLINE parseJSON #-} @@ -1110,7 +1107,8 @@ instance IsBlockHeader BlockHeader where -- but might be worth it! -- newBlockHeader - :: HM.HashMap ChainId (Parent BlockHeader) + :: HasVersion + => HM.HashMap ChainId (Parent BlockHeader) -- ^ Adjacent parent hashes. The hash and the PoW target of these are -- needed for construction the new header. -> BlockPayloadHash @@ -1133,20 +1131,19 @@ newBlockHeader adj pay nonce t p@(Parent b) = :+: cid :+: _blockWeight b + BlockWeight (targetToDifficulty target) :+: _blockHeight b + 1 - :+: _versionCode v + :+: _versionCode implicitVersion :+: epochStart p adj t :+: nonce :+: MerkleLogBody (blockHashRecordToVector adjHashes) where cid = _chainId p - v = _chainwebVersion p target = powTarget p adj t adjHashes = BlockHashRecord $ fmap _blockHash <$> adj -- -------------------------------------------------------------------------- -- -- TreeDBEntry instance -instance TreeDbEntry BlockHeader where +instance HasVersion => TreeDbEntry BlockHeader where type Key BlockHeader = BlockHash key = _blockHash rank = int . _blockHeight @@ -1170,8 +1167,10 @@ instance IsRanked BlockHeader where -- Note that for all but genesis headers the number of adjacent hashes depends -- on the graph of the parent. -- -headerSizes :: ChainwebVersion -> Rule BlockHeight Natural -headerSizes v = fmap (\g -> _versionHeaderBaseSizeBytes v + 36 * degree g + 2) $ _versionGraphs v +headerSizes :: HasVersion => Rule BlockHeight Natural +headerSizes = + fmap (\g -> _versionHeaderBaseSizeBytes implicitVersion + 36 * degree g + 2) + $ _versionGraphs implicitVersion -- | The size of the serialized block header. -- @@ -1182,18 +1181,17 @@ headerSizes v = fmap (\g -> _versionHeaderBaseSizeBytes v + 36 * degree g + 2) $ -- on the graph of the parent. -- headerSizeBytes - :: HasCallStack - => ChainwebVersion - -> ChainId + :: (HasCallStack, HasVersion) + => ChainId -> BlockHeight -> Natural -headerSizeBytes v cid h = snd +headerSizeBytes cid h = snd $ ruleHead $ ruleDropWhile (> relevantHeight) - $ headerSizes v + $ headerSizes where relevantHeight - | genesisHeight v cid == h = h + | genesisHeight cid == h = h | otherwise = h - 1 -- | The size of the work bytes /without/ the preamble of the chain id and target @@ -1211,14 +1209,12 @@ headerSizeBytes v cid h = snd -- call it a day. -- workSizeBytes - :: HasCallStack - => ChainwebVersion - -> BlockHeight + :: (HasCallStack, HasVersion) + => BlockHeight -> Natural -workSizeBytes v h - | hashedAdjacentRecord (_chainwebVersion v) (unsafeChainId 0) h = - headerSizeBytes Mainnet01 (unsafeChainId 0) 0 - 32 - | otherwise = headerSizeBytes v (unsafeChainId 0) h - 32 +workSizeBytes h + | hashedAdjacentRecord (unsafeChainId 0) h = + headerSizeBytes (unsafeChainId 0) 0 - 32 _rankedBlockHash :: BlockHeader -> RankedBlockHash _rankedBlockHash h = RankedBlockHash diff --git a/src/Chainweb/BlockHeader/Validation.hs b/src/Chainweb/BlockHeader/Validation.hs index 3d8517bc13..d10490d208 100644 --- a/src/Chainweb/BlockHeader/Validation.hs +++ b/src/Chainweb/BlockHeader/Validation.hs @@ -131,11 +131,7 @@ instance HasChainId ValidatedHeader where _chainId = _chainId . _validatedHeader {-# INLINE _chainId #-} -instance HasChainwebVersion ValidatedHeader where - _chainwebVersion = _chainwebVersion . _validatedHeader - {-# INLINE _chainwebVersion #-} - -instance HasChainGraph ValidatedHeader where +instance HasVersion => HasChainGraph ValidatedHeader where _chainGraph = _chainGraph . _validatedHeader {-# INLINE _chainGraph #-} @@ -286,7 +282,7 @@ webStepFailure hp = ValidationFailure (Just $ _webStepAdjs hp) (_webStepHeader hp) -instance Show ValidationFailure where +instance HasVersion => Show ValidationFailure where show (ValidationFailure p as e ts) = T.unpack $ "Validation failure" <> ". Description: " <> T.unlines (map description ts) @@ -377,7 +373,7 @@ data ValidationFailureType -- version of the validated header. deriving (Show, Eq, Ord) -instance Exception ValidationFailure +instance HasVersion => Exception ValidationFailure -- | The list of validation failures that are definite and independent of any -- external context. A block for which validation fails with one of these @@ -442,7 +438,7 @@ isEphemeral failures -- * IncorrectPayloadHash -- validateBlockHeaderM - :: MonadThrow m + :: (MonadThrow m, HasVersion) => Time Micros -- ^ The current clock time -> (ChainValue BlockHash -> m (Maybe BlockHeader)) @@ -470,7 +466,7 @@ validateBlockHeaderM t lookupHeader h = -- * IncorrectPayloadHash -- validateBlockHeadersM - :: MonadThrow m + :: (MonadThrow m, HasVersion) => Time Micros -- ^ The current clock time -> (ChainValue BlockHash -> m (Maybe BlockHeader)) @@ -498,7 +494,7 @@ validateBlockHeadersM t lookupHeader as = do -- performed -- validateIntrinsicM - :: MonadThrow m + :: (MonadThrow m, HasVersion) => Time Micros -- ^ The current clock time -> BlockHeader @@ -516,7 +512,7 @@ validateIntrinsicM t e = unless (null failures) -- performed -- validateInductiveChainM - :: MonadThrow m + :: (MonadThrow m, HasVersion) => (BlockHash -> m (Maybe BlockHeader)) -- ^ Context of Validated BlockHeaders -> BlockHeader @@ -541,7 +537,7 @@ validateInductiveChainM lookupHeader h = -- Returns the parent if it exists or an validation failure otherwise. -- validateBlockParentExists - :: Monad m + :: (Monad m, HasVersion) => (BlockHash -> m (Maybe BlockHeader)) -> BlockHeader -> m (Either ValidationFailureType ChainStep) @@ -557,7 +553,7 @@ validateBlockParentExists lookupParent h -- Returns the parents if they exist or an validation failure otherwise. -- validateAllParentsExist - :: Monad m + :: (Monad m, HasVersion) => (ChainValue BlockHash -> m (Maybe BlockHeader)) -> BlockHeader -> m (Either ValidationFailureType WebStep) @@ -566,10 +562,9 @@ validateAllParentsExist lookupParent h = runExceptT $ WebStep <*> ExceptT (validateBlockParentExists lookupOnChain h) where lookupOnChain = lookupParent . ChainValue (_chainId h) - v = _chainwebVersion h f c ph - | genesisParentBlockHash v c == ph = return - $ Parent $ genesisBlockHeader v c + | genesisParentBlockHash c == ph = return + $ Parent $ genesisBlockHeader c | otherwise = lift (lookupParent $ fmap unwrapParent $ ChainValue c ph) >>= \case (Just !p) -> return $ Parent p Nothing -> throwError MissingAdjacentParent @@ -586,7 +581,8 @@ validateAllParentsExist lookupParent h = runExceptT $ WebStep -- * IncorrectPayloadHash -- isValidBlockHeader - :: Time Micros + :: HasVersion + => Time Micros -- ^ The current clock time -> WebStep -> Bool @@ -602,7 +598,8 @@ isValidBlockHeader t p = null $ validateBlockHeader t p -- * IncorrectPayloadHash -- validateBlockHeader - :: Time Micros + :: HasVersion + => Time Micros -- ^ The current clock time -> WebStep -> [ValidationFailureType] @@ -618,7 +615,8 @@ validateBlockHeader t p -- without observing the remainder of the database. -- validateIntrinsic - :: Time Micros + :: HasVersion + => Time Micros -- ^ The current clock time -> BlockHeader -- ^ block header to be validated @@ -637,7 +635,8 @@ validateIntrinsic t b = concat -- | Validate properties of a block with respect to a given parent. -- validateInductive - :: WebStep + :: HasVersion + => WebStep -> [ValidationFailureType] -- ^ A list of ways in which the block header isn't valid validateInductive ps @@ -645,7 +644,8 @@ validateInductive ps <> validateInductiveWebStep ps validateInductiveChainStep - :: ChainStep + :: HasVersion + => ChainStep -- ^ parent block header. The genesis header is considered its own parent. -> [ValidationFailureType] -- ^ A list of ways in which the block header isn't valid @@ -657,7 +657,8 @@ validateInductiveChainStep s = concat ] validateInductiveWebStep - :: WebStep + :: HasVersion + => WebStep -- ^ parent block header. The genesis header is considered its own parent. -> [ValidationFailureType] -- ^ A list of ways in which the block header isn't valid @@ -674,50 +675,49 @@ validateInductiveWebStep s = concat -- Intrinsic BlockHeader properties -- -------------------------------------------------------------------------- -- -prop_block_pow :: BlockHeader -> Bool +prop_block_pow :: HasVersion => BlockHeader -> Bool prop_block_pow b | isGenesisBlockHeader b = True -- Genesis block headers are not mined. So there's not need for POW - | b ^. chainwebVersion . versionCheats . disablePow = True + | implicitVersion ^. versionCheats . disablePow = True | otherwise = checkTarget (view blockTarget b) (view blockPow b) -prop_block_hash :: BlockHeader -> Bool +prop_block_hash :: HasVersion => BlockHeader -> Bool prop_block_hash b = view blockHash b == computeBlockHash b -prop_block_genesis_parent :: BlockHeader -> Bool +prop_block_genesis_parent :: HasVersion => BlockHeader -> Bool prop_block_genesis_parent b = isGenesisBlockHeader b ==> hasGenesisParentHash b && hasGenesisParentHash b ==> isGenesisBlockHeader b where hasGenesisParentHash b' = - view blockParent b' == genesisParentBlockHash (_chainwebVersion b') (_chainId b') + view blockParent b' == genesisParentBlockHash (_chainId b') -prop_block_genesis_target :: BlockHeader -> Bool +prop_block_genesis_target :: HasVersion => BlockHeader -> Bool prop_block_genesis_target b = isGenesisBlockHeader b - ==> view blockTarget b == _chainwebVersion b ^?! versionGenesis . genesisBlockTarget . atChain (_chainId b) + ==> view blockTarget b == implicitVersion ^?! versionGenesis . genesisBlockTarget . atChain (_chainId b) prop_block_current :: Time Micros -> BlockHeader -> Bool prop_block_current t b = BlockCreationTime t >= view blockCreationTime b -prop_block_featureFlags :: BlockHeader -> Bool +prop_block_featureFlags :: HasVersion => BlockHeader -> Bool prop_block_featureFlags b - | skipFeatureFlagValidationGuard v cid h = True + | skipFeatureFlagValidationGuard cid h = True | otherwise = view blockFlags b == mkFeatureFlags where - v = _chainwebVersion b h = view blockHeight b cid = _chainId b -- | Verify that the adjacent hashes of the block are for the correct set of -- chain ids. -- -prop_block_adjacent_chainIds :: BlockHeader -> Bool +prop_block_adjacent_chainIds :: HasVersion => BlockHeader -> Bool prop_block_adjacent_chainIds b = isJust $ checkAdjacentChainIds adjGraph b (Expected $ view blockAdjacentChainIds b) where adjGraph | isGenesisBlockHeader b = _chainGraph b - | otherwise = chainGraphAt (_chainwebVersion b) (view blockHeight b - 1) + | otherwise = chainGraphAt (view blockHeight b - 1) -- -------------------------------------------------------------------------- -- -- Inductive BlockHeader Properties @@ -726,7 +726,7 @@ prop_block_adjacent_chainIds b -- -------------------------------------------------------------------------- -- -- Single chain inductive properties -prop_block_height :: ChainStep -> Bool +prop_block_height :: HasVersion => ChainStep -> Bool prop_block_height (ChainStep (Parent p) b) | isGenesisBlockHeader b = view blockHeight b == view blockHeight p | otherwise = view blockHeight b == view blockHeight p + 1 @@ -735,7 +735,7 @@ prop_block_chainwebVersion :: ChainStep -> Bool prop_block_chainwebVersion (ChainStep (Parent p) b) = view blockChainwebVersion p == view blockChainwebVersion b -prop_block_weight :: ChainStep -> Bool +prop_block_weight :: HasVersion => ChainStep -> Bool prop_block_weight (ChainStep (Parent p) b) | isGenesisBlockHeader b = view blockWeight b == view blockWeight p | otherwise = view blockWeight b == expectedWeight @@ -749,13 +749,13 @@ prop_block_chainId (ChainStep (Parent p) b) -- -------------------------------------------------------------------------- -- -- Multi chain inductive properties -prop_block_target :: WebStep -> Bool +prop_block_target :: HasVersion => WebStep -> Bool prop_block_target (WebStep as (ChainStep p b)) = view blockTarget b == powTarget p as (view blockCreationTime b) -prop_block_epoch :: WebStep -> Bool +prop_block_epoch :: HasVersion => WebStep -> Bool prop_block_epoch (WebStep as (ChainStep p b)) - | oldDaGuard (_chainwebVersion b) (_chainId b) (view blockHeight b) + | oldDaGuard (_chainId b) (view blockHeight b) = view blockEpochStart b <= EpochStartTime (_bct $ view blockCreationTime b) && view blockEpochStart (unwrapParent p) <= view blockEpochStart b && view blockEpochStart b == epochStart p as (view blockCreationTime b) @@ -763,11 +763,11 @@ prop_block_epoch (WebStep as (ChainStep p b)) = view blockEpochStart b <= EpochStartTime (_bct $ view blockCreationTime b) && view blockEpochStart b == epochStart p as (view blockCreationTime b) -prop_block_creationTime :: WebStep -> Bool +prop_block_creationTime :: HasVersion => WebStep -> Bool prop_block_creationTime (WebStep as (ChainStep (Parent p) b)) | isGenesisBlockHeader b = view blockCreationTime b == view blockCreationTime p - | oldDaGuard (_chainwebVersion b) (_chainId b) (view blockHeight b) + | oldDaGuard (_chainId b) (view blockHeight b) = view blockCreationTime b > view blockCreationTime p | otherwise = view blockCreationTime b > view blockCreationTime p @@ -781,10 +781,10 @@ prop_block_creationTime (WebStep as (ChainStep (Parent p) b)) -- we include it here again as assertion (to double check during testing) and -- for documentation purposes. -- -prop_block_adjacent_parents :: WebStep -> Bool +prop_block_adjacent_parents :: HasVersion => WebStep -> Bool prop_block_adjacent_parents (WebStep as (ChainStep _ b)) | isGenesisBlockHeader b - = adjsHashes == imap (\cid _ -> genesisParentBlockHash v cid) as + = adjsHashes == imap (\cid _ -> genesisParentBlockHash cid) as -- chainId indexes in web adjadent parent record references the -- genesis block parent hashes | otherwise @@ -796,7 +796,6 @@ prop_block_adjacent_parents (WebStep as (ChainStep _ b)) -- it is indexed where adjsHashes = _getBlockHashRecord (view blockAdjacentHashes b) - v = _chainwebVersion b prop_block_adjacent_parents_version :: WebStep -> Bool prop_block_adjacent_parents_version (WebStep as (ChainStep _ b)) diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index 727ec26813..6a8aafea56 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -97,15 +97,11 @@ newtype RankedBlockHeader = RankedBlockHeader { _getRankedBlockHeader :: BlockHe deriving anyclass (NFData) deriving newtype (Hashable, Eq, ToJSON, FromJSON) -instance HasChainwebVersion RankedBlockHeader where - _chainwebVersion = _chainwebVersion . _getRankedBlockHeader - {-# INLINE _chainwebVersion #-} - instance HasChainId RankedBlockHeader where _chainId = _chainId . _getRankedBlockHeader {-# INLINE _chainId #-} -instance HasChainGraph RankedBlockHeader where +instance HasVersion => HasChainGraph RankedBlockHeader where _chainGraph = _chainGraph . _getRankedBlockHeader {-# INLINE _chainGraph #-} @@ -162,11 +158,7 @@ instance HasChainId BlockHeaderDb where _chainId = _chainDbId {-# INLINE _chainId #-} -instance HasChainwebVersion BlockHeaderDb where - _chainwebVersion = _chainDbChainwebVersion - {-# INLINE _chainwebVersion #-} - -instance (k ~ CasKeyType BlockHeader) => ReadableTable BlockHeaderDb k BlockHeader where +instance (k ~ CasKeyType BlockHeader, HasVersion) => ReadableTable BlockHeaderDb k BlockHeader where tableLookup = lookup {-# INLINE tableLookup #-} @@ -179,7 +171,7 @@ instance (k ~ CasKeyType BlockHeader) => ReadableTable BlockHeaderDb k BlockHead -- -- Updates all indices. -- -dbAddChecked :: BlockHeaderDb -> BlockHeader -> IO () +dbAddChecked :: HasVersion => BlockHeaderDb -> BlockHeader -> IO () dbAddChecked db e = unlessM (tableMember (_chainDbCas db) ek) dbAddCheckedInternal where r = int $ rank e @@ -215,7 +207,7 @@ dbAddChecked db e = unlessM (tableMember (_chainDbCas db) ek) dbAddCheckedIntern -- | Initialize a database handle -- -initBlockHeaderDb :: Configuration -> IO BlockHeaderDb +initBlockHeaderDb :: HasVersion => Configuration -> IO BlockHeaderDb initBlockHeaderDb config = do dbAddChecked db rootEntry return db @@ -237,7 +229,7 @@ initBlockHeaderDb config = do ["BlockHeader", cidNs, "rank"] !db = BlockHeaderDb cid - (_chainwebVersion rootEntry) + implicitVersion headerTable rankTable @@ -247,14 +239,14 @@ closeBlockHeaderDb :: BlockHeaderDb -> IO () closeBlockHeaderDb _ = return () withBlockHeaderDb - :: RocksDb - -> ChainwebVersion + :: HasVersion + => RocksDb -> ChainId -> ResourceT IO BlockHeaderDb -withBlockHeaderDb db v cid = snd <$> allocate start closeBlockHeaderDb +withBlockHeaderDb db cid = snd <$> allocate start closeBlockHeaderDb where start = initBlockHeaderDb Configuration - { _configRoot = genesisBlockHeader v cid + { _configRoot = genesisBlockHeader cid , _configRocksDb = db } @@ -264,7 +256,7 @@ withBlockHeaderDb db v cid = snd <$> allocate start closeBlockHeaderDb -- | TODO provide more efficient branchEntries implementation that uses -- iterators. -- -instance TreeDb BlockHeaderDb where +instance HasVersion => TreeDb BlockHeaderDb where type DbEntry BlockHeaderDb = BlockHeader lookup db h = runMaybeT $ do @@ -374,10 +366,10 @@ seekTreeDb db k mir it = do -- -------------------------------------------------------------------------- -- -- Insertions -insertBlockHeaderDb :: BlockHeaderDb -> ValidatedHeader -> IO () +insertBlockHeaderDb :: HasVersion => BlockHeaderDb -> ValidatedHeader -> IO () insertBlockHeaderDb db = dbAddChecked db . _validatedHeader {-# INLINE insertBlockHeaderDb #-} -unsafeInsertBlockHeaderDb :: BlockHeaderDb -> BlockHeader -> IO () +unsafeInsertBlockHeaderDb :: HasVersion => BlockHeaderDb -> BlockHeader -> IO () unsafeInsertBlockHeaderDb = dbAddChecked {-# INLINE unsafeInsertBlockHeaderDb #-} diff --git a/src/Chainweb/BlockHeaderDB/PruneForks.hs b/src/Chainweb/BlockHeaderDB/PruneForks.hs index a6a7fef2d6..87b3200363 100644 --- a/src/Chainweb/BlockHeaderDB/PruneForks.hs +++ b/src/Chainweb/BlockHeaderDB/PruneForks.hs @@ -67,7 +67,8 @@ import Data.LogMessage -- Chain Database Pruning pruneForksLogg - :: Logger logger + :: HasVersion + => Logger logger => logger -> BlockHeaderDb -> Natural @@ -102,7 +103,8 @@ pruneForksLogg = pruneForks . logFunctionText -- indicates whether the related block is pruned or not. -- pruneForks - :: LogFunctionText + :: HasVersion + => LogFunctionText -> BlockHeaderDb -> Natural -- ^ The depth at which pruning starts. Block at this depth are used as @@ -137,9 +139,8 @@ pruneForks logg cdb depth callback = do let mar = MaxRank $ Max $ int (view blockHeight hdr) - depth pruneForks_ logg cdb mar (MinRank $ Min $ int genHeight) callback where - v = _chainwebVersion cdb cid = _chainId cdb - genHeight = genesisHeight v cid + genHeight = genesisHeight cid data PruneForksException = PruneForksDbInvariantViolation BlockHeight [BlockHeight] T.Text @@ -158,7 +159,7 @@ instance Exception PruneForksException -- TODO add option to also validate the block headers -- pruneForks_ - :: HasCallStack + :: (HasCallStack, HasVersion) => LogFunctionText -> BlockHeaderDb -> MaxRank diff --git a/src/Chainweb/BlockHeaderDB/RemoteDB.hs b/src/Chainweb/BlockHeaderDB/RemoteDB.hs index 9c939c4565..a2184dc83f 100644 --- a/src/Chainweb/BlockHeaderDB/RemoteDB.hs +++ b/src/Chainweb/BlockHeaderDB/RemoteDB.hs @@ -54,51 +54,50 @@ import Data.LogMessage data RemoteDb = RemoteDb { _remoteEnv :: !ClientEnv , _remoteLogFunction :: !ALogFunction - , _remoteVersion :: !ChainwebVersion , _remoteChainId :: {-# UNPACK #-} !ChainId } -instance TreeDb RemoteDb where +instance HasVersion => TreeDb RemoteDb where type DbEntry RemoteDb = BlockHeader maxEntry = error "Chainweb.TreeDB.RemoteDB.RemoteDb.maxEntry: not implemented" -- If other default functions rely on this, it could be quite inefficient. - lookup (RemoteDb env alog ver cid) k = hush <$> runClientM client env + lookup (RemoteDb env alog cid) k = hush <$> runClientM client env where client = logServantError alog "failed to query tree db entry" - $ headerClient ver cid k + $ headerClient cid k - keys (RemoteDb env alog ver cid) next limit minr maxr f + keys (RemoteDb env alog cid) next limit minr maxr f = f $ callAndPage client next 0 env where client :: Maybe (NextItem BlockHash) -> ClientM (Page (NextItem BlockHash) BlockHash) client nxt = logServantError alog "failed to query tree db keys" - $ hashesClient ver cid limit nxt minr maxr + $ hashesClient cid limit nxt minr maxr - entries (RemoteDb env alog ver cid) next limit minr maxr f + entries (RemoteDb env alog cid) next limit minr maxr f = f $ callAndPage client next 0 env where client :: Maybe (NextItem BlockHash) -> ClientM (Page (NextItem BlockHash) BlockHeader) client nxt = logServantError alog "failed to query tree db entries" - $ headersClient ver cid limit nxt minr maxr + $ headersClient cid limit nxt minr maxr - branchKeys (RemoteDb env alog ver cid) next limit minr maxr lower upper f + branchKeys (RemoteDb env alog cid) next limit minr maxr lower upper f = f $ callAndPage client next 0 env where client :: Maybe (NextItem BlockHash) -> ClientM (Page (NextItem BlockHash) BlockHash) client nxt = logServantError alog "failed to query remote branch keys" - $ branchHashesClient ver cid limit nxt minr maxr (BranchBounds lower upper) + $ branchHashesClient cid limit nxt minr maxr (BranchBounds lower upper) - branchEntries (RemoteDb env alog ver cid) next limit minr maxr lower upper f + branchEntries (RemoteDb env alog cid) next limit minr maxr lower upper f = f $ callAndPage client next 0 env where client :: Maybe (NextItem BlockHash) -> ClientM (Page (NextItem BlockHash) BlockHeader) client nxt = logServantError alog "failed to query remote branch entries" - $ branchHeadersClient ver cid limit nxt minr maxr (BranchBounds lower upper) + $ branchHeadersClient cid limit nxt minr maxr (BranchBounds lower upper) -- We could either use the cut or create a new API - -- maxEntry (RemoteDb env alog ver cid) e = + -- maxEntry (RemoteDb env alog cid) e = logServantError :: ALogFunction -> T.Text -> ClientM a -> ClientM a logServantError alog msg = handle $ \(e :: ClientError) -> do @@ -138,4 +137,4 @@ remoteDb -> IO RemoteDb remoteDb db logg env = do h <- root db - pure $! RemoteDb env (ALogFunction logg) (_chainwebVersion h) (view blockChainId h) + pure $! RemoteDb env (ALogFunction logg) (view blockChainId h) diff --git a/src/Chainweb/BlockHeaderDB/RestAPI.hs b/src/Chainweb/BlockHeaderDB/RestAPI.hs index 5dce533f55..39890aabb7 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI.hs @@ -119,19 +119,19 @@ type BlockHeaderPage = Page (NextItem BlockHash) BlockHeader -- because this endpoint is only used on the service API, we assume clients -- want object-encoded block headers. -blockProperties :: KeyValue e kv => Block -> [kv] +blockProperties :: (HasVersion, KeyValue e kv) => Block -> [kv] blockProperties o = [ "header" .= ObjectEncoded (_blockHeader o) , "payloadWithOutputs" .= _blockPayloadWithOutputs o ] -instance ToJSON Block where +instance HasVersion => ToJSON Block where toJSON = object . blockProperties toEncoding = pairs . mconcat . blockProperties {-# INLINE toJSON #-} {-# INLINE toEncoding #-} -instance FromJSON Block where +instance HasVersion => FromJSON Block where parseJSON = withObject "Block" $ \o -> Block <$> (_objectEncoded <$> o .: "header") <*> o .: "payloadWithOutputs" @@ -155,7 +155,7 @@ instance MimeRender OctetStream BlockHeader where {-# INLINE mimeRender #-} -- | Orphan instance to encode pages of blocks as JSON -instance MimeRender JSON BlockPage where +instance HasVersion => MimeRender JSON BlockPage where mimeRender _ = encode {-# INLINE mimeRender #-} @@ -172,19 +172,19 @@ data JsonBlockHeaderObject instance Accept JsonBlockHeaderObject where contentType _ = "application" // "json" /: ("blockheader-encoding", "object") -instance MimeUnrender JsonBlockHeaderObject BlockHeader where +instance HasVersion => MimeUnrender JsonBlockHeaderObject BlockHeader where mimeUnrender _ = second _objectEncoded . eitherDecode {-# INLINE mimeUnrender #-} -instance MimeRender JsonBlockHeaderObject BlockHeader where +instance HasVersion => MimeRender JsonBlockHeaderObject BlockHeader where mimeRender _ = encode . ObjectEncoded {-# INLINE mimeRender #-} -instance MimeUnrender JsonBlockHeaderObject BlockHeaderPage where +instance HasVersion => MimeUnrender JsonBlockHeaderObject BlockHeaderPage where mimeUnrender _ = second (fmap _objectEncoded) . eitherDecode {-# INLINE mimeUnrender #-} -instance MimeRender JsonBlockHeaderObject BlockHeaderPage where +instance HasVersion => MimeRender JsonBlockHeaderObject BlockHeaderPage where mimeRender _ = encode . fmap ObjectEncoded {-# INLINE mimeRender #-} @@ -272,10 +272,10 @@ data SomeBlockHeaderDb = forall v c . (KnownChainwebVersionSymbol v, KnownChainIdSymbol c) => SomeBlockHeaderDb (BlockHeaderDb_ v c) -someBlockHeaderDbVal :: ChainwebVersion -> ChainId -> BlockHeaderDb -> SomeBlockHeaderDb -someBlockHeaderDbVal v cid db = case someChainwebVersionVal v of - (SomeChainwebVersionT (Proxy :: Proxy vt)) -> case someChainIdVal cid of - (SomeChainIdT (Proxy :: Proxy cidt)) -> SomeBlockHeaderDb (BlockHeaderDb_ @vt @cidt db) +someBlockHeaderDbVal :: HasVersion => ChainId -> BlockHeaderDb -> SomeBlockHeaderDb +someBlockHeaderDbVal cid db = case someChainwebVersionVal of + SomeChainwebVersionT (Proxy :: Proxy vt) -> case someChainIdVal cid of + SomeChainIdT (Proxy :: Proxy cidt) -> SomeBlockHeaderDb (BlockHeaderDb_ @vt @cidt db) -- -------------------------------------------------------------------------- -- -- Query Parameters @@ -492,7 +492,7 @@ data HeaderUpdate = HeaderUpdate } deriving (Show, Eq) -headerUpdateProperties :: KeyValue e kv => HeaderUpdate -> [kv] +headerUpdateProperties :: HasVersion => KeyValue e kv => HeaderUpdate -> [kv] headerUpdateProperties o = [ "header" .= _huHeader o , "powHash" .= _huPowHash o @@ -500,13 +500,13 @@ headerUpdateProperties o = ] {-# INLINE headerUpdateProperties #-} -instance ToJSON HeaderUpdate where +instance HasVersion => ToJSON HeaderUpdate where toJSON = object . headerUpdateProperties toEncoding = pairs . mconcat . headerUpdateProperties {-# INLINE toJSON #-} {-# INLINE toEncoding #-} -instance FromJSON HeaderUpdate where +instance HasVersion => FromJSON HeaderUpdate where parseJSON = withObject "HeaderUpdate" $ \o -> HeaderUpdate <$> o .: "header" <*> o .: "powHash" diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Client.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Client.hs index 36217aa8b4..7ddd19ce46 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Client.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Client.hs @@ -78,8 +78,8 @@ headerClient_ headerClient_ = headerClientContentType_ @v @c @OctetStream headerClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> DbKey BlockHeaderDb -> ClientM BlockHeader headerClient = headerClientJsonBinary @@ -96,32 +96,32 @@ headerClientContentType_ headerClientContentType_ = client (Proxy @(SetRespBodyContentType ct x)) headerClientJson - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> DbKey BlockHeaderDb -> ClientM BlockHeader -headerClientJson v c k = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +headerClientJson c k = runIdentity $ do + SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ headerClientContentType_ @v @c @JSON k headerClientJsonPretty - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BlockHash -> ClientM BlockHeader -headerClientJsonPretty v c k = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) - (SomeSing (SChainId :: Sing c)) <- return $ toSing c +headerClientJsonPretty c k = runIdentity $ do + SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName implicitVersion) + SomeSing (SChainId :: Sing c) <- return $ toSing c return $ headerClientContentType_ @v @c @JsonBlockHeaderObject k headerClientJsonBinary - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BlockHash -> ClientM BlockHeader -headerClientJsonBinary v c k = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +headerClientJsonBinary c k = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ headerClientContentType_ @v @c @OctetStream k @@ -140,9 +140,9 @@ headersClient_ headersClient_ = headersClientContentType_ @v @c @JSON headersClient - :: ChainwebVersion + :: HasVersion -- ^ The remote chainweb that you wish to query from. - -> ChainId + => ChainId -- ^ The remote chain within the web that you wish to query from. -> Maybe Limit -- ^ The number of responses per-`Page` to return. @@ -172,9 +172,9 @@ headersClientContentType_ headersClientContentType_ = client $ Proxy @(SetRespBodyContentType ct (HeadersApi v c)) headersClientJson - :: ChainwebVersion + :: HasVersion -- ^ The remote chainweb that you wish to query from. - -> ChainId + => ChainId -- ^ The remote chain within the web that you wish to query from. -> Maybe Limit -- ^ The number of responses per-`Page` to return. @@ -187,15 +187,15 @@ headersClientJson -> Maybe MaxRank -- ^ Filter: no header of `BlockHeight` higher than this will be returned. -> ClientM BlockHeaderPage -headersClientJson v c limit start minr maxr = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +headersClientJson c limit start minr maxr = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ headersClientContentType_ @v @c @JSON limit start minr maxr headersClientJsonPretty - :: ChainwebVersion + :: HasVersion -- ^ The remote chainweb that you wish to query from. - -> ChainId + => ChainId -- ^ The remote chain within the web that you wish to query from. -> Maybe Limit -- ^ The number of responses per-`Page` to return. @@ -208,8 +208,9 @@ headersClientJsonPretty -> Maybe MaxRank -- ^ Filter: no header of `BlockHeight` higher than this will be returned. -> ClientM BlockHeaderPage -headersClientJsonPretty (FromSingChainwebVersion (SChainwebVersion :: Sing v)) c limit start minr maxr = runIdentity $ do - (SomeSing (SChainId :: Sing c)) <- return $ toSing c +headersClientJsonPretty c limit start minr maxr = runIdentity $ do + SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName implicitVersion) + SomeSing (SChainId :: Sing c) <- return $ toSing c return $ headersClientContentType_ @v @c @JsonBlockHeaderObject limit start minr maxr -- -------------------------------------------------------------------------- -- @@ -228,16 +229,16 @@ branchHashesClient_ branchHashesClient_ = client (branchHashesApi @v @c) branchHashesClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank -> Maybe MaxRank -> BranchBounds BlockHeaderDb -> ClientM BlockHashPage -branchHashesClient v c limit start minr maxr bounds = runIdentity $ do - SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName v) +branchHashesClient c limit start minr maxr bounds = runIdentity $ do + SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName implicitVersion) SomeSing (SChainId :: Sing c) <- return $ toSing c return $ branchHashesClient_ @v @c limit start minr maxr bounds @@ -257,8 +258,8 @@ branchHeadersClient_ branchHeadersClient_ = branchHeadersClientContentType_ @v @c @JSON branchHeadersClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank @@ -283,30 +284,30 @@ branchHeadersClientContentType_ = client $ Proxy @(SetRespBodyContentType ct (BranchHeadersApi v c)) branchHeadersClientJson - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank -> Maybe MaxRank -> BranchBounds BlockHeaderDb -> ClientM BlockHeaderPage -branchHeadersClientJson v c limit start minr maxr bounds = runIdentity $ do - SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName v) +branchHeadersClientJson c limit start minr maxr bounds = runIdentity $ do + SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName implicitVersion) SomeSing (SChainId :: Sing c) <- return $ toSing c return $ branchHeadersClientContentType_ @v @c @JSON limit start minr maxr bounds branchHeadersClientJsonPretty - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank -> Maybe MaxRank -> BranchBounds BlockHeaderDb -> ClientM BlockHeaderPage -branchHeadersClientJsonPretty v c limit start minr maxr bounds = runIdentity $ do - SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName v) +branchHeadersClientJsonPretty c limit start minr maxr bounds = runIdentity $ do + SomeSing (SChainwebVersion :: Sing v) <- return $ toSing (_versionName implicitVersion) SomeSing (SChainId :: Sing c) <- return $ toSing c return $ branchHeadersClientContentType_ @v @c @JsonBlockHeaderObject limit start minr maxr bounds @@ -325,14 +326,14 @@ hashesClient_ hashesClient_ = client (hashesApi @v @c) hashesClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank -> Maybe MaxRank -> ClientM BlockHashPage -hashesClient v c limit start minr maxr = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +hashesClient c limit start minr maxr = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ hashesClient_ @v @c limit start minr maxr diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index 368c21590d..1c486cf770 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -274,7 +274,8 @@ headerHandler db k = liftIO (lookup db k) >>= \case -- Full BlockHeader DB API (used for Service API) -- blockHeaderDbServer - :: BlockHeaderDb_ v c + :: HasVersion + => BlockHeaderDb_ v c -> Server (BlockHeaderDbApi v c) blockHeaderDbServer (BlockHeaderDb_ db) = hashesHandler db @@ -285,7 +286,7 @@ blockHeaderDbServer (BlockHeaderDb_ db) -- Restricted P2P BlockHeader DB API -- -p2pBlockHeaderDbServer :: BlockHeaderDb_ v c -> Server (P2pBlockHeaderDbApi v c) +p2pBlockHeaderDbServer :: HasVersion => BlockHeaderDb_ v c -> Server (P2pBlockHeaderDbApi v c) p2pBlockHeaderDbServer (BlockHeaderDb_ db) = headersHandler db p2pEntryLimit :<|> headerHandler db @@ -295,35 +296,37 @@ p2pBlockHeaderDbServer (BlockHeaderDb_ db) -- Multichain Server someBlockHeaderDbServer - :: SomeBlockHeaderDb + :: HasVersion + => SomeBlockHeaderDb -> SomeServer someBlockHeaderDbServer (SomeBlockHeaderDb (db :: BlockHeaderDb_ v c)) = SomeServer (Proxy @(BlockHeaderDbApi v c)) (blockHeaderDbServer db) someBlockHeaderDbServers - :: ChainwebVersion - -> ChainMap BlockHeaderDb + :: HasVersion + => ChainMap BlockHeaderDb -> SomeServer -someBlockHeaderDbServers v cdbs = ifoldMap - (\cid cdb -> someBlockHeaderDbServer (someBlockHeaderDbVal v cid cdb)) +someBlockHeaderDbServers cdbs = ifoldMap + (\cid cdb -> someBlockHeaderDbServer (someBlockHeaderDbVal cid cdb)) cdbs -someP2pBlockHeaderDbServer :: SomeBlockHeaderDb -> SomeServer +someP2pBlockHeaderDbServer :: HasVersion => SomeBlockHeaderDb -> SomeServer someP2pBlockHeaderDbServer (SomeBlockHeaderDb (db :: BlockHeaderDb_ v c)) = SomeServer (Proxy @(P2pBlockHeaderDbApi v c)) (p2pBlockHeaderDbServer db) -someP2pBlockHeaderDbServers :: ChainwebVersion -> ChainMap BlockHeaderDb -> SomeServer -someP2pBlockHeaderDbServers v = ifoldMap - (\cid bhdb -> someP2pBlockHeaderDbServer (someBlockHeaderDbVal v cid bhdb)) +someP2pBlockHeaderDbServers :: HasVersion => ChainMap BlockHeaderDb -> SomeServer +someP2pBlockHeaderDbServers = ifoldMap + (\cid bhdb -> someP2pBlockHeaderDbServer (someBlockHeaderDbVal cid bhdb)) -- -------------------------------------------------------------------------- -- -- BlockHeader Event Stream -someBlockStreamServer :: ChainwebVersion -> CutDb -> SomeServer -someBlockStreamServer (FromSingChainwebVersion (SChainwebVersion :: Sing v)) cdb = - SomeServer (Proxy @(BlockStreamApi v)) $ blockStreamHandler cdb +someBlockStreamServer :: HasVersion => CutDb -> SomeServer +someBlockStreamServer cdb = runIdentity $ do + SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal + Identity $ SomeServer (Proxy @(BlockStreamApi v)) $ blockStreamHandler cdb -blockStreamHandler :: CutDb -> Tagged Handler Application +blockStreamHandler :: HasVersion => CutDb -> Tagged Handler Application blockStreamHandler db = Tagged $ \req resp -> do streamRef <- newIORef $ SP.map f $ SP.mapM g $ SP.concat $ blockDiffStream db eventSourceAppIO (run streamRef) req resp diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 4ed9d88a0e..ca0cf2d2e6 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -199,10 +199,6 @@ makeLenses ''Chainweb chainwebSocket :: Getter (Chainweb logger) Socket chainwebSocket = chainwebPeer . peerResSocket -instance HasChainwebVersion (Chainweb logger) where - _chainwebVersion = _chainwebVersion . _chainwebCutResources - {-# INLINE _chainwebVersion #-} - -- Intializes all service API chainweb components but doesn't start any networking. -- withChainweb @@ -216,32 +212,31 @@ withChainweb -> (StartedChainweb logger -> IO ()) -> IO () withChainweb c logger rocksDb pactDbDir backupDir inner = - withPeerResources v (_configP2p confWithBootstraps) logger $ \logger' peerRes -> - withSocket serviceApiPort serviceApiHost $ \serviceSock -> do - let conf' = confWithBootstraps - & set configP2p (_peerResConfig peerRes) - & set (configServiceApi . serviceApiConfigPort) (fst serviceSock) - withChainwebInternal - conf' - logger' - peerRes - serviceSock - rocksDb - pactDbDir - backupDir - inner + withVersion (c ^. configChainwebVersion) $ + withPeerResources (view configP2p confWithBootstraps) logger $ \logger' peerRes -> + withSocket serviceApiPort serviceApiHost $ \serviceSock -> do + let conf' = confWithBootstraps + & set configP2p (_peerResConfig peerRes) + & set (configServiceApi . serviceApiConfigPort) (fst serviceSock) + withChainwebInternal + conf' + logger' + peerRes + serviceSock + rocksDb + pactDbDir + backupDir + inner where serviceApiPort = _serviceApiConfigPort $ _configServiceApi c serviceApiHost = _serviceApiConfigInterface $ _configServiceApi c - v = _chainwebVersion c - -- Here we inject the hard-coded bootstrap peer infos for the configured -- chainweb version into the configuration. confWithBootstraps | _p2pConfigIgnoreBootstrapNodes (_configP2p c) = c | otherwise = configP2p . p2pConfigKnownPeers - %~ (\x -> L.nub $ x <> _versionBootstraps v) $ c + %~ (\x -> L.nub $ x <> _versionBootstraps (c ^. configChainwebVersion)) $ c data StartedChainweb logger where StartedChainweb @@ -267,7 +262,8 @@ data ChainwebStatus -- withChainwebInternal :: forall logger - . Logger logger + . Logger logger + => HasVersion => ChainwebConfiguration -> logger -> PeerResources logger @@ -295,7 +291,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir runResourceT $ do cr <- withChainResources (chainLogger cid) - v cid rocksDb (_peerResManager peerRes) @@ -316,10 +311,8 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir ) (onChains [(cid, cid) | cid <- cidsList]) where - v = _configChainwebVersion conf - cids :: HS.HashSet ChainId - cids = chainIds v + cids = chainIds cidsList :: [ChainId] cidsList = toList cids @@ -341,7 +334,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir -- FIXME: make this configurable cutDbParams :: CutDbParams - cutDbParams = (defaultCutDbParams v $ _cutFetchTimeout cutConf) + cutDbParams = (defaultCutDbParams $ _cutFetchTimeout cutConf) { _cutDbParamsLogLevel = Info , _cutDbParamsTelemetryLevel = Info , _cutDbParamsInitialHeightLimit = _cutInitialBlockHeightLimit cutConf @@ -381,7 +374,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir :: ChainMap (ChainResources logger) -> IO () global cs = runResourceT $ do - let !webchain = mkWebBlockHeaderDb v (fmap _chainResBlockHeaderDb cs) + let !webchain = mkWebBlockHeaderDb (fmap _chainResBlockHeaderDb cs) -- !pact = mkWebPactExecutionService (HM.map _chainResPact cs) !providers = payloadProvidersForAllChains cs !cutLogger = setComponent "cut" logger @@ -631,6 +624,7 @@ makeLenses ''NowServing runChainweb :: forall logger . Logger logger + => HasVersion => Chainweb logger -> ((NowServing -> NowServing) -> IO ()) -> IO () @@ -639,7 +633,7 @@ runChainweb cw nowServing = do -- Create OpenAPI Validation Middlewars mkValidationMiddleware <- interleaveIO $ - OpenAPIValidation.mkValidationMiddleware (_chainwebLogger cw) (_chainwebVersion cw) (_chainwebManager cw) + OpenAPIValidation.mkValidationMiddleware (_chainwebLogger cw) (_chainwebManager cw) p2pValidationMiddleware <- if _p2pConfigValidateSpec (_configP2p $ _chainwebConfig cw) then do @@ -681,12 +675,10 @@ runChainweb cw nowServing = do clients :: IO () clients = do - mpClients <- mempoolSyncClients concurrentlies_ $ concat [ miner , cutNetworks (_chainwebCutResources cw) , runP2pNodesOfAllChains (_chainwebChains cw) - , mpClients ] logg :: LogFunctionText @@ -853,7 +845,6 @@ runChainweb cw nowServing = do serveServiceApiSocket (serviceApiServerSettings clientClosedConnectionsCounter (fst $ _chainwebServiceSocket cw) serviceApiHost) (snd $ _chainwebServiceSocket cw) - (_chainwebVersion cw) ChainwebServerDbs { _chainwebServerCutDb = Just cutDb , _chainwebServerBlockHeaderDbs = chainDbsToServe @@ -884,35 +875,4 @@ runChainweb cw nowServing = do cutPeerDb = _cutResPeerDb $ _chainwebCutResources cw miner :: [IO ()] - miner = maybe [] (\m -> [ runMiner (_chainwebVersion cw) m ]) $ _chainwebMiner cw - - -- Mempool - - mempoolP2pConfig :: EnableConfig MempoolP2pConfig - -- mempoolP2pConfig = _configMempoolP2p $ _chainwebConfig cw - -- FIXME - mempoolP2pConfig = error "Chainweb.Chainweb.runChainweb: FIXME Pact mempoolP2pConfig moved into Pact and is not yet implemented" - - -- | Decide whether to enable the mempool sync clients. - -- - -- FIXME: Pact mempools are now part of the pact payload provider and are - -- configured there. - -- - mempoolSyncClients :: IO [IO ()] - mempoolSyncClients = return [] - -- mempoolSyncClients = case enabledConfig mempoolP2pConfig of - -- Nothing -> disabled - -- Just c - -- | cw ^. chainwebVersion . versionDefaults . disableMempoolSync -> disabled - -- | otherwise -> enabled c - -- where - -- disabled = do - -- logg Info "Mempool p2p sync disabled" - -- return [] - -- enabled conf = do - -- logg Info "Mempool p2p sync enabled" - -- -- FIXME FIXME FIXME - -- -- return $ map (runMempoolSyncClient mgr conf (_chainwebPeer cw)) chainVals - -- logg Warn "Overwriting mempool p2p sync client configuration. It is currently not supported" - -- logg Warn "Chainweb.Chainweb.runChainweb.mempoolSyncClient: Pact mempool synchronization is currently disabled" - -- return [] + miner = maybe [] (\m -> [ runMiner m ]) $ _chainwebMiner cw diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index bc38f38f6b..6ce0a34ae2 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -133,12 +133,10 @@ data PayloadP2pResources = PayloadP2pResources -- ^ API endpoints that are are served by the node P2P API } -instance HasChainwebVersion PayloadP2pResources where - _chainwebVersion = _chainwebVersion . _payloadResPeerDb - payloadP2pResources :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) logger tbl . Logger logger + => HasVersion => ReadableTable tbl RankedBlockPayloadHash (PayloadType p) => KnownChainwebVersionSymbol v => KnownChainIdSymbol c @@ -181,7 +179,7 @@ payloadP2pResources logger p2pConfig myInfo peerDb tbl queue mgr = do -- | IO actions for running Payload P2p Nodes -- -runPayloadP2pNodes :: PayloadP2pResources -> [IO ()] +runPayloadP2pNodes :: HasVersion => PayloadP2pResources -> [IO ()] runPayloadP2pNodes r = [ p2pRunNode (_payloadResP2pNode r) ] -- -------------------------------------------------------------------------- -- @@ -223,8 +221,8 @@ makeLenses ''ProviderResources withPayloadProviderResources :: Logger logger + => HasVersion => logger - -> ChainwebVersion -> ChainId -> P2pConfiguration -> PeerInfo @@ -237,8 +235,8 @@ withPayloadProviderResources -- ^ whether to allow unlimited rewind on startup -> PayloadProviderConfig -> ResourceT IO ProviderResources -withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs = do - SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal v +withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs = do + SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal SomeChainIdT @c' _ <- return $ someChainIdVal cid withSomeSing provider $ \case SMinimalProvider -> do @@ -252,7 +250,7 @@ withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewind -- provider. let config = _payloadProviderConfigMinimal configs - p <- liftIO $ newMinimalPayloadProvider logger v cid rdb (Just mgr) config + p <- liftIO $ newMinimalPayloadProvider logger cid rdb (Just mgr) config let pdb = view minimalPayloadDb p let queue = view minimalPayloadQueue p p2pRes <- liftIO $ payloadP2pResources @v' @c' @'MinimalProvider @@ -271,7 +269,7 @@ withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewind -- FIXME move the following to the pact provider initialization - let maxGasLimit = Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit v maxBound + let maxGasLimit = Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit maxBound case maxGasLimit of Just maxGasLimit' | _pactConfigBlockGasLimit conf > maxGasLimit' -> @@ -300,7 +298,7 @@ withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewind rec pp <- withPactPayloadProvider - (_chainwebVersion v) cid + cid (Just mgr) logger Nothing @@ -308,10 +306,10 @@ withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewind pdb pactDbDir pactConfig - (Pact.genesisPayload (_chainwebVersion v) ^? atChain cid) + (Pact.genesisPayload ^? atChain cid) let mempoolConfig = Mempool.validatingMempoolConfig - cid (_chainwebVersion v) + cid (_pactNewBlockGasLimit pactConfig) (_pactConfigMinGasPrice conf) (\txs -> @@ -319,7 +317,7 @@ withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewind (pactPayloadProviderLogger pp) (pactPayloadProviderServiceEnv pp) txs ) - mempool <- Mempool.withInMemoryMempool (setComponent "mempool" logger) mempoolConfig v + mempool <- Mempool.withInMemoryMempool (setComponent "mempool" logger) mempoolConfig let queue = _payloadStoreQueue $ _psPdb $ pactPayloadProviderServiceEnv pp p2pRes <- liftIO $ payloadP2pResources @v' @c' @'PactProvider logger p2pConfig myInfo peerDb pdb queue mgr @@ -331,13 +329,13 @@ withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewind , Pact._pactServerDataPact = pactPayloadProviderServiceEnv pp } - let pactServer = Pact.somePactServer (Pact.somePactServerData v cid pactServerData) + let pactServer = Pact.somePactServer (Pact.somePactServerData cid pactServerData) return ProviderResources { _providerResPayloadProvider = ConfiguredPayloadProvider pp , _providerResServiceApi = Just $ PayloadServiceApiResources -- TODO: I think this isn't what was in mind for this... -- this seems to really just be for the payload API - { _payloadResServiceApi = Pact.somePactServiceApi v cid + { _payloadResServiceApi = Pact.somePactServiceApi cid , _payloadResServiceServer = pactServer } , _providerResP2pApiResources = Just p2pRes @@ -351,7 +349,7 @@ withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewind -- and answering API requests. -- It also starts to awaiting and devlivering new payloads if mining -- is enabled. - p <- withEvmPayloadProvider logger v cid rdb (Just mgr) config + p <- withEvmPayloadProvider logger cid rdb (Just mgr) config let pdb = view evmPayloadDb p let queue = view evmPayloadQueue p p2pRes <- liftIO $ payloadP2pResources @v' @c' @('EvmProvider n) @@ -365,7 +363,7 @@ withPayloadProviderResources logger v cid p2pConfig myInfo peerDb rdb mgr rewind where provider :: PayloadProviderType - provider = payloadProviderTypeForChain v cid + provider = payloadProviderTypeForChain cid -- -------------------------------------------------------------------------- -- -- Single Chain Resources @@ -390,18 +388,14 @@ _chainResServiceApiResources -> Maybe PayloadServiceApiResources _chainResServiceApiResources = _providerResServiceApi . _chainResPayloadProvider -instance HasChainwebVersion (ChainResources logger) where - _chainwebVersion = _chainwebVersion . _chainResBlockHeaderDb - {-# INLINE _chainwebVersion #-} - instance HasChainId (ChainResources logger) where _chainId = _chainId . _chainResBlockHeaderDb {-# INLINE _chainId #-} withChainResources :: Logger logger + => HasVersion => logger - -> ChainwebVersion -> ChainId -> RocksDb -> HTTP.Manager @@ -416,15 +410,15 @@ withChainResources -- ^ whether to allow unlimited rewind on startup -> PayloadProviderConfig -> ResourceT IO (ChainResources logger) -withChainResources logger v cid rdb mgr _pactDbDir p2pConf myInfo peerDb rewindLimit initialUnlimitedRewind configs = do +withChainResources logger cid rdb mgr _pactDbDir p2pConf myInfo peerDb rewindLimit initialUnlimitedRewind configs = do -- This uses the the CutNetwork for fetching block headers. - cdb <- withBlockHeaderDb rdb v cid + cdb <- withBlockHeaderDb rdb cid -- Payload Providers are using per chain payload networks for fetching -- block headers. provider <- withPayloadProviderResources - providerLogger v cid p2pConf myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs + providerLogger cid p2pConf myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs return ChainResources { _chainResBlockHeaderDb = cdb @@ -432,7 +426,7 @@ withChainResources logger v cid rdb mgr _pactDbDir p2pConf myInfo peerDb rewindL , _chainResLogger = logger } where - providerType = payloadProviderTypeForChain v cid + providerType = payloadProviderTypeForChain cid providerLogger = logger & setComponent "payload-provider" & addLabel ("provider", toText providerType) @@ -477,6 +471,7 @@ payloadProvidersForAllChains chains = -- runP2pNodesOfAllChains :: Foldable l + => HasVersion => l (ChainResources logger) -> [IO ()] runP2pNodesOfAllChains diff --git a/src/Chainweb/Chainweb/CheckReachability.hs b/src/Chainweb/Chainweb/CheckReachability.hs index 1c72e2afd2..42b223943d 100644 --- a/src/Chainweb/Chainweb/CheckReachability.hs +++ b/src/Chainweb/Chainweb/CheckReachability.hs @@ -80,16 +80,16 @@ instance Exception ReachabilityException -- checkReachability :: Logger logger + => HasVersion => Socket -> HTTP.Manager - -> ChainwebVersion -> logger -> PeerDb -> [PeerInfo] -> Peer -> Double -> IO () -checkReachability sock mgr v logger pdb peers peer threshold = do +checkReachability sock mgr logger pdb peers peer threshold = do nis <- if null peers then return [] else withPeerDbServer $ do @@ -123,7 +123,7 @@ checkReachability sock mgr v logger pdb peers peer threshold = do logg = logFunctionText logger run p = runClientM - (peerPutClient v CutNetwork pinf) + (peerPutClient CutNetwork pinf) (peerInfoClientEnv mgr p) withPeerDbServer inner = withAsync servePeerDb $ const inner @@ -137,7 +137,6 @@ checkReachability sock mgr v logger pdb peers peer threshold = do (_peerCertificateChain peer) (_peerKey peer) sock - v CutNetwork pdb id -- TODO add middleware for request logging? diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 0d50746612..c46c2d661b 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -94,7 +94,7 @@ import Chainweb.Version.Development import Chainweb.Version.EvmDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.RecapDevelopment -import Chainweb.Version.Registry +import Chainweb.Version.Registry (findKnownVersion, knownVersions) import Configuration.Utils hiding (Error, Lens', disabled) import Control.Lens hiding ((.=), (<.>)) import Control.Monad @@ -148,12 +148,12 @@ defaultPayloadProviderConfig = PayloadProviderConfig , _payloadProviderConfigEvm = mempty } -validatePayloadProviderConfig :: ChainwebVersion -> ConfigValidation PayloadProviderConfig [] -validatePayloadProviderConfig v conf = do +validatePayloadProviderConfig :: HasVersion => ConfigValidation PayloadProviderConfig [] +validatePayloadProviderConfig conf = do void $ HM.traverseWithKey checkPactProvider $ _payloadProviderConfigPact conf void $ HM.traverseWithKey checkEvmProvider $ _payloadProviderConfigEvm conf where - checkPactProvider cid _conf = case payloadProviderTypeForChain v cid of + checkPactProvider cid _conf = case payloadProviderTypeForChain cid of PactProvider -> return () -- FIXME implement validation e -> do tell [ "Pact provider configured for chain " <> sshow cid <> ": " <> sshow conf ] @@ -162,8 +162,8 @@ validatePayloadProviderConfig v conf = do , ". Expected " <> sshow e <> " but found Pact" ] - checkEvmProvider cid _conf = case payloadProviderTypeForChain v cid of - EvmProvider _ -> validateEvmProviderConfig v cid _conf + checkEvmProvider cid _conf = case payloadProviderTypeForChain cid of + EvmProvider _ -> validateEvmProviderConfig cid _conf e -> do tell [ "EVM provider configured for chain " <> sshow cid <> ": " <> sshow conf ] throwError $ mconcat $ @@ -528,18 +528,15 @@ data ChainwebConfiguration = ChainwebConfiguration makeLenses ''ChainwebConfiguration -instance HasChainwebVersion ChainwebConfiguration where - _chainwebVersion = _configChainwebVersion - {-# INLINE _chainwebVersion #-} - validateChainwebConfiguration :: ConfigValidation ChainwebConfiguration [] validateChainwebConfiguration c = do - validateMinerConfig (_configChainwebVersion c) (_configMining c) - validateBackupConfig (_configBackup c) - unless (c ^. chainwebVersion . versionDefaults . disablePeerValidation) $ - validateP2pConfiguration (_configP2p c) validateChainwebVersion (_configChainwebVersion c) - validatePayloadProviderConfig (_configChainwebVersion c) (_configPayloadProviders c) + withVersion (_configChainwebVersion c) $ do + validateMinerConfig (_configMining c) + validateBackupConfig (_configBackup c) + unless (c ^. configChainwebVersion . versionDefaults . disablePeerValidation) $ + validateP2pConfiguration (_configP2p c) + validatePayloadProviderConfig (_configPayloadProviders c) validateChainwebVersion :: ConfigValidation ChainwebVersion [] validateChainwebVersion v = do @@ -668,7 +665,7 @@ parseVersion = constructVersion ForkAtBlockHeight fubHeight -> HM.filterWithKey (\bh _ -> bh <= fubHeight) (winningVersion ^?! versionUpgrades . atChain cid) ForkAtGenesis -> winningVersion ^?! versionUpgrades . atChain cid ) - (HS.toMap (chainIds winningVersion)) + (HS.toMap (withVersion winningVersion chainIds)) ) fub & versionCheats . disablePow .~ disablePow' where diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 48ef22de64..12496c8697 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -32,7 +32,6 @@ module Chainweb.Chainweb.CutResources ) where import Control.Lens -import Control.Lens.TH import Control.Monad.IO.Class import Control.Monad.Trans.Resource @@ -75,12 +74,9 @@ data CutResources = CutResources makeLenses ''CutResources -instance HasChainwebVersion CutResources where - _chainwebVersion = _chainwebVersion . _cutResCutDb - {-# INLINE _chainwebVersion #-} - withCutResources :: Logger logger + => HasVersion => logger -> CutDbParams -> P2pConfiguration @@ -129,7 +125,7 @@ withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain provide -- | The networks that are used by the cut DB. -- -cutNetworks :: CutResources -> [IO ()] +cutNetworks :: HasVersion => CutResources -> [IO ()] cutNetworks cuts = [ p2pRunNode (_cutResCutP2pNode cuts) , p2pRunNode (_cutResHeaderP2pNode cuts) diff --git a/src/Chainweb/Chainweb/MempoolSyncClient.hs b/src/Chainweb/Chainweb/MempoolSyncClient.hs index e63a4508b7..73d632efcd 100644 --- a/src/Chainweb/Chainweb/MempoolSyncClient.hs +++ b/src/Chainweb/Chainweb/MempoolSyncClient.hs @@ -66,6 +66,7 @@ import qualified Servant.Client as Sv -- runMempoolSyncClient :: Logger logger + => HasVersion => HTTP.Manager -- ^ HTTP connection pool -> MempoolP2pConfig @@ -105,7 +106,8 @@ runMempoolSyncClient mgr memP2pConfig peerRes chain = -- in the same way the Payload APIs are published. -- mempoolSyncP2pSession - :: ChainResources logger + :: HasVersion + => ChainResources logger -> Seconds -> P2pSession mempoolSyncP2pSession chain (Seconds pollInterval) logg0 env _ = do diff --git a/src/Chainweb/Chainweb/MinerResources.hs b/src/Chainweb/Chainweb/MinerResources.hs index 3b60d56f37..ea6fbc3fd1 100644 --- a/src/Chainweb/Chainweb/MinerResources.hs +++ b/src/Chainweb/Chainweb/MinerResources.hs @@ -59,6 +59,7 @@ import System.Random.MWC qualified as MWC -- withMiningCoordination :: Logger logger + => HasVersion => logger -> MiningConfig -> CutDb @@ -78,7 +79,6 @@ withMiningCoordination logger conf cdb inner coordConf = _miningCoordination conf -- -------------------------------------------------------------------------- -- - -- | Miner resources are used by the test-miner when in-node mining is -- configured or by the mempool noop-miner (which keeps the mempool updated) in -- production setups. @@ -121,14 +121,14 @@ withMinerResources logger conf chainRes cutDb tpw inner = runMiner :: forall logger . Logger logger - => ChainwebVersion - -> MinerResources logger + => HasVersion + => MinerResources logger -> IO () -runMiner v mr +runMiner mr | enabled = case _minerResCoordination mr of Nothing -> error "Mining coordination must be enabled in order to use the in-node test miner" - Just coord -> case v ^. versionCheats . disablePow of + Just coord -> case implicitVersion ^. versionCheats . disablePow of True -> testMiner coord False -> powMiner coord | otherwise = return () @@ -150,6 +150,6 @@ runMiner v mr testMiner coord = do gen <- MWC.createSystemRandom - localTest lf v coord cdb gen (_nodeTestMiners conf) + localTest lf coord cdb gen (_nodeTestMiners conf) powMiner coord = localPOW lf coord cdb diff --git a/src/Chainweb/Chainweb/PeerResources.hs b/src/Chainweb/Chainweb/PeerResources.hs index 4416e325a0..fe3ca1b6e1 100644 --- a/src/Chainweb/Chainweb/PeerResources.hs +++ b/src/Chainweb/Chainweb/PeerResources.hs @@ -102,10 +102,6 @@ data PeerResources logger = PeerResources , _peerLogger :: !logger } -instance HasChainwebVersion (PeerResources logger) where - _chainwebVersion = _chainwebVersion . _peerResDb - {-# INLINE _chainwebVersion #-} - makeLenses ''PeerResources -- | Allocate Peer resources. All P2P networks of a chainweb node share a single @@ -122,15 +118,15 @@ makeLenses ''PeerResources -- withPeerResources :: Logger logger - => ChainwebVersion - -> P2pConfiguration + => HasVersion + => P2pConfiguration -> logger -> (logger -> PeerResources logger -> IO a) -> IO a -withPeerResources v conf logger inner = withPeerSocket conf $ \(conf', sock) -> do - withPeerDb_ v conf' $ \peerDb -> do +withPeerResources conf logger inner = withPeerSocket conf $ \(conf', sock) -> do + withPeerDb_ conf' $ \peerDb -> do (!mgr, !counter) <- connectionManager peerDb - withHost mgr v conf' logger $ \conf'' -> do + withHost mgr conf' logger $ \conf'' -> do peer <- unsafeCreatePeer $ _p2pConfigPeer conf'' @@ -152,7 +148,7 @@ withPeerResources v conf logger inner = withPeerSocket conf $ \(conf', sock) -> let peers = filter (((/=) `on` _peerAddr) pinf) $ _p2pConfigKnownPeers conf - checkReachability sock mgr v logger' localDb peers + checkReachability sock mgr logger' localDb peers peer (_p2pConfigBootstrapReachability conf'') @@ -170,24 +166,24 @@ withPeerResources v conf logger inner = withPeerSocket conf $ \(conf', sock) -> -- withHost :: Logger logger + => HasVersion => HTTP.Manager - -> ChainwebVersion -> P2pConfiguration -> logger -> (P2pConfiguration -> IO a) -> IO a -withHost mgr v conf logger f +withHost mgr conf logger f | null peers = do logFunctionText logger Warn $ "Unable to verify configured host " <> toText confHost <> ": No peers are available." f (set (p2pConfigPeer . peerConfigHost) confHost conf) | anyIpv4 == confHost = do - h <- getHost mgr v logger peers >>= \case + h <- getHost mgr logger peers >>= \case Right x -> return x Left e -> error $ "withHost failed: " <> T.unpack e f (set (p2pConfigPeer . peerConfigHost) h conf) | otherwise = do - getHost mgr v logger peers >>= \case + getHost mgr logger peers >>= \case Left e -> logFunctionText logger Warn $ "Failed to verify configured host " <> toText confHost <> ": " <> e @@ -204,14 +200,14 @@ withHost mgr v conf logger f getHost :: Logger logger + => HasVersion => HTTP.Manager - -> ChainwebVersion -> logger -> [PeerInfo] -> IO (Either T.Text Hostname) -getHost mgr ver logger peers = do +getHost mgr logger peers = do nis <- forConcurrently peers $ \p -> - tryAllSynchronous (requestRemoteNodeInfo mgr (_versionName ver) (_peerAddr p) Nothing) >>= \case + tryAllSynchronous (requestRemoteNodeInfo mgr (_versionName implicitVersion) (_peerAddr p) Nothing) >>= \case Right x -> Just x <$ do logFunctionText logger Info $ "got remote info from " <> toText (_peerAddr p) @@ -241,17 +237,17 @@ withPeerSocket conf act = withSocket port interface $ \(p, s) -> -- -------------------------------------------------------------------------- -- -- Run PeerDb for a Chainweb Version -startPeerDb_ :: ChainwebVersion -> P2pConfiguration -> IO PeerDb -startPeerDb_ v c = - startPeerDb v nids (_p2pConfigPrivate c) (_p2pConfigKnownPeers c) +startPeerDb_ :: HasVersion => P2pConfiguration -> IO PeerDb +startPeerDb_ c = + startPeerDb nids (_p2pConfigPrivate c) (_p2pConfigKnownPeers c) where nids = HS.singleton CutNetwork `HS.union` HS.map MempoolNetwork cids `HS.union` HS.map ChainNetwork cids - cids = chainIds v + cids = chainIds -withPeerDb_ :: ChainwebVersion -> P2pConfiguration -> (PeerDb -> IO a) -> IO a -withPeerDb_ v conf = bracket (startPeerDb_ v conf) stopPeerDb +withPeerDb_ :: HasVersion => P2pConfiguration -> (PeerDb -> IO a) -> IO a +withPeerDb_ conf = bracket (startPeerDb_ conf) stopPeerDb -- -------------------------------------------------------------------------- -- -- Connection Manager diff --git a/src/Chainweb/Cut.hs b/src/Chainweb/Cut.hs index 911a7a20b5..68204bfcf7 100644 --- a/src/Chainweb/Cut.hs +++ b/src/Chainweb/Cut.hs @@ -177,7 +177,6 @@ import Chainweb.Parent -- data Cut = Cut' { _cutHeaders' :: !(HM.HashMap ChainId BlockHeader) - , _cutChainwebVersion' :: !ChainwebVersion -- Memoize properties that have linear compute cost , _cutHeight' :: {- lazy -} CutHeight @@ -205,14 +204,6 @@ cutMap :: Getter Cut (HM.HashMap ChainId BlockHeader) cutMap = cutHeaders {-# INLINE cutMap #-} -_cutChainwebVersion :: Cut -> ChainwebVersion -_cutChainwebVersion = _cutChainwebVersion' -{-# INLINE _cutChainwebVersion #-} - -cutChainwebVersion :: Getter Cut ChainwebVersion -cutChainwebVersion = to _cutChainwebVersion -{-# INLINE cutChainwebVersion #-} - _cutWeight :: Cut -> BlockWeight _cutWeight = _cutWeight' {-# INLINE _cutWeight #-} @@ -256,14 +247,10 @@ cutIsTransition = to _cutIsTransition -- | The chain graph is the graph at the /minimum/ height of the block headers -- in the cut. -- -instance HasChainGraph Cut where - _chainGraph c = chainGraphAt (_chainwebVersion c) (_cutMinHeight c) +instance HasVersion => HasChainGraph Cut where + _chainGraph c = chainGraphAt (_cutMinHeight c) {-# INLINE _chainGraph #-} -instance HasChainwebVersion Cut where - _chainwebVersion = view cutChainwebVersion - {-# INLINE _chainwebVersion #-} - type instance Index Cut = ChainId type instance IxValue Cut = BlockHeader @@ -272,25 +259,24 @@ instance IxedGet Cut where {-# INLINE ixg #-} lookupCutM - :: MonadThrow m + :: (MonadThrow m, HasVersion) => HasChainId cid => cid -> Cut -> m BlockHeader lookupCutM cid c = firstOf (ixg (_chainId cid)) c ??? ChainNotInChainGraphException - (Expected $ chainIds c) + (Expected chainIds) (Actual (_chainId cid)) -unsafeMkCut :: ChainwebVersion -> HM.HashMap ChainId BlockHeader -> Cut -unsafeMkCut v hdrs = Cut' +unsafeMkCut :: HasVersion => HM.HashMap ChainId BlockHeader -> Cut +unsafeMkCut hdrs = Cut' { _cutHeaders' = hdrs - , _cutChainwebVersion' = v , _cutHeight' = int $ sum $ view blockHeight <$> hdrs , _cutWeight' = sum $ view blockWeight <$> hdrs , _cutMinHeight' = minimum $ view blockHeight <$> hdrs , _cutMaxHeight' = maximum $ view blockHeight <$> hdrs - , _cutIsTransition' = minheight < lastGraphChange v (maxheight) + , _cutIsTransition' = minheight < lastGraphChange (maxheight) } where minheight = minimum $ view blockHeight <$> hdrs @@ -370,10 +356,6 @@ cutHeadersMinHeight :: HM.HashMap ChainId BlockHeader -> BlockHeight cutHeadersMinHeight = minimum . fmap (view blockHeight) {-# INLINE cutHeadersMinHeight #-} -cutHeadersChainwebVersion :: HM.HashMap ChainId BlockHeader -> ChainwebVersion -cutHeadersChainwebVersion m = _chainwebVersion $ unsafeHead "Chainweb.Cut.cutHeadersChainwebVersion" $ toList m -{-# INLINE cutHeadersChainwebVersion #-} - -- | The function projects onto the chains available at the minimum block height -- in input headers. -- @@ -385,17 +367,17 @@ cutHeadersChainwebVersion m = _chainwebVersion $ unsafeHead "Chainweb.Cut.cutHea -- form a valid cut. In particular, the input must not be empty. -- projectChains - :: HM.HashMap ChainId BlockHeader + :: HasVersion + => HM.HashMap ChainId BlockHeader -> HM.HashMap ChainId BlockHeader projectChains m = HM.intersection m $ HS.toMap - $ chainIdsAt (cutHeadersChainwebVersion m) (cutHeadersMinHeight m) + $ chainIdsAt (cutHeadersMinHeight m) {-# INLINE projectChains #-} -cutProjectChains :: Cut -> Cut -cutProjectChains c = unsafeMkCut v $ projectChains $ _cutHeaders c +cutProjectChains :: HasVersion => Cut -> Cut +cutProjectChains c = unsafeMkCut $ projectChains $ _cutHeaders c where - v = _chainwebVersion c {-# INLINE cutProjectChains #-} -- | Extend the chains for the graph at the minimum block height of the input @@ -406,12 +388,11 @@ cutProjectChains c = unsafeMkCut v $ projectChains $ _cutHeaders c -- form a valid cut. In particular, the input must not be empty. -- extendChains - :: HM.HashMap ChainId BlockHeader + :: HasVersion + => HM.HashMap ChainId BlockHeader -> HM.HashMap ChainId BlockHeader extendChains m = HM.union m - $ genesisBlockHeadersAtHeight - (cutHeadersChainwebVersion m) - (cutHeadersMinHeight m) + $ genesisBlockHeadersAtHeight (cutHeadersMinHeight m) {-# INLINE extendChains #-} -- | This function adds all chains that are available in either of the input @@ -426,13 +407,13 @@ extendChains m = HM.union m -- form a valid cut. In particular, the input must not be empty. -- joinChains - :: HM.HashMap ChainId BlockHeader + :: HasVersion + => HM.HashMap ChainId BlockHeader -> HM.HashMap ChainId BlockHeader -> (HM.HashMap ChainId BlockHeader, HM.HashMap ChainId BlockHeader) joinChains a b = (HM.union a c, HM.union b c) where - v = cutHeadersChainwebVersion a - c = genesisBlockHeader v <$> a <> b + c = genesisBlockHeader <$> a <> b {-# INLINE joinChains #-} -- -------------------------------------------------------------------------- -- @@ -448,7 +429,7 @@ joinChains a b = (HM.union a c, HM.union b c) -- is returned. -- limitCut - :: HasCallStack + :: (HasCallStack, HasVersion) => WebBlockHeaderDb -> BlockHeight -- ^ upper bound for the block height of each chain. This is not a tight @@ -460,9 +441,8 @@ limitCut wdb h c return c | otherwise = do hdrs <- itraverse go $ view cutHeaders c - return $! unsafeMkCut v $ projectChains $ HM.mapMaybe id hdrs + return $! unsafeMkCut $ projectChains $ HM.mapMaybe id hdrs where - v = _chainwebVersion c go :: ChainId -> BlockHeader -> IO (Maybe BlockHeader) go cid bh = do @@ -482,7 +462,7 @@ limitCut wdb h c -- cut is returned. -- tryLimitCut - :: HasCallStack + :: (HasCallStack, HasVersion) => WebBlockHeaderDb -> BlockHeight -- upper bound for the block height of each chain. This is not a tight bound. @@ -493,9 +473,8 @@ tryLimitCut wdb h c return c | otherwise = do hdrs <- itraverse go $ view cutHeaders c - return $! unsafeMkCut v hdrs + return $! unsafeMkCut hdrs where - v = _chainwebVersion wdb go :: ChainId -> BlockHeader -> IO BlockHeader go cid bh = do if h >= view blockHeight bh @@ -505,23 +484,21 @@ tryLimitCut wdb h c -- this is safe because it's guaranteed that the requested rank is -- smaller then the block height of the argument let ancestorHeight = min (int $ view blockHeight bh) (int h) - if ancestorHeight <= fromIntegral (genesisHeight v cid) - then return $ genesisBlockHeader v cid + if ancestorHeight <= fromIntegral (genesisHeight cid) + then return $ genesisBlockHeader cid else fromJuste <$> seekAncestor db bh ancestorHeight -- | The resulting headers are valid cut headers only if the input headers are -- valid cut headers, too. The inverse is not true. -- limitCutHeaders - :: HasCallStack + :: (HasCallStack, HasVersion) => WebBlockHeaderDb -> BlockHeight -- ^ upper bound for the block height of each chain. This is not a tight bound. -> HM.HashMap ChainId BlockHeader -> IO (HM.HashMap ChainId BlockHeader) -limitCutHeaders whdb h ch = _cutHeaders <$> limitCut whdb h (unsafeMkCut v ch) - where - v = _chainwebVersion whdb +limitCutHeaders whdb h ch = _cutHeaders <$> limitCut whdb h (unsafeMkCut ch) -- -------------------------------------------------------------------------- -- -- Genesis Cut @@ -540,9 +517,9 @@ limitCutHeaders whdb h ch = _cutHeaders <$> limitCut whdb h (unsafeMkCut v ch) -- These properties are maintained as inductive invariants for all Cuts. -- genesisCut - :: ChainwebVersion - -> Cut -genesisCut v = unsafeMkCut v (genesisBlockHeadersAtHeight v 0) + :: HasVersion + => Cut +genesisCut = unsafeMkCut (genesisBlockHeadersAtHeight 0) -- -------------------------------------------------------------------------- -- -- Exceptions @@ -664,7 +641,7 @@ isBraidingOfCutPair a b = do -- TODO: do we have to check that the correct graph is used? -- isMonotonicCutExtension - :: HasCallStack + :: (HasCallStack, HasVersion) => MonadThrow m => Cut -> BlockHeader @@ -682,16 +659,14 @@ isMonotonicCutExtension c h = do validBraidingCid cid a | Just b <- c ^? ixg cid = Parent (view blockHash b) == a || view blockParent b == a - | view blockHeight h == genesisHeight v cid = a == genesisParentBlockHash v cid + | view blockHeight h == genesisHeight cid = a == genesisParentBlockHash cid | otherwise = error $ T.unpack $ "isMonotonicCutExtension.validBraiding: missing adjacent parent on chain " <> toText cid <> " in cut. " <> encodeToText h - v = _chainwebVersion c - -- | Extend a cut with a block header. Throws 'InvalidCutExtension' if the block -- header isn't a monotonic cut extension. -- monotonicCutExtension - :: MonadThrow m + :: (MonadThrow m, HasVersion) => Cut -> BlockHeader -> m Cut @@ -703,19 +678,17 @@ monotonicCutExtension c h = tryMonotonicCutExtension c h >>= \case -- a monotonic cut extension. -- tryMonotonicCutExtension - :: MonadThrow m + :: (MonadThrow m, HasVersion) => Cut -> BlockHeader -> m (Maybe Cut) tryMonotonicCutExtension c h = isMonotonicCutExtension c h >>= \case False -> return Nothing True -> return $! Just - $! unsafeMkCut v + $! unsafeMkCut $ extendChains $ set (ix' (_chainId h)) h $ _cutHeaders c - where - v = _chainwebVersion c -- -------------------------------------------------------------------------- -- -- Join @@ -741,7 +714,7 @@ data Join a = Join -- chains. -- join - :: Ord a + :: (Ord a, HasVersion) => WebBlockHeaderDb -> (DiffItem BlockHeader -> DiffItem (Maybe a)) -> Cut @@ -764,7 +737,7 @@ join wdb f = join_ wdb f `on` _cutHeaders -- join_ :: forall a - . Ord a + . (Ord a, HasVersion) => WebBlockHeaderDb -> (DiffItem BlockHeader -> DiffItem (Maybe a)) -> HM.HashMap ChainId BlockHeader @@ -772,7 +745,7 @@ join_ -> IO (Join a) join_ wdb prioFun a b = do (m, h) <- runStateT (HM.traverseWithKey f (HM.intersectionWith (,) a' b')) mempty - return $! Join (unsafeMkCut (_chainwebVersion wdb) m) h + return $! Join (unsafeMkCut m) h where (a', b') = joinChains a b @@ -802,7 +775,7 @@ join_ wdb prioFun a b = do -- -- Non-existing chains are stripped from the result. -- -applyJoin :: MonadThrow m => Join a -> m Cut +applyJoin :: (MonadThrow m, HasVersion) => Join a -> m Cut applyJoin m = cutProjectChains <$> foldM (\c b -> fromMaybe c <$> tryMonotonicCutExtension c (H.payload b)) @@ -827,7 +800,8 @@ applyJoin m = cutProjectChains -- for those chains that you care about. -- joinIntoHeavier - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> Cut -> Cut -> IO Cut @@ -841,7 +815,8 @@ joinIntoHeavier wdb = joinIntoHeavier_ wdb `on` _cutHeaders -- for those chains that you care about. -- joinIntoHeavier_ - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> HM.HashMap ChainId BlockHeader -> HM.HashMap ChainId BlockHeader -> IO Cut @@ -902,20 +877,22 @@ prioritizeHeavier_ a b = f -- | Intersection of cuts -- meet - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> Cut -> Cut -> IO Cut meet wdb a b = do !r <- imapM f $ HM.intersectionWith (,) (_cutHeaders a) (_cutHeaders b) - return $! unsafeMkCut (_chainwebVersion wdb) r + return $! unsafeMkCut r where f !cid (!x, !y) = do db <- getWebBlockHeaderDb wdb cid forkEntry db x y forkDepth - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> Cut -> Cut -> IO Natural diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 2c31a9caf7..112bfa9186 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -63,7 +63,7 @@ module Chainweb.Cut.Create -- * Work , MiningWork(..) , encodeMiningWork -, decodeMiningWork +, decodeWorkHeader , newMiningWorkPure -- * Solved Work @@ -163,10 +163,6 @@ instance HasChainId CutExtension where _chainId = _chainId . _cutExtensionParent {-# INLINE _chainId #-} -instance HasChainwebVersion CutExtension where - _chainwebVersion = _chainwebVersion . _cutExtensionCut - {-# INLINE _chainwebVersion #-} - -- | Witness that a cut can be extended for the given chain by trying to -- assemble the adjacent hashes for a new work header. -- @@ -192,7 +188,7 @@ instance HasChainwebVersion CutExtension where -- that can't be further extended. -- getCutExtension - :: HasCallStack + :: (HasCallStack, HasVersion) => HasChainId cid => Cut -- ^ the cut which is to be extended @@ -216,13 +212,12 @@ getCutExtension c cid = do } where p = c ^?! ixg (_chainId cid) - v = _chainwebVersion c parentHeight = view blockHeight p targetHeight = parentHeight + 1 - parentGraph = chainGraphAt p parentHeight + parentGraph = chainGraphAt parentHeight -- true if the parent height is the first of a new graph. - isGraphTransitionPost = isGraphChange c parentHeight + isGraphTransitionPost = isGraphChange parentHeight -- true if a graph transition occurs in the cut. isGraphTransitionCut = _cutIsTransition c @@ -239,7 +234,7 @@ getCutExtension c cid = do hashForChain acid -- existing chain | Just b <- lookupCutM acid c = tryAdj b - | targetHeight == genesisHeight v acid = error $ T.unpack + | targetHeight == genesisHeight acid = error $ T.unpack $ "getAdjacentParents: invalid cut extension, requested parent of a genesis block for chain " <> sshow acid <> ".\n Parent: " <> encodeToText (ObjectEncoded p) @@ -307,17 +302,18 @@ encodeMiningWork wh = do -- FIXME: We really want this indepenent of the block height. For production -- chainweb version this is actually the case. -- -decodeMiningWork :: ChainwebVersion -> BlockHeight -> Get MiningWork -decodeMiningWork ver h = MiningWork +decodeWorkHeader :: HasVersion => BlockHeight -> Get MiningWork +decodeWorkHeader h = MiningWork <$> decodeChainId <*> decodeHashTarget - <*> (SB.toShort <$> getByteString (int $ workSizeBytes ver h)) + <*> (SB.toShort <$> getByteString (int $ workSizeBytes h)) -- | A pure version of 'newWorkHeader' that is useful in testing. It is not used -- in production code. -- newMiningWorkPure :: Applicative m + => HasVersion => (ChainValue BlockHash -> m BlockHeader) -> BlockCreationTime -> CutExtension @@ -360,6 +356,7 @@ newMiningWorkPure hdb creationTime extension phash = do -- getAdjacentParentHeaders :: HasCallStack + => HasVersion => Applicative m => (ChainValue BlockHash -> m BlockHeader) -> CutExtension @@ -424,6 +421,7 @@ workParentsAdjacentHashes = to _workParentsAdjacentHashes -- workParents :: HasCallStack + => HasVersion => Applicative m => HasChainId cid => (ChainValue BlockHash -> m BlockHeader) @@ -438,7 +436,8 @@ workParents hdb c cid = case getCutExtension c cid of <$> getAdjacentParentHeaders hdb e newWork - :: BlockCreationTime + :: HasVersion + => BlockCreationTime -> WorkParents -> BlockPayloadHash -> MiningWork @@ -455,7 +454,8 @@ newWork creationTime parents pldHash = MiningWork -- | TODO: do we have to verify that the solved for matches the work parents? -- newHeader - :: WorkParents + :: HasVersion + => WorkParents -> SolvedWork -> BlockHeader newHeader parents solved = @@ -494,7 +494,7 @@ instance HasChainId SolvedWork where {-# INLINE _chainId #-} -- | This is a special decoding function that decode solved work from the --- mining work bytes that are minter returns as solution. +-- mining work bytes that the miner returns as solution. -- decodeSolvedWork :: Get SolvedWork decodeSolvedWork = do @@ -557,7 +557,7 @@ instance Brief SolvedWork where -- work. -- extend - :: MonadThrow m + :: (MonadThrow m, HasVersion) => Cut -> Maybe EncodedPayloadData -> Maybe EncodedPayloadOutputs @@ -579,7 +579,7 @@ extend c pld pwo ps s = do -- | For internal use and testing -- extendCut - :: MonadThrow m + :: (MonadThrow m, HasVersion) => Cut -> WorkParents -> SolvedWork @@ -597,4 +597,3 @@ extendCut c ps s = do (bh,) <$> tryMonotonicCutExtension c bh where bh = newHeader ps s - diff --git a/src/Chainweb/Cut/CutHashes.hs b/src/Chainweb/Cut/CutHashes.hs index 56d893bb5d..0bdc61ff70 100644 --- a/src/Chainweb/Cut/CutHashes.hs +++ b/src/Chainweb/Cut/CutHashes.hs @@ -44,7 +44,6 @@ module Chainweb.Cut.CutHashes , BlockHashWithHeight(..) , CutHashes(..) , cutHashes -, cutHashesChainwebVersion , cutHashesId , cutOrigin , cutHashesWeight @@ -63,7 +62,7 @@ module Chainweb.Cut.CutHashes import Control.Arrow import Control.DeepSeq import Control.Lens (Getter, Lens', makeLenses, to, view) -import Control.Monad ((<$!>)) +import Control.Monad ((<$!>), unless) import Control.Monad.Catch import qualified Crypto.Hash as C @@ -100,7 +99,7 @@ import Chainweb.Storage.Table import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version -import Chainweb.Version.Registry (fabricateVersionWithName) +import Chainweb.Version.Registry import Chainweb.PayloadProvider(EncodedPayloadData(..), EncodedPayloadOutputs(..)) import P2P.Peer @@ -275,7 +274,6 @@ data CutHashes = CutHashes -- ^ 'Nothing' is used for locally mined Cuts , _cutHashesWeight :: !BlockWeight , _cutHashesHeight :: !CutHeight - , _cutHashesChainwebVersion :: ChainwebVersion , _cutHashesId :: !CutId , _cutHashesHeaders :: !(HM.HashMap BlockHash BlockHeader) -- ^ optional block headers @@ -333,13 +331,14 @@ instance Ord CutHashes where compare = compare `on` (_cutHashesWeight &&& _cutHashesId) {-# INLINE compare #-} -cutHashesProperties :: forall e kv . KeyValue e kv => CutHashes -> [kv] +cutHashesProperties + :: forall e kv . KeyValue e kv => HasVersion => CutHashes -> [kv] cutHashesProperties c = [ "hashes" .= _cutHashes c , "origin" .= _cutOrigin c , "weight" .= _cutHashesWeight c , "height" .= _cutHashesHeight c - , "instance" .= _versionName (_cutHashesChainwebVersion c) + , "instance" .= _versionName implicitVersion , "id" .= _cutHashesId c ] <> ifNotEmpty "headers" cutHashesHeaders @@ -355,23 +354,27 @@ cutHashesProperties c = | x <- view l c, not (HM.null x) = [ s .= x ] | otherwise = mempty -instance ToJSON CutHashes where +instance HasVersion => ToJSON CutHashes where toJSON = object . cutHashesProperties toEncoding = pairs . mconcat . cutHashesProperties {-# INLINE toJSON #-} {-# INLINE toEncoding #-} -instance FromJSON CutHashes where - parseJSON = withObject "CutHashes" $ \o -> CutHashes - <$> o .: "hashes" - <*> o .: "origin" - <*> o .: "weight" - <*> o .: "height" - <*> (fabricateVersionWithName <$> o .: "instance") - <*> o .: "id" - <*> o .:? "headers" .!= mempty - <*> o .:? "payloads" .!= mempty - <*> pure Nothing +instance HasVersion => FromJSON CutHashes where + parseJSON = withObject "CutHashes" $ \o -> do + v <- o .: "instance" + unless (v == _versionName implicitVersion) $ fail $ T.unpack $ + "incorrect version: expected " <> getChainwebVersionName (_versionName implicitVersion) <> + ", but got " <> getChainwebVersionName v + CutHashes + <$> o .: "hashes" + <*> o .: "origin" + <*> o .: "weight" + <*> o .: "height" + <*> o .: "id" + <*> o .:? "headers" .!= mempty + <*> o .:? "payloads" .!= mempty + <*> pure Nothing -- | Compute a 'CutHashes' structure from a 'Cut'. The result doesn't include -- any block headers or payloads. @@ -382,7 +385,6 @@ cutToCutHashes p c = CutHashes , _cutOrigin = p , _cutHashesWeight = _cutWeight c , _cutHashesHeight = _cutHeight c - , _cutHashesChainwebVersion = _chainwebVersion c , _cutHashesId = _cutId c , _cutHashesHeaders = mempty , _cutHashesPayloads = mempty diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 227a5289d8..e6bd533851 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -185,8 +185,8 @@ data CutDbParams = CutDbParams makeLenses ''CutDbParams -defaultCutDbParams :: ChainwebVersion -> Int -> CutDbParams -defaultCutDbParams v ft = CutDbParams +defaultCutDbParams :: HasVersion => Int -> CutDbParams +defaultCutDbParams ft = CutDbParams { _cutDbParamsInitialCutFile = Nothing , _cutDbParamsBufferSize = (order g ^ (2 :: Int)) * diameter g , _cutDbParamsLogLevel = Warn @@ -199,7 +199,7 @@ defaultCutDbParams v ft = CutDbParams , _cutDbParamsReadOnly = False } where - g = _chainGraph (v, maxBound @BlockHeight) + g = chainGraphAt (maxBound @BlockHeight) -- | We ignore cuts that are two far ahead of the current best cut that we have. -- There are two reasons for this: @@ -233,7 +233,7 @@ farAheadThreshold = 20 -- -------------------------------------------------------------------------- -- -- CutHashes Table -cutHashesTable :: RocksDb -> Casify RocksDbTable CutHashes +cutHashesTable :: HasVersion => RocksDb -> Casify RocksDbTable CutHashes cutHashesTable rdb = Casify $ newTable rdb valueCodec keyCodec ["CutHashes"] where keyCodec = Codec @@ -269,10 +269,6 @@ data CutDb = CutDb , _cutDbFastForwardHeightLimit :: !(Maybe BlockHeight) } -instance HasChainwebVersion CutDb where - _chainwebVersion = _chainwebVersion . _cutDbHeaderStore - {-# INLINE _chainwebVersion #-} - cutDbPayloadProviders :: Getter CutDb (ChainMap ConfiguredPayloadProvider) cutDbPayloadProviders = to _cutDbPayloadProviders {-# INLINE cutDbPayloadProviders #-} @@ -372,16 +368,16 @@ awaitNewCutByChainIdStm cdb cid c = do return c' pruneCuts - :: LogFunction - -> ChainwebVersion + :: HasVersion + => LogFunction -> CutDbParams -> BlockHeight -> Casify RocksDbTable CutHashes -> IO () -pruneCuts logfun v conf curAvgBlockHeight cutHashesStore = do +pruneCuts logfun conf curAvgBlockHeight cutHashesStore = do let avgBlockHeightPruningDepth = _cutDbParamsAvgBlockHeightPruningDepth conf let pruneCutHeight = - avgCutHeightAt v (curAvgBlockHeight - min curAvgBlockHeight avgBlockHeightPruningDepth) + avgCutHeightAt (curAvgBlockHeight - min curAvgBlockHeight avgBlockHeightPruningDepth) logfun @T.Text Info $ "pruning CutDB before cut height " <> T.pack (show pruneCutHeight) deleteRangeRocksDb (unCasify cutHashesStore) (Nothing, Just (pruneCutHeight, 0, maxBound :: CutId)) @@ -394,7 +390,8 @@ cutDbQueueSize :: CutDb -> IO Natural cutDbQueueSize = pQueueSize . _cutDbQueue withCutDb - :: CutDbParams + :: HasVersion + => CutDbParams -> LogFunction -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider @@ -415,7 +412,8 @@ withCutDb config logfun headerStore providers cutHashesStore -- read-only version of the payload store. -- startCutDb - :: CutDbParams + :: HasVersion + => CutDbParams -> LogFunction -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider @@ -449,7 +447,6 @@ startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do where logg = logfun @T.Text wbhdb = _webBlockHeaderStoreCas headerStore - v = _chainwebVersion headerStore processor :: PQueue (Down CutHashes) -> TVar Cut -> IO () processor queue cutVar = runForever logfun "CutDB" $ @@ -457,19 +454,21 @@ startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do readInitialCut :: IO Cut readInitialCut = do - unsafeMkCut v <$> do - hm <- readHighestCutHeaders v logg wbhdb cutHashesStore + unsafeMkCut <$> do + hm <- readHighestCutHeaders logg wbhdb cutHashesStore case _cutDbParamsInitialHeightLimit config of Nothing -> return hm Just h -> do limitedCutHeaders <- limitCutHeaders wbhdb h hm - let limitedCut = unsafeMkCut v limitedCutHeaders + let limitedCut = unsafeMkCut limitedCutHeaders unless (_cutDbParamsReadOnly config) $ casInsert cutHashesStore (cutToCutHashes Nothing limitedCut) return limitedCutHeaders -readHighestCutHeaders :: ChainwebVersion -> LogFunctionText -> WebBlockHeaderDb -> Casify RocksDbTable CutHashes -> IO (HM.HashMap ChainId BlockHeader) -readHighestCutHeaders v logg wbhdb cutHashesStore = withTableIterator (unCasify cutHashesStore) $ \it -> do +readHighestCutHeaders + :: HasVersion + => LogFunctionText -> WebBlockHeaderDb -> Casify RocksDbTable CutHashes -> IO (HM.HashMap ChainId BlockHeader) +readHighestCutHeaders logg wbhdb cutHashesStore = withTableIterator (unCasify cutHashesStore) $ \it -> do iterLast it go it where @@ -478,7 +477,7 @@ readHighestCutHeaders v logg wbhdb cutHashesStore = withTableIterator (unCasify go it = iterValue it >>= \case Nothing -> do logg Warn "No initial cut found in database or configuration, falling back to genesis cut" - return $ view cutMap $ genesisCut v + return $ view cutMap genesisCut Just ch -> try (lookupCutHashes wbhdb ch) >>= \case Left (e@(TreeDbKeyNotFound _) :: TreeDbException BlockHeaderDb) -> do logg Warn @@ -491,16 +490,15 @@ readHighestCutHeaders v logg wbhdb cutHashesStore = withTableIterator (unCasify Left e -> throwM e Right hm -> return hm -fastForwardCutDb :: CutDb -> IO () +fastForwardCutDb :: HasVersion => CutDb -> IO () fastForwardCutDb cutDb = do highestCutHeaders <- - readHighestCutHeaders v (_cutDbLogFunction cutDb) wbhdb (_cutDbCutStore cutDb) + readHighestCutHeaders (_cutDbLogFunction cutDb) wbhdb (_cutDbCutStore cutDb) limitedCutHeaders <- limitCutHeaders wbhdb (fromMaybe maxBound (_cutDbFastForwardHeightLimit cutDb)) highestCutHeaders - let limitedCut = unsafeMkCut (_chainwebVersion cutDb) limitedCutHeaders + let limitedCut = unsafeMkCut limitedCutHeaders atomically $ writeTVar (_cutDbCut cutDb) limitedCut where - v = _chainwebVersion cutDb wbhdb = _webBlockHeaderStoreCas $ _cutDbHeaderStore cutDb -- | Stop the cut validation pipeline. @@ -516,15 +514,16 @@ stopCutDb db = do -- the lookup for some BlockHash in the input CutHashes. -- lookupCutHashes - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> CutHashes -> IO (HM.HashMap ChainId BlockHeader) lookupCutHashes wbhdb hs = flip itraverse (_cutHashes hs) $ \cid (BlockHashWithHeight _ h) -> lookupWebBlockHeaderDb wbhdb cid h -cutAvgBlockHeight :: ChainwebVersion -> Cut -> BlockHeight -cutAvgBlockHeight v = BlockHeight . round . avgBlockHeightAtCutHeight v . _cutHeight +cutAvgBlockHeight :: HasVersion => Cut -> BlockHeight +cutAvgBlockHeight = BlockHeight . round . avgBlockHeightAtCutHeight . _cutHeight -- | This is at the heart of 'Chainweb' POW: Deciding the current "longest" cut -- among the incoming candiates. @@ -537,7 +536,8 @@ cutAvgBlockHeight v = BlockHeight . round . avgBlockHeightAtCutHeight v . _cutHe -- stores the longest cut. -- processCuts - :: CutDbParams + :: HasVersion + => CutDbParams -> LogFunction -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider @@ -567,7 +567,7 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do !resultCut <- trace logFun "Chainweb.CutDB.processCuts._joinIntoHeavier" () 1 $ joinIntoHeavier_ hdrStore (_cutMap curCut) newCut unless (_cutDbParamsReadOnly conf) $ do - maybePrune rng (cutAvgBlockHeight v curCut) + maybePrune rng (cutAvgBlockHeight curCut) loggCutId logFun Debug newCut "writing cut" casInsert cutHashesStore (cutToCutHashes Nothing resultCut) atomically $ writeTVar cutVar resultCut @@ -585,12 +585,10 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do ) where - v = _chainwebVersion headerStore - maybePrune rng curCutAvgBlockHeight = do r :: Double <- Prob.uniform rng - when (r < 1 / int (int (_cutDbParamsPruningFrequency conf) * chainCountAt v maxBound)) $ - pruneCuts logFun v conf curCutAvgBlockHeight cutHashesStore + when (r < 1 / int (int (_cutDbParamsPruningFrequency conf) * chainCountAt maxBound)) $ + pruneCuts logFun conf curCutAvgBlockHeight cutHashesStore hdrStore = _webBlockHeaderStoreCas headerStore @@ -631,7 +629,7 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do -- isVeryOld x = do curMin <- _cutMinHeight <$> readTVarIO cutVar - let diam = diameter $ chainGraphAt headerStore curMin + let diam = diameter $ chainGraphAt curMin newMin = _cutHashesMinHeight x let r = newMin + 2 * (1 + int diam) <= curMin when r $ loggCutId logFun Debug x "skip very old cut" @@ -674,6 +672,7 @@ cutStream db = liftIO (_cut db) >>= \c -> S.yield c >> go c cutStreamToHeaderStream :: forall m r . MonadIO m + => HasVersion => CutDb -> S.Stream (Of Cut) m r -> S.Stream (Of BlockHeader) m r @@ -703,6 +702,7 @@ cutStreamToHeaderStream db s = S.for (go Nothing s) $ \(T2 p n) -> cutStreamToHeaderDiffStream :: forall m r . MonadIO m + => HasVersion => CutDb -> S.Stream (Of Cut) m r -> S.Stream (Of (Either BlockHeader BlockHeader)) m r @@ -740,18 +740,19 @@ cutStreamToHeaderDiffStream db s = S.for (cutUpdates Nothing s) $ \(T2 p n) -> -- -- @chainId + blockHeight * graphOrder@ -- -uniqueBlockNumber :: BlockHeader -> Natural +uniqueBlockNumber :: HasVersion => BlockHeader -> Natural uniqueBlockNumber bh = chainIdInt (_chainId bh) + int (view blockHeight bh) * order (_chainGraph bh) -blockStream :: MonadIO m => CutDb -> S.Stream (Of BlockHeader) m r +blockStream :: (MonadIO m, HasVersion) => CutDb -> S.Stream (Of BlockHeader) m r blockStream db = cutStreamToHeaderStream db $ cutStream db -blockDiffStream :: MonadIO m => CutDb -> S.Stream (Of (Either BlockHeader BlockHeader)) m r +blockDiffStream :: (MonadIO m, HasVersion) => CutDb -> S.Stream (Of (Either BlockHeader BlockHeader)) m r blockDiffStream db = cutStreamToHeaderDiffStream db $ cutStream db cutHashesToBlockHeaderMap - :: CutDbParams + :: HasVersion + => CutDbParams -> LogFunction -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider @@ -839,7 +840,8 @@ cutHashesToBlockHeaderMap conf logfun headerStore providers hs = -- Membership Queries memberOfHeader - :: CutDb + :: HasVersion + => CutDb -> ChainId -> BlockHash -- ^ the block hash to look up (the member) @@ -856,7 +858,8 @@ memberOfHeader db cid h ctx = do chainDb = db ^?! cutDbWebBlockHeaderDb . ixg cid memberOfM - :: CutDb + :: HasVersion + => CutDb -> ChainId -> BlockHash -- ^ the block hash to look up (the member) @@ -869,7 +872,12 @@ memberOfM db cid h ctx = do where chainDb = db ^?! cutDbWebBlockHeaderDb . ixg cid -member :: CutDb -> ChainId -> BlockHash -> IO Bool +member + :: HasVersion + => CutDb + -> ChainId + -> BlockHash + -> IO Bool member db cid h = do th <- maxEntry chainDb memberOfHeader db cid h th @@ -886,8 +894,10 @@ newtype CutDbT (v :: ChainwebVersionT) = CutDbT CutDb data SomeCutDb = forall v . KnownChainwebVersionSymbol v => SomeCutDb (CutDbT v) -someCutDbVal :: ChainwebVersion -> CutDb -> SomeCutDb -someCutDbVal (FromSingChainwebVersion (SChainwebVersion :: Sing v)) db = SomeCutDb $ CutDbT @v db +someCutDbVal :: HasVersion => CutDb -> SomeCutDb +someCutDbVal db = case implicitVersion of + FromSingChainwebVersion (SChainwebVersion :: Sing v) -> + SomeCutDb $ CutDbT @v db -- -------------------------------------------------------------------------- -- -- Queue Stats diff --git a/src/Chainweb/CutDB/RestAPI.hs b/src/Chainweb/CutDB/RestAPI.hs index adf03104aa..f178bdd3a8 100644 --- a/src/Chainweb/CutDB/RestAPI.hs +++ b/src/Chainweb/CutDB/RestAPI.hs @@ -84,5 +84,6 @@ cutApi = Proxy -- -------------------------------------------------------------------------- -- -- Some Cut Api -someCutApi :: ChainwebVersion -> SomeApi -someCutApi (FromSingChainwebVersion (SChainwebVersion :: Sing v)) = SomeApi $ cutApi @v +someCutApi :: HasVersion => SomeApi +someCutApi = case implicitVersion of + FromSingChainwebVersion (SChainwebVersion :: Sing v) -> SomeApi $ cutApi @v diff --git a/src/Chainweb/CutDB/RestAPI/Client.hs b/src/Chainweb/CutDB/RestAPI/Client.hs index 1ca26c7c5e..b10aae7932 100644 --- a/src/Chainweb/CutDB/RestAPI/Client.hs +++ b/src/Chainweb/CutDB/RestAPI/Client.hs @@ -31,23 +31,27 @@ import Chainweb.Version -- GET Cut Client cutGetClient - :: ChainwebVersion - -> ClientM CutHashes -cutGetClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - = client (cutGetApi @v) Nothing + :: HasVersion + => ClientM CutHashes +cutGetClient = case implicitVersion of + FromSingChainwebVersion (SChainwebVersion :: Sing v) -> + client (cutGetApi @v) Nothing cutGetClientLimit - :: ChainwebVersion - -> MaxRank + :: HasVersion + => MaxRank -> ClientM CutHashes -cutGetClientLimit (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - = client (cutGetApi @v) . Just +cutGetClientLimit = case implicitVersion of + FromSingChainwebVersion (SChainwebVersion :: Sing v) -> + client (cutGetApi @v) . Just -- -------------------------------------------------------------------------- -- -- PUT Cut Client cutPutClient - :: ChainwebVersion - -> CutHashes + :: HasVersion + => CutHashes -> ClientM NoContent -cutPutClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) = client $ cutPutApi @v +cutPutClient = case implicitVersion of + FromSingChainwebVersion (SChainwebVersion :: Sing v) -> + client $ cutPutApi @v diff --git a/src/Chainweb/CutDB/RestAPI/Server.hs b/src/Chainweb/CutDB/RestAPI/Server.hs index 9ad2eda3f1..5867693fe6 100644 --- a/src/Chainweb/CutDB/RestAPI/Server.hs +++ b/src/Chainweb/CutDB/RestAPI/Server.hs @@ -65,12 +65,11 @@ import P2P.Peer -- -------------------------------------------------------------------------- -- -- Handlers -cutGetHandler :: CutDb -> Maybe MaxRank -> IO CutHashes +cutGetHandler :: HasVersion => CutDb -> Maybe MaxRank -> IO CutHashes cutGetHandler db Nothing = liftIO $ cutToCutHashes Nothing <$> _cut db cutGetHandler db (Just (MaxRank (Max mar))) = liftIO $ do !c <- _cut db - let v = _chainwebVersion db - let !bh = BlockHeight $ floor (avgBlockHeightAtCutHeight v (CutHeight $ int mar)) + let !bh = BlockHeight $ floor (avgBlockHeightAtCutHeight (CutHeight $ int mar)) !c' <- limitCut (view cutDbWebBlockHeaderDb db) bh c return $! cutToCutHashes Nothing c' @@ -88,38 +87,38 @@ cutPutHandler pdb db c = case _peerAddr <$> _cutOrigin c of cutServer :: forall (v :: ChainwebVersionT) - . PeerDb + . HasVersion + => PeerDb -> CutDbT v -> Server (CutApi v) cutServer pdb (CutDbT db) = liftIO . cutGetHandler db :<|> cutPutHandler pdb db cutGetServer :: forall (v :: ChainwebVersionT) - . CutDbT v + . HasVersion + => CutDbT v -> Server (CutGetApi v) cutGetServer (CutDbT db) = liftIO . cutGetHandler db -- -------------------------------------------------------------------------- -- -- Some Cut Server -someCutServerT :: PeerDb -> SomeCutDb -> SomeServer +someCutServerT :: HasVersion => PeerDb -> SomeCutDb -> SomeServer someCutServerT pdb (SomeCutDb (db :: CutDbT v)) = SomeServer (Proxy @(CutApi v)) (cutServer pdb db) -someCutServer :: ChainwebVersion -> PeerDb -> CutDb -> SomeServer -someCutServer v pdb = someCutServerT pdb . someCutDbVal v +someCutServer :: HasVersion => PeerDb -> CutDb -> SomeServer +someCutServer pdb = someCutServerT pdb . someCutDbVal -someCutGetServerT :: SomeCutDb -> SomeServer +someCutGetServerT :: HasVersion => SomeCutDb -> SomeServer someCutGetServerT (SomeCutDb (db :: CutDbT v)) = SomeServer (Proxy @(CutGetApi v)) (cutGetServer db) -someCutGetServer :: ChainwebVersion -> CutDb -> SomeServer -someCutGetServer v = someCutGetServerT . someCutDbVal v +someCutGetServer :: HasVersion => CutDb -> SomeServer +someCutGetServer = someCutGetServerT . someCutDbVal -- -------------------------------------------------------------------------- -- -- Run Server -serveCutOnPort :: Port -> ChainwebVersion -> PeerDb -> CutDb -> IO () -serveCutOnPort p v pdb = run (int p) . someServerApplication . someCutServer v pdb - - +serveCutOnPort :: HasVersion => Port -> PeerDb -> CutDb -> IO () +serveCutOnPort p pdb = run (int p) . someServerApplication . someCutServer pdb diff --git a/src/Chainweb/CutDB/Sync.hs b/src/Chainweb/CutDB/Sync.hs index 68f37e17e4..9eee976b40 100644 --- a/src/Chainweb/CutDB/Sync.hs +++ b/src/Chainweb/CutDB/Sync.hs @@ -49,8 +49,7 @@ import P2P.Session -- Client Env data CutClientEnv = CutClientEnv - { _envChainwebVersion :: !ChainwebVersion - , _envClientEnv :: !ClientEnv + { _envClientEnv :: !ClientEnv } deriving (Generic) @@ -58,16 +57,18 @@ runClientThrowM :: ClientM a -> ClientEnv -> IO a runClientThrowM req = fromEitherM <=< runClientM req putCut - :: CutClientEnv + :: HasVersion + => CutClientEnv -> CutHashes -> IO () -putCut (CutClientEnv v env) = void . flip runClientThrowM env . cutPutClient v +putCut (CutClientEnv env) = void . flip runClientThrowM env . cutPutClient getCut - :: CutClientEnv + :: HasVersion + => CutClientEnv -> CutHeight -> IO CutHashes -getCut (CutClientEnv v env) h = runClientThrowM (cutGetClientLimit v (int h)) env +getCut (CutClientEnv env) h = runClientThrowM (cutGetClientLimit (int h)) env -- -------------------------------------------------------------------------- -- -- Sync Session @@ -84,7 +85,8 @@ catchupStepSize :: CutHeight catchupStepSize = 100 syncSession - :: PeerInfo + :: HasVersion + => PeerInfo -> CutDb -> P2pSession syncSession p db logg env pinf = do @@ -101,8 +103,7 @@ syncSession p db logg env pinf = do logg @T.Text Error "unexpectedly exited cut sync session" return False where - v = _chainwebVersion db - cenv = CutClientEnv v env + cenv = CutClientEnv env send c = do putCut cenv c diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Mempool/InMem.hs index 7b17a97e10..0326d8c180 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Mempool/InMem.hs @@ -78,7 +78,6 @@ import Chainweb.Mempool.Mempool import Chainweb.Pact.Validations (defaultMaxTTLSeconds, defaultMaxCoinDecimalPlaces) import Chainweb.Time import Chainweb.Utils -import Chainweb.Version (ChainwebVersion) import Pact.Core.Gas import Chainweb.PayloadProvider (EvaluationCtx) @@ -164,9 +163,8 @@ withInMemoryMempool => NFData t => logger -> InMemConfig t - -> ChainwebVersion -> ResourceT IO (MempoolBackend t) -withInMemoryMempool l cfg _v = do +withInMemoryMempool l cfg = do inMem <- liftIO $ makeInMemPool cfg monitorAsync <- withAsyncR (monitor inMem) liftIO $ link monitorAsync diff --git a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs index 52a68f875f..ae7ce78ac2 100644 --- a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs +++ b/src/Chainweb/Mempool/InMem/ValidatingConfig.hs @@ -28,13 +28,13 @@ import qualified Pact.Core.Command.Types as Pact import qualified Pact.Core.ChainData as Pact validatingMempoolConfig - :: ChainId - -> ChainwebVersion + :: HasVersion + => ChainId -> GasLimit -> GasPrice -> (V.Vector Pact.Transaction -> IO (V.Vector (Maybe InsertError))) -> InMemConfig Pact.Transaction -validatingMempoolConfig cid v gl gp preInsertCheck = InMemConfig +validatingMempoolConfig cid gl gp preInsertCheck = InMemConfig { _inmemTxCfg = pactTransactionConfig , _inmemTxBlockSizeLimit = gl , _inmemTxMinGasPrice = gp @@ -59,10 +59,10 @@ validatingMempoolConfig cid v gl gp preInsertCheck = InMemConfig pcid = Pact._pmChainId $ Pact._pMeta pay sigs = Pact._cmdSigs tx ver = Pact._pNetworkId pay - if | not $ assertChainId cid pcid -> Left InsertErrorMetadataMismatch - | not $ assertSigSize sigs -> Left InsertErrorTooManySigs - | not $ assertNetworkId v ver -> Left InsertErrorMetadataMismatch - | otherwise -> Right tx + if | not $ assertChainId cid pcid -> Left InsertErrorMetadataMismatch + | not $ assertSigSize sigs -> Left InsertErrorTooManySigs + | not $ assertNetworkId ver -> Left InsertErrorMetadataMismatch + | otherwise -> Right tx -- | Validation: All checks that should occur before a TX is inserted into -- the mempool. A rejection at this stage means that something is diff --git a/src/Chainweb/Mempool/RestAPI.hs b/src/Chainweb/Mempool/RestAPI.hs index 2caf1fc569..6f3d73c714 100644 --- a/src/Chainweb/Mempool/RestAPI.hs +++ b/src/Chainweb/Mempool/RestAPI.hs @@ -57,9 +57,9 @@ data SomeMempool t = forall v c . (KnownChainwebVersionSymbol v, KnownChainIdSymbol c) => SomeMempool (Mempool_ v c t) -someMempoolVal :: ChainwebVersion -> ChainId -> MempoolBackend t -> SomeMempool t -someMempoolVal v cid m = - case someChainwebVersionVal v of +someMempoolVal :: HasVersion => ChainId -> MempoolBackend t -> SomeMempool t +someMempoolVal cid m = + case someChainwebVersionVal of (SomeChainwebVersionT (Proxy :: Proxy vt)) -> case someChainIdVal cid of (SomeChainIdT (Proxy :: Proxy cidt)) -> SomeMempool (Mempool_ @vt @cidt m) @@ -148,4 +148,3 @@ mempoolGetPendingApi :: forall (v :: ChainwebVersionT) (c :: ChainIdT) . Proxy (MempoolGetPendingApi v c) mempoolGetPendingApi = Proxy - diff --git a/src/Chainweb/Mempool/RestAPI/Client.hs b/src/Chainweb/Mempool/RestAPI/Client.hs index 6c61eb09e3..245c336ffb 100644 --- a/src/Chainweb/Mempool/RestAPI/Client.hs +++ b/src/Chainweb/Mempool/RestAPI/Client.hs @@ -46,12 +46,12 @@ import Chainweb.Version -- TODO: all of these operations need timeout support. toMempool :: (Show t, NFData t) - => ChainwebVersion - -> ChainId + => HasVersion + => ChainId -> TransactionConfig t -> ClientEnv -> MempoolBackend t -toMempool version chain txcfg env = +toMempool chain txcfg env = MempoolBackend { mempoolTxConfig = txcfg , mempoolMember = member @@ -71,12 +71,12 @@ toMempool version chain txcfg env = where go m = runClientM m env >>= either throwIO return - member v = V.fromList <$> go (memberClient version chain (V.toList v)) - lookup v = V.fromList <$> go (lookupClient txcfg version chain (V.toList v)) - insert _ v = void $ go (insertClient txcfg version chain (V.toList v)) + member v = V.fromList <$> go (memberClient chain (V.toList v)) + lookup v = V.fromList <$> go (lookupClient txcfg chain (V.toList v)) + insert _ v = void $ go (insertClient txcfg chain (V.toList v)) getPending hw cb = do - runClientM (getPendingClient version chain hw) env >>= \case + runClientM (getPendingClient chain hw) env >>= \case Left e -> throwIO e Right ptxs -> do void $ cb (V.fromList $ _pendingTransationsHashes ptxs) @@ -94,14 +94,14 @@ insertClient_ insertClient_ = client (mempoolInsertApi @v @c) insertClient - :: TransactionConfig t - -> ChainwebVersion + :: HasVersion + => TransactionConfig t -> ChainId -> [t] -> ClientM NoContent -insertClient txcfg v c k0 = runIdentity $ do +insertClient txcfg c k0 = runIdentity $ do let k = map (T.decodeUtf8 . codecEncode (txCodec txcfg)) k0 - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ insertClient_ @v @c k @@ -115,12 +115,12 @@ memberClient_ memberClient_ = client (mempoolMemberApi @v @c) memberClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> [TransactionHash] -> ClientM [Bool] -memberClient v c txs = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +memberClient c txs = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ memberClient_ @v @c txs @@ -134,13 +134,13 @@ lookupClient_ lookupClient_ = client (mempoolLookupApi @v @c) lookupClient - :: TransactionConfig t - -> ChainwebVersion + :: HasVersion + => TransactionConfig t -> ChainId -> [TransactionHash] -> ClientM [LookupResult t] -lookupClient txcfg v c txs = do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +lookupClient txcfg c txs = do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c cs <- lookupClient_ @v @c txs mapM (traverse go) cs @@ -161,13 +161,13 @@ getPendingClient_ getPendingClient_ = client (mempoolGetPendingApi @v @c) getPendingClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe (ServerNonce, MempoolTxId) -> ClientM PendingTransactions -getPendingClient v c hw = runIdentity $ do +getPendingClient c hw = runIdentity $ do let nonce = fst <$> hw let tx = snd <$> hw - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ getPendingClient_ @v @c nonce tx diff --git a/src/Chainweb/Mempool/RestAPI/Server.hs b/src/Chainweb/Mempool/RestAPI/Server.hs index 022583558b..0ad518b9be 100644 --- a/src/Chainweb/Mempool/RestAPI/Server.hs +++ b/src/Chainweb/Mempool/RestAPI/Server.hs @@ -111,26 +111,22 @@ handleErrs = flip catchAllSynchronous $ \e -> someMempoolServer :: (Show t) - => ChainwebVersion - -> SomeMempool t + => HasVersion + => SomeMempool t -> SomeServer -someMempoolServer ver (SomeMempool (mempool :: Mempool_ v c t)) - = SomeServer (Proxy @(MempoolApi v c)) (mempoolServer ver mempool) +someMempoolServer (SomeMempool (mempool :: Mempool_ v c t)) + = SomeServer (Proxy @(MempoolApi v c)) (mempoolServer mempool) someMempoolServers :: (Show t) - => ChainwebVersion - -> ChainMap (MempoolBackend t) - -> SomeServer -someMempoolServers v = ifoldMap - (\cid mempool -> someMempoolServer v (someMempoolVal v cid mempool)) - -mempoolServer - :: Show t - => ChainwebVersion - -> Mempool_ v c t - -> Server (MempoolApi v c) -mempoolServer _v (Mempool_ mempool) = + => HasVersion + => ChainMap (MempoolBackend t) -> SomeServer +someMempoolServers = mconcat + . fmap (someMempoolServer . uncurry someMempoolVal) + . itoList + +mempoolServer :: HasVersion => Show t => Mempool_ v c t -> Server (MempoolApi v c) +mempoolServer (Mempool_ mempool) = insertHandler mempool :<|> memberHandler mempool :<|> lookupHandler mempool diff --git a/src/Chainweb/Miner/Config.hs b/src/Chainweb/Miner/Config.hs index 78f53abb5f..8774853feb 100644 --- a/src/Chainweb/Miner/Config.hs +++ b/src/Chainweb/Miner/Config.hs @@ -64,8 +64,8 @@ newtype MinerCount = MinerCount { _minerCount :: Natural } -- -------------------------------------------------------------------------- -- -- Mining Config -validateMinerConfig :: ChainwebVersion -> ConfigValidation MiningConfig [] -validateMinerConfig v c = do +validateMinerConfig :: HasVersion => ConfigValidation MiningConfig [] +validateMinerConfig c = do when (_nodeMiningEnabled nmc) $ do tell [ "In-node mining is enabled. This should only be used for testing" @@ -87,7 +87,7 @@ validateMinerConfig v c = do -- future we may also consider uname -m and/or cpuinfo (including flags) here. -- supportedArchs = [ "x86_64" ] - isProd = v `elem` [Mainnet01, Testnet04] + isProd = implicitVersion `elem` [Mainnet01, Testnet04] -- | Full configuration for Mining. -- diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 302679831c..90f0997620 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -130,16 +130,12 @@ solvedId s = type PayloadCaches = ChainMap PayloadCache -newPayloadCaches - :: HasChainwebVersion v - => HasChainGraph v - => v - -> IO PayloadCaches -newPayloadCaches v = tabulateChainsM (_chainwebVersion v) (\_ -> newIO depth) +newPayloadCaches :: HasVersion => IO PayloadCaches +newPayloadCaches = tabulateChainsM (\_ -> newIO depth) where -- FIXME: Make this configurable? depth :: Natural - depth = diameter (_chainGraph v) + depth = diameter (chainGraphAt (maxBound :: BlockHeight)) -- | Await the next payload for a cut that is different from the latest payload. -- @@ -253,17 +249,16 @@ instance Brief ParentState where -- -------------------------------------------------------------------------- -- -- Mining State -newMiningState :: Cut -> IO (ChainMap (TVar (Maybe ParentState))) +newMiningState :: HasVersion => Cut -> IO (ChainMap (TVar (Maybe ParentState))) newMiningState c = do states <- forM cids $ \cid -> do var <- newTVarIO Nothing return (cid, var) return $! onChains states where - v = _chainwebVersion c cids :: [ChainId] - cids = HS.toList (chainIds v) + cids = HS.toList chainIds -- TODO: consider storing the mining state more efficiently: -- @@ -275,7 +270,8 @@ newMiningState c = do -- | Update work state for all chains for a new cut. -- updateForCut - :: LogFunctionText + :: HasVersion + => LogFunctionText -> (ChainValue BlockHash -> IO BlockHeader) -> (ChainMap (TVar (Maybe ParentState))) -> Cut @@ -301,7 +297,7 @@ updateForCut lf hdb ms c = do , parentStateSolved = Nothing } -updateForSolved :: LogFunction -> CutDb -> PayloadCache -> TVar (Maybe ParentState) -> SolvedWork -> IO () +updateForSolved :: HasVersion => LogFunction -> CutDb -> PayloadCache -> TVar (Maybe ParentState) -> SolvedWork -> IO () updateForSolved lf cdb payloadCache var sw = do stateOrErr <- runExceptT $ do solvedParentState <- mapExceptT atomically $ do @@ -382,11 +378,10 @@ data MiningCoordination logger = MiningCoordination , _coordConf :: !CoordinationConfig , _coordPayloadCache :: !PayloadCaches } -instance HasChainwebVersion (MiningCoordination logger) where - _chainwebVersion = _chainwebVersion . _coordCutDb newMiningCoordination :: Logger logger + => HasVersion => logger -> CoordinationConfig -> CutDb @@ -394,7 +389,7 @@ newMiningCoordination newMiningCoordination logger conf cdb = do c <- _cut cdb state <- newMiningState c - caches <- newPayloadCaches c + caches <- newPayloadCaches return $ MiningCoordination { _coordLogger = logger , _coordCutDb = cdb @@ -419,6 +414,7 @@ newMiningCoordination logger conf cdb = do runCoordination :: forall l . Logger l + => HasVersion => MiningCoordination l -> IO () runCoordination mr = do @@ -566,7 +562,7 @@ awaitEvent cdb caches c p = -- 3. some payload providers are deadlocked, or -- 4. some payload providers are very slow in producing new payloads. -- -randomWork :: LogFunction -> PayloadCaches -> ChainMap (TVar (Maybe ParentState)) -> IO MiningWork +randomWork :: HasVersion => LogFunction -> PayloadCaches -> ChainMap (TVar (Maybe ParentState)) -> IO MiningWork randomWork logFun caches state = do -- Pick a random chain. @@ -680,6 +676,7 @@ staleMiningStateDelay = 2_000_000 work :: forall l . Logger l + => HasVersion => MiningCoordination l -> IO MiningWork work mr = randomWork lf (_coordPayloadCache mr) (_coordParentState mr) @@ -728,6 +725,7 @@ instance Exception NoAsscociatedPayload solve :: forall l . Logger l + => HasVersion => MiningCoordination l -> SolvedWork -> IO () @@ -745,7 +743,8 @@ solve mr solved = lf = logFunction $ _coordLogger mr logMinedBlock - :: LogFunction + :: HasVersion + => LogFunction -> BlockHeader -> NewPayload -> IO () diff --git a/src/Chainweb/Miner/Core.hs b/src/Chainweb/Miner/Core.hs index 3e06741a1f..8532065316 100644 --- a/src/Chainweb/Miner/Core.hs +++ b/src/Chainweb/Miner/Core.hs @@ -46,6 +46,7 @@ import Chainweb.Difficulty import Chainweb.Time hiding (second) import Chainweb.Utils import Chainweb.Utils.Serialization +import Chainweb.Version (HasVersion) --- @@ -93,7 +94,7 @@ timestampPosition = 8 -- mine :: forall a - . HashAlgorithm a + . (HashAlgorithm a, HasVersion) => Nonce -> MiningWork -> IO SolvedWork diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index 0f1aa8a538..d204870395 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -85,14 +85,14 @@ import Control.Concurrent.STM localTest :: HasCallStack => Logger logger + => HasVersion => LogFunction - -> ChainwebVersion -> MiningCoordination logger -> CutDb -> MWC.GenIO -> MinerCount -> IO () -localTest lf v coord cdb gen miners = +localTest lf coord cdb gen miners = runForever lf "Chainweb.Miner.Miners.localTest" $ do c <- _cut cdb wh <- work coord @@ -105,7 +105,7 @@ localTest lf v coord cdb gen miners = void $ awaitNewCut cdb c where meanBlockTime :: Double - meanBlockTime = int (_getBlockDelay (_versionBlockDelay v)) / 1_000_000 + meanBlockTime = int (_getBlockDelay (_versionBlockDelay implicitVersion)) / 1_000_000 go :: BlockHeight -> MiningWork -> IO SolvedWork go height w = do @@ -116,7 +116,7 @@ localTest lf v coord cdb gen miners = t = int graphOrder / (int (_minerCount miners) * meanBlockTime * 1_000_000) graphOrder :: Natural - graphOrder = order $ chainGraphAt v height + graphOrder = order $ chainGraphAt height -- | A miner that grabs new blocks from mempool and discards them. Mempool -- pruning happens during new-block time, so we need to ask for a new block @@ -137,6 +137,7 @@ mempoolNoopMiner lf chainRes = -- localPOW :: Logger logger + => HasVersion => LogFunctionText -> MiningCoordination logger -> CutDb @@ -177,5 +178,3 @@ localPOW lf coord cdb = runForever lf "Chainweb.Miner.Miners.localPOW" $ do c <- _cutStm cdb let h' = view blockHeight $ c ^?! ixg cid guard (h <= h') - - diff --git a/src/Chainweb/Miner/RestAPI/Server.hs b/src/Chainweb/Miner/RestAPI/Server.hs index cdf18e5a32..720d8e255c 100644 --- a/src/Chainweb/Miner/RestAPI/Server.hs +++ b/src/Chainweb/Miner/RestAPI/Server.hs @@ -63,6 +63,7 @@ import System.Random workHandler :: Logger l + => HasVersion => MiningCoordination l -> Handler WorkBytes workHandler mr = do @@ -75,6 +76,7 @@ workHandler mr = do solvedHandler :: forall l . Logger l + => HasVersion => MiningCoordination l -> HeaderBytes -> Handler NoContent @@ -220,6 +222,7 @@ awaitWorkChange var payloadCache timer prevVar = go -- updatesHandler :: Logger l + => HasVersion => MiningCoordination l -> ChainBytes -> Tagged Handler Application @@ -269,10 +272,12 @@ updatesHandler mr (ChainBytes cbytes) = Tagged $ \req resp -> do miningServer :: forall l (v :: ChainwebVersionT) . Logger l + => HasVersion => MiningCoordination l -> Server (MiningApi v) miningServer mr = workHandler mr :<|> solvedHandler mr :<|> updatesHandler mr -someMiningServer :: Logger l => ChainwebVersion -> MiningCoordination l -> SomeServer -someMiningServer (FromSingChainwebVersion (SChainwebVersion :: Sing vT)) mr = - SomeServer (Proxy @(MiningApi vT)) $ miningServer mr +someMiningServer :: Logger l => HasVersion => MiningCoordination l -> SomeServer +someMiningServer mr = case implicitVersion of + FromSingChainwebVersion (SChainwebVersion :: Sing vT) -> + SomeServer (Proxy @(MiningApi vT)) $ miningServer mr diff --git a/src/Chainweb/MinerReward.hs b/src/Chainweb/MinerReward.hs index 1cc41a2df1..a19c5ac0e5 100644 --- a/src/Chainweb/MinerReward.hs +++ b/src/Chainweb/MinerReward.hs @@ -176,14 +176,14 @@ minerRewardKda (MinerReward d) = stuToKda d -- in any network, we can still change the algorithm. -- blockMinerReward - :: ChainwebVersion - -> BlockHeight + :: HasVersion + => BlockHeight -> MinerReward -blockMinerReward v h = case M.lookupGE h minerRewards of +blockMinerReward h = case M.lookupGE h minerRewards of Nothing -> MinerReward $ Stu 0 Just (_, s) -> MinerReward $ divideStu s n where - !n = int . order $ chainGraphAt v h + !n = int . order $ chainGraphAt h -- | Binary encoding of mining rewards as unsigned integral number in little -- endian encoding. @@ -277,4 +277,3 @@ expectedMinerRewardsHash = read "8e4fb006c5045b3baab638d16d62c952e4981a4ba473ec6 expectedRawMinerRewardsHash :: Digest SHA512 expectedRawMinerRewardsHash = read "903d10b06666c0d619c8a28c74c3bb0af47209002f005b12bbda7b7df1131b2072ce758c1a8148facb1506022215ea201629f38863feb285c7e66f5965498fe0" - diff --git a/src/Chainweb/OpenAPIValidation.hs b/src/Chainweb/OpenAPIValidation.hs index ad7e61f31a..9d2ea3f5ae 100644 --- a/src/Chainweb/OpenAPIValidation.hs +++ b/src/Chainweb/OpenAPIValidation.hs @@ -25,8 +25,8 @@ import Chainweb.Time import Chainweb.Utils import Chainweb.Version -mkValidationMiddleware :: Logger logger => logger -> ChainwebVersion -> HTTP.Manager -> IO Middleware -mkValidationMiddleware logger v mgr = do +mkValidationMiddleware :: (Logger logger, HasVersion) => logger -> HTTP.Manager -> IO Middleware +mkValidationMiddleware logger mgr = do (chainwebSpec, pactSpec) <- fetchOpenApiSpecs apiCoverageRef <- newIORef $ WV.initialCoverageMap [chainwebSpec, pactSpec] apiCoverageLogTimeRef <- newIORef =<< getCurrentTimeIntegral @@ -38,15 +38,15 @@ mkValidationMiddleware logger v mgr = do ("" : "chainweb" : "0.0" : rawVersion : "chain" : rawChainId : "pact" : rest) -> do findPact pactSpec rawVersion rawChainId rest _ -> Nothing - , (,chainwebSpec) <$> BS8.stripPrefix (T.encodeUtf8 $ "/chainweb/0.0/" <> toText (_versionName v)) path + , (,chainwebSpec) <$> BS8.stripPrefix (T.encodeUtf8 $ "/chainweb/0.0/" <> toText (_versionName implicitVersion)) path , Just (path,chainwebSpec) ] where findPact pactSpec rawVersion rawChainId rest = do let reqVersion = ChainwebVersionName (T.decodeUtf8 rawVersion) - guard (reqVersion == _versionName v) + guard (reqVersion == _versionName implicitVersion) reqChainId <- chainIdFromText (T.decodeUtf8 rawChainId) - guard (HS.member reqChainId (chainIds v)) + guard (HS.member reqChainId chainIds) return (BS8.intercalate "/" ("":rest), pactSpec) fetchOpenApiSpecs = do diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 693223b510..f3643c8fe2 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -149,7 +149,6 @@ import Chainweb.Ranked data BlockHandlerEnv logger = BlockHandlerEnv { _blockHandlerDb :: !SQLiteEnv , _blockHandlerLogger :: !logger - , _blockHandlerVersion :: !ChainwebVersion , _blockHandlerBlockHeight :: !BlockHeight , _blockHandlerUpperBoundTxId :: !Pact.TxId , _blockHandlerChainId :: !ChainId @@ -160,9 +159,6 @@ data BlockHandlerEnv logger = BlockHandlerEnv instance HasChainId (BlockHandlerEnv logger) where _chainId = _blockHandlerChainId -instance HasChainwebVersion (BlockHandlerEnv logger) where - _chainwebVersion = _blockHandlerVersion - -- | The state used by database operations. -- Includes both the state re: the whole block, and the state re: a transaction in progress. data BlockState = BlockState @@ -236,7 +232,7 @@ runOnBlockGassed env stateVar act = do return (newState, fmap fst r) liftEither r -chainwebPactBlockDb :: (Logger logger) => BlockHandlerEnv logger -> ChainwebPactDb +chainwebPactBlockDb :: (Logger logger) => HasVersion => BlockHandlerEnv logger -> ChainwebPactDb chainwebPactBlockDb env = ChainwebPactDb { doChainwebPactDbTransaction = \blockHandle maybeRequestKey kont -> do stateVar <- newMVar $ BlockState blockHandle (_blockHandlePending blockHandle) Nothing @@ -281,7 +277,8 @@ chainwebPactBlockDb env = ChainwebPactDb doReadRow :: forall k v logger -- ^ the highest block we should be reading writes from - . Pact.Domain k v Pact.CoreBuiltin Pact.Info + . HasVersion + => Pact.Domain k v Pact.CoreBuiltin Pact.Info -> k -> BlockHandler logger (Maybe v) doReadRow d k = do @@ -427,7 +424,8 @@ latestTxId :: Lens' BlockState Pact.TxId latestTxId = bsBlockHandle . blockHandleTxId . coerced writeSys - :: Pact.Domain k v Pact.CoreBuiltin Pact.Info + :: HasVersion + => Pact.Domain k v Pact.CoreBuiltin Pact.Info -> k -> v -> BlockHandler logger () @@ -445,7 +443,8 @@ writeSys d k v = do recordTxLog d encodedKey encodedValue recordPendingUpdate - :: Pact.Domain k v Pact.CoreBuiltin Pact.Info + :: HasVersion + => Pact.Domain k v Pact.CoreBuiltin Pact.Info -> k -> Pact.TxId -> (ByteString, v) @@ -455,7 +454,8 @@ recordPendingUpdate d k txid (encodedValue, decodedValue) = InMemDb.insert d k (InMemDb.WriteEntry txid encodedValue decodedValue) writeUser - :: Pact.WriteType + :: HasVersion + => Pact.WriteType -> Pact.TableName -> Pact.RowKey -> Pact.RowData @@ -485,8 +485,8 @@ writeUser wt tableName k (Pact.RowData newRow) = do (Nothing, Pact.Update) -> liftGas $ Pact.throwDbOpErrorGasM (Pact.NoRowFound tableName k) doWriteRow - -- ^ the highest block we should be reading writes from - :: Pact.WriteType + :: HasVersion + => Pact.WriteType -> Pact.Domain k v Pact.CoreBuiltin Pact.Info -> k -> v @@ -497,8 +497,9 @@ doWriteRow wt d k v = case d of doKeys :: forall k v logger + . HasVersion -- ^ the highest block we should be reading writes from - . Pact.Domain k v Pact.CoreBuiltin Pact.Info + => Pact.Domain k v Pact.CoreBuiltin Pact.Info -> BlockHandler logger [k] doKeys d = do dbKeys <- getDbKeys @@ -572,8 +573,8 @@ recordTableCreationTxLog tn = do !uti = Pact.UserTableInfo (Pact._tableModuleName tn) doCreateUserTable - -- ^ the highest block we should be seeing tables from - :: Pact.TableName + :: HasVersion + => Pact.TableName -> BlockHandler logger () doCreateUserTable tableName = do -- first check if tablename already exists in pending queues @@ -621,9 +622,12 @@ doBegin _m = do bsPendingTxLog .= Just mempty Just <$> use latestTxId -toTxLog :: MonadThrow m => ChainwebVersion -> ChainId -> BlockHeight -> T.Text -> SQ3.Utf8 -> BS.ByteString -> m (Pact.TxLog Pact.RowData) -toTxLog version cid bh d key value = do - let serialiser = pact5Serialiser version cid bh +toTxLog + :: (MonadThrow m, HasVersion) + => ChainId -> BlockHeight + -> T.Text -> SQ3.Utf8 -> BS.ByteString -> m (Pact.TxLog Pact.RowData) +toTxLog cid bh d key value = do + let serialiser = pact5Serialiser cid bh case fmap (view Pact.document) $ Pact._decodeRowData serialiser value of Nothing -> error $ "toTxLog: Unexpected value, unable to deserialize log: " <> sshow value Just v -> return $! Pact.TxLog d (fromUtf8 key) v @@ -859,12 +863,11 @@ initSchema sql = "CREATE INDEX IF NOT EXISTS \ \ transactionIndexByBH ON TransactionIndex(blockheight)"; -getSerialiser :: BlockHandler logger (Pact.PactSerialise Pact.CoreBuiltin Pact.LineInfo) +getSerialiser :: HasVersion => BlockHandler logger (Pact.PactSerialise Pact.CoreBuiltin Pact.LineInfo) getSerialiser = do - version <- view blockHandlerVersion cid <- view blockHandlerChainId blockHeight <- view blockHandlerBlockHeight - return $ pact5Serialiser version cid blockHeight + return $ pact5Serialiser cid blockHeight getPayloadsAfter :: HasCallStack => SQLiteEnv -> Parent BlockHeight -> ExceptT LocatedSQ3Error IO [Ranked BlockPayloadHash] getPayloadsAfter db parentHeight = do diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index 63f68bc9bf..57009349be 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -78,9 +78,9 @@ import Chainweb.Pact.Backend.Utils import Chainweb.Payload.PayloadStore (addNewPayload, lookupPayloadWithHeight) import Chainweb.Payload.PayloadStore.RocksDB (newPayloadDb) import Chainweb.Utils (sshow, fromText, toText, int) -import Chainweb.Version (ChainId, ChainwebVersion(..), chainIdToText) +import Chainweb.Version (ChainId, HasVersion(..), withVersion, ChainwebVersion(..), chainIdToText) +import Chainweb.Version.Registry (findKnownVersion) import Chainweb.Version.Mainnet (mainnet) -import Chainweb.Version.Registry (lookupVersionByName) import Chainweb.Version.Testnet04 (testnet04) import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb, initWebBlockHeaderDb) import Data.LogMessage (SomeLogMessage, logText) @@ -191,14 +191,15 @@ getConfig = do parseVersion :: Text -> ChainwebVersion parseVersion = - lookupVersionByName - . fromMaybe (error "ChainwebVersion parse failed") + fromMaybe (error "ChainwebVersion parse failed") + . (>>= findKnownVersion) . fromText main :: IO () main = do compact =<< getConfig + compactPactState :: (Logger logger) => logger -> Retainment -> BlockHeight -> SQLiteEnv -> SQLiteEnv -> IO () compactPactState logger rt targetBlockHeight srcDb targetDb = do let log = logFunctionText logger @@ -333,8 +334,8 @@ blockHeightKeepDepth :: BlockHeight blockHeightKeepDepth = 2_000 compact :: Config -> IO () -compact cfg = do - let cids = allChains cfg.chainwebVersion +compact cfg = withVersion cfg.chainwebVersion $ do + let cids = allChains let _compactThese = case (cfg.noRocksDb, cfg.noPactState) of (True, True) -> CompactNeither @@ -380,7 +381,7 @@ compact cfg = do withRocksDbFileLogger cfg.logDir LL.Debug $ \logger -> do withReadOnlyRocksDb (rocksDir cfg.fromDir) modernDefaultOptions $ \srcRocksDb -> do withRocksDb (rocksDir cfg.toDir) (modernDefaultOptions { compression = NoCompression }) $ \targetRocksDb -> do - compactRocksDb (set setLoggerLevel (l2l LL.Info) logger) cfg.chainwebVersion cids (targetBlockHeight - blockHeightKeepDepth) srcRocksDb targetRocksDb + compactRocksDb (set setLoggerLevel (l2l LL.Info) logger) cids (targetBlockHeight - blockHeightKeepDepth) srcRocksDb targetRocksDb -- Compact the pact state. let retainment = Retainment @@ -700,14 +701,14 @@ rocksDir db = db "0/rocksDb" -- | Copy over all CutHashes, all BlockHeaders, and only some Payloads. compactRocksDb :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -- ^ cw version -> [ChainId] -- ^ ChainIds -> BlockHeight -- ^ minBlockHeight for payload copying -> RocksDb -- ^ source db, should be opened read-only -> RocksDb -- ^ target db -> IO () -compactRocksDb logger cwVersion cids minBlockHeight srcDb targetDb = do +compactRocksDb logger cids minBlockHeight srcDb targetDb = do let log = logFunctionText logger -- Copy over entirety of CutHashes table @@ -732,8 +733,8 @@ compactRocksDb logger cwVersion cids minBlockHeight srcDb targetDb = do -- The target payload db has to be initialised. TODO PP: does it? log LL.Info "Initializing payload db" - srcWbhdb <- initWebBlockHeaderDb srcDb cwVersion - targetWbhdb <- initWebBlockHeaderDb targetDb cwVersion + srcWbhdb <- initWebBlockHeaderDb srcDb + targetWbhdb <- initWebBlockHeaderDb targetDb forM_ cids $ \cid -> do let log' = logFunctionText (addChainIdLabel cid logger) log' LL.Info $ "Starting chain " <> chainIdToText cid diff --git a/src/Chainweb/Pact/Backend/PactState.hs b/src/Chainweb/Pact/Backend/PactState.hs index 904bc5e5b7..202c70523c 100644 --- a/src/Chainweb/Pact/Backend/PactState.hs +++ b/src/Chainweb/Pact/Backend/PactState.hs @@ -56,7 +56,7 @@ module Chainweb.Pact.Backend.PactState import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.Logger (Logger, addLabel) import Chainweb.Utils (T2(..), int) -import Chainweb.Version (ChainId, ChainwebVersion, chainIdToText) +import Chainweb.Version import Chainweb.Version.Utils (chainIdsAt) import Control.Exception (bracket) import Control.Monad (forM, when) @@ -404,5 +404,5 @@ addChainIdLabel :: (Logger logger) -> logger addChainIdLabel cid = addLabel ("chainId", chainIdToText cid) -allChains :: ChainwebVersion -> [ChainId] -allChains v = List.sort $ F.toList $ chainIdsAt v (BlockHeight maxBound) +allChains :: HasVersion => [ChainId] +allChains = List.sort $ F.toList $ chainIdsAt (BlockHeight maxBound) diff --git a/src/Chainweb/Pact/Backend/PactState/Diff.hs b/src/Chainweb/Pact/Backend/PactState/Diff.hs index 49353068b5..48839d210e 100644 --- a/src/Chainweb/Pact/Backend/PactState/Diff.hs +++ b/src/Chainweb/Pact/Backend/PactState/Diff.hs @@ -29,9 +29,9 @@ import Chainweb.Logger (logFunctionText, logFunctionJson) import Chainweb.Pact.Backend.Compaction qualified as C import Chainweb.Pact.Backend.PactState (TableDiffable(..), getLatestPactStateAtDiffable, doesPactDbExist, withChainDb, allChains) import Chainweb.Utils (fromText, toText) -import Chainweb.Version (ChainwebVersion(..), ChainId, chainIdToText) +import Chainweb.Version (ChainwebVersion(..), withVersion, ChainId, chainIdToText) import Chainweb.Version.Mainnet (mainnet) -import Chainweb.Version.Registry (lookupVersionByName) +import Chainweb.Version.Registry (findKnownVersion) import Control.Monad (forM_, when, void) import Control.Monad.IO.Class (MonadIO(liftIO)) import Data.Aeson ((.=)) @@ -74,56 +74,57 @@ instance Monoid IsDifferent where main :: IO () main = do cfg <- execParser opts - - when (cfg.firstDbDir == cfg.secondDbDir) $ do - Text.putStrLn "Source and target Pact database directories cannot be the same." - exitFailure - - let cids = allChains cfg.chainwebVersion - - isDifferentRef <- newIORef @(Map ChainId IsDifferent) M.empty - - forM_ cids $ \cid -> do - C.withPerChainFileLogger cfg.logDir cid Info $ \logger -> do - let logText = logFunctionText logger - - sqliteFileExists1 <- doesPactDbExist cid cfg.firstDbDir - sqliteFileExists2 <- doesPactDbExist cid cfg.secondDbDir - - if | not sqliteFileExists1 -> do - logText Warn $ "[SQLite for chain in " <> Text.pack cfg.firstDbDir <> " doesn't exist. Skipping]" - | not sqliteFileExists2 -> do - logText Warn $ "[SQLite for chain in " <> Text.pack cfg.secondDbDir <> " doesn't exist. Skipping]" - | otherwise -> runResourceT $ do - db1 <- withChainDb cid logger cfg.firstDbDir - db2 <- withChainDb cid logger cfg.secondDbDir - liftIO $ do - logText Info "[Starting diff]" - let getPactState db = getLatestPactStateAtDiffable db cfg.target - let diff :: Stream (Of (Text, Stream (Of RowKeyDiffExists) IO ())) IO () - diff = diffLatestPactState (getPactState db1) (getPactState db2) - isDifferent <- S.foldMap_ id $ flip S.mapM diff $ \(tblName, tblDiff) -> do - logText Info $ "[Starting table " <> tblName <> "]" - d <- S.foldMap_ id $ flip S.mapM tblDiff $ \d -> do - logFunctionJson logger Warn $ rowKeyDiffExistsToObject d - pure Difference - logText Info $ "[Finished table " <> tblName <> "]" - pure d - - logText Info $ case isDifferent of - Difference -> "[Non-empty diff]" - NoDifference -> "[Empty diff]" - logText Info $ "[Finished chain " <> chainIdToText cid <> "]" - - atomicModifyIORef' isDifferentRef $ \m -> (M.insert cid isDifferent m, ()) - - isDifferent <- readIORef isDifferentRef - case M.foldMapWithKey (\_ d -> d) isDifferent of - Difference -> do - Text.putStrLn "Diff complete. Differences found." + withVersion cfg.chainwebVersion $ do + when (cfg.firstDbDir == cfg.secondDbDir) $ do + Text.putStrLn "Source and target Pact database directories cannot be the same." exitFailure - NoDifference -> do - Text.putStrLn "Diff complete. No differences found." + + let cids = allChains + + isDifferentRef <- newIORef @(Map ChainId IsDifferent) M.empty + + forM_ cids $ \cid -> do + C.withPerChainFileLogger cfg.logDir cid Info $ \logger -> do + let logText = logFunctionText logger + + sqliteFileExists1 <- doesPactDbExist cid cfg.firstDbDir + sqliteFileExists2 <- doesPactDbExist cid cfg.secondDbDir + + if + | not sqliteFileExists1 -> do + logText Warn $ "[SQLite for chain in " <> Text.pack cfg.firstDbDir <> " doesn't exist. Skipping]" + | not sqliteFileExists2 -> do + logText Warn $ "[SQLite for chain in " <> Text.pack cfg.secondDbDir <> " doesn't exist. Skipping]" + | otherwise -> runResourceT $ do + db1 <- withChainDb cid logger cfg.firstDbDir + db2 <- withChainDb cid logger cfg.secondDbDir + liftIO $ do + logText Info "[Starting diff]" + let getPactState db = getLatestPactStateAtDiffable db cfg.target + let diff :: Stream (Of (Text, Stream (Of RowKeyDiffExists) IO ())) IO () + diff = diffLatestPactState (getPactState db1) (getPactState db2) + isDifferent <- S.foldMap_ id $ flip S.mapM diff $ \(tblName, tblDiff) -> do + logText Info $ "[Starting table " <> tblName <> "]" + d <- S.foldMap_ id $ flip S.mapM tblDiff $ \d -> do + logFunctionJson logger Warn $ rowKeyDiffExistsToObject d + pure Difference + logText Info $ "[Finished table " <> tblName <> "]" + pure d + + logText Info $ case isDifferent of + Difference -> "[Non-empty diff]" + NoDifference -> "[Empty diff]" + logText Info $ "[Finished chain " <> chainIdToText cid <> "]" + + atomicModifyIORef' isDifferentRef $ \m -> (M.insert cid isDifferent m, ()) + + isDifferent <- readIORef isDifferentRef + case M.foldMapWithKey (\_ d -> d) isDifferent of + Difference -> do + Text.putStrLn "Diff complete. Differences found." + exitFailure + NoDifference -> do + Text.putStrLn "Diff complete. No differences found." where opts :: ParserInfo PactDiffConfig opts = info (parser <**> helper) @@ -138,7 +139,7 @@ main = do <*> strOption (long "log-dir" <> help "Directory where logs will be placed" <> value ".") parseChainwebVersion :: Text -> ChainwebVersion - parseChainwebVersion = lookupVersionByName . fromMaybe (error "ChainwebVersion parse failed") . fromText + parseChainwebVersion = fromMaybe (error "ChainwebVersion parse failed") . (>>= findKnownVersion) . fromText -- | We don't include the entire rowdata in the diff, only the rowkey. -- This is just a space-saving measure. diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs index f78163d741..8dd505420a 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs @@ -35,7 +35,7 @@ import Chainweb.Pact.Backend.PactState.GrandHash.Utils (resolveLatestCutHeaders, import Chainweb.Pact.Backend.Types import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) import Chainweb.Utils (sshow) -import Chainweb.Version (ChainwebVersion(..), ChainwebVersionName(..)) +import Chainweb.Version (ChainwebVersion(..), HasVersion, ChainwebVersionName(..), withVersion) import Chainweb.Version.Development (devnet) import Chainweb.Version.Mainnet (mainnet) import Chainweb.Version.RecapDevelopment (recapDevnet) @@ -73,8 +73,8 @@ data BlockHeightTargets -- | Calculate the snapshots at each BlockHeight target. pactCalc :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> HashMap ChainId SQLiteEnv -- ^ pact database dir -> RocksDb @@ -82,23 +82,23 @@ pactCalc :: (Logger logger) -> BlockHeightTargets -- ^ target for calculation -> IO [(BlockHeight, HashMap ChainId Snapshot)] -pactCalc logger v pactConns rocksDb targets = do +pactCalc logger pactConns rocksDb targets = do logFunctionText logger Debug "Starting pact-calc" chainTargets <- case targets of LatestAll -> do - List.singleton <$> resolveLatestCutHeaders logger v pactConns rocksDb + List.singleton <$> resolveLatestCutHeaders logger pactConns rocksDb TargetAll ts -> do - resolveCutHeadersAtHeights logger v pactConns rocksDb (Set.toDescList ts) + resolveCutHeadersAtHeights logger pactConns rocksDb (Set.toDescList ts) Every n -> do -- Get the latest cut headers - (latestBlockHeight, _cutHeaders) <- resolveLatestCutHeaders logger v pactConns rocksDb + (latestBlockHeight, _cutHeaders) <- resolveLatestCutHeaders logger pactConns rocksDb -- Then make sure we don't exceed them. It's okay if we attempt to access -- history that doesn't exist, the code is resilient to that, it will just -- emit warnings. let ts = List.sortOn Down $ List.takeWhile (< latestBlockHeight) $ List.map (* n) [0 .. ] -- Now it's just the same as the 'TargetAll' case. - resolveCutHeadersAtHeights logger v pactConns rocksDb ts + resolveCutHeadersAtHeights logger pactConns rocksDb ts pooledForConcurrently chainTargets $ \(b, cutHeader) -> do fmap (b,) $ computeGrandHashesAt pactConns cutHeader @@ -120,46 +120,47 @@ data PactCalcConfig = PactCalcConfig pactCalcMain :: IO () pactCalcMain = do cfg <- O.execParser opts - C.withDefaultLogger Debug $ \logger -> do - withConnections logger cfg.pactDir (allChains cfg.chainwebVersion) $ \pactConns -> do - withReadOnlyRocksDb cfg.rocksDir modernDefaultOptions $ \rocksDb -> do - chainHashes <- pactCalc logger cfg.chainwebVersion pactConns rocksDb cfg.target - when cfg.writeModule $ do - when (cfg.chainwebVersion == mainnet) $ do - let snapshotToDiffable = Map.fromList . List.map (\(b, hm) -> (b, Map.fromList (HM.toList hm))) - let currentSnapshot = snapshotToDiffable MainnetSnapshot.grands - let newSnapshot = snapshotToDiffable chainHashes + withVersion cfg.chainwebVersion $ do + C.withDefaultLogger Debug $ \logger -> do + withConnections logger cfg.pactDir allChains $ \pactConns -> do + withReadOnlyRocksDb cfg.rocksDir modernDefaultOptions $ \rocksDb -> do + chainHashes <- pactCalc logger pactConns rocksDb cfg.target + when cfg.writeModule $ do + when (cfg.chainwebVersion == mainnet) $ do + let snapshotToDiffable = Map.fromList . List.map (\(b, hm) -> (b, Map.fromList (HM.toList hm))) + let currentSnapshot = snapshotToDiffable MainnetSnapshot.grands + let newSnapshot = snapshotToDiffable chainHashes - forM_ (Map.toList (P.diff currentSnapshot newSnapshot)) $ \(height, d) -> do - case d of - -- We only care about pre-existing blockheights with differences. - -- - -- - For 'Same', there is definitely no cause for concern. - -- - For 'Old', that probably means that we are just using a new offset (see 'Every'). - -- - For 'New', that means that we are adding a new BlockHeight(s). - -- - -- - But for 'Delta', we need to check if any of the hashes have - -- changed. If they have, that is very bad. - P.Delta cur new -> do - forM_ (Map.toList (P.diff cur new)) $ \(cid, sd) -> do - case sd of - -- Here, similarly, we only care about changed - -- pre-existing values. - P.Delta _ _ -> do - let msg = Text.concat - [ "Hash mismatch when attempting to regenerate snapshot: " - , "blockheight = ", sshow height, "; " - , "chainId = ", chainIdToText cid, "; " - ] - logFunctionText logger Error msg - _ -> do - pure () - _ -> do - pure () + forM_ (Map.toList (P.diff currentSnapshot newSnapshot)) $ \(height, d) -> do + case d of + -- We only care about pre-existing blockheights with differences. + -- + -- - For 'Same', there is definitely no cause for concern. + -- - For 'Old', that probably means that we are just using a new offset (see 'Every'). + -- - For 'New', that means that we are adding a new BlockHeight(s). + -- + -- - But for 'Delta', we need to check if any of the hashes have + -- changed. If they have, that is very bad. + P.Delta cur new -> do + forM_ (Map.toList (P.diff cur new)) $ \(cid, sd) -> do + case sd of + -- Here, similarly, we only care about changed + -- pre-existing values. + P.Delta _ _ -> do + let msg = Text.concat + [ "Hash mismatch when attempting to regenerate snapshot: " + , "blockheight = ", sshow height, "; " + , "chainId = ", chainIdToText cid, "; " + ] + logFunctionText logger Error msg + _ -> do + pure () + _ -> do + pure () - let modulePath = "src/Chainweb/Pact/Backend/PactState/EmbeddedSnapshot/" <> versionModuleName cfg.chainwebVersion <> ".hs" - writeFile modulePath (chainHashesToModule cfg.chainwebVersion chainHashes) - BLC8.putStrLn $ grandsToJson chainHashes + let modulePath = "src/Chainweb/Pact/Backend/PactState/EmbeddedSnapshot/" <> versionModuleName cfg.chainwebVersion <> ".hs" + writeFile modulePath (chainHashesToModule cfg.chainwebVersion chainHashes) + BLC8.putStrLn $ grandsToJson chainHashes where opts :: ParserInfo PactCalcConfig opts = O.info (parser <**> O.helper) (O.fullDesc <> O.progDesc helpText) diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs index a1550861ae..9188665970 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs @@ -83,15 +83,16 @@ import Chainweb.Pact.Backend.PactState.GrandHash.Utils (resolveLatestCutHeaders, import Chainweb.Pact.Backend.Types import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) import Chainweb.Utils (sshow) -import Chainweb.Version (ChainwebVersion(..)) +import Chainweb.Version (ChainwebVersion(..), HasVersion, withVersion) -- | Verifies that the hashes and headers match @grands@. -- -- Returns the latest (highest) blockheight along with the snapshot -- thereat. -pactVerify :: (Logger logger) +pactVerify + :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> HashMap ChainId SQLiteEnv -- ^ pact connections -> RocksDb @@ -99,11 +100,11 @@ pactVerify :: (Logger logger) -> [(BlockHeight, HashMap ChainId Snapshot)] -- ^ grands -> IO (BlockHeight, HashMap ChainId Snapshot) -pactVerify logger v pactConns rocksDb grands = do +pactVerify logger pactConns rocksDb grands = do logFunctionText logger Debug "Starting pact-verify" -- Get the highest common blockheight across all chains. - latestBlockHeight <- fst <$> resolveLatestCutHeaders logger v pactConns rocksDb + latestBlockHeight <- fst <$> resolveLatestCutHeaders logger pactConns rocksDb snapshot@(snapshotBlockHeight, expectedChainHashes) <- do -- Find the first element of 'grands' such that @@ -116,7 +117,7 @@ pactVerify logger v pactConns rocksDb grands = do pure s chainHashes <- do - chainTargets <- resolveCutHeadersAtHeight logger v pactConns rocksDb snapshotBlockHeight + chainTargets <- resolveCutHeadersAtHeight logger pactConns rocksDb snapshotBlockHeight computeGrandHashesAt pactConns chainTargets let deltas = Map.filter (not . P.isSame) @@ -151,8 +152,8 @@ pactVerify logger v pactConns rocksDb grands = do pure snapshot pactDropPostVerified :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> FilePath -- ^ source pact dir -> FilePath @@ -162,7 +163,7 @@ pactDropPostVerified :: (Logger logger) -> HashMap ChainId Snapshot -- ^ Grand Hashes & BlockHeaders at this blockheight -> IO () -pactDropPostVerified logger v srcDir tgtDir snapshotBlockHeight snapshotChainHashes = do +pactDropPostVerified logger srcDir tgtDir snapshotBlockHeight snapshotChainHashes = do logFunctionText logger Info $ "Creating " <> Text.pack tgtDir createDirectoryIfMissing True tgtDir @@ -186,7 +187,7 @@ pactDropPostVerified logger v srcDir tgtDir snapshotBlockHeight snapshotChainHas let logger' = addChainIdLabel cid logger logFunctionText logger' Info $ "Dropping anything post verified state (BlockHeight " <> sshow snapshotBlockHeight <> ")" - PactDb.rewindDbTo v cid sqliteEnv $ view rankedBlockHash $ blockHeader $ snapshotChainHashes ^?! ix cid + PactDb.rewindDbTo cid sqliteEnv $ view rankedBlockHash $ blockHeader $ snapshotChainHashes ^?! ix cid data PactImportConfig = PactImportConfig { sourcePactDir :: FilePath @@ -199,22 +200,23 @@ pactImportMain :: IO () pactImportMain = do cfg <- O.execParser opts - let chains = allChains cfg.chainwebVersion + withVersion cfg.chainwebVersion $ do + let chains = allChains - C.withDefaultLogger Info $ \logger -> do - withConnections logger cfg.sourcePactDir chains $ \srcConns -> do - withReadOnlyRocksDb cfg.rocksDir modernDefaultOptions $ \rocksDb -> do - (snapshotBlockHeight, snapshotChainHashes) <- pactVerify logger cfg.chainwebVersion srcConns rocksDb MainnetSnapshots.grands + C.withDefaultLogger Info $ \logger -> do + withConnections logger cfg.sourcePactDir chains $ \srcConns -> do + withReadOnlyRocksDb cfg.rocksDir modernDefaultOptions $ \rocksDb -> do + (snapshotBlockHeight, snapshotChainHashes) <- pactVerify logger srcConns rocksDb MainnetSnapshots.grands - -- Set this before pactDropPostVerified, in case there's a failure, so - -- it's still recoverable. - -- - -- pact-import doesn't use this environment variable; it's for - -- debugging and/or consumption by other tools. - setEnv "SNAPSHOT_BLOCKHEIGHT" (show snapshotBlockHeight) + -- Set this before pactDropPostVerified, in case there's a failure, so + -- it's still recoverable. + -- + -- pact-import doesn't use this environment variable; it's for + -- debugging and/or consumption by other tools. + setEnv "SNAPSHOT_BLOCKHEIGHT" (show snapshotBlockHeight) - forM_ cfg.targetPactDir $ \targetDir -> do - pactDropPostVerified logger cfg.chainwebVersion cfg.sourcePactDir targetDir snapshotBlockHeight snapshotChainHashes + forM_ cfg.targetPactDir $ \targetDir -> do + pactDropPostVerified logger cfg.sourcePactDir targetDir snapshotBlockHeight snapshotChainHashes where opts :: ParserInfo PactImportConfig opts = O.info (parser <**> O.helper) (O.fullDesc <> O.progDesc helpText) diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs index c72be94445..db27f71711 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs @@ -38,9 +38,9 @@ import Chainweb.Pact.Backend.Utils (startSqliteDb, stopSqliteDb) import Chainweb.Storage.Table.RocksDB (RocksDb) import Chainweb.TreeDB (seekAncestor) import Chainweb.Utils (fromText, toText, sshow) -import Chainweb.Version (ChainwebVersion(..)) +import Chainweb.Version (ChainwebVersion(..), HasVersion(..)) import Chainweb.Version.Mainnet (mainnet) -import Chainweb.Version.Registry (lookupVersionByName) +import Chainweb.Version.Registry import Chainweb.WebBlockHeaderDB (WebBlockHeaderDb, getWebBlockHeaderDb, initWebBlockHeaderDb) import Control.Exception (bracket) import Control.Lens ((^?!), ix, view) @@ -66,6 +66,7 @@ import UnliftIO.Async (pooledForConcurrently, pooledForConcurrently_) -- and calling 'seekAncestor' to find the 'BlockHeader's associated with the -- specified blockheight at each chain (this is the cut header). limitCut :: (Logger logger) + => HasVersion => logger -> WebBlockHeaderDb -> HashMap ChainId BlockHeader -- ^ latest cut headers @@ -101,13 +102,13 @@ limitCut logger wbhdb latestCutHeaders pactConns bHeight = do -- -- Also returns a 'WebBlockHeaderDb' for convenience to callers. getLatestCutHeaders :: () - => ChainwebVersion - -> RocksDb + => HasVersion + => RocksDb -> IO (WebBlockHeaderDb, HashMap ChainId BlockHeader) -getLatestCutHeaders v rocksDb = do - wbhdb <- initWebBlockHeaderDb rocksDb v +getLatestCutHeaders rocksDb = do + wbhdb <- initWebBlockHeaderDb rocksDb let cutHashes = cutHashesTable rocksDb - latestCutHeaders <- readHighestCutHeaders v (\_ _ -> pure ()) wbhdb cutHashes + latestCutHeaders <- readHighestCutHeaders (\_ _ -> pure ()) wbhdb cutHashes pure (wbhdb, latestCutHeaders) -- | Take the latest cut headers, and find the minimum 'BlockHeight' across @@ -115,13 +116,13 @@ getLatestCutHeaders v rocksDb = do -- amongst all chains. Then return the cut headers associated with that -- latest common 'BlockHeight'. resolveLatestCutHeaders :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> HashMap ChainId SQLiteEnv -> RocksDb -> IO (BlockHeight, HashMap ChainId BlockHeader) -resolveLatestCutHeaders logger v pactConns rocksDb = do - (wbhdb, latestCutHeaders) <- getLatestCutHeaders v rocksDb +resolveLatestCutHeaders logger pactConns rocksDb = do + (wbhdb, latestCutHeaders) <- getLatestCutHeaders rocksDb let latestCommonBlockHeight = minimum $ fmap (view blockHeight) latestCutHeaders headers <- limitCut logger wbhdb latestCutHeaders pactConns latestCommonBlockHeight pure (latestCommonBlockHeight, headers) @@ -129,14 +130,14 @@ resolveLatestCutHeaders logger v pactConns rocksDb = do -- | Take the latest cut headers, and return the cut headers associated with -- the specified 'BlockHeight'. resolveCutHeadersAtHeight :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> HashMap ChainId SQLiteEnv -> RocksDb -> BlockHeight -> IO (HashMap ChainId BlockHeader) -resolveCutHeadersAtHeight logger v pactConns rocksDb target = do - (wbhdb, latestCutHeaders) <- getLatestCutHeaders v rocksDb +resolveCutHeadersAtHeight logger pactConns rocksDb target = do + (wbhdb, latestCutHeaders) <- getLatestCutHeaders rocksDb limitCut logger wbhdb latestCutHeaders pactConns target -- | Take the latest cut headers, and, for each specified 'BlockHeight', @@ -144,14 +145,14 @@ resolveCutHeadersAtHeight logger v pactConns rocksDb target = do -- -- The list returned pairs the input 'BlockHeight's with the found headers. resolveCutHeadersAtHeights :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> HashMap ChainId SQLiteEnv -> RocksDb -> [BlockHeight] -- ^ targets -> IO [(BlockHeight, HashMap ChainId BlockHeader)] -resolveCutHeadersAtHeights logger v pactConns rocksDb targets = do - (wbhdb, latestCutHeaders) <- getLatestCutHeaders v rocksDb +resolveCutHeadersAtHeights logger pactConns rocksDb targets = do + (wbhdb, latestCutHeaders) <- getLatestCutHeaders rocksDb forM targets $ \target -> do fmap (target, ) $ limitCut logger wbhdb latestCutHeaders pactConns target @@ -219,7 +220,7 @@ hex :: ByteString -> Text hex = Text.decodeUtf8 . Base16.encode cwvParser :: O.Parser ChainwebVersion -cwvParser = fmap (lookupVersionByName . fromMaybe (error "ChainwebVersion parse failed") . fromText) +cwvParser = fmap (fromMaybe (error "ChainwebVersion parse failed") . (>>= findKnownVersion) . fromText) $ O.strOption (O.long "graph-version" <> O.short 'v' diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 9781a681a2..44b1b0cd74 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -402,9 +402,9 @@ doLookupSuccessful db curHeight hashes = throwOnDbError $ do return $! T4 txhash' (fromIntegral blockheight) payloadhash' blockhash' go _ = fail "impossible" -getEndTxId :: HasCallStack => ChainwebVersion -> ChainId -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) -getEndTxId v cid sql pc - | isGenesisBlockHeader' v cid (_rankedBlockHashHash <$> pc) = +getEndTxId :: (HasVersion, HasCallStack) => ChainId -> SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) +getEndTxId cid sql pc + | isGenesisBlockHeader' cid (_rankedBlockHashHash <$> pc) = return (Historical (Pact.TxId 0)) | otherwise = getEndTxId' sql pc @@ -425,17 +425,17 @@ getEndTxId' sql (Parent rbh) = throwOnDbError $ do -- | Delete any state from the database newer than the input parent header. -- Returns the ending txid of the input parent header. rewindDbTo - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> SQLiteEnv -> RankedBlockHash -> IO Pact.TxId -rewindDbTo v cid db pc - | isGenesisBlockHeader' v cid (Parent $ _rankedBlockHashHash pc) = do +rewindDbTo cid db pc + | isGenesisBlockHeader' cid (Parent $ _rankedBlockHashHash pc) = do rewindDbToGenesis db return (Pact.TxId 0) | otherwise = do - !historicalEndingTxId <- getEndTxId v cid db (Parent pc) + !historicalEndingTxId <- getEndTxId cid db (Parent pc) endingTxId <- case historicalEndingTxId of NoHistory -> error diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 9f978b5946..78a8949293 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -124,8 +124,8 @@ import Chainweb.Core.Brief withPactService :: (Logger logger, CanPayloadCas tbl) - => ChainwebVersion - -> ChainId + => HasVersion + => ChainId -> Maybe HTTP.Manager -> MemPoolAccess -> logger @@ -136,8 +136,8 @@ withPactService -> PactServiceConfig -> GenesisConfig -> ResourceT IO (ServiceEnv tbl) -withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config pactGenesis = do - SomeChainwebVersionT @v _ <- pure $ someChainwebVersionVal ver +withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config pactGenesis = do + SomeChainwebVersionT @v _ <- pure someChainwebVersionVal SomeChainIdT @c _ <- pure $ someChainIdVal cid let payloadClient = Rest.payloadClient @v @c @'PactProvider payloadStore <- liftIO $ newPayloadStore http (logFunction chainwebLogger) pdb payloadClient @@ -151,8 +151,7 @@ withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb candidatePdb <- liftIO MapTable.emptyTable let !pse = ServiceEnv - { _psVersion = ver - , _psChainId = cid + { _psChainId = cid -- TODO: PPgaslog -- , _psGasLogger = undefined <$ guard (_pactLogGas config) , _psGasLogger = Nothing @@ -183,36 +182,38 @@ withPactService ver cid http memPoolAccess chainwebLogger txFailuresCounter pdb initialPayloadState :: Logger logger + => HasVersion => CanPayloadCas tbl => logger -> ServiceEnv tbl -> IO () initialPayloadState logger serviceEnv -- TODO PP: no more, once we can disable payload providers - | serviceEnv ^. chainwebVersion . versionCheats . disablePact = pure () + | implicitVersion ^. versionCheats . disablePact = pure () | otherwise = runGenesisIfNeeded logger serviceEnv runGenesisIfNeeded :: forall tbl logger. (CanPayloadCas tbl, Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> IO () runGenesisIfNeeded logger serviceEnv = do latestBlock <- fmap _consensusStateLatest <$> Checkpointer.getConsensusState (_psReadWriteSql serviceEnv) - when (maybe True (isGenesisBlockHeader' v cid . Parent . _syncStateBlockHash) latestBlock) $ do + when (maybe True (isGenesisBlockHeader' cid . Parent . _syncStateBlockHash) latestBlock) $ do logFunctionText logger Debug "running genesis" - let genesisBlockHash = genesisBlockHeader v cid ^. blockHash - let genesisPayloadHash = genesisBlockPayloadHash v cid - let gTime = v ^?! versionGenesis . genesisTime . atChain cid - let targetSyncState = genesisConsensusState v cid - let genesisRankedBlockHash = RankedBlockHash (genesisHeight v cid) genesisBlockHash + let genesisBlockHash = genesisBlockHeader cid ^. blockHash + let genesisPayloadHash = genesisBlockPayloadHash cid + let gTime = implicitVersion ^?! versionGenesis . genesisTime . atChain cid + let targetSyncState = genesisConsensusState cid + let genesisRankedBlockHash = RankedBlockHash (genesisHeight cid) genesisBlockHash let evalCtx = genesisEvaluationCtx serviceEnv - let blockCtx = blockCtxOfEvaluationCtx v cid evalCtx + let blockCtx = blockCtxOfEvaluationCtx cid evalCtx let !genesisPayload = case _psGenesisPayload serviceEnv of Nothing -> error "genesis needs to be run, but the genesis payload is missing!" Just p -> p - maybeErr <- runExceptT $ Checkpointer.restoreAndSave logger v cid (_psReadWriteSql serviceEnv) + maybeErr <- runExceptT $ Checkpointer.restoreAndSave logger cid (_psReadWriteSql serviceEnv) $ NEL.singleton $ (blockCtx, \blockEnv -> do _ <- Pact.execExistingBlock logger serviceEnv blockEnv @@ -224,12 +225,12 @@ runGenesisIfNeeded logger serviceEnv = do Right () -> do addNewPayload (_payloadStoreTable $ _psPdb serviceEnv) - (genesisHeight v cid) + (genesisHeight cid) genesisPayload Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState -- we have to kick off payload refreshing here emptyBlock <- (throwIfNoHistory =<<) $ - Checkpointer.readFrom logger v cid + Checkpointer.readFrom logger cid (_psReadWriteSql serviceEnv) (Parent gTime) (Parent genesisRankedBlockHash) $ @@ -239,23 +240,22 @@ runGenesisIfNeeded logger serviceEnv = do atomically $ writeTMVar (_psMiningPayloadVar serviceEnv) (refresherThread, emptyBlock) where - v = _chainwebVersion serviceEnv cid = _chainId serviceEnv -- | only for use in generating genesis blocks in tools. -- execNewGenesisBlock :: (Logger logger, CanReadablePayloadCas tbl) + => HasVersion => logger -> ServiceEnv tbl -> Vector Pact.Transaction -> IO PayloadWithOutputs execNewGenesisBlock logger serviceEnv newTrans = do - let v = _chainwebVersion serviceEnv let cid = _chainId serviceEnv - let parentCreationTime = Parent (v ^?! versionGenesis . genesisTime . atChain cid) - let genesisParent = Parent $ RankedBlockHash (genesisHeight v cid) (unwrapParent $ genesisParentBlockHash v cid) - historicalBlock <- Checkpointer.readFrom logger v cid (_psReadWriteSql serviceEnv) parentCreationTime genesisParent $ \blockEnv startHandle -> do + let parentCreationTime = Parent (implicitVersion ^?! versionGenesis . genesisTime . atChain cid) + let genesisParent = Parent $ RankedBlockHash (genesisHeight cid) (unwrapParent $ genesisParentBlockHash cid) + historicalBlock <- Checkpointer.readFrom logger cid (_psReadWriteSql serviceEnv) parentCreationTime genesisParent $ \blockEnv startHandle -> do let bipStart = BlockInProgress { _blockInProgressHandle = startHandle @@ -296,13 +296,13 @@ execNewGenesisBlock logger serviceEnv newTrans = do execReadOnlyReplay :: forall logger tbl . (Logger logger, CanReadablePayloadCas tbl) + => HasVersion => logger -> ServiceEnv tbl -> [EvaluationCtx BlockPayloadHash] -> IO [BlockInvalidError] execReadOnlyReplay logger serviceEnv blocks = do let readSqlPool = view psReadSqlPool serviceEnv - let v = view chainwebVersion serviceEnv let cid = view chainId serviceEnv let pdb = view psPdb serviceEnv blocks @@ -310,12 +310,11 @@ execReadOnlyReplay logger serviceEnv blocks = do payload <- liftIO $ fromJuste <$> lookupPayloadWithHeight (_payloadStoreTable pdb) (Just $ _evaluationCtxCurrentHeight evalCtx) (_evaluationCtxPayload evalCtx) let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload) - let isUpgradeBlock = isJust $ v ^? versionUpgrades . atChain cid . ix (_evaluationCtxCurrentHeight evalCtx) + let isUpgradeBlock = isJust $ implicitVersion ^? versionUpgrades . atChain cid . ix (_evaluationCtxCurrentHeight evalCtx) if isPayloadEmpty && not isUpgradeBlock then Pool.withResource readSqlPool $ \sql -> do hist <- Checkpointer.readFrom logger - v cid sql (_evaluationCtxParentCreationTime evalCtx) @@ -331,6 +330,7 @@ execReadOnlyReplay logger serviceEnv blocks = do execLocal :: (Logger logger, CanReadablePayloadCas tbl) + => HasVersion => logger -> ServiceEnv tbl -> Pact.Transaction @@ -353,7 +353,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do doLocal = Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> do fakeNewBlockCtx <- liftIO Checkpointer.mkFakeParentCreationTime - Checkpointer.readFromNthParent logger v cid sql fakeNewBlockCtx (fromIntegral rewindDepth) $ \blockEnv blockHandle -> do + Checkpointer.readFromNthParent logger cid sql fakeNewBlockCtx (fromIntegral rewindDepth) $ \blockEnv blockHandle -> do let blockCtx = view psBlockCtx blockEnv let requestKey = Pact.cmdToRequestKey cwtx evalContT $ withEarlyReturn $ \earlyReturn -> do @@ -419,7 +419,6 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do gasLogger = view psGasLogger serviceEnv enableLocalTimeout = view psEnableLocalTimeout serviceEnv - v = _chainwebVersion serviceEnv cid = _chainId serviceEnv -- when no depth is defined, treat @@ -433,6 +432,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do makeEmptyBlock :: forall logger tbl. (Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> BlockEnv @@ -476,6 +476,7 @@ makeEmptyBlock logger serviceEnv blockEnv initialBlockHandle = syncToFork :: forall tbl logger . (CanPayloadCas tbl, Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> Maybe Hints @@ -504,7 +505,7 @@ syncToFork logger serviceEnv hints forkInfo = do -- TODO PP: disallow rewinding final? logFunctionText logger Debug $ "pure rewind to " <> brief forkInfo._forkInfoTargetState rewoundTxs <- getRewoundTxs (Parent $ forkInfo._forkInfoTargetState._consensusStateLatest._syncStateHeight) - Checkpointer.rewindTo v cid sql (_syncStateRankedBlockHash (_consensusStateLatest forkInfo._forkInfoTargetState)) + Checkpointer.rewindTo cid sql (_syncStateRankedBlockHash (_consensusStateLatest forkInfo._forkInfoTargetState)) Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) else do @@ -540,14 +541,14 @@ syncToFork logger serviceEnv hints forkInfo = do Just payload -> return payload let expectedPayloadHash = _consensusPayloadHash $ _evaluationCtxPayload evalCtx return $ - (blockCtxOfEvaluationCtx v cid evalCtx, \blockEnv -> do + (blockCtxOfEvaluationCtx cid evalCtx, \blockEnv -> do (_, pwo, validatedTxs) <- Pact.execExistingBlock logger serviceEnv blockEnv (CheckablePayload expectedPayloadHash payload) -- add payload immediately after executing the block, because this is when we learn it's valid liftIO $ addNewPayload (_payloadStoreTable $ _psPdb serviceEnv) (_evaluationCtxCurrentHeight evalCtx) pwo return $ (DList.singleton validatedTxs, (_ranked rankedBHash, expectedPayloadHash)) ) - runExceptT (Checkpointer.restoreAndSave logger v cid sql runnableBlocks) >>= \case + runExceptT (Checkpointer.restoreAndSave logger cid sql runnableBlocks) >>= \case Left err -> do logFunctionText logger Error $ "Error in execValidateBlock: " <> sshow err return (mempty, mempty, pactConsensusState) @@ -565,7 +566,7 @@ syncToFork logger serviceEnv hints forkInfo = do -- immediately. then we set up a separate thread -- to add new transactions to the block. logFunctionText logger Debug "producing new block" - emptyBlock <- Checkpointer.readFromLatest logger v cid sql (_newBlockCtxParentCreationTime newBlockCtx) $ \blockEnv blockHandle -> + emptyBlock <- Checkpointer.readFromLatest logger cid sql (_newBlockCtxParentCreationTime newBlockCtx) $ \blockEnv blockHandle -> makeEmptyBlock logger serviceEnv blockEnv blockHandle let payloadVar = view psMiningPayloadVar serviceEnv @@ -586,7 +587,6 @@ syncToFork logger serviceEnv hints forkInfo = do memPoolAccess = view psMempoolAccess serviceEnv sql = view psReadWriteSql serviceEnv pdb = view psPdb serviceEnv - v = _chainwebVersion serviceEnv cid = _chainId serviceEnv findForkChain @@ -635,7 +635,12 @@ syncToFork logger serviceEnv hints forkInfo = do (fmap (fromRight (error "invalid payload in database")) . runExceptT . pact5TransactionsFromPayload) rewoundPayloads -refreshPayloads :: Logger logger => logger -> ServiceEnv tbl -> IO () +refreshPayloads + :: Logger logger + => HasVersion + => logger + -> ServiceEnv tbl + -> IO () refreshPayloads logger serviceEnv = do -- note that if this is empty, we wait; taking from it is the way to make us stop let logOutraced = @@ -645,7 +650,7 @@ refreshPayloads logger serviceEnv = do "refreshing payloads for " <> brief (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) maybeRefreshedBlockInProgress <- Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> - Checkpointer.readFrom logger v cid sql (_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress) (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) $ \blockEnv _bh -> do + Checkpointer.readFrom logger cid sql (_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress) (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) $ \blockEnv _bh -> do let dbEnv = view psBlockDbEnv blockEnv continueBlock logger serviceEnv dbEnv blockInProgress case maybeRefreshedBlockInProgress of @@ -669,7 +674,6 @@ refreshPayloads logger serviceEnv = do payloadVar = _psMiningPayloadVar serviceEnv cid = _chainId serviceEnv - v = _chainwebVersion serviceEnv getPayloadForContext :: CanReadablePayloadCas tbl @@ -702,6 +706,7 @@ getPayloadForContext logger serviceEnv h ctx = do execPreInsertCheckReq :: (Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> Vector Pact.Transaction @@ -710,7 +715,7 @@ execPreInsertCheckReq logger serviceEnv txs = do let requestKeys = V.map Pact.cmdToRequestKey txs logFunctionText logger Info $ "(pre-insert check " <> sshow requestKeys <> ")" fakeParentCreationTime <- Checkpointer.mkFakeParentCreationTime - let act sql = Checkpointer.readFromLatest logger v cid sql fakeParentCreationTime $ \blockEnv bh -> do + let act sql = Checkpointer.readFromLatest logger cid sql fakeParentCreationTime $ \blockEnv bh -> do forM txs $ \tx -> fmap (either Just (\_ -> Nothing)) $ runExceptT $ do -- it's safe to use initialBlockHandle here because it's @@ -732,7 +737,6 @@ execPreInsertCheckReq logger serviceEnv txs = do where preInsertCheckTimeout = view psPreInsertCheckTimeout serviceEnv - v = _chainwebVersion serviceEnv cid = _chainId serviceEnv timeoutLimit = fromIntegral $ (\(Micros n) -> n) preInsertCheckTimeout attemptBuyGas @@ -756,6 +760,7 @@ execPreInsertCheckReq logger serviceEnv txs = do execLookupPactTxs :: (CanReadablePayloadCas tbl, Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> Maybe ConfirmationDepth @@ -768,9 +773,8 @@ execLookupPactTxs logger serviceEnv confDepth txs = do go =<< liftIO Checkpointer.mkFakeParentCreationTime where depth = maybe 0 (fromIntegral . _confirmationDepth) confDepth - v = _chainwebVersion serviceEnv cid = _chainId serviceEnv go ctx = Pool.withResource (_psReadSqlPool serviceEnv) $ \sql -> - Checkpointer.readFromNthParent logger v cid sql ctx depth $ \blockEnv _ -> do + Checkpointer.readFromNthParent logger cid sql ctx depth $ \blockEnv _ -> do let dbenv = view psBlockDbEnv blockEnv fmap (HM.mapKeys coerce) $ liftIO $ Pact.lookupPactTransactions dbenv (coerce txs) diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 75c6284529..4626b2fbcc 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -101,37 +101,35 @@ mkFakeParentCreationTime = do -- we just keep grabbing the new "latest header" until we succeed. -- note: this function will never rewind before genesis. readFromLatest - :: (HasCallStack, Logger logger) + :: (HasCallStack, HasVersion, Logger logger) => logger - -> ChainwebVersion -> ChainId -> SQLiteEnv -> Parent BlockCreationTime -> (BlockEnv -> BlockHandle -> IO a) -> IO a -readFromLatest logger v cid sql parentCreationTime doRead = - readFromNthParent logger v cid sql parentCreationTime 0 doRead >>= \case +readFromLatest logger cid sql parentCreationTime doRead = + readFromNthParent logger cid sql parentCreationTime 0 doRead >>= \case NoHistory -> error "readFromLatest: failed to grab the latest header, this is a bug in chainweb" Historical a -> return a -- read-only rewind to the nth parent before the latest block. -- note: this function will never rewind before genesis. readFromNthParent - :: (HasCallStack, Logger logger) + :: (HasCallStack, HasVersion, Logger logger) => logger - -> ChainwebVersion -> ChainId -> SQLiteEnv -> Parent BlockCreationTime -> Word -> (BlockEnv -> BlockHandle -> IO a) -> IO (Historical a) -readFromNthParent logger v cid sql parentCreationTime n doRead = do +readFromNthParent logger cid sql parentCreationTime n doRead = do withSavepoint sql ReadFromNSavepoint $ do latest <- _consensusStateLatest . fromMaybe (error "readFromNthParent is illegal to call before genesis") <$> getConsensusState sql - if genesisHeight v cid + fromIntegral @Word @BlockHeight n > _syncStateHeight latest + if genesisHeight cid + fromIntegral @Word @BlockHeight n > _syncStateHeight latest then do logFunctionText logger Warn $ "readFromNthParent asked to rewind beyond genesis, to " <> sshow (int (_syncStateHeight latest) - int n :: Integer) @@ -144,14 +142,13 @@ readFromNthParent logger v cid sql parentCreationTime n doRead = do logFunctionText logger Warn "readFromNthParent asked to rewind beyond known blocks" return NoHistory Just nthBlock -> - readFrom logger v cid sql parentCreationTime (Parent nthBlock) doRead + readFrom logger cid sql parentCreationTime (Parent nthBlock) doRead -- read-only rewind to a target block. -- if that target block is missing, return Nothing. readFrom - :: (HasCallStack, Logger logger) + :: (HasCallStack, HasVersion, Logger logger) => logger - -> ChainwebVersion -> ChainId -> SQLiteEnv -> Parent BlockCreationTime @@ -159,28 +156,26 @@ readFrom -> Parent RankedBlockHash -> (BlockEnv -> BlockHandle -> IO a) -> IO (Historical a) -readFrom logger v cid sql parentCreationTime parent doRead = do +readFrom logger cid sql parentCreationTime parent doRead = do let blockCtx = BlockCtx { _bctxParentCreationTime = parentCreationTime , _bctxParentHash = _rankedBlockHashHash <$> parent , _bctxParentHeight = _rankedBlockHashHeight <$> parent - , _bctxMinerReward = blockMinerReward v (childBlockHeight v cid parent) - , _bctxChainwebVersion = v + , _bctxMinerReward = blockMinerReward (childBlockHeight cid parent) , _bctxChainId = cid } liftIO $ withSavepoint sql ReadFromSavepoint $ do - !latestHeader <- maybe (genesisRankedParentBlockHash v cid) (Parent . _syncStateRankedBlockHash . _consensusStateLatest) <$> + !latestHeader <- maybe (genesisRankedParentBlockHash cid) (Parent . _syncStateRankedBlockHash . _consensusStateLatest) <$> ChainwebPactDb.throwOnDbError (ChainwebPactDb.getConsensusState sql) -- is the parent the latest header, i.e., can we get away without rewinding? let parentIsLatestHeader = latestHeader == parent let currentHeight = _bctxCurrentBlockHeight blockCtx - if pact5 v cid currentHeight - then PactDb.getEndTxId v cid sql parent >>= traverse \startTxId -> do + if pact5 cid currentHeight + then PactDb.getEndTxId cid sql parent >>= traverse \startTxId -> do let blockHandlerEnv = ChainwebPactDb.BlockHandlerEnv { ChainwebPactDb._blockHandlerDb = sql , ChainwebPactDb._blockHandlerLogger = logger - , ChainwebPactDb._blockHandlerVersion = v , ChainwebPactDb._blockHandlerChainId = cid , ChainwebPactDb._blockHandlerBlockHeight = currentHeight , ChainwebPactDb._blockHandlerMode = Pact.Transactional @@ -194,11 +189,14 @@ readFrom logger v cid sql parentCreationTime parent doRead = do -- the special case where one doesn't want to extend the chain, just rewind it. rewindTo - :: ChainwebVersion -> ChainId -> SQLiteEnv - -> RankedBlockHash -> IO () -rewindTo v cid sql ancestor = do + :: HasVersion + => ChainId + -> SQLiteEnv + -> RankedBlockHash + -> IO () +rewindTo cid sql ancestor = do withSavepoint sql RewindSavePoint $ - void $ PactDb.rewindDbTo v cid sql ancestor + void $ PactDb.rewindDbTo cid sql ancestor -- TODO: log more? -- | Given a list of blocks in ascending order, rewind to the first @@ -213,18 +211,16 @@ rewindTo v cid sql ancestor = do -- - each subsequent block must be the direct child of the previous block. restoreAndSave :: forall logger m q. - (Logger logger, MonadIO m, MonadMask m, Semigroup q, HasCallStack) + (Logger logger, MonadIO m, MonadMask m, Semigroup q, HasCallStack, HasVersion) => logger - -> ChainwebVersion -> ChainId -> SQLiteEnv -> NonEmpty (BlockCtx, BlockEnv -> StateT BlockHandle m (q, (BlockHash, BlockPayloadHash))) -> m q -restoreAndSave logger v cid sql blocks = do +restoreAndSave logger cid sql blocks = do withSavepoint sql RestoreAndSaveSavePoint $ do -- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above. startTxId <- liftIO $ PactDb.rewindDbTo - v cid sql (unwrapParent $ _bctxParentRankedBlockHash $ fst $ NE.head blocks) @@ -247,7 +243,6 @@ restoreAndSave logger v cid sql blocks = do blockEnv = ChainwebPactDb.BlockHandlerEnv { ChainwebPactDb._blockHandlerDb = sql , ChainwebPactDb._blockHandlerLogger = logger - , ChainwebPactDb._blockHandlerVersion = v , ChainwebPactDb._blockHandlerBlockHeight = bh , ChainwebPactDb._blockHandlerChainId = cid , ChainwebPactDb._blockHandlerMode = Pact.Transactional @@ -255,7 +250,7 @@ restoreAndSave logger v cid sql blocks = do , ChainwebPactDb._blockHandlerAtTip = True } pactDb = ChainwebPactDb.chainwebPactBlockDb blockEnv - in if pact5 v cid bh then do + in if pact5 cid bh then do -- run the block ((m, blockInfo), blockHandle) <- diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index 1bc40c62cd..aacc7748c6 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -83,6 +83,7 @@ import qualified Chainweb.Pact.Types as Pact runCoinbase :: (Logger logger) + => HasVersion => logger -> BlockEnv -> Miner @@ -104,6 +105,7 @@ runCoinbase logger blockEnv miner = do continueBlock :: forall logger tbl . (Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> ChainwebPactDb @@ -296,6 +298,7 @@ type InvalidTransactions = [Pact.RequestKey] -- This function completely ignores timeouts! applyCmdInBlock :: (Traversable t, Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> BlockEnv @@ -414,6 +417,7 @@ applyCmdInBlock logger serviceEnv blockEnv miner txIdxInBlock tx = StateT $ \(bl -- validateParsedChainwebTx :: (Logger logger) + => HasVersion => logger -> BlockEnv -- ^ reference time for tx validation. @@ -432,7 +436,6 @@ validateParsedChainwebTx _logger blockEnv tx db = _psBlockDbEnv blockEnv blockCtx = _psBlockCtx blockEnv cid = blockCtx ^. chainId - v = blockCtx ^. chainwebVersion bh = _bctxCurrentBlockHeight blockCtx txValidationTime = _bctxParentCreationTime blockCtx @@ -454,7 +457,7 @@ validateParsedChainwebTx _logger blockEnv tx checkTimes :: Pact.Transaction -> ExceptT InsertError IO () checkTimes t = do - if | skipTxTimingValidation v cid bh -> pure () + if | skipTxTimingValidation cid bh -> pure () | not (Pact.assertTxNotInFuture txValidationTime (view Pact.payloadObj <$> t)) -> do throwError InsertErrorTimeInFuture | not (Pact.assertTxTimeRelativeToParent txValidationTime (view Pact.payloadObj <$> t)) -> do @@ -466,7 +469,7 @@ validateParsedChainwebTx _logger blockEnv tx checkTxHash t = do case Pact.verifyHash (Pact._cmdHash t) (SB.fromShort $ view Pact.payloadBytes $ Pact._cmdPayload t) of Left _ - | doCheckTxHash v cid bh -> throwError InsertErrorInvalidHash + | doCheckTxHash cid bh -> throwError InsertErrorInvalidHash | otherwise -> pure () Right _ -> pure () @@ -503,6 +506,7 @@ pact5TransactionsFromPayload plData = do execExistingBlock :: (CanReadablePayloadCas tbl, Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> BlockEnv @@ -513,7 +517,6 @@ execExistingBlock logger serviceEnv blockEnv payload = do let plData = checkablePayloadToPayloadData payload miner :: Miner <- decodeStrictOrThrow (_minerData $ view payloadDataMiner plData) txs <- lift $ pact5TransactionsFromPayload plData - let v = view chainwebVersion serviceEnv let errors <- liftIO $ flip foldMap txs $ \tx -> do errorOrSuccess <- runExceptT $ @@ -530,7 +533,7 @@ execExistingBlock logger serviceEnv blockEnv payload = do $ runCoinbase logger blockEnv miner let blockGasLimit = - Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit v (_bctxCurrentBlockHeight blockCtx) + Pact.GasLimit . Pact.Gas . fromIntegral <$> maxBlockGasLimit (_bctxCurrentBlockHeight blockCtx) (V.fromList -> results, _finalBlockGasLimit) <- flip weaveStatesFst blockGasLimit $ -- flip runStateT (postCoinbaseBlockHandle, blockGasLimit) $ diff --git a/src/Chainweb/Pact/RestAPI.hs b/src/Chainweb/Pact/RestAPI.hs index 8737c3a00d..dc183697ac 100644 --- a/src/Chainweb/Pact/RestAPI.hs +++ b/src/Chainweb/Pact/RestAPI.hs @@ -214,11 +214,11 @@ pactServiceApi = Proxy -- -------------------------------------------------------------------------- -- -- Some Cut Api -somePactServiceApi :: ChainwebVersion -> ChainId -> SomeApi +somePactServiceApi :: HasVersion => ChainId -> SomeApi somePactServiceApi - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) - = SomeApi $ pactServiceApi @v @c + | (SomeChainwebVersionT (_ :: Proxy v)) <- someChainwebVersionVal + = SomeApi $ pactServiceApi @v @c -somePactServiceApis :: ChainwebVersion -> [ChainId] -> SomeApi -somePactServiceApis v = mconcat . fmap (somePactServiceApi v) +somePactServiceApis :: HasVersion => [ChainId] -> SomeApi +somePactServiceApis = mconcat . fmap somePactServiceApi diff --git a/src/Chainweb/Pact/RestAPI/Client.hs b/src/Chainweb/Pact/RestAPI/Client.hs index ae5eb9156e..ec90bb1fb3 100644 --- a/src/Chainweb/Pact/RestAPI/Client.hs +++ b/src/Chainweb/Pact/RestAPI/Client.hs @@ -46,6 +46,7 @@ import Chainweb.Pact.Types import Chainweb.SPV.PayloadProof import Chainweb.Version import qualified Pact.Core.Command.Server as Pact +import Data.Proxy (Proxy) -- -------------------------------------------------------------------------- -- -- Pact Spv Transaction Output Proof Client @@ -63,8 +64,8 @@ pactSpvApiClient_ pactSpvApiClient_ = client (pactSpvApi @v @c) pactSpvApiClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -- ^ the chain id of the source chain id used in the -- execution of a cross-chain-transfer. -> SpvRequest @@ -73,10 +74,9 @@ pactSpvApiClient -- Also contains the request key of of the cross-chain transfer -- tx request. -> ClientM TransactionOutputProofB64 -pactSpvApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = pactSpvApiClient_ @v @c +pactSpvApiClient (FromSingChainId (SChainId :: Sing c)) r = do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + pactSpvApiClient_ @v @c r -- -------------------------------------------------------------------------- -- -- Pact ETH Spv Transaction Output Proof Client @@ -90,16 +90,15 @@ ethSpvApiClient_ ethSpvApiClient_ = client (ethSpvApi @v @c) ethSpvApiClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -- ^ chain to which the request is submitted. The resuting proof does -- not depend on the chain and can be validated on any chain. -> EthSpvRequest -> ClientM EthSpvResponse -ethSpvApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = ethSpvApiClient_ @v @c +ethSpvApiClient (FromSingChainId (SChainId :: Sing c)) = do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + ethSpvApiClient_ @v @c -- -------------------------------------------------------------------------- -- -- Pact ETH Spv Transaction Output Proof Client @@ -117,15 +116,14 @@ pactSpv2ApiClient_ pactSpv2ApiClient_ = client (pactSpv2Api @v @c) pactSpv2ApiClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -- ^ the chain id of the target chain of the proof. -> Spv2Request -> ClientM SomePayloadProof -pactSpv2ApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = pactSpv2ApiClient_ @v @c +pactSpv2ApiClient (FromSingChainId (SChainId :: Sing c)) = do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + pactSpv2ApiClient_ @v @c -- -------------------------------------------------------------------------- -- -- Pact local @@ -142,17 +140,16 @@ pactLocalApiClient_ pactLocalApiClient_ = client (pactLocalApi @v @c) pactLocalApiClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe LocalPreflightSimulation -> Maybe LocalSignatureVerification -> Maybe RewindDepth -> Command T.Text -> ClientM LocalResult -pactLocalApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = pactLocalApiClient_ @v @c +pactLocalApiClient (FromSingChainId (SChainId :: Sing c)) = do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + pactLocalApiClient_ @v @c -- -------------------------------------------------------------------------- -- -- Pact Listen @@ -166,14 +163,13 @@ pactListenApiClient_ pactListenApiClient_ = client (pactListenApi @v @c) pactListenApiClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Pact.ListenRequest -> ClientM Pact.ListenResponse -pactListenApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = pactListenApiClient_ @v @c +pactListenApiClient (FromSingChainId (SChainId :: Sing c)) = do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + pactListenApiClient_ @v @c -- -------------------------------------------------------------------------- -- -- Pact Send @@ -187,14 +183,13 @@ pactSendApiClient_ pactSendApiClient_ = client (pactSendApi @v @c) pactSendApiClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Pact.SendRequest -> ClientM Pact.SendResponse -pactSendApiClient - (FromSingChainwebVersion (SChainwebVersion :: Sing v)) - (FromSingChainId (SChainId :: Sing c)) - = pactSendApiClient_ @v @c +pactSendApiClient (FromSingChainId (SChainId :: Sing c)) = do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + pactSendApiClient_ @v @c -- -------------------------------------------------------------------------- -- -- Pact Poll @@ -209,10 +204,11 @@ pactPollApiClient_ pactPollApiClient_ = client (pactPollApi @v @c) pactPollApiClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe ConfirmationDepth -> Pact.PollRequest -> ClientM Pact.PollResponse -pactPollApiClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) (FromSingChainId (SChainId :: Sing c)) confirmationDepth poll = do - pactPollApiClient_ @v @c confirmationDepth poll +pactPollApiClient (FromSingChainId (SChainId :: Sing c)) = do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + pactPollApiClient_ @v @c diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index b506fd7444..19b6eab350 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -164,12 +164,12 @@ data SomePactServerData = forall v c logger tbl somePactServerData :: CanReadablePayloadCas tbl => Logger logger - => ChainwebVersion - -> ChainId + => HasVersion + => ChainId -> PactServerData logger tbl -> SomePactServerData -somePactServerData v cid db = - case someChainwebVersionVal v of +somePactServerData cid db = + case someChainwebVersionVal of (SomeChainwebVersionT (Proxy :: Proxy vt)) -> case someChainIdVal cid of (SomeChainIdT (Proxy :: Proxy cidt)) -> @@ -181,6 +181,7 @@ pactServer => KnownChainIdSymbol c => CanReadablePayloadCas tbl => Logger logger + => HasVersion => PactServerData logger tbl -> Server (PactServiceApi v c) pactServer d = @@ -202,18 +203,18 @@ pactServer d = -- pactSpvHandler = spvHandler logger cdb pdb pact cid -- pactSpv2Handler = spv2Handler logger cdb pdb pact cid -somePactServer :: SomePactServerData -> SomeServer +somePactServer :: HasVersion => SomePactServerData -> SomeServer somePactServer (SomePactServerData (db :: PactServerData_ v c logger tbl)) = SomeServer (Proxy @(PactServiceApi v c)) (pactServer @v @c $ _unPactServerData db) somePactServers :: CanReadablePayloadCas tbl => Logger logger - => ChainwebVersion - -> [(ChainId, PactServerData logger tbl)] + => HasVersion + => [(ChainId, PactServerData logger tbl)] -> SomeServer -somePactServers v = - mconcat . fmap (somePactServer . uncurry (somePactServerData v)) +somePactServers = + mconcat . fmap (somePactServer . uncurry somePactServerData) data PactCmdLog = PactCmdLogSend (NonEmpty (Pact.Command Text)) @@ -311,6 +312,7 @@ sendHandler logger mempool (Pact.SendRequest (Pact.SubmitBatch cmds)) = Handler -- TODO: convert to Pact 5? pollHandler :: (HasCallStack, CanReadablePayloadCas tbl, Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> MempoolBackend Pact.Transaction @@ -329,6 +331,7 @@ pollHandler logger pact mem confDepth (Pact.PollRequest request) = do -- TODO: convert to Pact 5? listenHandler :: (CanReadablePayloadCas tbl, Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> MempoolBackend Pact.Transaction @@ -365,6 +368,7 @@ listenHandler logger pact mem (Pact.ListenRequest key) = do -- TODO: convert to Pact 5? localHandler :: Logger logger + => HasVersion => CanReadablePayloadCas tbl => logger -> ServiceEnv tbl @@ -592,6 +596,7 @@ localHandler logger pact preflight sigVerify rewindDepth cmd = do internalPoll :: (CanReadablePayloadCas tbl, Logger logger) + => HasVersion => logger -> MempoolBackend Pact.Transaction -> ServiceEnv tbl @@ -695,8 +700,8 @@ barf e = maybe (throwError e) return -- TODO: all of the functions in this module can instead grab the current block height from consensus -- and pass it here to get a better estimate of what behavior is correct. -validateCommand :: ChainwebVersion -> Pact.Command Text -> Either String Pact.Transaction -validateCommand _v cmdText = case parsedCmd of +validateCommand :: HasVersion => Pact.Command Text -> Either String Pact.Transaction +validateCommand cmdText = case parsedCmd of Right (commandParsed :: Pact.Transaction) -> if isRight (Pact.assertCommand commandParsed) then Right commandParsed diff --git a/src/Chainweb/Pact/TransactionExec.hs b/src/Chainweb/Pact/TransactionExec.hs index 561c23146f..339f50cb1c 100644 --- a/src/Chainweb/Pact/TransactionExec.hs +++ b/src/Chainweb/Pact/TransactionExec.hs @@ -169,18 +169,17 @@ chargeGas info gasArgs = do -- run verifiers -- nasty... perhaps later convert verifier plugins to use GasM instead of tracking "gas remaining" -- TODO: Verifiers are also tied to Pact enough that this is going to be an annoying migration -runVerifiers :: Logger logger => BlockCtx -> Command (Payload PublicMeta ParsedCode) -> TransactionM logger () +runVerifiers :: (Logger logger, HasVersion) => BlockCtx -> Command (Payload PublicMeta ParsedCode) -> TransactionM logger () runVerifiers txCtx cmd = do logger <- view txEnvLogger - let v = _chainwebVersion txCtx let gasLimit = cmd ^. cmdPayload . pMeta . pmGasLimit gasUsed <- liftIO . readIORef . _geGasRef . _txEnvGasEnv =<< ask let initGasRemaining = MilliGas $ case (gasToMilliGas (gasLimit ^. _GasLimit), gasUsed) of (MilliGas gasLimitMilliGasWord, MilliGas gasUsedMilliGasWord) -> gasLimitMilliGasWord - gasUsedMilliGasWord - let allVerifiers = verifiersAt v (_chainId txCtx) (_bctxCurrentBlockHeight txCtx) + let allVerifiers = verifiersAt (_chainId txCtx) (_bctxCurrentBlockHeight txCtx) let txVerifiers = fromMaybe [] $ cmd ^. cmdPayload . pVerifiers verifierResult <- liftIO $ runVerifierPlugins - (_chainwebVersion txCtx, _chainId txCtx, _bctxCurrentBlockHeight txCtx) logger + (_chainId txCtx, _bctxCurrentBlockHeight txCtx) logger allVerifiers (milliGasToGas $ initGasRemaining) txVerifiers @@ -205,7 +204,7 @@ runVerifiers txCtx cmd = do chargeGas noInfo $ GAConstant $ MilliGas $ coerce initGasRemaining - coerce (gasToMilliGas (Gas verifierGasRemaining)) applyLocal - :: (Logger logger) + :: (Logger logger, HasVersion) => logger -- ^ Pact logger -> Maybe logger @@ -281,6 +280,7 @@ applyLocal logger maybeGasLogger coreDb txCtx spvSupport cmd = do -- applyCmd :: forall logger. (Logger logger) + => HasVersion => logger -- ^ Pact logger -> Maybe GasLogger @@ -422,6 +422,7 @@ ctxToPublicData pm ctx = PublicData -- a transaction which pays miners their block reward. applyCoinbase :: (Logger logger) + => HasVersion => logger -- ^ Pact logger -> PactDb CoreBuiltin Info @@ -486,16 +487,16 @@ applyCoinbase logger db (Miner mid mks) txCtx = do -- applyUpgrades :: (Logger logger) + => HasVersion => logger -> PactDb CoreBuiltin Info -> BlockCtx -> IO () applyUpgrades logger db txCtx | Just PactUpgrade{_pactUpgradeTransactions = upgradeTxs} <- - v ^? versionUpgrades . atChain cid . ix currentHeight = applyUpgrade upgradeTxs + implicitVersion ^? versionUpgrades . atChain cid . ix currentHeight = applyUpgrade upgradeTxs | otherwise = return () where - v = _chainwebVersion txCtx currentHeight = _bctxCurrentBlockHeight txCtx cid = _chainId txCtx applyUpgrade :: [Transaction] -> IO () @@ -514,6 +515,7 @@ applyUpgrades logger db txCtx -- * Any failures are fatal to PactService runGenesisPayload :: Logger logger + => HasVersion => logger -> PactDb CoreBuiltin Info -> SPVSupport @@ -563,6 +565,7 @@ runGenesisPayload logger db spv ctx cmd = do runPayload :: (Logger logger) + => HasVersion => ExecutionMode -> Set ExecutionFlag -> PactDb CoreBuiltin Info @@ -627,12 +630,12 @@ runPayload execMode execFlags db spv specialCaps namespacePolicy gasEnv txCtx tx verifiers = payload ^. pVerifiers . _Just signers = payload ^. pSigners publicMeta = cmd ^. cmdPayload . pMeta - v = _chainwebVersion txCtx cid = _chainId txCtx - maybeQuirkGasFee = v ^? versionQuirks . quirkGasFees . ixg cid . ix (_bctxCurrentBlockHeight txCtx, txIdxInBlock) + maybeQuirkGasFee = implicitVersion ^? versionQuirks . quirkGasFees . ixg cid . ix (_bctxCurrentBlockHeight txCtx, txIdxInBlock) runUpgrade :: (Logger logger) + => HasVersion => logger -> PactDb CoreBuiltin Info -> BlockCtx @@ -697,6 +700,7 @@ enrichedMsgBodyForGasPayer dat cmd = case (_pPayload $ _cmdPayload cmd) of -- buyGas :: (Logger logger) + => HasVersion => logger -> GasEnv CoreBuiltin Info -> PactDb CoreBuiltin Info @@ -804,6 +808,7 @@ buyGas logger origGasEnv db (Miner mid mks) txCtx cmd = do -- command results (see 'TransactionExec.applyCmd') -- redeemGas :: (Logger logger) + => HasVersion => logger -> PactDb CoreBuiltin Info -> Miner @@ -951,13 +956,13 @@ dumpGasLogs ctx txHash maybeGasLogger gasEnv = do -- After every dump, we clear the gas logs, so that each context only writes the gas logs it induced. writeIORef gasLogRef mempty -guardDisablePact51Flags :: BlockCtx -> Set ExecutionFlag +guardDisablePact51Flags :: HasVersion => BlockCtx -> Set ExecutionFlag guardDisablePact51Flags txCtx | guardCtx chainweb228Pact txCtx = Set.empty | otherwise = Set.singleton FlagDisablePact51 -- TODO: PP, make sure this is right -guardDisablePact52Flags :: BlockCtx -> Set ExecutionFlag +guardDisablePact52Flags :: HasVersion => BlockCtx -> Set ExecutionFlag guardDisablePact52Flags txCtx | guardCtx chainweb229Pact txCtx = Set.empty | otherwise = Set.singleton FlagDisablePact52 diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 7efb426633..8bb50d4673 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -22,7 +22,6 @@ module Chainweb.Pact.Types ( ServiceEnv(..) - , psVersion , psChainId , psGasLogger , psReadWriteSql @@ -355,7 +354,6 @@ data BlockCtx = BlockCtx , _bctxParentHash :: !(Parent BlockHash) , _bctxParentHeight :: !(Parent BlockHeight) , _bctxChainId :: !ChainId - , _bctxChainwebVersion :: !ChainwebVersion , _bctxMinerReward :: !MinerReward } deriving stock (Eq, Generic, Show) @@ -365,17 +363,15 @@ instance ToJSON BlockCtx where , "parentHash" A..= _bctxParentHash , "parentHeight" A..= _bctxParentHeight , "chainId" A..= _bctxChainId - , "chainwebVersion" A..= _versionName _bctxChainwebVersion , "minerReward" A..= _bctxMinerReward ] -blockCtxOfEvaluationCtx :: ChainwebVersion -> ChainId -> EvaluationCtx p -> BlockCtx -blockCtxOfEvaluationCtx v cid ec = BlockCtx +blockCtxOfEvaluationCtx :: ChainId -> EvaluationCtx p -> BlockCtx +blockCtxOfEvaluationCtx cid ec = BlockCtx { _bctxParentCreationTime = _evaluationCtxParentCreationTime ec , _bctxParentHash = _evaluationCtxParentHash ec , _bctxParentHeight = _evaluationCtxParentHeight ec , _bctxChainId = cid - , _bctxChainwebVersion = v , _bctxMinerReward = _evaluationCtxMinerReward ec } @@ -388,16 +384,14 @@ evaluationCtxOfBlockCtx bctx = EvaluationCtx , _evaluationCtxPayload = () } -guardCtx :: (ChainwebVersion -> ChainId -> BlockHeight -> a) -> BlockCtx -> a -guardCtx g txCtx = g (_chainwebVersion txCtx) (_chainId txCtx) (_bctxCurrentBlockHeight txCtx) +guardCtx :: HasVersion => (ChainId -> BlockHeight -> a) -> BlockCtx -> a +guardCtx g txCtx = g (_chainId txCtx) (_bctxCurrentBlockHeight txCtx) instance HasChainId BlockCtx where _chainId = _bctxChainId -instance HasChainwebVersion BlockCtx where - _chainwebVersion = _bctxChainwebVersion -_bctxIsGenesis :: BlockCtx -> Bool -_bctxIsGenesis bc = isGenesisBlockHeader' (_chainwebVersion bc) (_chainId bc) (_bctxParentHash bc) +_bctxIsGenesis :: HasVersion => BlockCtx -> Bool +_bctxIsGenesis bc = isGenesisBlockHeader' (_chainId bc) (_bctxParentHash bc) _bctxParentRankedBlockHash :: BlockCtx -> Parent RankedBlockHash _bctxParentRankedBlockHash bc = Parent RankedBlockHash @@ -405,9 +399,9 @@ _bctxParentRankedBlockHash bc = Parent RankedBlockHash , _rankedBlockHashHeight = unwrapParent $ _bctxParentHeight bc } -_bctxCurrentBlockHeight :: BlockCtx -> BlockHeight +_bctxCurrentBlockHeight :: HasVersion => BlockCtx -> BlockHeight _bctxCurrentBlockHeight bc = - childBlockHeight (_chainwebVersion bc) (_chainId bc) (_bctxParentRankedBlockHash bc) + childBlockHeight (_chainId bc) (_bctxParentRankedBlockHash bc) -- | Externally-injected PactService properties. @@ -501,8 +495,7 @@ instance Monoid MemPoolAccess where type GasLogger = Pact.RequestKey -> [Pact.GasLogEntry Pact.CoreBuiltin Pact.Info] -> IO () data ServiceEnv tbl = ServiceEnv - { _psVersion :: ChainwebVersion - , _psChainId :: ChainId + { _psChainId :: ChainId , _psGasLogger :: Maybe GasLogger -- ^ Used to emit gas logs of Pact code; gas logs are disabled if this is set to `Nothing`. @@ -543,10 +536,6 @@ data ServiceEnv tbl = ServiceEnv , _psBlockRefreshInterval :: Micros } -instance HasChainwebVersion (ServiceEnv tbl) where - _chainwebVersion = _psVersion - {-# INLINE _chainwebVersion #-} - instance HasChainId (ServiceEnv tbl) where _chainId = _psChainId {-# INLINE _chainId #-} @@ -556,28 +545,25 @@ data BlockEnv = BlockEnv , _psBlockDbEnv :: !ChainwebPactDb } -instance HasChainwebVersion BlockEnv where - _chainwebVersion = _chainwebVersion . _psBlockCtx instance HasChainId BlockEnv where _chainId = _chainId . _psBlockCtx -- the evaluation context for the genesis block; note that the payload is filled in -genesisEvaluationCtx :: ServiceEnv tbl -> EvaluationCtx ConsensusPayload +genesisEvaluationCtx :: HasVersion => ServiceEnv tbl -> EvaluationCtx ConsensusPayload genesisEvaluationCtx serviceEnv = EvaluationCtx - { _evaluationCtxParentCreationTime = Parent $ v ^?! versionGenesis . genesisTime . atChain cid - , _evaluationCtxParentHash = genesisParentBlockHash v cid - , _evaluationCtxParentHeight = Parent $ genesisHeight v cid + { _evaluationCtxParentCreationTime = Parent $ implicitVersion ^?! versionGenesis . genesisTime . atChain cid + , _evaluationCtxParentHash = genesisParentBlockHash cid + , _evaluationCtxParentHeight = Parent $ genesisHeight cid -- should not be used , _evaluationCtxMinerReward = MinerReward 0 , _evaluationCtxPayload = ConsensusPayload - { _consensusPayloadHash = genesisBlockPayloadHash v cid + { _consensusPayloadHash = genesisBlockPayloadHash cid , _consensusPayloadData = EncodedPayloadData . Chainweb.encodePayloadData . Chainweb.payloadWithOutputsToPayloadData <$> _psGenesisPayload serviceEnv } } where - v = _chainwebVersion serviceEnv cid = _chainId serviceEnv -- State from a block in progress, which is used to extend blocks after @@ -602,10 +588,6 @@ instance Eq BlockInProgress where _blockInProgressRemainingGasLimit bip == _blockInProgressRemainingGasLimit bip' && _blockInProgressTransactions bip == _blockInProgressTransactions bip' -instance HasChainwebVersion BlockInProgress where - _chainwebVersion = _chainwebVersion . _blockInProgressBlockCtx - {-# INLINE _chainwebVersion #-} - instance HasChainId BlockInProgress where _chainId = _chainId . _blockInProgressBlockCtx {-# INLINE _chainId #-} diff --git a/src/Chainweb/Pact/Validations.hs b/src/Chainweb/Pact/Validations.hs index c9cfd7db14..c98e81e68e 100644 --- a/src/Chainweb/Pact/Validations.hs +++ b/src/Chainweb/Pact/Validations.hs @@ -66,16 +66,16 @@ import Numeric.Natural -- | Check whether a local Api request has valid metadata -- assertPreflightMetadata - :: ServiceEnv tbl + :: HasVersion + => ServiceEnv tbl -> Pact.Command (Pact.Payload Pact.PublicMeta c) -> BlockCtx -> Maybe LocalSignatureVerification -> Either (NonEmpty Text) () assertPreflightMetadata env cmd@(Pact.Command pay sigs hsh) blockCtx sigVerify = do - let v = view chainwebVersion env let cid = view chainId env -- TODO PP: fix this in master too; master uses the wrong block gas limit. - let mbgl = maxBlockGasLimit v (_bctxCurrentBlockHeight blockCtx) + let mbgl = maxBlockGasLimit (_bctxCurrentBlockHeight blockCtx) let Pact.PublicMeta pcid _ gl gp _ _ = Pact._pMeta pay nid = Pact._pNetworkId pay @@ -88,7 +88,7 @@ assertPreflightMetadata env cmd@(Pact.Command pay sigs hsh) blockCtx sigVerify = eUnless "Transaction Gas limit exceeds block gas limit" $ assertBlockGasLimit (Pact.GasLimit $ Pact.Gas $ int @Natural @Pact.SatWord bgl) gl , eUnless "Gas price decimal precision too high" $ assertGasPrice gp - , eUnless "Network id mismatch" $ assertNetworkId v nid + , eUnless "Network id mismatch" $ assertNetworkId nid , eUnless "Signature list size too big" $ assertSigSize sigs , eUnless "Invalid transaction signatures" $ sigValidate signers , eUnless "Tx time outside of valid range" $ assertTxTimeRelativeToParent pct cmd @@ -131,9 +131,9 @@ assertBlockGasLimit bgl tgl = bgl >= tgl -- | Check and assert that 'ChainwebVersion' is equal to some pact 'NetworkId'. -- -assertNetworkId :: ChainwebVersion -> Maybe Pact.NetworkId -> Bool -assertNetworkId _ Nothing = False -assertNetworkId v (Just (Pact.NetworkId nid)) = ChainwebVersionName nid == _versionName v +assertNetworkId :: HasVersion => Maybe Pact.NetworkId -> Bool +assertNetworkId Nothing = False +assertNetworkId (Just (Pact.NetworkId nid)) = ChainwebVersionName nid == _versionName implicitVersion -- | Check and assert that the number of signatures in a 'Command' is -- at most 100. diff --git a/src/Chainweb/Parent.hs b/src/Chainweb/Parent.hs index 1365ef2624..351d668f8f 100644 --- a/src/Chainweb/Parent.hs +++ b/src/Chainweb/Parent.hs @@ -31,8 +31,6 @@ instance Applicative Parent where instance HasChainId h => HasChainId (Parent h) where _chainId = _chainId . unwrapParent -instance HasChainwebVersion h => HasChainwebVersion (Parent h) where - _chainwebVersion = _chainwebVersion . unwrapParent instance HasChainGraph h => HasChainGraph (Parent h) where _chainGraph = _chainGraph . unwrapParent diff --git a/src/Chainweb/Payload/PayloadStore.hs b/src/Chainweb/Payload/PayloadStore.hs index 4f782ef758..0272b63f19 100644 --- a/src/Chainweb/Payload/PayloadStore.hs +++ b/src/Chainweb/Payload/PayloadStore.hs @@ -64,9 +64,6 @@ module Chainweb.Payload.PayloadStore , lookupPayloadDataWithHeight , lookupPayloadDataWithHeightBatch --- ** Initialize Payload Database with Genesis Payloads --- , initializePayloadDb - -- ** insert new payload , addPayload , addNewPayload @@ -333,29 +330,6 @@ lookupPayloadWithHeightBatch db = traverse (uncurry $ lookupPayloadWithHeight db instance (pk ~ CasKeyType (PayloadData_ a), CanReadableTransactionDbCas_ a tbl) => ReadableTable (TransactionDb_ a tbl) pk (PayloadData_ a) where tableLookup db = lookupPayloadDataWithHeight' db Nothing --- -------------------------------------------------------------------------- -- --- Initialize a PayloadDb with Genesis Payloads - --- | Initialize a PayloadDb with genesis payloads for the given chainweb --- version. --- --- initializePayloadDb --- :: CanPayloadCas tbl --- => ChainwebVersion --- -> --- -> PayloadDb tbl --- -> IO () --- initializePayloadDb v db = traverse_ initForChain $ chainIds v --- where --- initForChain cid --- | provider /= PactProvider = --- error "Chainweb.Payload.PayloadStore.initializePayloadDb: this module must only be used by Pact" --- | otherwise = --- addNewPayload db (genesisBlockHeight v cid) $ genesisPayload v ^?! atChain cid --- where --- provider = payloadProviderTypeForChain v cid - - -- -------------------------------------------------------------------------- -- -- Insert new Payload diff --git a/src/Chainweb/Payload/RestAPI.hs b/src/Chainweb/Payload/RestAPI.hs index 977d9957fb..63715d91b8 100644 --- a/src/Chainweb/Payload/RestAPI.hs +++ b/src/Chainweb/Payload/RestAPI.hs @@ -101,9 +101,12 @@ data SomePayloadDb tbl = forall v c . (KnownChainwebVersionSymbol v, KnownChainIdSymbol c) => SomePayloadDb (PayloadDb' tbl v c) -somePayloadDbVal :: forall tbl . ChainwebVersion -> ChainId -> PayloadDb tbl -> SomePayloadDb tbl -somePayloadDbVal v cid db = runIdentity $ do - SomeChainwebVersionT (Proxy :: Proxy vt) <- return $ someChainwebVersionVal v +somePayloadDbVal + :: forall tbl + . HasVersion + => ChainId -> PayloadDb tbl -> SomePayloadDb tbl +somePayloadDbVal cid db = runIdentity $ do + SomeChainwebVersionT (Proxy :: Proxy vt) <- return $ someChainwebVersionVal SomeChainIdT (Proxy :: Proxy cidt) <- return $ someChainIdVal cid return $! SomePayloadDb (PayloadDb' @tbl @vt @cidt db) @@ -233,11 +236,11 @@ payloadApi = Proxy -- -------------------------------------------------------------------------- -- -- Some Payload API -somePayloadApi :: ChainwebVersion -> ChainId -> SomeApi -somePayloadApi v c = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v') <- return $ someChainwebVersionVal v +somePayloadApi :: HasVersion => ChainId -> SomeApi +somePayloadApi c = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v') <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c') <- return $ someChainIdVal c return $! SomeApi (payloadApi @v' @c') -somePayloadApis :: ChainwebVersion -> [ChainId] -> SomeApi -somePayloadApis v = mconcat . fmap (somePayloadApi v) +somePayloadApis :: HasVersion => [ChainId] -> SomeApi +somePayloadApis = mconcat . fmap somePayloadApi diff --git a/src/Chainweb/Payload/RestAPI/Client.hs b/src/Chainweb/Payload/RestAPI/Client.hs index 0ba7a9cdd5..98b922cd1b 100644 --- a/src/Chainweb/Payload/RestAPI/Client.hs +++ b/src/Chainweb/Payload/RestAPI/Client.hs @@ -49,13 +49,13 @@ payloadClient_ payloadClient_ = client (payloadGetApi @v @c) payloadClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BlockPayloadHash -> Maybe BlockHeight -> ClientM PayloadData -payloadClient v c k h = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +payloadClient c k h = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ payloadClient_ @v @c k h @@ -74,12 +74,12 @@ payloadBatchClient_ = client (payloadPostApi @v @c) -- data. Results are returned in any order. -- payloadBatchClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BatchBody -> ClientM PayloadDataList -payloadBatchClient v c k = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +payloadBatchClient c k = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ payloadBatchClient_ @v @c k @@ -96,13 +96,13 @@ outputsClient_ outputsClient_ = client (outputsGetApi @v @c) outputsClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BlockPayloadHash -> Maybe BlockHeight -> ClientM PayloadWithOutputs -outputsClient v c k h = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +outputsClient c k h = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ outputsClient_ @v @c k h @@ -118,11 +118,11 @@ outputsBatchClient_ outputsBatchClient_ = client (outputsPostApi @v @c) outputsBatchClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BatchBody -> ClientM PayloadWithOutputsList -outputsBatchClient v c k = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +outputsBatchClient c k = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ outputsBatchClient_ @v @c k diff --git a/src/Chainweb/Payload/RestAPI/Server.hs b/src/Chainweb/Payload/RestAPI/Server.hs index fff98a0182..48a36e73e9 100644 --- a/src/Chainweb/Payload/RestAPI/Server.hs +++ b/src/Chainweb/Payload/RestAPI/Server.hs @@ -173,6 +173,7 @@ payloadApiLayout _ = T.putStrLn $ layout (Proxy @(PayloadApi v c)) somePayloadServer :: CanReadablePayloadCas tbl + => HasVersion => PayloadBatchLimit -> SomePayloadDb tbl -> SomeServer @@ -181,9 +182,9 @@ somePayloadServer batchLimit (SomePayloadDb (db :: PayloadDb' tbl v c)) somePayloadServers :: CanReadablePayloadCas tbl - => ChainwebVersion - -> PayloadBatchLimit + => HasVersion + => PayloadBatchLimit -> [(ChainId, PayloadDb tbl)] -> SomeServer -somePayloadServers v batchLimit - = mconcat . fmap (somePayloadServer batchLimit . uncurry (somePayloadDbVal v)) +somePayloadServers batchLimit + = mconcat . fmap (somePayloadServer batchLimit . uncurry somePayloadDbVal) diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 5206b50014..b8dde221c1 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -209,24 +209,22 @@ data ConsensusState = ConsensusState deriving (Show, Eq, Ord) genesisSyncState - :: HasChainwebVersion v + :: HasVersion => HasChainId c - => v - -> c + => c -> SyncState -genesisSyncState v c = - syncStateOfBlockHeader (genesisBlockHeader (_chainwebVersion v) c) +genesisSyncState c = + syncStateOfBlockHeader (genesisBlockHeader c) genesisConsensusState - :: HasChainwebVersion v + :: HasVersion => HasChainId c - => v - -> c + => c -> ConsensusState -genesisConsensusState v c = ConsensusState - { _consensusStateLatest = genesisSyncState v c - , _consensusStateSafe = genesisSyncState v c - , _consensusStateFinal = genesisSyncState v c +genesisConsensusState c = ConsensusState + { _consensusStateLatest = genesisSyncState c + , _consensusStateSafe = genesisSyncState c + , _consensusStateFinal = genesisSyncState c } latestRankedBlockPayloadHash :: ConsensusState -> RankedBlockPayloadHash @@ -380,19 +378,19 @@ data NewBlockCtx = NewBlockCtx -- | Get the evaluation context for given parent header and block payload hash -- blockHeaderToEvaluationCtx - :: Parent BlockHeader + :: HasVersion + => Parent BlockHeader -> EvaluationCtx () blockHeaderToEvaluationCtx (Parent ph) = EvaluationCtx { _evaluationCtxParentCreationTime = Parent $ view blockCreationTime ph , _evaluationCtxParentHash = Parent $ view blockHash ph , _evaluationCtxParentHeight = parentHeight - , _evaluationCtxMinerReward = blockMinerReward v height + , _evaluationCtxMinerReward = blockMinerReward height , _evaluationCtxPayload = () } where parentHeight = Parent $ view blockHeight ph height = unwrapParent parentHeight + 1 - v = _chainwebVersion ph newBlockCtxProperties :: forall e kv . KeyValue e kv => NewBlockCtx -> [kv] newBlockCtxProperties a = @@ -613,8 +611,7 @@ instance FromJSON EncodedPayloadOutputs where -- TODO: describe encoding -- data NewPayload = NewPayload - { _newPayloadChainwebVersion :: !ChainwebVersion - , _newPayloadChainId :: !ChainId + { _newPayloadChainId :: !ChainId , _newPayloadParentHeight :: !(Parent BlockHeight) , _newPayloadParentHash :: !(Parent BlockHash) , _newPayloadBlockPayloadHash :: !BlockPayloadHash @@ -653,7 +650,6 @@ instance Eq NewPayload where -- move entropy to the beginning of the comparision to fail fast -- (assuming that tuple starts at the front) ( _newPayloadBlockPayloadHash x - , _newPayloadChainwebVersion x , _newPayloadChainId x , _newPayloadNumber x , isJust (_newPayloadEncodedPayloadData x) @@ -667,8 +663,7 @@ instance Eq NewPayload where instance Ord NewPayload where compare = on compare $ \x -> - ( _newPayloadChainwebVersion x - , _newPayloadChainId x + ( _newPayloadChainId x , _newPayloadBlockPayloadHash x , _newPayloadNumber x , isJust (_newPayloadEncodedPayloadData x) @@ -687,18 +682,13 @@ instance Hashable NewPayload where hashWithSalt s = hashWithSalt s . _newPayloadBlockPayloadHash {-# INLINE hashWithSalt #-} -instance HasChainwebVersion NewPayload where - _chainwebVersion = _newPayloadChainwebVersion - {-# INLINE _chainwebVersion #-} - instance HasChainId NewPayload where _chainId = _newPayloadChainId {-# INLINE _chainId #-} newPayloadProperties :: forall e kv . KeyValue e kv => NewPayload -> [kv] newPayloadProperties a = - [ "chainwebVersion" .= _versionName (_newPayloadChainwebVersion a) - , "chainId" .= _newPayloadChainId a + [ "chainId" .= _newPayloadChainId a , "parentHeight" .= _newPayloadParentHeight a , "parentHash" .= _newPayloadParentHash a , "payloadHash" .= _newPayloadBlockPayloadHash a @@ -756,7 +746,7 @@ instance ToJSON NewPayload where -- payload itself in a single data structure. This API can accomodate this -- behavior. -- -class (HasChainwebVersion p, HasChainId p) => PayloadProvider p where +class (HasChainId p) => PayloadProvider p where -- | Returns the current sync state of the payload provider. -- Note that this may be ahead of that returned by `prefetchBlock`. @@ -771,7 +761,8 @@ class (HasChainwebVersion p, HasChainId p) => PayloadProvider p where -- TODO: is this allowed to fail? Does it return or is it fire and forget? -- prefetchPayloads - :: p + :: HasVersion + => p -> Maybe Hints -> ForkInfo -- ^ TODO: do we really want to pass the full ForkInfo here? What is @@ -803,7 +794,8 @@ class (HasChainwebVersion p, HasChainId p) => PayloadProvider p where -- and persistent. -- syncToBlock - :: p + :: HasVersion + => p -- ^ Payload provider handle -> Maybe Hints -- ^ hints for fetching missing payloads @@ -831,32 +823,32 @@ class (HasChainwebVersion p, HasChainId p) => PayloadProvider p where -- are not either integrated into the longest chain or definitely -- abandoned. Payload providers may also cache the validation result. -- - latestPayloadSTM :: p -> STM NewPayload + latestPayloadSTM :: HasVersion => p -> STM NewPayload -- If backed by an TVar, this can usually be implemented more efficiently -- using 'readTVarIO' -- - latestPayloadIO :: p -> IO NewPayload + latestPayloadIO :: HasVersion => p -> IO NewPayload latestPayloadIO = atomically . latestPayloadSTM -- FIXME FIXME FIXME - eventProof :: p -> XEventId -> IO SpvProof + eventProof :: HasVersion => p -> XEventId -> IO SpvProof -nextPayloadStm :: PayloadProvider p => p -> NewPayload -> STM NewPayload +nextPayloadStm :: (HasVersion, PayloadProvider p) => p -> NewPayload -> STM NewPayload nextPayloadStm p cur = do new <- latestPayloadSTM p when (new == cur) retry return new -nextPayload :: PayloadProvider p => p -> NewPayload -> IO NewPayload +nextPayload :: (HasVersion, PayloadProvider p) => p -> NewPayload -> IO NewPayload nextPayload p = atomically . nextPayloadStm p -waitForChangedPayload :: PayloadProvider p => p -> IO NewPayload +waitForChangedPayload :: (HasVersion, PayloadProvider p) => p -> IO NewPayload waitForChangedPayload p = do old <- latestPayloadIO p nextPayload p old -payloadStream :: PayloadProvider p => p -> S.Stream (S.Of NewPayload) IO () +payloadStream :: (HasVersion, PayloadProvider p) => p -> S.Stream (S.Of NewPayload) IO () payloadStream p = do cur <- liftIO $ latestPayloadIO p S.yield cur @@ -995,12 +987,11 @@ _finalHeight :: ConsensusState -> BlockHeight _finalHeight = _syncStateHeight . _consensusStateFinal genesisState - :: HasChainwebVersion v + :: HasVersion => HasChainId c - => v - -> c + => c -> ConsensusState -genesisState v c = ConsensusState +genesisState c = ConsensusState { _consensusStateLatest = s , _consensusStateSafe = s , _consensusStateFinal = s @@ -1011,4 +1002,4 @@ genesisState v c = ConsensusState , _syncStateBlockHash = view blockHash hdr , _syncStateBlockPayloadHash = view blockPayloadHash hdr } - hdr = genesisBlockHeader (_chainwebVersion v) c + hdr = genesisBlockHeader c diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 71b045ef12..bed60bb04c 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -127,10 +127,9 @@ initPayloadDb :: EvmDB.Configuration -> IO (EvmDB.HeaderDb_ a RocksDbTable) initPayloadDb = EvmDB.initHeaderDb payloadDbConfiguration - :: HasChainwebVersion v + :: HasVersion => HasChainId c - => v - -> c + => c -> RocksDb -> Payload -> EvmDB.Configuration @@ -183,8 +182,8 @@ defaultEvmProviderConfig = EvmProviderConfig , _evmConfMinerAddress = Nothing } -validateEvmProviderConfig :: ChainwebVersion -> ChainId -> ConfigValidation EvmProviderConfig [] -validateEvmProviderConfig _ cid conf = do +validateEvmProviderConfig :: HasVersion => ChainId -> ConfigValidation EvmProviderConfig [] +validateEvmProviderConfig cid conf = do when (euri == _evmConfEngineUri defaultEvmProviderConfig) $ tell [ "EVM Engine URI for " <> sshow cid <> " is to the default value: " <> sshow euri @@ -241,8 +240,7 @@ pEvmProviderConfig cid = id -- 3. EL time = CL parent time -- data EvmPayloadProvider logger = EvmPayloadProvider - { _evmChainwebVersion :: !ChainwebVersion - , _evmChainId :: !ChainId + { _evmChainId :: !ChainId , _evmLogger :: !logger , _evmPayloadStore :: !(PayloadStore (PayloadDb RocksDbTable) Payload) -- ^ The BlockPayloadHash in the ConsensusState is different from the @@ -330,9 +328,6 @@ evmPayloadDb = to (_payloadStoreTable . _evmPayloadStore) evmPayloadQueue :: Getter (EvmPayloadProvider l) (PQueue (Task ClientEnv Payload)) evmPayloadQueue = to (_payloadStoreQueue . _evmPayloadStore) -instance HasChainwebVersion (EvmPayloadProvider logger) where - _chainwebVersion = _evmChainwebVersion - instance HasChainId (EvmPayloadProvider logger) where _chainId = _evmChainId @@ -482,10 +477,9 @@ instance Exception InvalidPayloadException -- withEvmPayloadProvider :: Logger logger - => HasChainwebVersion v + => HasVersion => HasChainId c => logger - -> v -> c -> RocksDb -> Maybe HTTP.Manager @@ -495,27 +489,26 @@ withEvmPayloadProvider -- It is /not/ used for communication with the execution engine client. -> EvmProviderConfig -> ResourceT IO (EvmPayloadProvider logger) -withEvmPayloadProvider logger v c rdb mgr conf - | FromSing @_ @p (SEvmProvider ecid) <- payloadProviderTypeForChain v c = do +withEvmPayloadProvider logger c rdb mgr conf + | FromSing @_ @p (SEvmProvider ecid) <- payloadProviderTypeForChain c = do engineCtx <- liftIO $ mkEngineCtx (_evmConfEngineJwtSecret conf) (_engineUri $ _evmConfEngineUri conf) - SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal v + SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal SomeChainIdT @c _ <- return $ someChainIdVal c let pldCli h = Rest.payloadClient @v @c @p h - genPld <- liftIO $ checkExecutionClient logger v c engineCtx (EVM.ChainId (fromSNat ecid)) + genPld <- liftIO $ checkExecutionClient logger c engineCtx (EVM.ChainId (fromSNat ecid)) liftIO $ logFunctionText logger Info $ "genesis payload block hash: " <> sshow (EVM._hdrPayloadHash genPld) liftIO $ logFunctionText logger Debug $ "genesis payload from execution client: " <> sshow genPld - pdb <- liftIO $ initPayloadDb $ payloadDbConfiguration v c rdb genPld + pdb <- liftIO $ initPayloadDb $ payloadDbConfiguration c rdb genPld store <- liftIO $ newPayloadStore mgr (logFunction pldStoreLogger) pdb pldCli pldVar <- liftIO newEmptyTMVarIO pldIdVar <- liftIO newEmptyTMVarIO candidates <- liftIO $ emptyTable - stateVar <- liftIO $ newTVarIO (T2 (genesisState v c) Nothing) + stateVar <- liftIO $ newTVarIO (T2 (genesisState c) Nothing) lock <- liftIO $ newMVar () let p = EvmPayloadProvider - { _evmChainwebVersion = _chainwebVersion v - , _evmChainId = _chainId c + { _evmChainId = _chainId c , _evmLogger = logger , _evmState = stateVar , _evmPayloadStore = store @@ -539,7 +532,7 @@ withEvmPayloadProvider logger v c rdb mgr conf where pldStoreLogger = addLabel ("sub-component", "payloadStore") logger -payloadListener :: Logger logger => EvmPayloadProvider logger -> IO () +payloadListener :: (Logger logger, HasVersion) => EvmPayloadProvider logger -> IO () payloadListener p = case (_evmMinerAddress p) of Nothing -> do lf Info "New payload creation is disabled." @@ -559,17 +552,16 @@ payloadListener p = case (_evmMinerAddress p) of -- Returns the genesis header. -- checkExecutionClient - :: (HasChainwebVersion v) + :: HasVersion => Logger logger => HasChainId c => logger - -> v -> c -> JsonRpcHttpCtx -> EVM.ChainId -- ^ expected Ethereum Network ID -> IO Payload -checkExecutionClient logger v c ctx expectedEcid = do +checkExecutionClient logger c ctx expectedEcid = do ecid <- try @_ @SomeException (callMethodHttp @Eth_ChainId ctx Nothing) >>= \case Left err -> do logFunctionText logger Error @@ -588,7 +580,7 @@ checkExecutionClient logger v c ctx expectedEcid = do (Actual $ EVM._hdrPayloadHash h) return h where - expectedGenesisHeader = genesisBlockPayloadHash (_chainwebVersion v) (_chainId c) + expectedGenesisHeader = genesisBlockPayloadHash (_chainId c) -- -------------------------------------------------------------------------- -- -- Engine Calls @@ -845,7 +837,7 @@ newPayloadTimeout = 30_000_000 -- -- This is called only if payload creation is enabled in the configuration. -- -awaitNewPayload :: Logger logger => EvmPayloadProvider logger -> IO () +awaitNewPayload :: (Logger logger, HasVersion) => EvmPayloadProvider logger -> IO () awaitNewPayload p = do lf Debug "await new payload ID" awaitPid >>= \case @@ -877,7 +869,6 @@ awaitNewPayload p = do where lf = loggS p "awaitNewPayload" ctx = _evmEngineCtx p - v = _chainwebVersion p cid = _chainId p fees v1 = Stu $ bf * gu @@ -972,7 +963,6 @@ awaitNewPayload p = do , _newPayloadFees = fees v1 , _newPayloadEncodedPayloadOutputs = Nothing , _newPayloadEncodedPayloadData = Just (EncodedPayloadData $ putRlpByteString pld) - , _newPayloadChainwebVersion = v , _newPayloadChainId = cid } threadDelay newPayloadRate @@ -1075,6 +1065,7 @@ forkchoiceUpdatedTimeout = 3_000_000 -- evmSyncToBlock :: Logger logger + => HasVersion => EvmPayloadProvider logger -> Maybe Hints -> ForkInfo @@ -1195,10 +1186,10 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do -- ignore that case. We want a solution where we don't have permanent cost -- overhead for this exceptional scenario. -- -candidatePruningDepth :: EvmPayloadProvider logger -> BlockHeight -> BlockHeight -candidatePruningDepth p h = int $ diameter (chainGraphAt (_chainwebVersion p) h) +candidatePruningDepth :: HasVersion => EvmPayloadProvider logger -> BlockHeight -> BlockHeight +candidatePruningDepth p h = int $ diameter (chainGraphAt h) -pruneCandidates :: EvmPayloadProvider logger -> IO () +pruneCandidates :: HasVersion => EvmPayloadProvider logger -> IO () pruneCandidates p = do lrh <- latestRankedBlockPayloadHash . sfst <$> stateIO p let h = _rankedHeight lrh @@ -1319,13 +1310,14 @@ getLogEntry p e = do getSpvProof :: Logger logger + => HasVersion => EvmPayloadProvider logger -> XEventId -> IO SpvProof getSpvProof p e = do le <- getLogEntry p e lf Info $ "got logEntry: " <> encodeToText le - ld <- parseXLogData (_chainwebVersion p) e le + ld <- parseXLogData e le lf Info $ "got logData: " <> sshow ld return $ SpvProof $ object [ "origin" .= object diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 5aa6d085a8..bb2eacb283 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -1,5 +1,6 @@ {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE LambdaCase #-} -- | -- Module: Chainweb.PayloadProvider.EVM.Genesis @@ -11,11 +12,13 @@ module Chainweb.PayloadProvider.EVM.Genesis ( genesisBlocks ) where + import Chainweb.Version import Chainweb.PayloadProvider.EVM.Header import Chainweb.Version.EvmDevelopment import Chainweb.Utils import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) +import Data.Text qualified as T -- -------------------------------------------------------------------------- -- -- Genesis Blocks @@ -69,56 +72,57 @@ import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) -- @ -- genesisBlocks - :: HasChainwebVersion v + :: HasVersion => HasChainId c - => v - -> c + => c -> Header -genesisBlocks v c = go (_chainwebVersion v) (_chainId c) +genesisBlocks c = go (_versionCode implicitVersion) (_chainId c) where - go EvmDevelopment (ChainId 20) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGnV3kPaffv02pFz2XaK1abwEUqbp-L5K03hpucRJqlToFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 21) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJSFmb3gp1R0kVepD6Cb9wKqd4mvZhjGCfLWOJYhfOtXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 22) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIHnBqDOlLrBUgs1mmDAGcyoZGAiI4MDWIm5TSP2DfXHoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 23) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOG8PeZNPmM3lrR8_XYZiV77h9JISltItpJrMg3uWzISoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 24) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoL_zw0Kq1MfRVs38rgQLmxq1zFk8ac976rfmBmNiPvc0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 25) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAR82Mmf_nzgqOuk-JuK2c6y4cUsnjj_mthufymqwKl4oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 26) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoLIoqmCAur1Y7jpeSGyRpx71kbUqXT-R5dTjIswNvgyXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 27) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoEa3cdjo4w54WX1slUf3N6RgIFddjDWTfYNUg4mMF_ExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 28) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJGErv8Pw9R_vy2jSuU8h22F9iPtZMzBDYhF33EA0PGhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 29) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMh5sX_kYQkGl1nZ-RHbBlbBwbp-VcznAOB0nAVnXOzroFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 30) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGcOIoUzg5FTvy0MC0EZJsxo030YmtHVbISpQi0iaD3poFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 31) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNkhLvaaRHuHwXutFpApXpQMuKIUXeN0riiBvHCjpezvoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 32) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAZn-r-AVUqdftFL0aAYgnXg3Ihh3TpvNHF1Lk2f5dO7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 33) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoI5MZQFQY_vcdKf1l4cilyj0fjzDsAGNw5ZFew7mLNB7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 34) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFIGyZq62efyMMlN-YowvgItKWe3F4BY3CG2g_9q7jfRoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 35) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBV-hCyAvRGG3t7rNKZd1fKF1ZuQjEBKezJpX2508UDioFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 36) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFH9aUrJ9ByuptHRBLi9Qz5ZfxB9PHQo49SlwaBIgb6ZoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 37) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNMYg9Jdt7H6aQ8qvbWwbcqKrAYx8dFuK4ZQjkUPo-jNoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 38) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoArbi8-FGeh8uGxs7MxDXJUwVMznEnD_W-imVRojX9PnoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go EvmDevelopment (ChainId 39) = f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoPitbBNoKjvjSCImGCqCmwST-kMGThKeiK5WSDMw-CfqoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - go _ _ = error "requested genesis block for unsupported chain" + go v + | v == _versionCode EvmDevelopment = \case + ChainId 20 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGnV3kPaffv02pFz2XaK1abwEUqbp-L5K03hpucRJqlToFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 21 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJSFmb3gp1R0kVepD6Cb9wKqd4mvZhjGCfLWOJYhfOtXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 22 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIHnBqDOlLrBUgs1mmDAGcyoZGAiI4MDWIm5TSP2DfXHoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 23 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOG8PeZNPmM3lrR8_XYZiV77h9JISltItpJrMg3uWzISoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 24 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoL_zw0Kq1MfRVs38rgQLmxq1zFk8ac976rfmBmNiPvc0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 25 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAR82Mmf_nzgqOuk-JuK2c6y4cUsnjj_mthufymqwKl4oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 26 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoLIoqmCAur1Y7jpeSGyRpx71kbUqXT-R5dTjIswNvgyXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 27 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoEa3cdjo4w54WX1slUf3N6RgIFddjDWTfYNUg4mMF_ExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 28 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJGErv8Pw9R_vy2jSuU8h22F9iPtZMzBDYhF33EA0PGhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 29 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMh5sX_kYQkGl1nZ-RHbBlbBwbp-VcznAOB0nAVnXOzroFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 30 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGcOIoUzg5FTvy0MC0EZJsxo030YmtHVbISpQi0iaD3poFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 31 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNkhLvaaRHuHwXutFpApXpQMuKIUXeN0riiBvHCjpezvoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 32 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAZn-r-AVUqdftFL0aAYgnXg3Ihh3TpvNHF1Lk2f5dO7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 33 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoI5MZQFQY_vcdKf1l4cilyj0fjzDsAGNw5ZFew7mLNB7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 34 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFIGyZq62efyMMlN-YowvgItKWe3F4BY3CG2g_9q7jfRoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 35 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBV-hCyAvRGG3t7rNKZd1fKF1ZuQjEBKezJpX2508UDioFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 36 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFH9aUrJ9ByuptHRBLi9Qz5ZfxB9PHQo49SlwaBIgb6ZoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 37 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNMYg9Jdt7H6aQ8qvbWwbcqKrAYx8dFuK4ZQjkUPo-jNoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 38 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoArbi8-FGeh8uGxs7MxDXJUwVMznEnD_W-imVRojX9PnoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 39 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoPitbBNoKjvjSCImGCqCmwST-kMGThKeiK5WSDMw-CfqoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) + | otherwise = error "requested genesis block for unsupported chain" f t = case decodeB64UrlNoPaddingText t >>= decodeRlpM of Left e -> error $ "Chainweb.PayloadProvider.EVM.genesisBlocks: invalid genesis block: " <> sshow e Right x -> x - diff --git a/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs b/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs index 42d50c013d..6cfd12dc65 100644 --- a/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs +++ b/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs @@ -92,38 +92,32 @@ import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) -- data Configuration = Configuration - { _configChainwebVersion :: !ChainwebVersion - , _configChainId :: ChainId + { _configChainId :: ChainId , _configGenesis :: !Header , _configRocksDb :: !RocksDb } configuration :: HasCallStack - => HasChainwebVersion v + => HasVersion => HasChainId c - => v - -> c + => c -> RocksDb -> Header -> Configuration -configuration v c rdb gen +configuration c rdb gen | not isEvmProvider = error "Chainweb.PayloadProvider.Evm.HeaderDB.configuration: chain does not use evm provider" | otherwise = Configuration - { _configChainwebVersion = _chainwebVersion v - , _configChainId = _chainId c + { _configChainId = _chainId c , _configRocksDb = rdb , _configGenesis = gen } where - isEvmProvider = case payloadProviderTypeForChain v c of + isEvmProvider = case payloadProviderTypeForChain c of EvmProvider{} -> True _ -> False -instance HasChainwebVersion Configuration where - _chainwebVersion = _configChainwebVersion - instance HasChainId Configuration where _chainId = _configChainId @@ -172,7 +166,6 @@ type HeaderDb tbl = HeaderDb_ ChainwebMerkleHashAlgorithm tbl data HeaderDb_ a tbl = HeaderDb { _headerDbChainId :: !ChainId - , _headerDbChainwebVersion :: !ChainwebVersion , _headerDbTable :: !(tbl RankedBlockPayloadHash RankedHeader) } @@ -180,10 +173,6 @@ instance HasChainId (HeaderDb_ a tbl) where _chainId = _headerDbChainId {-# INLINE _chainId #-} -instance HasChainwebVersion (HeaderDb_ a tbl) where - _chainwebVersion = _headerDbChainwebVersion - {-# INLINE _chainwebVersion #-} - instance ReadableTable1 tbl => ReadableTable (HeaderDb_ a tbl) RankedBlockPayloadHash Header where tableLookup db k = fmap _getRankedHeader <$> tableLookup (_headerDbTable db) k {-# INLINE tableLookup #-} @@ -215,7 +204,6 @@ initHeaderDb config = do !db = HeaderDb { _headerDbChainId = cid - , _headerDbChainwebVersion = _configChainwebVersion config , _headerDbTable = headerTable } @@ -226,16 +214,14 @@ closeHeaderDb _ = return () withHeaderDb :: RocksDb - -> ChainwebVersion -> ChainId -> Header -> (HeaderDb_ a RocksDbTable -> IO b) -> IO b -withHeaderDb db v cid genesisHeader = bracket start closeHeaderDb +withHeaderDb db cid genesisHeader = bracket start closeHeaderDb where start = initHeaderDb Configuration - { _configChainwebVersion = v - , _configChainId = cid + { _configChainId = cid , _configGenesis = genesisHeader , _configRocksDb = db } @@ -294,4 +280,3 @@ dbAddChecked db e = -- * Item is not yet in database -- dbAddCheckedInternal = casInsert (_headerDbTable db) (RankedHeader e) - diff --git a/src/Chainweb/PayloadProvider/EVM/SPV.hs b/src/Chainweb/PayloadProvider/EVM/SPV.hs index b81d70bf93..e9a3f0cfc9 100644 --- a/src/Chainweb/PayloadProvider/EVM/SPV.hs +++ b/src/Chainweb/PayloadProvider/EVM/SPV.hs @@ -122,11 +122,11 @@ xLogDataSignature = b parseXLogData :: MonadThrow m - => ChainwebVersion - -> XEventId + => HasVersion + => XEventId -> LogEntry -> m XLogData -parseXLogData v eid e = do +parseXLogData eid e = do (LogTopic t0, LogTopic t1, LogTopic t2, LogTopic t3) <- getTopics -- FIXME FIXME FIXME @@ -135,7 +135,7 @@ parseXLogData v eid e = do unless (t0 == xLogDataSignature) $ throwM $ UnsupportedEventType eid - targetChain <- mkChainId v h =<< runGetS decodeWordBe (E.bytes $ dropN @32 @4 t1) + targetChain <- mkChainId h =<< runGetS decodeWordBe (E.bytes $ dropN @32 @4 t1) operationName <- XChainOperationName <$> runGetS decodeWordBe (E.bytes $ dropN @32 @8 t3) return XLogData @@ -151,4 +151,3 @@ parseXLogData v eid e = do (t0 : t1 : t2 : t3: _ ) -> return (t0, t1, t2, t3) l -> throwM $ InvalidEvent eid $ "expected at least four topics but got " <> sshow (length l) - diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index 02e4dd9ba6..f589a4a0be 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -175,11 +175,10 @@ pMinimalProviderConfig = id <> help "a valid account on the redeem chain for the default payload provider" validateMinimalProviderConfig - :: HasChainwebVersion v - => v - -> ConfigValidation MinimalProviderConfig [] -validateMinimalProviderConfig v o - | HS.member rcid (chainIds v) = return () + :: HasVersion + => ConfigValidation MinimalProviderConfig [] +validateMinimalProviderConfig o + | HS.member rcid chainIds = return () | otherwise = do throwError $ mconcat [ "The provided redeem chain for the default payload provider is not a valid chain in the current chainweb" @@ -191,8 +190,7 @@ validateMinimalProviderConfig v o -- -------------------------------------------------------------------------- -- data MinimalPayloadProvider = MinimalPayloadProvider - { _minimalChainwebVersion :: !ChainwebVersion - , _minimalChainId :: !ChainId + { _minimalChainId :: !ChainId , _minimalPayloadVar :: !(TMVar NewPayload) , _minimalRedeemChain :: !ChainId , _minimalMinerInfo :: !Account @@ -212,31 +210,29 @@ minimalPayloadQueue = to (_payloadStoreQueue . _minimalPayloadStore) newMinimalPayloadProvider :: Logger logger - => HasChainwebVersion v + => HasVersion => HasChainId c => logger - -> v -> c -> RocksDb -> Maybe HTTP.Manager -> MinimalProviderConfig -> IO MinimalPayloadProvider -newMinimalPayloadProvider logger v c rdb mgr conf - | payloadProviderTypeForChain v c /= MinimalProvider = +newMinimalPayloadProvider logger c rdb mgr conf + | payloadProviderTypeForChain c /= MinimalProvider = error "Chainweb.PayloadProvider.Minimal.configuration: chain does not use minimal provider" | otherwise = do - SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal v + SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal SomeChainIdT @c _ <- return $ someChainIdVal c let payloadClient h = Rest.payloadClient @v @c @'MinimalProvider h - pdb <- PDB.initPayloadDb $ PDB.configuration v c rdb + pdb <- PDB.initPayloadDb $ PDB.configuration c rdb store <- newPayloadStore mgr (logFunction pldStoreLogger) pdb payloadClient var <- newEmptyTMVarIO candidates <- emptyTable logFunctionText logger Info "minimal payload provider started" return MinimalPayloadProvider - { _minimalChainwebVersion = _chainwebVersion v - , _minimalChainId = _chainId c + { _minimalChainId = _chainId c , _minimalPayloadVar = var , _minimalRedeemChain = _mpcRedeemChain conf , _minimalMinerInfo = _mpcRedeemAccount conf @@ -247,9 +243,6 @@ newMinimalPayloadProvider logger v c rdb mgr conf where pldStoreLogger = addLabel ("sub-component", "payloadStore") logger -instance HasChainwebVersion MinimalPayloadProvider where - _chainwebVersion = _minimalChainwebVersion - instance HasChainId MinimalPayloadProvider where _chainId = _minimalChainId @@ -301,14 +294,12 @@ instance Exception PayloadValidationFailure validatePayload :: forall m . MonadThrow m + => HasVersion => MinimalPayloadProvider -> Payload -> EvaluationCtx ConsensusPayload -> m () validatePayload p pld ctx = do - checkEq PayloadInvalidChainwebVersion - (_chainwebVersion p) - (_chainwebVersion pld) checkEq PayloadInvalidChainId (_chainId p) (_chainId pld) @@ -389,7 +380,8 @@ getPayloadForContext p h ctx = do -- Should we also expose a version that is fire-and-forget? -- minimalPrefetchPayloads - :: MinimalPayloadProvider + :: HasVersion + => MinimalPayloadProvider -> Maybe Hints -> ForkInfo -> IO () @@ -419,7 +411,8 @@ minimalPrefetchPayloads p h i = do -- history. -- minimalSyncToBlock - :: MinimalPayloadProvider + :: HasVersion + => MinimalPayloadProvider -> Maybe Hints -> ForkInfo -> IO ConsensusState @@ -446,13 +439,13 @@ logg :: MinimalPayloadProvider -> LogLevel -> T.Text -> IO () logg p l t = _minimalLogger p l t makeNewPayload - :: MinimalPayloadProvider + :: HasVersion + => MinimalPayloadProvider -> SyncState -> NewBlockCtx -> NewPayload makeNewPayload p latest ctx = NewPayload - { _newPayloadChainwebVersion = _chainwebVersion p - , _newPayloadChainId = _chainId p + { _newPayloadChainId = _chainId p , _newPayloadParentHeight = Parent $ _syncStateHeight latest , _newPayloadParentHash = Parent $ _syncStateBlockHash latest , _newPayloadBlockPayloadHash = view payloadHash pld @@ -465,7 +458,7 @@ makeNewPayload p latest ctx = NewPayload , _newPayloadFees = 0 } where - pld = newPayload p p + pld = newPayload p (_syncStateHeight latest + 1) (_newBlockCtxMinerReward ctx) (_minimalRedeemChain p) @@ -482,7 +475,8 @@ makeNewPayload p latest ctx = NewPayload -- pipeline, but this might not always be the case in the future. -- validatePayloads - :: MinimalPayloadProvider + :: HasVersion + => MinimalPayloadProvider -> Maybe Hints -> ForkInfo -> IO () @@ -516,10 +510,10 @@ minimalLatestPayloadStm = readTMVar . _minimalPayloadVar -- ignore that case. We want a solution where we don't have permanent cost -- overhead for this exceptional scenario. -- -candidatePruningDepth :: MinimalPayloadProvider -> BlockHeight -> BlockHeight -candidatePruningDepth p h = int $ diameter (chainGraphAt (_chainwebVersion p) h) +candidatePruningDepth :: HasVersion => MinimalPayloadProvider -> BlockHeight -> BlockHeight +candidatePruningDepth p h = int $ diameter (chainGraphAt h) -pruneCandidates :: MinimalPayloadProvider -> ConsensusState -> IO () +pruneCandidates :: HasVersion => MinimalPayloadProvider -> ConsensusState -> IO () pruneCandidates p s = deleteLt (_minimalCandidatePayloads p) lrh { _rankedHeight = h - candidatePruningDepth p h } diff --git a/src/Chainweb/PayloadProvider/Minimal/Payload.hs b/src/Chainweb/PayloadProvider/Minimal/Payload.hs index 30dff5cebb..8f1f8b9b73 100644 --- a/src/Chainweb/PayloadProvider/Minimal/Payload.hs +++ b/src/Chainweb/PayloadProvider/Minimal/Payload.hs @@ -179,9 +179,6 @@ data Payload = Payload } deriving (Show, Generic) -instance HasChainwebVersion Payload where - _chainwebVersion = lookupVersionByCode . _payloadChainwebVersion - instance HasChainId Payload where _chainId = _payloadChainId @@ -319,19 +316,18 @@ decodePayload = do genesisPayload :: HasCallStack - => HasChainwebVersion v + => HasVersion => HasChainId cid - => v - -> cid + => cid -> Payload -genesisPayload v cid - | payloadProviderTypeForChain v cid /= MinimalProvider = +genesisPayload cid + | payloadProviderTypeForChain cid /= MinimalProvider = error "Chainweb.PayloadProvider.Minimal.Payload.genesisPayload: chain does not use minimal provider" | otherwise = pld where - genHeight = genesisBlockHeight (_chainwebVersion v) (_chainId cid) + genHeight = genesisBlockHeight (_chainId cid) pld = Payload - { _payloadChainwebVersion = _versionCode (_chainwebVersion v) + { _payloadChainwebVersion = _versionCode implicitVersion , _payloadChainId = _chainId cid , _payloadBlockHeight = genHeight , _payloadMinerReward = MinerReward 0 @@ -347,19 +343,18 @@ genesisPayload v cid newPayload :: HasCallStack - => HasChainwebVersion v + => HasVersion => HasChainId cid - => v - -> cid + => cid -> BlockHeight -> MinerReward -> ChainId -> Account -> Payload -newPayload v c h r rc acc = pld +newPayload c h r rc acc = pld where pld = Payload - { _payloadChainwebVersion = _versionCode (_chainwebVersion v) + { _payloadChainwebVersion = _versionCode implicitVersion , _payloadChainId = _chainId c , _payloadBlockHeight = h , _payloadMinerReward = r @@ -421,4 +416,3 @@ payloadRedeemAccount = to _payloadRedeemAccount payloadHash :: Getter Payload BlockPayloadHash payloadHash = to _payloadHash - diff --git a/src/Chainweb/PayloadProvider/Minimal/PayloadDB.hs b/src/Chainweb/PayloadProvider/Minimal/PayloadDB.hs index f5930f4d25..57b168b4b1 100644 --- a/src/Chainweb/PayloadProvider/Minimal/PayloadDB.hs +++ b/src/Chainweb/PayloadProvider/Minimal/PayloadDB.hs @@ -68,8 +68,7 @@ instance Exception PayloadNotFoundException -- data Configuration = Configuration - { _configChainwebVersion' :: !ChainwebVersion - , _configChainId' :: !ChainId + { _configChainId' :: !ChainId , _configRocksDb' :: !RocksDb } @@ -78,24 +77,19 @@ _configRocksDb = _configRocksDb' configuration :: HasCallStack - => HasChainwebVersion v + => HasVersion => HasChainId c - => v - -> c + => c -> RocksDb -> Configuration -configuration v c rdb - | payloadProviderTypeForChain v c /= MinimalProvider = +configuration c rdb + | payloadProviderTypeForChain c /= MinimalProvider = error "Chainweb.PayloadProvider.Minimal.PayloadDB.configuration: chain does not use minimal provider" | otherwise = Configuration - { _configChainwebVersion' = _chainwebVersion v - , _configChainId' = _chainId c + { _configChainId' = _chainId c , _configRocksDb' = rdb } -instance HasChainwebVersion Configuration where - _chainwebVersion = _configChainwebVersion' - instance HasChainId Configuration where _chainId = _configChainId' @@ -144,7 +138,6 @@ type PayloadDb tbl = PayloadDb_ ChainwebMerkleHashAlgorithm tbl data PayloadDb_ a tbl = PayloadDb { _payloadDbChainId' :: !ChainId - , _payloadDbChainwebVersion' :: !ChainwebVersion , _payloadDbTable' :: !(tbl RankedBlockPayloadHash RankedPayload) } @@ -155,10 +148,6 @@ instance HasChainId (PayloadDb_ a tbl) where _chainId = _payloadDbChainId' {-# INLINE _chainId #-} -instance HasChainwebVersion (PayloadDb_ a tbl) where - _chainwebVersion = _payloadDbChainwebVersion' - {-# INLINE _chainwebVersion #-} - instance ReadableTable1 tbl => ReadableTable (PayloadDb_ a tbl) RankedBlockPayloadHash Payload where tableLookup db k = fmap _getRankedPayload <$> tableLookup (_payloadDbTable db) k {-# INLINE tableLookup #-} @@ -174,10 +163,10 @@ instance Table1 tbl => Table (PayloadDb_ a tbl) RankedBlockPayloadHash Payload w -- | Initialize a database handle -- -initPayloadDb :: Configuration -> IO (PayloadDb_ a RocksDbTable) +initPayloadDb :: HasVersion => Configuration -> IO (PayloadDb_ a RocksDbTable) initPayloadDb config = do -- Add genesis payload - dbAddChecked db (genesisPayload config config) + dbAddChecked db (genesisPayload config) return db where cid = _chainId config @@ -191,7 +180,6 @@ initPayloadDb config = do !db = PayloadDb { _payloadDbChainId' = cid - , _payloadDbChainwebVersion' = _chainwebVersion config , _payloadDbTable' = payloadTable } @@ -201,14 +189,14 @@ closePayloadDb :: PayloadDb_ a tbl -> IO () closePayloadDb _ = return () withPayloadDb - :: RocksDb - -> ChainwebVersion + :: HasVersion + => RocksDb -> ChainId -> (PayloadDb_ a RocksDbTable -> IO b) -> IO b -withPayloadDb db v cid = bracket start closePayloadDb +withPayloadDb db cid = bracket start closePayloadDb where - start = initPayloadDb $ configuration v cid db + start = initPayloadDb $ configuration cid db -- -------------------------------------------------------------------------- -- -- Validated Payload @@ -266,4 +254,3 @@ dbAddChecked db e = -- * Item is not yet in database -- dbAddCheckedInternal = casInsert (_payloadDbTable db) (RankedPayload e) - diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs index 68e5232c45..332037a2f5 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs @@ -255,13 +255,13 @@ payloadApi = Proxy somePayloadApi :: IsPayloadProvider 'MinimalProvider => IsPayloadProvider 'PactProvider - => ChainwebVersion - -> ChainId + => HasVersion + => ChainId -> SomeApi -somePayloadApi v c = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v') <- return $ someChainwebVersionVal v +somePayloadApi c = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v') <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c') <- return $ someChainIdVal c - withSomeSing (payloadProviderTypeForChain v c) $ \case + withSomeSing (payloadProviderTypeForChain c) $ \case SMinimalProvider -> return $! SomeApi (payloadApi @v' @c' @'MinimalProvider) SPactProvider -> @@ -269,8 +269,8 @@ somePayloadApi v c = runIdentity $ do SEvmProvider @n _ -> return $! SomeApi (payloadApi @v' @c' @('EvmProvider n)) -somePayloadApis :: ChainwebVersion -> [ChainId] -> SomeApi -somePayloadApis v = mconcat . fmap (somePayloadApi v) +somePayloadApis :: HasVersion => [ChainId] -> SomeApi +somePayloadApis = mconcat . fmap somePayloadApi -- -------------------------------------------------------------------------- -- -- Implementations of IsPayloadProvider @@ -341,4 +341,3 @@ instance MimeRender OctetStream HeaderList where instance MimeUnrender OctetStream HeaderList where mimeUnrender _ = EVM.getLazy EVM.getRlp {-# INLINE mimeUnrender #-} - diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 9a108e29ad..fc4a0fe9ae 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -56,26 +56,19 @@ makePrisms ''PactPayloadProvider instance HasChainId (PactPayloadProvider logger tbl) where chainId = _PactPayloadProvider . _2 . chainId -instance HasChainwebVersion (PactPayloadProvider logger tbl) where - chainwebVersion = _PactPayloadProvider . _2 . chainwebVersion - instance (Logger logger, CanPayloadCas tbl) => PayloadProvider (PactPayloadProvider logger tbl) where - prefetchPayloads :: Logger logger => PactPayloadProvider logger tbl -> Maybe Hints -> ForkInfo -> IO () prefetchPayloads _pp _hints _forkInfo = return () - syncToBlock :: Logger logger => PactPayloadProvider logger tbl -> Maybe Hints -> ForkInfo -> IO ConsensusState syncToBlock (PactPayloadProvider logger e) hints forkInfo = PactService.syncToFork logger e hints forkInfo - latestPayloadSTM :: Logger logger => PactPayloadProvider logger tbl -> STM NewPayload latestPayloadSTM (PactPayloadProvider _logger e) = do (_, bip) <- readTMVar (_psMiningPayloadVar e) let pwo = toPayloadWithOutputs (fromJuste $ _psMiner e) (_blockInProgressTransactions bip) return NewPayload - { _newPayloadChainwebVersion = _chainwebVersion e - , _newPayloadChainId = _chainId e + { _newPayloadChainId = _chainId e , _newPayloadParentHeight = _bctxParentHeight $ _blockInProgressBlockCtx bip , _newPayloadParentHash = _bctxParentHash $ _blockInProgressBlockCtx bip , _newPayloadBlockPayloadHash = _payloadWithOutputsPayloadHash pwo @@ -111,8 +104,8 @@ decodeNewPayload NewPayload{..} = do withPactPayloadProvider :: CanPayloadCas tbl => Logger logger - => ChainwebVersion - -> ChainId + => HasVersion + => ChainId -> Maybe HTTP.Manager -> logger -> Maybe (Counter "txFailures") @@ -122,7 +115,7 @@ withPactPayloadProvider -> PactServiceConfig -> Maybe PayloadWithOutputs -> ResourceT IO (PactPayloadProvider logger tbl) -withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir config maybeGenesisPayload = do +withPactPayloadProvider cid http logger txFailuresCounter mp pdb pactDbDir config maybeGenesisPayload = do readWriteSqlenv <- withSqliteDb cid logger pactDbDir False (_, readOnlySqlPool) <- allocate (Pool.newPool $ Pool.defaultPoolConfig @@ -134,7 +127,7 @@ withPactPayloadProvider ver cid http logger txFailuresCounter mp pdb pactDbDir c ) Pool.destroyAllResources PactPayloadProvider logger <$> - PactService.withPactService ver cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config + PactService.withPactService cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config (maybe GenesisNotNeeded GenesisPayload maybeGenesisPayload) where mpa = pactMemPoolAccess mp $ addLabel ("sub-component", "MempoolAccess") logger diff --git a/src/Chainweb/PayloadProvider/Pact/Genesis.hs b/src/Chainweb/PayloadProvider/Pact/Genesis.hs index 51c1c8cae3..39a9c35573 100644 --- a/src/Chainweb/PayloadProvider/Pact/Genesis.hs +++ b/src/Chainweb/PayloadProvider/Pact/Genesis.hs @@ -40,9 +40,9 @@ import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment1to9Payload as RDN import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload as RDNKAD import Chainweb.Utils -genesisPayload :: ChainwebVersion -> ChainMap PayloadWithOutputs -genesisPayload v - | _versionCode v == _versionCode Mainnet01 = ChainMap $ HM.fromList $ concat +genesisPayload :: HasVersion => ChainMap PayloadWithOutputs +genesisPayload + | _versionCode implicitVersion == _versionCode Mainnet01 = ChainMap $ HM.fromList $ concat [ [ (unsafeChainId 0, MN0.payloadBlock) , (unsafeChainId 1, MN1.payloadBlock) @@ -57,22 +57,22 @@ genesisPayload v ] , [(unsafeChainId i, MNKAD.payloadBlock) | i <- [10..19]] ] - | _versionCode v == _versionCode Testnet04 = ChainMap $ HM.fromList $ concat + | _versionCode implicitVersion == _versionCode Testnet04 = ChainMap $ HM.fromList $ concat [ [(unsafeChainId 0, T04N0.payloadBlock)] , [(unsafeChainId i, T04NN.payloadBlock) | i <- [1..19]] ] - | _versionCode v == _versionCode Development = ChainMap $ HM.fromList $ concat + | _versionCode implicitVersion == _versionCode Development = ChainMap $ HM.fromList $ concat [ [(unsafeChainId 0, DN0.payloadBlock)] , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] ] - | _versionCode v == _versionCode RecapDevelopment = ChainMap $ HM.fromList $ concat + | _versionCode implicitVersion == _versionCode RecapDevelopment = ChainMap $ HM.fromList $ concat [ [(unsafeChainId 0, RDN0.payloadBlock)] , [(unsafeChainId i, RDNN.payloadBlock) | i <- [1..9]] , [(unsafeChainId i, RDNKAD.payloadBlock) | i <- [10..19]] ] - | _versionCode v == _versionCode EvmDevelopment = ChainMap $ HM.fromList $ concat + | _versionCode implicitVersion == _versionCode EvmDevelopment = ChainMap $ HM.fromList $ concat [ [(unsafeChainId 0, DN0.payloadBlock)] , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] ] -genesisPayload v = error $ - "Chainweb.PayloadProvider.Pact.Genesis.genesisPayload: unsupported chainweb version: " <> sshow v + | otherwise = error $ + "Chainweb.PayloadProvider.Pact.Genesis.genesisPayload: unsupported chainweb version: " <> sshow implicitVersion diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index e3f61a279b..76651c2b92 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -200,15 +200,16 @@ chainwebServiceMiddlewares -- Chainweb Peer Server someChainwebServer - :: ChainwebConfiguration + :: HasVersion + => ChainwebConfiguration -> ChainwebServerDbs -> SomeServer someChainwebServer config dbs = - maybe mempty (someCutServer v cutPeerDb) cuts + maybe mempty (someCutServer cutPeerDb) cuts <> fold payloads - <> someP2pBlockHeaderDbServers v blocks - <> Mempool.someMempoolServers v mempools - <> someP2pServers v peers + <> someP2pBlockHeaderDbServers blocks + <> Mempool.someMempoolServers mempools + <> someP2pServers peers <> someGetConfigServer config where payloads = _chainwebServerPayloads dbs @@ -217,24 +218,24 @@ someChainwebServer config dbs = peers = _chainwebServerPeerDbs dbs mempools = _chainwebServerMempools dbs cutPeerDb = fromJuste $ lookup CutNetwork peers - v = _configChainwebVersion config -- | Legacy version with Hashes API that is used in tests -- -- When we have comprehensive testing for the service API we can remove this -- someChainwebServerWithHashesAndSpvApi - :: ChainwebConfiguration + :: HasVersion + => ChainwebConfiguration -> ChainwebServerDbs -> SomeServer someChainwebServerWithHashesAndSpvApi config dbs = - maybe mempty (someCutServer v cutPeerDb) cuts + maybe mempty (someCutServer cutPeerDb) cuts <> fold payloads - <> someBlockHeaderDbServers v blocks - <> Mempool.someMempoolServers v mempools - <> someP2pServers v peers + <> someBlockHeaderDbServers blocks + <> Mempool.someMempoolServers mempools + <> someP2pServers peers <> someGetConfigServer config - <> maybe mempty (someSpvServers v) cuts + <> maybe mempty someSpvServers cuts where payloads = _chainwebServerPayloads dbs blocks = _chainwebServerBlockHeaderDbs dbs @@ -242,13 +243,13 @@ someChainwebServerWithHashesAndSpvApi config dbs = peers = _chainwebServerPeerDbs dbs mempools = _chainwebServerMempools dbs cutPeerDb = fromJuste $ lookup CutNetwork peers - v = _configChainwebVersion config -- -------------------------------------------------------------------------- -- -- Chainweb P2P API Application chainwebApplication - :: ChainwebConfiguration + :: HasVersion + => ChainwebConfiguration -> ChainwebServerDbs -> Application chainwebApplication config dbs @@ -261,7 +262,8 @@ chainwebApplication config dbs -- When we have comprehensive testing for the service API we can remove this -- chainwebApplicationWithHashesAndSpvApi - :: ChainwebConfiguration + :: HasVersion + => ChainwebConfiguration -> ChainwebServerDbs -> Application chainwebApplicationWithHashesAndSpvApi config dbs @@ -270,21 +272,24 @@ chainwebApplicationWithHashesAndSpvApi config dbs $ someChainwebServerWithHashesAndSpvApi config dbs serveChainwebOnPort - :: Port + :: HasVersion + => Port -> ChainwebConfiguration -> ChainwebServerDbs -> IO () serveChainwebOnPort p c dbs = run (int p) $ chainwebApplication c dbs serveChainweb - :: Settings + :: HasVersion + => Settings -> ChainwebConfiguration -> ChainwebServerDbs -> IO () serveChainweb s c dbs = runSettings s $ chainwebApplication c dbs serveChainwebSocket - :: Settings + :: HasVersion + => Settings -> Socket -> ChainwebConfiguration -> ChainwebServerDbs @@ -294,7 +299,8 @@ serveChainwebSocket settings sock c dbs m = runSettingsSocket settings sock $ m $ chainwebApplication c dbs serveChainwebSocketTls - :: Settings + :: HasVersion + => Settings -> X509CertChainPem -> X509KeyPem -> Socket @@ -310,47 +316,46 @@ serveChainwebSocketTls settings certChain key sock c dbs m = -- Run Chainweb P2P Server that serves a single PeerDb servePeerDbSocketTls - :: Settings + :: HasVersion + => Settings -> X509CertChainPem -> X509KeyPem -> Socket - -> ChainwebVersion -> NetworkId -> PeerDb -> Middleware -> IO () -servePeerDbSocketTls settings certChain key sock v nid pdb m = +servePeerDbSocketTls settings certChain key sock nid pdb m = serveSocketTls settings certChain key sock $ m $ chainwebP2pMiddlewares $ someServerApplication $ someP2pServer - $ somePeerDbVal v nid pdb + $ somePeerDbVal nid pdb -- -------------------------------------------------------------------------- -- -- Chainweb Service API Application someServiceApiServer :: Logger logger - => ChainwebVersion - -> ChainwebServerDbs + => HasVersion + => ChainwebServerDbs -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) -> PayloadBatchLimit -> SomeServer -someServiceApiServer v dbs mr (HeaderStream hs) backupEnv pbl = +someServiceApiServer dbs mr (HeaderStream hs) backupEnv pbl = someHealthCheckServer - <> maybe mempty (someBackupServer v) backupEnv - <> maybe mempty (someNodeInfoServer v) cuts - <> mempty -- PactAPI.somePactServers v - <> maybe mempty (Mining.someMiningServer v) mr - <> maybe mempty (someSpvServers v) cuts -- AFAIK currently not used + <> maybe mempty someBackupServer backupEnv + <> maybe mempty someNodeInfoServer cuts + <> maybe mempty Mining.someMiningServer mr + <> maybe mempty someSpvServers cuts -- AFAIK currently not used -- GET Cut, Payload, and Headers endpoints - <> maybe mempty (someCutGetServer v) cuts + <> maybe mempty someCutGetServer cuts <> fold payloads - <> someBlockHeaderDbServers v blocks - <> maybe mempty (someBlockStreamServer v) (bool Nothing cuts hs) + <> someBlockHeaderDbServers blocks + <> maybe mempty someBlockStreamServer (bool Nothing cuts hs) where cuts = _chainwebServerCutDb dbs payloads = _chainwebServerPayloads dbs @@ -358,23 +363,23 @@ someServiceApiServer v dbs mr (HeaderStream hs) backupEnv pbl = serviceApiApplication :: Logger logger - => ChainwebVersion - -> ChainwebServerDbs + => HasVersion + => ChainwebServerDbs -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) -> PayloadBatchLimit -> Application -serviceApiApplication v dbs mr hs benv pbl +serviceApiApplication dbs mr hs benv pbl = chainwebServiceMiddlewares . someServerApplication - $ someServiceApiServer v dbs mr hs benv pbl + $ someServiceApiServer dbs mr hs benv pbl serveServiceApiSocket :: Logger logger + => HasVersion => Settings -> Socket - -> ChainwebVersion -> ChainwebServerDbs -> Maybe (MiningCoordination logger) -> HeaderStream @@ -382,5 +387,5 @@ serveServiceApiSocket -> PayloadBatchLimit -> Middleware -> IO () -serveServiceApiSocket s sock v dbs mr hs benv pbl m = - runSettingsSocket s sock $ m $ serviceApiApplication v dbs mr hs benv pbl +serveServiceApiSocket s sock dbs mr hs benv pbl m = + runSettingsSocket s sock $ m $ serviceApiApplication dbs mr hs benv pbl diff --git a/src/Chainweb/RestAPI/Backup.hs b/src/Chainweb/RestAPI/Backup.hs index 6c9c9ec463..efccced472 100644 --- a/src/Chainweb/RestAPI/Backup.hs +++ b/src/Chainweb/RestAPI/Backup.hs @@ -52,9 +52,10 @@ globalCurrentBackup = unsafePerformIO $! newTVarIO Nothing someBackupApi :: ChainwebVersion -> SomeApi someBackupApi (FromSingChainwebVersion (SChainwebVersion :: Sing v)) = SomeApi $ backupApi @v -someBackupServer :: Logger logger => ChainwebVersion -> Backup.BackupEnv logger -> SomeServer -someBackupServer (FromSingChainwebVersion (SChainwebVersion :: Sing vT)) backupEnv = - SomeServer (Proxy @(BackupApi vT)) $ makeBackup :<|> checkBackup +someBackupServer :: (HasVersion, Logger logger) => Backup.BackupEnv logger -> SomeServer +someBackupServer backupEnv = case implicitVersion of + (FromSingChainwebVersion (SChainwebVersion :: Sing vT)) -> + SomeServer (Proxy @(BackupApi vT)) $ makeBackup :<|> checkBackup where noSuchBackup = setErrText "no such backup" err404 makeBackup backupPactFlag = liftIO $ do @@ -87,4 +88,3 @@ getNextBackupIdentifier :: IO Text getNextBackupIdentifier = do Time (epochToNow :: TimeSpan Integer) <- getCurrentTimeIntegral return $ microsToText (timeSpanToMicros epochToNow) - diff --git a/src/Chainweb/RestAPI/NodeInfo.hs b/src/Chainweb/RestAPI/NodeInfo.hs index f7d6e76403..00816ce375 100644 --- a/src/Chainweb/RestAPI/NodeInfo.hs +++ b/src/Chainweb/RestAPI/NodeInfo.hs @@ -45,9 +45,9 @@ type NodeInfoApi = "info" :> Get '[JSON] NodeInfo someNodeInfoApi :: SomeApi someNodeInfoApi = SomeApi (Proxy @NodeInfoApi) -someNodeInfoServer :: ChainwebVersion -> CutDb -> SomeServer -someNodeInfoServer v c = - SomeServer (Proxy @NodeInfoApi) (nodeInfoHandler v $ someCutDbVal v c) +someNodeInfoServer :: HasVersion => CutDb -> SomeServer +someNodeInfoServer c = + SomeServer (Proxy @NodeInfoApi) (nodeInfoHandler $ someCutDbVal c) data NodeInfo = NodeInfo { nodeVersion :: ChainwebVersionName @@ -78,33 +78,34 @@ data NodeInfo = NodeInfo deriving (Show, Eq, Generic) deriving anyclass (ToJSON, FromJSON) -nodeInfoHandler :: ChainwebVersion -> SomeCutDb -> Server NodeInfoApi -nodeInfoHandler v (SomeCutDb (CutDbT db :: CutDbT v)) = do +nodeInfoHandler :: HasVersion => SomeCutDb -> Server NodeInfoApi +nodeInfoHandler (SomeCutDb (CutDbT db :: CutDbT v)) = do curCut <- liftIO $ _cut db let ch = cutToCutHashes Nothing curCut let curHeight = maximum $ map _bhwhHeight $ HM.elems $ _cutHashes ch - let graphs = unpackGraphs v + let graphs = unpackGraphs let curGraph = unsafeHead "Chainweb.RestAPI.NodeInfo.nodeInfoHandler.curGraph" $ dropWhile (\(h,_) -> h > curHeight) graphs let curChains = map fst $ snd curGraph return $ NodeInfo - { nodeVersion = _versionName v + { nodeVersion = _versionName implicitVersion , nodePackageVersion = chainwebNodeVersionHeaderValue , nodeApiVersion = prettyApiVersion , nodeChains = T.pack . show <$> curChains , nodeNumberOfChains = length curChains , nodeGraphHistory = graphs - , nodeLatestBehaviorHeight = latestBehaviorAt v - , nodeGenesisHeights = map (\c -> (chainIdToText c, genesisHeight v c)) $ HS.toList (chainIds v) - , nodeHistoricalChains = ruleElems $ fmap (HM.toList . HM.map HS.toList . toAdjacencySets) $ _versionGraphs v - , nodeServiceDate = T.pack <$> _versionServiceDate v - , nodeBlockDelay = _versionBlockDelay v + , nodeLatestBehaviorHeight = latestBehaviorAt + , nodeGenesisHeights = map (\c -> (chainIdToText c, genesisHeight c)) $ HS.toList chainIds + , nodeHistoricalChains = + ruleElems $ fmap (HM.toList . HM.map HS.toList . toAdjacencySets) $ _versionGraphs implicitVersion + , nodeServiceDate = T.pack <$> _versionServiceDate implicitVersion + , nodeBlockDelay = _versionBlockDelay implicitVersion } -- | Converts chainwebGraphs to a simpler structure that has invertible JSON -- instances. -unpackGraphs :: ChainwebVersion -> [(BlockHeight, [(Int, [Int])])] -unpackGraphs v = gs +unpackGraphs :: HasVersion => [(BlockHeight, [(Int, [Int])])] +unpackGraphs = gs where - gs = map (second graphAdjacencies) $ NE.toList $ ruleElems $ _versionGraphs v + gs = map (second graphAdjacencies) $ NE.toList $ ruleElems $ _versionGraphs implicitVersion graphAdjacencies = map unChain . HM.toList . fmap HS.toList . G.adjacencySets . view chainGraphGraph unChain (a, bs) = (chainIdInt a, map chainIdInt bs) diff --git a/src/Chainweb/SPV/CreateProof.hs b/src/Chainweb/SPV/CreateProof.hs index 36851e1ef1..3230fced30 100644 --- a/src/Chainweb/SPV/CreateProof.hs +++ b/src/Chainweb/SPV/CreateProof.hs @@ -135,6 +135,7 @@ createTransactionOutputProof cutDb tcid scid = -- createSmallTransactionOutputProof :: HasCallStack + => HasVersion => PayloadProvider p => WebBlockHeaderDb -- ^ Block Header Database @@ -214,6 +215,7 @@ type PayloadProofPrefix = -- createPayloadProof_ :: HasCallStack + => HasVersion => (Int -> BlockHeight -> BlockPayloadHash -> IO PayloadProofPrefix) -> WebBlockHeaderDb -> ChainId @@ -326,7 +328,8 @@ createPayloadProof_ getPrefix headerDb tcid scid txHeight txIx trgHeader = do -- Returns 'Nothing' if @i >= view blockHeight h@. -- crumbsOnChain - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> BlockHeader -> BlockHeight -> IO (Maybe (N.NonEmpty BlockHeader)) @@ -346,16 +349,17 @@ crumbsOnChain db trgHeader srcHeight -- Returns 'Nothing' if no such path exists. -- crumbsToChain - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> ChainId -> BlockHeader -> IO (Maybe (BlockHeader, [(Int, BlockHeader)])) -- ^ bread crumbs that lead from to source Chain to targetHeader crumbsToChain db srcCid trgHeader - | (int (view blockHeight trgHeader) + 1) < length path = return Nothing + | int (view blockHeight trgHeader) + 1 < length path = return Nothing | otherwise = Just <$> go trgHeader path [] where - graph = chainGraphAt db (view blockHeight trgHeader) + graph = chainGraphAt (view blockHeight trgHeader) path = shortestPath (_chainId trgHeader) srcCid graph go @@ -374,7 +378,8 @@ crumbsToChain db srcCid trgHeader go adjpHdr t ((adjIdx, cur) : acc) minimumTrgHeader - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> ChainId -- ^ target chain. The proof asserts that the subject is included in -- this chain @@ -403,7 +408,7 @@ minimumTrgHeader headerDb tcid scid bh = do -- This assumes that graph changes are at least graph-diameter -- blocks appart. - srcGraph = chainGraphAt headerDb bh + srcGraph = chainGraphAt bh srcDistance = length $ shortestPath tcid scid srcGraph - trgGraph = chainGraphAt headerDb (bh + int srcDistance) + trgGraph = chainGraphAt (bh + int srcDistance) trgDistance = length $ shortestPath tcid scid trgGraph diff --git a/src/Chainweb/SPV/EventProof.hs b/src/Chainweb/SPV/EventProof.hs index df4914db2b..002e56f6ca 100644 --- a/src/Chainweb/SPV/EventProof.hs +++ b/src/Chainweb/SPV/EventProof.hs @@ -149,6 +149,7 @@ import Pact.Core.Hash import Pact.Core.ModRefs import Pact.Core.Errors import Pact.Core.Pretty (renderCompactText) +import Chainweb.Version (HasVersion) -- -------------------------------------------------------------------------- -- -- Pact Encoding Exceptions @@ -571,6 +572,7 @@ createEventsProofKeccak256 = createEventsProof_ createEventsProofDb_ :: forall a tbl . MerkleHashAlgorithm a + => HasVersion => CanReadablePayloadCas tbl => BlockHeaderDb -> PayloadDb tbl @@ -601,6 +603,7 @@ createEventsProofDb_ headerDb payloadDb d h reqKey = do createEventsProofDb :: CanReadablePayloadCas tbl + => HasVersion => BlockHeaderDb -> PayloadDb tbl -> Natural @@ -615,6 +618,7 @@ createEventsProofDb = createEventsProofDb_ createEventsProofDbKeccak256 :: CanReadablePayloadCas tbl + => HasVersion => BlockHeaderDb -> PayloadDb tbl -> Natural diff --git a/src/Chainweb/SPV/OutputProof.hs b/src/Chainweb/SPV/OutputProof.hs index a3d2d72395..804c038440 100644 --- a/src/Chainweb/SPV/OutputProof.hs +++ b/src/Chainweb/SPV/OutputProof.hs @@ -65,6 +65,7 @@ import Chainweb.TreeDB import Chainweb.Utils import Chainweb.Storage.Table +import Chainweb.Version (HasVersion) -- -------------------------------------------------------------------------- -- -- Utils @@ -200,6 +201,7 @@ createOutputProofDb_ :: forall a tbl . MerkleHashAlgorithm a => CanReadablePayloadCas tbl + => HasVersion => BlockHeaderDb -> PayloadDb tbl -> Natural @@ -232,6 +234,7 @@ createOutputProofDb_ headerDb payloadDb d h reqKey = do -- createOutputProofDb :: CanReadablePayloadCas tbl + => HasVersion => BlockHeaderDb -> PayloadDb tbl -> Natural @@ -249,6 +252,7 @@ createOutputProofDb = createOutputProofDb_ -- createOutputProofDbKeccak256 :: CanReadablePayloadCas tbl + => HasVersion => BlockHeaderDb -> PayloadDb tbl -> Natural diff --git a/src/Chainweb/SPV/RestAPI/Client.hs b/src/Chainweb/SPV/RestAPI/Client.hs index b36cc8040b..81fcc2e28c 100644 --- a/src/Chainweb/SPV/RestAPI/Client.hs +++ b/src/Chainweb/SPV/RestAPI/Client.hs @@ -57,8 +57,8 @@ spvGetTransactionOutputProofClient_ spvGetTransactionOutputProofClient_ = client (spvGetTransactionOutputProofApi @v @c) spvGetTransactionOutputProofClient - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -- ^ the target chain of the proof. This is the chain for which inclusion -- is proved. -> ChainId @@ -72,7 +72,7 @@ spvGetTransactionOutputProofClient -- ^ the index of the proof subject, the transaction output for which -- inclusion is proven. -> ClientM (TransactionOutputProof ChainwebMerkleHashAlgorithm) -spvGetTransactionOutputProofClient v tcid scid h i = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal v +spvGetTransactionOutputProofClient tcid scid h i = runIdentity $ do + SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal tcid return $ spvGetTransactionOutputProofClient_ @v @c scid h i diff --git a/src/Chainweb/SPV/RestAPI/Server.hs b/src/Chainweb/SPV/RestAPI/Server.hs index fa3d0b0d67..f6a46bfb57 100644 --- a/src/Chainweb/SPV/RestAPI/Server.hs +++ b/src/Chainweb/SPV/RestAPI/Server.hs @@ -51,7 +51,8 @@ import Chainweb.MerkleUniverse -- SPV Transaction Output Proof Handler spvGetTransactionOutputProofHandler - :: CutDb + :: HasVersion + => CutDb -> ChainId -- ^ the target chain of the proof. This is the chain for which inclusion -- is proved. @@ -81,7 +82,8 @@ spvGetTransactionOutputProofHandler db tcid scid bh i = -- overkill. -- spvGetEventProofHandler - :: CutDb + :: HasVersion + => CutDb -> ChainId -- ^ the target chain of the proof. This is the chain for which inclusion -- is proved. @@ -136,6 +138,7 @@ spvGetEventProofHandler db tcid scid bh i e = do spvServer :: forall v (c :: ChainIdT) . KnownChainIdSymbol c + => HasVersion => CutDbT v -> Server (SpvApi v c) spvServer (CutDbT db) @@ -151,6 +154,7 @@ spvServer (CutDbT db) spvApp :: forall v c . KnownChainwebVersionSymbol v + => HasVersion => KnownChainIdSymbol c => CutDbT v -> Application @@ -167,6 +171,7 @@ spvApiLayout _ = T.putStrLn $ layout (Proxy @(SpvApi v c)) someSpvServer :: forall c . KnownChainIdSymbol c + => HasVersion => SomeCutDb -> SomeServer someSpvServer (SomeCutDb (db :: CutDbT v)) @@ -176,10 +181,10 @@ someSpvServer (SomeCutDb (db :: CutDbT v)) -- Multichain Server someSpvServers - :: ChainwebVersion - -> CutDb + :: HasVersion + => CutDb -> SomeServer -someSpvServers v db = mconcat $ flip fmap cids $ \(FromSingChainId (SChainId :: Sing c)) -> - someSpvServer @c (someCutDbVal v db) +someSpvServers db = mconcat $ flip fmap cids $ \(FromSingChainId (SChainId :: Sing c)) -> + someSpvServer @c (someCutDbVal db) where - cids = toList $ chainIds db + cids = toList chainIds diff --git a/src/Chainweb/SPV/VerifyProof.hs b/src/Chainweb/SPV/VerifyProof.hs index 70c93c40ae..f42572719e 100644 --- a/src/Chainweb/SPV/VerifyProof.hs +++ b/src/Chainweb/SPV/VerifyProof.hs @@ -23,9 +23,13 @@ import Data.MerkleLog.V1 qualified as V1 -- internal modules import Chainweb.BlockHash +import Chainweb.BlockHeaderDB +import Chainweb.CutDB import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse +import Chainweb.Payload import Chainweb.SPV +import Chainweb.Version -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 67e8a97e28..22eddcfce8 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -113,10 +113,6 @@ setResponseTimeout t env = env newtype WebBlockHeaderCas = WebBlockHeaderCas WebBlockHeaderDb -instance HasChainwebVersion WebBlockHeaderCas where - _chainwebVersion (WebBlockHeaderCas db) = _chainwebVersion db - {-# INLINE _chainwebVersion #-} - -- -------------------------------------------------------------------------- -- -- Obtain and Validate Block Payloads @@ -156,10 +152,6 @@ data WebBlockHeaderStore = WebBlockHeaderStore , _webBlockHeaderStoreMgr :: !HTTP.Manager } -instance HasChainwebVersion WebBlockHeaderStore where - _chainwebVersion = _chainwebVersion . _webBlockHeaderStoreCas - {-# INLINE _chainwebVersion #-} - -- -------------------------------------------------------------------------- -- -- Overlay CAS with asynchronous weak HashMap @@ -184,96 +176,6 @@ memoInsert cas m k a = tableLookup cas k >>= \case return v (Just !x) -> return x --- -- | Query a payload either from the local store, or the origin, or P2P network. --- -- --- -- The payload is only queried and not inserted into the local store. We want to --- -- insert it only after it got validate by pact in order to avoid accumlation of --- -- garbage. --- -- --- getBlockPayload --- :: CanReadablePayloadCas tbl --- => Cas candidateCas PayloadData --- => WebBlockPayloadStore tbl --- -> candidateCas --- -> Priority --- -> Maybe PeerInfo --- -- ^ Peer from with the BlockPayloadHash originated, if available. --- -> BlockHeader --- -- ^ The BlockHeader for which the payload is requested --- -> IO PayloadData --- getBlockPayload s candidateStore priority maybeOrigin h = do --- logfun Debug $ "getBlockPayload: " <> sshow h --- tableLookup candidateStore payloadHash >>= \case --- Just !x -> return x --- Nothing -> lookupPayloadWithHeight cas (Just $ view blockHeight h) payloadHash >>= \case --- Just !x -> return $! payloadWithOutputsToPayloadData x --- Nothing -> memo memoMap payloadHash $ \k -> --- pullOrigin (view blockHeight h) k maybeOrigin >>= \case --- Nothing -> do --- t <- queryPayloadTask (view blockHeight h) k --- pQueueInsert queue t --- awaitTask t --- (Just !x) -> return x --- --- where --- v = _chainwebVersion h --- payloadHash = view blockPayloadHash h --- cid = _chainId h --- --- mgr = _webBlockPayloadStoreMgr s --- cas = _webBlockPayloadStoreCas s --- memoMap = _webBlockPayloadStoreMemo s --- queue = _webBlockPayloadStoreQueue s --- --- logfun :: LogLevel -> T.Text -> IO () --- logfun = _webBlockPayloadStoreLogFunction s --- --- traceLogfun :: LogMessage a => LogLevel -> a -> IO () --- traceLogfun = _webBlockPayloadStoreLogFunction s --- --- taskMsg k msg = "payload task " <> sshow k <> " @ " <> sshow (view blockHash h) <> ": " <> msg --- --- traceLabel subfun = --- "Chainweb.Sync.WebBlockHeaderStore.getBlockPayload." <> subfun --- --- -- | Try to pull a block payload from the given origin peer --- -- --- pullOrigin :: BlockHeight -> BlockPayloadHash -> Maybe PeerInfo -> IO (Maybe PayloadData) --- pullOrigin _ k Nothing = do --- logfun Debug $ taskMsg k "no origin" --- return Nothing --- pullOrigin _ k (Just origin) = do --- let originEnv = setResponseTimeout pullOriginResponseTimeout $ peerInfoClientEnv mgr origin --- logfun Debug $ taskMsg k "lookup origin" --- !r <- trace traceLogfun (traceLabel "pullOrigin") k 0 --- $ runClientM (payloadClient v cid k Nothing) originEnv --- case r of --- (Right !x) -> do --- logfun Debug $ taskMsg k "received from origin" --- return $ Just x --- Left (e :: ClientError) -> do --- logfun Debug $ taskMsg k $ "failed to receive from origin: " <> sshow e --- return Nothing --- --- -- | Query a block payload via the task queue --- -- --- queryPayloadTask :: BlockHeight -> BlockPayloadHash -> IO (Task ClientEnv PayloadData) --- queryPayloadTask bh k = newTask (sshow k) priority $ \logg env -> do --- logg @T.Text Debug $ taskMsg k "query remote block payload" --- let taskEnv = setResponseTimeout taskResponseTimeout env --- !r <- trace traceLogfun (traceLabel "queryPayloadTask") k (let Priority i = priority in i) --- $ runClientM (payloadClient v cid k (Just bh)) taskEnv --- case r of --- (Right !x) -> do --- logg @T.Text Debug $ taskMsg k "received remote block payload" --- return x --- Left (e :: ClientError) -> do --- logg @T.Text Debug $ taskMsg k $ "failed: " <> sshow e --- throwM e - --- -------------------------------------------------------------------------- -- --- Consensus State - -- | For a given latest BlockHeader return the state of consensus -- -- THIS IS NOT FINAL! @@ -297,6 +199,7 @@ memoInsert cas m k a = tableLookup cas k >>= \case -- consensusState :: HasCallStack + => HasVersion => WebBlockHeaderDb -> BlockHeader -> IO ConsensusState @@ -310,11 +213,11 @@ consensusState wdb hdr = do , _consensusStateFinal = syncStateOfBlockHeader finalHdr } where - WindowWidth w = _versionWindow (_chainwebVersion hdr) + WindowWidth w = _versionWindow implicitVersion finalHeight = int @Int @_ $ max 0 (int height - int w * 4) safeHeight = int @Int @_ $ max 0 (int height - 6 * int diam) height = view blockHeight hdr - diam = diameterAt hdr height + diam = diameterAt height -- -------------------------------------------------------------------------- -- -- ForkInfo For Header @@ -322,7 +225,8 @@ consensusState wdb hdr = do -- | Compute ForkInfo Object for a single newly added BlockHeader -- forkInfoForHeader - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> BlockHeader -> Maybe EncodedPayloadData -> IO ForkInfo @@ -357,11 +261,10 @@ forkInfoForHeader wdb hdr pldData pld = view blockPayloadHash hdr nbctx = NewBlockCtx - { _newBlockCtxMinerReward = blockMinerReward v (height + 1) + { _newBlockCtxMinerReward = blockMinerReward (height + 1) , _newBlockCtxParentCreationTime = Parent $ view blockCreationTime hdr } height = view blockHeight hdr - v = _chainwebVersion hdr -- -------------------------------------------------------------------------- -- -- Obtain, Validate, and Store BlockHeaders @@ -381,7 +284,8 @@ instance Exception GetBlockHeaderFailure -- iterative algorithm is preferable. -- getBlockHeaderInternal - :: BlockHeaderCas candidateHeaderCas + :: HasVersion + => BlockHeaderCas candidateHeaderCas -- ^ CandidateHeaderCas is a content addressed store for BlockHeaders => ReadableTable candidatePldTbl BlockPayloadHash EncodedPayloadData => WebBlockHeaderStore @@ -451,7 +355,7 @@ getBlockHeaderInternal -- prerequesite in the memo-table it is awaited, otherwise a new job is -- created. -- - let isGenesisParentHash p = _chainValueValue p == genesisParentBlockHash v p + let isGenesisParentHash p = _chainValueValue p == genesisParentBlockHash p queryAdjacentParent p = Concurrently $ unless (isGenesisParentHash p) $ void $ do logg Debug $ taskMsg k $ "getBlockHeaderInternal.getPrerequisiteHeader (adjacent) for " <> sshow h @@ -579,7 +483,6 @@ getBlockHeaderInternal cas = WebBlockHeaderCas $ _webBlockHeaderStoreCas headerStore memoMap = _webBlockHeaderStoreMemo headerStore queue = _webBlockHeaderStoreQueue headerStore - v = _chainwebVersion cas wdb = _webBlockHeaderStoreCas headerStore logfun :: LogFunction @@ -626,7 +529,6 @@ getBlockHeaderInternal rDb cid env = RemoteDb { _remoteEnv = env , _remoteLogFunction = ALogFunction logfun - , _remoteVersion = v , _remoteChainId = cid } @@ -682,7 +584,8 @@ newWebPayloadStore mgr payloadDb logfun = do payloadDb payloadMemo payloadTaskQueue logfun mgr getBlockHeader - :: BlockHeaderCas candidateHeaderCas + :: HasVersion + => BlockHeaderCas candidateHeaderCas => ReadableTable candidatePldTbl BlockPayloadHash EncodedPayloadData => WebBlockHeaderStore -> candidateHeaderCas @@ -709,7 +612,7 @@ getBlockHeader headerStore candidateHeaderCas candidatePldTbl providers localPay (ChainValue cid h) {-# INLINE getBlockHeader #-} -instance (CasKeyType (ChainValue BlockHeader) ~ k) => ReadableTable WebBlockHeaderCas k (ChainValue BlockHeader) where +instance (HasVersion, CasKeyType (ChainValue BlockHeader) ~ k) => ReadableTable WebBlockHeaderCas k (ChainValue BlockHeader) where tableLookup (WebBlockHeaderCas db) (ChainValue cid h) = (Just . ChainValue cid <$> lookupWebBlockHeaderDb db cid h) `catch` \e -> case e of @@ -717,7 +620,7 @@ instance (CasKeyType (ChainValue BlockHeader) ~ k) => ReadableTable WebBlockHead _ -> throwM @_ @(TDB.TreeDbException BlockHeaderDb) e {-# INLINE tableLookup #-} -instance (CasKeyType (ChainValue BlockHeader) ~ k) => Table WebBlockHeaderCas k (ChainValue BlockHeader) where +instance (HasVersion, CasKeyType (ChainValue BlockHeader) ~ k) => Table WebBlockHeaderCas k (ChainValue BlockHeader) where tableInsert (WebBlockHeaderCas db) _ (ChainValue _ h) = insertWebBlockHeaderDb db h {-# INLINE tableInsert #-} diff --git a/src/Chainweb/VerifierPlugin.hs b/src/Chainweb/VerifierPlugin.hs index 6f0623f472..a5a53dcd6e 100644 --- a/src/Chainweb/VerifierPlugin.hs +++ b/src/Chainweb/VerifierPlugin.hs @@ -51,7 +51,8 @@ newtype VerifierPlugin = VerifierPlugin { runVerifierPlugin :: forall s - . (ChainwebVersion, ChainId, BlockHeight) + . HasVersion + => (ChainId, BlockHeight) -> PactValue -> Set SigCapability -> STRef s Gas @@ -71,8 +72,8 @@ chargeGas r g = do lift $ writeSTRef r (Gas $ _gas gasRemaining - _gas g) runVerifierPlugins - :: Logger logger - => (ChainwebVersion, ChainId, BlockHeight) + :: (Logger logger, HasVersion) + => (ChainId, BlockHeight) -> logger -> Map VerifierName VerifierPlugin -> Gas diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs index f5a6540636..014d91cffa 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs @@ -19,5 +19,5 @@ import Chainweb.VerifierPlugin import qualified Chainweb.VerifierPlugin.Hyperlane.Message.After225 as After225 plugin :: VerifierPlugin -plugin = VerifierPlugin $ \(v, cid, bh) proof caps gasRef -> +plugin = VerifierPlugin $ \(cid, bh) proof caps gasRef -> After225.runPlugin proof caps gasRef diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 3c07bd31ac..f0a110e593 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -104,7 +104,8 @@ module Chainweb.Version , Sing(..) -- * HasChainwebVersion - , HasChainwebVersion(..) + , HasVersion(..) + , withVersion , mkChainId , chainIds @@ -190,6 +191,7 @@ import Chainweb.Utils.Serialization import Data.Singletons import P2P.Peer +import GHC.Exts (WithDict(..)) -- -------------------------------------------------------------------------- -- -- Forks @@ -511,6 +513,12 @@ data ChainwebVersion deriving stock (Generic) deriving anyclass NFData +class HasVersion where + implicitVersion :: ChainwebVersion + +withVersion :: ChainwebVersion -> (HasVersion => a) -> a +withVersion = withDict @HasVersion + instance Show ChainwebVersion where show = show . _versionName @@ -586,11 +594,8 @@ makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionCheats makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionDefaults makeLensesWith (lensRules & generateLazyPatterns .~ True) 'VersionQuirks -genesisBlockPayloadHash :: ChainwebVersion -> ChainId -> BlockPayloadHash -genesisBlockPayloadHash v cid = v - ^?! versionGenesis - . genesisBlockPayload - . atChain cid +genesisBlockPayloadHash :: HasVersion => ChainId -> BlockPayloadHash +genesisBlockPayloadHash cid = implicitVersion ^?! versionGenesis . genesisBlockPayload . atChain cid -------------------------------------------------------------------------- -- -- Type level ChainwebVersion @@ -608,8 +613,8 @@ instance (KnownSymbol n) => KnownChainwebVersionSymbol ('ChainwebVersionT n) whe type ChainwebVersionSymbol ('ChainwebVersionT n) = n chainwebVersionSymbolVal _ = T.pack $ symbolVal (Proxy @n) -someChainwebVersionVal :: HasChainwebVersion v => v -> SomeChainwebVersionT -someChainwebVersionVal v = someChainwebVersionVal' (_versionName (_chainwebVersion v)) +someChainwebVersionVal :: HasVersion => SomeChainwebVersionT +someChainwebVersionVal = someChainwebVersionVal' (_versionName implicitVersion) someChainwebVersionVal' :: ChainwebVersionName -> SomeChainwebVersionT someChainwebVersionVal' v = case someSymbolVal (show v) of @@ -642,28 +647,16 @@ pattern FromSingChainwebVersion sng <- (\v -> withSomeSing (_versionName v) Some -------------------------------------------------------------------------- -- -- HasChainwebVersion Class -class HasChainwebVersion a where - _chainwebVersion :: a -> ChainwebVersion - _chainwebVersion = view chainwebVersion - - chainwebVersion :: Getter a ChainwebVersion - chainwebVersion = to _chainwebVersion - - {-# MINIMAL _chainwebVersion | chainwebVersion #-} - -instance HasChainwebVersion ChainwebVersion where - _chainwebVersion = id - -- | All known chainIds. This includes chains that are not yet "active". -- -chainIds :: HasChainwebVersion v => v -> HS.HashSet ChainId -chainIds = graphChainIds . snd . ruleHead . _versionGraphs . _chainwebVersion +chainIds :: HasVersion => HS.HashSet ChainId +chainIds = graphChainIds $ snd $ ruleHead $ _versionGraphs implicitVersion mkChainId - :: (MonadThrow m, HasChainwebVersion v) - => v -> BlockHeight -> Word32 -> m ChainId -mkChainId v h i = cid - <$ checkWebChainId (chainGraphAt (_chainwebVersion v) h) cid + :: (MonadThrow m, HasVersion) + => BlockHeight -> Word32 -> m ChainId +mkChainId h i = cid + <$ checkWebChainId (chainGraphAt h) cid where cid = unsafeChainId i @@ -682,16 +675,15 @@ class HasPayloadProviderType p where {-# MINIMAL _payloadProviderType | payloadProviderType #-} payloadProviderTypeForChain - :: HasChainwebVersion v + :: HasVersion => HasChainId c - => v - -> c + => c -> PayloadProviderType -payloadProviderTypeForChain v c = v - ^?! chainwebVersion . versionPayloadProviderTypes . atChain c +payloadProviderTypeForChain c = implicitVersion + ^?! versionPayloadProviderTypes . atChain c -instance (HasChainwebVersion p, HasChainId p) => HasPayloadProviderType p where - _payloadProviderType p = payloadProviderTypeForChain p p +instance (HasVersion, HasChainId p) => HasPayloadProviderType p where + _payloadProviderType p = payloadProviderTypeForChain p {-# INLINE _payloadProviderType #-} -------------------------------------------------------------------------- -- @@ -706,18 +698,15 @@ instance (HasChainwebVersion p, HasChainId p) => HasPayloadProviderType p where -- This function is safe because of invariants provided by 'chainwebGraphs'. -- (There are also unit tests the confirm this.) chainwebGraphsAt - :: ChainwebVersion - -> BlockHeight + :: HasVersion + => BlockHeight -> Rule BlockHeight ChainGraph -chainwebGraphsAt v h = - ruleDropWhile (> h) (_versionGraphs v) +chainwebGraphsAt h = + ruleDropWhile (> h) (_versionGraphs implicitVersion) -- | The 'ChainGraph' for the given 'BlockHeight' -chainGraphAt :: HasChainwebVersion v => v -> BlockHeight -> ChainGraph -chainGraphAt v = snd . ruleHead . chainwebGraphsAt (_chainwebVersion v) - -instance HasChainGraph (ChainwebVersion, BlockHeight) where - _chainGraph = uncurry chainGraphAt +chainGraphAt :: HasVersion => BlockHeight -> ChainGraph +chainGraphAt = snd . ruleHead . chainwebGraphsAt -- | The genesis block height for a given chain. -- @@ -725,8 +714,8 @@ instance HasChainGraph (ChainwebVersion, BlockHeight) where -- (We generally assume that this invariant holds throughout the code base. -- It is enforced via the 'mkChainId' smart constructor for ChainId.) -- -genesisBlockHeight :: HasCallStack => ChainwebVersion -> ChainId -> BlockHeight -genesisBlockHeight v c = fst $ genesisHeightAndGraph v c +genesisBlockHeight :: (HasVersion, HasCallStack) => ChainId -> BlockHeight +genesisBlockHeight = fst . genesisHeightAndGraph {-# inlinable genesisBlockHeight #-} -- | The genesis graph for a given Chain @@ -739,13 +728,12 @@ genesisBlockHeight v c = fst $ genesisHeightAndGraph v c -- genesisHeightAndGraph :: HasCallStack - => HasChainwebVersion v + => HasVersion => HasChainId c - => v - -> c + => c -> (BlockHeight, ChainGraph) -genesisHeightAndGraph v c = - case ruleSeek (\_ g -> not (isWebChain g c)) (_versionGraphs (_chainwebVersion v)) of +genesisHeightAndGraph c = + case ruleSeek (\_ g -> not (isWebChain g c)) (_versionGraphs implicitVersion) of -- the chain was in every graph down to the bottom, -- so the bottom has the genesis graph (False, z) -> ruleZipperHere z @@ -758,7 +746,7 @@ genesisHeightAndGraph v c = where missingChainError = error $ "Invalid ChainId " <> show (_chainId c) - <> " for chainweb version " <> show (_versionName (_chainwebVersion v)) + <> " for chainweb version " <> show (_versionName implicitVersion) {-# inlinable genesisHeightAndGraph #-} -------------------------------------------------------------------------- -- @@ -770,10 +758,10 @@ domainAddr2PeerInfo = fmap (PeerInfo Nothing) -- | A utility to allow indexing behavior by forks, returning that behavior -- indexed by the block heights of those forks. indexByForkHeights - :: ChainwebVersion - -> [(Fork, ChainMap a)] + :: HasVersion + => [(Fork, ChainMap a)] -> ChainMap (HashMap BlockHeight a) -indexByForkHeights v = ChainMap . foldl' go (HM.empty <$ HS.toMap (chainIds v)) +indexByForkHeights = ChainMap . foldl' go (HM.empty <$ HS.toMap chainIds) where conflictError fork h = error $ "conflicting behavior at block height " <> show h <> " when adding behavior for fork " <> show fork @@ -786,14 +774,14 @@ indexByForkHeights v = ChainMap . foldl' go (HM.empty <$ HS.toMap (chainIds v)) [ (cid, HM.singleton forkHeight upg) | cid <- HM.keys acc , Just upg <- [txsPerChain ^? atChain cid] - , ForkAtBlockHeight forkHeight <- [v ^?! versionForks . at fork . _Just . atChain cid] + , ForkAtBlockHeight forkHeight <- [implicitVersion ^?! versionForks . at fork . _Just . atChain cid] , forkHeight /= maxBound ] -- | The block height at all chains at which the latest known behavior changes -- will have taken effect: forks, upgrade transactions, or graph changes. -latestBehaviorAt :: ChainwebVersion -> BlockHeight -latestBehaviorAt v = foldlOf' behaviorChanges max 0 v + 1 +latestBehaviorAt :: HasVersion => BlockHeight +latestBehaviorAt = foldlOf' behaviorChanges max 0 implicitVersion + 1 where behaviorChanges = fold [ versionForks . folded . folded . _ForkAtBlockHeight @@ -801,21 +789,21 @@ latestBehaviorAt v = foldlOf' behaviorChanges max 0 v + 1 , versionGraphs . to ruleHead . _1 ] -onAllChains :: ChainwebVersion -> a -> ChainMap a -onAllChains v a = tabulateChains v (\_ -> a) +onAllChains :: HasVersion => a -> ChainMap a +onAllChains a = tabulateChains (\_ -> a) -tabulateChains :: ChainwebVersion -> (ChainId -> a) -> ChainMap a -tabulateChains v f = runIdentity $ tabulateChainsM v (Identity . f) +tabulateChains :: HasVersion => (ChainId -> a) -> ChainMap a +tabulateChains f = runIdentity $ tabulateChainsM (Identity . f) -- | Easy construction of a `ChainMap` with entries for every chain -- in a `ChainwebVersion`. -tabulateChainsM :: Applicative m => ChainwebVersion -> (ChainId -> m a) -> m (ChainMap a) -tabulateChainsM v f = ChainMap <$> +tabulateChainsM :: (HasVersion, Applicative m) => (ChainId -> m a) -> m (ChainMap a) +tabulateChainsM f = ChainMap <$> HM.traverseWithKey (\cid () -> f cid) - (HS.toMap (chainIds v)) + (HS.toMap chainIds) -noQuirks :: ChainwebVersion -> VersionQuirks -noQuirks v = VersionQuirks - { _quirkGasFees = onAllChains v HM.empty +noQuirks :: HasVersion => VersionQuirks +noQuirks = VersionQuirks + { _quirkGasFees = onAllChains HM.empty } diff --git a/src/Chainweb/Version/Development.hs b/src/Chainweb/Version/Development.hs index 854e920b5f..69ff8e4d3c 100644 --- a/src/Chainweb/Version/Development.hs +++ b/src/Chainweb/Version/Development.hs @@ -25,20 +25,20 @@ pattern Development <- ((== devnet) -> True) where Development = devnet devnet :: ChainwebVersion -devnet = ChainwebVersion +devnet = withVersion devnet $ ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000002 , _versionName = ChainwebVersionName "development" , _versionForks = tabulateHashMap $ - \_ -> onAllChains devnet ForkAtGenesis - , _versionUpgrades = onAllChains devnet mempty + \_ -> onAllChains ForkAtGenesis + , _versionUpgrades = onAllChains mempty , _versionGraphs = Bottom (minBound, twentyChainGraph) , _versionBlockDelay = BlockDelay 30_000_000 , _versionWindow = WindowWidth 120 , _versionHeaderBaseSizeBytes = 318 - 110 , _versionBootstraps = [] , _versionGenesis = VersionGenesis - { _genesisBlockTarget = onAllChains devnet $ HashTarget (maxBound `div` 100_000) - , _genesisTime = onAllChains devnet $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] + { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 100_000) + , _genesisTime = onAllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] , _genesisBlockPayload = onChains [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") , (unsafeChainId 1, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") @@ -75,9 +75,9 @@ devnet = ChainwebVersion { _disablePeerValidation = True , _disableMempoolSync = False } - , _versionVerifierPluginNames = onAllChains devnet $ Bottom + , _versionVerifierPluginNames = onAllChains $ Bottom (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) - , _versionQuirks = noQuirks devnet + , _versionQuirks = noQuirks , _versionServiceDate = Nothing - , _versionPayloadProviderTypes = onAllChains devnet PactProvider + , _versionPayloadProviderTypes = onAllChains PactProvider } diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 56b743e8df..0e462c0320 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -52,22 +52,22 @@ pattern EvmDevelopment <- ((== evmDevnet) -> True) where -- TODO (use ea?) evmDevnet :: ChainwebVersion -evmDevnet = ChainwebVersion +evmDevnet = withVersion evmDevnet $ ChainwebVersion { _versionCode = ChainwebVersionCode 0x0000_000a , _versionName = ChainwebVersionName "evm-development" , _versionForks = tabulateHashMap $ \case -- TODO: for now, Pact 5 is never enabled on EVM devnet. -- this will change as it stabilizes. - Pact5Fork -> onAllChains evmDevnet ForkNever - _ -> onAllChains evmDevnet ForkAtGenesis - , _versionUpgrades = onAllChains evmDevnet mempty + Pact5Fork -> onAllChains ForkNever + _ -> onAllChains ForkAtGenesis + , _versionUpgrades = onAllChains mempty , _versionGraphs = Bottom (minBound, d4k4ChainGraph) , _versionBlockDelay = BlockDelay 30_000_000 , _versionWindow = WindowWidth 120 , _versionHeaderBaseSizeBytes = 318 - 110 , _versionBootstraps = [] , _versionGenesis = VersionGenesis - { _genesisBlockTarget = onAllChains evmDevnet $ HashTarget (maxBound `div` 500_000) + { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 500_000) , _genesisTime = onChains $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0..19] ] <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..39] ] @@ -189,9 +189,9 @@ evmDevnet = ChainwebVersion { _disablePeerValidation = True , _disableMempoolSync = False } - , _versionVerifierPluginNames = onAllChains evmDevnet $ Bottom + , _versionVerifierPluginNames = onAllChains $ Bottom (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) - , _versionQuirks = noQuirks evmDevnet + , _versionQuirks = noQuirks , _versionServiceDate = Nothing -- FIXME make this safe for graph changes diff --git a/src/Chainweb/Version/Guards.hs b/src/Chainweb/Version/Guards.hs index 441f2ec3fb..1b948ceeaf 100644 --- a/src/Chainweb/Version/Guards.hs +++ b/src/Chainweb/Version/Guards.hs @@ -78,13 +78,14 @@ import Pact.Core.Serialise qualified as Pact import Pact.Types.KeySet (PublicKeyText, ed25519HexFormat, webAuthnFormat) import Pact.Types.Scheme (PPKScheme(ED25519, WebAuthn)) -getForkHeight :: Fork -> ChainwebVersion -> ChainId -> ForkHeight -getForkHeight fork v cid = v ^?! versionForks . at fork . _Just . atChain cid +getForkHeight :: HasVersion => Fork -> ChainId -> ForkHeight +getForkHeight fork cid = implicitVersion ^?! versionForks . at fork . _Just . atChain cid checkFork - :: (BlockHeight -> ForkHeight -> Bool) - -> Fork -> ChainwebVersion -> ChainId -> BlockHeight -> Bool -checkFork p f v cid h = p h (getForkHeight f v cid) + :: HasVersion + => (BlockHeight -> ForkHeight -> Bool) + -> Fork -> ChainId -> BlockHeight -> Bool +checkFork p f cid h = p h (getForkHeight f cid) after :: BlockHeight -> ForkHeight -> Bool after bh (ForkAtBlockHeight bh') = bh > bh' @@ -141,8 +142,8 @@ before _ ForkNever = True -- is usually more important than throughput. -- slowEpochGuard - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BlockHeight -- ^ BlockHeight of parent Header -> Bool @@ -155,7 +156,7 @@ slowEpochGuard = checkFork before SlowEpoch -- blocks. The target computation won't compensate for that, since the effects -- are marginal. -- -oldTargetGuard :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +oldTargetGuard :: HasVersion => ChainId -> BlockHeight -> Bool oldTargetGuard = checkFork before OldTargetGuard -- | Skip validation of feature flags. @@ -165,20 +166,20 @@ oldTargetGuard = checkFork before OldTargetGuard -- live chains, enforcing the following condition must be ignored for the -- historical blocks for which both the Nonce and Flags could be anything. -- -skipFeatureFlagValidationGuard :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +skipFeatureFlagValidationGuard :: HasVersion => ChainId -> BlockHeight -> Bool skipFeatureFlagValidationGuard = checkFork before SkipFeatureFlagValidation -oldDaGuard :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +oldDaGuard :: HasVersion => ChainId -> BlockHeight -> Bool oldDaGuard = checkFork before OldDAGuard ----------------- -- Payload validation guards -vuln797Fix :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +vuln797Fix :: HasVersion => ChainId -> BlockHeight -> Bool vuln797Fix = checkFork atOrAfter Vuln797Fix -- | Preserve Pact bugs pre-1.6 chainweb. -pactBackCompat_v16 :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +pactBackCompat_v16 :: HasVersion => ChainId -> BlockHeight -> Bool pactBackCompat_v16 = checkFork before PactBackCompat_v16 -- | Early versions of chainweb used the creation time of the current header @@ -187,119 +188,119 @@ pactBackCompat_v16 = checkFork before PactBackCompat_v16 -- -- When this guard is enabled timing validation is skipped. -- -skipTxTimingValidation :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +skipTxTimingValidation :: HasVersion => ChainId -> BlockHeight -> Bool skipTxTimingValidation = checkFork before SkipTxTimingValidation -- | Checks height after which module name fix in effect. -- -enableModuleNameFix :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +enableModuleNameFix :: HasVersion => ChainId -> BlockHeight -> Bool enableModuleNameFix = checkFork atOrAfter ModuleNameFix -- | Related, later fix (Pact #801). -- -enableModuleNameFix2 :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +enableModuleNameFix2 :: HasVersion => ChainId -> BlockHeight -> Bool enableModuleNameFix2 = checkFork atOrAfter ModuleNameFix2 -- | Turn on pact events in command output. -enablePactEvents :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +enablePactEvents :: HasVersion => ChainId -> BlockHeight -> Bool enablePactEvents = checkFork atOrAfter PactEvents -- | Bridge support: ETH and event SPV. -enableSPVBridge :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +enableSPVBridge :: HasVersion => ChainId -> BlockHeight -> Bool enableSPVBridge = checkFork atOrAfter SPVBridge -enforceKeysetFormats :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +enforceKeysetFormats :: HasVersion => ChainId -> BlockHeight -> Bool enforceKeysetFormats = checkFork atOrAfter EnforceKeysetFormats -doCheckTxHash :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +doCheckTxHash :: HasVersion => ChainId -> BlockHeight -> Bool doCheckTxHash = checkFork atOrAfter CheckTxHash -- | Fork for musl trans funs -pact44NewTrans :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +pact44NewTrans :: HasVersion => ChainId -> BlockHeight -> Bool pact44NewTrans = checkFork atOrAfter Pact44NewTrans -pact4Coin3 :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +pact4Coin3 :: HasVersion => ChainId -> BlockHeight -> Bool pact4Coin3 = checkFork after Pact4Coin3 -pact42 :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +pact42 :: HasVersion => ChainId -> BlockHeight -> Bool pact42 = checkFork atOrAfter Pact42 -pact5 :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +pact5 :: HasVersion => ChainId -> BlockHeight -> Bool pact5 = checkFork atOrAfter Pact5Fork -chainweb213Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb213Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb213Pact = checkFork atOrAfter Chainweb213Pact -chainweb214Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb214Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb214Pact = checkFork after Chainweb214Pact -chainweb215Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb215Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb215Pact = checkFork after Chainweb215Pact -chainweb216Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb216Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb216Pact = checkFork after Chainweb216Pact -chainweb217Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb217Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb217Pact = checkFork after Chainweb217Pact -chainweb218Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb218Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb218Pact = checkFork atOrAfter Chainweb218Pact -chainweb219Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb219Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb219Pact = checkFork atOrAfter Chainweb219Pact -chainweb220Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb220Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb220Pact = checkFork atOrAfter Chainweb220Pact -chainweb221Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb221Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb221Pact = checkFork atOrAfter Chainweb221Pact -chainweb222Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb222Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb222Pact = checkFork atOrAfter Chainweb222Pact -chainweb223Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb223Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb223Pact = checkFork atOrAfter Chainweb223Pact -chainweb224Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb224Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb224Pact = checkFork atOrAfter Chainweb224Pact -chainweb225Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb225Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb225Pact = checkFork atOrAfter Chainweb225Pact -chainweb226Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb226Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb226Pact = checkFork atOrAfter Chainweb226Pact -chainweb228Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb228Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb228Pact = checkFork atOrAfter Chainweb228Pact -chainweb229Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb229Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb229Pact = checkFork atOrAfter Chainweb229Pact -chainweb230Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb230Pact :: HasVersion => ChainId -> BlockHeight -> Bool chainweb230Pact = checkFork atOrAfter Chainweb230Pact -pact5Serialiser :: ChainwebVersion -> ChainId -> BlockHeight -> Pact.PactSerialise Pact.CoreBuiltin Pact.LineInfo -pact5Serialiser v cid bh - | chainweb228Pact v cid bh = Pact.serialisePact_lineinfo_pact51 - | otherwise = Pact.serialisePact_lineinfo_pact50 +pact5Serialiser :: HasVersion => ChainId -> BlockHeight -> Pact.PactSerialise Pact.CoreBuiltin Pact.LineInfo +pact5Serialiser cid bh + | chainweb228Pact cid bh = Pact.serialisePact_lineinfo_pact51 + | otherwise = Pact.serialisePact_lineinfo_pact50 -maxBlockGasLimit :: ChainwebVersion -> BlockHeight -> Maybe Natural -maxBlockGasLimit v bh = snd $ ruleZipperHere $ snd - $ ruleSeek (\h _ -> bh >= h) (_versionMaxBlockGasLimit v) +maxBlockGasLimit :: HasVersion => BlockHeight -> Maybe Natural +maxBlockGasLimit bh = snd $ ruleZipperHere $ snd + $ ruleSeek (\h _ -> bh >= h) (_versionMaxBlockGasLimit implicitVersion) -hashedAdjacentRecord :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +hashedAdjacentRecord :: HasVersion => ChainId -> BlockHeight -> Bool hashedAdjacentRecord = checkFork atOrAfter HashedAdjacentRecord -- | Different versions of Chainweb allow different PPKSchemes. -- -validPPKSchemes :: ChainwebVersion -> ChainId -> BlockHeight -> [PPKScheme] -validPPKSchemes v cid bh = - if chainweb221Pact v cid bh +validPPKSchemes :: HasVersion => ChainId -> BlockHeight -> [PPKScheme] +validPPKSchemes cid bh = + if chainweb221Pact cid bh then [ED25519, WebAuthn] else [ED25519] -validKeyFormats :: ChainwebVersion -> ChainId -> BlockHeight -> [PublicKeyText -> Bool] -validKeyFormats v cid bh = - if chainweb222Pact v cid bh +validKeyFormats :: HasVersion => ChainId -> BlockHeight -> [PublicKeyText -> Bool] +validKeyFormats cid bh = + if chainweb222Pact cid bh then [ed25519HexFormat, webAuthnFormat] else [ed25519HexFormat] diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index 41e0222225..26ea9fb3f4 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -67,11 +67,11 @@ pattern Mainnet01 <- ((== mainnet) -> True) where Mainnet01 = mainnet mainnet :: ChainwebVersion -mainnet = ChainwebVersion +mainnet = withVersion mainnet $ ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000005 , _versionName = ChainwebVersionName "mainnet01" , _versionForks = tabulateHashMap $ \case - SlowEpoch -> onAllChains mainnet (ForkAtBlockHeight $ BlockHeight 80_000) + SlowEpoch -> onAllChains (ForkAtBlockHeight $ BlockHeight 80_000) Vuln797Fix -> onChains $ [ (unsafeChainId 0, ForkAtBlockHeight $ BlockHeight 121_452) -- 2019-12-10T21:00:00.0 , (unsafeChainId 1, ForkAtBlockHeight $ BlockHeight 121_452) @@ -96,39 +96,39 @@ mainnet = ChainwebVersion , (unsafeChainId 8, ForkAtBlockHeight $ BlockHeight 140_808) , (unsafeChainId 9, ForkAtBlockHeight $ BlockHeight 140_808) ] <> [(unsafeChainId i, ForkAtGenesis) | i <- [10..19]] - PactBackCompat_v16 -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 328_000 - ModuleNameFix -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 448_501 - SkipTxTimingValidation -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 449_940 - OldTargetGuard -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 452_820 -- ~ 2020-04-04T00:00:00Z - SkipFeatureFlagValidation -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 530_500 -- ~ 2020-05-01T00:00:xxZ - ModuleNameFix2 -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 752_214 - OldDAGuard -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 771_414 -- ~ 2020-07-23 16:00:00 - PactEvents -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 1_138_000 - SPVBridge -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 1_275_000 - Pact4Coin3 -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 1_722_500 -- 2021-06-19T03:34:05+00:00 - EnforceKeysetFormats -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_162_000 -- 2022-01-17T17:51:12 - Pact42 -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_334_500 -- 2022-01-17T17:51:12+00:00 - CheckTxHash -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_349_800 -- 2022-01-23T02:53:38 - Chainweb213Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_447_315 -- 2022-02-26T00:00:00+00:00 - Chainweb214Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_605_663 -- 2022-04-22T00:00:00+00:00 - Chainweb215Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_766_630 -- 2022-06-17T00:00:00+00:00 - Pact44NewTrans -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_939_323 -- Todo: add date - Chainweb216Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 2_988_324 -- 2022-09-02T00:00:00+00:00 - Chainweb217Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 3_250_348 -- 2022-12-02T00:00:00+00:00 - Chainweb218Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 3_512_363 -- 2023-03-03 00:00:00+00:00 - Chainweb219Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 3_774_423 -- 2023-06-02 00:00:00+00:00 - Chainweb220Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_056_499 -- 2023-09-08 00:00:00+00:00 - Chainweb221Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_177_889 -- 2023-10-20 00:00:00+00:00 - Chainweb222Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_335_753 -- 2023-12-14 00:00:00+00:00 - Chainweb223Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_577_530 -- 2024-03-07 00:00:00+00:00 - Chainweb224Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 4_819_246 -- 2024-05-30 00:00:00+00:00 - Chainweb225Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_060_924 -- 2024-08-22 00:00:00+00:00 - Chainweb226Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_302_559 -- 2024-11-14 00:00:00+00:00 - Pact5Fork -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_555_698 -- 2025-02-10 00:00:00+00:00 - Chainweb228Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_659_280 -- 2025-03-18 00:00:00+00:00 - Chainweb229Pact -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_785_923 -- 2025-05-01 00:00:00+00:00 - HashedAdjacentRecord -> onAllChains mainnet $ ForkAtBlockHeight $ BlockHeight 5_785_923 -- 2025-05-01 00:00:00+00:00 - Chainweb230Pact -> onAllChains mainnet ForkNever + PactBackCompat_v16 -> onAllChains $ ForkAtBlockHeight $ BlockHeight 328_000 + ModuleNameFix -> onAllChains $ ForkAtBlockHeight $ BlockHeight 448_501 + SkipTxTimingValidation -> onAllChains $ ForkAtBlockHeight $ BlockHeight 449_940 + OldTargetGuard -> onAllChains $ ForkAtBlockHeight $ BlockHeight 452_820 -- ~ 2020-04-04T00:00:00Z + SkipFeatureFlagValidation -> onAllChains $ ForkAtBlockHeight $ BlockHeight 530_500 -- ~ 2020-05-01T00:00:xxZ + ModuleNameFix2 -> onAllChains $ ForkAtBlockHeight $ BlockHeight 752_214 + OldDAGuard -> onAllChains $ ForkAtBlockHeight $ BlockHeight 771_414 -- ~ 2020-07-23 16:00:00 + PactEvents -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1_138_000 + SPVBridge -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1_275_000 + Pact4Coin3 -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1_722_500 -- 2021-06-19T03:34:05+00:00 + EnforceKeysetFormats -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_162_000 -- 2022-01-17T17:51:12 + Pact42 -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_334_500 -- 2022-01-17T17:51:12+00:00 + CheckTxHash -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_349_800 -- 2022-01-23T02:53:38 + Chainweb213Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_447_315 -- 2022-02-26T00:00:00+00:00 + Chainweb214Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_605_663 -- 2022-04-22T00:00:00+00:00 + Chainweb215Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_766_630 -- 2022-06-17T00:00:00+00:00 + Pact44NewTrans -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_939_323 -- Todo: add date + Chainweb216Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_988_324 -- 2022-09-02T00:00:00+00:00 + Chainweb217Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 3_250_348 -- 2022-12-02T00:00:00+00:00 + Chainweb218Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 3_512_363 -- 2023-03-03 00:00:00+00:00 + Chainweb219Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 3_774_423 -- 2023-06-02 00:00:00+00:00 + Chainweb220Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_056_499 -- 2023-09-08 00:00:00+00:00 + Chainweb221Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_177_889 -- 2023-10-20 00:00:00+00:00 + Chainweb222Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_335_753 -- 2023-12-14 00:00:00+00:00 + Chainweb223Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_577_530 -- 2024-03-07 00:00:00+00:00 + Chainweb224Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_819_246 -- 2024-05-30 00:00:00+00:00 + Chainweb225Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_060_924 -- 2024-08-22 00:00:00+00:00 + Chainweb226Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_302_559 -- 2024-11-14 00:00:00+00:00 + Pact5Fork -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_555_698 -- 2025-02-10 00:00:00+00:00 + Chainweb228Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_659_280 -- 2025-03-18 00:00:00+00:00 + Chainweb229Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_785_923 -- 2025-05-01 00:00:00+00:00 + HashedAdjacentRecord -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_785_923 -- 2025-05-01 00:00:00+00:00 + Chainweb230Pact -> onAllChains ForkNever , _versionGraphs = (to20ChainsMainnet, twentyChainGraph) `Above` Bottom (minBound, petersenChainGraph) @@ -144,7 +144,7 @@ mainnet = ChainwebVersion [ [(unsafeChainId i, maxTarget) | i <- [0..9]] , [(unsafeChainId i, mainnet20InitialHashTarget) | i <- [10..19]] ] - , _genesisTime = onAllChains mainnet $ BlockCreationTime [timeMicrosQQ| 2019-10-30T00:01:00.0 |] + , _genesisTime = onAllChains $ BlockCreationTime [timeMicrosQQ| 2019-10-30T00:01:00.0 |] , _genesisBlockPayload = onChains [ ( unsafeChainId 0, unsafeFromText "k1H3DsInAPvJ0W_zPxnrpkeSNdPUT0S9U8bqDLG739o") , ( unsafeChainId 1, unsafeFromText "kClp_Tw7keCLXMfaCyjH-gToAGmLvRQqiNRmhWUCbxs") @@ -178,7 +178,7 @@ mainnet = ChainwebVersion { _disablePeerValidation = False , _disableMempoolSync = False } - , _versionVerifierPluginNames = onAllChains mainnet $ + , _versionVerifierPluginNames = onAllChains $ (4_577_530, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) `Above` Bottom (minBound, mempty) , _versionQuirks = VersionQuirks @@ -188,5 +188,5 @@ mainnet = ChainwebVersion ] } , _versionServiceDate = Just "2025-07-23T00:00:00Z" - , _versionPayloadProviderTypes = onAllChains mainnet PactProvider + , _versionPayloadProviderTypes = onAllChains PactProvider } diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index 44db09224f..eaab20cbdc 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -29,47 +29,47 @@ pattern RecapDevelopment <- ((== recapDevnet) -> True) where RecapDevelopment = recapDevnet recapDevnet :: ChainwebVersion -recapDevnet = ChainwebVersion +recapDevnet = withVersion recapDevnet ChainwebVersion { _versionCode = ChainwebVersionCode 0x0000_0001 , _versionName = ChainwebVersionName "recap-development" , _versionForks = tabulateHashMap $ \case - SlowEpoch -> onAllChains recapDevnet ForkAtGenesis - Vuln797Fix -> onAllChains recapDevnet ForkAtGenesis - CoinV2 -> onAllChains recapDevnet ForkAtGenesis - PactBackCompat_v16 -> onAllChains recapDevnet ForkAtGenesis - SkipTxTimingValidation -> onAllChains recapDevnet ForkAtGenesis - OldTargetGuard -> onAllChains recapDevnet ForkAtGenesis - SkipFeatureFlagValidation -> onAllChains recapDevnet ForkAtGenesis - ModuleNameFix -> onAllChains recapDevnet ForkAtGenesis - ModuleNameFix2 -> onAllChains recapDevnet ForkAtGenesis - OldDAGuard -> onAllChains recapDevnet ForkAtGenesis - PactEvents -> onAllChains recapDevnet ForkAtGenesis - SPVBridge -> onAllChains recapDevnet ForkAtGenesis - Pact4Coin3 -> onAllChains recapDevnet ForkAtGenesis - EnforceKeysetFormats -> onAllChains recapDevnet ForkAtGenesis - Pact42 -> onAllChains recapDevnet ForkAtGenesis - CheckTxHash -> onAllChains recapDevnet ForkAtGenesis - Chainweb213Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb214Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb215Pact -> onAllChains recapDevnet ForkAtGenesis - Pact44NewTrans -> onAllChains recapDevnet ForkAtGenesis - Chainweb216Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb217Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb218Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb219Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb220Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb221Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb222Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb223Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb224Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb225Pact -> onAllChains recapDevnet ForkAtGenesis - Chainweb226Pact -> onAllChains recapDevnet ForkAtGenesis - Pact5Fork -> onAllChains recapDevnet $ ForkAtGenesis - Chainweb228Pact -> onAllChains recapDevnet $ ForkAtBlockHeight $ BlockHeight 10 - Chainweb229Pact -> onAllChains recapDevnet $ ForkAtBlockHeight $ BlockHeight 20 - Chainweb230Pact -> onAllChains recapDevnet $ ForkAtBlockHeight $ BlockHeight 30 - HashedAdjacentRecord -> onAllChains recapDevnet $ ForkAtBlockHeight $ BlockHeight 40 + SlowEpoch -> onAllChains ForkAtGenesis + Vuln797Fix -> onAllChains ForkAtGenesis + CoinV2 -> onAllChains ForkAtGenesis + PactBackCompat_v16 -> onAllChains ForkAtGenesis + SkipTxTimingValidation -> onAllChains ForkAtGenesis + OldTargetGuard -> onAllChains ForkAtGenesis + SkipFeatureFlagValidation -> onAllChains ForkAtGenesis + ModuleNameFix -> onAllChains ForkAtGenesis + ModuleNameFix2 -> onAllChains ForkAtGenesis + OldDAGuard -> onAllChains ForkAtGenesis + PactEvents -> onAllChains ForkAtGenesis + SPVBridge -> onAllChains ForkAtGenesis + Pact4Coin3 -> onAllChains ForkAtGenesis + EnforceKeysetFormats -> onAllChains ForkAtGenesis + Pact42 -> onAllChains ForkAtGenesis + CheckTxHash -> onAllChains ForkAtGenesis + Chainweb213Pact -> onAllChains ForkAtGenesis + Chainweb214Pact -> onAllChains ForkAtGenesis + Chainweb215Pact -> onAllChains ForkAtGenesis + Pact44NewTrans -> onAllChains ForkAtGenesis + Chainweb216Pact -> onAllChains ForkAtGenesis + Chainweb217Pact -> onAllChains ForkAtGenesis + Chainweb218Pact -> onAllChains ForkAtGenesis + Chainweb219Pact -> onAllChains ForkAtGenesis + Chainweb220Pact -> onAllChains ForkAtGenesis + Chainweb221Pact -> onAllChains ForkAtGenesis + Chainweb222Pact -> onAllChains ForkAtGenesis + Chainweb223Pact -> onAllChains ForkAtGenesis + Chainweb224Pact -> onAllChains ForkAtGenesis + Chainweb225Pact -> onAllChains ForkAtGenesis + Chainweb226Pact -> onAllChains ForkAtGenesis + Pact5Fork -> onAllChains $ ForkAtGenesis + Chainweb228Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 10 + Chainweb229Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 20 + Chainweb230Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 30 + HashedAdjacentRecord -> onAllChains $ ForkAtBlockHeight $ BlockHeight 40 , _versionUpgrades = onChains [] , _versionGraphs = @@ -85,7 +85,7 @@ recapDevnet = ChainwebVersion [ [(unsafeChainId i, HashTarget $ maxBound `div` 100_000) | i <- [0..9]] , [(unsafeChainId i, HashTarget 0x0000088f99632cadf39b0db7655be62cb7dbc84ebbd9a90e5b5756d3e7d9196c) | i <- [10..19]] ] - , _genesisTime = onAllChains recapDevnet $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] + , _genesisTime = onAllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] , _genesisBlockPayload = onChains [ (unsafeChainId 0, unsafeFromText "5TWTF5R6Vc85vWHqcklTY91ljkV6mJ1wYfDJShooTCw") , (unsafeChainId 1, unsafeFromText "yZ6Syxl34TTrGGKxVHInV0S29BH8v-C8VZTbJr2eK2k") @@ -120,10 +120,10 @@ recapDevnet = ChainwebVersion { _disablePeerValidation = True , _disableMempoolSync = False } - , _versionVerifierPluginNames = onAllChains recapDevnet $ + , _versionVerifierPluginNames = onAllChains $ (600, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) `Above` Bottom (minBound, mempty) - , _versionQuirks = noQuirks recapDevnet + , _versionQuirks = noQuirks , _versionServiceDate = Nothing - , _versionPayloadProviderTypes = onAllChains recapDevnet PactProvider + , _versionPayloadProviderTypes = onAllChains PactProvider } diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index d12f9feb15..0f3e8e9785 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -19,14 +19,9 @@ -- because it works badly with tests. -- module Chainweb.Version.Registry - ( registerVersion - , unregisterVersion - , lookupVersionByCode - , lookupVersionByName - , fabricateVersionWithName + ( validateVersion , knownVersions , findKnownVersion - , versionMap ) where import Control.DeepSeq @@ -53,38 +48,12 @@ import Chainweb.Version.Testnet04 import Chainweb.Utils.Rule -- temporarily left off because it doesn't validate -{-# NOINLINE versionMap #-} -versionMap :: IORef (HashMap ChainwebVersionCode ChainwebVersion) -versionMap = unsafePerformIO $ do - traverse_ validateVersion knownVersions - newIORef $ HM.fromList [(_versionCode v, v) | v <- [mainnet, testnet04]] - --- | Register a version into our registry by code, ensuring it contains no --- errors and there are no others registered with that code. -registerVersion :: HasCallStack => ChainwebVersion -> IO () -registerVersion v = do - validateVersion v - atomicModifyIORef' versionMap $ \m -> - case HM.lookup (_versionCode v) m of - Just v' - | v /= v' -> error "registerVersion: conflicting version registered already" - | otherwise -> (m, ()) - Nothing -> - (HM.insert (_versionCode v) v m, ()) - --- | Unregister a version from the registry. This is ONLY for testing versions. -unregisterVersion :: HasCallStack => ChainwebVersion -> IO () -unregisterVersion v = do - if elem (_versionCode v) (_versionCode <$> [mainnet, testnet04]) - then error "You cannot unregister mainnet or testnet04 versions" - else atomicModifyIORef' versionMap $ \m -> (HM.delete (_versionCode v) m, ()) - validateVersion :: HasCallStack => ChainwebVersion -> IO () validateVersion v = do evaluate (rnf v) let hasAllChains :: ChainMap a -> Bool - hasAllChains (ChainMap m) = HS.fromMap (void m) == chainIds v + hasAllChains (ChainMap m) = HS.fromMap (void m) == withVersion v chainIds errors = concat [ [ "validateVersion: version does not have heights for all forks" | not (HS.fromMap (void $ _versionForks v) == HS.fromList [minBound :: Fork .. maxBound :: Fork]) ] @@ -109,56 +78,6 @@ validateVersion v = do where isUpgradeEmpty PactUpgrade{_pactUpgradeTransactions = upg} = null upg --- | Look up a version in the registry by code. -lookupVersionByCode :: HasCallStack => ChainwebVersionCode -> ChainwebVersion -lookupVersionByCode code - -- these two cases exist to ensure that the mainnet and testnet versions - -- cannot be accidentally replaced and are the most performant to look up. - -- registering them is still allowed, as long as they are not conflicting. - | code == _versionCode mainnet = mainnet - | code == _versionCode testnet04 = testnet04 - | otherwise = - -- Setting the version code here allows us to delay doing the lookup in - -- the case that we don't actually need the version, just the code. - lookupVersion & versionCode .~ code - where - - lookupVersion :: HasCallStack => ChainwebVersion - lookupVersion = unsafeDupablePerformIO $ do - m <- readIORef versionMap - return $ fromMaybe (error notRegistered) $ - HM.lookup code m - - notRegistered - | code == _versionCode recapDevnet = "recapDevnet version used but not registered, remember to do so after it's configured. " <> perhaps - | code == _versionCode devnet = "devnet version used but not registered, remember to do so after it's configured. " <> perhaps - | code == _versionCode evmDevnet = "EVM devnet version used but not registered, remember to do so after it's configured. " <> perhaps - | otherwise = "version not registered with code " <> show code <> ", have you seen Chainweb.Test.TestVersions.testVersions?" - - perhaps = "Perhaps you are attempting to run a different devnet version than a previous run, and you need to delete your db directory before restarting devnet with the new version?" - --- TODO: ideally all uses of this are deprecated. currently in use in --- ObjectEncoded block header decoder and CutHashes decoder. -lookupVersionByName :: HasCallStack => ChainwebVersionName -> ChainwebVersion -lookupVersionByName name - | name == _versionName mainnet = mainnet - | name == _versionName testnet04 = testnet04 - | otherwise = lookupVersion & versionName .~ name - where - lookupVersion = unsafeDupablePerformIO $ do - m <- readIORef versionMap - return $ fromMaybe (error notRegistered) $ - listToMaybe [ v | v <- HM.elems m, _versionName v == name ] - notRegistered - | name == _versionName recapDevnet = "recapDevnet version used but not registered, remember to do so after it's configured" - | name == _versionName devnet = "devnet version used but not registered, remember to do so after it's configured" - | name == _versionName evmDevnet = "EVM devnet version used but not registered, remember to do so after it's configured" - | otherwise = "version not registered with name " <> show name <> ", have you seen Chainweb.Test.TestVersions.testVersions?" - -fabricateVersionWithName :: HasCallStack => ChainwebVersionName -> ChainwebVersion -fabricateVersionWithName name = - error "attempted to access field of fabricated version." & versionName .~ name - -- | Versions known to us by name. knownVersions :: [ChainwebVersion] knownVersions = [mainnet, testnet04, recapDevnet, devnet, evmDevnet] @@ -168,5 +87,7 @@ knownVersions = [mainnet, testnet04, recapDevnet, devnet, evmDevnet] findKnownVersion :: MonadFail m => ChainwebVersionName -> m ChainwebVersion findKnownVersion vn = case find (\v -> _versionName v == vn) knownVersions of - Nothing -> fail $ T.unpack (getChainwebVersionName vn) <> " is not a known version: try development, mainnet01, or testnet04" + Nothing -> fail $ + T.unpack (getChainwebVersionName vn) <> " is not a known version. perhaps you meant one of these:\n" + <> show (getChainwebVersionName . _versionName <$> knownVersions) Just v -> return v diff --git a/src/Chainweb/Version/Testnet04.hs b/src/Chainweb/Version/Testnet04.hs index c966d87808..e25577bf68 100644 --- a/src/Chainweb/Version/Testnet04.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -75,49 +75,49 @@ pattern Testnet04 <- ((== testnet04) -> True) where Testnet04 = testnet04 testnet04 :: ChainwebVersion -testnet04 = ChainwebVersion +testnet04 = withVersion testnet04 $ ChainwebVersion { _versionCode = ChainwebVersionCode 0x00000007 , _versionName = ChainwebVersionName "testnet04" , _versionForks = tabulateHashMap $ \case - SlowEpoch -> onAllChains testnet04 ForkAtGenesis - Vuln797Fix -> onAllChains testnet04 ForkAtGenesis + SlowEpoch -> onAllChains ForkAtGenesis + Vuln797Fix -> onAllChains ForkAtGenesis CoinV2 -> onChains $ concat [ [(unsafeChainId i, ForkAtBlockHeight $ BlockHeight 1) | i <- [0..9]] , [(unsafeChainId i, ForkAtBlockHeight $ BlockHeight 337_000) | i <- [10..19]] ] - PactBackCompat_v16 -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 0 - ModuleNameFix -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2 - SkipTxTimingValidation -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1 - OldTargetGuard -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 0 - SkipFeatureFlagValidation -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 0 - ModuleNameFix2 -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 289_966 -- ~ 2020-07-13 - OldDAGuard -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 318_204 -- ~ 2020-07-23 16:00:00 - PactEvents -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 660_000 - SPVBridge -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 820_000 -- 2021-01-14T17:12:02 - Pact4Coin3 -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_261_000 -- 2021-06-17T15:54:14 - EnforceKeysetFormats -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_701_000 -- 2021-11-18T17:54:36 - Pact42 -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_862_000 -- 2021-06-19T03:34:05 - CheckTxHash -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_889_000 -- 2022-01-24T04:19:24 - Chainweb213Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 1_974_556 -- 2022-02-25 00:00:00 - Chainweb214Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_134_331 -- 2022-04-21T12:00:00Z - Chainweb215Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_295_437 -- 2022-06-16T12:00:00+00:00 - Pact44NewTrans -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_500_369 -- Todo: add date - Chainweb216Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_516_739 -- 2022-09-01 12:00:00+00:00 - Chainweb217Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 2_777_367 -- 2022-12-01 12:00:00+00:00 - Chainweb218Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_038_343 -- 2023-03-02 12:00:00+00:00 - Chainweb219Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_299_753 -- 2023-06-01 12:00:00+00:00 - Chainweb220Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_580_964 -- 2023-09-08 12:00:00+00:00 - Chainweb221Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_702_250 -- 2023-10-19 12:00:00+00:00 - Chainweb222Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 3_859_808 -- 2023-12-13 12:00:00+00:00 - Chainweb223Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 4_100_681 -- 2024-03-06 12:00:00+00:00 - Chainweb224Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 4_333_587 -- 2024-05-29 12:00:00+00:00 - Chainweb225Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 4_575_072 -- 2024-08-21 12:00:00+00:00 - Chainweb226Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 4_816_925 -- 2024-11-13 12:00:00+00:00 - Pact5Fork -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 5_058_738 -- 2025-02-05 12:00:00+00:00 - Chainweb228Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 5_155_146 -- 2025-03-11 00:00:00+00:00 - Chainweb229Pact -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 5_300_466 -- 2025-04-30 12:00:00+00:00 - HashedAdjacentRecord -> onAllChains testnet04 $ ForkAtBlockHeight $ BlockHeight 5_300_466 -- 2025-04-30 12:00:00+00:00 - Chainweb230Pact -> onAllChains testnet04 ForkNever + PactBackCompat_v16 -> onAllChains $ ForkAtBlockHeight $ BlockHeight 0 + ModuleNameFix -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2 + SkipTxTimingValidation -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1 + OldTargetGuard -> onAllChains $ ForkAtBlockHeight $ BlockHeight 0 + SkipFeatureFlagValidation -> onAllChains $ ForkAtBlockHeight $ BlockHeight 0 + ModuleNameFix2 -> onAllChains $ ForkAtBlockHeight $ BlockHeight 289_966 -- ~ 2020-07-13 + OldDAGuard -> onAllChains $ ForkAtBlockHeight $ BlockHeight 318_204 -- ~ 2020-07-23 16:00:00 + PactEvents -> onAllChains $ ForkAtBlockHeight $ BlockHeight 660_000 + SPVBridge -> onAllChains $ ForkAtBlockHeight $ BlockHeight 820_000 -- 2021-01-14T17:12:02 + Pact4Coin3 -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1_261_000 -- 2021-06-17T15:54:14 + EnforceKeysetFormats -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1_701_000 -- 2021-11-18T17:54:36 + Pact42 -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1_862_000 -- 2021-06-19T03:34:05 + CheckTxHash -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1_889_000 -- 2022-01-24T04:19:24 + Chainweb213Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 1_974_556 -- 2022-02-25 00:00:00 + Chainweb214Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_134_331 -- 2022-04-21T12:00:00Z + Chainweb215Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_295_437 -- 2022-06-16T12:00:00+00:00 + Pact44NewTrans -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_500_369 -- Todo: add date + Chainweb216Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_516_739 -- 2022-09-01 12:00:00+00:00 + Chainweb217Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 2_777_367 -- 2022-12-01 12:00:00+00:00 + Chainweb218Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 3_038_343 -- 2023-03-02 12:00:00+00:00 + Chainweb219Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 3_299_753 -- 2023-06-01 12:00:00+00:00 + Chainweb220Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 3_580_964 -- 2023-09-08 12:00:00+00:00 + Chainweb221Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 3_702_250 -- 2023-10-19 12:00:00+00:00 + Chainweb222Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 3_859_808 -- 2023-12-13 12:00:00+00:00 + Chainweb223Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_100_681 -- 2024-03-06 12:00:00+00:00 + Chainweb224Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_333_587 -- 2024-05-29 12:00:00+00:00 + Chainweb225Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_575_072 -- 2024-08-21 12:00:00+00:00 + Chainweb226Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 4_816_925 -- 2024-11-13 12:00:00+00:00 + Pact5Fork -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_058_738 -- 2025-02-05 12:00:00+00:00 + Chainweb228Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_155_146 -- 2025-03-11 00:00:00+00:00 + Chainweb229Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_300_466 -- 2025-04-30 12:00:00+00:00 + HashedAdjacentRecord -> onAllChains $ ForkAtBlockHeight $ BlockHeight 5_300_466 -- 2025-04-30 12:00:00+00:00 + Chainweb230Pact -> onAllChains ForkNever , _versionGraphs = (to20ChainsTestnet, twentyChainGraph) `Above` @@ -134,7 +134,7 @@ testnet04 = ChainwebVersion [ [(unsafeChainId i, maxTarget) | i <- [0..9]] , [(unsafeChainId i, testnet20InitialHashTarget) | i <- [10..19]] ] - , _genesisTime = onAllChains testnet04 $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] + , _genesisTime = onAllChains $ BlockCreationTime [timeMicrosQQ| 2019-07-17T18:28:37.613832 |] , _genesisBlockPayload = onChains [ (unsafeChainId 0, unsafeFromText "nfYm3e_fk2ICws0Uowos6OMuqfFg5Nrl_zqXVx9v_ZQ") , (unsafeChainId 1, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") @@ -158,6 +158,7 @@ testnet04 = ChainwebVersion , (unsafeChainId 19, unsafeFromText "HU-ZhdfsQCiTrfxjtbkr5MHmjoukOt6INqB2vuYiF3g") ] } + -- all upgrades have been removed due to the removal of Pact 4 , _versionUpgrades = onChains [] , _versionCheats = VersionCheats { _disablePow = False @@ -168,7 +169,7 @@ testnet04 = ChainwebVersion { _disablePeerValidation = False , _disableMempoolSync = False } - , _versionVerifierPluginNames = onAllChains testnet04 $ (4_100_681, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) `Above` + , _versionVerifierPluginNames = onAllChains $ (4_100_681, Set.fromList $ map VerifierName ["hyperlane_v3_message"]) `Above` Bottom (minBound, mempty) , _versionQuirks = VersionQuirks { _quirkGasFees = onChains @@ -177,5 +178,5 @@ testnet04 = ChainwebVersion ] } , _versionServiceDate = Just "2025-07-23T00:00:00Z" - , _versionPayloadProviderTypes = onAllChains testnet04 PactProvider + , _versionPayloadProviderTypes = onAllChains PactProvider } diff --git a/src/Chainweb/Version/Utils.hs b/src/Chainweb/Version/Utils.hs index cb623e19d6..4d0b811c08 100644 --- a/src/Chainweb/Version/Utils.hs +++ b/src/Chainweb/Version/Utils.hs @@ -124,14 +124,13 @@ atCutHeight h = snd . fromJuste . M.lookupLE h -- Post-condition: -- -- @ --- 0 == minimum $ M.keys $ chainGraphs v +-- 0 == minimum $ M.keys chainGraphs -- @ -- -chainGraphs :: HasChainwebVersion v => v -> M.Map BlockHeight ChainGraph -chainGraphs = \case - (_chainwebVersion -> v) - | _versionCode v == _versionCode mainnet -> mainnetGraphs - | otherwise -> M.fromDistinctDescList . toList . ruleElems $ _versionGraphs v +chainGraphs :: HasVersion => M.Map BlockHeight ChainGraph +chainGraphs + | _versionCode implicitVersion == _versionCode mainnet = mainnetGraphs + | otherwise = M.fromDistinctDescList . toList . ruleElems $ _versionGraphs implicitVersion where mainnetGraphs = M.fromDistinctDescList . toList . ruleElems $ _versionGraphs mainnet @@ -141,104 +140,91 @@ chainGraphs = \case -- Post-condition: -- -- @ --- 0 == minimum $ M.keys $ chainGraphs v +-- 0 == minimum $ M.keys chainGraphs -- @ -- chainGraphsAt - :: HasChainwebVersion v - => v - -> BlockHeight + :: HasVersion + => BlockHeight -> M.Map BlockHeight ChainGraph -chainGraphsAt v h = limitHeight h (chainGraphs v) +chainGraphsAt h = limitHeight h chainGraphs {-# INLINE chainGraphsAt #-} lastGraphChange - :: HasCallStack - => HasChainwebVersion v - => v - -> BlockHeight + :: (HasCallStack, HasVersion) + => BlockHeight -> BlockHeight -lastGraphChange v h = fst . fromJuste . M.lookupLE h $ chainGraphs v +lastGraphChange h = fst . fromJuste . M.lookupLE h $ chainGraphs {-# INLINE lastGraphChange #-} nextGraphChange - :: HasCallStack - => HasChainwebVersion v - => v + :: (HasCallStack, HasVersion) + => BlockHeight -> BlockHeight - -> BlockHeight -nextGraphChange v h = fst . fromJuste . M.lookupGT h $ chainGraphs v +nextGraphChange h = fst . fromJuste . M.lookupGT h $ chainGraphs {-# INLINE nextGraphChange #-} chainCountAt - :: HasCallStack - => HasChainwebVersion v - => v - -> BlockHeight + :: (HasCallStack, HasVersion) + => BlockHeight -> Natural -chainCountAt v h = order $ atHeight h $ chainGraphs v +chainCountAt h = order $ atHeight h $ chainGraphs {-# INLINE chainCountAt #-} degreeAt - :: HasCallStack - => HasChainwebVersion v - => v - -> BlockHeight + :: (HasCallStack, HasVersion) + => BlockHeight -> Natural -degreeAt v h = degree $ atHeight h $ chainGraphs v +degreeAt h = degree $ atHeight h $ chainGraphs {-# INLINE degreeAt #-} diameterAt - :: HasCallStack - => HasChainwebVersion v - => v - -> BlockHeight + :: (HasCallStack, HasVersion) + => BlockHeight -> Natural -diameterAt v h = diameter $ atHeight h $ chainGraphs v +diameterAt h = diameter $ atHeight h $ chainGraphs {-# INLINE diameterAt #-} chainIdsAt - :: HasCallStack - => HasChainwebVersion v - => v - -> BlockHeight + :: (HasCallStack, HasVersion) + => BlockHeight -> HS.HashSet ChainId -chainIdsAt v h = graphChainIds $ atHeight h $ chainGraphs v +chainIdsAt h = graphChainIds $ atHeight h $ chainGraphs {-# INLINE chainIdsAt #-} -- | Uniformily get a random ChainId at the top of the current chainweb -- -randomChainId :: HasChainwebVersion v => v -> IO ChainId -randomChainId v = randomChainIdAt v maxBound +randomChainId :: HasVersion => IO ChainId +randomChainId = randomChainIdAt maxBound {-# INLINE randomChainId #-} -- | Uniformily get a random ChainId at the given height of the chainweb -- -randomChainIdAt :: HasChainwebVersion v => v -> BlockHeight -> IO ChainId -randomChainIdAt v h = (!!) (toList cs) <$> randomRIO (0, length cs - 1) +randomChainIdAt :: HasVersion => BlockHeight -> IO ChainId +randomChainIdAt h = (!!) (toList cs) <$> randomRIO (0, length cs - 1) where - cs = chainIdsAt v h + cs = chainIdsAt h {-# INLINE randomChainIdAt #-} -- | Sometimes, in particular for testing and examples, some fixed chain id is -- needed, but it doesn't matter which one. This function provides some valid -- chain ids for the top of the current chainweb. -- -someChainId :: HasCallStack => HasChainwebVersion v => v -> ChainId -someChainId v = someChainIdAt v maxBound +someChainId :: HasVersion => ChainId +someChainId = someChainIdAt maxBound {-# INLINE someChainId #-} -- | Sometimes, in particular for testing and examples, some fixed chain id is -- needed, but it doesn't matter which one. This function provides some valid -- chain ids for the chainweb at the given height. -- -someChainIdAt :: HasCallStack => HasChainwebVersion v => v -> BlockHeight -> ChainId -someChainIdAt v h = minimum $ chainIdsAt v h +someChainIdAt :: HasVersion => BlockHeight -> ChainId +someChainIdAt h = minimum $ chainIdsAt h -- guaranteed to succeed because the empty graph isn't a valid chain graph. {-# INLINE someChainIdAt #-} -isGraphChange :: HasChainwebVersion v => v -> BlockHeight -> Bool -isGraphChange v h = M.member h (chainGraphs v) +isGraphChange :: HasVersion => BlockHeight -> Bool +isGraphChange h = M.member h chainGraphs {-# INLINE isGraphChange #-} -- -------------------------------------------------------------------------- -- @@ -249,41 +235,35 @@ isGraphChange v h = M.member h (chainGraphs v) -- Precondition: h > genesisHeight -- blockCountAt - :: HasCallStack - => HasChainwebVersion v + :: (HasCallStack, HasVersion) => HasChainId cid - => v - -> cid + => cid -> BlockHeight -> Natural -blockCountAt v cid h +blockCountAt cid h | h < gh = 0 | otherwise = 1 + int h - int gh where - gh = genesisBlockHeight (_chainwebVersion v) (_chainId cid) + gh = genesisBlockHeight (_chainId cid) -- | The block count accross all chains at a given block height -- globalBlockCountAt - :: HasCallStack - => HasChainwebVersion v - => v - -> BlockHeight + :: (HasCallStack, HasVersion) + => BlockHeight -> Natural -globalBlockCountAt v h = sum - $ fmap (\c -> blockCountAt v c h) +globalBlockCountAt h = sum + $ fmap (\c -> blockCountAt c h) $ toList - $ chainIdsAt v h + $ chainIdsAt h globalBlockDelayAt - :: HasCallStack - => HasChainwebVersion v - => v - -> BlockHeight + :: (HasCallStack, HasVersion) + => BlockHeight -> Double -globalBlockDelayAt v h = (int r / 1_000_000) / int (chainCountAt v h) +globalBlockDelayAt h = (int r / 1_000_000) / int (chainCountAt h) where - BlockDelay r = _versionBlockDelay (_chainwebVersion v) + BlockDelay r = _versionBlockDelay implicitVersion -- -------------------------------------------------------------------------- -- -- Cut Heights @@ -294,8 +274,8 @@ globalBlockDelayAt v h = (int r / 1_000_000) / int (chainCountAt v h) -- Note, that the result isn't accurate for block heights around a chain graph -- change. -- -avgCutHeightAt :: HasChainwebVersion v => v -> BlockHeight -> CutHeight -avgCutHeightAt v h = int $ int h * chainCountAt v h +avgCutHeightAt :: HasVersion => BlockHeight -> CutHeight +avgCutHeightAt h = int $ int h * chainCountAt h {-# INLINE avgCutHeightAt #-} -- | Cut height intervals for the chain graphs of a chainweb version @@ -303,54 +283,53 @@ avgCutHeightAt v h = int $ int h * chainCountAt v h -- Post-condition: -- -- @ --- 0 == minimum $ M.keys $ cutHeights v +-- 0 == minimum $ M.keys cutHeights -- @ -- chainGraphsByCutHeight - :: HasChainwebVersion v - => v - -> M.Map CutHeight ChainGraph + :: HasVersion + => M.Map CutHeight ChainGraph chainGraphsByCutHeight = M.fromList . fmap (\(h,g) -> (int h * int (order g), g)) . M.toAscList - . chainGraphs + $ chainGraphs {-# INLINE chainGraphsByCutHeight #-} -- | The chain graph at the given cut height -- -- Note, that the result isn't accurate during a chain graph change -- -chainGraphAtCutHeight :: HasChainwebVersion v => v -> CutHeight -> ChainGraph -chainGraphAtCutHeight v h = atCutHeight h $ chainGraphsByCutHeight v +chainGraphAtCutHeight :: HasVersion => CutHeight -> ChainGraph +chainGraphAtCutHeight h = atCutHeight h $ chainGraphsByCutHeight -- | The number of chains that exist at the given cut height -- -- Note, that the result isn't accurate during a chain graph change -- -chainCountAtCutHeight :: HasChainwebVersion v => v -> CutHeight -> Natural -chainCountAtCutHeight v = order . chainGraphAtCutHeight v +chainCountAtCutHeight :: HasVersion => CutHeight -> Natural +chainCountAtCutHeight = order . chainGraphAtCutHeight -- | The diameter of the chain graph at the given cut height -- -- Note, that the result isn't accurate during a chain graph change -- -diameterAtCutHeight :: HasChainwebVersion v => v -> CutHeight -> Natural -diameterAtCutHeight v = diameter . chainGraphAtCutHeight v +diameterAtCutHeight :: HasVersion => CutHeight -> Natural +diameterAtCutHeight = diameter . chainGraphAtCutHeight -- | The degree of the chain graph at the given cut height -- -- Note, that the result isn't accurate during a chain graph change -- -degreeAtCutHeight :: HasChainwebVersion v => v -> CutHeight -> Natural -degreeAtCutHeight v = degree . chainGraphAtCutHeight v +degreeAtCutHeight :: HasVersion => CutHeight -> Natural +degreeAtCutHeight = degree . chainGraphAtCutHeight -- | The average chain height at a given cut height. -- -- Note, that the result isn't accurate for block heights around a chain graph -- change. -- -avgBlockHeightAtCutHeight :: HasChainwebVersion v => v -> CutHeight -> Double -avgBlockHeightAtCutHeight v h = int h / int (chainCountAtCutHeight v h) +avgBlockHeightAtCutHeight :: HasVersion => CutHeight -> Double +avgBlockHeightAtCutHeight h = int h / int (chainCountAtCutHeight h) -- | The global number of blocks that exist at the given cut height. -- @@ -358,33 +337,27 @@ avgBlockHeightAtCutHeight v h = int h / int (chainCountAtCutHeight v h) -- change. -- blockCountAtCutHeight - :: HasCallStack - => HasChainwebVersion v - => v - -> CutHeight + :: (HasCallStack, HasVersion) + => CutHeight -> Natural -blockCountAtCutHeight v h - = globalBlockCountAt v (int k `div` int (order g)) + int (h - k) +blockCountAtCutHeight h + = globalBlockCountAt (int k `div` int (order g)) + int (h - k) where - (k, g) = fromJuste $ M.lookupLE h $ chainGraphsByCutHeight v + (k, g) = fromJuste $ M.lookupLE h $ chainGraphsByCutHeight lastGraphChangeByCutHeight - :: HasCallStack - => HasChainwebVersion v - => v + :: (HasCallStack, HasVersion) + => CutHeight -> CutHeight - -> CutHeight -lastGraphChangeByCutHeight v h - = fst $ fromJuste $ M.lookupLE h $ chainGraphsByCutHeight v +lastGraphChangeByCutHeight h + = fst $ fromJuste $ M.lookupLE h $ chainGraphsByCutHeight nextGraphChangeByCutHeight - :: HasCallStack - => HasChainwebVersion v - => v - -> CutHeight + :: (HasCallStack, HasVersion) + => CutHeight -> CutHeight -nextGraphChangeByCutHeight v h - = fst $ fromJuste $ M.lookupGT h $ chainGraphsByCutHeight v +nextGraphChangeByCutHeight h + = fst $ fromJuste $ M.lookupGT h $ chainGraphsByCutHeight -- -------------------------------------------------------------------------- -- -- Expected Block Count, Block Heights, and Cut Heights @@ -393,19 +366,17 @@ nextGraphChangeByCutHeight v h -- expected number of mined blocks during a test on a given chain. -- expectedBlockCountAfterSeconds - :: HasCallStack - => HasChainwebVersion v + :: (HasCallStack, HasVersion) => HasChainId cid - => v - -> cid + => cid -> Seconds -> Double -expectedBlockCountAfterSeconds v cid s = max 0 (1 + (int s / (int r / 1_000_000)) - int gh) +expectedBlockCountAfterSeconds cid s = max 0 (1 + (int s / (int r / 1_000_000)) - int gh) -- The `max 0` term is required for chains that were added during graph transitions -- and thus have `genesisHeight > 0` where - BlockDelay r = _versionBlockDelay (_chainwebVersion v) - gh = genesisBlockHeight (_chainwebVersion v) (_chainId cid) + BlockDelay r = _versionBlockDelay implicitVersion + gh = genesisBlockHeight (_chainId cid) -- | This function is useful for performance testing when calculating the -- expected number of mined blocks during a test accross all chains. @@ -415,50 +386,44 @@ expectedBlockCountAfterSeconds v cid s = max 0 (1 + (int s / (int r / 1_000_000) -- chainweb versions with fixed expected solve times and no difficulty adjustment. -- expectedGlobalBlockCountAfterSeconds - :: HasCallStack - => HasChainwebVersion v - => v - -> Seconds + :: (HasCallStack, HasVersion) + => Seconds -> Double -expectedGlobalBlockCountAfterSeconds v s = (* 0.4) +expectedGlobalBlockCountAfterSeconds s = (* 0.4) $ sum - $ fmap (\c -> expectedBlockCountAfterSeconds v c s) + $ fmap (\c -> expectedBlockCountAfterSeconds c s) $ toList - $ chainIdsAt v (round eh) + $ chainIdsAt (round eh) where - eh = expectedBlockHeightAfterSeconds v s + eh = expectedBlockHeightAfterSeconds s -- | The expected BlockHeight after the given number of seconds has passed. -- -- This function is useful for performance testing. -- expectedBlockHeightAfterSeconds - :: HasCallStack - => HasChainwebVersion v - => v - -> Seconds + :: (HasCallStack, HasVersion) + => Seconds -> Double -expectedBlockHeightAfterSeconds v s = int s / (int r / 1_000_000) +expectedBlockHeightAfterSeconds s = int s / (int r / 1_000_000) where - BlockDelay r = _versionBlockDelay (_chainwebVersion v) + BlockDelay r = _versionBlockDelay implicitVersion -- | The expected CutHeight after the given number of seconds has passed. -- -- This function is useful for performance testing. -- expectedCutHeightAfterSeconds - :: HasCallStack - => HasChainwebVersion v - => v - -> Seconds + :: (HasCallStack, HasVersion) + => Seconds -> Double -expectedCutHeightAfterSeconds v s = eh * int (chainCountAt v (round eh)) +expectedCutHeightAfterSeconds s = eh * int (chainCountAt (round eh)) where - eh = expectedBlockHeightAfterSeconds v s + eh = expectedBlockHeightAfterSeconds s -- | The verifier plugins enabled for a particular block. -verifiersAt :: ChainwebVersion -> ChainId -> BlockHeight -> Map VerifierName VerifierPlugin -verifiersAt v cid bh = +verifiersAt :: HasVersion => ChainId -> BlockHeight -> Map VerifierName VerifierPlugin +verifiersAt cid bh = M.restrictKeys allVerifierPlugins activeVerifierNames where activeVerifierNames @@ -466,7 +431,7 @@ verifiersAt v cid bh = $ ruleZipperHere $ snd $ ruleSeek (\h _ -> bh >= h) - $ _versionVerifierPluginNames v ^?! atChain cid + $ _versionVerifierPluginNames implicitVersion ^?! atChain cid -- the mappings from names to verifier plugins is global. the list of verifier -- plugins active in any particular block validation context is the only thing diff --git a/src/Chainweb/WebBlockHeaderDB.hs b/src/Chainweb/WebBlockHeaderDB.hs index 919ada2acf..96df451a23 100644 --- a/src/Chainweb/WebBlockHeaderDB.hs +++ b/src/Chainweb/WebBlockHeaderDB.hs @@ -85,23 +85,14 @@ import Chainweb.Storage.Table.RocksDB -- data WebBlockHeaderDb = WebBlockHeaderDb { _webBlockHeaderDb :: !(ChainMap BlockHeaderDb) - , _webChainwebVersion :: !ChainwebVersion } -instance HasChainGraph (WebBlockHeaderDb, BlockHeight) where - _chainGraph (v, h) = _chainGraph (_webChainwebVersion v, h) - {-# INLINE _chainGraph #-} - -instance HasChainwebVersion WebBlockHeaderDb where - _chainwebVersion = _webChainwebVersion - {-# INLINE _chainwebVersion #-} - webBlockHeaderDb :: Getter WebBlockHeaderDb (ChainMap BlockHeaderDb) webBlockHeaderDb = to _webBlockHeaderDb -- | Returns all blocks in all block header databases. -- -webEntries :: WebBlockHeaderDb -> (S.Stream (Of BlockHeader) IO () -> IO a) -> IO a +webEntries :: HasVersion => WebBlockHeaderDb -> (S.Stream (Of BlockHeader) IO () -> IO a) -> IO a webEntries db f = go (view (webBlockHeaderDb . to toList) db) mempty where go [] s = f s @@ -116,32 +107,30 @@ instance IxedGet WebBlockHeaderDb where ixg i = webBlockHeaderDb . ixg i {-# INLINE ixg #-} -instance (k ~ CasKeyType (ChainValue BlockHeader)) => ReadableTable WebBlockHeaderDb k (ChainValue BlockHeader) where +instance (HasVersion, k ~ CasKeyType (ChainValue BlockHeader)) => ReadableTable WebBlockHeaderDb k (ChainValue BlockHeader) where tableLookup db k = case preview (ixg (_chainId k)) db of Nothing -> return Nothing Just cdb -> sequence <$> traverse (tableLookup cdb) k {-# INLINE tableLookup #-} initWebBlockHeaderDb - :: RocksDb - -> ChainwebVersion + :: HasVersion + => RocksDb -> IO WebBlockHeaderDb -initWebBlockHeaderDb db v = WebBlockHeaderDb - <$!> tabulateChainsM v (\cid -> initBlockHeaderDb (conf cid db)) - <*> pure v +initWebBlockHeaderDb db = WebBlockHeaderDb + <$!> tabulateChainsM (\cid -> initBlockHeaderDb (conf cid db)) where - conf cid = Configuration (genesisBlockHeader v cid) + conf cid = Configuration (genesisBlockHeader cid) -- | FIXME: this needs some consistency checks -- mkWebBlockHeaderDb - :: ChainwebVersion - -> ChainMap BlockHeaderDb + :: ChainMap BlockHeaderDb -> WebBlockHeaderDb -mkWebBlockHeaderDb v m = WebBlockHeaderDb m v +mkWebBlockHeaderDb m = WebBlockHeaderDb m getWebBlockHeaderDb - :: MonadThrow m + :: (MonadThrow m, HasVersion) => HasChainId p => WebBlockHeaderDb -> p @@ -150,21 +139,22 @@ getWebBlockHeaderDb db p = do checkWebChainId graph p return $! _webBlockHeaderDb db ^?! atChain (_chainId p) where - v = _chainwebVersion db - graph = chainGraphAt v maxBound + graph = chainGraphAt maxBound lookupWebBlockHeaderDb - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> ChainId -> BlockHash -> IO BlockHeader lookupWebBlockHeaderDb wdb c h = do - checkWebChainId (wdb, maxBound @BlockHeight) c + checkWebChainId (chainGraphAt $ maxBound @BlockHeight) c db <- getWebBlockHeaderDb wdb c lookupM db h blockAdjacentParentHeaders - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> BlockHeader -> IO (HM.HashMap ChainId (Parent BlockHeader)) blockAdjacentParentHeaders db h @@ -173,21 +163,23 @@ blockAdjacentParentHeaders db h $ view blockAdjacentHashes h lookupAdjacentParentHeader - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> BlockHeader -> ChainId -> IO (Parent BlockHeader) lookupAdjacentParentHeader db h cid = do - checkWebChainId (db, view blockHeight h) h + checkWebChainId (chainGraphAt $ view blockHeight h) h let ph = h ^?! (blockAdjacentHashes . ix cid) traverse (lookupWebBlockHeaderDb db cid) ph lookupParentHeader - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> BlockHeader -> IO (Parent BlockHeader) lookupParentHeader db h = do - checkWebChainId (db, view blockHeight h) h + checkWebChainId (chainGraphAt $ view blockHeight h) h traverse (lookupWebBlockHeaderDb db (_chainId h)) (view blockParent h) -- -------------------------------------------------------------------------- -- @@ -196,7 +188,8 @@ lookupParentHeader db h = do -- TODO create monotonic IsCas that doesn't support deletion insertWebBlockHeaderDbValidated - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> ValidatedHeader -> IO () insertWebBlockHeaderDbValidated wdb h = do @@ -204,7 +197,8 @@ insertWebBlockHeaderDbValidated wdb h = do insertBlockHeaderDb db h insertWebBlockHeaderDb - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> BlockHeader -> IO () insertWebBlockHeaderDb wdb h = do @@ -213,7 +207,8 @@ insertWebBlockHeaderDb wdb h = do insertWebBlockHeaderDbValidated wdb valHdr insertWebBlockHeaderDbManyValidated - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> ValidatedHeaders -> IO () insertWebBlockHeaderDbManyValidated wdb hdrs = do @@ -227,7 +222,8 @@ insertWebBlockHeaderDbManyValidated wdb hdrs = do unsafeInsertBlockHeaderDb db h insertWebBlockHeaderDbMany - :: Foldable f + :: HasVersion + => Foldable f => WebBlockHeaderDb -> f BlockHeader -> IO () @@ -250,7 +246,7 @@ insertWebBlockHeaderDbMany db es = do -- the graph of the parent headers. -- checkBlockHeaderGraph - :: MonadThrow m + :: (MonadThrow m, HasVersion) => BlockHeader -> m () checkBlockHeaderGraph b = void @@ -258,14 +254,15 @@ checkBlockHeaderGraph b = void where graph | isGenesisBlockHeader b = _chainGraph b - | otherwise = chainGraphAt (_chainwebVersion b) (view blockHeight b - 1) + | otherwise = chainGraphAt (view blockHeight b - 1) {-# INLINE checkBlockHeaderGraph #-} -- | Given a 'WebBlockHeaderDb' @db@, @checkBlockAdjacentParents h@ checks that -- all referenced adjacent parents block headers exist in @db@. -- checkBlockAdjacentParents - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> BlockHeader -> IO () checkBlockAdjacentParents db = void . blockAdjacentParentHeaders db diff --git a/src/P2P/Node.hs b/src/P2P/Node.hs index 8371c5e40b..f984577ea3 100644 --- a/src/P2P/Node.hs +++ b/src/P2P/Node.hs @@ -231,12 +231,6 @@ data P2pNode = P2pNode , _p2pNodeSessionTimeout :: !Seconds } -_p2pNodeChainwebVersion :: P2pNode -> ChainwebVersion -_p2pNodeChainwebVersion = _chainwebVersion . _p2pNodePeerDb - -instance HasChainwebVersion P2pNode where - _chainwebVersion = _p2pNodeChainwebVersion - showSessionId :: PeerInfo -> Async (Maybe Bool) -> T.Text showSessionId pinf ses = showInfo pinf <> ":" <> (T.drop 9 . sshow $ asyncThreadId ses) @@ -387,12 +381,12 @@ displayPeerValidationFailure IsLocalPeerAddress -- or white listing. -- guardPeerDb - :: ChainwebVersion - -> NetworkId + :: HasVersion + => NetworkId -> PeerDb -> PeerInfo -> IO (Either PeerValidationFailure PeerInfo) -guardPeerDb v nid peerDb pinf = do +guardPeerDb nid peerDb pinf = do peers <- peerDbSnapshot peerDb if | isMe -> return $ Left $ IsLocalPeerAddress @@ -405,7 +399,7 @@ guardPeerDb v nid peerDb pinf = do else return $ Left $ NodeVersionNotAccepted nodeVersion where isReserved :: Bool - isReserved = not (v ^. versionDefaults . disablePeerValidation) && isReservedHostAddress (_peerAddr pinf) + isReserved = not (implicitVersion ^. versionDefaults . disablePeerValidation) && isReservedHostAddress (_peerAddr pinf) -- Currently we are using 'getNewPeerManager' which doesn't validate -- certificates. We could be more strict and check that the certificate @@ -413,7 +407,10 @@ guardPeerDb v nid peerDb pinf = do -- canConnect = do mgr <- getNewPeerManager - getNodeVersion mgr (_versionName v) (_peerAddr pinf) (Just $ networkIdToText nid <> "/peer") + getNodeVersion mgr + (_versionName implicitVersion) + (_peerAddr pinf) + (Just $ networkIdToText nid <> "/peer") -- Only compare the address because even for equal peer infos the peer -- ID may be 'Nothing' for one peer and 'Just' some value for the other. @@ -424,7 +421,8 @@ isKnown :: PeerSet -> PeerInfo -> Bool isKnown peers pinf = not . IXS.null $ IXS.getEQ (_peerAddr pinf) peers guardPeerDbOfNode - :: P2pNode + :: HasVersion + => P2pNode -> PeerInfo -> IO (Maybe PeerInfo) guardPeerDbOfNode node pinf = go >>= \case @@ -439,7 +437,6 @@ guardPeerDbOfNode node pinf = go >>= \case Right x -> return (Just x) where go = guardPeerDb - (_chainwebVersion node) (_p2pNodeNetworkId node) (_p2pNodePeerDb node) pinf @@ -454,7 +451,7 @@ peerClientEnv node = peerInfoClientEnv (_p2pNodeManager node) -- -- TODO: handle paging -- -syncFromPeer :: P2pNode -> PeerInfo -> IO Bool +syncFromPeer :: HasVersion => P2pNode -> PeerInfo -> IO Bool syncFromPeer node info = do prunePeerDb (_p2pNodeLogFunction node) peerDb runClientM sync env >>= \case @@ -486,7 +483,6 @@ syncFromPeer node info = do return True where env = peerClientEnv node info - v = _p2pNodeChainwebVersion node nid = _p2pNodeNetworkId node peerDb = _p2pNodePeerDb node @@ -495,9 +491,9 @@ syncFromPeer node info = do sync :: ClientM (Page (NextItem Int) PeerInfo) sync = do - !p <- peerGetClient v nid Nothing Nothing + !p <- peerGetClient nid Nothing Nothing liftIO $ logg node Debug $ "got " <> sshow (_pageLimit p) <> " peers " <> showInfo info - void $ peerPutClient v nid (_p2pNodePeerInfo node) + void $ peerPutClient nid (_p2pNodePeerInfo node) liftIO $ logg node Debug $ "put own peer info to " <> showInfo info return p @@ -629,7 +625,7 @@ findNextPeer node = do -- | This can loop forever if there are no peers available for the respective -- network. -- -newSession :: P2pNode -> IO () +newSession :: HasVersion => P2pNode -> IO () newSession node = do newPeer <- findNextPeer node let newPeerInfo = _peerEntryInfo newPeer @@ -754,15 +750,15 @@ waitAnySession node = do -- | Start a 'PeerDb' for the given set of NetworkIds -- startPeerDb - :: ChainwebVersion - -> HS.HashSet NetworkId + :: HasVersion + => HS.HashSet NetworkId -> Bool -- ^ Whether this node is private -> [PeerInfo] -- ^ Set of statically known peers. -> IO PeerDb -startPeerDb v nids isPrivate knownPeers = do - !peerDb <- newEmptyPeerDb v +startPeerDb nids isPrivate knownPeers = do + !peerDb <- newEmptyPeerDb forM_ nids $ \nid -> peerDbInsertPeerInfoList_ True nid knownPeers peerDb return $ if isPrivate @@ -778,16 +774,16 @@ stopPeerDb _ = return () -- | Run a computation with a PeerDb -- withPeerDb - :: ChainwebVersion - -> HS.HashSet NetworkId + :: HasVersion + => HS.HashSet NetworkId -> Bool -- ^ Whether this node is private -> [PeerInfo] -- ^ Set of statically known peers -> (PeerDb -> IO a) -> IO a -withPeerDb v nids isPrivate knownPeers = - bracket (startPeerDb v nids isPrivate knownPeers) stopPeerDb +withPeerDb nids isPrivate knownPeers = + bracket (startPeerDb nids isPrivate knownPeers) stopPeerDb -- -------------------------------------------------------------------------- -- -- Create @@ -809,10 +805,7 @@ data P2pNodeParameters = P2pNodeParameters , _p2pNodeParamsSession :: !P2pSession } -instance HasChainwebVersion P2pNodeParameters where - _chainwebVersion = _chainwebVersion . _p2pNodeParamsPeerDb - -p2pCreateNode :: P2pNodeParameters -> IO P2pNode +p2pCreateNode :: HasVersion => P2pNodeParameters -> IO P2pNode p2pCreateNode params = do -- intialize P2P State sessionsVar <- newTVarIO mempty @@ -843,21 +836,21 @@ p2pCreateNode params = do -- -------------------------------------------------------------------------- -- -- Run P2P Node -p2pStartNodeInactive :: P2pNode -> IO () +p2pStartNodeInactive :: HasVersion => P2pNode -> IO () p2pStartNodeInactive node = do atomically (setInactive node) concurrently_ (runForever (logg node) "P2P.Node.awaitSessions" $ awaitSessions node) (runForever (logg node) "P2P.Node.newSessions" $ newSession node) -p2pStartNode :: P2pNode -> IO () +p2pStartNode :: HasVersion => P2pNode -> IO () p2pStartNode node = do atomically (setActive node) concurrently_ (runForever (logg node) "P2P.Node.awaitSessions" $ awaitSessions node) (runForever (logg node) "P2P.Node.newSessions" $ newSession node) -p2pStopNode :: P2P.Node.P2pNode -> IO () +p2pStopNode :: P2pNode -> IO () p2pStopNode node = do sessions <- atomically $ do setInactive node @@ -869,6 +862,5 @@ p2pStopNode node = do -- -- The node is stopped when an asynchronoous exception is raised in the thread. -- -p2pRunNode :: P2pNode -> IO () +p2pRunNode :: HasVersion => P2pNode -> IO () p2pRunNode n = finally (p2pStartNode n) (p2pStopNode n) - diff --git a/src/P2P/Node/PeerDB.hs b/src/P2P/Node/PeerDB.hs index af10898117..5adee8a088 100644 --- a/src/P2P/Node/PeerDB.hs +++ b/src/P2P/Node/PeerDB.hs @@ -326,35 +326,31 @@ insertPeerEntryList l m = foldl' (flip addPeerEntry) m l data PeerDb = PeerDb { _peerDbIsPrivate :: !Bool - , _peerDbChainwebVersion :: !ChainwebVersion , _peerDbLocalPeer :: !(Maybe PeerInfo) , _peerDbLock :: !(MVar ()) , _peerDbPeerSet :: !(TVar PeerSet) } deriving (Eq, Generic) -instance HasChainwebVersion PeerDb where - _chainwebVersion = _peerDbChainwebVersion - peerDbSetLocalPeer :: PeerInfo -> PeerDb -> IO PeerDb peerDbSetLocalPeer pinfo db = do peerDbDelete_ db True {- force deletion of sticky peers -} pinfo return db { _peerDbLocalPeer = Just pinfo } peerDbSnapshot :: PeerDb -> IO PeerSet -peerDbSnapshot (PeerDb _ _ _ _ var) = readTVarIO var +peerDbSnapshot (PeerDb _ _ _ var) = readTVarIO var {-# INLINE peerDbSnapshot #-} peerDbSnapshotSTM :: PeerDb -> STM PeerSet -peerDbSnapshotSTM (PeerDb _ _ _ _ var) = readTVar var +peerDbSnapshotSTM (PeerDb _ _ _ var) = readTVar var {-# INLINE peerDbSnapshotSTM #-} peerDbSize :: PeerDb -> IO Natural -peerDbSize (PeerDb _ _ _ _ var) = int . size <$!> readTVarIO var +peerDbSize (PeerDb _ _ _ var) = int . size <$!> readTVarIO var {-# INLINE peerDbSize #-} peerDbSizeSTM :: PeerDb -> STM Natural -peerDbSizeSTM (PeerDb _ _ _ _ var) = int . size <$!> readTVar var +peerDbSizeSTM (PeerDb _ _ _ var) = int . size <$!> readTVar var {-# INLINE peerDbSizeSTM #-} -- | Adds new 'PeerInfo' values for a given chain id. @@ -364,8 +360,8 @@ peerDbSizeSTM (PeerDb _ _ _ _ var) = int . size <$!> readTVar var -- contention. -- peerDbInsert :: PeerDb -> NetworkId -> PeerInfo -> IO () -peerDbInsert (PeerDb True _ _ _ _) _ _ = return () -peerDbInsert (PeerDb _ _ _ lock var) nid i = do +peerDbInsert (PeerDb True _ _ _) _ _ = return () +peerDbInsert (PeerDb _ _ lock var) nid i = do now <- getCurrentTime mark <- randomPeerMark withMVar lock @@ -378,7 +374,7 @@ peerDbInsert (PeerDb _ _ _ lock var) nid i = do -- | Delete a peer, identified by its host address, from the peer database. -- peerDbDelete :: PeerDb -> PeerInfo -> IO () -peerDbDelete (PeerDb _ _ _ lock var) i = withMVar lock +peerDbDelete (PeerDb _ _ lock var) i = withMVar lock . const . atomically . modifyTVar' var @@ -391,7 +387,7 @@ peerDbDelete_ -- ^ whether to force deletion of sticky peers (e.g. bootstrap peers) -> PeerInfo -> IO () -peerDbDelete_ (PeerDb _ _ _ lock var) forceSticky i = withMVar lock +peerDbDelete_ (PeerDb _ _ lock var) forceSticky i = withMVar lock . const . atomically . modifyTVar' var @@ -404,7 +400,7 @@ peerDbDelete_ (PeerDb _ _ _ lock var) forceSticky i = withMVar lock -- 3. have had more than 5 failed connection attempts. -- prunePeerDb :: LogFunction -> PeerDb -> IO () -prunePeerDb lg (PeerDb _ _ _ lock var) = do +prunePeerDb lg (PeerDb _ _ lock var) = do now <- getCurrentTime withMVar lock $ \_ -> do deletes <- atomically $ do @@ -423,8 +419,8 @@ prunePeerDb lg (PeerDb _ _ _ lock var) = do <> sshow (_peerAddr . _peerEntryInfo <$> IxSet.toList deletes) peerDbInsertList :: [PeerEntry] -> PeerDb -> IO () -peerDbInsertList _ (PeerDb True _ _ _ _) = return () -peerDbInsertList peers (PeerDb _ _ _ lock var) = do +peerDbInsertList _ (PeerDb True _ _ _) = return () +peerDbInsertList peers (PeerDb _ _ lock var) = do withMVar lock . const . atomically @@ -432,7 +428,7 @@ peerDbInsertList peers (PeerDb _ _ _ lock var) = do $ insertPeerEntryList peers peerDbInsertPeerInfoList :: NetworkId -> [PeerInfo] -> PeerDb -> IO () -peerDbInsertPeerInfoList _ _ (PeerDb True _ _ _ _) = return () +peerDbInsertPeerInfoList _ _ (PeerDb True _ _ _) = return () peerDbInsertPeerInfoList nid ps db = do now <- getCurrentTime entries <- traverse (mkEntry now) ps @@ -444,7 +440,7 @@ peerDbInsertPeerInfoList nid ps db = do & set peerEntryLastSuccess (LastSuccess (Just now)) peerDbInsertPeerInfoList_ :: Bool -> NetworkId -> [PeerInfo] -> PeerDb -> IO () -peerDbInsertPeerInfoList_ _ _ _ (PeerDb True _ _ _ _) = return () +peerDbInsertPeerInfoList_ _ _ _ (PeerDb True _ _ _) = return () peerDbInsertPeerInfoList_ sticky nid peerInfos db = do newEntries <- forM peerInfos $ \info -> do mark <- randomPeerMark @@ -452,17 +448,17 @@ peerDbInsertPeerInfoList_ sticky nid peerInfos db = do peerDbInsertList newEntries db peerDbInsertSet :: S.Set PeerEntry -> PeerDb -> IO () -peerDbInsertSet _ (PeerDb True _ _ _ _) = return () +peerDbInsertSet _ (PeerDb True _ _ _) = return () peerDbInsertSet s db = peerDbInsertList (F.toList s) db -newEmptyPeerDb :: ChainwebVersion -> IO PeerDb -newEmptyPeerDb v = PeerDb False v Nothing <$> newMVar () <*> newTVarIO mempty +newEmptyPeerDb :: IO PeerDb +newEmptyPeerDb = PeerDb False Nothing <$> newMVar () <*> newTVarIO mempty makePeerDbPrivate :: PeerDb -> PeerDb -makePeerDbPrivate (PeerDb _ v localPeer lock var) = PeerDb True v localPeer lock var +makePeerDbPrivate (PeerDb _ localPeer lock var) = PeerDb True localPeer lock var updatePeerDb :: PeerDb -> HostAddress -> (PeerEntry -> PeerEntry) -> IO () -updatePeerDb (PeerDb _ _ _ lock var) a f +updatePeerDb (PeerDb _ _ lock var) a f = withMVar lock . const . atomically . modifyTVar' var $ \s -> case getOne $ getEQ a s of Nothing -> s @@ -503,9 +499,9 @@ data SomePeerDb = forall v n . (KnownChainwebVersionSymbol v, SingI n) => SomePeerDb (PeerDbT v n) -somePeerDbVal :: ChainwebVersion -> NetworkId -> PeerDb -> SomePeerDb -somePeerDbVal (FromSingChainwebVersion (SChainwebVersion :: Sing v)) n db = f n - where - f (FromSingNetworkId (SChainNetwork SChainId :: Sing n)) = SomePeerDb $ PeerDbT @v @n db - f (FromSingNetworkId (SMempoolNetwork SChainId :: Sing n)) = SomePeerDb $ PeerDbT @v @n db - f (FromSingNetworkId (SCutNetwork :: Sing n)) = SomePeerDb $ PeerDbT @v @n db +somePeerDbVal :: HasVersion => NetworkId -> PeerDb -> SomePeerDb +somePeerDbVal n db = case implicitVersion of + (FromSingChainwebVersion (SChainwebVersion :: Sing v)) -> case n of + (FromSingNetworkId (SChainNetwork SChainId :: Sing n)) -> SomePeerDb $ PeerDbT @v @n db + (FromSingNetworkId (SMempoolNetwork SChainId :: Sing n)) -> SomePeerDb $ PeerDbT @v @n db + (FromSingNetworkId (SCutNetwork :: Sing n)) -> SomePeerDb $ PeerDbT @v @n db diff --git a/src/P2P/Node/RestAPI/Client.hs b/src/P2P/Node/RestAPI/Client.hs index 937601cf43..fe754aeeda 100644 --- a/src/P2P/Node/RestAPI/Client.hs +++ b/src/P2P/Node/RestAPI/Client.hs @@ -35,28 +35,28 @@ import P2P.Peer -- GET Peer Client peerGetClient - :: ChainwebVersion - -> NetworkId + :: HasVersion + => NetworkId -> Maybe Limit -> Maybe (NextItem Int) -> ClientM (Page (NextItem Int) PeerInfo) -peerGetClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) = f - where - f (FromSingNetworkId (SChainNetwork SChainId :: Sing n)) = client $ peerGetApi @v @n - f (FromSingNetworkId (SMempoolNetwork SChainId :: Sing n)) = client $ peerGetApi @v @n - f (FromSingNetworkId (SCutNetwork :: Sing n)) = client $ peerGetApi @v @n +peerGetClient nid = + case implicitVersion of + FromSingChainwebVersion (SChainwebVersion :: Sing v) -> case nid of + FromSingNetworkId (SChainNetwork SChainId :: Sing n) -> client $ peerGetApi @v @n + FromSingNetworkId (SMempoolNetwork SChainId :: Sing n) -> client $ peerGetApi @v @n + FromSingNetworkId (SCutNetwork :: Sing n) -> client $ peerGetApi @v @n -- -------------------------------------------------------------------------- -- -- PUT Peer Client peerPutClient - :: ChainwebVersion - -> NetworkId + :: HasVersion + => NetworkId -> PeerInfo -> ClientM NoContent -peerPutClient (FromSingChainwebVersion (SChainwebVersion :: Sing v)) = f - where - f (FromSingNetworkId (SChainNetwork SChainId :: Sing n)) = client $ peerPutApi @v @n - f (FromSingNetworkId (SMempoolNetwork SChainId :: Sing n)) = client $ peerPutApi @v @n - f (FromSingNetworkId (SCutNetwork :: Sing n)) = client $ peerPutApi @v @n - +peerPutClient n = case implicitVersion of + FromSingChainwebVersion (SChainwebVersion :: Sing v) -> case n of + FromSingNetworkId (SChainNetwork SChainId :: Sing n) -> client $ peerPutApi @v @n + FromSingNetworkId (SMempoolNetwork SChainId :: Sing n) -> client $ peerPutApi @v @n + FromSingNetworkId (SCutNetwork :: Sing n) -> client $ peerPutApi @v @n diff --git a/src/P2P/Node/RestAPI/Server.hs b/src/P2P/Node/RestAPI/Server.hs index 7e37e79ca1..b809b29a52 100644 --- a/src/P2P/Node/RestAPI/Server.hs +++ b/src/P2P/Node/RestAPI/Server.hs @@ -106,12 +106,12 @@ peerGetHandler db nid limit next = do (limit <|> Just defaultPeerInfoLimit) peerPutHandler - :: PeerDb - -> ChainwebVersion + :: HasVersion + => PeerDb -> NetworkId -> PeerInfo -> Handler NoContent -peerPutHandler db v nid e = liftIO (guardPeerDb v nid db e) >>= \case +peerPutHandler db nid e = liftIO (guardPeerDb nid db e) >>= \case Left failure -> throwError $ setErrText ("Invalid hostaddress: " <> sshow failure) err400 Right _ -> NoContent <$ liftIO (peerDbInsert db nid e) @@ -121,29 +121,27 @@ peerPutHandler db v nid e = liftIO (guardPeerDb v nid db e) >>= \case p2pServer :: forall (v :: ChainwebVersionT) (n :: NetworkIdT) - . SingI n + . (SingI n, HasVersion) => KnownChainwebVersionSymbol v => PeerDbT v n -> Server (P2pApi v n) p2pServer (PeerDbT db) = case sing @_ @n of SCutNetwork -> peerGetHandler db CutNetwork - :<|> peerPutHandler db v CutNetwork + :<|> peerPutHandler db CutNetwork SChainNetwork cid -> peerGetHandler db (ChainNetwork $ FromSing cid) - :<|> peerPutHandler db v (ChainNetwork $ FromSing cid) + :<|> peerPutHandler db (ChainNetwork $ FromSing cid) SMempoolNetwork cid -> peerGetHandler db (MempoolNetwork $ FromSing cid) - :<|> peerPutHandler db v (MempoolNetwork $ FromSing cid) - where - v = _chainwebVersion db + :<|> peerPutHandler db (MempoolNetwork $ FromSing cid) -- -------------------------------------------------------------------------- -- -- Application for a single P2P Network chainP2pApp :: forall v n - . KnownChainwebVersionSymbol v + . (KnownChainwebVersionSymbol v, HasVersion) => SingI n => PeerDbT v n -> Application @@ -166,30 +164,30 @@ chainP2pApiLayout _ = case sing @_ @n of -- -------------------------------------------------------------------------- -- -- Multichain Server -someP2pServer :: SomePeerDb -> SomeServer +someP2pServer :: HasVersion => SomePeerDb -> SomeServer someP2pServer (SomePeerDb (db :: PeerDbT v n)) = case sing @_ @n of SCutNetwork -> SomeServer (Proxy @(P2pApi v n)) (p2pServer db) SChainNetwork SChainId -> SomeServer (Proxy @(P2pApi v n)) (p2pServer db) SMempoolNetwork SChainId -> SomeServer (Proxy @(P2pApi v n)) (p2pServer db) -someP2pServers :: ChainwebVersion -> [(NetworkId, PeerDb)] -> SomeServer -someP2pServers v = mconcat - . fmap (someP2pServer . uncurry (somePeerDbVal v)) +someP2pServers :: HasVersion => [(NetworkId, PeerDb)] -> SomeServer +someP2pServers = mconcat + . fmap (someP2pServer . uncurry somePeerDbVal) -- -------------------------------------------------------------------------- -- -- Run Server serveP2pOnPort - :: Port - -> ChainwebVersion + :: HasVersion + => Port -> [(NetworkId, PeerDb)] -> IO () -serveP2pOnPort p v = run (int p) . someServerApplication . someP2pServers v +serveP2pOnPort p = run (int p) . someServerApplication . someP2pServers serveP2pSocket - :: Settings + :: HasVersion + => Settings -> Socket - -> ChainwebVersion -> [(NetworkId, PeerDb)] -> IO () -serveP2pSocket s sock v = runSettingsSocket s sock . someServerApplication . someP2pServers v +serveP2pSocket s sock = runSettingsSocket s sock . someServerApplication . someP2pServers diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index 54121d443f..dc409b3e2c 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -146,7 +146,7 @@ arbitraryBlockTimeOffset lower upper = do -- | Solve Work. Doesn't check that the nonce and the time are valid. -- -solveWork :: HasCallStack => MiningWork -> Nonce -> Time Micros -> SolvedWork +solveWork :: (HasCallStack, HasVersion) => MiningWork -> Nonce -> Time Micros -> SolvedWork solveWork work n t = s { _solvedWorkCreationTime = BlockCreationTime t , _solvedWorkNonce = n @@ -176,6 +176,7 @@ instance Exception MineFailure testMine :: forall cid . HasChainId cid + => HasVersion => WebBlockHeaderDb -> Nonce -> Time Micros @@ -190,6 +191,7 @@ testMine wdb n t payloadHash i c = testMine' wdb n (\_ _ -> t) payloadHash i c testMine' :: forall cid . HasChainId cid + => HasVersion => WebBlockHeaderDb -> Nonce -> GenBlockTime @@ -206,6 +208,7 @@ testMine' wdb n t payloadHash i c = testMineWithPayloadHash :: forall cid hdb . HasChainId cid + => HasVersion => ChainValueCasLookup hdb BlockHeader => hdb -> Nonce @@ -226,6 +229,7 @@ createNewCut :: HasCallStack => MonadCatch m => HasChainId cid + => HasVersion => (ChainValue BlockHash -> m BlockHeader) -> Nonce -> Time Micros @@ -251,6 +255,7 @@ createNewCut1Second . HasCallStack => MonadCatch m => HasChainId cid + => HasVersion => (ChainValue BlockHash -> m BlockHeader) -> Nonce -> BlockPayloadHash @@ -263,23 +268,23 @@ createNewCut1Second db n p i c -- -------------------------------------------------------------------------- -- -- Arbitrary Cuts -arbitraryChainId :: HasChainwebVersion v => v -> T.Gen ChainId -arbitraryChainId = T.elements . toList . chainIds +arbitraryChainId :: HasVersion => T.Gen ChainId +arbitraryChainId = T.elements $ toList chainIds arbitraryCut :: HasCallStack - => ChainwebVersion - -> T.Gen Cut -arbitraryCut v = T.sized $ \s -> do + => HasVersion + => T.Gen Cut +arbitraryCut = T.sized $ \s -> do k <- T.chooseEnum (0,s) fst <$> foldlM (\x _ -> genCut x) (genesis, initDb) [0..(k-1)] where - genesis = genesisCut v + genesis = genesisCut initDb = foldl' (\d h -> HM.insert (view blockHash h) h d) mempty $ _cutMap genesis genCut :: (Cut, TestHeaderMap) -> T.Gen (Cut, TestHeaderMap) genCut (c, db) = do - cids <- T.shuffle (toList $ chainIds v) + cids <- T.shuffle (toList chainIds) S.each cids & S.mapMaybeM (mine db c) & S.map (\(T2 h x) -> (x, HM.insert (view blockHash h) h db)) @@ -290,7 +295,7 @@ arbitraryCut v = T.sized $ \s -> do mine db c cid = do n <- Nonce <$> T.arbitrary let pay = _payloadWithOutputsPayloadHash $ testPayload - $ B8.intercalate "," [ sshow v, sshow cid, "TEST PAYLOAD"] + $ B8.intercalate "," [ sshow implicitVersion, sshow cid, "TEST PAYLOAD"] case try (createNewCut1Second (testLookup db) n pay cid c) of Left e -> throw e Right (Left BadAdjacents) -> return Nothing @@ -304,6 +309,7 @@ arbitraryChainGraphChainId = T.elements . toList . graphChainIds -- arbitraryWebChainCut :: HasCallStack + => HasVersion => WebBlockHeaderDb -> Cut -- @genesisCut Test@ is always a valid cut @@ -314,6 +320,7 @@ arbitraryWebChainCut wdb i = arbitraryWebChainCut_ wdb i 0 -- arbitraryWebChainCut_ :: HasCallStack + => HasVersion => WebBlockHeaderDb -> Cut -- @genesisCut Test@ is always a valid cut @@ -328,7 +335,7 @@ arbitraryWebChainCut_ wdb initialCut seed = do cids <- T.pick $ T.shuffle $ toList - $ chainIds initialCut + $ chainIds S.each cids & S.mapMaybeM (mine c) & S.map (\(T2 _ c') -> c') @@ -343,16 +350,12 @@ arbitraryWebChainCut_ wdb initialCut seed = do Left BadAdjacents -> return Nothing Left e -> throw e where - v = _chainwebVersion wdb pay = _payloadWithOutputsPayloadHash $ testPayload - $ B8.intercalate "," [ sshow v, sshow cid, "TEST PAYLOAD"] + $ B8.intercalate "," [ sshow (_versionName implicitVersion), sshow cid, "TEST PAYLOAD"] -- -------------------------------------------------------------------------- -- -- Arbitrary Fork -testGenCut :: WebBlockHeaderDb -> Cut -testGenCut = genesisCut . _chainwebVersion - data TestFork = TestFork { _testForkBase :: !Cut , _testForkLeft :: !Cut @@ -360,7 +363,10 @@ data TestFork = TestFork } deriving (Show, Eq, Ord, Generic) -arbitraryJoin :: WebBlockHeaderDb -> T.PropertyM IO (Join Int) +arbitraryJoin + :: HasVersion + => WebBlockHeaderDb + -> T.PropertyM IO (Join Int) arbitraryJoin wdb = do TestFork _ cl cr <- arbitraryFork wdb liftIO $ join wdb (prioritizeHeavier cl cr) cl cr @@ -370,10 +376,11 @@ arbitraryJoin wdb = do -- TODO: provide option to fork of elsewhere -- arbitraryFork - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO TestFork arbitraryFork wdb = do - base <- arbitraryWebChainCut wdb (testGenCut wdb) + base <- arbitraryWebChainCut wdb genesisCut TestFork base <$> arbitraryWebChainCut_ wdb base 11 <*> arbitraryWebChainCut_ wdb base 23 @@ -401,15 +408,17 @@ arbitraryFork wdb = do -- Join prop_joinIdempotent - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_joinIdempotent wdb = do - c <- arbitraryWebChainCut wdb (testGenCut wdb) + c <- arbitraryWebChainCut wdb genesisCut T.run $ (==) c <$> joinIntoHeavier wdb c c -- FIXME! prop_joinCommutative - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_joinCommutative wdb = do TestFork _ cl cr <- arbitraryFork wdb @@ -420,7 +429,8 @@ prop_joinCommutative wdb = do -- Fails for heuristic joins -- prop_joinAssociative - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_joinAssociative wdb = do TestFork _ c0 c1 <- arbitraryFork wdb @@ -442,25 +452,27 @@ prop_joinAssociative wdb = do <*> (m c0 c10 >>= \x -> m x c11) prop_joinIdentity - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_joinIdentity wdb = do - c <- arbitraryWebChainCut wdb gen - T.run $ (==) c <$> joinIntoHeavier wdb gen c + c <- arbitraryWebChainCut wdb genesisCut + T.run $ (==) c <$> joinIntoHeavier wdb genesisCut c where - gen = testGenCut wdb -- Meet prop_meetIdempotent - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_meetIdempotent wdb = do - c <- arbitraryWebChainCut wdb (testGenCut wdb) + c <- arbitraryWebChainCut wdb genesisCut T.run $ (==) c <$> meet wdb c c prop_meetCommutative - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_meetCommutative wdb = do TestFork _ cl cr <- arbitraryFork wdb @@ -469,7 +481,8 @@ prop_meetCommutative wdb = do <*> meet wdb cr cl prop_meetAssociative - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_meetAssociative wdb = do TestFork _ c0 c1 <- arbitraryFork wdb @@ -485,18 +498,19 @@ prop_meetAssociative wdb = do -- | this a corollary of 'prop_joinIdentity' and 'prop_meetJoinAbsorption' -- prop_meetZeroAbsorption - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_meetZeroAbsorption wdb = do - c <- arbitraryWebChainCut wdb gen + c <- arbitraryWebChainCut wdb genesisCut T.run $ do - c' <- meet wdb gen c + c' <- meet wdb genesisCut c return (c == c') where - gen = testGenCut wdb prop_joinMeetAbsorption - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_joinMeetAbsorption wdb = do TestFork _ c0 c1 <- arbitraryFork wdb @@ -505,7 +519,8 @@ prop_joinMeetAbsorption wdb = do return (c0' == c0) prop_meetJoinAbsorption - :: WebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool prop_meetJoinAbsorption wdb = do TestFork _ c0 c1 <- arbitraryFork wdb @@ -513,7 +528,9 @@ prop_meetJoinAbsorption wdb = do c0' <- meet wdb c0 =<< joinIntoHeavier wdb c0 c1 return (c0' == c0) -properties_lattice :: RocksDb -> ChainwebVersion -> [(String, T.Property)] +properties_lattice + :: HasVersion + => RocksDb -> ChainwebVersion -> [(String, T.Property)] properties_lattice db v = [ ("joinIdemPotent", ioTest db v prop_joinIdempotent) , ("joinCommutative", ioTest db v prop_joinCommutative) @@ -529,7 +546,9 @@ properties_lattice db v = , ("meetJoinAbsorption", ioTest db v prop_meetJoinAbsorption) -- Fails ] -properties_lattice_passing :: RocksDb -> ChainwebVersion -> [(String, T.Property)] +properties_lattice_passing + :: HasVersion + => RocksDb -> ChainwebVersion -> [(String, T.Property)] properties_lattice_passing db v = [ ("joinIdemPotent", ioTest db v prop_joinIdempotent) , ("joinCommutative", ioTest db v prop_joinCommutative) @@ -548,9 +567,9 @@ properties_lattice_passing db v = prop_cutBraiding :: Cut -> Bool prop_cutBraiding = either throw (const True) . checkBraidingOfCut -prop_cutBraidingGenesis :: ChainwebVersion -> Bool -prop_cutBraidingGenesis v = either throw (const True) - $ checkBraidingOfCut (genesisCut v) +prop_cutBraidingGenesis :: HasVersion => Bool +prop_cutBraidingGenesis = either throw (const True) + $ checkBraidingOfCut genesisCut -- TODO -- @@ -559,24 +578,27 @@ prop_cutBraidingGenesis v = either throw (const True) -- -- * this order induces a lattice -properties_cut :: ChainwebVersion -> [(String, T.Property)] -properties_cut v = - [ ("Cut has valid braiding" , T.property $ T.forAll (arbitraryCut v) prop_cutBraiding) - , ("Genesis Cut has valid braiding", T.property (prop_cutBraidingGenesis v)) +properties_cut :: HasVersion => [(String, T.Property)] +properties_cut = + [ ("Cut has valid braiding" , T.property $ T.forAll arbitraryCut prop_cutBraiding) + , ("Genesis Cut has valid braiding", T.property prop_cutBraidingGenesis) ] -- -------------------------------------------------------------------------- -- -- Meet Properties -prop_meetGenesisCut :: WebBlockHeaderDb -> T.PropertyM IO Bool -prop_meetGenesisCut wdb = liftIO $ (==) c <$> meet wdb c c - where - c = testGenCut wdb +prop_meetGenesisCut + :: HasVersion + => WebBlockHeaderDb -> T.PropertyM IO Bool +prop_meetGenesisCut wdb = liftIO $ + (==) genesisCut <$> meet wdb genesisCut genesisCut -- -------------------------------------------------------------------------- -- -- Misc Properties -prop_arbitraryForkBraiding :: RocksDb -> ChainwebVersion -> T.Property +prop_arbitraryForkBraiding + :: HasVersion + => RocksDb -> ChainwebVersion -> T.Property prop_arbitraryForkBraiding db v = ioTest db v $ \wdb -> do TestFork b cl cr <- arbitraryFork wdb T.assert (prop_cutBraiding b) @@ -584,24 +606,32 @@ prop_arbitraryForkBraiding db v = ioTest db v $ \wdb -> do T.assert (prop_cutBraiding cr) return True -prop_joinBase :: RocksDb -> ChainwebVersion -> T.Property +prop_joinBase + :: HasVersion + => RocksDb -> ChainwebVersion -> T.Property prop_joinBase db v = ioTest db v $ \wdb -> do TestFork b cl cr <- arbitraryFork wdb m <- liftIO $ join wdb (prioritizeHeavier cl cr) cl cr return (_joinBase m == b) -prop_joinBaseMeet :: RocksDb -> ChainwebVersion -> T.Property +prop_joinBaseMeet + :: HasVersion + => RocksDb -> ChainwebVersion -> T.Property prop_joinBaseMeet db v = ioTest db v $ \wdb -> do TestFork _ a b <- arbitraryFork wdb liftIO $ (==) <$> meet wdb a b <*> (_joinBase <$> join wdb (prioritizeHeavier a b) a b) -properties_testMining :: RocksDb -> ChainwebVersion -> [(String, T.Property)] +properties_testMining + :: HasVersion + => RocksDb -> ChainwebVersion -> [(String, T.Property)] properties_testMining db v = [ ("Cuts of arbitrary fork have valid braiding", prop_arbitraryForkBraiding db v)] -properties_miscCut :: RocksDb -> ChainwebVersion -> [(String, T.Property)] +properties_miscCut + :: HasVersion + => RocksDb -> ChainwebVersion -> [(String, T.Property)] properties_miscCut db v = [ ("prop_joinBase", prop_joinBase db v) , ("prop_joinBaseMeet", prop_joinBaseMeet db v) @@ -613,10 +643,11 @@ properties_miscCut db v = -- Other Miscelaneous Properties prop_blockCountAtChainHeight :: ChainGraph -> ChainGraph -> T.Property -prop_blockCountAtChainHeight g0 g1 = T.counterexample (show v) - $ T.conjoin $ p <$> [0..10] +prop_blockCountAtChainHeight g0 g1 = + T.counterexample (show v) + $ T.conjoin $ p <$> [0..10] where - p i = T.counterexample (show i) $ int @_ @Int (globalBlockCountAt v i) T.=== h (int i) + p i = withVersion v $ T.counterexample (show i) $ int @_ @Int (globalBlockCountAt i) T.=== h (int i) h i = min 8 (i + 1) * int (order g0) + max 0 (i - 7) * int (order g1) -- (8, g1) :| [(0, g0)] @@ -643,8 +674,9 @@ properties_misc = properties :: RocksDb -> [(String, T.Property)] properties db - = properties_lattice_passing db v - <> properties_cut v + = withVersion v + $ properties_lattice_passing db v + <> withVersion v properties_cut <> properties_testMining db v <> properties_miscCut db v <> properties_misc @@ -661,7 +693,7 @@ ioTest -> T.Property ioTest baseDb v f = T.monadicIO $ do db' <- liftIO $ testRocksDb "Chainweb.Test.Cut" baseDb - liftIO (initWebBlockHeaderDb db' v) >>= f >>= T.assert + liftIO (withVersion v initWebBlockHeaderDb db') >>= f >>= T.assert liftIO $ deleteNamespaceRocksDb db' pickBlind :: T.Gen a -> T.PropertyM IO a diff --git a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs index 084c6631e0..3af5ebf3f2 100644 --- a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs +++ b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs @@ -54,18 +54,14 @@ data TestBlockDb = TestBlockDb , _bdbCut :: MVar Cut } -instance HasChainwebVersion TestBlockDb where - _chainwebVersion = _chainwebVersion . _bdbWebBlockHeaderDb - -- | Initialize TestBlockDb. -mkTestBlockDb :: ChainwebVersion -> RocksDb -> ResourceT IO TestBlockDb -mkTestBlockDb cv rdb = do +mkTestBlockDb :: HasVersion => RocksDb -> ResourceT IO TestBlockDb +mkTestBlockDb rdb = do testRdb <- withTestRocksDb "mkTestBlockDb" rdb liftIO $ do - wdb <- initWebBlockHeaderDb testRdb cv + wdb <- initWebBlockHeaderDb testRdb let pdb = newPayloadDb testRdb - -- initializePayloadDb cv pdb - initCut <- newMVar $ genesisCut cv + initCut <- newMVar genesisCut return $! TestBlockDb wdb pdb initCut -- | Initialize TestBlockDb in 'IO'. This is discouraged in most test environments. @@ -73,13 +69,12 @@ mkTestBlockDb cv rdb = do -- -- Take care to call 'deleteNamespaceRocksDb' on the 'RocksDb' that this returns -- in between test runs. -mkTestBlockDbIO :: ChainwebVersion -> RocksDb -> IO (T2 TestBlockDb RocksDb) -mkTestBlockDbIO v rdb = do +mkTestBlockDbIO :: HasVersion => RocksDb -> IO (T2 TestBlockDb RocksDb) +mkTestBlockDbIO rdb = do testRdb <- testRocksDb "mkTestBlockDbIO" rdb - wdb <- initWebBlockHeaderDb testRdb v + wdb <- initWebBlockHeaderDb testRdb let pdb = newPayloadDb testRdb - -- initializePayloadDb v pdb - initCut <- newMVar $ genesisCut v + initCut <- newMVar genesisCut return $! T2 (TestBlockDb wdb pdb initCut) testRdb -- | Add a block. @@ -88,7 +83,8 @@ mkTestBlockDbIO v rdb = do -- the chain is blocked. Retry with another chain! -- addTestBlockDb - :: TestBlockDb + :: HasVersion + => TestBlockDb -> BlockHeight -> Nonce -> GenBlockTime @@ -137,6 +133,6 @@ setCutTestBlockDb :: TestBlockDb -> Cut -> IO () setCutTestBlockDb (TestBlockDb _ _ cmv) c = void $ swapMVar cmv c -- | Convenience accessor -getBlockHeaderDb :: MonadThrow m => ChainId -> TestBlockDb -> m BlockHeaderDb +getBlockHeaderDb :: HasVersion => MonadThrow m => ChainId -> TestBlockDb -> m BlockHeaderDb getBlockHeaderDb cid (TestBlockDb wdb _ _) = getWebBlockHeaderDb wdb cid diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index 7d384b8ad6..a91e943bda 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -123,11 +123,11 @@ import Test.Tasty.HUnit -- | Test Configuration for a scaled down Test chainweb. -- multiConfig - :: ChainwebVersion - -> Natural + :: HasVersion + => Natural -- ^ number of node -> ChainwebConfiguration -multiConfig v n = defaultChainwebConfiguration v +multiConfig n = defaultChainwebConfiguration implicitVersion & set (configP2p . p2pConfigPeer . peerConfigHost) host & set (configP2p . p2pConfigPeer . peerConfigPort) 0 & set (configP2p . p2pConfigPeer . peerConfigInterface) interface @@ -196,7 +196,8 @@ multiBootstrapConfig conf = conf -- Minimal Node Setup that logs conensus state to the given mvar harvestConsensusState - :: GenericLogger + :: HasVersion + => GenericLogger -> MVar ConsensusState -> Int -> StartedChainweb logger @@ -306,15 +307,15 @@ runNodesForSeconds loglevel write baseConf n (Seconds seconds) rdb pactDbDir inn -- -- 4. At the end we compare how long each thread took. The node thread should -- -- outlive the compaction thread. -- compactLiveNodeTest :: () +-- => HasVersion -- => LogLevel --- -> ChainwebVersion -- -> Natural -- -> RocksDb -- -> FilePath -- -> FilePath -- -> (String -> IO ()) -- -> IO () --- compactLiveNodeTest logLevel v n rocksDb srcPactDir targetPactDir step = do +-- compactLiveNodeTest logLevel n rocksDb srcPactDir targetPactDir step = do -- let logFun = step . T.unpack -- let logger = genericLogger logLevel logFun @@ -324,12 +325,12 @@ runNodesForSeconds loglevel write baseConf n (Seconds seconds) rdb pactDbDir inn -- -- in RocksDB, rather than the number of blocks in all chains -- -- on the current cut. This is fine because we ultimately just want -- -- to make sure that we are making progress (i.e, new blocks). --- stateVar <- newMVar (emptyConsensusState v) +-- stateVar <- newMVar emptyConsensusState -- let ct :: Int -> StartedChainweb logger -> IO () -- ct = harvestConsensusState logger stateVar -- do --- runNodesForSeconds logLevel logFun (multiConfig v n) n 10 rocksDb srcPactDir ct --- Just stats1 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) +-- runNodesForSeconds logLevel logFun (multiConfig n) n 10 rocksDb srcPactDir ct +-- Just stats1 <- consensusStateSummary <$> swapMVar stateVar emptyConsensusState -- assertGe "average block count before proceeding" (Actual $ _statBlockCount stats1) (Expected 50) -- logFun $ sshow stats1 @@ -351,8 +352,8 @@ runNodesForSeconds loglevel write baseConf n (Seconds seconds) rdb pactDbDir inn -- -- compacted state (see that we are using 'srcPactDir' here). This is -- -- because we are only trying to see that compacting a node as it is, -- -- does not disrupt it. --- runNodesForSeconds logLevel logFun (multiConfig v n) n 60 rocksDb srcPactDir ct --- Just stats2 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) +-- runNodesForSeconds logLevel logFun (multiConfig n) n 60 rocksDb srcPactDir ct +-- Just stats2 <- consensusStateSummary <$> swapMVar stateVar emptyConsensusState -- assertGe "average block count before proceeding" (Actual $ _statBlockCount stats2) (Expected 100) -- logFun $ sshow stats2 @@ -378,14 +379,14 @@ runNodesForSeconds loglevel write baseConf n (Seconds seconds) rdb pactDbDir inn -- 6. Lastly, we compare the state between the two databases (original and -- copy) at the verified 'BlockHeight'. There should be no differences. pactImportTest :: () + => HasVersion => LogLevel - -> ChainwebVersion -> Natural -> RocksDb -> FilePath -> (String -> IO ()) -> IO () -pactImportTest logLevel v n rocksDb pactDir step = do +pactImportTest logLevel n rocksDb pactDir step = do let logFun = step . T.unpack let logger = genericLogger logLevel logFun @@ -396,16 +397,16 @@ pactImportTest logLevel v n rocksDb pactDir step = do -- in RocksDB, rather than the number of blocks in all chains -- on the current cut. This is fine because we ultimately just want -- to make sure that we are making progress (i.e, new blocks). - stateVar <- newMVar (emptyConsensusState v) + stateVar <- newMVar emptyConsensusState let ct :: Int -> StartedChainweb logger -> IO () ct = harvestConsensusState logger stateVar - runNodesForSeconds logLevel logFun (multiConfig v n) n 10 rocksDb pactDir ct - consensusState <- swapMVar stateVar (emptyConsensusState v) + runNodesForSeconds logLevel logFun (multiConfig n) n 10 rocksDb pactDir ct + consensusState <- swapMVar stateVar emptyConsensusState Just stats1 <- pure $ consensusStateSummary consensusState assertGe "average block count before proceeding" (Actual $ _statBlockCount stats1) (Expected 50) logFun $ sshow stats1 - let chains = allChains v + let chains = allChains forM_ [0 .. int @_ @Word n - 1] $ \nid -> do let logger' = addLabel ("nodeId", sshow nid) logger logFunctionText logger' Info $ "Verifying node " <> sshow nid @@ -417,8 +418,8 @@ pactImportTest logLevel v n rocksDb pactDir step = do let rdb = rocksDb { _rocksDbNamespace = T.encodeUtf8 $ toText @Word nid } latestBlockHeight <- do - wbhdb <- initWebBlockHeaderDb rdb v - latestCutHeaders <- readHighestCutHeaders v (\_ _ -> pure ()) wbhdb (cutHashesTable rdb) + wbhdb <- initWebBlockHeaderDb rdb + latestCutHeaders <- readHighestCutHeaders (\_ _ -> pure ()) wbhdb (cutHashesTable rdb) pure $ maximum $ fmap (view blockHeight) latestCutHeaders let targetChunkSize :: BlockHeight @@ -432,17 +433,17 @@ pactImportTest logLevel v n rocksDb pactDir step = do (\t -> if t >= targetChunkSize then Just (t, t - targetChunkSize) else Nothing) (latestBlockHeight - latestBlockHeight `mod` targetChunkSize) - grands <- GrandHash.Calc.pactCalc logger' v pactConns rdb (GrandHash.Calc.TargetAll targets) + grands <- GrandHash.Calc.pactCalc logger' pactConns rdb (GrandHash.Calc.TargetAll targets) logFunctionText logger' Info "Verifying state" - snapshot@(snapshotBlockHeight, snapshotHashes) <- GrandHash.Import.pactVerify logger' v pactConns rdb grands + snapshot@(snapshotBlockHeight, snapshotHashes) <- GrandHash.Import.pactVerify logger' pactConns rdb grands logFunctionText logger' Debug $ "SNAPSHOT BLOCKHEIGHT = " <> sshow (fst snapshot) logFunctionText logger' Debug $ "SNAPSHOT HASHES = " <> sshow (HM.map (\s -> (T.decodeUtf8 (Base16.encode (getChainGrandHash s.pactHash)), view blockHeight s.blockHeader)) (snd snapshot)) logFunctionText logger' Info "Making a copy of the pact state, and dropping the post-verified content" withSystemTempDirectory "pact-copy" $ \copyPactDir -> do let copyNodeDir = copyPactDir show nid - GrandHash.Import.pactDropPostVerified logger' v pactNodeDir copyNodeDir snapshotBlockHeight snapshotHashes + GrandHash.Import.pactDropPostVerified logger' pactNodeDir copyNodeDir snapshotBlockHeight snapshotHashes GrandHash.Utils.withConnections logger' copyNodeDir chains $ \pactCopyConns -> do logFunctionText logger' Info "Checking latest block heights on copy to make sure they match verified state" @@ -495,8 +496,8 @@ pactImportTest logLevel v n rocksDb pactDir step = do -- -- We restart all nodes with the same database dirs -- -- We observe that they can make progress -- compactAndResumeTest :: () +-- => HasVersion -- => LogLevel --- -> ChainwebVersion -- -> Natural -- -> RocksDb -- -> RocksDb @@ -504,7 +505,7 @@ pactImportTest logLevel v n rocksDb pactDir step = do -- -> FilePath -- -> (String -> IO ()) -- -> IO () --- compactAndResumeTest logLevel v n srcRocksDb targetRocksDb srcPactDir targetPactDir step = do +-- compactAndResumeTest logLevel n srcRocksDb targetRocksDb srcPactDir targetPactDir step = do -- let logFun = step . T.unpack -- let logger = genericLogger logLevel logFun @@ -513,11 +514,11 @@ pactImportTest logLevel v n rocksDb pactDir step = do -- -- in RocksDB, rather than the number of blocks in all chains -- -- on the current cut. This is fine because we ultimately just want -- -- to make sure that we are making progress (i.e, new blocks). --- stateVar <- newMVar (emptyConsensusState v) +-- stateVar <- newMVar emptyConsensusState -- let ct :: Int -> StartedChainweb logger -> IO () -- ct = harvestConsensusState logger stateVar --- runNodesForSeconds logLevel logFun (multiConfig v n) n 10 srcRocksDb srcPactDir ct --- Just stats1 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) +-- runNodesForSeconds logLevel logFun (multiConfig n) n 10 srcRocksDb srcPactDir ct +-- Just stats1 <- consensusStateSummary <$> swapMVar stateVar emptyConsensusState -- assertGe "average block count before compaction" (Actual $ _statBlockCount stats1) (Expected 50) -- logFun $ sshow stats1 @@ -535,36 +536,36 @@ pactImportTest logLevel v n rocksDb pactDir step = do -- forM_ [0 .. int @_ @Word n - 1] $ \nid -> do -- let srcRdb = srcRocksDb { _rocksDbNamespace = T.encodeUtf8 (toText nid) } -- let tgtRdb = targetRocksDb { _rocksDbNamespace = T.encodeUtf8 (toText nid) } --- Sigma.compactRocksDb (addLabel ("nodeId", sshow nid) logger) v (allChains v) 20 srcRdb tgtRdb +-- Sigma.compactRocksDb (addLabel ("nodeId", sshow nid) logger) allChains 20 srcRdb tgtRdb -- logFun "phase 3... restarting nodes and ensuring progress" --- runNodesForSeconds logLevel logFun (multiConfig v n) { _configFullHistoricPactState = False } n 10 targetRocksDb targetPactDir ct --- Just stats2 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) +-- runNodesForSeconds logLevel logFun (multiConfig n) { _configFullHistoricPactState = False } n 10 targetRocksDb targetPactDir ct +-- Just stats2 <- consensusStateSummary <$> swapMVar stateVar emptyConsensusState -- -- We ensure that we've gotten to at least 1.5x the previous block count -- assertGe "average block count post-compaction" (Actual $ _statBlockCount stats2) (Expected (3 * _statBlockCount stats1 `div` 2)) -- logFun $ sshow stats2 replayTest - :: LogLevel - -> ChainwebVersion + :: HasVersion + => LogLevel -> Natural -> RocksDb -> FilePath -> (String -> IO ()) -> IO () -replayTest loglevel v n rdb pactDbDir step = do +replayTest loglevel n rdb pactDbDir step = do let tastylog = step . T.unpack let logFun = step . T.unpack tastylog "phase 1..." - stateVar <- newMVar $ emptyConsensusState v + stateVar <- newMVar emptyConsensusState let ct = harvestConsensusState (genericLogger loglevel logFun) stateVar - runNodesForSeconds loglevel logFun (multiConfig v n) n 60 rdb pactDbDir ct - Just stats1 <- consensusStateSummary <$> swapMVar stateVar (emptyConsensusState v) + runNodesForSeconds loglevel logFun (multiConfig n) n 60 rdb pactDbDir ct + Just stats1 <- consensusStateSummary <$> swapMVar stateVar emptyConsensusState assertGe "maximum cut height before reset" (Actual $ _statMaxHeight stats1) (Expected $ 10) tastylog $ sshow stats1 tastylog $ "phase 2... resetting" - runNodesForSeconds loglevel logFun (multiConfig v n & set (configCuts . cutInitialBlockHeightLimit) (Just 5)) n 30 rdb pactDbDir ct - state2 <- swapMVar stateVar (emptyConsensusState v) + runNodesForSeconds loglevel logFun (multiConfig n & set (configCuts . cutInitialBlockHeightLimit) (Just 5)) n 30 rdb pactDbDir ct + state2 <- swapMVar stateVar emptyConsensusState let stats2 = fromJuste $ consensusStateSummary state2 tastylog $ sshow stats2 assertGe "block count after reset" (Actual $ _statBlockCount stats2) (Expected $ _statBlockCount stats1) @@ -572,7 +573,7 @@ replayTest loglevel v n rdb pactDbDir step = do let replayInitialHeight = 5 firstReplayCompleteRef <- newIORef False runNodesForSeconds loglevel logFun - (multiConfig v n + (multiConfig n & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight) & set configOnlySync True) n (Seconds 20) rdb pactDbDir $ \nid cw -> case cw of @@ -593,7 +594,7 @@ replayTest loglevel v n rdb pactDbDir step = do tastylog $ "phase 4... replaying with fast-forward limit" secondReplayCompleteRef <- newIORef False runNodesForSeconds loglevel logFun - (multiConfig v n + (multiConfig n & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight) & set (configCuts . cutFastForwardBlockHeightLimit) (Just fastForwardHeight) & set configOnlySync True) @@ -615,15 +616,15 @@ replayTest loglevel v n rdb pactDbDir step = do -- Test test - :: LogLevel - -> ChainwebVersion + :: HasVersion + => LogLevel -> Natural -> Seconds -> RocksDb -> FilePath -> (String -> IO ()) -> IO () -test loglevel v n seconds rdb pactDbDir step = do +test loglevel n seconds rdb pactDbDir step = do -- Count log messages and only print the first 60 messages let tastylog = step . T.unpack let logFun = tastylog @@ -631,15 +632,15 @@ test loglevel v n seconds rdb pactDbDir step = do var <- newMVar (0 :: Int) let countedLog msg = modifyMVar_ var $ \c -> force (succ c) <$ when (c < maxLogMsgs) (logFun msg) - stateVar <- newMVar (emptyConsensusState v) - runNodesForSeconds loglevel countedLog (multiConfig v n) n seconds rdb pactDbDir + stateVar <- newMVar emptyConsensusState + runNodesForSeconds loglevel countedLog (multiConfig n) n seconds rdb pactDbDir (harvestConsensusState (genericLogger loglevel logFun) stateVar) consensusStateSummary <$> readMVar stateVar >>= \case Nothing -> assertFailure "chainweb didn't make any progress" Just stats -> do logsCount <- readMVar var tastylog $ "Number of logs: " <> sshow logsCount - tastylog $ "Expected BlockCount: " <> sshow (expectedBlockCount v seconds) -- 80 + 19.5 * 20 + tastylog $ "Expected BlockCount: " <> sshow (expectedBlockCount seconds) -- 80 + 19.5 * 20 tastylog $ encodeToText stats tastylog $ encodeToText $ object [ "maxEfficiency%" .= (realToFrac (bc $ _statMaxHeight stats) * (100 :: Double) / int (_statBlockCount stats)) @@ -660,10 +661,10 @@ test loglevel v n seconds rdb pactDbDir step = do (assertLe "average cut height") (Actual $ _statAvgHeight stats) (Expected $ _statAvgHeight u) where - l = lowerStats v seconds - u = upperStats v seconds + l = lowerStats seconds + u = upperStats seconds - bc x = blockCountAtCutHeight v x - order (chainGraphAtCutHeight v x) + bc x = blockCountAtCutHeight x - order (chainGraphAtCutHeight x) -- -------------------------------------------------------------------------- -- -- Results @@ -675,18 +676,15 @@ data ConsensusState = ConsensusState , _stateCutMap :: !(HM.HashMap Int Cut) -- ^ Node Id map - , _stateChainwebVersion :: !ChainwebVersion } deriving (Show, Generic, NFData) -instance HasChainwebVersion ConsensusState where - _chainwebVersion = _stateChainwebVersion - -emptyConsensusState :: ChainwebVersion -> ConsensusState -emptyConsensusState v = ConsensusState mempty mempty v +emptyConsensusState :: ConsensusState +emptyConsensusState = ConsensusState mempty mempty sampleConsensusState - :: Int + :: HasVersion + => Int -- ^ node Id -> WebBlockHeaderDb -> CutDb @@ -711,7 +709,7 @@ data Stats = Stats } deriving (Show, Eq, Ord, Generic, ToJSON) -consensusStateSummary :: ConsensusState -> Maybe Stats +consensusStateSummary :: HasVersion => ConsensusState -> Maybe Stats consensusStateSummary s | HM.null (_stateCutMap s) = Nothing | otherwise = Just Stats @@ -723,7 +721,7 @@ consensusStateSummary s } where cutHeights = _cutHeight <$> _stateCutMap s - graph = chainGraphAt s + graph = chainGraphAt $ maximum . concatMap chainHeights $ toList $ _stateCutMap s @@ -743,11 +741,11 @@ consensusStateSummary s avgHeight = avg $ HM.elems cutHeights medHeight = median $ HM.elems cutHeights -expectedBlockCount :: ChainwebVersion -> Seconds -> Double -expectedBlockCount v s = expectedGlobalBlockCountAfterSeconds v s +expectedBlockCount :: HasVersion => Seconds -> Double +expectedBlockCount s = expectedGlobalBlockCountAfterSeconds s -lowerStats :: ChainwebVersion -> Seconds -> Stats -lowerStats v seconds = Stats +lowerStats :: HasVersion => Seconds -> Stats +lowerStats seconds = Stats { _statBlockCount = round $ ebc * 0.6 -- temporarily, was 0.8 , _statMaxHeight = round $ ebc * 0.5 -- temporarily, was 0.7 , _statMinHeight = round $ ebc * 0.1 -- temporarily, was 0.3 @@ -755,11 +753,11 @@ lowerStats v seconds = Stats , _statAvgHeight = ebc * 0.5 } where - ebc = expectedBlockCount v seconds + ebc = expectedBlockCount seconds -upperStats :: ChainwebVersion -> Seconds -> Stats -upperStats v seconds = Stats +upperStats :: HasVersion => Seconds -> Stats +upperStats seconds = Stats { _statBlockCount = round $ ebc * 1.4 , _statMaxHeight = round $ ech * 1.4 , _statMinHeight = round $ ech * 1.4 @@ -767,8 +765,8 @@ upperStats v seconds = Stats , _statAvgHeight = ech * 1.4 } where - ebc = expectedBlockCount v seconds - ech = expectedCutHeightAfterSeconds v seconds + ebc = expectedBlockCount seconds + ech = expectedCutHeightAfterSeconds seconds nextLowestPowerOfTen :: forall a. (Integral a, Ord a) => a -> a nextLowestPowerOfTen n diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index 2986257089..d066777d29 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -291,22 +291,23 @@ instance Arbitrary NodeInfo where arbitrary = do v <- arbitrary curHeight <- arbitrary - let graphs = unpackGraphs v - let curGraph = head $ dropWhile (\(h,_) -> h > curHeight) graphs - let curChains = map fst $ snd curGraph - return $ NodeInfo - { nodeVersion = _versionName v - , nodePackageVersion = chainwebNodeVersionHeaderValue - , nodeApiVersion = prettyApiVersion - , nodeChains = T.pack . show <$> curChains - , nodeNumberOfChains = length curChains - , nodeGraphHistory = graphs - , nodeLatestBehaviorHeight = latestBehaviorAt v - , nodeGenesisHeights = map (\c -> (chainIdToText c, genesisHeight v c)) $ HS.toList $ chainIds v - , nodeHistoricalChains = ruleElems $ fmap (HM.toList . HM.map HS.toList . toAdjacencySets) $ _versionGraphs v - , nodeServiceDate = T.pack <$> _versionServiceDate v - , nodeBlockDelay = _versionBlockDelay v - } + withVersion v $ do + let graphs = unpackGraphs + let curGraph = head $ dropWhile (\(h,_) -> h > curHeight) graphs + let curChains = map fst $ snd curGraph + return $ NodeInfo + { nodeVersion = _versionName v + , nodePackageVersion = chainwebNodeVersionHeaderValue + , nodeApiVersion = prettyApiVersion + , nodeChains = T.pack . show <$> curChains + , nodeNumberOfChains = length curChains + , nodeGraphHistory = graphs + , nodeLatestBehaviorHeight = latestBehaviorAt + , nodeGenesisHeights = map (\c -> (chainIdToText c, genesisHeight c)) $ HS.toList chainIds + , nodeHistoricalChains = ruleElems $ fmap (HM.toList . HM.map HS.toList . toAdjacencySets) $ _versionGraphs v + , nodeServiceDate = T.pack <$> _versionServiceDate v + , nodeBlockDelay = _versionBlockDelay v + } -- -------------------------------------------------------------------------- -- -- Block Header @@ -335,15 +336,15 @@ instance Arbitrary EpochStartTime where instance Arbitrary FeatureFlags where arbitrary = return mkFeatureFlags -instance Arbitrary BlockHeader where - arbitrary = arbitrary >>= arbitraryBlockHeaderVersion +instance HasVersion => Arbitrary BlockHeader where + arbitrary = arbitraryBlockHeaderVersion arbitraryBlockHashRecordVersionHeightChain - :: ChainwebVersion - -> BlockHeight + :: HasVersion + => BlockHeight -> ChainId -> Gen BlockHashRecord -arbitraryBlockHashRecordVersionHeightChain v h cid +arbitraryBlockHashRecordVersionHeightChain h cid | isWebChain graph cid = BlockHashRecord . HM.fromList . zip (toList $ adjacentChainIds graph cid) @@ -351,29 +352,29 @@ arbitraryBlockHashRecordVersionHeightChain v h cid | otherwise = discard where graph - | h == genesisHeight v cid = chainGraphAt v h - | otherwise = chainGraphAt v (h - 1) + | h == genesisHeight cid = chainGraphAt h + | otherwise = chainGraphAt (h - 1) -arbitraryBlockHeaderVersion :: ChainwebVersion -> Gen BlockHeader -arbitraryBlockHeaderVersion v = do +arbitraryBlockHeaderVersion :: HasVersion => Gen BlockHeader +arbitraryBlockHeaderVersion = do h <- arbitrary - arbitraryBlockHeaderVersionHeight v h + arbitraryBlockHeaderVersionHeight h arbitraryBlockHeaderVersionHeight - :: ChainwebVersion - -> BlockHeight + :: HasVersion + => BlockHeight -> Gen BlockHeader -arbitraryBlockHeaderVersionHeight v h = do - cid <- elements $ toList $ chainIdsAt v h - arbitraryBlockHeaderVersionHeightChain v h cid +arbitraryBlockHeaderVersionHeight h = do + cid <- elements $ toList $ chainIdsAt h + arbitraryBlockHeaderVersionHeightChain h cid arbitraryBlockHeaderVersionHeightChain - :: ChainwebVersion - -> BlockHeight + :: HasVersion + => BlockHeight -> ChainId -> Gen BlockHeader -arbitraryBlockHeaderVersionHeightChain v h cid - | isWebChain (chainGraphAt v h) cid = do +arbitraryBlockHeaderVersionHeightChain h cid + | isWebChain (chainGraphAt h) cid = do t <- chooseEnum (epoch, add (scaleTimeSpan @Int (365 * 200) day) epoch) fromLog @ChainwebMerkleHashAlgorithm . newMerkleLog <$> entries t | otherwise = discard @@ -387,13 +388,13 @@ arbitraryBlockHeaderVersionHeightChain v h cid $ liftA2 (:+:) (pure cid) -- chain id $ liftA2 (:+:) arbitrary -- weight $ liftA2 (:+:) (pure h) -- height - $ liftA2 (:+:) (pure (_versionCode v)) -- version + $ liftA2 (:+:) (pure (_versionCode implicitVersion)) -- version $ liftA2 (:+:) (EpochStartTime <$> chooseEnum (toEnum 0, t)) -- epoch start $ liftA2 (:+:) (Nonce <$> chooseAny) -- nonce $ fmap (MerkleLogBody . blockHashRecordToVector) - (arbitraryBlockHashRecordVersionHeightChain v h cid) -- adjacents + (arbitraryBlockHashRecordVersionHeightChain h cid) -- adjacents -instance Arbitrary HeaderUpdate where +instance HasVersion => Arbitrary HeaderUpdate where arbitrary = HeaderUpdate <$> (ObjectEncoded <$> arbitrary) <*> arbitrary @@ -415,10 +416,10 @@ instance Arbitrary CutId where Left e -> error $ "Arbitrary Instance for CutId: " <> show e Right x -> return x -instance Arbitrary CutHashes where +instance HasVersion => Arbitrary CutHashes where arbitrary = CutHashes <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary - <*> arbitrary <*> arbitrary <*> arbitrary <*> (fmap EncodedPayloadData <$> arbitrary) + <*> arbitrary <*> arbitrary <*> (fmap EncodedPayloadData <$> arbitrary) <*> return Nothing instance Arbitrary CutHeight where @@ -428,7 +429,7 @@ instance Arbitrary CutHeight where -- -------------------------------------------------------------------------- -- -- Mining Work -instance Arbitrary MiningWork where +instance HasVersion => Arbitrary MiningWork where arbitrary = do hdr <- arbitrary return $ MiningWork @@ -437,7 +438,7 @@ instance Arbitrary MiningWork where , _miningWorkBytes = BS.toShort $ runPutS $ encodeAsMiningWork hdr } -instance Arbitrary SolvedWork where +instance HasVersion => Arbitrary SolvedWork where arbitrary = fromJuste . runGetS decodeSolvedWork . BS.fromShort . work <$> arbitrary where work hdr = BS.toShort $ runPutS $ encodeAsMiningWork hdr @@ -493,11 +494,11 @@ instance MerkleHashAlgorithm a => Arbitrary (OutputTree_ a) where instance Arbitrary (MerkleTree ChainwebMerkleHashAlgorithm) where arbitrary = oneof [ arbitraryPayloadMerkleTree - , arbitraryMerkleTree @_ @BlockHeader + , arbitrary >>= \v -> withVersion v $ arbitraryMerkleTree @_ @BlockHeader ] arbitraryHeaderMerkleTree :: Gen (MerkleTree ChainwebMerkleHashAlgorithm) -arbitraryHeaderMerkleTree = arbitraryMerkleTree @_ @BlockHeader +arbitraryHeaderMerkleTree = arbitrary >>= \v -> withVersion v $ arbitraryMerkleTree @_ @BlockHeader arbitraryPayloadMerkleTree :: forall a diff --git a/test/lib/Chainweb/Test/Pact/CmdBuilder.hs b/test/lib/Chainweb/Test/Pact/CmdBuilder.hs index 87bb96639a..f3b3c85f63 100644 --- a/test/lib/Chainweb/Test/Pact/CmdBuilder.hs +++ b/test/lib/Chainweb/Test/Pact/CmdBuilder.hs @@ -187,28 +187,28 @@ defaultCmd cid = CmdBuilder -- | Build parsed + verified Pact command -- TODO: Use the new `assertPact4Command` function. -buildCwCmd :: (HasCallStack, MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact.Transaction -buildCwCmd v cmd = buildTextCmd v cmd >>= \(c :: Command Text) -> - case validateCommand v c of +buildCwCmd :: (HasCallStack, HasVersion, MonadThrow m, MonadIO m) => CmdBuilder -> m Pact.Transaction +buildCwCmd cmd = buildTextCmd cmd >>= \(c :: Command Text) -> + case validateCommand c of Left err -> error $ "buildCwCmd failed: " ++ err Right cmd' -> return cmd' -- | Build parsed, not verified Pact command -buildCwCmdNoSigCheck :: (HasCallStack, MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m Pact.Transaction -buildCwCmdNoSigCheck v cmd = buildTextCmd v cmd >>= \(cmdText :: Command Text) -> +buildCwCmdNoSigCheck :: (HasCallStack, HasVersion, MonadThrow m, MonadIO m) => CmdBuilder -> m Pact.Transaction +buildCwCmdNoSigCheck cmd = buildTextCmd cmd >>= \(cmdText :: Command Text) -> case Pact.parseCommand cmdText of Left err -> error $ "buildCwCmd failed: " ++ sshow err Right parsedCmd -> return parsedCmd -- | Build unparsed, unverified command -- -buildTextCmd :: (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m (Command Text) -buildTextCmd v = fmap (fmap T.decodeUtf8) . buildRawCmd v +buildTextCmd :: (MonadThrow m, MonadIO m, HasVersion) => CmdBuilder -> m (Command Text) +buildTextCmd = fmap (fmap T.decodeUtf8) . buildRawCmd -- | Build a raw bytestring command -- -buildRawCmd :: (MonadThrow m, MonadIO m) => ChainwebVersion -> CmdBuilder -> m (Command ByteString) -buildRawCmd v CmdBuilder{..} = do +buildRawCmd :: (MonadThrow m, MonadIO m, HasVersion) => CmdBuilder -> m (Command ByteString) +buildRawCmd CmdBuilder{..} = do kps <- liftIO $ traverse mkDynKeyPairs _cbSigners nonce <- liftIO $ maybe (fmap T.pack $ replicateM 10 $ randomRIO ('a', 'z')) return _cbNonce creationTime <- liftIO $ do @@ -229,7 +229,7 @@ buildRawCmd v CmdBuilder{..} = do cmd <- liftIO $ mkCommandWithDynKeys kps _cbVerifiers (StableEncoding pm) nonce (Just nid) _cbRPC pure cmd where - nid = NetworkId (sshow v) + nid = NetworkId (sshow implicitVersion) cid = Pact.ChainId _cbChainId dieL :: MonadThrow m => [Char] -> Either [Char] a -> m a diff --git a/test/lib/Chainweb/Test/Pact/Utils.hs b/test/lib/Chainweb/Test/Pact/Utils.hs index e68c20f760..eefd637a7b 100644 --- a/test/lib/Chainweb/Test/Pact/Utils.hs +++ b/test/lib/Chainweb/Test/Pact/Utils.hs @@ -77,11 +77,10 @@ import Pact.Core.PactValue import Pact.Core.Capabilities import Pact.Core.Names -withBlockDbs :: ChainwebVersion -> RocksDb -> ResourceT IO (PayloadDb RocksDbTable, WebBlockHeaderDb) -withBlockDbs v rdb = do - webBHDb <- liftIO $ initWebBlockHeaderDb rdb v +withBlockDbs :: HasVersion => RocksDb -> ResourceT IO (PayloadDb RocksDbTable, WebBlockHeaderDb) +withBlockDbs rdb = do + webBHDb <- liftIO $ initWebBlockHeaderDb rdb let payloadDb = newPayloadDb rdb - -- liftIO $ initializePayloadDb v payloadDb return (payloadDb, webBHDb) -- | Internal. See https://www.sqlite.org/c3ref/open.html @@ -94,12 +93,13 @@ withSQLiteResource file = snd <$> allocate withMempool :: (Logger logger) + => HasVersion => logger -> ServiceEnv tbl -> ResourceT IO (MempoolBackend Pact.Transaction) withMempool logger pact = do let mempoolCfg = validatingMempoolConfig - (_chainId pact) (_chainwebVersion pact) + (_chainId pact) (Pact.GasLimit $ Pact.Gas 150_000) (Pact.GasPrice 1e-8) (execPreInsertCheckReq logger pact) liftIO $ startInMemoryMempoolTest mempoolCfg diff --git a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs deleted file mode 100644 index c0081878ee..0000000000 --- a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Transaction/Utils.hs +++ /dev/null @@ -1,247 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TypeApplications #-} - -module Chainweb.Test.Pact4.VerifierPluginTest.Transaction.Utils where - -import Control.Concurrent.MVar -import Control.Lens hiding ((.=)) -import Control.Monad -import Control.Monad.Reader -import qualified Data.HashMap.Strict as HM -import Data.IORef -import qualified Data.Text as T -import qualified Data.Vector as V -import Test.Tasty.HUnit - --- internal modules - -import Pact.Types.Command -import Pact.Types.Hash -import Pact.Types.PactError -import Pact.Types.PactValue -import Pact.Types.Pretty -import Pact.Types.RPC -import Pact.Types.Term - -import Chainweb.BlockCreationTime -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Cut -import Chainweb.Mempool.Mempool -import Chainweb.Miner.Pact - -import Chainweb.Payload -import Chainweb.Test.Cut -import Chainweb.Test.Cut.TestBlockDb -import Chainweb.Test.Pact4.Utils -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time -import Chainweb.Utils -import Chainweb.Version -import Chainweb.WebPactExecutionService -import Chainweb.Payload.PayloadStore (lookupPayloadWithHeight) -import Chainweb.Pact.Types (MemPoolAccess, mpaGetBlock) - -testVersion :: ChainwebVersion -testVersion = slowForkingCpmTestVersion petersen - -cid :: ChainId -cid = unsafeChainId 9 - -- several tests in this file expect chain 9 - -data SingleEnv = SingleEnv - { _menvBdb :: !TestBlockDb - , _menvPact :: !WebPactExecutionService - , _menvMpa :: !(IORef MemPoolAccess) - , _menvMiner :: !Miner - , _menvChainId :: !ChainId - } - -makeLenses ''SingleEnv - -type PactTestM = ReaderT SingleEnv IO - -newtype MempoolCmdBuilder = MempoolCmdBuilder - { _mempoolCmdBuilder :: ChainId -> BlockCreationTime -> CmdBuilder - } - --- | Block filler. A 'Nothing' result means "skip this filler". -newtype MempoolBlock = MempoolBlock - { _mempoolBlock :: ChainId -> BlockCreationTime -> Maybe [MempoolCmdBuilder] - } - --- | Mempool with an ordered list of fillers. -newtype PactMempool = PactMempool - { _pactMempool :: [MempoolBlock] - } - deriving (Semigroup,Monoid) - - --- | Pair a builder with a test -data PactTxTest = PactTxTest - { _pttBuilder :: MempoolCmdBuilder - , _pttTest :: CommandResult Hash -> Assertion - } - -checkVerifierNotInTx :: T.Text -> PactTxTest -checkVerifierNotInTx v = PactTxTest - (buildBasic (mkExec' "(free.m.x)")) - (\cr -> liftIO $ do - assertTxFailure - "verifier not present" - ("Verifier failure " <> pretty v <> ": not in transaction") - cr - assertTxGas "verifier errors charge all gas" 10000 cr) - --- | Sets mempool with block fillers. A matched filler --- (returning a 'Just' result) is executed and removed from the list. --- Fillers are tested in order. -setPactMempool :: PactMempool -> PactTestM () -setPactMempool (PactMempool fs) = do - mpa <- view menvMpa - mpsRef <- liftIO $ newIORef fs - liftIO $ writeIORef mpa $ mempty { - mpaGetBlock = \_ -> go mpsRef - } - where - go ref mempoolPreBlockCheck bHeight bHash bct = do - mps <- readIORef ref - let runMps i = \case - [] -> return mempty - (mp:r) -> case _mempoolBlock mp cid bct of - Just bs -> do - writeIORef ref (take i mps ++ r) - cmds <- fmap V.fromList $ forM bs $ \b -> - buildCwCmd (sshow bct) testVersion $ _mempoolCmdBuilder b cid bct - tos <- mempoolPreBlockCheck bHeight bHash ((fmap . fmap . fmap) _pcCode cmds) - return $ V.fromList - [ t - | Right t <- V.toList tos - ] - -- return $ fmap fst $ V.filter snd (V.zip cmds validationResults) - Nothing -> runMps (succ i) r - runMps 0 mps - -filterBlock :: (ChainId -> BlockCreationTime -> Bool) -> MempoolBlock -> MempoolBlock -filterBlock f (MempoolBlock b) = MempoolBlock $ \chain bct -> - if f chain bct then b chain bct else Nothing - -blockForChain :: ChainId -> MempoolBlock -> MempoolBlock -blockForChain chid = filterBlock $ \chain _ -> - chain == chid - -runCut' :: PactTestM () -runCut' = do - pact <- view menvPact - bdb <- view menvBdb - liftIO $ runCut testVersion bdb pact (offsetBlockTime second) zeroNoncer - -assertTxGas :: (HasCallStack, MonadIO m) => String -> Gas -> CommandResult Hash -> m () -assertTxGas msg g = liftIO . assertEqual msg g . _crGas - -assertTxSuccess - :: HasCallStack - => MonadIO m - => String - -> PactValue - -> CommandResult Hash - -> m () -assertTxSuccess msg r tx = do - liftIO $ assertEqual msg (Just r) - (tx ^? crResult . to _pactResult . _Right) - --- | Exact match on error doc -assertTxFailure :: (HasCallStack, MonadIO m) => String -> Doc -> CommandResult Hash -> m () -assertTxFailure msg d tx = - liftIO $ assertEqual msg (Just d) - (tx ^? crResult . to _pactResult . _Left . to peDoc) - --- | Run a single mempool block on current chain with tests for each tx. --- Limitations: can only run a single-chain, single-refill test for --- a given cut height. -runBlockTest :: HasCallStack => [PactTxTest] -> PactTestM () -runBlockTest pts = do - chid <- view menvChainId - setPactMempool $ PactMempool [testsToBlock chid pts] - runCut' - runBlockTests pts - --- | Convert tests to block for specified chain. -testsToBlock :: ChainId -> [PactTxTest] -> MempoolBlock -testsToBlock chid pts = blockForChain chid $ MempoolBlock $ \_ _ -> - pure $ map _pttBuilder pts - --- | Run tests on current cut and chain. -runBlockTests :: HasCallStack => [PactTxTest] -> PactTestM () -runBlockTests pts = do - rs <- txResults - liftIO $ assertEqual "Result length should equal transaction length" (length pts) (length rs) - zipWithM_ go pts (V.toList rs) - where - go :: PactTxTest -> CommandResult Hash -> PactTestM () - go (PactTxTest _ t) cr = liftIO $ t cr - --- | Run cuts to block height. -runToHeight :: BlockHeight -> PactTestM () -runToHeight bhi = do - chid <- view menvChainId - bh <- getHeader chid - when (view blockHeight bh < bhi) $ do - runCut' - runToHeight bhi - -signSender00 :: CmdBuilder -> CmdBuilder -signSender00 = set cbSigners [mkEd25519Signer' sender00 []] - -setFromHeader :: BlockHeader -> CmdBuilder -> CmdBuilder -setFromHeader bh = - set cbChainId (_chainId bh) - . set cbCreationTime (toTxCreationTime $ _bct $ view blockCreationTime bh) - -buildBasic - :: PactRPC T.Text - -> MempoolCmdBuilder -buildBasic = buildBasic' id - -buildBasicGas :: GasLimit -> PactRPC T.Text -> MempoolCmdBuilder -buildBasicGas g = buildBasic' (set cbGasLimit g) - --- | Build with specified setter to mutate defaults. -buildBasic' - :: (CmdBuilder -> CmdBuilder) - -> PactRPC T.Text - -> MempoolCmdBuilder -buildBasic' f r = MempoolCmdBuilder $ \chain bct -> - f $ signSender00 - $ set cbChainId chain - $ set cbCreationTime (toTxCreationTime $ _bct bct) - $ set cbRPC r - $ defaultCmd - --- | Get output on latest cut for chain -getPWO :: ChainId -> PactTestM (PayloadWithOutputs,BlockHeader) -getPWO chid = do - (TestBlockDb _ pdb _) <- view menvBdb - h <- getHeader chid - Just pwo <- liftIO $ lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) - return (pwo,h) - -getHeader :: ChainId -> PactTestM BlockHeader -getHeader chid = do - (TestBlockDb _ _ cmv) <- view menvBdb - c <- liftIO $ readMVar cmv - fromMaybeM (userError $ "chain lookup failed for " ++ show chid) $ HM.lookup chid (_cutMap c) - -txResults :: HasCallStack => PactTestM (V.Vector (CommandResult Hash)) -txResults = do - chid <- view menvChainId - (o,_h) <- getPWO chid - forM (_payloadWithOutputsTransactions o) $ \(_,txo) -> - decodeStrictOrThrow @_ @(CommandResult Hash) (_transactionOutputBytes txo) diff --git a/test/lib/Chainweb/Test/RestAPI/Client_.hs b/test/lib/Chainweb/Test/RestAPI/Client_.hs index 23ce13e233..d9926a45c1 100644 --- a/test/lib/Chainweb/Test/RestAPI/Client_.hs +++ b/test/lib/Chainweb/Test/RestAPI/Client_.hs @@ -64,24 +64,24 @@ import Chainweb.BlockHeight (BlockHeight) -- Payload API payloadGetClient' - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BlockPayloadHash -> Maybe BlockHeight -> ClientM_ PayloadData -payloadGetClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +payloadGetClient' c = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(PayloadGetApi v c) outputsGetClient' - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BlockPayloadHash -> Maybe BlockHeight -> ClientM_ PayloadWithOutputs -outputsGetClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +outputsGetClient' c = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(OutputsGetApi v c) @@ -89,81 +89,80 @@ outputsGetClient' v c = runIdentity $ do -- Cut API cutGetClient' - :: ChainwebVersion - -> Maybe MaxRank + :: HasVersion + => Maybe MaxRank -> ClientM_ CutHashes -cutGetClient' v = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +cutGetClient' = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) return $ client_ @(CutGetApi v) cutPutClient' - :: ChainwebVersion - -> CutHashes + :: HasVersion + => CutHashes -> ClientM_ NoContent -cutPutClient' v = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +cutPutClient' = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) return $ client_ @(CutPutApi v) -- -------------------------------------------------------------------------- -- -- BlockHeaderDB API hashesClient' - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank -> Maybe MaxRank -> ClientM_ (Page (NextItem BlockHash) BlockHash) -hashesClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +hashesClient' c = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(HashesApi v c) -headerClient' :: ChainwebVersion -> ChainId -> BlockHash -> ClientM_ BlockHeader -headerClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +headerClient' :: HasVersion => ChainId -> BlockHash -> ClientM_ BlockHeader +headerClient' c = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(HeaderApi v c) headersClient' - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank -> Maybe MaxRank -> ClientM_ (Page (NextItem BlockHash) BlockHeader) -headersClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +headersClient' c = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(HeadersApi v c) branchHashesClient' - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank -> Maybe MaxRank -> BranchBounds BlockHeaderDb -> ClientM_ (Page (NextItem BlockHash) BlockHash) -branchHashesClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +branchHashesClient' c = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(BranchHashesApi v c) branchHeadersClient' - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> Maybe Limit -> Maybe (NextItem BlockHash) -> Maybe MinRank -> Maybe MaxRank -> BranchBounds BlockHeaderDb -> ClientM_ (Page (NextItem BlockHash) BlockHeader) -branchHeadersClient' v c = runIdentity $ do - (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName v) +branchHeadersClient' c = runIdentity $ do + (SomeSing (SChainwebVersion :: Sing v)) <- return $ toSing (_versionName implicitVersion) (SomeSing (SChainId :: Sing c)) <- return $ toSing c return $ client_ @(BranchHeadersApi v c) - diff --git a/test/lib/Chainweb/Test/RestAPI/Utils.hs b/test/lib/Chainweb/Test/RestAPI/Utils.hs index de56c08bb6..9e0828e8e0 100644 --- a/test/lib/Chainweb/Test/RestAPI/Utils.hs +++ b/test/lib/Chainweb/Test/RestAPI/Utils.hs @@ -97,16 +97,16 @@ repeatUntil test action = retrying testRetryPolicy -- | Calls to /local via the pact local api client with retry -- localWithQueryParams - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> ClientEnv -> Maybe LocalPreflightSimulation -> Maybe LocalSignatureVerification -> Maybe RewindDepth -> Pact.Command Text -> IO LocalResult -localWithQueryParams v sid cenv pf sv rd cmd = - runClientM (pactLocalApiClient v sid pf sv rd cmd) cenv >>= \case +localWithQueryParams sid cenv pf sv rd cmd = + runClientM (pactLocalApiClient sid pf sv rd cmd) cenv >>= \case Left e -> throwM $ LocalFailure (show e) Right t -> return t @@ -114,26 +114,26 @@ localWithQueryParams v sid cenv pf sv rd cmd = -- turned off. Retries. -- local - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> ClientEnv -> Pact.Command Text -- TODO: PP. This needs to become a full PactError eventually -> IO (Pact.CommandResult Pact.Hash Pact.PactOnChainError) -local v sid cenv cmd = do +local sid cenv cmd = do Just cr <- preview _LocalResultLegacy <$> - localWithQueryParams v sid cenv Nothing Nothing Nothing cmd + localWithQueryParams sid cenv Nothing Nothing Nothing cmd return cr -- | Request an SPV proof using exponential retry logic -- spv - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> ClientEnv -> SpvRequest -> IO TransactionOutputProofB64 -spv v sid cenv r = +spv sid cenv r = recovering testRetryPolicy [h] $ \s -> do debug $ "requesting spv proof for " <> show r @@ -141,7 +141,7 @@ spv v sid cenv r = -- send a single spv request and return the result -- - runClientM (pactSpvApiClient v sid r) cenv >>= \case + runClientM (pactSpvApiClient sid r) cenv >>= \case Left e -> throwM $ SpvFailure (show e) Right t -> return t where @@ -152,12 +152,12 @@ spv v sid cenv r = -- | Request an Eth SPV proof using exponential retry logic -- ethSpv - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> ClientEnv -> EthSpvRequest -> IO EthSpvResponse -ethSpv v sid cenv r = +ethSpv sid cenv r = recovering testRetryPolicy [h] $ \s -> do debug $ "requesting eth-spv proof for " <> show (_ethSpvReqTransactionHash r) @@ -165,7 +165,7 @@ ethSpv v sid cenv r = -- send a single spv request and return the result -- - runClientM (ethSpvApiClient v sid r) cenv >>= \case + runClientM (ethSpvApiClient sid r) cenv >>= \case Left e -> throwM $ SpvFailure (show e) Right t -> return t where @@ -173,14 +173,14 @@ ethSpv v sid cenv r = SpvFailure _ -> return True _ -> return False --- | Send a batch with retry logic waiting for success. +-- | Send a batch. sending - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> ClientEnv -> Pact.SubmitBatch -> IO Pact.RequestKeys -sending v sid cenv batch = +sending sid cenv batch = recovering testRetryPolicy [h] $ \s -> do debug $ "sending requestkeys " <> show (Pact._cmdHash <$> toList ss) @@ -188,7 +188,7 @@ sending v sid cenv batch = -- Send and return naively -- - runClientM (pactSendApiClient v sid (Pact.SendRequest batch)) cenv >>= \case + runClientM (pactSendApiClient sid (Pact.SendRequest batch)) cenv >>= \case Left e -> throwM $ SendFailure (show e) Right (Pact.SendResponse rs) -> return rs @@ -205,24 +205,24 @@ data PollingExpectation = ExpectPactError | ExpectPactResult deriving Eq polling - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> ClientEnv -> Pact.RequestKeys -> PollingExpectation -> IO Pact.PollResponse -polling v sid cenv rks pollingExpectation = - pollingWithDepth v sid cenv rks Nothing pollingExpectation +polling sid cenv rks pollingExpectation = + pollingWithDepth sid cenv rks Nothing pollingExpectation pollingWithDepth - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> ClientEnv -> Pact.RequestKeys -> Maybe ConfirmationDepth -> PollingExpectation -> IO Pact.PollResponse -pollingWithDepth v sid cenv rks confirmationDepth pollingExpectation = +pollingWithDepth sid cenv rks confirmationDepth pollingExpectation = recovering testRetryPolicy [h] $ \s -> do debug $ "polling for requestkeys " <> show (toList rs) @@ -232,7 +232,7 @@ pollingWithDepth v sid cenv rks confirmationDepth pollingExpectation = -- by making sure results are successful and request keys -- are sane - runClientM (pactPollApiClient v sid confirmationDepth $ Pact.PollRequest rs) cenv >>= \case + runClientM (pactPollApiClient sid confirmationDepth $ Pact.PollRequest rs) cenv >>= \case Left e -> throwM $ PollingFailure (show e) Right r@(Pact.PollResponse mp) -> if all (go mp) (toList rs) @@ -253,9 +253,9 @@ pollingWithDepth v sid cenv rks confirmationDepth pollingExpectation = Just cr -> Pact._crReqKey cr == rk && validate (Pact._crResult cr) Nothing -> False -getCurrentBlockHeight :: ChainwebVersion -> ClientEnv -> ChainId -> IO BlockHeight -getCurrentBlockHeight сv cenv cid = - runClientM (cutGetClient сv) cenv >>= \case +getCurrentBlockHeight :: HasVersion => ClientEnv -> ChainId -> IO BlockHeight +getCurrentBlockHeight cenv cid = + runClientM cutGetClient cenv >>= \case Left e -> throwM $ GetBlockHeightFailure $ "Failed to get cuts: " ++ show e Right cuts -> return $ fromJust $ _bhwhHeight <$> HM.lookup cid (_cutHashes cuts) diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index cc50c9669b..69cea4a2b4 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -6,6 +6,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE RankNTypes #-} module Chainweb.Test.TestVersions ( barebonesTestVersion @@ -72,15 +73,15 @@ testBootstrapPeerInfos = } } -type VersionBuilder = ChainwebVersion -> ChainwebVersion +type VersionBuilder = HasVersion => ChainwebVersion -- | Executes a `VersionBuilder` to build a `ChainwebVersion`, by taking its -- fixed point. Additionally registers it in the global version registry. buildTestVersion :: VersionBuilder -> ChainwebVersion buildTestVersion f = - unsafePerformIO (v <$ registerVersion v) & versionName .~ v ^. versionName + v where - v = f v + v = withVersion v f {-# noinline buildTestVersion #-} -- | All testing `ChainwebVersion`s *must* have unique names and *must* be @@ -127,19 +128,19 @@ testVersions = _versionName <$> concat -- index in `testVersions`, to ensure that all test versions have unique codes -- in the global version registry in `Chainweb.Version.Registry`. testVersionTemplate :: VersionBuilder -testVersionTemplate v = v - & versionCode .~ ChainwebVersionCode (int (fromJuste $ List.elemIndex (_versionName v) testVersions) + 0x80000000) +testVersionTemplate = implicitVersion + & versionCode .~ ChainwebVersionCode (int (fromJuste $ List.elemIndex (_versionName implicitVersion) testVersions) + 0x80000000) & versionHeaderBaseSizeBytes .~ 318 - 110 & versionWindow .~ WindowWidth 120 & versionMaxBlockGasLimit .~ Bottom (minBound, Just 2_000_000) & versionBootstraps .~ [testBootstrapPeerInfos] - & versionVerifierPluginNames .~ onAllChains v (Bottom (minBound, mempty)) + & versionVerifierPluginNames .~ onAllChains (Bottom (minBound, mempty)) & versionServiceDate .~ Nothing -- | A test version without Pact or PoW, with only one chain graph. barebonesTestVersion :: ChainGraph -> ChainwebVersion -barebonesTestVersion g = buildTestVersion $ \v -> - testVersionTemplate v +barebonesTestVersion g = buildTestVersion $ + testVersionTemplate & versionWindow .~ WindowWidth 120 & versionBlockDelay .~ BlockDelay 1_000_000 & versionName .~ ChainwebVersionName ("test-" <> toText g) @@ -154,28 +155,28 @@ barebonesTestVersion g = buildTestVersion $ \v -> , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onAllChains v $ _payloadWithOutputsPayloadHash emptyPayload - , _genesisBlockTarget = onAllChains v maxTarget - , _genesisTime = onAllChains v $ BlockCreationTime epoch + { _genesisBlockPayload = onAllChains $ _payloadWithOutputsPayloadHash emptyPayload + , _genesisBlockTarget = onAllChains maxTarget + , _genesisTime = onAllChains $ BlockCreationTime epoch } - & versionForks .~ HM.fromList [ (f, onAllChains v ForkAtGenesis) | f <- [minBound..maxBound] ] - & versionQuirks .~ noQuirks v - & versionUpgrades .~ onAllChains v HM.empty + & versionForks .~ HM.fromList [ (f, onAllChains ForkAtGenesis) | f <- [minBound..maxBound] ] + & versionQuirks .~ noQuirks + & versionUpgrades .~ onAllChains HM.empty -- | A test version without Pact or PoW, with a chain graph upgrade at block height 8. timedConsensusVersion :: ChainGraph -> ChainGraph -> ChainwebVersion -timedConsensusVersion g1 g2 = buildTestVersion $ \v -> v - & testVersionTemplate +timedConsensusVersion g1 g2 = buildTestVersion $ + testVersionTemplate & versionName .~ ChainwebVersionName ("timedConsensus-" <> toText g1 <> "-" <> toText g2) & versionBlockDelay .~ BlockDelay 1_000_000 & versionWindow .~ WindowWidth 120 & versionForks .~ tabulateHashMap (\case - SkipTxTimingValidation -> onAllChains v $ ForkAtBlockHeight (BlockHeight 2) + SkipTxTimingValidation -> onAllChains $ ForkAtBlockHeight (BlockHeight 2) -- pact is disabled, we don't care about pact forks - _ -> onAllChains v ForkAtGenesis + _ -> onAllChains ForkAtGenesis ) - & versionQuirks .~ noQuirks v - & versionUpgrades .~ onAllChains v HM.empty + & versionQuirks .~ noQuirks + & versionUpgrades .~ onAllChains HM.empty & versionGraphs .~ (BlockHeight 8, g2) `Above` Bottom (minBound, g1) & versionCheats .~ VersionCheats { _disablePow = True @@ -190,24 +191,24 @@ timedConsensusVersion g1 g2 = buildTestVersion $ \v -> v { _genesisBlockPayload = onChains $ [] -- TODO: PP -- (unsafeChainId 0, TN0.payloadBlock) : -- [(n, TNN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] - , _genesisBlockTarget = onAllChains v maxTarget - , _genesisTime = onAllChains v $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains maxTarget + , _genesisTime = onAllChains $ BlockCreationTime epoch } -- | A test version without Pact or PoW. checkpointerTestVersion :: ChainGraph -> ChainwebVersion -checkpointerTestVersion g1 = buildTestVersion $ \v -> v - & testVersionTemplate +checkpointerTestVersion g1 = buildTestVersion $ + testVersionTemplate & versionName .~ ChainwebVersionName ("pact5-checkpointertest-" <> toText g1) & versionBlockDelay .~ BlockDelay 1_000_000 & versionWindow .~ WindowWidth 120 & versionForks .~ tabulateHashMap (\case - SkipTxTimingValidation -> onAllChains v $ ForkAtBlockHeight (BlockHeight 2) + SkipTxTimingValidation -> onAllChains $ ForkAtBlockHeight (BlockHeight 2) -- pact is disabled, we don't care about pact forks - _ -> onAllChains v ForkAtGenesis + _ -> onAllChains ForkAtGenesis ) - & versionQuirks .~ noQuirks v - & versionUpgrades .~ onAllChains v HM.empty + & versionQuirks .~ noQuirks + & versionUpgrades .~ onAllChains HM.empty & versionGraphs .~ Bottom (minBound, g1) & versionCheats .~ VersionCheats { _disablePow = True @@ -219,16 +220,16 @@ checkpointerTestVersion g1 = buildTestVersion $ \v -> v , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains [ (n, _payloadWithOutputsPayloadHash emptyPayload) | n <- HS.toList (chainIds v) ] - , _genesisBlockTarget = onAllChains v maxTarget - , _genesisTime = onAllChains v $ BlockCreationTime epoch + { _genesisBlockPayload = onChains [ (n, _payloadWithOutputsPayloadHash emptyPayload) | n <- HS.toList chainIds ] + , _genesisBlockTarget = onAllChains maxTarget + , _genesisTime = onAllChains $ BlockCreationTime epoch } - & versionPayloadProviderTypes .~ onAllChains v PactProvider + & versionPayloadProviderTypes .~ onAllChains PactProvider -- | A family of versions each with Pact enabled and PoW disabled. cpmTestVersion :: ChainGraph -> VersionBuilder -cpmTestVersion g v = v - & testVersionTemplate +cpmTestVersion g = + testVersionTemplate & versionWindow .~ WindowWidth 120 & versionBlockDelay .~ BlockDelay (Micros 100_000) & versionGraphs .~ Bottom (minBound, g) @@ -241,17 +242,17 @@ cpmTestVersion g v = v { _disableMempoolSync = False , _disablePeerValidation = True } - & versionUpgrades .~ onAllChains v mempty - & versionPayloadProviderTypes .~ onAllChains v PactProvider + & versionUpgrades .~ onAllChains mempty + & versionPayloadProviderTypes .~ onAllChains PactProvider -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled, -- and with a gas fee quirk. quirkedGasInstantCpmTestVersion :: ChainGraph -> ChainwebVersion -quirkedGasInstantCpmTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g +quirkedGasInstantCpmTestVersion g = buildTestVersion $ + cpmTestVersion g & versionName .~ ChainwebVersionName ("quirked-instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> onAllChains v ForkAtGenesis) + _ -> onAllChains ForkAtGenesis) & versionQuirks .~ VersionQuirks { _quirkGasFees = onChain (unsafeChainId 0) $ HM.singleton (BlockHeight 2, TxBlockIdx 0) (Pact.Gas 1) @@ -260,20 +261,20 @@ quirkedGasInstantCpmTestVersion g = buildTestVersion $ \v -> v { _genesisBlockPayload = onChains $ [] -- TODO: PP -- (unsafeChainId 0, IN0.payloadBlock) : -- [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = onAllChains v maxTarget - , _genesisTime = onAllChains v $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains maxTarget + , _genesisTime = onAllChains $ BlockCreationTime epoch } - & versionUpgrades .~ onAllChains v mempty - & versionVerifierPluginNames .~ onAllChains v (Bottom (minBound, mempty)) + & versionUpgrades .~ onAllChains mempty + & versionVerifierPluginNames .~ onAllChains (Bottom (minBound, mempty)) -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled, -- and with a gas fee quirk. quirkedGasPact5InstantCpmTestVersion :: ChainGraph -> ChainwebVersion -quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g +quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ + cpmTestVersion g & versionName .~ ChainwebVersionName ("quirked-pact5-instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> onAllChains v ForkAtGenesis) + _ -> onAllChains ForkAtGenesis) & versionQuirks .~ VersionQuirks { _quirkGasFees = onChain (unsafeChainId 0) $ HM.singleton (BlockHeight 1, TxBlockIdx 0) (Pact.Gas 1) @@ -282,31 +283,31 @@ quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ \v -> v { _genesisBlockPayload = onChains $ (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : [(n, _payloadWithOutputsPayloadHash INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = onAllChains v maxTarget - , _genesisTime = onAllChains v $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains maxTarget + , _genesisTime = onAllChains $ BlockCreationTime epoch } - & versionUpgrades .~ onAllChains v mempty - & versionVerifierPluginNames .~ onAllChains v (Bottom (minBound, mempty)) + & versionUpgrades .~ onAllChains mempty + & versionVerifierPluginNames .~ onAllChains (Bottom (minBound, mempty)) -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled -- at genesis EXCEPT Pact 5. instantCpmTestVersion :: ChainGraph -> ChainwebVersion -instantCpmTestVersion g = buildTestVersion $ \v -> v - & cpmTestVersion g +instantCpmTestVersion g = buildTestVersion $ + cpmTestVersion g & versionName .~ ChainwebVersionName ("instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> onAllChains v ForkAtGenesis + _ -> onAllChains ForkAtGenesis ) - & versionQuirks .~ noQuirks v + & versionQuirks .~ noQuirks & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : [(n, _payloadWithOutputsPayloadHash INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = onAllChains v maxTarget - , _genesisTime = onAllChains v $ BlockCreationTime epoch + , _genesisBlockTarget = onAllChains maxTarget + , _genesisTime = onAllChains $ BlockCreationTime epoch } - & versionUpgrades .~ onAllChains v mempty - & versionVerifierPluginNames .~ onAllChains v + & versionUpgrades .~ onAllChains mempty + & versionVerifierPluginNames .~ onAllChains (Bottom ( minBound , Set.fromList $ map Pact.VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] @@ -324,23 +325,23 @@ instantCpmTestVersion g = buildTestVersion $ \v -> v -- -- genesis blocks are not ever run with Pact 5 -- Pact5Fork -> onChains [ (cid, ForkAtBlockHeight (succ $ genesisBlockHeight v cid)) | cid <- HS.toList $ graphChainIds g ] -- -- SPV Bridge is not in effect for Pact 5 yet. --- SPVBridge -> onAllChains v ForkNever --- _ -> onAllChains v ForkAtGenesis +-- SPVBridge -> onAllChains ForkNever +-- _ -> onAllChains ForkAtGenesis -- ) --- & versionQuirks .~ noQuirks v +-- & versionQuirks .~ noQuirks -- & versionGenesis .~ VersionGenesis -- { _genesisBlockPayload = onChains $ [] -- TODO: PP -- -- (unsafeChainId 0, IN0.payloadBlock) : -- -- [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] --- , _genesisBlockTarget = onAllChains v maxTarget --- , _genesisTime = onAllChains v $ BlockCreationTime epoch +-- , _genesisBlockTarget = onAllChains maxTarget +-- , _genesisTime = onAllChains $ BlockCreationTime epoch -- } -- & versionUpgrades .~ indexByForkHeights v -- -- TODO: PP --- -- [ (Pact5Fork, onAllChains v (Pact5Upgrade (List.map pactTxFrom4To5 CoinV6.transactions))) +-- -- [ (Pact5Fork, onAllChains (Pact5Upgrade (List.map pactTxFrom4To5 CoinV6.transactions))) -- [ -- ] --- & versionVerifierPluginNames .~ onAllChains v +-- & versionVerifierPluginNames .~ onAllChains -- (Bottom -- ( minBound -- , Set.fromList $ map Pact.VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index b9cb3a830f..ef0b2ee1ef 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -279,7 +279,8 @@ withResourceT rt act = -- Initialize Test BlockHeader DB testBlockHeaderDb - :: RocksDb + :: HasVersion + => RocksDb -> BlockHeader -> IO BlockHeaderDb testBlockHeaderDb rdb h = do @@ -287,7 +288,8 @@ testBlockHeaderDb rdb h = do initBlockHeaderDb (Configuration h rdb') withTestBlockHeaderDb - :: RocksDb + :: HasVersion + => RocksDb -> BlockHeader -> ResourceT IO BlockHeaderDb withTestBlockHeaderDb rdb h = @@ -342,17 +344,17 @@ toyVersion :: ChainwebVersion toyVersion = barebonesTestVersion singletonChainGraph toyChainId :: ChainId -toyChainId = someChainId toyVersion +toyChainId = withVersion toyVersion someChainId toyGenesis :: ChainId -> BlockHeader -toyGenesis cid = genesisBlockHeader toyVersion cid +toyGenesis cid = withVersion toyVersion genesisBlockHeader cid -- | Initialize an length-1 `BlockHeaderDb` for testing purposes. -- -- Borrowed from TrivialSync.hs -- toyBlockHeaderDb :: RocksDb -> ChainId -> IO (BlockHeader, BlockHeaderDb) -toyBlockHeaderDb db cid = (g,) <$> testBlockHeaderDb db g +toyBlockHeaderDb db cid = withVersion toyVersion $ (g,) <$> testBlockHeaderDb db g where g = toyGenesis cid @@ -372,12 +374,11 @@ mockBlockFill = BlockFill mockBlockGasLimit mempty 0 genesisBlockHeaderForChain :: MonadThrow m - => HasChainwebVersion v - => v - -> Word32 + => HasVersion + => Word32 -> m BlockHeader -genesisBlockHeaderForChain v i - = genesisBlockHeader (_chainwebVersion v) <$> mkChainId v maxBound i +genesisBlockHeaderForChain i + = genesisBlockHeader <$> mkChainId maxBound i -- | Populate a `TreeDb` with /n/ generated `BlockHeader`s. -- @@ -385,7 +386,7 @@ genesisBlockHeaderForChain v i -- includes the nonce. They payloads can be recovered using -- 'testBlockPayload_'. -- -insertN :: Int -> BlockHeader -> BlockHeaderDb -> IO () +insertN :: HasVersion => Int -> BlockHeader -> BlockHeaderDb -> IO () insertN n g db = traverse_ (unsafeInsertBlockHeaderDb db) bhs where bhs = take n $ testBlockHeaders $ Parent g @@ -394,7 +395,7 @@ insertN n g db = traverse_ (unsafeInsertBlockHeaderDb db) bhs -- includes the nonce. They payloads can be recovered using -- 'testBlockPayload_'. -- -insertN_ :: Nonce -> Natural -> BlockHeader -> BlockHeaderDb -> IO [BlockHeader] +insertN_ :: HasVersion => Nonce -> Natural -> BlockHeader -> BlockHeaderDb -> IO [BlockHeader] insertN_ s n g db = do traverse_ (unsafeInsertBlockHeaderDb db) bhs return bhs @@ -430,7 +431,7 @@ treeLeaves = toListOf . deep $ filtered (null . subForest) . LT.root newtype SparseTree = SparseTree { _sparseTree :: Tree BlockHeader } deriving (Show) instance Arbitrary SparseTree where - arbitrary = SparseTree <$> tree toyVersion Randomly + arbitrary = SparseTree <$> withVersion toyVersion tree Randomly -- | A specification for how the trunk of the `SparseTree` should grow. -- @@ -440,43 +441,43 @@ data Growth = Randomly | AtMost BlockHeight deriving (Eq, Ord, Show) -- The values of the tree constitute a legal chain, i.e. block heights start -- from 0 and increment, parent hashes propagate properly, etc. -- -tree :: ChainwebVersion -> Growth -> Gen (Tree BlockHeader) -tree v g = do - h <- genesis v +tree :: HasVersion => Growth -> Gen (Tree BlockHeader) +tree g = do + h <- genesis Node h <$> forest g h -- | Generate a sane, legal genesis block for 'Test' chainweb instance -- -genesis :: ChainwebVersion -> Gen BlockHeader -genesis v = either (error . sshow) return $ genesisBlockHeaderForChain v 0 +genesis :: HasVersion => Gen BlockHeader +genesis = either (error . sshow) return $ genesisBlockHeaderForChain 0 -forest :: Growth -> BlockHeader -> Gen (Forest BlockHeader) +forest :: HasVersion => Growth -> BlockHeader -> Gen (Forest BlockHeader) forest Randomly h = randomTrunk h forest g@(AtMost n) h | n < view blockHeight h = pure [] | otherwise = fixedTrunk g h -fixedTrunk :: Growth -> BlockHeader -> Gen (Forest BlockHeader) +fixedTrunk :: HasVersion => Growth -> BlockHeader -> Gen (Forest BlockHeader) fixedTrunk g h = frequency [ (1, sequenceA [fork h, trunk g h]) , (5, sequenceA [trunk g h]) ] -randomTrunk :: BlockHeader -> Gen (Forest BlockHeader) +randomTrunk :: HasVersion => BlockHeader -> Gen (Forest BlockHeader) randomTrunk h = frequency [ (2, pure []) , (4, sequenceA [fork h, trunk Randomly h]) , (18, sequenceA [trunk Randomly h]) ] -fork :: BlockHeader -> Gen (Tree BlockHeader) +fork :: HasVersion => BlockHeader -> Gen (Tree BlockHeader) fork h = do next <- header h Node next <$> frequency [ (1, pure []), (1, sequenceA [fork next]) ] -trunk :: Growth -> BlockHeader -> Gen (Tree BlockHeader) +trunk :: HasVersion => Growth -> BlockHeader -> Gen (Tree BlockHeader) trunk g h = do next <- header h Node next <$> forest g next -- | Generate some new `BlockHeader` based on a parent. -- -header :: BlockHeader -> Gen BlockHeader +header :: HasVersion => BlockHeader -> Gen BlockHeader header p = do nonce <- Nonce <$> chooseAny return @@ -490,14 +491,13 @@ header p = do :+: _chainId p :+: BlockWeight (targetToDifficulty target) + view blockWeight p :+: succ (view blockHeight p) - :+: _versionCode v + :+: _versionCode implicitVersion :+: epochStart (Parent p) mempty t' :+: nonce :+: MerkleLogBody mempty where BlockCreationTime t = view blockCreationTime p target = powTarget (Parent p) mempty t' - v = _chainwebVersion p t' = BlockCreationTime (scaleTimeSpan (10 :: Int) second `add` t) -- | get arbitrary value for seed. @@ -514,15 +514,15 @@ petersen = petersenChainGraph singleton :: ChainGraph singleton = singletonChainGraph -testBlockHeaderDbs :: RocksDb -> ChainwebVersion -> IO [(ChainId, BlockHeaderDb)] -testBlockHeaderDbs rdb v = mapM toEntry $ toList $ chainIds v +testBlockHeaderDbs :: HasVersion => RocksDb -> IO (ChainMap BlockHeaderDb) +testBlockHeaderDbs rdb = tabulateChainsM toEntry where toEntry c = do - d <- testBlockHeaderDb rdb (genesisBlockHeader v c) - return (c, d) + testBlockHeaderDb rdb (genesisBlockHeader c) linearBlockHeaderDbs - :: Natural + :: HasVersion + => Natural -> [(ChainId, BlockHeaderDb)] -> IO [(ChainId, BlockHeaderDb)] linearBlockHeaderDbs n dbs = do @@ -534,14 +534,15 @@ linearBlockHeaderDbs n dbs = do traverse_ (unsafeInsertBlockHeaderDb db) . take (int n) . testBlockHeaders $ Parent gbh0 starBlockHeaderDbs - :: Natural - -> [(ChainId, BlockHeaderDb)] - -> IO [(ChainId, BlockHeaderDb)] + :: HasVersion + => Natural + -> ChainMap BlockHeaderDb + -> IO (ChainMap BlockHeaderDb) starBlockHeaderDbs n dbs = do mapM_ populateDb dbs return dbs where - populateDb (_, db) = do + populateDb db = do gbh0 <- root db traverse_ (\i -> unsafeInsertBlockHeaderDb db . newEntry i $ Parent gbh0) [0 .. (int n-1)] @@ -558,32 +559,29 @@ data TestClientEnv t = TestClientEnv , _envCutDb :: !(Maybe CutDb) , _envBlockHeaderDbs :: !(ChainMap BlockHeaderDb) , _envPeerDbs :: ![(NetworkId, P2P.PeerDb)] - , _envVersion :: !ChainwebVersion } pattern BlockHeaderDbsTestClientEnv - :: ClientEnv + :: HasVersion + => ClientEnv -> ChainMap BlockHeaderDb - -> ChainwebVersion -> TestClientEnv t -pattern BlockHeaderDbsTestClientEnv { _cdbEnvClientEnv, _cdbEnvBlockHeaderDbs, _cdbEnvVersion } - <- TestClientEnv _cdbEnvClientEnv Nothing _cdbEnvBlockHeaderDbs [] _cdbEnvVersion +pattern BlockHeaderDbsTestClientEnv { _cdbEnvClientEnv, _cdbEnvBlockHeaderDbs } + <- TestClientEnv _cdbEnvClientEnv Nothing _cdbEnvBlockHeaderDbs [] pattern PeerDbsTestClientEnv :: ClientEnv -> [(NetworkId, P2P.PeerDb)] - -> ChainwebVersion -> TestClientEnv t -pattern PeerDbsTestClientEnv { _pdbEnvClientEnv, _pdbEnvPeerDbs, _pdbEnvVersion } - <- TestClientEnv _pdbEnvClientEnv Nothing _ _pdbEnvPeerDbs _pdbEnvVersion +pattern PeerDbsTestClientEnv { _pdbEnvClientEnv, _pdbEnvPeerDbs } + <- TestClientEnv _pdbEnvClientEnv Nothing _ _pdbEnvPeerDbs pattern PayloadTestClientEnv :: ClientEnv -> CutDb - -> ChainwebVersion -> TestClientEnv t -pattern PayloadTestClientEnv { _pEnvClientEnv, _pEnvCutDb, _eEnvVersion } - <- TestClientEnv _pEnvClientEnv (Just _pEnvCutDb) _ [] _eEnvVersion +pattern PayloadTestClientEnv { _pEnvClientEnv, _pEnvCutDb } + <- TestClientEnv _pEnvClientEnv (Just _pEnvCutDb) _ [] withTestAppServer :: Bool @@ -624,19 +622,19 @@ data ShouldValidateSpec = ValidateSpec | DoNotValidateSpec -- TODO: catch, wrap, and forward exceptions from chainwebApplication -- withChainwebTestServer - :: ShouldValidateSpec + :: HasVersion + => ShouldValidateSpec -> Bool - -> ChainwebVersion -> W.Application -> ResourceT IO Int -withChainwebTestServer shouldValidateSpec tls v app = +withChainwebTestServer shouldValidateSpec tls app = view _3 . snd <$> allocate start stop where verboseOnExceptionResponse exn = W.responseLBS HTTP.internalServerError500 [] ("exception: " <> sshow exn) start = do mw <- case shouldValidateSpec of - ValidateSpec -> mkApiValidationMiddleware v + ValidateSpec -> mkApiValidationMiddleware DoNotValidateSpec -> return id let app' = mw app (port, sock) <- W.openFreePort @@ -669,18 +667,18 @@ clientEnvWithChainwebTestServer . Show t => ToJSON t => FromJSON t + => HasVersion => ShouldValidateSpec -> Bool - -> ChainwebVersion -> ChainwebServerDbs -> ResourceT IO (TestClientEnv t) -clientEnvWithChainwebTestServer shouldValidateSpec tls v dbs = do +clientEnvWithChainwebTestServer shouldValidateSpec tls dbs = do -- FIXME: Hashes API got removed from the P2P API. We use an application that -- includes this API for testing. We should create comprehensive tests for the -- service API and move the tests over there. -- - let app = chainwebApplicationWithHashesAndSpvApi (defaultChainwebConfiguration v) dbs - port <- withChainwebTestServer shouldValidateSpec tls v app + let app = chainwebApplicationWithHashesAndSpvApi (defaultChainwebConfiguration implicitVersion) dbs + port <- withChainwebTestServer shouldValidateSpec tls app mgrSettings <- if | tls -> liftIO $ certificateCacheManagerSettings TlsInsecure | otherwise -> return HTTP.defaultManagerSettings @@ -690,19 +688,18 @@ clientEnvWithChainwebTestServer shouldValidateSpec tls v dbs = do (_chainwebServerCutDb dbs) (_chainwebServerBlockHeaderDbs dbs) (_chainwebServerPeerDbs dbs) - v withPeerDbsServer :: Show t => ToJSON t => FromJSON t + => HasVersion => ShouldValidateSpec -> Bool - -> ChainwebVersion -> [(NetworkId, P2P.PeerDb)] -> ResourceT IO (TestClientEnv t) -withPeerDbsServer shouldValidateSpec tls v peerDbs = - clientEnvWithChainwebTestServer shouldValidateSpec tls v +withPeerDbsServer shouldValidateSpec tls peerDbs = + clientEnvWithChainwebTestServer shouldValidateSpec tls emptyChainwebServerDbs { _chainwebServerPeerDbs = peerDbs } @@ -711,13 +708,13 @@ withPayloadServer :: Show t => ToJSON t => FromJSON t + => HasVersion => ShouldValidateSpec -> Bool - -> ChainwebVersion -> CutDb -> ResourceT IO (TestClientEnv t) -withPayloadServer shouldValidateSpec tls v cutDb = - clientEnvWithChainwebTestServer shouldValidateSpec tls v +withPayloadServer shouldValidateSpec tls cutDb = + clientEnvWithChainwebTestServer shouldValidateSpec tls emptyChainwebServerDbs { _chainwebServerCutDb = Just cutDb } @@ -726,13 +723,13 @@ withBlockHeaderDbsServer :: Show t => ToJSON t => FromJSON t + => HasVersion => ShouldValidateSpec -> Bool - -> ChainwebVersion -> ChainMap BlockHeaderDb -> ResourceT IO (TestClientEnv t) -withBlockHeaderDbsServer shouldValidateSpec tls v chainDbs = - clientEnvWithChainwebTestServer shouldValidateSpec tls v +withBlockHeaderDbsServer shouldValidateSpec tls chainDbs = + clientEnvWithChainwebTestServer shouldValidateSpec tls emptyChainwebServerDbs { _chainwebServerBlockHeaderDbs = chainDbs } @@ -907,19 +904,19 @@ data ChainwebNetwork = ChainwebNetwork withNodes_ :: Logger logger + => HasVersion => logger - -> ChainwebVersion -> (ChainwebConfiguration -> ChainwebConfiguration) -> [NodeDbDirs] -> ResourceT IO ChainwebNetwork -withNodes_ logger v confChange nodeDbDirs = do +withNodes_ logger confChange nodeDbDirs = do (p2p, service) <- start pure (ChainwebNetwork p2p service nodeDbDirs) where start :: ResourceT IO (ClientEnv, ClientEnv) start = do peerInfoVar <- liftIO newEmptyMVar - runTestNodes logger v confChange peerInfoVar nodeDbDirs + runTestNodes logger confChange peerInfoVar nodeDbDirs (i, servicePort) <- liftIO $ readMVar peerInfoVar cwEnv <- liftIO $ getClientEnv $ getCwBaseUrl Https $ _hostAddressPort $ _peerAddr i cwServiceEnv <- liftIO $ getClientEnv $ getCwBaseUrl Http servicePort @@ -934,22 +931,22 @@ withNodes_ logger v confChange nodeDbDirs = do } withNodes - :: ChainwebVersion - -> (ChainwebConfiguration -> ChainwebConfiguration) + :: HasVersion + => (ChainwebConfiguration -> ChainwebConfiguration) -> [NodeDbDirs] -> ResourceT IO ChainwebNetwork -withNodes v confF nodeDbDirs = do +withNodes confF nodeDbDirs = do logLevel <- liftIO getTestLogLevel - withNodes_ (genericLogger logLevel T.putStrLn) v confF nodeDbDirs + withNodes_ (genericLogger logLevel T.putStrLn) confF nodeDbDirs withNodesAtLatestBehavior - :: ChainwebVersion - -> (ChainwebConfiguration -> ChainwebConfiguration) + :: HasVersion + => (ChainwebConfiguration -> ChainwebConfiguration) -> [NodeDbDirs] -> ResourceT IO ChainwebNetwork -withNodesAtLatestBehavior v conf dbDirs = do - net <- withNodes v conf dbDirs - liftIO $ awaitBlockHeight v (_getServiceClientEnv net) (latestBehaviorAt v) +withNodesAtLatestBehavior conf dbDirs = do + net <- withNodes conf dbDirs + liftIO $ awaitBlockHeight (_getServiceClientEnv net) latestBehaviorAt return net -- | Network initialization takes some time. Within my ghci session it took @@ -957,13 +954,13 @@ withNodesAtLatestBehavior v conf dbDirs = do -- blocks were mined almost instantaneously. -- awaitBlockHeight - :: ChainwebVersion - -> ClientEnv + :: HasVersion + => ClientEnv -> BlockHeight -> IO () -awaitBlockHeight v cenv i = do +awaitBlockHeight cenv i = do result <- retrying testRetryPolicy checkRetry - $ const $ runClientM (cutGetClient v) cenv + $ const $ runClientM cutGetClient cenv case result of Left e -> throwM e Right x @@ -977,18 +974,19 @@ awaitBlockHeight v cenv i = do checkRetry _ (Right c) = return $ any (\bh -> _bhwhHeight bh < i) (_cutHashes c) -runTestNodes :: Logger logger +runTestNodes + :: Logger logger + => HasVersion => logger - -> ChainwebVersion -> (ChainwebConfiguration -> ChainwebConfiguration) -> MVar (PeerInfo, Port) -> [NodeDbDirs] -- ^ A Map from Node Id to (Pact DB Dir, Backups Dir). -- The index is just the position in the list. -> ResourceT IO () -runTestNodes logger ver confChange portMVar nodesDbDirs = do +runTestNodes logger confChange portMVar nodesDbDirs = do forConcurrently_ (zip [0 ..] nodesDbDirs) $ \(nid, NodeDbDirs {..}) -> do - let baseConf = confChange $ config ver (int (length nodesDbDirs)) + let baseConf = confChange $ config implicitVersion (int (length nodesDbDirs)) conf <- liftIO $ if nid == 0 then return $ bootstrapConfig baseConf else setBootstrapPeerInfo <$> (fst <$> readMVar portMVar) <*> pure baseConf @@ -1005,6 +1003,7 @@ runTestNodes logger ver confChange portMVar nodesDbDirs = do node :: Logger logger + => HasVersion => RocksDb -> logger -> TVar NowServing @@ -1137,7 +1136,7 @@ getClientEnv url = flip mkClientEnv url <$> HTTP.newTlsManagerWith mgrSettings -- | Backoff up to a constant 250ms, limiting to ~40s -- (actually saw a test have to wait > 22s) testRetryPolicy :: RetryPolicy -testRetryPolicy = stepped <> limitRetries 150 +testRetryPolicy = stepped <> limitRetries 10 where stepped = retryPolicy $ \rs -> case rsIterNumber rs of 0 -> Just 20_000 diff --git a/test/lib/Chainweb/Test/Utils/APIValidation.hs b/test/lib/Chainweb/Test/Utils/APIValidation.hs index 10e8d53c94..e68aa39e69 100644 --- a/test/lib/Chainweb/Test/Utils/APIValidation.hs +++ b/test/lib/Chainweb/Test/Utils/APIValidation.hs @@ -77,13 +77,13 @@ pactOpenApiSpec = unsafePerformIO $ do -- -------------------------------------------------------------------------- -- -- API Validation Middleware -mkApiValidationMiddleware :: HasCallStack => ChainwebVersion -> IO W.Middleware -mkApiValidationMiddleware v = do +mkApiValidationMiddleware :: (HasCallStack, HasVersion) => IO W.Middleware +mkApiValidationMiddleware = do coverageRef <- newIORef $ WV.CoverageMap Map.empty _ <- evaluate chainwebOpenApiSpec _ <- evaluate pactOpenApiSpec return $ WV.mkValidator coverageRef (WV.Log lg (const (return ()))) findPath - where + where lg (_, req) (respBody, resp) err = do let ex = ValidationException req (W.responseHeaders resp, W.responseStatus resp, respBody) err error $ "Chainweb.Test.Utils.APIValidation.mkApValidationMiddleware: validation error. " <> ppShow ex @@ -91,11 +91,14 @@ mkApiValidationMiddleware v = do [ case B8.split '/' path of ("" : "chainweb" : "0.0" : rawVersion : "chain" : rawChainId : "pact" : "api" : "v1" : rest) -> do let reqVersion = T.decodeUtf8 rawVersion - guard (reqVersion == getChainwebVersionName (_versionName v)) + guard (reqVersion == getChainwebVersionName (_versionName implicitVersion)) reqChainId <- chainIdFromText (T.decodeUtf8 rawChainId) - guard (HashSet.member reqChainId (chainIds v)) + guard (HashSet.member reqChainId chainIds) return (B8.intercalate "/" ("":rest), pactOpenApiSpec) _ -> Nothing - , (,chainwebOpenApiSpec) <$> B8.stripPrefix (T.encodeUtf8 $ "/chainweb/0.0/" <> getChainwebVersionName (_versionName v)) path + , (,chainwebOpenApiSpec) <$> + B8.stripPrefix + (T.encodeUtf8 $ "/chainweb/0.0/" <> getChainwebVersionName (_versionName implicitVersion)) + path , Just (path,chainwebOpenApiSpec) ] diff --git a/test/lib/Chainweb/Test/Utils/BlockHeader.hs b/test/lib/Chainweb/Test/Utils/BlockHeader.hs index f9586bb84f..82b7be9142 100644 --- a/test/lib/Chainweb/Test/Utils/BlockHeader.hs +++ b/test/lib/Chainweb/Test/Utils/BlockHeader.hs @@ -67,9 +67,9 @@ testPayload n = newPayloadWithOutputs -- Payloads that are created with this function match respective payloads -- that are created with 'testBlockPayload'. -- -testBlockPayloadFromParent :: Parent BlockHeader -> PayloadWithOutputs +testBlockPayloadFromParent :: HasVersion => Parent BlockHeader -> PayloadWithOutputs testBlockPayloadFromParent (Parent b) = testPayload $ B8.intercalate "," - [ sshow (_chainwebVersion b) + [ sshow implicitVersion , sshow (view blockHeight b + 1) ] @@ -79,9 +79,9 @@ testBlockPayloadFromParent (Parent b) = testPayload $ B8.intercalate "," -- Payloads that are created with this function match respective payloads -- that are created with 'testBlockPayloadFromParent'. -- -testBlockPayload :: BlockHeader -> PayloadWithOutputs +testBlockPayload :: HasVersion => BlockHeader -> PayloadWithOutputs testBlockPayload b = testPayload $ B8.intercalate "," - [ sshow (_chainwebVersion b) + [ sshow implicitVersion , sshow (view blockHeight b) ] @@ -92,9 +92,9 @@ testBlockPayload b = testPayload $ B8.intercalate "," -- that are created with 'testBlockPayload_', assuming that the same nonce is -- used. -- -testBlockPayloadFromParent_ :: Nonce -> Parent BlockHeader -> PayloadWithOutputs +testBlockPayloadFromParent_ :: HasVersion => Nonce -> Parent BlockHeader -> PayloadWithOutputs testBlockPayloadFromParent_ n (Parent b) = testPayload $ B8.intercalate "," - [ sshow (_chainwebVersion b) + [ sshow implicitVersion , sshow (view blockHeight b + 1) , sshow n ] @@ -105,9 +105,9 @@ testBlockPayloadFromParent_ n (Parent b) = testPayload $ B8.intercalate "," -- that are created with 'testBlockPayloadFromParent_', assuming that the same -- nonce is used. -- -testBlockPayload_ :: BlockHeader -> PayloadWithOutputs +testBlockPayload_ :: HasVersion => BlockHeader -> PayloadWithOutputs testBlockPayload_ b = testPayload $ B8.intercalate "," - [ sshow (_chainwebVersion b) + [ sshow implicitVersion , sshow (view blockHeight b) , sshow (view blockNonce b) ] @@ -118,18 +118,19 @@ testBlockPayload_ b = testPayload $ B8.intercalate "," testGetNewAdjacentParentHeaders :: HasCallStack => Applicative m - => ChainwebVersion - -> (ChainValue BlockHash -> m BlockHeader) + => HasVersion + => (ChainValue BlockHash -> m BlockHeader) -> BlockHashRecord -> m (HM.HashMap ChainId (Either (Parent BlockHash) (Parent BlockHeader))) -testGetNewAdjacentParentHeaders v hdb = itraverse select . _getBlockHashRecord +testGetNewAdjacentParentHeaders hdb = itraverse select . _getBlockHashRecord where select cid h - | h == genesisParentBlockHash v cid = pure $ Left $ h + | h == genesisParentBlockHash cid = pure $ Left $ h | otherwise = Right . Parent <$> hdb (ChainValue cid (unwrapParent h)) testBlockHeader - :: HM.HashMap ChainId (Parent BlockHeader) + :: HasVersion + => HM.HashMap ChainId (Parent BlockHeader) -- ^ Adjacent parent hashes -> Nonce -- ^ Randomness to affect the block hash. It is also included into @@ -148,7 +149,7 @@ testBlockHeader adj nonce p@(Parent b) = -- -- Should only be used for testing purposes. -- -testBlockHeaders :: Parent BlockHeader -> [BlockHeader] +testBlockHeaders :: HasVersion => Parent BlockHeader -> [BlockHeader] testBlockHeaders (Parent p) = L.unfoldr (Just . (id &&& id) . f) p where f b = testBlockHeader mempty (view blockNonce b) $ Parent b @@ -158,7 +159,7 @@ testBlockHeaders (Parent p) = L.unfoldr (Just . (id &&& id) . f) p -- -- Should only be used for testing purposes. -- -testBlockHeadersWithNonce :: Nonce -> Parent BlockHeader -> [BlockHeader] +testBlockHeadersWithNonce :: HasVersion => Nonce -> Parent BlockHeader -> [BlockHeader] testBlockHeadersWithNonce n (Parent p) = L.unfoldr (Just . (id &&& id) . f) p where f b = testBlockHeader mempty n $ Parent b diff --git a/test/lib/Chainweb/Test/Utils/TestHeader.hs b/test/lib/Chainweb/Test/Utils/TestHeader.hs index 47f033c5ff..6f861b42ac 100644 --- a/test/lib/Chainweb/Test/Utils/TestHeader.hs +++ b/test/lib/Chainweb/Test/Utils/TestHeader.hs @@ -78,10 +78,7 @@ makeLenses ''TestHeader instance HasChainId TestHeader where _chainId = _chainId . _testHeaderHdr -instance HasChainwebVersion TestHeader where - _chainwebVersion = _chainwebVersion . _testHeaderHdr - -instance HasChainGraph TestHeader where +instance HasVersion => HasChainGraph TestHeader where _chainGraph = _chainGraph . _testHeaderHdr instance (k ~ CasKeyType BlockHeader) => ReadableTable TestHeader k BlockHeader where @@ -129,25 +126,25 @@ testHeader v = case fromJSON (object v) of -- This construction will satisfy all block header valdiation properties except -- for POW. -- -arbitraryTestHeader :: ChainwebVersion -> ChainId -> Gen TestHeader -arbitraryTestHeader v cid = do - h <- chooseEnum (genesisHeight v cid, maxBound `div` 2) - arbitraryTestHeaderHeight v cid h +arbitraryTestHeader :: HasVersion => ChainId -> Gen TestHeader +arbitraryTestHeader cid = do + h <- chooseEnum (genesisHeight cid, maxBound `div` 2) + arbitraryTestHeaderHeight cid h arbitraryTestHeaderHeight - :: ChainwebVersion - -> ChainId + :: HasVersion + => ChainId -> BlockHeight -> Gen TestHeader -arbitraryTestHeaderHeight v cid h = do - parent <- Parent <$> arbitraryBlockHeaderVersionHeightChain v h cid +arbitraryTestHeaderHeight cid h = do + parent <- Parent <$> arbitraryBlockHeaderVersionHeightChain h cid trace "a" $ return () -- TODO: support graph changes in arbitary? as <- fmap HM.fromList - $ traverse (\c -> (c,) <$> arbitraryBlockHeaderVersionHeightChain v h c) + $ traverse (\c -> (c,) <$> arbitraryBlockHeaderVersionHeightChain h c) $ toList - $ adjacentChainIds (chainGraphAt v h) cid + $ adjacentChainIds (chainGraphAt h) cid nonce <- arbitrary payloadHash <- arbitrary let pt = maximum $ _bct . view blockCreationTime @@ -175,21 +172,20 @@ testHeaderChainLookup h x = pure $! testHeaderLookup h $ _chainValueValue x -- Genesis Test Headers genesisTestHeaders - :: HasChainwebVersion v - => v -> [TestHeader] -genesisTestHeaders v = genesisTestHeader v <$> toList (chainIds v) + :: HasVersion + => [TestHeader] +genesisTestHeaders = genesisTestHeader <$> toList chainIds genesisTestHeader - :: HasChainwebVersion v + :: HasVersion => HasChainId c - => v - -> c + => c -> TestHeader -genesisTestHeader v cid = TestHeader +genesisTestHeader cid = TestHeader { _testHeaderHdr = gen , _testHeaderParent = Parent gen - , _testHeaderAdjs = Parent . genesisBlockHeader (_chainwebVersion v) + , _testHeaderAdjs = Parent . genesisBlockHeader <$> toList (adjacentChainIds (_chainGraph gen) cid) } where - gen = genesisBlockHeader (_chainwebVersion v) (_chainId cid) + gen = genesisBlockHeader (_chainId cid) diff --git a/test/unit/Chainweb/Test/BlockHeader/Validation.hs b/test/unit/Chainweb/Test/BlockHeader/Validation.hs index b9a635c5db..18b82a7c43 100644 --- a/test/unit/Chainweb/Test/BlockHeader/Validation.hs +++ b/test/unit/Chainweb/Test/BlockHeader/Validation.hs @@ -68,14 +68,14 @@ tests :: TestTree tests = testGroup "Chainweb.Test.Blockheader.Validation" [ prop_validateMainnet , prop_validateTestnet04 - , prop_fail_validate - , prop_da_validate - , prop_legacy_da_validate - , prop_featureFlag (barebonesTestVersion petersenChainGraph) 10 - , testProperty "validate arbitrary test header" prop_validateArbitrary - , testProperty "validate arbitrary test header for mainnet" $ prop_validateArbitrary Mainnet01 - , testProperty "validate arbitrary test header for testnet04" $ prop_validateArbitrary Testnet04 - , testProperty "validate arbitrary test header for devnet" $ prop_validateArbitrary RecapDevelopment + , withVersion mainnet prop_fail_validate + , withVersion mainnet prop_da_validate + , withVersion mainnet prop_legacy_da_validate + , withVersion (barebonesTestVersion petersenChainGraph) prop_featureFlag 10 + , testProperty "validate arbitrary test header" $ \v -> withVersion v prop_validateArbitrary + , testProperty "validate arbitrary test header for mainnet" $ withVersion Mainnet01 $ prop_validateArbitrary + , testProperty "validate arbitrary test header for testnet04" $ withVersion Testnet04 $ prop_validateArbitrary + , testProperty "validate arbitrary test header for devnet" $ withVersion RecapDevelopment $ prop_validateArbitrary ] -- -------------------------------------------------------------------------- -- @@ -84,11 +84,11 @@ tests = testGroup "Chainweb.Test.Blockheader.Validation" -- There is an input for which the rule fails. -- -prop_featureFlag :: ChainwebVersion -> BlockHeight -> TestTree -prop_featureFlag v h = testCase ("Invalid feature flags fail validation for " <> sshow v) $ do +prop_featureFlag :: HasVersion => BlockHeight -> TestTree +prop_featureFlag h = testCase ("Invalid feature flags fail validation for " <> sshow implicitVersion) $ do hdr <- (blockHeight .~ h) . (blockFlags .~ fromJuste (decode "1")) - . (blockChainwebVersion .~ _versionCode v) + . (blockChainwebVersion .~ _versionCode implicitVersion) <$> generate arbitrary let r = prop_block_featureFlags hdr assertBool @@ -116,16 +116,16 @@ prop_featureFlag v h = testCase ("Invalid feature flags fail validation for " <> -- * New minded blocks prop_validateMainnet :: TestTree -prop_validateMainnet = prop_validateHeaders "validate Mainnet01 BlockHeaders" mainnet01Headers +prop_validateMainnet = withVersion mainnet $ prop_validateHeaders "validate Mainnet01 BlockHeaders" mainnet01Headers prop_validateTestnet04 :: TestTree -prop_validateTestnet04 = prop_validateHeaders "validate Testnet04 BlockHeaders" testnet04Headers +prop_validateTestnet04 = withVersion testnet04 $ prop_validateHeaders "validate Testnet04 BlockHeaders" testnet04Headers -prop_validateHeaders :: String -> [TestHeader] -> TestTree +prop_validateHeaders :: HasVersion => String -> [TestHeader] -> TestTree prop_validateHeaders msg hdrs = testGroup msg $ do [ prop_validateHeader ("header " <> show @Int i) h | h <- hdrs | i <- [0..] ] -prop_validateHeader :: String -> TestHeader -> TestTree +prop_validateHeader :: HasVersion => String -> TestHeader -> TestTree prop_validateHeader msg h = testCase msg $ do now <- getCurrentTimeIntegral case validateBlockHeaderM now (testHeaderChainLookup h) (_testHeaderHdr h) of @@ -143,16 +143,16 @@ prop_validateHeader msg h = testCase msg $ do -- would have to be valid. -- -prop_fail_validate :: TestTree +prop_fail_validate :: HasVersion => TestTree prop_fail_validate = validate_cases "validate invalid BlockHeaders" validationFailures -prop_da_validate :: TestTree +prop_da_validate :: HasVersion => TestTree prop_da_validate = validate_cases "difficulty adjustment validation" daValidation -prop_legacy_da_validate :: TestTree +prop_legacy_da_validate :: HasVersion => TestTree prop_legacy_da_validate = validate_cases "legacy difficulty adjustment validation" legacyDaValidation -validate_cases :: String -> [(TestHeader, [ValidationFailureType])] -> TestTree +validate_cases :: HasVersion => String -> [(TestHeader, [ValidationFailureType])] -> TestTree validate_cases msg testCases = testCase msg $ do now <- getCurrentTimeIntegral traverse_ (f now) $ zip [0 :: Int ..] testCases @@ -184,12 +184,12 @@ validate_cases msg testCases = testCase msg $ do -- -------------------------------------------------------------------------- -- -- Validation of Arbitrary Test Headers -prop_validateArbitrary :: ChainwebVersion -> Property -prop_validateArbitrary v = - forAll (elements $ toList $ chainIds v) $ \cid -> - forAll (arbitraryTestHeader v cid) validateTestHeader +prop_validateArbitrary :: HasVersion => Property +prop_validateArbitrary = + forAll (elements $ toList chainIds) $ \cid -> + forAll (arbitraryTestHeader cid) validateTestHeader -validateTestHeader :: TestHeader -> Property +validateTestHeader :: HasVersion => TestHeader -> Property validateTestHeader h = case try val of Right (Left ValidationFailure{ _validationFailureFailures = errs }) -> verify errs Right _ -> property True @@ -422,7 +422,7 @@ legacyDaValidation = -- history -- mainnet01Headers :: [TestHeader] -mainnet01Headers = genesisTestHeaders Mainnet01 <> +mainnet01Headers = withVersion Mainnet01 genesisTestHeaders <> [ testHeader [ "parent" .= t "AFHBANxHkLyt2kf7v54FAByxfFrR-pBP8iMLDNKO0SSt-ntTEh1IVT2E4mSPkq02AwACAAAAfaGIEe7a-wGT8OdEXz9RvlzJVkJgmEPmzk42bzjQOi0GAAAAjFsgdB2riCtIs0j40vovGGfcFIZmKPnxEXEekcV28eUIAAAAQcKA2py0L5t1Z1u833Z93V5N4hoKv_7-ZejC_QKTCzTtgKwxXj4Eovf97ELmo_iBruVLoK_Yann5LQIAAAAAALFMJ1gcC8oKW90MW2xY07gN10bM2-GvdC7fDvKDDwAPBwAAAJkPwMVeS7ZkAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOdsEAAAAAAAFAAAAT3hhzb-eBQAAAGFSDbQAAJru7keLmw3rHfSVm9wkTHWQBBTwEPwEg8RA99vzMuj-" , "header" .= t "AEbpAIzqpiins1r8v54FAJru7keLmw3rHfSVm9wkTHWQBBTwEPwEg8RA99vzMuj-AwACAAAAy7QSAHoIeFj0JXide_co-OaEzzYWbeZhAfphXI8-IR0GAAAAa-PzO_zUmk1yLOyt2kD3iI6cehKqQ_KdK8D6qZ-X6X4IAAAA79Vw2kqbVDHm9WDzksFwxZcmx5OJJNW-ge7jVa3HiHbtgKwxXj4Eovf97ELmo_iBruVLoK_Yann5LQIAAAAAAL701u70FOrdivm6quNUsKgfi2L8zYHeyOI0j2gfP16jBwAAANz0ZdfSwLZkAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOtsEAAAAAAAFAAAAT3hhzb-eBQAAAPvI7fkAAFFuYkCHZRcNl1k3-A1EZvyPxhiFKdHZwZRTqos57aiO" @@ -444,7 +444,7 @@ mainnet01Headers = genesisTestHeaders Mainnet01 <> ] testnet04Headers :: [TestHeader] -testnet04Headers = genesisTestHeaders Testnet04 +testnet04Headers = withVersion Testnet04 genesisTestHeaders -- TODO replace these by "interesting headers" form mainnet (e.g. fork block heights) _testnet04InvalidHeaders :: [(Parent BlockHeader, BlockHeader, [ValidationFailureType])] diff --git a/test/unit/Chainweb/Test/BlockHeaderDB.hs b/test/unit/Chainweb/Test/BlockHeaderDB.hs index 0b1222d6f2..cab9fd2579 100644 --- a/test/unit/Chainweb/Test/BlockHeaderDB.hs +++ b/test/unit/Chainweb/Test/BlockHeaderDB.hs @@ -32,9 +32,11 @@ import Chainweb.Test.Utils import Chainweb.TreeDB import Chainweb.Storage.Table.RocksDB +import Chainweb.Version (withVersion, HasVersion) tests :: RocksDb -> TestTree -tests rdb = testGroup "Unit Tests" +tests rdb = withVersion toyVersion $ + testGroup "Unit Tests" [ testGroup "Basic Interaction" [ testCase "Initialization + Shutdown" $ toyBlockHeaderDb rdb toyChainId >>= closeBlockHeaderDb . snd ] @@ -54,22 +56,21 @@ tests rdb = testGroup "Unit Tests" testGroup ] -insertItems :: RocksDb -> Assertion +insertItems :: HasVersion => RocksDb -> Assertion insertItems rdb = runResourceT $ do (g, db) <- withToyDB rdb toyChainId liftIO $ insertN 10 g db -correctHeight :: RocksDb -> Assertion +correctHeight :: HasVersion => RocksDb -> Assertion correctHeight rdb = runResourceT $ do (g, db) <- withToyDB rdb toyChainId liftIO $ maxRank db >>= \r -> r @?= 0 liftIO $ insertN 10 g db liftIO $ maxRank db >>= \r -> r @?= 10 -rankFiltering :: RocksDb -> Assertion +rankFiltering :: HasVersion => RocksDb -> Assertion rankFiltering rdb = runResourceT $ do (g, db) <- withToyDB rdb toyChainId liftIO $ insertN 100 g db l <- liftIO $ entries db Nothing Nothing (Just . MinRank $ Min 90) Nothing $ S.length_ liftIO $ l @?= 11 - diff --git a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs index 00d8f57bed..5fb76f8b88 100644 --- a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs +++ b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs @@ -67,7 +67,8 @@ testLogLevel = Warn -- | otherwise = return () withDbs - :: IO RocksDb + :: HasVersion + => IO RocksDb -> (RocksDb -> BlockHeaderDb -> PayloadDb RocksDbTable -> BlockHeader -> IO ()) -> IO () withDbs rio inner = do @@ -77,7 +78,7 @@ withDbs rio inner = do rdb <- rio >>= testRocksDb (sshow x) let pdb = newPayloadDb rdb - initializePayloadDb toyVersion pdb + initializePayloadDb pdb bracket (initBlockHeaderDb (Configuration h rdb)) closeBlockHeaderDb @@ -86,7 +87,8 @@ withDbs rio inner = do h = toyGenesis cid createForks - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> PayloadDb RocksDbTable -> BlockHeader -> IO ([BlockHeader], [BlockHeader]) @@ -95,7 +97,8 @@ createForks bdb pdb h = (,) <*> insertWithPayloads bdb pdb h (Nonce 2) 5 insertWithPayloads - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> PayloadDb RocksDbTable -> BlockHeader -> Nonce @@ -120,7 +123,7 @@ delHdr cdb k = do -- Test cases tests :: TestTree -tests = withResourceT withRocksResource $ \rio -> +tests = withVersion toyVersion $ withResourceT withRocksResource $ \rio -> testGroup "Chainweb.BlockHeaderDb.PruneForks" [ testCaseSteps "simple 1" (test0 rio) , testCaseSteps "simple 2" (test1 rio) @@ -139,7 +142,7 @@ tests = withResourceT withRocksResource $ \rio -> , testCaseSteps "full gc" $ testFullGc rio ] -pruneWithChecksTests :: IO RocksDb -> TestTree +pruneWithChecksTests :: HasVersion => IO RocksDb -> TestTree pruneWithChecksTests rio = testGroup "prune with checks" $ go <$> [ [CheckPayloads] , [CheckPayloadsExist] @@ -151,7 +154,7 @@ pruneWithChecksTests rio = testGroup "prune with checks" $ go <$> where go checks = testCaseSteps (sshow checks) $ testPruneWithChecks rio checks -failPruningChecksTests :: IO RocksDb -> TestTree +failPruningChecksTests :: HasVersion => IO RocksDb -> TestTree failPruningChecksTests rio = testGroup "fail pruning checks" [ testCaseSteps "CheckPayloadExists" $ failPayloadCheck rio [CheckPayloadsExist] 7 , testCaseSteps "CheckPayload" $ failPayloadCheck rio [CheckPayloads] 7 @@ -166,7 +169,8 @@ failPruningChecksTests rio = testGroup "fail pruning checks" ] singleForkTest - :: IO RocksDb + :: HasVersion + => IO RocksDb -> (String -> IO ()) -> Natural -> Int @@ -183,12 +187,12 @@ singleForkTest rio step d expect msg = where logg = logFunctionText $ genericLogger testLogLevel (step . T.unpack) -assertHeaders :: BlockHeaderDb -> [BlockHeader] -> IO () +assertHeaders :: HasVersion => BlockHeaderDb -> [BlockHeader] -> IO () assertHeaders db f = unlessM (fmap and $ mapM (tableMember db) $ view blockHash <$> f) $ assertFailure "missing block header that should not have been pruned" -assertPrunedHeaders :: BlockHeaderDb -> [BlockHeader] -> IO () +assertPrunedHeaders :: HasVersion => BlockHeaderDb -> [BlockHeader] -> IO () assertPrunedHeaders db f = whenM (fmap or $ mapM (tableMember db) $ view blockHash <$> f) $ assertFailure "failed to prune some block header" @@ -224,26 +228,26 @@ lookupPayloadWithHeightExists db h k = lookupPayloadWithHeight db h k >>= \case -- -------------------------------------------------------------------------- -- -- Header Pruning Tests -test0 :: IO RocksDb -> (String -> IO ()) -> IO () +test0 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () test0 rio step = singleForkTest rio step 1 5 "5 block headers pruned" -test1 :: IO RocksDb -> (String -> IO ()) -> IO () +test1 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () test1 rio step = singleForkTest rio step 2 5 "5 block headers pruned" -test2 :: IO RocksDb -> (String -> IO ()) -> IO () +test2 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () test2 rio step = singleForkTest rio step 4 5 "5 block headers pruned" -test3 :: IO RocksDb -> (String -> IO ()) -> IO () +test3 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () test3 rio step = singleForkTest rio step 5 0 "0 block headers pruned" -test4 :: IO RocksDb -> (String -> IO ()) -> IO () +test4 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () test4 rio step = singleForkTest rio step 9 0 "Skipping: max bound 1" -test5 :: IO RocksDb -> (String -> IO ()) -> IO () +test5 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () test5 rio step = singleForkTest rio step 10 0 "Skipping: depth == max block height" -failTest :: IO RocksDb -> Natural -> (String -> IO ()) -> IO () +failTest :: HasVersion => IO RocksDb -> Natural -> (String -> IO ()) -> IO () failTest rio n step = withDbs rio $ \_rdb db pdb h -> do (f0, _) <- createForks db pdb h delHdr db $ f0 !! (int n) @@ -263,10 +267,10 @@ failTest rio n step = withDbs rio $ \_rdb db pdb h -> do -- -------------------------------------------------------------------------- -- -- GC Tests -testFullGc :: IO RocksDb -> (String -> IO ()) -> IO () +testFullGc :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () testFullGc rio step = withDbs rio $ \rdb db pdb h -> do (f0, f1) <- createForks db pdb h - fullGc logger rdb toyVersion + fullGc logger rdb assertHeaders db f0 assertPrunedHeaders db f1 assertPayloads pdb f0 @@ -274,10 +278,10 @@ testFullGc rio step = withDbs rio $ \rdb db pdb h -> do where logger = genericLogger testLogLevel (step . T.unpack) -testPruneWithChecks :: IO RocksDb -> [PruningChecks] -> (String -> IO ()) -> IO () +testPruneWithChecks :: HasVersion => IO RocksDb -> [PruningChecks] -> (String -> IO ()) -> IO () testPruneWithChecks rio checks step = withDbs rio $ \rdb db pdb h -> do (f0, f1) <- createForks db pdb h - pruneAllChains logger rdb toyVersion checks + pruneAllChains logger rdb checks assertHeaders db f0 assertPrunedHeaders db f1 where @@ -285,14 +289,14 @@ testPruneWithChecks rio checks step = withDbs rio $ \rdb db pdb h -> do -- | Remove BlockPayload from the Payload. -- -failIntrinsicCheck :: IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () +failIntrinsicCheck :: HasVersion => IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () failIntrinsicCheck rio checks n step = withDbs rio $ \rdb bdb pdb h -> do (f0, _) <- createForks bdb pdb h let b = f0 !! int n delHdr bdb b unsafeInsertBlockHeaderDb bdb $ b & blockChainwebVersion .~ _versionCode RecapDevelopment - try (pruneAllChains logger rdb toyVersion checks) >>= \case + try (pruneAllChains logger rdb checks) >>= \case Left e | CheckFull `elem` checks && VersionMismatch `elem` _validationFailureFailures e @@ -317,7 +321,7 @@ failIntrinsicCheck rio checks n step = withDbs rio $ \rdb bdb pdb h -> do -- -- CheckPayloadsExist and CheckPayload fail for this scenario -- -failPayloadCheck :: IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () +failPayloadCheck :: HasVersion => IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () failPayloadCheck rio checks n step = withDbs rio $ \rdb bdb pdb h -> do (f0, _) <- createForks bdb pdb h let b = f0 !! int n @@ -326,7 +330,7 @@ failPayloadCheck rio checks n step = withDbs rio $ \rdb bdb pdb h -> do Just x -> return x deletePayload pdb (payloadDataToBlockPayload p) - try (pruneAllChains logger rdb toyVersion checks) >>= \case + try (pruneAllChains logger rdb checks) >>= \case Left (MissingPayloadException{}) -> return () Left e -> assertFailure $ "Expected MissingPayloadException but got: " @@ -343,7 +347,7 @@ failPayloadCheck rio checks n step = withDbs rio $ \rdb bdb pdb h -> do -- -- CheckPayloadsExist succeeds for this scenario. CheckPayload fails. -- -failPayloadCheck2 :: IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () +failPayloadCheck2 :: HasVersion => IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () failPayloadCheck2 rio checks n step = withDbs rio $ \rdb bdb pdb h -> do (f0, _) <- createForks bdb pdb h let b = f0 !! int n @@ -352,7 +356,7 @@ failPayloadCheck2 rio checks n step = withDbs rio $ \rdb bdb pdb h -> do Just x -> return x tableDelete (_newTransactionDbBlockTransactionsTbl $ _transactionDb pdb) (view blockHeight b, _payloadWithOutputsTransactionsHash payload) - try (pruneAllChains logger rdb toyVersion checks) >>= \case + try (pruneAllChains logger rdb checks) >>= \case Left (MissingPayloadException{}) -> return () Left e -> assertFailure $ "Expected MissingPayloadException but got: " diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 88c7472dd2..bb3e159061 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -112,8 +112,8 @@ cutFetchTimeout = 3_000_000 -- withTestCutDb :: HasCallStack + => HasVersion => RocksDb - -> ChainwebVersion -- ^ the chainweb version -> (CutDbParams -> CutDbParams) -- ^ any alterations to the CutDB's configuration @@ -131,15 +131,15 @@ withTestCutDb -> LogFunction -- ^ a logg function (use @\_ _ -> return ()@ turn of logging) -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb) -withTestCutDb rdb v conf n providers logfun = do +withTestCutDb rdb conf n providers logfun = do rocksDb <- liftIO $ testRocksDb "withTestCutDb" rdb let cutHashesDb = cutHashesTable rocksDb - webDb <- liftIO $ initWebBlockHeaderDb rocksDb v + webDb <- liftIO $ initWebBlockHeaderDb rocksDb mgr <- liftIO $ HTTP.newManager HTTP.defaultManagerSettings headerStore <- withLocalWebBlockHeaderStore mgr webDb - cutDb <- withCutDb (conf $ defaultCutDbParams v cutFetchTimeout) logfun headerStore providers cutHashesDb + cutDb <- withCutDb (conf $ defaultCutDbParams cutFetchTimeout) logfun headerStore providers cutHashesDb liftIO $ logfun @Text Debug "GOING TO MINE AT THE START" - liftIO $ foldM_ (\c _ -> view _1 <$> mine providers cutDb c) (genesisCut v) [1..n] + liftIO $ foldM_ (\c _ -> view _1 <$> mine cutDb c) genesisCut [1..n] return (cutHashesDb, cutDb) -- -- | Adds the requested number of new blocks to the given 'CutDb'. @@ -227,8 +227,8 @@ awaitBlockHeight cdb bh cid = atomically $ do -- withTestCutDbWithoutPact :: HasCallStack + => HasVersion => RocksDb - -> ChainwebVersion -- ^ the chainweb version -> (CutDbParams -> CutDbParams) -- ^ any alterations to the CutDB's configuration @@ -237,43 +237,42 @@ withTestCutDbWithoutPact -> LogFunction -- ^ a logg function (use @\_ _ -> return ()@ turn of logging) -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb) -withTestCutDbWithoutPact rdb v conf n = - withTestCutDb rdb v conf n (onAllChains v DisabledPayloadProvider) +withTestCutDbWithoutPact rdb conf n = + withTestCutDb rdb conf n (onAllChains DisabledPayloadProvider) -- | A version of withTestCutDb that can be used as a Tasty TestTree resource. -- withTestPayloadResource - :: RocksDb - -> ChainwebVersion + :: HasVersion + => RocksDb -> Int -> LogFunction -> ResourceT IO CutDb -withTestPayloadResource rdb v n logfun +withTestPayloadResource rdb n logfun = view _2 . snd <$> allocate start stopTestPayload where - start = startTestPayload rdb v logfun n + start = startTestPayload rdb logfun n -- -------------------------------------------------------------------------- -- -- Internal Utils for mocking up the backends startTestPayload - :: RocksDb - -> ChainwebVersion + :: HasVersion + => RocksDb -> LogFunction -> Int -> IO (Async (), CutDb) -startTestPayload rdb v logfun n = do +startTestPayload rdb logfun n = do rocksDb <- testRocksDb "startTestPayload" rdb let cutHashesDb = cutHashesTable rocksDb - webDb <- initWebBlockHeaderDb rocksDb v + webDb <- initWebBlockHeaderDb rocksDb mgr <- HTTP.newManager HTTP.defaultManagerSettings (hserver, hstore) <- startLocalWebBlockHeaderStore mgr webDb - let disabledPayloadProviders = onAllChains v DisabledPayloadProvider - cutDb <- startCutDb (defaultCutDbParams v cutFetchTimeout) logfun hstore disabledPayloadProviders cutHashesDb - foldM_ (\c _ -> view _1 <$> mine disabledPayloadProviders cutDb c) (genesisCut v) [0..n] + let disabledPayloadProviders = onAllChains DisabledPayloadProvider + cutDb <- startCutDb (defaultCutDbParams cutFetchTimeout) logfun hstore disabledPayloadProviders cutHashesDb + foldM_ (\c _ -> view _1 <$> mine cutDb c) genesisCut [0..n] return (hserver, cutDb) - stopTestPayload :: (Async (), CutDb) -> IO () stopTestPayload (hserver, cutDb) = do stopCutDb cutDb @@ -302,14 +301,12 @@ startLocalWebBlockHeaderStore mgr webDb = do -- mine :: HasCallStack + => HasVersion -- ^ The miner. For testing you may use 'defaultMiner'. - => ChainMap ConfiguredPayloadProvider - -- ^ only the new-block generator is used. For testing you may use - -- 'fakePact'. - -> CutDb + => CutDb -> Cut -> IO (Cut, ChainId, NewPayload) -mine pact cutDb c = do +mine cutDb c = do -- Pick a chain that isn't blocked. With that mining is guaranteed to -- succeed if @@ -320,7 +317,7 @@ mine pact cutDb c = do -- - the transaction generator produces valid blocks. cid <- getRandomUnblockedChain c - tryMineForChain pact cutDb c cid >>= \case + tryMineForChain cutDb c cid >>= \case Left _ -> throwM $ InternalInvariantViolation "Failed to create new cut. This is a bug in Chainweb.Test.CutDB or one of it's users" Right x -> do @@ -348,17 +345,15 @@ getRandomUnblockedChain c = do -- tryMineForChain :: HasCallStack + => HasVersion -- ^ The miner. For testing you may use 'defaultMiner'. -- miner. - => ChainMap ConfiguredPayloadProvider - -- ^ only the new-block generator is used. For testing you may use - -- 'fakePact'. - -> CutDb + => CutDb -> Cut -> ChainId -> IO (Either MineFailure (Cut, ChainId, NewPayload)) -tryMineForChain providers cutDb c cid = do - newPayload <- case providers ^?! atChain cid of +tryMineForChain cutDb c cid = do + newPayload <- case view cutDbPayloadProviders cutDb ^?! atChain cid of ConfiguredPayloadProvider p -> latestPayloadIO p DisabledPayloadProvider -> error "missing payload provider, cannot mine for chain" let payloadHash = _newPayloadBlockPayloadHash newPayload @@ -385,6 +380,7 @@ tryMineForChain providers cutDb c cid = do -- randomBlockHeader :: HasCallStack + => HasVersion => CutDb -> IO BlockHeader randomBlockHeader cutDb = do @@ -402,6 +398,7 @@ randomBlockHeader cutDb = do -- randomTransaction :: HasCallStack + => HasVersion => CanReadablePayloadCas tbl => PayloadDb tbl -> CutDb @@ -474,11 +471,10 @@ tests rdb = testGroup "CutDB" ] testCutPruning :: RocksDb -> TestTree -testCutPruning rdb = testCase "cut pruning" $ runResourceT $ do +testCutPruning rdb = testCase "cut pruning" $ runResourceT $ withVersion (barebonesTestVersion pairChainGraph) $ do -- initialize cut DB and mine enough to trigger pruning - let v = barebonesTestVersion pairChainGraph - (cutHashesStore, _) <- withTestCutDbWithoutPact rdb v alterPruningSettings - (int $ avgCutHeightAt v minedBlockHeight) + (cutHashesStore, _) <- withTestCutDbWithoutPact rdb alterPruningSettings + (int $ avgCutHeightAt minedBlockHeight) (\_ _ -> return ()) liftIO $ do -- peek inside the cut DB's store to find the oldest and newest cuts @@ -488,10 +484,10 @@ testCutPruning rdb = testCase "cut pruning" $ runResourceT $ do let fuzz = 10 :: Integer -- we must have pruned the older cuts assertBool "oldest cuts are too old" $ - round (avgBlockHeightAtCutHeight v leastCutHeight) >= fuzz + round (avgBlockHeightAtCutHeight leastCutHeight) >= fuzz -- we must keep the latest cut assertBool "newest cut is too old" $ - round (avgBlockHeightAtCutHeight v mostCutHeight) >= int minedBlockHeight - fuzz + round (avgBlockHeightAtCutHeight mostCutHeight) >= int minedBlockHeight - fuzz where alterPruningSettings = set cutDbParamsAvgBlockHeightPruningDepth 50 . @@ -499,13 +495,12 @@ testCutPruning rdb = testCase "cut pruning" $ runResourceT $ do minedBlockHeight = 300 testCutGet :: RocksDb -> TestTree -testCutGet rdb = testCase "cut get" $ runResourceT $ do - let v = barebonesTestVersion pairChainGraph +testCutGet rdb = testCase "cut get" $ withVersion (barebonesTestVersion pairChainGraph) $ runResourceT $ do let bh = BlockHeight 300 - let ch = avgCutHeightAt v bh + let ch = avgCutHeightAt bh let halfCh = ch `div` 2 - (_, cutDb) <- withTestCutDbWithoutPact rdb v id (2 * int ch) (\_ _ -> return ()) + (_, cutDb) <- withTestCutDbWithoutPact rdb id (2 * int ch) (\_ _ -> return ()) liftIO $ do curHeight <- _cutHeight <$> _cut cutDb assertGe "cut height is large enough" (Actual curHeight) (Expected $ 2 * int ch) diff --git a/test/unit/Chainweb/Test/Mempool/Consensus.hs b/test/unit/Chainweb/Test/Mempool/Consensus.hs index 70b6c42552..6361425d76 100644 --- a/test/unit/Chainweb/Test/Mempool/Consensus.hs +++ b/test/unit/Chainweb/Test/Mempool/Consensus.hs @@ -59,7 +59,7 @@ import Chainweb.Storage.Table.RocksDB import Data.LogMessage ---------------------------------------------------------------------------------------------------- -tests :: BlockHeaderDb -> BlockHeader -> TestTree +tests :: HasVersion => BlockHeaderDb -> BlockHeader -> TestTree tests db h0 = testGroup "mempool-consensus-quickcheck-tests" [ testProperty "valid-transactions-source" (prop_validTxSource db h0) , testProperty "no-orphaned-txs" (prop_noOrphanedTxs db h0) @@ -70,7 +70,8 @@ tests db h0 = testGroup "mempool-consensus-quickcheck-tests" -- | Property: All transactions returned by processFork (for re-introduction to the mempool) come from -- the old fork and are not represented in the new fork blocks prop_validTxSource - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> BlockHeader -> Property prop_validTxSource db genBlock = monadicIO $ do @@ -107,7 +108,8 @@ splitHsAt n x = -- marked available to re-entry into the mempool) (i.e., should be found in the Vector returned by -- processFork) prop_noOrphanedTxs - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> BlockHeader -> Property prop_noOrphanedTxs db genBlock = monadicIO $ do @@ -126,7 +128,8 @@ prop_noOrphanedTxs db genBlock = monadicIO $ do ---------------------------------------------------------------------------------------------------- -- Tests filtering within processFork'. prop_noOldCrap - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> BlockHeader -> Property prop_noOldCrap db genBlock = monadicIO $ do @@ -195,7 +198,8 @@ getTransPool = ---------------------------------------------------------------------------------------------------- -- | Generate a tree containing a fork genFork - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> IORef (HashMap BlockHeader (HashSet TransactionHash)) -> BlockHeader -> PropertyM IO ForkInfo @@ -225,7 +229,8 @@ takeTrans txs = do ---------------------------------------------------------------------------------------------------- genTree - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> IORef (HashMap BlockHeader (HashSet TransactionHash)) -> BlockHeader -> HashSet TransactionHash @@ -258,7 +263,8 @@ newNode mapRef blockTrans children = do ---------------------------------------------------------------------------------------------------- preForkTrunk - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> IORef (HashMap BlockHeader (HashSet TransactionHash)) -> BlockHeader -> HashSet TransactionHash @@ -286,7 +292,8 @@ frequencyM xs = do ---------------------------------------------------------------------------------------------------- fork - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> IORef (HashMap BlockHeader (HashSet TransactionHash)) -> BlockHeader -> HashSet TransactionHash @@ -316,7 +323,8 @@ genForkLengths = do ---------------------------------------------------------------------------------------------------- postForkTrunk - :: BlockHeaderDb + :: HasVersion + => BlockHeaderDb -> IORef (HashMap BlockHeader (HashSet TransactionHash)) -> BlockHeader -> HashSet TransactionHash @@ -339,7 +347,7 @@ postForkTrunk db mapRef h avail count = do -- TODO: does this test really has to go that low-level? Let try to refactor it use -- existing functionlity for creating a test block chain. -- -header' :: BlockHeader -> PropertyM IO BlockHeader +header' :: HasVersion => BlockHeader -> PropertyM IO BlockHeader header' h = do nonce <- Nonce <$> pick chooseAny return @@ -353,14 +361,13 @@ header' h = do :+: _chainId h :+: BlockWeight (targetToDifficulty target) + view blockWeight h :+: succ (view blockHeight h) - :+: _versionCode v + :+: _versionCode implicitVersion :+: epochStart (ParentHeader h) mempty t' :+: nonce :+: MerkleLogBody mempty where BlockCreationTime t = view blockCreationTime h target = powTarget (ParentHeader h) mempty t' - v = _chainwebVersion h t' = BlockCreationTime (scaleTimeSpan (10 :: Int) second `add` t) ---------------------------------------------------------------------------------------------------- @@ -465,6 +472,6 @@ _runGhci = withTempRocksDb "mempool-consensus-test" $ \rdb -> runResourceT $ do (h0, db) <- withToyDB rdb toyChainId - liftIO $ quickCheck (prop_validTxSource db h0) - liftIO $ quickCheck (prop_noOrphanedTxs db h0) + liftIO $ quickCheck (withVersion toyVersion $ prop_validTxSource db h0) + liftIO $ quickCheck (withVersion toyVersion $ prop_noOrphanedTxs db h0) return () diff --git a/test/unit/Chainweb/Test/Mempool/RestAPI.hs b/test/unit/Chainweb/Test/Mempool/RestAPI.hs index 51ad9e6df2..3264058036 100644 --- a/test/unit/Chainweb/Test/Mempool/RestAPI.hs +++ b/test/unit/Chainweb/Test/Mempool/RestAPI.hs @@ -51,15 +51,16 @@ data TestServer = TestServer } newTestServer :: IO TestServer -newTestServer = mask_ $ do +newTestServer = withVersion version $ mask_ $ do + let chain = someChainId checkMv <- newMVar (pure . V.map Right) let inMemCfg = InMemConfig txcfg mockBlockGasLimit 0 2048 Right (checkMvFunc checkMv) (1024 * 10) inmemMv <- newEmptyMVar envMv <- newEmptyMVar - tid <- forkIOWithUnmask $ \u -> server inMemCfg inmemMv envMv u + tid <- forkIOWithUnmask $ \u -> server chain inMemCfg inmemMv envMv u inmem <- takeMVar inmemMv env <- takeMVar envMv - let remoteMp0 = MClient.toMempool version chain txcfg env + let remoteMp0 = MClient.toMempool chain txcfg env -- allow remoteMp to call the local mempool's getBlock (for testing) let remoteMp = remoteMp0 { mempoolGetBlock = mempoolGetBlock inmem } return $! TestServer remoteMp inmem checkMv tid @@ -68,10 +69,10 @@ newTestServer = mask_ $ do f <- readMVar mv f xs - server inMemCfg inmemMv envMv restore = do + server chain inMemCfg inmemMv envMv restore = withVersion version $ do inmem <- InMem.startInMemoryMempoolTest inMemCfg putMVar inmemMv inmem - restore $ withTestAppServer True (return $! mkApp inmem) mkEnv $ \env -> do + restore $ withTestAppServer True (return $! mkApp chain inmem) mkEnv $ \env -> do putMVar envMv env atomically retry @@ -81,11 +82,8 @@ newTestServer = mask_ $ do host :: String host = "127.0.0.1" - chain :: ChainId - chain = someChainId version - - mkApp :: MempoolBackend MockTx -> Application - mkApp mp = chainwebApplication conf (serverMempools [(chain, mp)]) + mkApp :: ChainId -> MempoolBackend MockTx -> Application + mkApp chain mp = withVersion version $ chainwebApplication conf (serverMempools [(chain, mp)]) conf = defaultChainwebConfiguration version diff --git a/test/unit/Chainweb/Test/Mempool/Sync.hs b/test/unit/Chainweb/Test/Mempool/Sync.hs index e6ccc27761..33fc9d6854 100644 --- a/test/unit/Chainweb/Test/Mempool/Sync.hs +++ b/test/unit/Chainweb/Test/Mempool/Sync.hs @@ -37,7 +37,7 @@ tests = testGroup "Chainweb.Mempool.sync" wf :: (InsertCheck -> MempoolBackend MockTx -> IO a) -> IO a wf f = do mv <- newMVar (pure . V.map Right) - let cfg = InMemConfig txcfg mockBlockGasLimit 0 2048 Right (checkMv mv) (1024 * 10) + let cfg = InMemConfig txcfg mockBlockGasLimit (GasPrice 0) 2048 Right (checkMv mv) (1024 * 10) f mv =<< startInMemoryMempoolTest cfg checkMv :: MVar (t -> IO b) -> t -> IO b @@ -65,7 +65,7 @@ txcfg = TransactionConfig mockCodec hasher hashmeta mockGasPrice testInMemCfg :: InMemConfig MockTx testInMemCfg = - InMemConfig txcfg mockBlockGasLimit 0 2048 Right (pure . V.map Right) (1024 * 10) + InMemConfig txcfg mockBlockGasLimit (GasPrice 0) 2048 Right (pure . V.map Right) (1024 * 10) propSync :: (Set MockTx, Set MockTx , Set MockTx) diff --git a/test/unit/Chainweb/Test/MinerReward.hs b/test/unit/Chainweb/Test/MinerReward.hs index b69d999637..a69c404489 100644 --- a/test/unit/Chainweb/Test/MinerReward.hs +++ b/test/unit/Chainweb/Test/MinerReward.hs @@ -74,31 +74,33 @@ prop_stuToKdaToStu :: Stu -> Property prop_stuToKdaToStu stu = kdaToStu (stuToKda stu) === stu prop_blockMinerRewardLegacyCompat :: BlockHeight -> Property -prop_blockMinerRewardLegacyCompat h - | h < maxRewardHeight - 2 = - legacyBlockMinerReward v h === minerRewardKda (blockMinerReward v h) - | h == maxRewardHeight - 1 = - legacyBlockMinerReward v h =/= minerRewardKda (blockMinerReward v h) - | h == maxRewardHeight = - legacyBlockMinerReward v h === minerRewardKda (blockMinerReward v h) - | otherwise = expectFailure - -- legacyMinerRewards is expected to throw an exception - $ legacyBlockMinerReward v h === minerRewardKda (blockMinerReward v h) +prop_blockMinerRewardLegacyCompat h = withVersion mainnet go + where + go :: HasVersion => Property + go + | h < maxRewardHeight - 2 = + legacyBlockMinerReward h === minerRewardKda (blockMinerReward h) + | h == maxRewardHeight - 1 = + legacyBlockMinerReward h =/= minerRewardKda (blockMinerReward h) + | h == maxRewardHeight = + legacyBlockMinerReward h === minerRewardKda (blockMinerReward h) + | otherwise = expectFailure + -- legacyMinerRewards is expected to throw an exception + $ legacyBlockMinerReward h === minerRewardKda (blockMinerReward h) - where - v = Mainnet01 -- 2.304523 -- test_finalMinerReward :: Assertion -test_finalMinerReward = do +test_finalMinerReward = withVersion mainnet $ do mapM_ rewardIsZero $ take 100 [maxRewardHeight..] mapM_ rewardIsZero $ take 10 [maxRewardHeight, (maxRewardHeight + 1000)..] where + rewardIsZero :: HasVersion => BlockHeight -> Assertion rewardIsZero h = assertEqual "The final miner reward is 0" (Kda 0) - (minerRewardKda (blockMinerReward Mainnet01 h)) + (minerRewardKda (blockMinerReward h)) test_minerRewardsMax :: Assertion test_minerRewardsMax = assertBool @@ -131,7 +133,7 @@ test_expectedRawMinerRewardsHash = assertEqual -- - block heights strictly larger than 125538057 -- test_blockMinerRewardLegacyCompat :: Assertion -test_blockMinerRewardLegacyCompat = do +test_blockMinerRewardLegacyCompat = withVersion mainnet $ do mapM_ rewardsMatch [0..10000] mapM_ rewardsMatch [0,1000..maxRewardHeight - 2] mapM_ rewardsMatch [maxRewardHeight - 1000 .. maxRewardHeight - 2] @@ -141,14 +143,15 @@ test_blockMinerRewardLegacyCompat = do [maxRewardHeight - 1] legacyCompatExceptions where - v = Mainnet01 + rewardsMatch :: HasVersion => BlockHeight -> Assertion rewardsMatch h = assertEqual "miner reward value matches the legacy value" - (legacyBlockMinerReward v h) - (minerRewardKda (blockMinerReward v h)) + (legacyBlockMinerReward h) + (minerRewardKda (blockMinerReward h)) + legacyCompatExceptions :: HasVersion => [BlockHeight] legacyCompatExceptions = M.keys $ M.filterWithKey - (\k _ -> legacyBlockMinerReward v k /= minerRewardKda (blockMinerReward v k)) + (\k _ -> legacyBlockMinerReward k /= minerRewardKda (blockMinerReward k)) minerRewards -- This should be a CAF and can thus not include the computation in @@ -171,13 +174,12 @@ mkLegacyMinerRewards = formatRow (!a,!b) = (BlockHeight $ int a, (_csvDecimal b)) legacyBlockMinerReward - :: ChainwebVersion - -> BlockHeight + :: HasVersion + => BlockHeight -> Kda -legacyBlockMinerReward v h = +legacyBlockMinerReward h = case M.lookupGE h legacyMinerRewards of Nothing -> error "The end of the chain has been reached" Just (_, m) -> Kda $ roundTo 8 (_kda m / n) where - !n = int . order $ chainGraphAt v h - + !n = int . order $ chainGraphAt h diff --git a/test/unit/Chainweb/Test/Mining.hs b/test/unit/Chainweb/Test/Mining.hs index e97920e8de..419a5755be 100644 --- a/test/unit/Chainweb/Test/Mining.hs +++ b/test/unit/Chainweb/Test/Mining.hs @@ -46,6 +46,7 @@ import Chainweb.Test.CutDB hiding (tests) import Chainweb.Test.TestVersions (barebonesTestVersion) import Chainweb.Storage.Table.RocksDB +import Chainweb.Version (withVersion) -- -------------------------------------------------------------------------- -- -- @@ -67,10 +68,10 @@ withTestCoordinator -- set to enabled before the coordinator is initialized. -> (forall tbl logger . Logger logger => logger -> MiningCoordination logger tbl -> IO ()) -> IO () -withTestCoordinator rdb logg maybeConf a = do +withTestCoordinator rdb logg maybeConf a = withVersion v $ do var <- newEmptyMVar x <- race (takeMVar var) $ - withTestCutDb rdb v id 0 (\_ _ -> return fakePact) (logFunction logger) $ \_ cdb -> + withTestCutDb rdb id 0 (\_ _ -> return fakePact) (logFunction logger) $ \_ cdb -> withMiningCoordination logger conf cdb $ \case Nothing -> error "nonEmptyMiningAccount: Bug in the mining Code" Just coord -> do @@ -94,4 +95,3 @@ nonEmptyMiningAccount rdb logg = withTestCoordinator rdb logg Nothing $ \_logger PrimedWork w <- readTVarIO (_coordPrimedWork coord) forM_ (HM.keys w) $ \(MinerId k) -> assertBool "miner account name must not be the empty string" (not (T.null k)) - diff --git a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs index 130e301028..dc3609032a 100644 --- a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs +++ b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs @@ -196,33 +196,31 @@ runBlocks -> Parent RankedBlockHash -> [DbBlock (Const ())] -> IO [(BlockCtx, (BlockHash, BlockPayloadHash), DbBlock Identity)] -runBlocks sql rootBlockCtx blks = do +runBlocks sql rootBlockCtx blks = loop rootBlockCtx blks where - loop parent (block:blocks) = do + loop parent (block:blocks) = withVersion testVer $ do logger <- getTestLogger fakeParentCreationTime <- Checkpointer.mkFakeParentCreationTime (fakeBlockInfo, block', _finalBlockHandle) <- (throwIfNoHistory =<<) $ - Checkpointer.readFrom logger testVer cid sql fakeParentCreationTime parent $ + Checkpointer.readFrom logger cid sql fakeParentCreationTime parent $ executeBlockTransaction parent block let childBlockCtx = BlockCtx { _bctxParentCreationTime = fakeParentCreationTime , _bctxParentHash = Parent $ fst fakeBlockInfo - , _bctxParentHeight = Parent $ childBlockHeight testVer cid parent + , _bctxParentHeight = Parent $ childBlockHeight cid parent , _bctxChainId = cid - , _bctxChainwebVersion = testVer - , _bctxMinerReward = blockMinerReward testVer (childBlockHeight testVer cid parent) + , _bctxMinerReward = blockMinerReward (childBlockHeight cid parent) } let parentBlockCtx = BlockCtx { _bctxParentCreationTime = fakeParentCreationTime , _bctxParentHash = _rankedBlockHashHash <$> parent , _bctxParentHeight = _rankedBlockHashHeight <$> parent , _bctxChainId = cid - , _bctxChainwebVersion = testVer - , _bctxMinerReward = blockMinerReward testVer (unwrapParent $ _rankedBlockHashHeight <$> parent) + , _bctxMinerReward = blockMinerReward (unwrapParent $ _rankedBlockHashHeight <$> parent) } - _ <- Checkpointer.restoreAndSave logger testVer cid sql + _ <- Checkpointer.restoreAndSave logger cid sql (NE.singleton (parentBlockCtx, \blockEnv -> do blockHandle <- get (fakeBlockInfo', _blk, finalBlockHandle) <- @@ -245,10 +243,10 @@ runBlocks sql rootBlockCtx blks = do -- Check that a block's result at the time it was added to the checkpointer -- is consistent with us executing that block with `readFrom` assertBlock :: SQLiteEnv -> BlockCtx -> (BlockHash, BlockPayloadHash) -> DbBlock Identity -> IO () -assertBlock sql blockCtx expectedBlockInfo blk = do +assertBlock sql blockCtx expectedBlockInfo blk = withVersion testVer $ do fakeNewBlockCtx <- Checkpointer.mkFakeParentCreationTime logger <- getTestLogger - hist <- Checkpointer.readFrom logger testVer cid sql fakeNewBlockCtx (_bctxParentRankedBlockHash blockCtx) $ \blockEnv startHandle -> do + hist <- Checkpointer.readFrom logger cid sql fakeNewBlockCtx (_bctxParentRankedBlockHash blockCtx) $ \blockEnv startHandle -> do ((), _endHandle) <- doChainwebPactDbTransaction (_psBlockDbEnv blockEnv) startHandle Nothing $ \txdb _spv -> do _ <- ignoreGas noInfo $ _pdbBeginTx txdb Transactional blk' <- forM blk (runDbAction' txdb) @@ -273,32 +271,32 @@ assertBlock sql blockCtx expectedBlockInfo blk = do tests :: TestTree tests = testGroup "Pact5 Checkpointer tests" - [ withResourceT withTempSQLiteResource $ \sqlIO -> - testCase "valid PactDb before genesis" $ do - sql <- sqlIO + [ withResourceT (withTempChainSqlite cid) $ \sqlIO -> + testCase "valid PactDb before genesis" $ withVersion testVer $ do + (sql, _sqlReadPool) <- sqlIO ChainwebPactDb.initSchema sql - Checkpointer.setConsensusState sql $ genesisConsensusState testVer cid + Checkpointer.setConsensusState sql $ genesisConsensusState cid logger <- getTestLogger fakeNewBlockCtx <- Checkpointer.mkFakeParentCreationTime ((), _handle) <- (throwIfNoHistory =<<) $ - Checkpointer.readFrom logger testVer cid sql fakeNewBlockCtx genesisParentRanked + Checkpointer.readFrom logger cid sql fakeNewBlockCtx genesisParentRanked $ \db blockHandle -> do doChainwebPactDbTransaction (_psBlockDbEnv db) blockHandle Nothing $ \txdb _spv -> Pact.Core.runPactDbRegression txdb return () - , withResourceT withTempSQLiteResource $ \sqlIO -> - testProperty "readFrom with linear block history is valid" $ withTests 1000 $ property $ do + , withResourceT (withTempChainSqlite cid) $ \sqlIO -> + testProperty "readFrom with linear block history is valid" $ withTests 1000 $ property $ withVersion testVer $ do blocks <- forAll genBlockHistory - sql <- evalIO sqlIO + (sql, _sqlReadPool) <- evalIO sqlIO finishedBlocks <- evalIO $ do ChainwebPactDb.initSchema sql - Checkpointer.setConsensusState sql $ genesisConsensusState testVer cid + Checkpointer.setConsensusState sql $ genesisConsensusState cid logger <- getTestLogger -- extend this empty chain with the genesis block - _ <- Checkpointer.restoreAndSave logger testVer cid sql $ + _ <- Checkpointer.restoreAndSave logger cid sql $ ( - NE.singleton (blockCtxOfEvaluationCtx testVer cid (genesisEvalCtx cid), - \_ -> return ((), (view blockHash (genesisBlockHeader testVer cid), genesisBlockPayloadHash testVer cid))) + NE.singleton (blockCtxOfEvaluationCtx cid (genesisEvalCtx cid), + \_ -> return ((), (view blockHash (genesisBlockHeader cid), genesisBlockPayloadHash cid))) ) handle @_ @SomeException (\ex -> putStrLn (displayException ex) >> throw ex) @@ -311,14 +309,14 @@ tests = testGroup "Pact5 Checkpointer tests" assertBlock sql parent blockInfo block ] where - genesisEvalCtx c = EvaluationCtx - { _evaluationCtxParentCreationTime = Parent $ testVer ^?! versionGenesis . genesisTime . atChain c - , _evaluationCtxParentHash = genesisParentBlockHash testVer c - , _evaluationCtxParentHeight = Parent $ genesisHeight testVer c + genesisEvalCtx c = withVersion testVer $ EvaluationCtx + { _evaluationCtxParentCreationTime = Parent $ implicitVersion ^?! versionGenesis . genesisTime . atChain c + , _evaluationCtxParentHash = genesisParentBlockHash c + , _evaluationCtxParentHeight = Parent $ genesisHeight c -- should not be used , _evaluationCtxMinerReward = MinerReward 0 , _evaluationCtxPayload = ConsensusPayload - { _consensusPayloadHash = genesisBlockPayloadHash testVer c + { _consensusPayloadHash = genesisBlockPayloadHash c , _consensusPayloadData = Just $ EncodedPayloadData $ Chainweb.encodePayloadData $ Chainweb.payloadWithOutputsToPayloadData emptyPayload } @@ -332,12 +330,13 @@ cid :: ChainId cid = unsafeChainId 0 gh :: BlockHeader -gh = genesisBlockHeader testVer cid +gh = withVersion testVer $ genesisBlockHeader cid genesisParentRanked :: Parent RankedBlockHash -genesisParentRanked = Parent $ RankedBlockHash - (genesisHeight testVer cid) - (unwrapParent $ genesisParentBlockHash testVer cid) +genesisParentRanked = withVersion testVer $ + Parent $ RankedBlockHash + (genesisHeight cid) + (unwrapParent $ genesisParentBlockHash cid) instance (forall a. Show a => Show (f a)) => Show (DbAction f) where showsPrec n (DbRead tn k v) = showParen (n > 10) $ diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index 3d2031c75e..c4d3120605 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -32,6 +32,7 @@ module Chainweb.Test.Pact.CutFixture , fixtureCutDb , fixturePayloadDb , fixtureWebBlockHeaderDb + , fixtureLogger , fixtureMempools , fixturePacts , advanceAllChains @@ -40,51 +41,39 @@ module Chainweb.Test.Pact.CutFixture ) where -import Chainweb.Storage.Table (Casify) -import Chainweb.BlockCreationTime (BlockCreationTime(..)) -import Chainweb.BlockHash (BlockHash) import Chainweb.BlockHeader hiding (blockCreationTime, blockNonce) -import Chainweb.BlockHeader.Internal (blockCreationTime, blockNonce) import Chainweb.ChainId -import Chainweb.ChainValue (ChainValue(..), ChainValueCasLookup, chainLookupM) import Chainweb.Cut -import Chainweb.Cut.Create (InvalidSolvedHeader(..), SolvedWork, extendCut, getCutExtension, newHeaderForPayloadPure, makeSolvedWork) import Chainweb.Cut.CutHashes import Chainweb.CutDB import Chainweb.Logger import Chainweb.Mempool.Mempool (MempoolBackend) -import Chainweb.Miner.Pact import Chainweb.Pact.Types import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.Storage.Table.RocksDB -import Chainweb.Sync.WebBlockHeaderStore +import Chainweb.Test.Cut import Chainweb.Test.CutDB import Chainweb.Test.Pact.Utils import Chainweb.Test.Utils import Chainweb.Time import Chainweb.Utils -import Chainweb.Utils.Serialization (runGetS, runPutS) import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB -import Control.Concurrent.STM as STM import Control.Lens hiding (elements, only) import Control.Monad import Control.Monad.Catch hiding (finally) import Control.Monad.IO.Class -import Control.Monad.Trans.Resource (ResourceT, allocate) +import Control.Monad.Trans.Resource (ResourceT) import Data.ByteString.Lazy qualified as LBS import Data.Function import Data.HashMap.Strict qualified as HashMap import Data.HashSet qualified as HashSet -import Data.Text (Text) import Data.Text qualified as Text import Data.Vector (Vector) import GHC.Stack -import Network.HTTP.Client qualified as HTTP -import qualified Data.Pool as Pool import qualified Chainweb.Pact.PactService as PactService import Chainweb.PayloadProvider.Pact import Chainweb.PayloadProvider @@ -106,21 +95,21 @@ instance HasFixture Fixture where instance HasFixture a => HasFixture (IO a) where cutFixture = (>>= cutFixture) -mkFixture :: ChainwebVersion -> (ChainId -> PayloadWithOutputs) -> PactServiceConfig -> RocksDb -> ResourceT IO Fixture -mkFixture v genesisPayloadFor pactServiceConfig baseRdb = do +mkFixture :: HasVersion => (ChainId -> PayloadWithOutputs) -> PactServiceConfig -> RocksDb -> ResourceT IO Fixture +mkFixture genesisPayloadFor pactServiceConfig baseRdb = do logger <- liftIO getTestLogger testRdb <- liftIO $ testRocksDb "withBlockDbs" baseRdb - (payloadDb, webBHDb) <- withBlockDbs v testRdb - perChain <- fmap ChainMap $ iforM (HashSet.toMap (chainIds v)) $ \chain () -> do + (payloadDb, webBHDb) <- withBlockDbs testRdb + perChain <- fmap ChainMap $ iforM (HashSet.toMap chainIds) $ \chain () -> do (writeSqlite, readPool) <- withTempChainSqlite chain - serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing payloadDb readPool writeSqlite pactServiceConfig (GenesisPayload $ genesisPayloadFor chain) + serviceEnv <- PactService.withPactService chain Nothing mempty logger Nothing payloadDb readPool writeSqlite pactServiceConfig (GenesisPayload $ genesisPayloadFor chain) mempool <- withMempool logger serviceEnv let serviceEnv' = serviceEnv { _psMempoolAccess = pactMemPoolAccess mempool logger } return (mempool, serviceEnv') let pacts = snd <$> perChain let mempools = fst <$> perChain let providers = ConfiguredPayloadProvider . PactPayloadProvider logger <$> pacts - (_, cutDb) <- withTestCutDb testRdb v id 0 providers (logFunction logger) + (_, cutDb) <- withTestCutDb testRdb id 0 providers (logFunction logger) let fixture = Fixture { _fixtureCutDb = cutDb , _fixtureLogger = logger @@ -138,12 +127,11 @@ mkFixture v genesisPayloadFor pactServiceConfig baseRdb = do -- their mempools at the time. -- advanceAllChains - :: (HasCallStack, HasFixture a) + :: (HasCallStack, HasFixture a, HasVersion) => a -> IO (Cut, ChainMap (Vector TestPact5CommandResult)) advanceAllChains fx = do Fixture{..} <- cutFixture fx - let v = _chainwebVersion _fixtureCutDb latestCut <- liftIO $ _fixtureCutDb ^. cut let blockHeights = fmap (view blockHeight) $ latestCut ^. cutMap let latestBlockHeight = maximum blockHeights @@ -164,12 +152,12 @@ advanceAllChains fx = do return $ (newCut, (cid, commandResults) : acc) ) (latestCut, []) - (HashSet.toList (chainIdsAt v (latestBlockHeight + 1))) + (HashSet.toList (chainIdsAt (latestBlockHeight + 1))) return (finalCut, onChains perChainCommandResults) advanceAllChains_ - :: (HasCallStack, HasFixture a) + :: (HasCallStack, HasFixture a, HasVersion) => a -> IO () advanceAllChains_ = void . advanceAllChains @@ -178,6 +166,7 @@ advanceAllChains_ = void . advanceAllChains -- cutDb). No POW or poison delay is applied. Block times are real times. mine :: HasCallStack + => HasVersion => ChainId -> CutDb -> Cut @@ -193,64 +182,12 @@ mine cid cutDb c = do void $ awaitCut cutDb $ ((<=) `on` _cutHeight) (view _1 x) return x -testMineWithPayloadHash - :: forall cid hdb. (HasChainId cid, ChainValueCasLookup hdb BlockHeader) - => hdb - -> Nonce - -> Time Micros - -> BlockPayloadHash - -> cid - -> Cut - -> IO (Either MineFailure (T2 BlockHeader Cut)) -testMineWithPayloadHash db n t ph cid c = try $ createNewCut (chainLookupM db) n t ph cid c - --- | Create a new block. Only produces a new cut but doesn't insert it into the --- chain database. --- --- The creation time isn't checked. --- -createNewCut - :: (HasCallStack, MonadCatch m, MonadIO m, HasChainId cid) - => (ChainValue BlockHash -> m BlockHeader) - -> Nonce - -> Time Micros - -> BlockPayloadHash - -> cid - -> Cut - -> m (T2 BlockHeader Cut) -createNewCut hdb n t pay i c = do - extension <- fromMaybeM BadAdjacents $ getCutExtension c i - work <- newWorkHeaderPure hdb (BlockCreationTime t) extension pay - (h, mc') <- extendCut c (solveWork work n t) - `catch` \(InvalidSolvedHeader _ msg) -> throwM $ InvalidHeader msg - c' <- fromMaybeM BadAdjacents mc' - return $ T2 solvedHeader c' - --- | Solve Work. Doesn't check that the nonce and the time are valid. --- -solveWork :: HasCallStack => BlockHeader -> Nonce -> Time Micros -> SolvedWork -solveWork bh n t = - makeSolvedWork - $ fromJuste - $ runGetS decodeBlockHeaderWithoutHash - $ runPutS - $ encodeAsWorkHeader - -- After injecting the nonce and the creation time will have to do a - -- serialization roundtrip to update the Merkle hash. - -- - -- A "real" miner would inject the nonce and time without first - -- decoding the header and would hand over the header in serialized - -- form. - - $ set blockCreationTime (BlockCreationTime t) - $ set blockNonce n - $ bh - -- | Build a linear chainweb (no forks). No POW or poison delay is applied. -- Block times are real times. -- tryMineForChain :: HasCallStack + => HasVersion => CutDb -> Cut -> ChainId @@ -273,13 +210,3 @@ tryMineForChain cutDb c cid = do Left e -> return $ Left e where wdb = view cutDbWebBlockHeaderDb cutDb - -data MineFailure - = InvalidHeader Text - -- ^ The header is invalid, e.g. because of a bad nonce or creation time. - | MissingParentHeader - -- ^ A parent header is missing in the chain db - | BadAdjacents - -- ^ This could mean that the chain is blocked. - deriving stock (Show) - deriving anyclass (Exception) diff --git a/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs b/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs index f194cbf50d..22648aa4db 100644 --- a/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs +++ b/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs @@ -61,7 +61,7 @@ tests baseRdb = testGroup "Pact5 HyperlanePluginTests" ] v :: ChainwebVersion -v = pact5InstantCpmTestVersion petersonChainGraph +v = pact5InstantCpmTestVersion petersenChainGraph chain0 :: ChainId chain0 = unsafeChainId 0 diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index 69a3179952..6eda32ec1b 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -97,16 +97,16 @@ v :: ChainwebVersion v = instantCpmTestVersion singletonChainGraph mkFixtureWith :: PactServiceConfig -> RocksDb -> ResourceT IO Fixture -mkFixtureWith pactServiceConfig baseRdb = do - tdb <- mkTestBlockDb v baseRdb +mkFixtureWith pactServiceConfig baseRdb = withVersion v $ do + tdb <- mkTestBlockDb baseRdb logLevel <- liftIO getTestLogLevel let logger = genericLogger logLevel Text.putStrLn - perChain <- iforM (HashSet.toMap (chainIds v)) $ \chain () -> do + perChain <- iforM (HashSet.toMap chainIds) $ \chain () -> do (writeSqlite, readPool) <- withTempChainSqlite chain let pdb = _bdbPayloadDb tdb - serviceEnv <- PactService.withPactService v chain Nothing mempty logger Nothing pdb readPool writeSqlite pactServiceConfig (GenesisPayload $ genesisPayload chain) + serviceEnv <- PactService.withPactService chain Nothing mempty logger Nothing pdb readPool writeSqlite pactServiceConfig (GenesisPayload $ genesisPayload chain) let mempoolCfg = - validatingMempoolConfig chain v + validatingMempoolConfig chain (GasLimit (Gas 150_000)) (GasPrice 1e-8) (PactService.execPreInsertCheckReq logger serviceEnv) @@ -155,11 +155,11 @@ tests baseRdb = testGroup "Pact5 PactServiceTest" -- 3. pure rewinds simpleEndToEnd :: RocksDb -> IO () -simpleEndToEnd baseRdb = runResourceT $ do +simpleEndToEnd baseRdb = withVersion v $ runResourceT $ do fixture <- mkFixture baseRdb liftIO $ do - cmd1 <- buildCwCmd v (transferCmd 1.0) - cmd2 <- buildCwCmd v (transferCmd 2.0) + cmd1 <- buildCwCmd (transferCmd 1.0) + cmd2 <- buildCwCmd (transferCmd 2.0) results <- advanceAllChainsWithTxs fixture $ onChain chain0 [cmd1, cmd2] @@ -169,10 +169,10 @@ simpleEndToEnd baseRdb = runResourceT $ do P.alignExact ? Vector.replicate 2 successfulTx newBlockEmpty :: RocksDb -> IO () -newBlockEmpty baseRdb = runResourceT $ do +newBlockEmpty baseRdb = withVersion v $ runResourceT $ do fixture <- mkFixture baseRdb liftIO $ do - cmd <- buildCwCmd v (transferCmd 1.0) + cmd <- buildCwCmd (transferCmd 1.0) mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd] _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do finalizeBlock fixture <$> makeEmptyBlock fixture ph @@ -186,15 +186,15 @@ newBlockEmpty baseRdb = runResourceT $ do P.alignExact ? Vector.replicate 1 successfulTx continueBlockSpec :: RocksDb -> IO () -continueBlockSpec baseRdb = runResourceT $ do +continueBlockSpec baseRdb = withVersion v $ runResourceT $ do fixture <- mkFixture baseRdb liftIO $ do startCut <- getCut fixture -- construct some transactions that we plan to put into the block - cmd1 <- buildCwCmd v (transferCmd 1.0) - cmd2 <- buildCwCmd v (transferCmd 2.0) - cmd3 <- buildCwCmd v (transferCmd 3.0) + cmd1 <- buildCwCmd (transferCmd 1.0) + cmd2 <- buildCwCmd (transferCmd 2.0) + cmd3 <- buildCwCmd (transferCmd 3.0) -- insert all transactions mempoolInsert fixture chain0 Mempool.CheckedInsert [cmd1, cmd2, cmd3] @@ -244,7 +244,7 @@ continueBlockSpec baseRdb = runResourceT $ do -- -- * test that the NewBlock timeout works properly and doesn't leave any extra state from a timed-out transaction newBlockTimeoutSpec :: RocksDb -> IO () -newBlockTimeoutSpec baseRdb = runResourceT $ do +newBlockTimeoutSpec baseRdb = withVersion v $ runResourceT $ do let pactServiceConfig = defaultPactServiceConfig { _pactTxTimeLimit = Just (Micros 35_000) -- this may need to be tweaked for CI. @@ -255,17 +255,17 @@ newBlockTimeoutSpec baseRdb = runResourceT $ do fixture <- mkFixtureWith pactServiceConfig baseRdb liftIO $ do - tx1 <- buildCwCmd v (defaultCmd chain0) + tx1 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "1" , _cbGasPrice = GasPrice 1.0 , _cbGasLimit = GasLimit (Gas 400) } - tx2 <- buildCwCmd v (defaultCmd chain0) + tx2 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "2" , _cbGasPrice = GasPrice 2.0 , _cbGasLimit = GasLimit (Gas 400) } - timeoutTx <- buildCwCmd v (defaultCmd chain0) + timeoutTx <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' $ "(fold + 0 (enumerate 1 10000000))" , _cbGasPrice = GasPrice 1.5 , _cbGasLimit = GasLimit (Gas 130000) @@ -276,7 +276,7 @@ newBlockTimeoutSpec baseRdb = runResourceT $ do bip <- continueBlock fixture =<< makeEmptyBlock fixture ph return $ finalizeBlock fixture bip results - & P.alignExact ? tabulateChains v (\cid -> + & P.alignExact ? tabulateChains (\cid -> if cid == chain0 then P.alignExact ? Vector.singleton ? -- Mempool orders by GasPrice. 'buildCwCmd' sets the gas price to the transfer amount. @@ -290,36 +290,36 @@ newBlockTimeoutSpec baseRdb = runResourceT $ do pure () testNewBlockExcludesInvalid :: RocksDb -> IO () -testNewBlockExcludesInvalid baseRdb = runResourceT $ do +testNewBlockExcludesInvalid baseRdb = withVersion v $ runResourceT $ do fixture <- mkFixture baseRdb liftIO $ do -- The mempool should reject a tx that doesn't parse as valid pact. -- TODO PP: let's test this in the mempool + pact REST API tests - -- badParse <- buildCwCmd v (defaultCmd chain0) + -- badParse <- buildCwCmd (defaultCmd chain0) -- { _cbRPC = mkExec' "(not a valid pact tx" -- } - regularTx1 <- buildCwCmd v $ transferCmd 1.0 + regularTx1 <- buildCwCmd $ transferCmd 1.0 -- The mempool checks that a tx does not already exist in the chain before adding it. let badUnique = regularTx1 -- The mempool checks that a tx does not have a creation time too far into the future. - badFuture <- buildCwCmd v $ (transferCmd 1.0) + badFuture <- buildCwCmd $ (transferCmd 1.0) { _cbCreationTime = Just $ TxCreationTime (2 ^ (32 :: Word)) } -- The mempool checks that a tx does not have a creation time too far into the past. - badPast <- buildCwCmd v $ (transferCmd 1.0) + badPast <- buildCwCmd $ (transferCmd 1.0) { _cbCreationTime = Just $ TxCreationTime 0 } - regularTx2 <- buildCwCmd v $ transferCmd 1.0 + regularTx2 <- buildCwCmd $ transferCmd 1.0 -- The mempool checks that a tx has a valid hash. let badTxHash = regularTx2 { _cmdHash = Pact.hash "wrong string" } - badSigs <- buildCwCmdNoSigCheck v (defaultCmd chain0) + badSigs <- buildCwCmdNoSigCheck (defaultCmd chain0) { _cbSigners = [ CmdSigner { _csSigner = Signer @@ -333,7 +333,7 @@ testNewBlockExcludesInvalid baseRdb = runResourceT $ do ] } - badChain <- buildCwCmd v $ transferCmd 1.0 & set cbChainId (chainIdToText $ unsafeChainId 1) + badChain <- buildCwCmd $ transferCmd 1.0 & set cbChainId (chainIdToText $ unsafeChainId 1) _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do mempoolInsert fixture chain0 Mempool.CheckedInsert [regularTx1] @@ -374,11 +374,11 @@ testNewBlockExcludesInvalid baseRdb = runResourceT $ do return () lookupPactTxsSpec :: RocksDb -> IO () -lookupPactTxsSpec baseRdb = runResourceT $ do +lookupPactTxsSpec baseRdb = withVersion v $ runResourceT $ do fixture <- mkFixture baseRdb liftIO $ do - cmd1 <- buildCwCmd v (transferCmd 1.0) - cmd2 <- buildCwCmd v (transferCmd 2.0) + cmd1 <- buildCwCmd (transferCmd 1.0) + cmd2 <- buildCwCmd (transferCmd 2.0) -- Depth 0 _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do @@ -421,12 +421,12 @@ lookupPactTxsSpec baseRdb = runResourceT $ do lookupDontExpect (Just 3) failedTxsShouldGoIntoBlocks :: RocksDb -> IO () -failedTxsShouldGoIntoBlocks baseRdb = runResourceT $ do +failedTxsShouldGoIntoBlocks baseRdb = withVersion v $ runResourceT $ do fixture <- mkFixture baseRdb liftIO $ do - cmd1 <- buildCwCmd v (transferCmd 1.0) - cmd2 <- buildCwCmd v (defaultCmd chain0) + cmd1 <- buildCwCmd (transferCmd 1.0) + cmd2 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (module mod G (defcap G () true) (defun f () true)) (describe-module \"free.mod\")" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.1 @@ -444,34 +444,34 @@ failedTxsShouldGoIntoBlocks baseRdb = runResourceT $ do return () modulesWithHigherLevelTransitiveDependenciesSimple :: RocksDb -> IO () -modulesWithHigherLevelTransitiveDependenciesSimple baseRdb = runResourceT $ do +modulesWithHigherLevelTransitiveDependenciesSimple baseRdb = withVersion v $ runResourceT $ do fixture <- mkFixture baseRdb liftIO $ do - cmd1 <- buildCwCmd v (defaultCmd chain0) + cmd1 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.9 , _cbGasLimit = GasLimit (Gas 1000) } - cmd2 <- buildCwCmd v (defaultCmd chain0) + cmd2 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (module foo g (defcap g () true) (defun calls-foo () barbar.FOO_CONST))" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.8 , _cbGasLimit = GasLimit (Gas 1000) } - cmd3 <- buildCwCmd v (defaultCmd chain0) + cmd3 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (foo.calls-foo)))" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.7 , _cbGasLimit = GasLimit (Gas 1000) } - cmd4 <- buildCwCmd v (defaultCmd chain0) + cmd4 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.6 , _cbGasLimit = GasLimit (Gas 1000) } - cmd5 <- buildCwCmd v (defaultCmd chain0) + cmd5 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.5 @@ -501,16 +501,16 @@ modulesWithHigherLevelTransitiveDependenciesSimple baseRdb = runResourceT $ do return () modulesWithHigherLevelTransitiveDependenciesComplex :: RocksDb -> IO () -modulesWithHigherLevelTransitiveDependenciesComplex baseRdb = runResourceT $ do +modulesWithHigherLevelTransitiveDependenciesComplex baseRdb = withVersion v $ runResourceT $ do fixture <- mkFixture baseRdb liftIO $ do - cmd1 <- buildCwCmd v (defaultCmd chain0) + cmd1 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (interface barbar (defconst FOO_CONST:integer 1))" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.9 , _cbGasLimit = GasLimit (Gas 1000) } - cmd2 <- buildCwCmd v (defaultCmd chain0) + cmd2 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' $ T.unlines [ "(namespace 'free)" , "(module foo g" @@ -536,19 +536,19 @@ modulesWithHigherLevelTransitiveDependenciesComplex baseRdb = runResourceT $ do , _cbGasPrice = GasPrice 0.8 , _cbGasLimit = GasLimit (Gas 1000) } - cmd3 <- buildCwCmd v (defaultCmd chain0) + cmd3 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (module bar g (defcap g () true) (defun calls-bar () (install-capability (foo.FOO_MANAGED \"bob\" 100)) (foo.calls-foo \"bob\" 100)))" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.7 , _cbGasLimit = GasLimit (Gas 1000) } - cmd4 <- buildCwCmd v (defaultCmd chain0) + cmd4 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (module baz g (defcap g () true) (defun calls-baz () (bar.calls-bar)))" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.6 , _cbGasLimit = GasLimit (Gas 1000) } - cmd5 <- buildCwCmd v (defaultCmd chain0) + cmd5 <- buildCwCmd (defaultCmd chain0) { _cbRPC = mkExec' "(namespace 'free) (baz.calls-baz)" -- for ordering the transactions as they appear in the block , _cbGasPrice = GasPrice 0.5 @@ -599,27 +599,27 @@ modulesWithHigherLevelTransitiveDependenciesComplex baseRdb = runResourceT $ do chain0 :: ChainId chain0 = unsafeChainId 0 -finalizeBlock :: Fixture -> BlockInProgress -> PayloadWithOutputs +finalizeBlock :: HasVersion => Fixture -> BlockInProgress -> PayloadWithOutputs finalizeBlock Fixture{..} bip = toPayloadWithOutputs (fromJuste $ _psMiner $ _fixturePacts ^?! atChain (_chainId bip)) (_blockInProgressTransactions bip) -makeEmptyBlock :: Fixture -> Parent BlockHeader -> IO BlockInProgress +makeEmptyBlock :: HasVersion => Fixture -> Parent BlockHeader -> IO BlockInProgress makeEmptyBlock Fixture{..} ph = do Pool.withResource (_psReadSqlPool serviceEnv) $ \roSql -> do (throwIfNoHistory =<<) $ - Checkpointer.readFrom _fixtureLogger v cid roSql (view blockCreationTime <$> ph) (view rankedBlockHash <$> ph) $ + Checkpointer.readFrom _fixtureLogger cid roSql (view blockCreationTime <$> ph) (view rankedBlockHash <$> ph) $ \blockEnv initialBlockHandle -> PactService.makeEmptyBlock _fixtureLogger serviceEnv blockEnv initialBlockHandle where cid = _chainId ph serviceEnv = _fixturePacts ^?! atChain cid -continueBlock :: Fixture -> BlockInProgress -> IO BlockInProgress +continueBlock :: HasVersion => Fixture -> BlockInProgress -> IO BlockInProgress continueBlock Fixture{..} bip = do Pool.withResource (_psReadSqlPool serviceEnv) $ \roSql -> do (throwIfNoHistory =<<) $ - Checkpointer.readFrom _fixtureLogger v cid roSql parentCreationTime parentRankedHash $ + Checkpointer.readFrom _fixtureLogger cid roSql parentCreationTime parentRankedHash $ \blockEnv _initialBlockHandle -> PactService.continueBlock _fixtureLogger serviceEnv (_psBlockDbEnv blockEnv) bip where parentCreationTime = (_bctxParentCreationTime $ _blockInProgressBlockCtx bip) @@ -627,10 +627,10 @@ continueBlock Fixture{..} bip = do cid = _chainId bip serviceEnv = _fixturePacts ^?! atChain cid -makeFilledBlock :: Fixture -> Parent BlockHeader -> IO BlockInProgress +makeFilledBlock :: HasVersion => Fixture -> Parent BlockHeader -> IO BlockInProgress makeFilledBlock fixture ph = continueBlock fixture =<< makeEmptyBlock fixture ph -lookupPactTxs :: Fixture -> ChainId -> Maybe ConfirmationDepth -> Vector Pact.Hash -> IO (Historical (HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash))) +lookupPactTxs :: HasVersion => Fixture -> ChainId -> Maybe ConfirmationDepth -> Vector Pact.Hash -> IO (Historical (HashMap SB.ShortByteString (T3 BlockHeight BlockPayloadHash BlockHash))) lookupPactTxs Fixture{..} chain depth hashes = PactService.execLookupPactTxs _fixtureLogger (_fixturePacts ^?! atChain chain) depth (Pact.unHash <$> hashes) @@ -642,9 +642,11 @@ mempoolClear :: Fixture -> ChainId -> IO () mempoolClear Fixture{..} cid = Mempool.mempoolClear (_fixtureMempools ^?! atChain cid) -advanceAllChainsWithTxs :: Fixture -> ChainMap [Pact.Transaction] -> IO (ChainMap (Vector TestPact5CommandResult)) +advanceAllChainsWithTxs + :: HasVersion + => Fixture -> ChainMap [Pact.Transaction] -> IO (ChainMap (Vector TestPact5CommandResult)) advanceAllChainsWithTxs fixture txsPerChain = do - advanceAllChains fixture $ tabulateChains v $ \cid ph -> do + advanceAllChains fixture $ tabulateChains $ \cid ph -> do let txs = txsPerChain ^?! atChain cid mempoolClear fixture cid mempoolInsert fixture cid Mempool.CheckedInsert txs @@ -653,13 +655,14 @@ advanceAllChainsWithTxs fixture txsPerChain = do -- this mines a block on *all chains*. if you don't specify a payload on a chain, -- it adds empty blocks! -advanceAllChains :: () +advanceAllChains + :: HasVersion => Fixture -> ChainMap (Parent BlockHeader -> IO PayloadWithOutputs) -> IO (ChainMap (Vector TestPact5CommandResult)) advanceAllChains fixture@Fixture{..} blocks = do commandResults <- - forConcurrently (HashSet.toList (chainIds v)) $ \c -> do + forConcurrently (HashSet.toList chainIds) $ \c -> do ph <- getParentTestBlockDb _fixtureBlockDb c creationTime <- getCurrentTimeIntegral let serviceEnv = _fixturePacts ^?! atChain c @@ -667,7 +670,7 @@ advanceAllChains fixture@Fixture{..} blocks = do Nothing -> finalizeBlock fixture <$> makeEmptyBlock fixture ph Just mkBlockOn -> mkBlockOn ph added <- addTestBlockDb _fixtureBlockDb - (childBlockHeight v c $ view rankedBlockHash <$> ph) + (childBlockHeight c $ view rankedBlockHash <$> ph) (Nonce 0) (\_ _ -> creationTime) c @@ -692,7 +695,7 @@ advanceAllChains fixture@Fixture{..} blocks = do return (onChains commandResults) -blockToForkInfo :: BlockHeader -> Parent BlockHeader -> Maybe NewBlockCtx -> ForkInfo +blockToForkInfo :: HasVersion => BlockHeader -> Parent BlockHeader -> Maybe NewBlockCtx -> ForkInfo blockToForkInfo bh ph newBlockCtx = ForkInfo { _forkInfoTrace = [ConsensusPayload (view blockPayloadHash bh) Nothing <$ @@ -704,13 +707,13 @@ blockToForkInfo bh ph newBlockCtx = ForkInfo where syncState = syncStateOfBlockHeader bh -getCut :: Fixture -> IO Cut +getCut :: HasVersion => Fixture -> IO Cut getCut Fixture{..} = getCutTestBlockDb _fixtureBlockDb -revert :: Fixture -> Cut -> IO () +revert :: HasVersion => Fixture -> Cut -> IO () revert Fixture{..} c = do setCutTestBlockDb _fixtureBlockDb c - forM_ (HashSet.toList (chainIds v)) $ \chain -> do + forM_ (HashSet.toList chainIds) $ \chain -> do ph <- getParentTestBlockDb _fixtureBlockDb chain let syncState = syncStateOfBlockHeader (unwrapParent ph) let serviceEnv = _fixturePacts ^?! atChain chain diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index 67064561f7..81880f9882 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -155,20 +155,19 @@ tests rdb = withResource' (evaluate httpManager >> evaluate cert) $ \_ -> ] pollingInvalidRequestKeyTest :: RocksDb -> Step -> IO () -pollingInvalidRequestKeyTest baseRdb _step = runResourceT $ do - let v = instantCpmTestVersion singletonChainGraph - let cid = unsafeChainId 0 - fx <- mkFixture v baseRdb +pollingInvalidRequestKeyTest baseRdb _step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do - poll fx v cid [pactDeadBeef] >>= + poll fx cid [pactDeadBeef] >>= P.equals [Nothing] + where + v = instantCpmTestVersion singletonChainGraph + cid = unsafeChainId 0 pollingConfirmationDepthTest :: RocksDb -> Step -> IO () -pollingConfirmationDepthTest baseRdb _step = runResourceT $ do - let v = instantCpmTestVersion singletonChainGraph - let cid = unsafeChainId 0 - fx <- mkFixture v baseRdb +pollingConfirmationDepthTest baseRdb _step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb let trivialTx :: Word -> CmdBuilder trivialTx n = (defaultCmd cid) @@ -176,8 +175,8 @@ pollingConfirmationDepthTest baseRdb _step = runResourceT $ do } liftIO $ do - cmd1 <- buildTextCmd v (trivialTx 42) - cmd2 <- buildTextCmd v (trivialTx 43) + cmd1 <- buildTextCmd (trivialTx 42) + cmd2 <- buildTextCmd (trivialTx 43) let rks = [cmdToRequestKey cmd1, cmdToRequestKey cmd2] let expectSuccessful :: (HasCallStack) => P.Prop [Maybe TestPact5CommandResult] @@ -189,59 +188,61 @@ pollingConfirmationDepthTest baseRdb _step = runResourceT $ do let expectEmpty :: (HasCallStack, Foldable t, Eq a, Show a) => t (Maybe a) -> IO () expectEmpty = traverse_ (P.equals Nothing) - send fx v cid [cmd1, cmd2] + send fx cid [cmd1, cmd2] - pollWithDepth fx v cid rks Nothing + pollWithDepth fx cid rks Nothing >>= expectEmpty - pollWithDepth fx v cid rks (Just (ConfirmationDepth 0)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 0)) >>= expectEmpty advanceAllChains_ fx - pollWithDepth fx v cid rks Nothing + pollWithDepth fx cid rks Nothing >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 0)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 0)) >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 1)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 1)) >>= expectEmpty advanceAllChains_ fx - pollWithDepth fx v cid rks Nothing + pollWithDepth fx cid rks Nothing >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 0)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 0)) >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 1)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 1)) >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 2)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 2)) >>= expectEmpty advanceAllChains_ fx - pollWithDepth fx v cid rks Nothing + pollWithDepth fx cid rks Nothing >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 0)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 0)) >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 1)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 1)) >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 2)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 2)) >>= expectSuccessful - pollWithDepth fx v cid rks (Just (ConfirmationDepth 3)) + pollWithDepth fx cid rks (Just (ConfirmationDepth 3)) >>= expectEmpty return () + where + v = instantCpmTestVersion singletonChainGraph + cid = unsafeChainId 0 crosschainTest :: RocksDb -> Step -> IO () -crosschainTest baseRdb step = runResourceT $ do - let v = instantCpmTestVersion petersenChainGraph - fx <- mkFixture v baseRdb +crosschainTest baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb let srcChain = unsafeChainId 0 let targetChain = unsafeChainId 9 liftIO $ do step "xchain initiate" - initiator <- buildTextCmd v + initiator <- buildTextCmd $ set cbSigners [ mkEd25519Signer' sender00 [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] @@ -256,26 +257,26 @@ crosschainTest baseRdb step = runResourceT $ do $ set cbRPC (mkExec ("(coin.transfer-crosschain \"sender00\" \"sender01\" (read-keyset 'k) \"" <> chainIdToText targetChain <> "\" 1.0)") (mkKeySetData "k" [sender01])) $ defaultCmd srcChain - send fx v srcChain [initiator] + send fx srcChain [initiator] let initiatorReqKey = cmdToRequestKey initiator -- what if the source chain hasn't got the xchain transfer in a block yet? - spvTxOutProof fx v targetChain srcChain initiatorReqKey + spvTxOutProof fx targetChain srcChain initiatorReqKey & P.throws ? P.match _FailureResponse ? P.fun responseBody ? P.equals ("Transaction hash not found: " <> sshow initiatorReqKey) advanceAllChains_ fx - [Just sendCr] <- pollWithDepth fx v srcChain [initiatorReqKey] (Just (ConfirmationDepth 0)) + [Just sendCr] <- pollWithDepth fx srcChain [initiatorReqKey] (Just (ConfirmationDepth 0)) let cont = fromMaybe (error "missing continuation") (_crContinuation sendCr) -- what if the target chain isn't aware of the source xchain transfer yet? - spvTxOutProof fx v targetChain srcChain initiatorReqKey + spvTxOutProof fx targetChain srcChain initiatorReqKey & P.throws ? P.match _FailureResponse ? P.fun responseBody ? P.equals "SPV target not reachable: target chain not reachable. Chainweb instance is too young" step "waiting" replicateM_ (int $ diameter petersenChainGraph) $ advanceAllChains_ fx - TransactionOutputProofB64 spvProof <- spvTxOutProof fx v targetChain srcChain initiatorReqKey + TransactionOutputProofB64 spvProof <- spvTxOutProof fx targetChain srcChain initiatorReqKey let contMsg = ContMsg { _cmPactId = _peDefPactId cont , _cmStep = succ $ _peStep cont @@ -286,25 +287,25 @@ crosschainTest baseRdb step = runResourceT $ do step "xchain recv" -- what if we try to finish the xchain on the wrong chain? - recvWrongChain <- buildTextCmd v + recvWrongChain <- buildTextCmd $ set cbRPC (mkCont contMsg) $ defaultCmd srcChain - send fx v srcChain [recvWrongChain] + send fx srcChain [recvWrongChain] let recvWrongChainReqKey = cmdToRequestKey recvWrongChain advanceAllChains_ fx - poll fx v srcChain [recvWrongChainReqKey] + poll fx srcChain [recvWrongChainReqKey] >>= P.match (_head . _Just) ? P.fun _crResult ? P.match _PactResultErr ? P.fun _peMsg -- sic ? P.equals "Continuation error: verifyCont: cannot redeem continuation proof on wrong targget chain" - recv <- buildTextCmd v + recv <- buildTextCmd $ set cbRPC (mkCont contMsg) $ defaultCmd targetChain - send fx v targetChain [recv] + send fx targetChain [recv] let recvReqKey = cmdToRequestKey recv advanceAllChains_ fx - poll fx v targetChain [recvReqKey] + poll fx targetChain [recvReqKey] >>= P.match (_head . _Just) ? P.checkAll [ P.fun _crResult ? P.match _PactResultOk P.succeed @@ -321,55 +322,57 @@ crosschainTest baseRdb step = runResourceT $ do ] -- what if we try to complete an already-completed xchain? - recvRepeated <- buildTextCmd v + recvRepeated <- buildTextCmd $ set cbRPC (mkCont contMsg) $ defaultCmd targetChain - send fx v targetChain [recvRepeated] + send fx targetChain [recvRepeated] let recvRepeatedReqKey = cmdToRequestKey recvRepeated advanceAllChains_ fx - poll fx v targetChain [recvRepeatedReqKey] + poll fx targetChain [recvRepeatedReqKey] >>= P.match (_head . _Just) ? P.fun _crResult ? P.match _PactResultErr ? P.fun _peMsg ? P.fun _boundedText ? P.equals ("Requested defpact execution already completed for defpact id: " <> T.take 20 (renderDefPactId $ _peDefPactId cont) <> "...") + where + v = instantCpmTestVersion petersenChainGraph -- this test suite really wants you not to put any transactions into the final block. sendInvalidTxsTest :: RocksDb -> TestTree -sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> +sendInvalidTxsTest rdb = withVersion v $ withResourceT (mkFixture rdb) $ \fx -> sequentialTestGroup "invalid txs in /send" AllFinish [ testGroup "send txs" [ testCase "syntax error" $ do - cmdParseFailure <- buildTextCmd v + cmdParseFailure <- buildTextCmd $ set cbRPC (mkExec' "(+ 1") $ defaultCmd cid - send fx v cid [cmdParseFailure] + send fx cid [cmdParseFailure] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains "ParseError: Expected: [')']" , testCase "invalid hash" $ do cmdInvalidPayloadHash <- do - bareCmd <- buildTextCmd v + bareCmd <- buildTextCmd $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) $ defaultCmd cid pure $ bareCmd { _cmdHash = hash "fakehash" } - send fx v cid [cmdInvalidPayloadHash] + send fx cid [cmdInvalidPayloadHash] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdInvalidPayloadHash "Invalid transaction hash") , testCase "signature length mismatch" $ do cmdSignersSigsLengthMismatch1 <- do - bareCmd <- buildTextCmd v + bareCmd <- buildTextCmd $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) $ defaultCmd cid pure $ bareCmd { _cmdSigs = [] } - send fx v cid [cmdSignersSigsLengthMismatch1] + send fx cid [cmdSignersSigsLengthMismatch1] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdSignersSigsLengthMismatch1 "Invalid transaction sigs: The number of signers and signatures do not match. Number of signers: 1. Number of signatures: 0.") cmdSignersSigsLengthMismatch2 <- do - bareCmd <- buildTextCmd v + bareCmd <- buildTextCmd $ set cbSigners [] $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) $ defaultCmd cid @@ -379,13 +382,13 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> -- but length signers == length signatures is checked first _cmdSigs = [ED25519Sig "fakeSig"] } - send fx v cid [cmdSignersSigsLengthMismatch2] + send fx cid [cmdSignersSigsLengthMismatch2] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdSignersSigsLengthMismatch2 "Invalid transaction sigs: The number of signers and signatures do not match. Number of signers: 0. Number of signatures: 1.") , testCase "invalid signatures" $ do cmdInvalidUserSig <- mkCmdInvalidUserSig - send fx v cid [cmdInvalidUserSig] + send fx cid [cmdInvalidUserSig] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") @@ -394,7 +397,7 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> cmdInvalidUserSig <- mkCmdInvalidUserSig -- Test that [badCmd, goodCmd] fails on badCmd, and the batch is rejected. -- We just re-use a previously built bad cmd. - send fx v cid [cmdInvalidUserSig, cmdGood] + send fx cid [cmdInvalidUserSig, cmdGood] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") @@ -402,7 +405,7 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> -- Order matters, and the error message also indicates the position of the -- failing tx. -- We just re-use a previously built bad cmd. - send fx v cid [cmdGood, cmdInvalidUserSig] + send fx cid [cmdGood, cmdInvalidUserSig] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 1 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") @@ -410,50 +413,48 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> , testCase "multiple bad txs in batch" $ do cmdGood <- mkCmdGood cmdInvalidUserSig <- mkCmdInvalidUserSig - cmdParseFailure <- buildTextCmd v + cmdParseFailure <- buildTextCmd $ set cbRPC (mkExec' "(+ 1") $ defaultCmd cid -- if any tx fails parsing, no txs even get validated - send fx v cid [cmdInvalidUserSig, cmdGood, cmdParseFailure] + send fx cid [cmdInvalidUserSig, cmdGood, cmdParseFailure] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (parseFailed 2 "ParseError: Expected: [')']") -- without parse failures, all txs get validated - send fx v cid [cmdInvalidUserSig, cmdGood] + send fx cid [cmdInvalidUserSig, cmdGood] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdInvalidUserSig "Invalid transaction sigs: The signature at position 0 is invalid: failed to parse ed25519 signature: invalid bytestring size.") , testCase "invalid metadata" $ do cmdGood <- mkCmdGood - send fx v wrongChain [cmdGood] + send fx wrongChain [cmdGood] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdGood "Transaction metadata (chain id, chainweb version) conflicts with this endpoint") - send fx wrongV cid [cmdGood] + sendWrongV fx cid [cmdGood] & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals notFound404 , P.fun responseBody ? P.equals "" ] let invalidCid = "invalid chain ID" - cmdInvalidChain <- buildTextCmd v (defaultCmd cid & set cbChainId invalidCid) - send fx v wrongChain [cmdInvalidChain] + cmdInvalidChain <- buildTextCmd (defaultCmd cid & set cbChainId invalidCid) + send fx wrongChain [cmdInvalidChain] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdInvalidChain "Transaction metadata (chain id, chainweb version) conflicts with this endpoint") - cmdWrongV <- buildTextCmd wrongV - $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) - $ defaultCmd cid - send fx v cid [cmdWrongV] + cmdWrongV <- buildCmdWrongV + send fx cid [cmdWrongV] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains (validationFailed 0 cmdWrongV "Transaction metadata (chain id, chainweb version) conflicts with this endpoint") - cmdExpiredTTL <- buildTextCmd v (defaultCmd cid & cbCreationTime .~ Just (Pact.TxCreationTime 0)) - send fx v cid [cmdExpiredTTL] + cmdExpiredTTL <- buildTextCmd (defaultCmd cid & cbCreationTime .~ Just (Pact.TxCreationTime 0)) + send fx cid [cmdExpiredTTL] & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains @@ -462,10 +463,10 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> ] , testCase "cannot buy gas" $ do - cmdExcessiveGasLimit <- buildTextCmd v + cmdExcessiveGasLimit <- buildTextCmd $ set cbGasLimit (GasLimit $ Gas 100000000000000) $ defaultCmd cid - send fx v cid [cmdExcessiveGasLimit] + send fx cid [cmdExcessiveGasLimit] & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains @@ -473,10 +474,10 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> "Transaction gas limit exceeds block gas limit") ] - cmdGasPriceTooPrecise <- buildTextCmd v + cmdGasPriceTooPrecise <- buildTextCmd $ set cbGasPrice (GasPrice 0.00000000000000001) $ defaultCmd cid - send fx v cid [cmdGasPriceTooPrecise] + send fx cid [cmdGasPriceTooPrecise] & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains @@ -484,11 +485,11 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> "This transaction's gas price (0.00000000000000001) is not correctly rounded. It should be rounded to at most 12 decimal places.") ] - cmdNotEnoughGasFunds <- buildTextCmd v + cmdNotEnoughGasFunds <- buildTextCmd $ set cbGasPrice (GasPrice 10_000_000_000) $ set cbGasLimit (GasLimit (Gas 10_000)) $ defaultCmd cid - send fx v cid [cmdNotEnoughGasFunds] + send fx cid [cmdNotEnoughGasFunds] & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains @@ -496,10 +497,10 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> "Failed to buy gas: Insufficient funds") ] - cmdInvalidSender <- buildTextCmd v + cmdInvalidSender <- buildTextCmd $ set cbSender "invalid-sender" $ defaultCmd cid - send fx v cid [cmdInvalidSender] + send fx cid [cmdInvalidSender] & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? textContains @@ -519,7 +520,11 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> where v = instantCpmTestVersion petersenChainGraph wrongV = instantCpmTestVersion twentyChainGraph - + buildCmdWrongV = withVersion wrongV $ + buildTextCmd + $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) + $ defaultCmd cid + sendWrongV = withVersion wrongV $ send cid = unsafeChainId 0 wrongChain = unsafeChainId 1 @@ -528,20 +533,18 @@ sendInvalidTxsTest rdb = withResourceT (mkFixture v rdb) $ \fx -> mkCmdInvalidUserSig = mkCmdGood <&> set cmdSigs [ED25519Sig "fakeSig"] - mkCmdGood = buildTextCmd v + mkCmdGood = withVersion v + $ buildTextCmd $ set cbRPC (mkExec "(+ 1 2)" (mkKeySetData "sender00" [sender00])) $ defaultCmd cid caplistTest :: RocksDb -> Step -> IO () -caplistTest baseRdb step = runResourceT $ do - let v = instantCpmTestVersion petersenChainGraph - fx <- mkFixture v baseRdb - - let cid = unsafeChainId 0 +caplistTest baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do - tx0 <- buildTextCmd v + tx0 <- buildTextCmd $ set cbSigners [ mkEd25519Signer' sender00 [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] @@ -556,7 +559,7 @@ caplistTest baseRdb step = runResourceT $ do $ defaultCmd cid step "sending" - send fx v cid [tx0] + send fx cid [tx0] let recvReqKey = cmdToRequestKey tx0 @@ -566,7 +569,7 @@ caplistTest baseRdb step = runResourceT $ do step "polling" - poll fx v cid [recvReqKey] + poll fx cid [recvReqKey] >>= P.alignExact ? List.singleton ? P.match _Just ? P.checkAll [ P.fun _crResult ? P.match (_PactResultOk . _PString) ? P.equals "Write succeeded" @@ -574,6 +577,9 @@ caplistTest baseRdb step = runResourceT $ do ? P.match (_Just . A._Object . at "blockHash") ? P.match (_Just . A._String . b64UrlNoPaddingPrism) P.succeed ] + where + v = instantCpmTestVersion petersenChainGraph + cid = unsafeChainId 0 allocation01KeyPair :: (Text, Text) @@ -595,21 +601,19 @@ allocation02KeyPair' = ) allocationTest :: RocksDb -> (String -> IO ()) -> IO () -allocationTest rdb step = runResourceT $ do - let v = instantCpmTestVersion petersenChainGraph - let cid = unsafeChainId 0 - fx <- mkFixture v rdb +allocationTest rdb step = withVersion v $ runResourceT $ do + fx <- mkFixture rdb liftIO $ do do step "allocation00" - release00Cmd <- buildTextCmd v + release00Cmd <- buildTextCmd $ set cbSigners [mkEd25519Signer' allocation00KeyPair [], mkEd25519Signer' sender00 []] $ set cbRPC (mkExec' "(coin.release-allocation \"allocation00\")") $ defaultCmd cid - send fx v cid [release00Cmd] + send fx cid [release00Cmd] advanceAllChains_ fx - poll fx v cid [cmdToRequestKey release00Cmd] >>= + poll fx cid [cmdToRequestKey release00Cmd] >>= P.alignExact [ P.match _Just ? P.checkAll [ P.fun _crResult ? P.match _PactResultOk ? P.succeed @@ -621,9 +625,9 @@ allocationTest rdb step = runResourceT $ do ] ] - buildTextCmd v + buildTextCmd (set cbRPC (mkExec' "(coin.details \"allocation00\")") $ defaultCmd cid) - >>= local fx v cid Nothing Nothing Nothing + >>= local fx cid Nothing Nothing Nothing >>= P.match _LocalResultLegacy ? P.fun _crResult ? P.match _PactResultOk @@ -636,12 +640,12 @@ allocationTest rdb step = runResourceT $ do step "allocation01" do - buildTextCmd v + buildTextCmd (set cbRPC (mkExec' "(coin.release-allocation \"allocation01\")") $ set cbSigners [mkEd25519Signer' allocation01KeyPair [], mkEd25519Signer' sender00 []] $ defaultCmd cid) - >>= local fx v cid Nothing Nothing Nothing + >>= local fx cid Nothing Nothing Nothing >>= P.match _LocalResultLegacy ? P.fun _crResult ? P.match _PactResultErr @@ -650,12 +654,12 @@ allocationTest rdb step = runResourceT $ do , P.fun _peMsg ? P.fun _boundedText ? textContains "funds locked until \"2100-10-31T18:00:00Z\"." ] - buildTextCmd v + buildTextCmd (set cbRPC (mkExec' "(coin.release-allocation \"allocation01\")") $ set cbSigners [mkEd25519Signer' allocation01KeyPair [], mkEd25519Signer' sender00 []] $ defaultCmd cid) - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + >>= local fx cid (Just PreflightSimulation) Nothing Nothing >>= P.match (_LocalResultWithWarns . _1) ? P.fun _crResult ? P.match _PactResultErr ? P.checkAll @@ -665,7 +669,7 @@ allocationTest rdb step = runResourceT $ do step "allocation02" do - redefineKeysetCmd <- buildTextCmd v + redefineKeysetCmd <- buildTextCmd $ set cbSigners [mkEd25519Signer' allocation02KeyPair []] $ set cbSender "allocation02" $ set cbRPC (mkExec @@ -673,23 +677,23 @@ allocationTest rdb step = runResourceT $ do (mkKeySetData "allocation02-keyset" [allocation02KeyPair']) ) $ defaultCmd cid - send fx v cid [redefineKeysetCmd] + send fx cid [redefineKeysetCmd] advanceAllChains_ fx - poll fx v cid [cmdToRequestKey redefineKeysetCmd] + poll fx cid [cmdToRequestKey redefineKeysetCmd] >>= P.alignExact [P.match _Just successfulTx] - releaseAllocationCmd <- buildTextCmd v + releaseAllocationCmd <- buildTextCmd $ set cbSender "allocation02" $ set cbSigners [mkEd25519Signer' allocation02KeyPair' []] $ set cbRPC (mkExec' "(coin.release-allocation \"allocation02\")") $ defaultCmd cid - send fx v cid [releaseAllocationCmd] + send fx cid [releaseAllocationCmd] advanceAllChains_ fx - poll fx v cid [cmdToRequestKey releaseAllocationCmd] + poll fx cid [cmdToRequestKey releaseAllocationCmd] >>= P.alignExact [P.match _Just successfulTx] - buildTextCmd v (set cbRPC (mkExec' "(coin.details \"allocation02\")") $ defaultCmd cid) - >>= local fx v cid Nothing Nothing Nothing + buildTextCmd (set cbRPC (mkExec' "(coin.details \"allocation02\")") $ defaultCmd cid) + >>= local fx cid Nothing Nothing Nothing >>= P.match _LocalResultLegacy ? P.fun _crResult ? P.match _PactResultOk @@ -699,12 +703,13 @@ allocationTest rdb step = runResourceT $ do , ("balance", (PDecimal 1_099_999.9748)) -- 1k + 1mm - gas , ("guard", (PGuard $ GKeySetRef (KeySetName "allocation02" Nothing))) ] + where + v = instantCpmTestVersion petersenChainGraph + cid = unsafeChainId 0 gasPurchaseFailureMessages :: RocksDb -> Step -> IO () -gasPurchaseFailureMessages rdb _step = runResourceT $ do - let v = instantCpmTestVersion petersenChainGraph - let cid = unsafeChainId 0 - fx <- mkFixture v rdb +gasPurchaseFailureMessages rdb _step = withVersion v $ runResourceT $ do + fx <- mkFixture rdb -- Check the ways buyGas can fail and its error messages. -- Each case is checked with both `/local` (with preflight) and `/send`. @@ -713,14 +718,14 @@ gasPurchaseFailureMessages rdb _step = runResourceT $ do -- (gas price * gas limit) should return an error -- this relies on sender00's starting balance. do - cmd <- buildTextCmd v + cmd <- buildTextCmd $ set cbSender "sender00" $ set cbSigners [mkEd25519Signer' sender00 []] $ set cbGasPrice (GasPrice 70_000) $ set cbGasLimit (GasLimit (Gas 100_000)) $ defaultCmd cid - local fx v cid (Just PreflightSimulation) Nothing Nothing cmd + local fx cid (Just PreflightSimulation) Nothing Nothing cmd >>= P.match _LocalResultWithWarns ? P.fun fst ? P.fun _crResult @@ -730,7 +735,7 @@ gasPurchaseFailureMessages rdb _step = runResourceT $ do , P.fun _peMsg ? P.fun _boundedText ? textContains "Failed to buy gas: Insufficient funds" ] - send fx v cid [cmd] + send fx cid [cmd] & P.throws ? P.match _FailureResponse ? P.fun responseBody @@ -739,7 +744,7 @@ gasPurchaseFailureMessages rdb _step = runResourceT $ do -- multiple gas payer caps should lead to an error, because it's unclear -- which module will pay for gas do - cmd <- buildTextCmd v + cmd <- buildTextCmd $ set cbSender "sender00" $ set cbSigners [ mkEd25519Signer' sender00 @@ -750,7 +755,7 @@ gasPurchaseFailureMessages rdb _step = runResourceT $ do ] $ defaultCmd cid - local fx v cid (Just PreflightSimulation) Nothing Nothing cmd + local fx cid (Just PreflightSimulation) Nothing Nothing cmd >>= P.match _LocalResultWithWarns ? P.fun fst ? P.fun _crResult @@ -761,38 +766,40 @@ gasPurchaseFailureMessages rdb _step = runResourceT $ do ? textContains "Failed to buy gas: multiple gas payer capabilities in signers list" ] - send fx v cid [cmd] + send fx cid [cmd] & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains "Failed to buy gas: multiple gas payer capabilities in signers list" return () + where + v = instantCpmTestVersion petersenChainGraph + cid = unsafeChainId 0 -- Test that transactions signed with (mock) WebAuthn keypairs are accepted -- by the pact service. webAuthnSignatureTest :: RocksDb -> Step -> IO () -webAuthnSignatureTest rdb _step = runResourceT $ do - let v = instantCpmTestVersion petersenChainGraph - let cid = unsafeChainId 0 - fx <- mkFixture v rdb +webAuthnSignatureTest rdb _step = withVersion v $ runResourceT $ do + fx <- mkFixture rdb liftIO $ do - cmd <- buildTextCmd v + cmd <- buildTextCmd $ set cbSigners [mkWebAuthnSigner' sender02WebAuthn [], mkEd25519Signer' sender00 []] $ set cbRPC (mkExec' "(concat [\"chainweb-\" \"node\"])") $ defaultCmd cid - send fx v cid [cmd] + send fx cid [cmd] advanceAllChains_ fx - poll fx v cid [cmdToRequestKey cmd] >>= + poll fx cid [cmdToRequestKey cmd] >>= P.alignExact [P.match _Just successfulTx] - -localTests :: RocksDb -> TestTree -localTests baseRdb = let + where v = instantCpmTestVersion petersenChainGraph cid = unsafeChainId 0 - in testGroup "tests for local" - [ testCase "ordinary txs" $ runResourceT $ do - fx <- mkFixture v baseRdb + +localTests :: RocksDb -> TestTree +localTests baseRdb = + testGroup "tests for local" + [ testCase "ordinary txs" $ withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do let expectation = P.checkAll [ P.fun _crResult ? P.match _PactResultOk ? P.equals (PInteger 1) @@ -810,127 +817,127 @@ localTests baseRdb = let ]) ] ] - buildTextCmd v (defaultCmd cid) - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + buildTextCmd (defaultCmd cid) + >>= local fx cid (Just PreflightSimulation) Nothing Nothing >>= P.match _LocalResultWithWarns ? P.fun fst ? expectation - buildTextCmd v (defaultCmd cid) - >>= local fx v cid (Just PreflightSimulation) (Just NoVerify) Nothing + buildTextCmd (defaultCmd cid) + >>= local fx cid (Just PreflightSimulation) (Just NoVerify) Nothing >>= P.match _LocalResultWithWarns ? P.fun fst ? expectation - , testCase "signature with the wrong key" $ runResourceT $ do - fx <- mkFixture v baseRdb + , testCase "signature with the wrong key" $ withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do let buildSender00Cmd = defaultCmd cid & cbSigners .~ [mkEd25519Signer' sender00 []] - goodCmdHash <- _cmdHash <$> buildTextCmd v buildSender00Cmd + goodCmdHash <- _cmdHash <$> buildTextCmd buildSender00Cmd sender01KeyPair <- either error return $ importEd25519KeyPair Nothing (PrivBS $ either error id $ B16.decode $ T.encodeUtf8 $ snd sender01) let sender01Sig = ED25519Sig $ T.decodeUtf8 $ B16.encode $ exportEd25519Signature $ signEd25519 (fst sender01KeyPair) (snd sender01KeyPair) goodCmdHash - buildTextCmd v buildSender00Cmd + buildTextCmd buildSender00Cmd <&> set cmdSigs [sender01Sig] -- preflight mode, verify signatures - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + >>= local fx cid (Just PreflightSimulation) Nothing Nothing & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"The signature at position 0 is invalid: invalid ed25519 signature.\"]" ] - buildTextCmd v buildSender00Cmd + buildTextCmd buildSender00Cmd <&> set cmdSigs [sender01Sig] -- preflight mode, do not verify signatures - >>= local fx v cid (Just PreflightSimulation) (Just NoVerify) Nothing + >>= local fx cid (Just PreflightSimulation) (Just NoVerify) Nothing >>= P.succeed - buildTextCmd v buildSender00Cmd + buildTextCmd buildSender00Cmd <&> set cmdSigs [sender01Sig] -- non-preflight mode, verify signatures - >>= local fx v cid Nothing Nothing Nothing + >>= local fx cid Nothing Nothing Nothing & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"The signature at position 0 is invalid: invalid ed25519 signature.\"]" ] - buildTextCmd v buildSender00Cmd + buildTextCmd buildSender00Cmd <&> set cmdSigs [sender01Sig] -- non-preflight mode, do not verify signatures - >>= local fx v cid Nothing (Just NoVerify) Nothing + >>= local fx cid Nothing (Just NoVerify) Nothing >>= P.match _LocalResultLegacy ? P.succeed - , testCase "invalid tx metadata" $ runResourceT $ do - fx <- mkFixture v baseRdb + , testCase "invalid tx metadata" $ withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do let wrongChain = unsafeChainId maxBound - buildTextCmd v (defaultCmd wrongChain) - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + buildTextCmd (defaultCmd wrongChain) + >>= local fx cid (Just PreflightSimulation) Nothing Nothing & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Chain id mismatch\"]" ] -- /local without preflight does not care about incorrect chains - buildTextCmd v (defaultCmd wrongChain) - >>= local fx v cid Nothing Nothing Nothing + buildTextCmd (defaultCmd wrongChain) + >>= local fx cid Nothing Nothing Nothing >>= P.succeed let invalidChain = "not a real chain" - buildTextCmd v (defaultCmd cid & set cbChainId invalidChain) - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + buildTextCmd (defaultCmd cid & set cbChainId invalidChain) + >>= local fx cid (Just PreflightSimulation) Nothing Nothing & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Chain id mismatch\"]" ] - buildTextCmd v (defaultCmd cid & set cbChainId invalidChain) - >>= local fx v cid Nothing Nothing Nothing + buildTextCmd (defaultCmd cid & set cbChainId invalidChain) + >>= local fx cid Nothing Nothing Nothing >>= P.succeed - buildTextCmd v (set cbGasLimit (GasLimit $ Gas 100000000000000) $ defaultCmd cid) - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + buildTextCmd (set cbGasLimit (GasLimit $ Gas 100000000000000) $ defaultCmd cid) + >>= local fx cid (Just PreflightSimulation) Nothing Nothing & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Transaction Gas limit exceeds block gas limit\"]" ] - buildTextCmd v (set cbGasPrice (GasPrice 0.00000000000000001) $ defaultCmd cid) - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + buildTextCmd (set cbGasPrice (GasPrice 0.00000000000000001) $ defaultCmd cid) + >>= local fx cid (Just PreflightSimulation) Nothing Nothing & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Gas price decimal precision too high\"]" ] - buildTextCmd mainnet (defaultCmd cid) - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + buildCmdMainnet + >>= local fx cid (Just PreflightSimulation) Nothing Nothing & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Network id mismatch\"]" ] let sigs' = replicate 101 $ mkEd25519Signer' sender00 [] - buildTextCmd v (defaultCmd cid & set cbSigners sigs') - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + buildTextCmd (defaultCmd cid & set cbSigners sigs') + >>= local fx cid (Just PreflightSimulation) Nothing Nothing & P.throws ? P.match _FailureResponse ? P.checkAll [ P.fun responseStatusCode ? P.equals badRequest400 , P.fun responseBody ? P.equals "Metadata validation failed: [\"Signature list size too big\"]" ] - buildTextCmd v + buildTextCmd (defaultCmd cid & set cbGasPrice (GasPrice 10_000_000_000) & set cbGasLimit (GasLimit (Gas 10_000))) - >>= local fx v cid (Just PreflightSimulation) Nothing Nothing + >>= local fx cid (Just PreflightSimulation) Nothing Nothing >>= P.match (_LocalResultWithWarns . _1) ? P.fun _crResult -- TODO: a more detailed check here than "is an error" might be nice ? P.match _PactResultErr P.succeed - , withResourceT (mkFixture v baseRdb) $ \fx -> testCase "local with depth" $ do - startBalance <- buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) - >>= local fx v cid Nothing Nothing (Just (RewindDepth 0)) + , withVersion v $ withResourceT (mkFixture baseRdb) $ \fx -> testCase "local with depth" $ do + startBalance <- buildTextCmd (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) + >>= local fx cid Nothing Nothing (Just (RewindDepth 0)) <&> unsafeHeadOf ? _LocalResultLegacy . to _crResult @@ -947,7 +954,7 @@ localTests baseRdb = let hasBlockHeight p = P.fun _crMetaData ? P.match (_Just . A._Object . at "blockHeight" . _Just . A._Number) p - transfer <- buildTextCmd v $ set cbSigners + transfer <- buildTextCmd $ set cbSigners [ mkEd25519Signer' sender00 [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] , CapToken (QualifiedName "TRANSFER" (ModuleName "coin" Nothing)) @@ -956,78 +963,80 @@ localTests baseRdb = let ] $ set cbRPC (mkExec' "(coin.transfer \"sender00\" \"sender01\" 100.0)") $ defaultCmd cid - send fx v cid [transfer] + send fx cid [transfer] advanceAllChains_ fx - buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) - >>= local fx v cid Nothing Nothing Nothing + buildTextCmd (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) + >>= local fx cid Nothing Nothing Nothing >>= P.match _LocalResultLegacy ? P.checkAll [ hasBalance ? P.lt startBalance , hasBlockHeight (P.equals 3) ] - buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) - >>= local fx v cid Nothing Nothing (Just (RewindDepth 0)) + buildTextCmd (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) + >>= local fx cid Nothing Nothing (Just (RewindDepth 0)) >>= P.match _LocalResultLegacy ? P.checkAll [ hasBalance ? P.lt startBalance , hasBlockHeight (P.equals 3) ] - buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) - >>= local fx v cid Nothing Nothing (Just (RewindDepth 1)) + buildTextCmd (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) + >>= local fx cid Nothing Nothing (Just (RewindDepth 1)) >>= P.match _LocalResultLegacy ? P.checkAll [ hasBalance ? P.equals startBalance , hasBlockHeight (P.equals 2) ] - buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) - >>= local fx v cid Nothing Nothing (Just (RewindDepth 2)) + buildTextCmd (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) + >>= local fx cid Nothing Nothing (Just (RewindDepth 2)) >>= P.match _LocalResultLegacy ? P.checkAll [ hasBalance ? P.equals startBalance , hasBlockHeight (P.equals 1) ] - buildTextCmd v (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) - >>= local fx v cid Nothing Nothing (Just (RewindDepth 3)) + buildTextCmd (defaultCmd cid & set cbRPC (mkExec' "(coin.details 'sender00)")) + >>= local fx cid Nothing Nothing (Just (RewindDepth 3)) & P.throws ? P.match _FailureResponse ? P.fun responseBody ? textContains "No block exists at the given rewind depth" - , withResourceT (mkFixture v baseRdb) $ \fx -> testCase "local continuation" $ do + , withVersion v $ withResourceT (mkFixture baseRdb) $ \fx -> testCase "local continuation" $ do let code = "(namespace 'free)(module m G (defcap G () true) (defpact p () (step (yield { \"a\" : (+ 1 1) })) (step (resume { \"a\" := a } a))))(free.m.p)" - initiator <- buildTextCmd v + initiator <- buildTextCmd $ set cbGasLimit (GasLimit (Gas 70_000)) $ set cbRPC (mkExec' code) $ defaultCmd cid - send fx v cid [initiator] + send fx cid [initiator] advanceAllChains_ fx - Just defPactId <- poll fx v cid [cmdToRequestKey initiator] + Just defPactId <- poll fx cid [cmdToRequestKey initiator] <&> preview (ix 0 . _Just . crContinuation . _Just . peDefPactId) - continuer <- buildTextCmd v + continuer <- buildTextCmd $ set cbRPC (mkCont (mkContMsg defPactId 1)) $ defaultCmd cid - local fx v cid Nothing Nothing Nothing continuer + local fx cid Nothing Nothing Nothing continuer >>= P.match _LocalResultLegacy ? P.fun _crResult ? P.match _PactResultOk ? P.equals (PInteger 2) ] + where + v = instantCpmTestVersion petersenChainGraph + cid = unsafeChainId 0 + buildCmdMainnet = withVersion mainnet $ buildTextCmd (defaultCmd cid) pollingMetadataTest :: RocksDb -> Step -> IO () -pollingMetadataTest baseRdb _step = runResourceT $ do - let v = instantCpmTestVersion singletonChainGraph - let cid = unsafeChainId 0 - fx <- mkFixture v baseRdb +pollingMetadataTest baseRdb _step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do - cmd <- buildTextCmd v (defaultCmd cid) - send fx v cid [cmd] + cmd <- buildTextCmd (defaultCmd cid) + send fx cid [cmd] (_, commandResults) <- advanceAllChains fx -- there is no metadata in the actual block outputs commandResults @@ -1037,7 +1046,7 @@ pollingMetadataTest baseRdb _step = runResourceT $ do -- the metadata reported by poll has a different shape from that -- reported by /local - poll fx v cid [cmdToRequestKey cmd] >>= + poll fx cid [cmdToRequestKey cmd] >>= P.alignExact [ P.match _Just ? P.fun _crMetaData ? P.match _Just ? P.match A._Object ? P.alignExact ? A.KeyMap.fromList [ ("blockHash", P.match (A._String . b64UrlNoPaddingPrism) P.succeed) @@ -1045,17 +1054,18 @@ pollingMetadataTest baseRdb _step = runResourceT $ do , ("blockPayloadHash", P.match (A._String . b64UrlNoPaddingPrism) P.succeed) ] ] + where + v = instantCpmTestVersion singletonChainGraph + cid = unsafeChainId 0 upgradeNamespaceTests :: RocksDb -> Step -> IO () -upgradeNamespaceTests baseRdb _step = runResourceT $ do - let v = instantCpmTestVersion singletonChainGraph - let cid = unsafeChainId 0 - fx <- mkFixture v baseRdb +upgradeNamespaceTests baseRdb _step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do upgradeNsContract <- T.readFile "pact/namespaces/ns.pact" do - unprivilegedUpgradeCmd <- buildTextCmd v $ + unprivilegedUpgradeCmd <- buildTextCmd $ set cbRPC (mkExec' upgradeNsContract) $ set cbSigners [mkEd25519Signer' sender00 @@ -1063,9 +1073,9 @@ upgradeNamespaceTests baseRdb _step = runResourceT $ do [CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) []] ] $ defaultCmd cid - send fx v cid [unprivilegedUpgradeCmd] + send fx cid [unprivilegedUpgradeCmd] advanceAllChains_ fx - poll fx v cid [cmdToRequestKey unprivilegedUpgradeCmd] + poll fx cid [cmdToRequestKey unprivilegedUpgradeCmd] >>= P.match (_head . _Just) ? P.fun _crResult ? P.match _PactResultErr @@ -1073,22 +1083,24 @@ upgradeNamespaceTests baseRdb _step = runResourceT $ do ? P.fun _boundedText ? textContains "Keyset failure" do - privilegedUpgradeCmd <- buildTextCmd v $ + privilegedUpgradeCmd <- buildTextCmd $ set cbRPC (mkExec' upgradeNsContract) $ set cbSigners -- sender00 controls ns, and module upgrades require unscoped signatures [mkEd25519Signer' sender00 [] ] $ defaultCmd cid - send fx v cid [privilegedUpgradeCmd] + send fx cid [privilegedUpgradeCmd] advanceAllChains_ fx - poll fx v cid [cmdToRequestKey privilegedUpgradeCmd] + poll fx cid [cmdToRequestKey privilegedUpgradeCmd] >>= P.match (_head . _Just) ? P.fun _crResult ? P.match _PactResultOk ? P.match _PString ? textContains "Loaded module ns" - + where + v = instantCpmTestVersion singletonChainGraph + cid = unsafeChainId 0 -- ---------------------------------------------------- @@ -1113,9 +1125,9 @@ instantCpmTestVersionGenesis chain | chain == unsafeChainId 0 = IN0.payloadBlock | otherwise = INN.payloadBlock -mkFixture :: ChainwebVersion -> RocksDb -> ResourceT IO Fixture -mkFixture v baseRdb = do - fx <- CutFixture.mkFixture v instantCpmTestVersionGenesis defaultPactServiceConfig { _pactBlockRefreshInterval = 10_000 } baseRdb +mkFixture :: HasVersion => RocksDb -> ResourceT IO Fixture +mkFixture baseRdb = do + fx <- CutFixture.mkFixture instantCpmTestVersionGenesis defaultPactServiceConfig { _pactBlockRefreshInterval = 10_000 } baseRdb logger <- liftIO getTestLogger let mkSomePactServerData cid = PactServerData @@ -1123,8 +1135,8 @@ mkFixture v baseRdb = do , _pactServerDataLogger = logger , _pactServerDataPact = fx ^?! CutFixture.fixturePacts . atChain cid } - let pactServer = somePactServers v $ List.map (\cid -> (cid, mkSomePactServerData cid)) (HashSet.toList (chainIds v)) - let cutGetServer = someCutGetServer v (fx ^. CutFixture.fixtureCutDb) + let pactServer = somePactServers $ List.map (\cid -> (cid, mkSomePactServerData cid)) (HashSet.toList chainIds) + let cutGetServer = someCutGetServer (fx ^. CutFixture.fixtureCutDb) let app = someServerApplication (pactServer <> cutGetServer) -- Run pact server API @@ -1153,25 +1165,25 @@ newtype PollException = PollException String poll :: HasFixture fx + => HasVersion => fx - -> ChainwebVersion -> ChainId -> [RequestKey] -> IO [Maybe TestPact5CommandResult] -poll fx v cid rks = pollWithDepth fx v cid rks Nothing +poll fx cid rks = pollWithDepth fx cid rks Nothing pollWithDepth :: HasFixture fx + => HasVersion => fx - -> ChainwebVersion -> ChainId -> [RequestKey] -> Maybe ConfirmationDepth -> IO [Maybe TestPact5CommandResult] -pollWithDepth fx v cid rks mConfirmationDepth = do +pollWithDepth fx cid rks mConfirmationDepth = do clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx let rksNel = NE.fromList rks - pollResult <- runClientM (pactPollApiClient v cid mConfirmationDepth (PollRequest rksNel)) clientEnv + pollResult <- runClientM (pactPollApiClient cid mConfirmationDepth (PollRequest rksNel)) clientEnv case pollResult of Left e -> do throwM (PollException (show e)) @@ -1197,17 +1209,16 @@ _FailureResponse = folding $ \case ClientException _ (FailureResponse _req resp) -> Just (TL.toStrict . TL.decodeUtf8 <$> resp) _ -> Nothing -send :: (HasCallStack, HasFixture fx) +send :: (HasVersion, HasCallStack, HasFixture fx) => fx - -> ChainwebVersion -> ChainId -> [Command Text] -> IO () -send fx v cid cmds = do +send fx cid cmds = do let commands = NE.fromList $ toListOf each cmds let batch = SendRequest (SubmitBatch commands) clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx - sendResult <- runClientM (pactSendApiClient v cid batch) clientEnv + sendResult <- runClientM (pactSendApiClient cid batch) clientEnv case sendResult of Left e -> do throwM (ClientException callStack e) @@ -1216,36 +1227,34 @@ send fx v cid cmds = do -- of the commands response & P.equals (cmdToRequestKey <$> commands) -local :: (HasCallStack, HasFixture fx) +local :: (HasVersion, HasCallStack, HasFixture fx) => fx - -> ChainwebVersion -> ChainId -> Maybe LocalPreflightSimulation -> Maybe LocalSignatureVerification -> Maybe RewindDepth -> Command Text -> IO LocalResult -local fx v cid preflight sigVerify depth cmd = do +local fx cid preflight sigVerify depth cmd = do -- send a single local request and return the result -- clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx r <- runClientM - (pactLocalApiClient v cid preflight sigVerify depth cmd) + (pactLocalApiClient cid preflight sigVerify depth cmd) clientEnv either (throwM . ClientException callStack) return r -spvTxOutProof :: (HasCallStack, HasFixture fx) +spvTxOutProof :: (HasVersion, HasCallStack, HasFixture fx) => fx - -> ChainwebVersion -> ChainId -> ChainId -> RequestKey -> IO TransactionOutputProofB64 -spvTxOutProof fx v trgChain srcChain reqKey = do +spvTxOutProof fx trgChain srcChain reqKey = do clientEnv <- _serviceClientEnv <$> remotePactTestFixture fx let pactTrgChain = Pact.ChainId $ toText trgChain r <- runClientM - (pactSpvApiClient v srcChain (SpvRequest reqKey pactTrgChain)) + (pactSpvApiClient srcChain (SpvRequest reqKey pactTrgChain)) clientEnv either (throwM . ClientException callStack) return r diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index d87a7bc7a0..08017cb3e3 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -17,7 +17,7 @@ module Chainweb.Test.Pact.TransactionExecTest (tests) where import Chainweb.BlockHeader -import Chainweb.Graph (petersonChainGraph) +import Chainweb.Graph (petersenChainGraph) import Chainweb.Miner.Pact (Miner(..), MinerId(..), MinerGuard(..), noMiner) import Chainweb.Pact.PactService (initialPayloadState, withPactService) import Chainweb.Pact.PactService.Checkpointer (readFrom, mkFakeParentCreationTime) @@ -71,7 +71,6 @@ import Chainweb.Logger import Chainweb.Pact.Backend.InMemDb qualified as InMemDb import Chainweb.Pact.Backend.Types import Control.Monad.State.Strict -import qualified Data.Pool as Pool import Chainweb.Parent import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as PIN0 @@ -104,28 +103,28 @@ tests baseRdb = testGroup "Pact5 TransactionExecTest" -- focused on the transaction level. Tests that need to be aware of blocks, for -- example to observe database writes, belong in a different suite, like -- PactServiceTest or RemotePactTest. -readFromAfterGenesis :: ChainwebVersion -> RocksDb -> (BlockEnv -> BlockHandle -> IO a) -> IO a -readFromAfterGenesis ver rdb act = runResourceT $ do +readFromAfterGenesis :: HasVersion => RocksDb -> (BlockEnv -> BlockHandle -> IO a) -> IO a +readFromAfterGenesis rdb act = runResourceT $ do (writeSql, readPool) <- withTempChainSqlite cid - tdb <- mkTestBlockDb ver rdb + tdb <- mkTestBlockDb rdb -- fake ro-sql pool, assuming we're using this single-threaded logger <- liftIO $ testLogger - serviceEnv <- withPactService ver cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) readPool writeSql defaultPactServiceConfig (GenesisPayload PIN0.payloadBlock) + serviceEnv <- withPactService cid Nothing mempty logger Nothing (_bdbPayloadDb tdb) readPool writeSql defaultPactServiceConfig (GenesisPayload PIN0.payloadBlock) liftIO $ do initialPayloadState logger serviceEnv fakeParentCreationTime <- mkFakeParentCreationTime throwIfNoHistory =<< - readFrom logger ver cid writeSql fakeParentCreationTime - (Parent (gh ver cid ^. rankedBlockHash)) + readFrom logger cid writeSql fakeParentCreationTime + (Parent (gh cid ^. rankedBlockHash)) act buyGasShouldTakeGasTokensFromTheTransactionSender :: RocksDb -> IO () -buyGasShouldTakeGasTokensFromTheTransactionSender rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> +buyGasShouldTakeGasTokensFromTheTransactionSender rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" assertEqual "starting balance" (Just 100_000_000) startSender00Bal - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbGasPrice = GasPrice 2 , _cbGasLimit = GasLimit (Gas 200) } @@ -138,7 +137,7 @@ buyGasShouldTakeGasTokensFromTheTransactionSender rdb = readFromAfterGenesis v r assertEqual "balance after buying gas" (Just $ 100_000_000 - 200 * 2) endSender00Bal buyGasFailures :: RocksDb -> IO () -buyGasFailures rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +buyGasFailures rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" assertEqual "starting balance" (Just 100_000_000) startSender00Bal @@ -146,7 +145,7 @@ buyGasFailures rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do -- buying gas with insufficient balance to pay for the full supply -- (gas price * gas limit) should return an error do - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbGasPrice = GasPrice 70_000 , _cbGasLimit = GasLimit (Gas 100_000) } @@ -160,7 +159,7 @@ buyGasFailures rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do -- multiple gas payer caps should lead to an error, because it's unclear -- which module will pay for gas do - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbSigners = [ mkEd25519Signer' sender00 [ CapToken (QualifiedName "GAS" (ModuleName "coin" Nothing)) [] @@ -176,13 +175,13 @@ buyGasFailures rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner :: RocksDb -> IO () redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner rdb = - readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do + withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" assertEqual "starting balance" (Just 100_000_000) startSender00Bal startMinerBal <- readBal pactDb "NoMiner" - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbGasPrice = GasPrice 2 , _cbGasLimit = GasLimit (Gas 10) } @@ -199,7 +198,7 @@ redeemGasShouldGiveGasTokensToTheTransactionSenderAndMiner rdb = assertEqual "miner balance after redeeming gas" (Just $ fromMaybe 0 startMinerBal + 3 * 2) endMinerBal redeemGasFailure :: RocksDb -> IO () -redeemGasFailure rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +redeemGasFailure rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do let miner = Miner (MinerId "sender00") $ MinerGuard @@ -208,7 +207,7 @@ redeemGasFailure rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do (Set.singleton (Pact.PublicKeyText $ fst sender00)) Pact.KeysAll - cmd <- buildCwCmd v + cmd <- buildCwCmd $ set cbRPC (mkExec ("(coin.rotate \"sender00\" (read-keyset 'ks))") (mkKeySetData "ks" [sender01])) $ set cbSigners [ mkEd25519Signer' sender00 @@ -225,9 +224,9 @@ redeemGasFailure rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do ? P.equals (UserEnforceError "account guards do not match") purchaseGasTxTooBig :: RocksDb -> IO () -purchaseGasTxTooBig rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +purchaseGasTxTooBig rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do - cmd <- buildCwCmd v + cmd <- buildCwCmd $ set cbSender "sender00" $ set cbSigners [mkEd25519Signer' sender00 []] $ set cbGasLimit (GasLimit (Gas 1)) -- We set the gas limit to lower than the initialGas passed to applyCmd so that this test fails @@ -239,13 +238,13 @@ purchaseGasTxTooBig rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> ? P.succeed payloadFailureShouldPayAllGasToTheMinerTypeError :: RocksDb -> IO () -payloadFailureShouldPayAllGasToTheMinerTypeError rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +payloadFailureShouldPayAllGasToTheMinerTypeError rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" assertEqual "starting balance" (Just 100_000_000) startSender00Bal startMinerBal <- readBal pactDb "NoMiner" - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(+ 1 \"hello\")" , _cbGasPrice = GasPrice 2 , _cbGasLimit = GasLimit (Gas 1000) @@ -283,13 +282,13 @@ payloadFailureShouldPayAllGasToTheMinerTypeError rdb = readFromAfterGenesis v rd assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal payloadFailureShouldPayAllGasToTheMinerInsufficientFunds :: RocksDb -> IO () -payloadFailureShouldPayAllGasToTheMinerInsufficientFunds rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> +payloadFailureShouldPayAllGasToTheMinerInsufficientFunds rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" assertEqual "starting balance" (Just 100_000_000) startSender00Bal startMinerBal <- readBal pactDb "NoMiner" - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' $ fromString $ "(coin.transfer \"sender00\" \"sender01\" " <> printf "%.f" (realToFrac @_ @Double $ fromMaybe 0 startSender00Bal + 1) @@ -337,9 +336,9 @@ payloadFailureShouldPayAllGasToTheMinerInsufficientFunds rdb = readFromAfterGene assertEqual "miner balance after payload failure" (Just $ fromMaybe 0 startMinerBal + gasToMiner) endMinerBal runPayloadShouldReturnEvalResultRelatedToTheInputCommand :: RocksDb -> IO () -runPayloadShouldReturnEvalResultRelatedToTheInputCommand rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +runPayloadShouldReturnEvalResultRelatedToTheInputCommand rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" } gasEnv <- mkTableGasEnv (MilliGasLimit (gasToMilliGas $ Gas 10)) GasLogsEnabled @@ -366,13 +365,13 @@ runPayloadShouldReturnEvalResultRelatedToTheInputCommand rdb = readFromAfterGene -- applyLocal should mostly be the same as applyCmd, this is mostly a smoke test applyLocalSpec :: RocksDb -> IO () -applyLocalSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +applyLocalSpec rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" assertEqual "starting balance" (Just 100_000_000) startSender00Bal startMinerBal <- readBal pactDb "NoMiner" - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" , _cbSigners = [] } @@ -396,14 +395,14 @@ applyLocalSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do assertEqual "miner balance after redeeming gas should have increased" startMinerBal endMinerBal applyCmdSpec :: RocksDb -> IO () -applyCmdSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +applyCmdSpec rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do bh <- flip execStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" let expectedStartingBal = 100_000_000 assertEqual "starting balance" (Just expectedStartingBal) startSender00Bal startMinerBal <- readBal pactDb "NoMiner" - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(fold + 0 [1 2 3 4 5])" , _cbGasPrice = GasPrice 2 , _cbGasLimit = GasLimit (Gas 500) @@ -477,9 +476,9 @@ applyCmdSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do ] quirkSpec :: RocksDb -> IO () -quirkSpec rdb = readFromAfterGenesis quirkVer rdb $ \blockEnv blockHandle -> +quirkSpec rdb = withVersion quirkVer $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(* 1000 200000)" , _cbSigners = [mkEd25519Signer' sender00 []] , _cbSender = "sender00" @@ -503,14 +502,14 @@ quirkSpec rdb = readFromAfterGenesis quirkVer rdb $ \blockEnv blockHandle -> , P.fun _crGas ? P.equals ? Gas 1 ] where - quirkVer = quirkedGasPact5InstantCpmTestVersion peterson + quirkVer = quirkedGasPact5InstantCpmTestVersion petersen applyCmdVerifierSpec :: RocksDb -> IO () -applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> +applyCmdVerifierSpec rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do -- Define module with capability do - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' $ T.unlines [ "(namespace 'free)" , "(module m G" @@ -546,7 +545,7 @@ applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> -- Invoke module when verifier capability isn't present. Should fail. do - cmd <- buildCwCmd v baseCmd + cmd <- buildCwCmd baseCmd logger <- testLogger applyCmd logger Nothing pactDb noMiner (_psBlockCtx blockEnv) (TxBlockIdx 0) noSPVSupport (Gas 1) (view payloadObj <$> cmd) >>= P.match _Right @@ -571,7 +570,7 @@ applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do let cap :: SigCapability cap = SigCapability $ CapToken (QualifiedName "G" (ModuleName "m" (Just (NamespaceName "free")))) [] - cmd <- buildCwCmd v baseCmd + cmd <- buildCwCmd baseCmd { _cbVerifiers = [ Verifier { _verifierName = VerifierName "allow" @@ -599,13 +598,13 @@ applyCmdVerifierSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> ] applyCmdFailureSpec :: RocksDb -> IO () -applyCmdFailureSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> +applyCmdFailureSpec rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" assertEqual "starting balance" (Just 100_000_000) startSender00Bal startMinerBal <- readBal pactDb "NoMiner" - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(+ 1 \"abc\")" , _cbGasPrice = GasPrice 2 , _cbGasLimit = GasLimit (Gas 500) @@ -650,13 +649,13 @@ applyCmdFailureSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> endMinerBal applyCmdCoinTransfer :: RocksDb -> IO () -applyCmdCoinTransfer rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +applyCmdCoinTransfer rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startSender00Bal <- readBal pactDb "sender00" assertEqual "starting balance" (Just 100_000_000) startSender00Bal startMinerBal <- readBal pactDb "NoMiner" - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0)" , _cbSigners = [ mkEd25519Signer' sender00 @@ -723,7 +722,7 @@ applyCmdCoinTransfer rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> endMinerBal applyCoinbaseSpec :: RocksDb -> IO () -applyCoinbaseSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> +applyCoinbaseSpec rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do startMinerBal <- readBal pactDb "NoMiner" @@ -754,9 +753,9 @@ applyCoinbaseSpec rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> endMinerBal testEvents :: RocksDb -> IO () -testEvents rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +testEvents rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(coin.transfer 'sender00 'sender01 420.0) (coin.transfer 'sender00 'sender01 69.0)" , _cbSigners = [ mkEd25519Signer' sender00 @@ -801,10 +800,10 @@ testEvents rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do ] testLocalOnlyFailsOutsideOfLocal :: RocksDb -> IO () -testLocalOnlyFailsOutsideOfLocal rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +testLocalOnlyFailsOutsideOfLocal rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do let testLocalOnly txt = do - cmd <- buildCwCmd v (defaultCmd cid) + cmd <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' txt } @@ -822,10 +821,10 @@ testLocalOnlyFailsOutsideOfLocal rdb = readFromAfterGenesis v rdb $ \blockEnv bl testLocalOnly "(describe-module \"coin\")" testWritesFromFailedTxDontMakeItIn :: RocksDb -> IO () -testWritesFromFailedTxDontMakeItIn rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +testWritesFromFailedTxDontMakeItIn rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do bh <- flip execStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do - moduleDeploy <- buildCwCmd v (defaultCmd cid) + moduleDeploy <- buildCwCmd (defaultCmd cid) { _cbRPC = mkExec' "(module m g (defcap g () (enforce false \"non-upgradeable\"))) (enforce false \"boom\")" , _cbGasLimit = GasLimit (Gas 200_000) , _cbSigners = [mkEd25519Signer' sender00 []] @@ -855,9 +854,9 @@ testWritesFromFailedTxDontMakeItIn rdb = readFromAfterGenesis v rdb $ \blockEnv ] testWritesToNonExistentTables :: RocksDb -> IO () -testWritesToNonExistentTables rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +testWritesToNonExistentTables rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do - cmd <- buildCwCmd v + cmd <- buildCwCmd $ set cbRPC (mkExec' $ T.concat [ "(namespace 'free)" , "(module m G" @@ -878,9 +877,9 @@ testWritesToNonExistentTables rdb = readFromAfterGenesis v rdb $ \blockEnv block ? P.equals (DbOpFailure (NoSuchTable (TableName "t" (ModuleName "m" (Just "free"))))) testKeccak256 :: RocksDb -> IO () -testKeccak256 rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do +testKeccak256 rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> do flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \pactDb _spv -> do - cmd <- buildCwCmd v + cmd <- buildCwCmd $ set cbRPC (mkExec' "(hash-keccak256 [\"T73FllCNJKKgAQ4UCYC4CfucbVXsdRJYkd2YXTdmW9gPm-tqUCB1iKvzzu6Md82KWtSKngqgdO04hzg2JJbS-yyHVDuzNJ6mSZfOPntCTqktEi9X27CFWoAwWEN_4Ir7DItecXm5BEu_TYGnFjsxOeMIiLU2sPlX7_macWL0ylqnVqSpgt-tvzHvJVCDxLXGwbmaEH19Ov_9uJFHwsxMmiZD9Hjl4tOTrqN7THy0tel9rc8WtrUKrg87VJ7OR3Rtts5vZ91EBs1OdVldUQPRP536eTcpJNMo-N0fy-taji6L9Mdt4I4_xGqgIfmJxJMpx6ysWmiFVte8vLKl1L5p0yhOnEDsSDjuhZISDOIKC2NeytqoT9VpBQn1T3fjWkF8WEZIvJg5uXTge_qwA46QKV0LE5AlMKgw0cK91T8fnJ-u1Dyk7tCo3XYbx-292iiih8YM1Cr1-cdY5cclAjHAmlglY2ia_GXit5p6K2ggBmd1LpEBdG8DGE4jmeTtiDXLjprpDilq8iCuI0JZ_gvQvMYPekpf8_cMXtTenIxRmhDpYvZzyCxek1F4aoo7_VcAMYV71Mh_T8ox7U1Q4U8hB9oCy1BYcAt06iQai0HXhGFljxsrkL_YSkwsnWVDhhqzxWRRdX3PubpgMzSI290C1gG0Gq4xfKdHTrbm3Q\"])") $ defaultCmd cid @@ -895,14 +894,14 @@ testKeccak256 rdb = readFromAfterGenesis v rdb $ \blockEnv blockHandle -> do cid :: ChainId cid = unsafeChainId 0 -gh :: ChainwebVersion -> ChainId -> BlockHeader +gh :: HasVersion => ChainId -> BlockHeader gh = genesisBlockHeader -- vUpgrades :: ChainwebVersion -- vUpgrades = slowCpmTestVersion singletonChainGraph v :: ChainwebVersion -v = instantCpmTestVersion petersonChainGraph +v = instantCpmTestVersion petersenChainGraph -- | this utility for reading balances from the pactdb also takes care of -- making a transaction for the read to live in diff --git a/test/unit/Chainweb/Test/RestAPI.hs b/test/unit/Chainweb/Test/RestAPI.hs index 88aea9b6bb..4e9cc2934d 100644 --- a/test/unit/Chainweb/Test/RestAPI.hs +++ b/test/unit/Chainweb/Test/RestAPI.hs @@ -5,6 +5,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# options_ghc -fno-warn-unused-local-binds -fno-warn-unused-imports #-} +{-# LANGUAGE RankNTypes #-} -- | -- Module: Chainweb.Test.RestAPI @@ -71,29 +72,30 @@ import Chainweb.Version import Chainweb.Storage.Table.RocksDB import Servant.Client_ +import Chainweb.Parent -- -------------------------------------------------------------------------- -- -- BlockHeaderDb queries -- TODO remove these? -hashes :: MonadIO m => BlockHeaderDb -> m [DbKey BlockHeaderDb] +hashes :: HasVersion => MonadIO m => BlockHeaderDb -> m [DbKey BlockHeaderDb] hashes db = liftIO $ SP.toList_ & keys db Nothing Nothing Nothing Nothing -headers :: MonadIO m => BlockHeaderDb -> m [DbEntry BlockHeaderDb] +headers :: HasVersion => MonadIO m => BlockHeaderDb -> m [DbEntry BlockHeaderDb] headers db = liftIO $ SP.toList_ & entries db Nothing Nothing Nothing Nothing -- -------------------------------------------------------------------------- -- -- BlockHeaderDb Utils -genesisBh :: MonadIO m => BlockHeaderDb -> m BlockHeader +genesisBh :: HasVersion => MonadIO m => BlockHeaderDb -> m BlockHeader genesisBh db = head <$> headers db -missingKey :: MonadIO m => BlockHeaderDb -> m (DbKey BlockHeaderDb) +missingKey :: HasVersion => MonadIO m => BlockHeaderDb -> m (DbKey BlockHeaderDb) missingKey db = key . head . testBlockHeadersWithNonce (Nonce 34523) - . ParentHeader + . Parent <$> genesisBh db -- -------------------------------------------------------------------------- -- @@ -116,7 +118,7 @@ tests rdb = testGroup "REST API tests" tests_ :: RocksDb -> Bool -> [TestTree] tests_ rdb tls = [ simpleSessionTests rdb tls - , pagingTests rdb tls + -- , pagingTests rdb tls ] version :: ChainwebVersion @@ -127,44 +129,44 @@ version = barebonesTestVersion singletonChainGraph -- | The type of 'TestClientEnv' that is used everywhere in this file -- -type TestClientEnv_ = TestClientEnv MockTx RocksDbTable +type TestClientEnv_ = TestClientEnv MockTx -mkEnv :: RocksDb -> Bool -> [(ChainId, BlockHeaderDb)] -> ResourceT IO TestClientEnv_ +mkEnv :: HasVersion => RocksDb -> Bool -> ChainMap BlockHeaderDb -> ResourceT IO TestClientEnv_ mkEnv rdb tls dbs = do let pdb = newPayloadDb rdb - liftIO $ initializePayloadDb version pdb - clientEnvWithChainwebTestServer ValidateSpec tls version emptyChainwebServerDbs + clientEnvWithChainwebTestServer ValidateSpec tls emptyChainwebServerDbs { _chainwebServerBlockHeaderDbs = dbs - , _chainwebServerPayloadDbs = [ (cid, pdb) | (cid, _) <- dbs ] + , _chainwebServerPayloads = undefined -- onAllChains pdb } simpleSessionTests :: RocksDb -> Bool -> TestTree -simpleSessionTests rdb tls = - withResource' (testBlockHeaderDbs rdb version) $ \dbsIO -> +simpleSessionTests rdb tls = withVersion version $ + withResource' (testBlockHeaderDbs rdb) $ \dbsIO -> withResourceT (mkEnv rdb tls =<< liftIO dbsIO) $ \env -> testGroup "client session tests" - $ httpHeaderTests env (head $ toList $ chainIds version) - : (simpleClientSession env <$> toList (chainIds version)) + $ [httpHeaderTests env (head $ toList chainIds)] + -- : (simpleClientSession env <$> toList chainIds) httpHeaderTests :: IO TestClientEnv_ -> ChainId -> TestTree httpHeaderTests envIO cid = testGroup ("http header tests for chain " <> sshow cid) - [ testCase "headerClient" $ go $ \v h -> headerClient' v cid (key h) - , testCase "headersClient" $ go $ \v _ -> headersClient' v cid Nothing Nothing Nothing Nothing - , testCase "blocksClient" $ go $ \v _ -> blocksClient' v cid Nothing Nothing Nothing Nothing - , testCase "hashesClient" $ go $ \v _ -> hashesClient' v cid Nothing Nothing Nothing Nothing - , testCase "branchHashesClient" $ go $ \v _ -> branchHashesClient' v cid Nothing Nothing Nothing + [ testCase "headerClient" $ go $ \h -> headerClient' cid (key h) + , testCase "headersClient" $ go $ \_ -> headersClient' cid Nothing Nothing Nothing Nothing + -- , testCase "blocksClient" $ go $ \_ -> blocksClient' cid Nothing Nothing Nothing Nothing + , testCase "hashesClient" $ go $ \_ -> hashesClient' cid Nothing Nothing Nothing Nothing + , testCase "branchHashesClient" $ go $ \_ -> branchHashesClient' cid Nothing Nothing Nothing Nothing (BranchBounds mempty mempty) - , testCase "branchHeadersClient" $ go $ \v _ -> branchHeadersClient' v cid Nothing Nothing Nothing - Nothing (BranchBounds mempty mempty) - , testCase "branchBlocksClient" $ go $ \v _ -> branchBlocksClient' v cid Nothing Nothing Nothing + , testCase "branchHeadersClient" $ go $ \_ -> branchHeadersClient' cid Nothing Nothing Nothing Nothing (BranchBounds mempty mempty) + -- , testCase "branchBlocksClient" $ go $ \_ -> branchBlocksClient' cid Nothing Nothing Nothing + -- Nothing (BranchBounds mempty mempty) ] where + go :: Show b => (HasVersion => BlockHeader -> ClientM_ b) -> IO () go run = do env <- _envClientEnv <$> envIO - res <- flip runClientM_ env $ modifyResponse checkHeader $ - run version (genesisBlockHeader version cid) + res <- withVersion version $ flip runClientM_ env $ modifyResponse checkHeader $ + run (genesisBlockHeader cid) assertBool ("test failed: " <> sshow res) (isRight res) checkHeader res = do @@ -180,350 +182,351 @@ httpHeaderTests envIO cid = (d <= 2) return res -simpleClientSession :: IO TestClientEnv_ -> ChainId -> TestTree -simpleClientSession envIO cid = - testCaseSteps ("simple session for chain " <> sshow cid) $ \step -> do - env <- _envClientEnv <$> envIO - bhdbs <- _envBlockHeaderDbs <$> envIO - pdbs <- _envPayloadDbs <$> envIO - res <- runClientM (session bhdbs pdbs step) env - assertBool ("test failed: " <> sshow res) (isRight res) - where - - session :: [(ChainId, BlockHeaderDb)] -> [(ChainId, PayloadDb RocksDbTable)] -> (String -> IO a) -> ClientM () - session bhdbs pdbs step = do - - let gbh0 = genesisBlockHeader version cid - - bhdb <- case Prelude.lookup cid bhdbs of - Just x -> return x - Nothing -> error "Chainweb.Test.RestAPI.simpleClientSession: missing block header db in test" - - pdb <- case Prelude.lookup cid pdbs of - Just x -> return x - Nothing -> error "Chainweb.Test.RestAPI.simpleClientSession: missing payload db in test" - - void $ liftIO $ step "headerClient: get genesis block header" - gen0 <- headerClient version cid (key gbh0) - assertExpectation "header client returned wrong entry" - (Expected gbh0) - (Actual gen0) - - void $ liftIO $ step "headerClient: get genesis block header pretty" - gen01 <- headerClientJsonPretty version cid (key gbh0) - assertExpectation "header client returned wrong entry" - (Expected gbh0) - (Actual gen01) - - void $ liftIO $ step "headerClient: get genesis block header binary" - gen02 <- headerClientJsonBinary version cid (key gbh0) - assertExpectation "header client returned wrong entry" - (Expected gbh0) - (Actual gen02) - - void $ liftIO $ step "headersClient: get genesis block header" - bhs1 <- headersClient version cid Nothing Nothing Nothing Nothing - gen1 <- case _pageItems bhs1 of - [] -> liftIO $ assertFailure "headersClient did return empty result" - (h:_) -> return h - assertExpectation "header client returned wrong entry" - (Expected gbh0) - (Actual gen1) - - void $ liftIO $ step "blocksClient: get genesis block" - block1 <- blocksClient version cid Nothing Nothing Nothing Nothing - gen1Block <- case _pageItems block1 of - [] -> liftIO $ assertFailure "blocksClient did return empty result" - (h:_) -> return h - assertExpectation "block client returned wrong entry" - (Expected (Block gbh0 (version ^?! versionGenesis . genesisBlockPayload . atChain cid))) - (Actual gen1Block) - - void $ liftIO $ step "put 3 new blocks" - let newHeaders = take 3 $ testBlockHeaders (ParentHeader gbh0) - liftIO $ traverse_ (unsafeInsertBlockHeaderDb bhdb) newHeaders - liftIO $ traverse_ (\x -> addNewPayload pdb (view blockHeight x) (testBlockPayload_ x)) newHeaders - - void $ liftIO $ step "headersClient: get all 4 block headers" - bhs2 <- headersClient version cid Nothing Nothing Nothing Nothing - assertExpectation "headersClient returned wrong number of entries" - (Expected 4) - (Actual $ _pageLimit bhs2) - - void $ liftIO $ step "blocksClient: get all 4 blocks" - blocks2 <- blocksClient version cid Nothing Nothing Nothing Nothing - assertExpectation "blocksClient returned wrong number of entries" - (Expected 4) - (Actual $ _pageLimit blocks2) - - void $ liftIO $ step "hashesClient: get all 4 block hashes" - hs2 <- hashesClient version cid Nothing Nothing Nothing Nothing - assertExpectation "hashesClient returned wrong number of entries" - (Expected $ _pageLimit bhs2) - (Actual $ _pageLimit hs2) - assertExpectation "hashesClient returned wrong hashes" - (Expected $ key <$> _pageItems bhs2) - (Actual $ _pageItems hs2) - - forM_ newHeaders $ \h -> do - void $ liftIO $ step $ "headerClient: " <> T.unpack (encodeToText (view blockHash h)) - r <- headerClient version cid (key h) - assertExpectation "header client returned wrong entry" - (Expected h) - (Actual r) - - -- branchHeaders - - do - void $ liftIO $ step "branchHeadersClient: BranchBounds limits exceeded" - clientEnv <- liftIO $ _envClientEnv <$> envIO - let query bounds = liftIO - $ flip runClientM clientEnv - $ branchHeadersClient - version cid Nothing Nothing Nothing Nothing bounds - let limit = 32 - let blockHeaders = testBlockHeaders (ParentHeader gbh0) - let maxBlockHeaders = take limit blockHeaders - let excessBlockHeaders = take (limit + 1) blockHeaders - - let mkLower :: [BlockHeader] -> HS.HashSet (LowerBound BlockHash) - mkLower hs = HS.fromList $ map (LowerBound . key) hs - let mkUpper :: [BlockHeader] -> HS.HashSet (UpperBound BlockHash) - mkUpper hs = HS.fromList $ map (UpperBound . key) hs - - let emptyLower = mkLower [] - let badLower = mkLower excessBlockHeaders - let goodLower = mkLower maxBlockHeaders - - let emptyUpper = mkUpper [] - let badUpper = mkUpper excessBlockHeaders - let goodUpper = mkUpper maxBlockHeaders - - let badRespCheck :: Int -> ClientError -> Bool - badRespCheck s e = isFailureResponse e && clientErrorStatusCode e == Just s - - badLowerResponse <- query (BranchBounds badLower emptyUpper) - assertExpectation "branchHeadersClient returned a 400 error code on excess lower" - (Expected (Left True)) - (Actual (first (badRespCheck 400) badLowerResponse)) - - badUpperResponse <- query (BranchBounds emptyLower badUpper) - assertExpectation "branchHeadersClient returned a 400 error code on excess upper" - (Expected (Left True)) - (Actual (first (badRespCheck 400) badUpperResponse)) - - -- This will still fail because a bunch of these keys won't be found, - -- but it won't fail the bounds check, which happens first - doesntFailBoundsCheck <- query (BranchBounds goodLower goodUpper) - assertExpectation "branchHeadersClient returned a 404; bounds were within the limits, still fails key exists check" - (Expected (Left True)) - (Actual (first (badRespCheck 404) doesntFailBoundsCheck)) - - doesntFailAtAll <- query (BranchBounds emptyLower emptyUpper) - assertExpectation "branchHeadersClient returned a 200; bounds were within the limits, and no keys to check at all" - (Expected (Right ())) - (Actual (() <$ doesntFailAtAll)) - - void $ liftIO $ step "branchHeadersClient: get no block headers" - bhs3 <- branchHeadersClient version cid Nothing Nothing Nothing Nothing - (BranchBounds mempty mempty) - assertExpectation "branchHeadersClient returned wrong number of entries" - (Expected 0) - (Actual $ _pageLimit bhs3) - - forM_ ([2..] `zip` newHeaders) $ \(i, h) -> do - void $ liftIO $ step $ "branchHeadersClient: get " <> sshow i <> " block headers with upper bound" - bhs4 <- branchHeadersClient version cid Nothing Nothing Nothing Nothing - (BranchBounds mempty (HS.singleton (UpperBound $ key h))) - assertExpectation "branchHeadersClient returned wrong number of entries" - (Expected i) - (Actual $ _pageLimit bhs4) - - void $ liftIO $ step "branchHeadersClient: get no block headers with lower and upper bound" - bhs5 <- branchHeadersClient version cid Nothing Nothing Nothing Nothing - (BranchBounds (HS.singleton (LowerBound $ key h)) (HS.singleton (UpperBound $ key h))) - assertExpectation "branchHeadersClient returned wrong number of entries" - (Expected 0) - (Actual $ _pageLimit bhs5) - - forM_ (newHeaders `zip` drop 1 newHeaders) $ \(h0, h1) -> do - void $ liftIO $ step "branchHeadersClient: get one block headers with lower and upper bound" - bhs5 <- branchHeadersClient version cid Nothing Nothing Nothing Nothing - (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) - assertExpectation "branchHeadersClient returned wrong number of entries" - (Expected 1) - (Actual $ _pageLimit bhs5) - - forM_ (newHeaders `zip` drop 2 newHeaders) $ \(h0, h1) -> do - void $ liftIO $ step "branchHeadersClient: get two block headers with lower and upper bound" - bhs5 <- branchHeadersClient version cid Nothing Nothing Nothing Nothing - (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) - assertExpectation "branchHeadersClient returned wrong number of entries" - (Expected 2) - (Actual $ _pageLimit bhs5) - - -- branchHeaders - - void $ liftIO $ step "branchHashesClient: get no block headers" - hs3 <- branchHashesClient version cid Nothing Nothing Nothing Nothing - (BranchBounds mempty mempty) - assertExpectation "branchHashesClient returned wrong number of entries" - (Expected 0) - (Actual $ _pageLimit hs3) - - forM_ ([2..] `zip` newHeaders) $ \(i, h) -> do - void $ liftIO $ step $ "branchHashesClient: get " <> sshow i <> " block headers with upper bound" - hs4 <- branchHashesClient version cid Nothing Nothing Nothing Nothing - (BranchBounds mempty (HS.singleton (UpperBound $ key h))) - assertExpectation "branchHashesClient returned wrong number of entries" - (Expected i) - (Actual $ _pageLimit hs4) - - void $ liftIO $ step "branchHashesClient: get no block headers with lower and upper bound" - hs5 <- branchHashesClient version cid Nothing Nothing Nothing Nothing - (BranchBounds (HS.singleton (LowerBound $ key h)) (HS.singleton (UpperBound $ key h))) - assertExpectation "branchHashesClient returned wrong number of entries" - (Expected 0) - (Actual $ _pageLimit hs5) - - forM_ (newHeaders `zip` drop 1 newHeaders) $ \(h0, h1) -> do - void $ liftIO $ step "branchHashesClient: get one block headers with lower and upper bound" - hs5 <- branchHashesClient version cid Nothing Nothing Nothing Nothing - (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) - assertExpectation "branchHashesClient returned wrong number of entries" - (Expected 1) - (Actual $ _pageLimit hs5) - - forM_ (newHeaders `zip` drop 2 newHeaders) $ \(h0, h1) -> do - void $ liftIO $ step "branchHashesClient: get two block headers with lower and upper bound" - hs5 <- branchHashesClient version cid Nothing Nothing Nothing Nothing - (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) - assertExpectation "branchHashesClient returned wrong number of entries" - (Expected 2) - (Actual $ _pageLimit hs5) - - -- branch hashes with fork - - void $ liftIO $ step "headerPutClient: put 3 new blocks on a new fork" - let newHeaders2 = take 3 $ testBlockHeadersWithNonce (Nonce 17) (ParentHeader gbh0) - liftIO $ traverse_ (unsafeInsertBlockHeaderDb bhdb) newHeaders2 - liftIO $ traverse_ (\x -> addNewPayload pdb (view blockHeight x) (testBlockPayload_ x)) newHeaders2 - - let lower = last newHeaders - forM_ ([1..] `zip` newHeaders2) $ \(i, h) -> do - void $ liftIO $ step "branchHashesClient: get one block headers with lower and upper bound" - hs5 <- branchHashesClient version cid Nothing Nothing Nothing Nothing - (BranchBounds (HS.singleton (LowerBound $ key lower)) (HS.singleton (UpperBound $ key h))) - assertExpectation "branchHashesClient returned wrong number of entries" - (Expected i) - (Actual $ _pageLimit hs5) +-- simpleClientSession :: IO TestClientEnv_ -> ChainId -> TestTree +-- simpleClientSession envIO cid = +-- testCaseSteps ("simple session for chain " <> sshow cid) $ \step -> do +-- env <- _envClientEnv <$> envIO +-- bhdbs <- _envBlockHeaderDbs <$> envIO +-- pdbs <- _envPayloads <$> envIO +-- res <- runClientM (withVersion version $ session bhdbs pdbs step) env +-- assertBool ("test failed: " <> sshow res) (isRight res) +-- where + +-- session :: HasVersion => [(ChainId, BlockHeaderDb)] -> [(ChainId, PayloadDb RocksDbTable)] -> (String -> IO a) -> ClientM () +-- session bhdbs pdbs step = do + +-- let gbh0 = genesisBlockHeader cid + +-- bhdb <- case Prelude.lookup cid bhdbs of +-- Just x -> return x +-- Nothing -> error "Chainweb.Test.RestAPI.simpleClientSession: missing block header db in test" + +-- pdb <- case Prelude.lookup cid pdbs of +-- Just x -> return x +-- Nothing -> error "Chainweb.Test.RestAPI.simpleClientSession: missing payload db in test" + +-- void $ liftIO $ step "headerClient: get genesis block header" +-- gen0 <- headerClient cid (key gbh0) +-- assertExpectation "header client returned wrong entry" +-- (Expected gbh0) +-- (Actual gen0) + +-- void $ liftIO $ step "headerClient: get genesis block header pretty" +-- gen01 <- headerClientJsonPretty cid (key gbh0) +-- assertExpectation "header client returned wrong entry" +-- (Expected gbh0) +-- (Actual gen01) + +-- void $ liftIO $ step "headerClient: get genesis block header binary" +-- gen02 <- headerClientJsonBinary cid (key gbh0) +-- assertExpectation "header client returned wrong entry" +-- (Expected gbh0) +-- (Actual gen02) + +-- void $ liftIO $ step "headersClient: get genesis block header" +-- bhs1 <- headersClient cid Nothing Nothing Nothing Nothing +-- gen1 <- case _pageItems bhs1 of +-- [] -> liftIO $ assertFailure "headersClient did return empty result" +-- (h:_) -> return h +-- assertExpectation "header client returned wrong entry" +-- (Expected gbh0) +-- (Actual gen1) + +-- void $ liftIO $ step "blocksClient: get genesis block" +-- block1 <- blocksClient cid Nothing Nothing Nothing Nothing +-- gen1Block <- case _pageItems block1 of +-- [] -> liftIO $ assertFailure "blocksClient did return empty result" +-- (h:_) -> return h +-- assertExpectation "block client returned wrong entry" +-- (Expected (Block gbh0 (implicitVersion ^?! versionGenesis . genesisBlockPayload . atChain cid))) +-- (Actual gen1Block) + +-- void $ liftIO $ step "put 3 new blocks" +-- let newHeaders = take 3 $ testBlockHeaders (ParentHeader gbh0) +-- liftIO $ traverse_ (unsafeInsertBlockHeaderDb bhdb) newHeaders +-- liftIO $ traverse_ (\x -> addNewPayload pdb (view blockHeight x) (testBlockPayload_ x)) newHeaders + +-- void $ liftIO $ step "headersClient: get all 4 block headers" +-- bhs2 <- headersClient cid Nothing Nothing Nothing Nothing +-- assertExpectation "headersClient returned wrong number of entries" +-- (Expected 4) +-- (Actual $ _pageLimit bhs2) + +-- void $ liftIO $ step "blocksClient: get all 4 blocks" +-- blocks2 <- blocksClient cid Nothing Nothing Nothing Nothing +-- assertExpectation "blocksClient returned wrong number of entries" +-- (Expected 4) +-- (Actual $ _pageLimit blocks2) + +-- void $ liftIO $ step "hashesClient: get all 4 block hashes" +-- hs2 <- hashesClient cid Nothing Nothing Nothing Nothing +-- assertExpectation "hashesClient returned wrong number of entries" +-- (Expected $ _pageLimit bhs2) +-- (Actual $ _pageLimit hs2) +-- assertExpectation "hashesClient returned wrong hashes" +-- (Expected $ key <$> _pageItems bhs2) +-- (Actual $ _pageItems hs2) + +-- forM_ newHeaders $ \h -> do +-- void $ liftIO $ step $ "headerClient: " <> T.unpack (encodeToText (view blockHash h)) +-- r <- headerClient cid (key h) +-- assertExpectation "header client returned wrong entry" +-- (Expected h) +-- (Actual r) + +-- -- branchHeaders + +-- do +-- void $ liftIO $ step "branchHeadersClient: BranchBounds limits exceeded" +-- clientEnv <- liftIO $ _envClientEnv <$> envIO +-- let query bounds = liftIO +-- $ flip runClientM clientEnv +-- $ branchHeadersClient +-- cid Nothing Nothing Nothing Nothing bounds +-- let limit = 32 +-- let blockHeaders = testBlockHeaders (ParentHeader gbh0) +-- let maxBlockHeaders = take limit blockHeaders +-- let excessBlockHeaders = take (limit + 1) blockHeaders + +-- let mkLower :: [BlockHeader] -> HS.HashSet (LowerBound BlockHash) +-- mkLower hs = HS.fromList $ map (LowerBound . key) hs +-- let mkUpper :: [BlockHeader] -> HS.HashSet (UpperBound BlockHash) +-- mkUpper hs = HS.fromList $ map (UpperBound . key) hs + +-- let emptyLower = mkLower [] +-- let badLower = mkLower excessBlockHeaders +-- let goodLower = mkLower maxBlockHeaders + +-- let emptyUpper = mkUpper [] +-- let badUpper = mkUpper excessBlockHeaders +-- let goodUpper = mkUpper maxBlockHeaders + +-- let badRespCheck :: Int -> ClientError -> Bool +-- badRespCheck s e = isFailureResponse e && clientErrorStatusCode e == Just s + +-- badLowerResponse <- query (BranchBounds badLower emptyUpper) +-- assertExpectation "branchHeadersClient returned a 400 error code on excess lower" +-- (Expected (Left True)) +-- (Actual (first (badRespCheck 400) badLowerResponse)) + +-- badUpperResponse <- query (BranchBounds emptyLower badUpper) +-- assertExpectation "branchHeadersClient returned a 400 error code on excess upper" +-- (Expected (Left True)) +-- (Actual (first (badRespCheck 400) badUpperResponse)) + +-- -- This will still fail because a bunch of these keys won't be found, +-- -- but it won't fail the bounds check, which happens first +-- doesntFailBoundsCheck <- query (BranchBounds goodLower goodUpper) +-- assertExpectation "branchHeadersClient returned a 404; bounds were within the limits, still fails key exists check" +-- (Expected (Left True)) +-- (Actual (first (badRespCheck 404) doesntFailBoundsCheck)) + +-- doesntFailAtAll <- query (BranchBounds emptyLower emptyUpper) +-- assertExpectation "branchHeadersClient returned a 200; bounds were within the limits, and no keys to check at all" +-- (Expected (Right ())) +-- (Actual (() <$ doesntFailAtAll)) + +-- void $ liftIO $ step "branchHeadersClient: get no block headers" +-- bhs3 <- branchHeadersClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds mempty mempty) +-- assertExpectation "branchHeadersClient returned wrong number of entries" +-- (Expected 0) +-- (Actual $ _pageLimit bhs3) + +-- forM_ ([2..] `zip` newHeaders) $ \(i, h) -> do +-- void $ liftIO $ step $ "branchHeadersClient: get " <> sshow i <> " block headers with upper bound" +-- bhs4 <- branchHeadersClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds mempty (HS.singleton (UpperBound $ key h))) +-- assertExpectation "branchHeadersClient returned wrong number of entries" +-- (Expected i) +-- (Actual $ _pageLimit bhs4) + +-- void $ liftIO $ step "branchHeadersClient: get no block headers with lower and upper bound" +-- bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds (HS.singleton (LowerBound $ key h)) (HS.singleton (UpperBound $ key h))) +-- assertExpectation "branchHeadersClient returned wrong number of entries" +-- (Expected 0) +-- (Actual $ _pageLimit bhs5) + +-- forM_ (newHeaders `zip` drop 1 newHeaders) $ \(h0, h1) -> do +-- void $ liftIO $ step "branchHeadersClient: get one block headers with lower and upper bound" +-- bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) +-- assertExpectation "branchHeadersClient returned wrong number of entries" +-- (Expected 1) +-- (Actual $ _pageLimit bhs5) + +-- forM_ (newHeaders `zip` drop 2 newHeaders) $ \(h0, h1) -> do +-- void $ liftIO $ step "branchHeadersClient: get two block headers with lower and upper bound" +-- bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) +-- assertExpectation "branchHeadersClient returned wrong number of entries" +-- (Expected 2) +-- (Actual $ _pageLimit bhs5) + +-- -- branchHeaders + +-- void $ liftIO $ step "branchHashesClient: get no block headers" +-- hs3 <- branchHashesClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds mempty mempty) +-- assertExpectation "branchHashesClient returned wrong number of entries" +-- (Expected 0) +-- (Actual $ _pageLimit hs3) + +-- forM_ ([2..] `zip` newHeaders) $ \(i, h) -> do +-- void $ liftIO $ step $ "branchHashesClient: get " <> sshow i <> " block headers with upper bound" +-- hs4 <- branchHashesClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds mempty (HS.singleton (UpperBound $ key h))) +-- assertExpectation "branchHashesClient returned wrong number of entries" +-- (Expected i) +-- (Actual $ _pageLimit hs4) + +-- void $ liftIO $ step "branchHashesClient: get no block headers with lower and upper bound" +-- hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds (HS.singleton (LowerBound $ key h)) (HS.singleton (UpperBound $ key h))) +-- assertExpectation "branchHashesClient returned wrong number of entries" +-- (Expected 0) +-- (Actual $ _pageLimit hs5) + +-- forM_ (newHeaders `zip` drop 1 newHeaders) $ \(h0, h1) -> do +-- void $ liftIO $ step "branchHashesClient: get one block headers with lower and upper bound" +-- hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) +-- assertExpectation "branchHashesClient returned wrong number of entries" +-- (Expected 1) +-- (Actual $ _pageLimit hs5) + +-- forM_ (newHeaders `zip` drop 2 newHeaders) $ \(h0, h1) -> do +-- void $ liftIO $ step "branchHashesClient: get two block headers with lower and upper bound" +-- hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) +-- assertExpectation "branchHashesClient returned wrong number of entries" +-- (Expected 2) +-- (Actual $ _pageLimit hs5) + +-- -- branch hashes with fork + +-- void $ liftIO $ step "headerPutClient: put 3 new blocks on a new fork" +-- let newHeaders2 = take 3 $ testBlockHeadersWithNonce (Nonce 17) (ParentHeader gbh0) +-- liftIO $ traverse_ (unsafeInsertBlockHeaderDb bhdb) newHeaders2 +-- liftIO $ traverse_ (\x -> addNewPayload pdb (view blockHeight x) (testBlockPayload_ x)) newHeaders2 + +-- let lower = last newHeaders +-- forM_ ([1..] `zip` newHeaders2) $ \(i, h) -> do +-- void $ liftIO $ step "branchHashesClient: get one block headers with lower and upper bound" +-- hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing +-- (BranchBounds (HS.singleton (LowerBound $ key lower)) (HS.singleton (UpperBound $ key h))) +-- assertExpectation "branchHashesClient returned wrong number of entries" +-- (Expected i) +-- (Actual $ _pageLimit hs5) -- -------------------------------------------------------------------------- -- -- Paging Tests -pagingTests :: RocksDb -> Bool -> TestTree -pagingTests rdb tls = - withResourceT - (mkEnv rdb tls =<< - liftIO (starBlockHeaderDbs 6 =<< testBlockHeaderDbs rdb version)) - $ \env -> testGroup "paging tests" - [ testPageLimitHeadersClient env - , testPageLimitHashesClient env - ] - -pagingTest - :: Eq a - => Show a - => String - -- ^ Test name - -> (BlockHeaderDb -> IO [a]) - -- ^ Get test items from database - -> (a -> DbKey BlockHeaderDb) - -- ^ Compute paging key from item - -> Bool - -- ^ whether the result represents an finite (True) or infinite (False) - -- set - -> (ChainId -> Maybe Limit -> Maybe (NextItem (DbKey BlockHeaderDb)) -> ClientM (Page (NextItem (DbKey BlockHeaderDb)) a)) - -- ^ Request with paging parameters - -> IO TestClientEnv_ - -- ^ Test environment - -> TestTree -pagingTest name getDbItems getKey fin request envIO = testGroup name - [ testCaseSteps "test limit parameter" $ \step -> do - env <- _envClientEnv <$> envIO - [(cid, db)] <- _envBlockHeaderDbs <$> envIO - ents <- getDbItems db - let l = len ents - res <- flip runClientM env $ forM_ [0 .. (l+2)] $ \i -> - session step ents cid (Just i) Nothing - assertBool ("test of limit failed: " <> sshow res) (isRight res) - - -- TODO Did a limit value of 0 mean something else previously? - -- The two last tests that are failing now are failing when - -- hitting `Limit 0`. - - , testCaseSteps "test next parameter" $ \step -> do - env <- _envClientEnv <$> envIO - [(cid, db)] <- _envBlockHeaderDbs <$> envIO - ents <- getDbItems db - let l = len ents - res <- flip runClientM env $ forM_ [0 .. (l-1)] $ \i -> do - let es = drop i ents - session step es cid Nothing (Just . Inclusive . getKey . head $ es) - assertBool ("test limit and next failed: " <> sshow res) (isRight res) - - , testCaseSteps "test limit and next parameter" $ \step -> do - env <- _envClientEnv <$> envIO - [(cid, db)] <- _envBlockHeaderDbs <$> envIO - ents <- getDbItems db - let l = len ents - res <- flip runClientM env - $ forM_ [0 .. (l-1)] $ \i -> forM_ [0 .. (l+2-i)] $ \j -> do - let es = drop (int i) ents - session step es cid (Just j) (Just . Inclusive . getKey . head $ es) - assertBool ("test limit and next failed: " <> sshow res) (isRight res) - - , testCase "non existing next parameter" $ do - env <- _envClientEnv <$> envIO - [(cid, db)] <- _envBlockHeaderDbs <$> envIO - missing <- missingKey db - res <- flip runClientM env $ request cid Nothing (Just $ Exclusive missing) - assertBool ("test failed with unexpected result: " <> sshow res) (isErrorCode 404 res) - ] - where - session step ents cid n next = do - void $ liftIO $ step $ "limit " <> sshow n <> ", next " <> sshow next - r <- request cid n next - assertExpectation "result has wrong page 'limit' value" - (Expected . maybe id min n . int $ length ents) - (Actual $ _pageLimit r) - assertExpectation "result contains wrong page 'items'" - (Expected . maybe id (take . int) n $ ents) - (Actual $ _pageItems r) - assertExpectation "result contains wrong page 'next' value" - (Expected $ expectedNext ents n) - (Actual $ _pageNext r) - - expectedNext = if fin then expectedNextFin else expectedNextInf - - -- Finite case - expectedNextFin _ Nothing = Nothing - expectedNextFin ents (Just n) = Inclusive . getKey <$> listToMaybe (drop (int n) ents) - - -- Infinite case - expectedNextInf ents Nothing = Exclusive . getKey <$> (Just $ last ents) - expectedNextInf ents (Just n) - | n >= len ents = Exclusive . getKey <$> (Just $ last ents) - | otherwise = Inclusive . getKey <$> listToMaybe (drop (int n) ents) - -testPageLimitHeadersClient :: IO TestClientEnv_ -> TestTree -testPageLimitHeadersClient = pagingTest "headersClient" headers key False request - where - request cid l n = headersClient version cid l n Nothing Nothing - -testPageLimitHashesClient :: IO TestClientEnv_ -> TestTree -testPageLimitHashesClient = pagingTest "hashesClient" hashes id False request - where - request cid l n = hashesClient version cid l n Nothing Nothing +-- pagingTests :: RocksDb -> Bool -> TestTree +-- pagingTests rdb tls = withVersion version $ +-- withResourceT +-- (mkEnv rdb tls =<< +-- liftIO (starBlockHeaderDbs 6 =<< testBlockHeaderDbs rdb)) +-- $ \env -> testGroup "paging tests" +-- [ testPageLimitHeadersClient env +-- , testPageLimitHashesClient env +-- ] + +-- pagingTest +-- :: HasVersion +-- => Eq a +-- => Show a +-- => String +-- -- ^ Test name +-- -> (BlockHeaderDb -> IO [a]) +-- -- ^ Get test items from database +-- -> (a -> DbKey BlockHeaderDb) +-- -- ^ Compute paging key from item +-- -> Bool +-- -- ^ whether the result represents an finite (True) or infinite (False) +-- -- set +-- -> (ChainId -> Maybe Limit -> Maybe (NextItem (DbKey BlockHeaderDb)) -> ClientM (Page (NextItem (DbKey BlockHeaderDb)) a)) +-- -- ^ Request with paging parameters +-- -> IO TestClientEnv_ +-- -- ^ Test environment +-- -> TestTree +-- pagingTest name getDbItems getKey fin request envIO = testGroup name +-- [ testCaseSteps "test limit parameter" $ \step -> do +-- env <- _envClientEnv <$> envIO +-- [(cid, db)] <- _envBlockHeaderDbs <$> envIO +-- ents <- getDbItems db +-- let l = len ents +-- res <- flip runClientM env $ forM_ [0 .. (l+2)] $ \i -> +-- session step ents cid (Just i) Nothing +-- assertBool ("test of limit failed: " <> sshow res) (isRight res) + +-- -- TODO Did a limit value of 0 mean something else previously? +-- -- The two last tests that are failing now are failing when +-- -- hitting `Limit 0`. + +-- , testCaseSteps "test next parameter" $ \step -> do +-- env <- _envClientEnv <$> envIO +-- [(cid, db)] <- _envBlockHeaderDbs <$> envIO +-- ents <- getDbItems db +-- let l = len ents +-- res <- flip runClientM env $ forM_ [0 .. (l-1)] $ \i -> do +-- let es = drop i ents +-- session step es cid Nothing (Just . Inclusive . getKey . head $ es) +-- assertBool ("test limit and next failed: " <> sshow res) (isRight res) + +-- , testCaseSteps "test limit and next parameter" $ \step -> do +-- env <- _envClientEnv <$> envIO +-- [(cid, db)] <- _envBlockHeaderDbs <$> envIO +-- ents <- getDbItems db +-- let l = len ents +-- res <- flip runClientM env +-- $ forM_ [0 .. (l-1)] $ \i -> forM_ [0 .. (l+2-i)] $ \j -> do +-- let es = drop (int i) ents +-- session step es cid (Just j) (Just . Inclusive . getKey . head $ es) +-- assertBool ("test limit and next failed: " <> sshow res) (isRight res) + +-- , testCase "non existing next parameter" $ do +-- env <- _envClientEnv <$> envIO +-- [(cid, db)] <- _envBlockHeaderDbs <$> envIO +-- missing <- missingKey db +-- res <- flip runClientM env $ request cid Nothing (Just $ Exclusive missing) +-- assertBool ("test failed with unexpected result: " <> sshow res) (isErrorCode 404 res) +-- ] +-- where +-- session step ents cid n next = do +-- void $ liftIO $ step $ "limit " <> sshow n <> ", next " <> sshow next +-- r <- request cid n next +-- assertExpectation "result has wrong page 'limit' value" +-- (Expected . maybe id min n . int $ length ents) +-- (Actual $ _pageLimit r) +-- assertExpectation "result contains wrong page 'items'" +-- (Expected . maybe id (take . int) n $ ents) +-- (Actual $ _pageItems r) +-- assertExpectation "result contains wrong page 'next' value" +-- (Expected $ expectedNext ents n) +-- (Actual $ _pageNext r) + +-- expectedNext = if fin then expectedNextFin else expectedNextInf + +-- -- Finite case +-- expectedNextFin _ Nothing = Nothing +-- expectedNextFin ents (Just n) = Inclusive . getKey <$> listToMaybe (drop (int n) ents) + +-- -- Infinite case +-- expectedNextInf ents Nothing = Exclusive . getKey <$> (Just $ last ents) +-- expectedNextInf ents (Just n) +-- | n >= len ents = Exclusive . getKey <$> (Just $ last ents) +-- | otherwise = Inclusive . getKey <$> listToMaybe (drop (int n) ents) + +-- testPageLimitHeadersClient :: HasVersion => IO TestClientEnv_ -> TestTree +-- testPageLimitHeadersClient = pagingTest "headersClient" headers key False request +-- where +-- request cid l n = headersClient cid l n Nothing Nothing + +-- testPageLimitHashesClient :: HasVersion => IO TestClientEnv_ -> TestTree +-- testPageLimitHashesClient = pagingTest "hashesClient" hashes id False request +-- where +-- request cid l n = hashesClient cid l n Nothing Nothing diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index bbb697c84f..7879216d1e 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -67,7 +67,7 @@ import Chainweb.RestAPI.NodeInfo import Chainweb.SPV import Chainweb.SPV.EventProof import Chainweb.SPV.PayloadProof -import Chainweb.Test.Orphans.Internal (EventPactValue(..), ProofPactEvent(..), arbitraryBlockHeaderVersion) +import Chainweb.Test.Orphans.Internal (EventPactValue(..), ProofPactEvent(..), arbitraryBlockHeaderVersion, arbitraryBlockHeaderVersionHeight, arbitraryBlockHashRecordVersionHeightChain, arbitraryBlockHeaderVersionHeightChain) import Chainweb.Test.SPV.EventProof hiding (tests) import Chainweb.Test.Utils import Chainweb.Time @@ -84,6 +84,8 @@ import P2P.Test.Orphans () import Utils.Logging import Control.Lens (view) +import Chainweb.Version.Mainnet (mainnet) +import Pact.Core.Gas -- -------------------------------------------------------------------------- -- -- Roundrip Tests @@ -113,8 +115,8 @@ encodeDecodeTests = testGroup "Encode-Decode roundtrips" $ prop_encodeDecode decodeMerkleLogHash (encodeMerkleLogHash @ChainwebMerkleHashAlgorithm) , testProperty "BlockHash" $ prop_encodeDecode decodeBlockHash (encodeBlockHash @ChainwebMerkleHashAlgorithm) - , testProperty "BlockHash_ Keccak_256" - $ prop_encodeDecode decodeBlockHash (encodeBlockHash @Keccak_256) + -- , testProperty "BlockHash_ Keccak_256" + -- $ prop_encodeDecode decodeBlockHash (encodeBlockHash @Keccak_256) , testProperty "BlockHeight" $ prop_encodeDecode decodeBlockHeight encodeBlockHeight , testProperty "CutHeight" @@ -132,8 +134,8 @@ encodeDecodeTests = testGroup "Encode-Decode roundtrips" , testProperty "BlockHashRecord" $ prop_encodeDecode decodeBlockHashRecord encodeBlockHashRecord - , testProperty "BlockHeader" - $ prop_encodeDecode decodeBlockHeader encodeBlockHeader + -- , testProperty "BlockHeader" + -- $ prop_encodeDecode decodeBlockHeader encodeBlockHeader , testProperty "Nonce" $ prop_encodeDecode decodeNonce encodeNonce , testProperty "Time" @@ -152,16 +154,16 @@ encodeDecodeTests = testGroup "Encode-Decode roundtrips" $ prop_encodeDecode decodeBlockEventsHash (encodeBlockEventsHash @ChainwebMerkleHashAlgorithm) ] - , testGroup "Keccak_256" - [ testProperty "BlockPayloadHash" - $ prop_encodeDecode decodeBlockPayloadHash (encodeBlockPayloadHash @Keccak_256) - , testProperty "BlockTransactionsHash" - $ prop_encodeDecode decodeBlockTransactionsHash (encodeBlockTransactionsHash @Keccak_256) - , testProperty "BlockTransactionsHash" - $ prop_encodeDecode decodeBlockTransactionsHash (encodeBlockTransactionsHash @Keccak_256) - , testProperty "BlockEventsHash" - $ prop_encodeDecode decodeBlockEventsHash (encodeBlockEventsHash @Keccak_256) - ] + -- , testGroup "Keccak_256" + -- [ testProperty "BlockPayloadHash" + -- $ prop_encodeDecode decodeBlockPayloadHash (encodeBlockPayloadHash @Keccak_256) + -- , testProperty "BlockTransactionsHash" + -- $ prop_encodeDecode decodeBlockTransactionsHash (encodeBlockTransactionsHash @Keccak_256) + -- , testProperty "BlockTransactionsHash" + -- $ prop_encodeDecode decodeBlockTransactionsHash (encodeBlockTransactionsHash @Keccak_256) + -- , testProperty "BlockEventsHash" + -- $ prop_encodeDecode decodeBlockEventsHash (encodeBlockEventsHash @Keccak_256) + -- ] -- SPV , testGroup "SPV" @@ -176,22 +178,25 @@ encodeDecodeTests = testGroup "Encode-Decode roundtrips" , testProperty "ModuleName" $ prop_encodeDecode decodeModuleName encodeModuleName -- FIXME limit to empty spec and info - , testProperty "ModRef" - $ prop_encodeDecode (PactEventModRef <$> decodeModRef) (encodeModRef . _getPactEventModRef) + -- TODO PP + -- , testProperty "ModRef" + -- $ prop_encodeDecode (PactEventModRef <$> decodeModRef) (encodeModRef . _getPactEventModRef) , testProperty "Integer" $ prop_encodeDecode decodeInteger encodeInteger , testProperty "Decimal" $ prop_encodeDecode (PactEventDecimal <$> decodeDecimal) (encodeDecimal . _getPactEventDecimal) - , testProperty "Hash" - $ prop_encodeDecode decodeHash encodeHash + -- TODO PP + -- , testProperty "Hash" + -- $ prop_encodeDecode decodeHash encodeHash , testProperty "Array[Int256]" $ prop_encodeDecode (decodeArray decodeInteger) (`encodeArray` encodeInteger) , testProperty "Array[Bytes]" $ prop_encodeDecode (decodeArray decodeBytes) (`encodeArray` encodeBytes) -- FIXME "too few bytes" - , testProperty "PactEvent" - $ prop_encodeDecode (ProofPactEvent <$> decodePactEvent) (encodePactEvent . getProofPactEvent) + -- TODO PP + -- , testProperty "PactEvent" + -- $ prop_encodeDecode (ProofPactEvent <$> decodePactEvent) (encodePactEvent . getProofPactEvent) -- FIXME "pending bytes" , testProperty "PactParam" $ prop_encodeDecode (EventPactValue <$> decodeParam) (encodeParam . getEventPactValue) @@ -202,12 +207,18 @@ encodeDecodeTests = testGroup "Encode-Decode roundtrips" ] -- Mining - , testProperty "SolvedWork" - $ prop_encodeDecode decodeSolvedWork encodeSolvedWork - - , testProperty "WorkHeader" - $ forAll arbitrary $ \v -> forAll (arbitraryBlockHeaderVersion v) $ \bh -> - prop_encodeDecode (decodeWorkHeader v (view blockHeight bh)) encodeWorkHeader (workOnHeader bh) + , testProperty "MiningWork" + $ withVersion mainnet $ forAll arbitrary $ \height -> prop_encodeDecode (decodeWorkHeader height) encodeMiningWork + + -- TODO: PP + -- , testProperty "SolvedWork" + -- $ forAll arbitrary + -- $ \v -> withVersion v $ forAll arbitrary + -- $ \cid -> forAll arbitrary + -- $ \height -> forAll (arbitraryBlockHeaderVersionHeightChain height cid) + -- $ \bh -> forAll (arbitraryBlockHashRecordVersionHeightChain height cid) + -- $ \bhr -> newHeader + -- prop_encodeDecode (decodeWorkHeader (view blockHeight bh)) encodeMiningWork -- TODO Fix this! -- The following doesn't hold: @@ -230,15 +241,19 @@ pactJsonTestCases f = , testProperty "Miner" $ f @Miner ] , testGroup "Mempool" - [ testProperty "GasLimit" $ f @GasLimit - , testProperty "GasPrice" $ f @GasPrice - , testProperty "ParsedDecimal" $ f @ParsedDecimal + -- [ testProperty "GasLimit" $ f @GasLimit + -- , testProperty "GasPrice" $ f @GasPrice + [ testProperty "ParsedDecimal" $ f @ParsedDecimal , testProperty "ParsedInteger" $ f @ParsedInteger ] ] instance Arbitrary MockTx where - arbitrary = MockTx <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary + arbitrary = MockTx + <$> arbitrary + <*> (GasPrice <$> arbitrary) + <*> (GasLimit . Gas . fromIntegral <$> arbitrary @Word) + <*> arbitrary jsonTestCases :: (forall a . Arbitrary a => Show a => ToJSON a => FromJSON a => Eq a => a -> Property) @@ -255,14 +270,14 @@ jsonTestCases f = , testProperty "HashTarget" $ f @HashTarget , testProperty "MerkleRootType" $ f @MerkleRootType , testProperty "MerkleLogHash" $ f @(MerkleLogHash ChainwebMerkleHashAlgorithm) - , testProperty "MerkleLogHash Keccak_256" $ f @(MerkleLogHash Keccak_256) + -- , testProperty "MerkleLogHash Keccak_256" $ f @(MerkleLogHash Keccak_256) , testProperty "PowHash" $ f @PowHash , testProperty "PowHashNat" $ f @PowHashNat , testProperty "BlockHash" $ f @BlockHash - , testProperty "BlockHash_ Keccak_256" $ f @(BlockHash_ Keccak_256) + -- , testProperty "BlockHash_ Keccak_256" $ f @(BlockHash_ Keccak_256) , testProperty "BlockHashRecord" $ f @BlockHashRecord - , testProperty "BlockHeader" $ f @BlockHeader - , testProperty "HeaderUpdate" $ f @HeaderUpdate + -- , testProperty "BlockHeader" $ f @BlockHeader + -- , testProperty "HeaderUpdate" $ withVersion mainnet $ f @HeaderUpdate , testProperty "BlockWeight" $ f @BlockWeight , testProperty "P2pNodeStats" $ f @P2pNodeStats , testProperty "P2pSessionResult" $ f @P2pSessionResult @@ -284,12 +299,12 @@ jsonTestCases f = , testProperty "LogFilter" $ f @LogFilter , testProperty "BlockHashWithHeight" $ f @BlockHashWithHeight , testProperty "CutId" $ f @CutId - , testProperty "CutHashes" $ f @CutHashes + -- , testProperty "CutHashes" $ withVersion mainnet $ f @CutHashes , testProperty "NodeVersion" $ f @NodeVersion , testProperty "NodeInfo" $ f @NodeInfo , testProperty "EnableConfig MiningConfig" $ f @(EnableConfig MiningConfig) , testProperty "NextItem Int" $ f @(NextItem Int) - , testProperty "Page BlockHash BlockHeader" $ f @(Page BlockHash BlockHeader) + -- , testProperty "Page BlockHash BlockHeader" $ f @(Page BlockHash BlockHeader) , testProperty "X509CertPem" $ f @X509CertPem , testProperty "X509CertChainPem" $ f @X509CertChainPem , testProperty "X509KeyPem" $ f @X509KeyPem @@ -340,21 +355,21 @@ jsonTestCases f = , testProperty "BlockEventsHash" $ f @(BlockEventsHash_ ChainwebMerkleHashAlgorithm) ] - , testGroup "Keccak_256" - [ testProperty "BlockPayloadHash" $ f @(BlockPayloadHash_ Keccak_256) - , testProperty "BlockTransactionsHash" $ f @(BlockTransactionsHash_ Keccak_256) - , testProperty "BlockOutputsHash" $ f @(BlockOutputsHash_ Keccak_256) - , testProperty "PayloadData" $ f @(PayloadData_ Keccak_256) - , testProperty "BlockTransactions" $ f @(BlockTransactions_ Keccak_256) - , testProperty "BlockPayload" $ f @(BlockPayload_ Keccak_256) - , testProperty "BlockOutputs" $ f @(BlockOutputs_ Keccak_256) - , testProperty "TransactionTree" $ f @(TransactionTree_ Keccak_256) - , testProperty "OutputTree" $ f @(OutputTree_ Keccak_256) - , testProperty "PayloadData" $ f @(PayloadData_ Keccak_256) - , testProperty "PayloadWithOutputs" $ f @(PayloadWithOutputs_ Keccak_256) - , testProperty "PayloadOutputProof" $ f @(PayloadProof Keccak_256) - , testProperty "BlockEventsHash" $ f @(BlockEventsHash_ Keccak_256) - ] + -- , testGroup "Keccak_256" + -- [ testProperty "BlockPayloadHash" $ f @(BlockPayloadHash_ Keccak_256) + -- , testProperty "BlockTransactionsHash" $ f @(BlockTransactionsHash_ Keccak_256) + -- , testProperty "BlockOutputsHash" $ f @(BlockOutputsHash_ Keccak_256) + -- , testProperty "PayloadData" $ f @(PayloadData_ Keccak_256) + -- , testProperty "BlockTransactions" $ f @(BlockTransactions_ Keccak_256) + -- , testProperty "BlockPayload" $ f @(BlockPayload_ Keccak_256) + -- , testProperty "BlockOutputs" $ f @(BlockOutputs_ Keccak_256) + -- , testProperty "TransactionTree" $ f @(TransactionTree_ Keccak_256) + -- , testProperty "OutputTree" $ f @(OutputTree_ Keccak_256) + -- , testProperty "PayloadData" $ f @(PayloadData_ Keccak_256) + -- , testProperty "PayloadWithOutputs" $ f @(PayloadWithOutputs_ Keccak_256) + -- , testProperty "PayloadOutputProof" $ f @(PayloadProof Keccak_256) + -- , testProperty "BlockEventsHash" $ f @(BlockEventsHash_ Keccak_256) + -- ] ] ] -- Types with ToJSON but without FromJSON instance diff --git a/test/unit/Chainweb/Test/SPV.hs b/test/unit/Chainweb/Test/SPV.hs index 83485dedf4..741e226bd6 100644 --- a/test/unit/Chainweb/Test/SPV.hs +++ b/test/unit/Chainweb/Test/SPV.hs @@ -91,10 +91,10 @@ import Chainweb.Storage.Table.RocksDB -- tests :: RocksDb -> TestTree tests rdb = testGroup "SPV tests" - [ testCaseStepsN "SPV transaction proof" 10 (spvTransactionRoundtripTest rdb version) - , testCaseStepsN "SPV transaction output proof" 10 (spvTransactionOutputRoundtripTest rdb version) - , apiTests rdb version - , testCaseSteps "SPV transaction proof test" (spvTest rdb version) + [ testCaseStepsN "SPV transaction proof" 10 (withVersion version $ spvTransactionRoundtripTest rdb) + , testCaseStepsN "SPV transaction output proof" 10 (withVersion version spvTransactionOutputRoundtripTest rdb ) + , withVersion version $ apiTests rdb + , testCaseSteps "SPV transaction proof test" (withVersion version $ spvTest rdb) , properties ] where @@ -110,9 +110,9 @@ testCaseStepsN name n test = testGroup name $ flip map [1..n] $ \i -> -- Find a reachable target chain -- -targetChain :: Cut -> BlockHeader -> IO ChainId +targetChain :: HasVersion => Cut -> BlockHeader -> IO ChainId targetChain c srcBlock = do - cids <- generate (shuffle $ toList $ chainIds c) + cids <- generate (shuffle $ toList chainIds) go cids where graph = _chainGraph c @@ -211,9 +211,9 @@ prop_outputProof_valid = forAll arbitraryPayloadWithStructuredOutputs go -- -- Also checks that the size of the created proofs meets the expectations. -- -spvTest :: RocksDb -> ChainwebVersion -> Step -> IO () -spvTest rdb v step = do - withTestCutDbWithoutPact rdb v id 100 logg $ \_ cutDb -> do +spvTest :: HasVersion => RocksDb -> Step -> IO () +spvTest rdb step = do + withTestCutDbWithoutPact rdb id 100 logg $ \_ cutDb -> do curCut <- _cutMap <$> _cut cutDb -- for each blockheader h in cut @@ -223,7 +223,7 @@ spvTest rdb v step = do -- for each transaction in ah & flip S.for (getPayloads cutDb) -- for each target chain c - & flip S.for (\(a,b,c,d) -> S.each $ (a,b,c,d,) <$> toList (chainIds cutDb)) + & flip S.for (\(a,b,c,d) -> S.each $ (a,b,c,d,) <$> toList chainIds) -- Create and verify transaction output proof & S.mapM (go cutDb) -- Ingore all cases where a proof couldn't be created @@ -294,7 +294,7 @@ spvTest rdb v step = do [ int $ BL.length $ encode proof , int n , int $ view blockHeight h - , int $ distance cutDb h trgChain + , int $ distance h trgChain , int $ B.length (_transactionOutputBytes txOut) ] @@ -303,14 +303,14 @@ spvTest rdb v step = do Right x -> do let msg = "SPV proof creation succeeded although target chain is not reachable (" <> "source height: " <> sshow (view blockHeight h) - <> ", distance: " <> sshow (distance cutDb h trgChain) + <> ", distance: " <> sshow (distance h trgChain) <> ")" assertBool msg isReachable return (Just x) Left SpvExceptionTargetNotReachable{} -> do let msg = "SPV proof creation failed although target chain is reachable (" <> "source height: " <> sshow (view blockHeight h) - <> ", distance: " <> sshow (distance cutDb h trgChain) + <> ", distance: " <> sshow (distance h trgChain) <> ")" assertBool msg (not isReachable) return Nothing @@ -318,16 +318,16 @@ spvTest rdb v step = do -- Distance between source chain an target chain -- - distance cutDb h trgChain = length + distance h trgChain = length $ shortestPath (_chainId h) trgChain - $ chainGraphAt cutDb (view blockHeight h) + $ chainGraphAt (view blockHeight h) -- Check whether target chain is reachable from the source block -- reachable :: CutDb as -> BlockHeader -> ChainId -> IO Bool reachable cutDb h trgChain = do m <- maxRank $ cutDb ^?! cutDbBlockHeaderDb trgChain - return $ (int m - int (view blockHeight h)) >= distance cutDb h trgChain + return $ (int m - int (view blockHeight h)) >= distance h trgChain -- regression model with @createTransactionOutputProof@. Proof size doesn't -- depend on target height. @@ -352,10 +352,10 @@ spvTest rdb v step = do -- -------------------------------------------------------------------------- -- -- SPV Tests -spvTransactionRoundtripTest :: RocksDb -> ChainwebVersion -> Step -> IO () -spvTransactionRoundtripTest rdb v step = do +spvTransactionRoundtripTest :: HasVersion => RocksDb -> Step -> IO () +spvTransactionRoundtripTest rdb step = do step "setup cut db" - withTestCutDbWithoutPact rdb v id 100 (\_ _ -> return ()) $ \_ cutDb -> do + withTestCutDbWithoutPact rdb id 100 (\_ _ -> return ()) $ \_ cutDb -> do step "pick random transaction" (h, txIx, tx, _) <- randomTransaction cutDb @@ -387,10 +387,10 @@ spvTransactionRoundtripTest rdb v step = do step "confirm that proof subject matches transaction" assertEqual "proof subject matches transaction" tx subj -spvTransactionOutputRoundtripTest :: RocksDb -> ChainwebVersion -> Step -> IO () -spvTransactionOutputRoundtripTest rdb v step = do +spvTransactionOutputRoundtripTest :: HasVersion => RocksDb -> Step -> IO () +spvTransactionOutputRoundtripTest rdb step = do step "setup cut db" - withTestCutDbWithoutPact rdb v id 100 (\_ _ -> return ()) $ \_ cutDb -> do + withTestCutDbWithoutPact rdb id 100 (\_ _ -> return ()) $ \_ cutDb -> do step "pick random transaction output" (h, outIx, _, out) <- randomTransaction cutDb @@ -428,24 +428,24 @@ spvTransactionOutputRoundtripTest rdb v step = do type TestClientEnv_ tbl = TestClientEnv MockTx tbl -apiTests :: RocksDb -> ChainwebVersion -> TestTree -apiTests rdb v = withResourceT (withTestPayloadResource rdb v 100 (\_ _ -> pure ())) $ \dbIO -> +apiTests :: HasVersion => RocksDb -> TestTree +apiTests rdb = withResourceT (withTestPayloadResource rdb 100 (\_ _ -> pure ())) $ \dbIO -> testGroup "SPV API tests" -- TODO: there is no openapi spec for this SPV API. - [ withResourceT (join $ withPayloadServer DoNotValidateSpec False v <$> liftIO dbIO <*> (liftIO $ payloadDbs . view cutDbPayloadDb <$> dbIO)) $ \env -> + [ withResourceT (join $ withPayloadServer DoNotValidateSpec False <$> liftIO dbIO <*> (liftIO $ payloadDbs . view cutDbPayloadDb <$> dbIO)) $ \env -> testCaseStepsN "spv api tests (without tls)" 10 (txApiTests env) - , withResourceT (join $ withPayloadServer DoNotValidateSpec True v <$> liftIO dbIO <*> (liftIO $ payloadDbs . view cutDbPayloadDb <$> dbIO)) $ \env -> + , withResourceT (join $ withPayloadServer DoNotValidateSpec True <$> liftIO dbIO <*> (liftIO $ payloadDbs . view cutDbPayloadDb <$> dbIO)) $ \env -> testCaseStepsN "spv api tests (with tls)" 10 (txApiTests env) ] where - cids = toList $ chainIds v + cids = toList chainIds payloadDbs :: CanReadablePayloadCas tbl' => PayloadDb tbl' -> [(ChainId, PayloadDb tbl')] payloadDbs db = (, db) <$> cids -txApiTests :: CanReadablePayloadCas tbl => IO (TestClientEnv_ tbl) -> Step -> IO () +txApiTests :: HasVersion => CanReadablePayloadCas tbl => IO (TestClientEnv_ tbl) -> Step -> IO () txApiTests envIO step = do - PayloadTestClientEnv env cutDb _payloadDbs v <- envIO + PayloadTestClientEnv env cutDb _payloadDbs <- envIO step "pick random transaction" (h, txIx, tx, out) <- randomTransaction cutDb @@ -459,7 +459,7 @@ txApiTests envIO step = do step "request transaction proof" txProof <- flip runClientM env $ - spvGetTransactionProofClient v trgChain (_chainId h) (view blockHeight h) (int txIx) + spvGetTransactionProofClient trgChain (_chainId h) (view blockHeight h) (int txIx) case txProof of @@ -477,7 +477,7 @@ txApiTests envIO step = do step "request transaction output proof" outProof <- flip runClientM env $ - spvGetTransactionOutputProofClient v trgChain (_chainId h) (view blockHeight h) (int txIx) + spvGetTransactionOutputProofClient trgChain (_chainId h) (view blockHeight h) (int txIx) case outProof of diff --git a/test/unit/Chainweb/Test/TreeDB.hs b/test/unit/Chainweb/Test/TreeDB.hs index f22fb9338f..e6581a5cbd 100644 --- a/test/unit/Chainweb/Test/TreeDB.hs +++ b/test/unit/Chainweb/Test/TreeDB.hs @@ -49,12 +49,14 @@ import Chainweb.Test.Utils.BlockHeader import Chainweb.TreeDB import Chainweb.Utils (int, len, tryAllSynchronous) import Chainweb.Utils.Paging +import Chainweb.Version (HasVersion) type Insert db = db -> [DbEntry db] -> IO () type WithTestDb db = forall prop . Testable prop => DbEntry db -> (db -> Insert db -> IO prop) -> IO prop treeDbInvariants :: (TreeDb db, IsBlockHeader (DbEntry db), Ord (DbEntry db), Ord (DbKey db)) + => HasVersion => WithTestDb db -- ^ Given a generic entry should yield a database and insert function for -- testing, and then safely close it after use. @@ -169,7 +171,9 @@ handOfGod_prop f (SparseTree t0) = ioProperty . withTreeDb f t $ \db insert -> d -- | Property: The root node's parent must always be itself. -- rootParent_prop - :: forall db. (TreeDb db, IsBlockHeader (DbEntry db)) + :: forall db + . (TreeDb db, IsBlockHeader (DbEntry db)) + => HasVersion => WithTestDb db -> SparseTree -> Property @@ -228,6 +232,7 @@ maxRank_prop f (SparseTree t0) = ioProperty . withTreeDb f t $ \db _ -> do -- entryOrder_prop :: forall db. (TreeDb db, IsBlockHeader (DbEntry db)) + => HasVersion => WithTestDb db -> SparseTree -> Property @@ -303,6 +308,7 @@ properties = prop_forkEntry :: forall db . TreeDb db + => HasVersion => IsBlockHeader (DbEntry db) => WithTestDb db -> Natural @@ -320,7 +326,7 @@ prop_forkEntry f i j = do a = take (int i) $ branch (Nonce 0) g b = take (int j) $ branch (Nonce 1) g - branch n x = view (from isoBH) <$> testBlockHeadersWithNonce n (ParentHeader $ view isoBH x) + branch n x = view (from isoBH) <$> testBlockHeadersWithNonce n (Parent $ view isoBH x) -- -------------------------------------------------------------------------- -- -- forward branch entries diff --git a/test/unit/Chainweb/Test/Version.hs b/test/unit/Chainweb/Test/Version.hs index e1ad4ab5c3..c78a1d940c 100644 --- a/test/unit/Chainweb/Test/Version.hs +++ b/test/unit/Chainweb/Test/Version.hs @@ -1,6 +1,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE RankNTypes #-} -- | -- Module: Chainweb.Test.Version @@ -48,12 +49,12 @@ tests = testGroup "ChainwebVersion properties" -- -------------------------------------------------------------------------- -- -- Utils -propForVersions :: String -> (ChainwebVersion -> Property) -> TestTree +propForVersions :: String -> (HasVersion => Property) -> TestTree propForVersions desc prop = testGroup desc - [ testProperty "arbitrary versions" $ prop - , testProperty "mainnet" $ prop Mainnet01 - , testProperty "testnet04" $ prop Testnet04 - , testProperty "recapDevnet" $ prop RecapDevelopment + [ testProperty "arbitrary versions" $ \v -> withVersion v prop + , testProperty "mainnet" $ withVersion Mainnet01 prop + , testProperty "testnet04" $ withVersion Testnet04 prop + , testProperty "recapDevnet" $ withVersion RecapDevelopment prop ] -- -------------------------------------------------------------------------- -- @@ -67,20 +68,20 @@ graphTests = testGroup "Graphs" , propForVersions "chainIds are chains of latest graph" prop_chainIds ] -prop_chainGraphs_sorted :: ChainwebVersion -> Property -prop_chainGraphs_sorted v - = property (ruleValid (_versionGraphs v)) +prop_chainGraphs_sorted :: HasVersion => Property +prop_chainGraphs_sorted + = property (ruleValid (_versionGraphs implicitVersion)) -prop_chainGraphs_order :: ChainwebVersion -> Property -prop_chainGraphs_order v = orders === NE.reverse (NE.sort orders) +prop_chainGraphs_order :: HasVersion => Property +prop_chainGraphs_order = orders === NE.reverse (NE.sort orders) where - orders = ruleElems $ fmap order $ _versionGraphs v + orders = ruleElems $ fmap order $ _versionGraphs implicitVersion -prop_genesisHeight :: ChainwebVersion -> Property -prop_genesisHeight v = property $ all ((>= 0) . genesisHeight v) $ chainIds v +prop_genesisHeight :: HasVersion => Property +prop_genesisHeight = property $ all ((>= 0) . genesisHeight) chainIds -prop_chainIds :: ChainwebVersion -> Property -prop_chainIds v = chainIds v === graphChainIds (snd $ ruleHead $ _versionGraphs v) +prop_chainIds :: HasVersion => Property +prop_chainIds = chainIds === graphChainIds (snd $ ruleHead $ _versionGraphs implicitVersion) -- -------------------------------------------------------------------------- -- -- Header Sizes @@ -99,50 +100,50 @@ headerSizeTests = testGroup "HeaderSize" -- | A "golden" test property. If the value changes the test will fails and must -- be manually updated. This protectes against accidentally changing this value. -- -prop_headerBaseSizeBytes_golden :: ChainwebVersion -> Property -prop_headerBaseSizeBytes_golden v = _versionHeaderBaseSizeBytes v === 208 +prop_headerBaseSizeBytes_golden :: HasVersion => Property +prop_headerBaseSizeBytes_golden = _versionHeaderBaseSizeBytes implicitVersion === 208 -prop_headerBaseSizeBytes :: ChainwebVersion -> Property -prop_headerBaseSizeBytes v = property $ do - cid <- elements $ toList $ chainIds v - let genHdr = genesisBlockHeader v cid +prop_headerBaseSizeBytes :: HasVersion => Property +prop_headerBaseSizeBytes = property $ do + cid <- elements $ toList $ chainIds + let genHdr = genesisBlockHeader cid gen = runPutS $ encodeBlockHeader genHdr as = runPutS $ encodeBlockHashRecord (view blockAdjacentHashes genHdr) - return $ _versionHeaderBaseSizeBytes v === int (B.length gen - B.length as) + return $ _versionHeaderBaseSizeBytes implicitVersion === int (B.length gen - B.length as) -prop_headerSizes_sorted :: ChainwebVersion -> Property -prop_headerSizes_sorted v - = NE.reverse (NE.sort (ruleElems (headerSizes v))) === ruleElems (headerSizes v) +prop_headerSizes_sorted :: HasVersion => Property +prop_headerSizes_sorted + = NE.reverse (NE.sort (ruleElems headerSizes)) === ruleElems headerSizes -prop_headerSizes_order :: ChainwebVersion -> Property -prop_headerSizes_order v = orders === NE.reverse (NE.sort orders) +prop_headerSizes_order :: HasVersion => Property +prop_headerSizes_order = orders === NE.reverse (NE.sort orders) where - orders = ruleElems $ fmap order $ _versionGraphs v + orders = ruleElems $ fmap order $ _versionGraphs implicitVersion -prop_headerSizeBytes_gen :: ChainwebVersion -> Property -prop_headerSizeBytes_gen v = property $ do - cid <- elements $ toList $ chainIds v - let hdr = genesisBlockHeader v cid +prop_headerSizeBytes_gen :: HasVersion => Property +prop_headerSizeBytes_gen = property $ do + cid <- elements $ toList chainIds + let hdr = genesisBlockHeader cid l = int $ B.length $ runPutS $ encodeBlockHeader $ hdr return $ counterexample ("chain: " <> sshow cid) - $ headerSizeBytes v cid (view blockHeight hdr) === l + $ headerSizeBytes cid (view blockHeight hdr) === l -prop_headerSizeBytes :: ChainwebVersion -> Property -prop_headerSizeBytes v = property $ do - h <- arbitraryBlockHeaderVersion v +prop_headerSizeBytes :: HasVersion => Property +prop_headerSizeBytes = property $ do + h <- arbitraryBlockHeaderVersion let l = int $ B.length $ runPutS $ encodeBlockHeader h return $ counterexample ("header: " <> sshow h) - $ headerSizeBytes (_chainwebVersion h) (view blockChainId h) (view blockHeight h) === l + $ headerSizeBytes (view blockChainId h) (view blockHeight h) === l -prop_workSizeBytes :: ChainwebVersion -> Property -prop_workSizeBytes v = property $ do - h <- arbitraryBlockHeaderVersion v - if (view blockHeight h == genesisHeight v (_chainId h)) +prop_workSizeBytes :: HasVersion => Property +prop_workSizeBytes = property $ do + h <- arbitraryBlockHeaderVersion + if (view blockHeight h == genesisHeight (_chainId h)) then discard else do let l = int $ B.length $ runPutS $ encodeBlockHeaderWithoutHash h return $ counterexample ("header: " <> sshow h) - $ workSizeBytes (_chainwebVersion h) (view blockHeight h) === l + $ workSizeBytes (view blockHeight h) === l diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index 27a83aab9f..22e58feac9 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -36,7 +36,7 @@ import Chainweb.Version.Registry -- chainweb-test-tools modules import Chainweb.Test.Utils - (independentSequentialTestGroup, toyChainId, withToyDB) + (independentSequentialTestGroup, toyChainId, withToyDB, toyVersion) -- internal modules @@ -75,6 +75,7 @@ import qualified Data.Test.PQueue (properties) import qualified Data.Test.Word.Encoding (properties) import qualified P2P.Test.Node (properties) import qualified P2P.Test.TaskQueue (properties) +import Chainweb.Version (withVersion) setTestLogLevel :: LogLevel -> IO () setTestLogLevel l = setEnv "CHAINWEB_TEST_LOG_LEVEL" (show l) @@ -95,13 +96,13 @@ main = do : nodeTestSuite rdb : suite rdb -- Coinbase Vuln Fix Tests are broken, waiting for Jose loadScript - where + where adj NoTimeout = Timeout (1_000_000 * 60 * 10) "10m" adj x = x mempoolTestSuite :: BlockHeaderDb -> BlockHeader -> TestTree mempoolTestSuite db genesisBlock = testGroup "Mempool Consensus Tests" - [Chainweb.Test.Mempool.Consensus.tests db genesisBlock] + [withVersion toyVersion $ Chainweb.Test.Mempool.Consensus.tests db genesisBlock] pactTestSuite :: RocksDb -> TestTree pactTestSuite rdb = testGroup "Chainweb-Pact Tests" From 8625383d2a7df3878a748cd286900bb3d20f2115 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 12:22:13 -0400 Subject: [PATCH 147/378] Fix loopy test versions --- src/Chainweb/Chainweb.hs | 2 +- src/Chainweb/Chainweb/PeerResources.hs | 3 +- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 4 +- src/Chainweb/Version.hs | 4 +- test/lib/Chainweb/Test/MultiNode.hs | 5 +- test/lib/Chainweb/Test/TestVersions.hs | 341 ++++++++++---------- test/multinode/MultiNodeNetworkTests.hs | 4 +- 7 files changed, 174 insertions(+), 189 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index ca0cf2d2e6..406a6faaae 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -213,7 +213,7 @@ withChainweb -> IO () withChainweb c logger rocksDb pactDbDir backupDir inner = withVersion (c ^. configChainwebVersion) $ - withPeerResources (view configP2p confWithBootstraps) logger $ \logger' peerRes -> + withPeerResources (view configP2p confWithBootstraps) logger $ \logger' peerRes -> do withSocket serviceApiPort serviceApiHost $ \serviceSock -> do let conf' = confWithBootstraps & set configP2p (_peerResConfig peerRes) diff --git a/src/Chainweb/Chainweb/PeerResources.hs b/src/Chainweb/Chainweb/PeerResources.hs index fe3ca1b6e1..43535622e0 100644 --- a/src/Chainweb/Chainweb/PeerResources.hs +++ b/src/Chainweb/Chainweb/PeerResources.hs @@ -127,7 +127,6 @@ withPeerResources conf logger inner = withPeerSocket conf $ \(conf', sock) -> do withPeerDb_ conf' $ \peerDb -> do (!mgr, !counter) <- connectionManager peerDb withHost mgr conf' logger $ \conf'' -> do - peer <- unsafeCreatePeer $ _p2pConfigPeer conf'' let pinf = _peerInfo peer @@ -228,7 +227,7 @@ getHost mgr logger peers = do -- Allocate Socket withPeerSocket :: P2pConfiguration -> ((P2pConfiguration, Socket) -> IO a) -> IO a -withPeerSocket conf act = withSocket port interface $ \(p, s) -> +withPeerSocket conf act = withSocket port interface $ \(p, s) -> do act (set (p2pConfigPeer . peerConfigPort) p conf, s) where port = _peerConfigPort $ _p2pConfigPeer conf diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index f3643c8fe2..16a687f063 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -409,8 +409,8 @@ withTableExistenceCheck tableName action = do case tableStatus of TableDoesNotExist -> liftGas $ throwDbOpErrorGasM $ Pact.NoSuchTable tableName TableCreationPending -> return Nothing - TableExists -> liftIO (putStrLn "WAT1") >> error (sshow err) - Left err -> liftIO (putStrLn "WAT2") >> error (sshow err) + TableExists -> error (sshow err) + Left err -> error (sshow err) Right result -> return (Just result) else do -- if we're rewound, we just check if the table exists first diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index f0a110e593..93795c97f8 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -327,7 +327,7 @@ instance FromJSON Fork where instance FromJSONKey Fork where fromJSONKey = FromJSONKeyTextParser $ either fail return . eitherFromText -data ForkHeight = ForkAtBlockHeight !BlockHeight | ForkAtGenesis | ForkNever +data ForkHeight = ForkAtBlockHeight BlockHeight | ForkAtGenesis | ForkNever deriving stock (Generic, Eq, Ord, Show) deriving anyclass (Hashable, NFData) @@ -401,7 +401,7 @@ makePrisms ''TxIdxInBlock -- sense one-offs which can't be expressed as upgrade transactions and must be -- preserved. data VersionQuirks = VersionQuirks - { _quirkGasFees :: !(ChainMap (HashMap (BlockHeight, TxIdxInBlock) Gas)) + { _quirkGasFees :: (ChainMap (HashMap (BlockHeight, TxIdxInBlock) Gas)) } deriving stock (Show, Eq, Ord, Generic) deriving anyclass (NFData) diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index a91e943bda..a760652b84 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -92,6 +92,7 @@ import Data.Set qualified as Set import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding qualified as T +import Data.Text.IO qualified as T import Database.SQLite3.Direct (Database) import GHC.Generics import Numeric.Natural @@ -236,7 +237,7 @@ multiNode loglevel write bootstrapPeerInfoVar conf rdb pactDbDir nid inner = do inner nid cw where logger :: GenericLogger - logger = addLabel ("node", toText nid) $ genericLogger loglevel write + logger = addLabel ("node", toText nid) $ genericLogger loglevel T.putStrLn namespacedNodeRocksDb = rdb { _rocksDbNamespace = T.encodeUtf8 $ toText nid } @@ -627,7 +628,7 @@ test test loglevel n seconds rdb pactDbDir step = do -- Count log messages and only print the first 60 messages let tastylog = step . T.unpack - let logFun = tastylog + let logFun = T.putStrLn maxLogMsgs = 60 var <- newMVar (0 :: Int) let countedLog msg = modifyMVar_ var $ \c -> force (succ c) <$ diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index 69cea4a2b4..c298c3eca9 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -7,6 +7,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE RankNTypes #-} +{-# OPTIONS_GHC -Wno-missing-fields #-} module Chainweb.Test.TestVersions ( barebonesTestVersion @@ -18,7 +19,6 @@ module Chainweb.Test.TestVersions , timedConsensusVersion , instantCpmTestVersion , checkpointerTestVersion - , testVersions ) where import Control.Lens hiding (elements) @@ -52,6 +52,7 @@ import P2P.Peer import Chainweb.Payload (PayloadWithOutputs_(_payloadWithOutputsPayloadHash), PayloadWithOutputs) import qualified Pact.Core.Names as Pact import qualified Pact.Core.Gas as Pact +import qualified Data.List as L testBootstrapPeerInfos :: PeerInfo testBootstrapPeerInfos = @@ -73,78 +74,27 @@ testBootstrapPeerInfos = } } -type VersionBuilder = HasVersion => ChainwebVersion - --- | Executes a `VersionBuilder` to build a `ChainwebVersion`, by taking its --- fixed point. Additionally registers it in the global version registry. -buildTestVersion :: VersionBuilder -> ChainwebVersion -buildTestVersion f = - v - where - v = withVersion v f -{-# noinline buildTestVersion #-} - --- | All testing `ChainwebVersion`s *must* have unique names and *must* be --- included in this list to be assigned a version code, and also registered via --- `buildTestVersion` into the global version registry. Failure to do so will --- result in runtime errors from `Chainweb.Version.Registry`. -testVersions :: [ChainwebVersionName] -testVersions = _versionName <$> concat - -- [ [ fastForkingCpmTestVersion (knownChainGraph g) - -- | g :: KnownGraph <- [minBound..maxBound] - -- ] - -- , [ slowForkingCpmTestVersion (knownChainGraph g) - -- | g :: KnownGraph <- [minBound..maxBound] - -- ] - [ [ barebonesTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - -- , [ noBridgeCpmTestVersion (knownChainGraph g) - -- | g :: KnownGraph <- [minBound..maxBound] - -- ] - , [ timedConsensusVersion (knownChainGraph g1) (knownChainGraph g2) - | g1 :: KnownGraph <- [minBound..maxBound] - , g2 :: KnownGraph <- [minBound..maxBound] - ] - , [ quirkedGasInstantCpmTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - , [ quirkedGasPact5InstantCpmTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - , [ instantCpmTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - -- , [ pact5InstantCpmTestVersion (knownChainGraph g) - -- | g :: KnownGraph <- [minBound..maxBound] - -- ] - , [ checkpointerTestVersion (knownChainGraph g) - | g :: KnownGraph <- [minBound..maxBound] - ] - ] - -- | Details common to all test versions thus far. --- Using this, a `ChainwebVersion`'s `versionCode` is set to the version's --- index in `testVersions`, to ensure that all test versions have unique codes --- in the global version registry in `Chainweb.Version.Registry`. -testVersionTemplate :: VersionBuilder -testVersionTemplate = implicitVersion - & versionCode .~ ChainwebVersionCode (int (fromJuste $ List.elemIndex (_versionName implicitVersion) testVersions) + 0x80000000) - & versionHeaderBaseSizeBytes .~ 318 - 110 - & versionWindow .~ WindowWidth 120 - & versionMaxBlockGasLimit .~ Bottom (minBound, Just 2_000_000) - & versionBootstraps .~ [testBootstrapPeerInfos] - & versionVerifierPluginNames .~ onAllChains (Bottom (minBound, mempty)) - & versionServiceDate .~ Nothing +testVersionTemplate :: Rule BlockHeight ChainGraph -> ChainwebVersion +testVersionTemplate gs = ChainwebVersion + { _versionCode = ChainwebVersionCode 0x80000000 + , _versionGraphs = gs + , _versionHeaderBaseSizeBytes = 318 - 110 + , _versionWindow = WindowWidth 120 + , _versionMaxBlockGasLimit = Bottom (minBound, Just 2_000_000) + , _versionBootstraps = [testBootstrapPeerInfos] + , _versionVerifierPluginNames = (Bottom (minBound, mempty) <$ cids) + , _versionServiceDate = Nothing + } + where cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs -- | A test version without Pact or PoW, with only one chain graph. barebonesTestVersion :: ChainGraph -> ChainwebVersion -barebonesTestVersion g = buildTestVersion $ - testVersionTemplate +barebonesTestVersion g = + testVersionTemplate gs & versionWindow .~ WindowWidth 120 & versionBlockDelay .~ BlockDelay 1_000_000 & versionName .~ ChainwebVersionName ("test-" <> toText g) - & versionGraphs .~ Bottom (minBound, g) & versionCheats .~ VersionCheats { _disablePow = True , _fakeFirstEpochStart = True @@ -155,126 +105,151 @@ barebonesTestVersion g = buildTestVersion $ , _disablePeerValidation = True } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onAllChains $ _payloadWithOutputsPayloadHash emptyPayload - , _genesisBlockTarget = onAllChains maxTarget - , _genesisTime = onAllChains $ BlockCreationTime epoch + { _genesisBlockPayload = _payloadWithOutputsPayloadHash emptyPayload <$ cids + , _genesisBlockTarget = maxTarget <$ cids + , _genesisTime = BlockCreationTime epoch <$ cids + } + & versionForks .~ HM.fromList [ (f, ForkAtGenesis <$ cids) | f <- [minBound..maxBound] ] + & versionQuirks .~ VersionQuirks + { _quirkGasFees = HM.empty <$ cids } - & versionForks .~ HM.fromList [ (f, onAllChains ForkAtGenesis) | f <- [minBound..maxBound] ] - & versionQuirks .~ noQuirks - & versionUpgrades .~ onAllChains HM.empty + & versionUpgrades .~ (HM.empty <$ cids) + where + gs = Bottom (minBound, g) + cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs -- | A test version without Pact or PoW, with a chain graph upgrade at block height 8. timedConsensusVersion :: ChainGraph -> ChainGraph -> ChainwebVersion -timedConsensusVersion g1 g2 = buildTestVersion $ - testVersionTemplate - & versionName .~ ChainwebVersionName ("timedConsensus-" <> toText g1 <> "-" <> toText g2) - & versionBlockDelay .~ BlockDelay 1_000_000 - & versionWindow .~ WindowWidth 120 - & versionForks .~ tabulateHashMap (\case - SkipTxTimingValidation -> onAllChains $ ForkAtBlockHeight (BlockHeight 2) - -- pact is disabled, we don't care about pact forks - _ -> onAllChains ForkAtGenesis - ) - & versionQuirks .~ noQuirks - & versionUpgrades .~ onAllChains HM.empty - & versionGraphs .~ (BlockHeight 8, g2) `Above` Bottom (minBound, g1) - & versionCheats .~ VersionCheats - { _disablePow = True - , _fakeFirstEpochStart = True - , _disablePact = True - } - & versionDefaults .~ VersionDefaults - { _disableMempoolSync = True - , _disablePeerValidation = True - } - & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ [] -- TODO: PP - -- (unsafeChainId 0, TN0.payloadBlock) : - -- [(n, TNN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` chainIds v)] - , _genesisBlockTarget = onAllChains maxTarget - , _genesisTime = onAllChains $ BlockCreationTime epoch - } +timedConsensusVersion g1 g2 = + testVersionTemplate gs + & versionName .~ ChainwebVersionName ("timedConsensus-" <> toText g1 <> "-" <> toText g2) + & versionBlockDelay .~ BlockDelay 1_000_000 + & versionWindow .~ WindowWidth 120 + & versionForks .~ tabulateHashMap (\case + SkipTxTimingValidation -> ForkAtBlockHeight (BlockHeight 2) <$ cids + -- pact is disabled, we don't care about pact forks + _ -> ForkAtGenesis <$ cids + ) + & versionQuirks .~ VersionQuirks + { _quirkGasFees = HM.empty <$ cids + } + & versionUpgrades .~ (HM.empty <$ cids) + & versionCheats .~ VersionCheats + { _disablePow = True + , _fakeFirstEpochStart = True + , _disablePact = True + } + & versionDefaults .~ VersionDefaults + { _disableMempoolSync = True + , _disablePeerValidation = True + } + & versionGenesis .~ VersionGenesis + { _genesisBlockPayload = onChains $ + (unsafeChainId 0, _payloadWithOutputsPayloadHash $ IN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash INN.payloadBlock) + | n <- unsafeChainId 0 `L.delete` fmap fst (itoList cids) + ] + , _genesisBlockTarget = maxTarget <$ cids + , _genesisTime = BlockCreationTime epoch <$ cids + } + & versionPayloadProviderTypes .~ (PactProvider <$ cids) + where + gs = (BlockHeight 8, g2) `Above` Bottom (minBound, g1) + cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs -- | A test version without Pact or PoW. checkpointerTestVersion :: ChainGraph -> ChainwebVersion -checkpointerTestVersion g1 = buildTestVersion $ - testVersionTemplate - & versionName .~ ChainwebVersionName ("pact5-checkpointertest-" <> toText g1) - & versionBlockDelay .~ BlockDelay 1_000_000 - & versionWindow .~ WindowWidth 120 - & versionForks .~ tabulateHashMap (\case - SkipTxTimingValidation -> onAllChains $ ForkAtBlockHeight (BlockHeight 2) - -- pact is disabled, we don't care about pact forks - _ -> onAllChains ForkAtGenesis - ) - & versionQuirks .~ noQuirks - & versionUpgrades .~ onAllChains HM.empty - & versionGraphs .~ Bottom (minBound, g1) - & versionCheats .~ VersionCheats - { _disablePow = True - , _fakeFirstEpochStart = True - , _disablePact = True - } - & versionDefaults .~ VersionDefaults - { _disableMempoolSync = True - , _disablePeerValidation = True - } - & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains [ (n, _payloadWithOutputsPayloadHash emptyPayload) | n <- HS.toList chainIds ] - , _genesisBlockTarget = onAllChains maxTarget - , _genesisTime = onAllChains $ BlockCreationTime epoch - } - & versionPayloadProviderTypes .~ onAllChains PactProvider +checkpointerTestVersion g1 = + testVersionTemplate gs + & versionName .~ ChainwebVersionName ("pact5-checkpointertest-" <> toText g1) + & versionBlockDelay .~ BlockDelay 1_000_000 + & versionWindow .~ WindowWidth 120 + & versionForks .~ tabulateHashMap (\case + SkipTxTimingValidation -> ForkAtBlockHeight (BlockHeight 2) <$ cids + -- pact is disabled, we don't care about pact forks + _ -> ForkAtGenesis <$ cids + ) + & versionQuirks .~ VersionQuirks + { _quirkGasFees = HM.empty <$ cids + } + & versionUpgrades .~ (HM.empty <$ cids) + & versionCheats .~ VersionCheats + { _disablePow = True + , _fakeFirstEpochStart = True + , _disablePact = True + } + & versionDefaults .~ VersionDefaults + { _disableMempoolSync = True + , _disablePeerValidation = True + } + & versionGenesis .~ VersionGenesis + { _genesisBlockPayload = onChains + [ (n, _payloadWithOutputsPayloadHash emptyPayload) + | n <- fst <$> itoList cids + ] + , _genesisBlockTarget = maxTarget <$ cids + , _genesisTime = BlockCreationTime epoch <$ cids + } + & versionPayloadProviderTypes .~ (PactProvider <$ cids) + where + gs = Bottom (minBound, g1) + cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs -- | A family of versions each with Pact enabled and PoW disabled. -cpmTestVersion :: ChainGraph -> VersionBuilder -cpmTestVersion g = - testVersionTemplate - & versionWindow .~ WindowWidth 120 - & versionBlockDelay .~ BlockDelay (Micros 100_000) - & versionGraphs .~ Bottom (minBound, g) - & versionCheats .~ VersionCheats - { _disablePow = True - , _fakeFirstEpochStart = True - , _disablePact = False - } - & versionDefaults .~ VersionDefaults - { _disableMempoolSync = False - , _disablePeerValidation = True - } - & versionUpgrades .~ onAllChains mempty - & versionPayloadProviderTypes .~ onAllChains PactProvider +cpmTestVersion :: Rule BlockHeight ChainGraph -> ChainwebVersion +cpmTestVersion gs = + testVersionTemplate gs + & versionWindow .~ WindowWidth 120 + & versionBlockDelay .~ BlockDelay (Micros 100_000) + & versionCheats .~ VersionCheats + { _disablePow = True + , _fakeFirstEpochStart = True + , _disablePact = False + } + & versionDefaults .~ VersionDefaults + { _disableMempoolSync = False + , _disablePeerValidation = True + } + & versionUpgrades .~ (mempty <$ cids) + & versionPayloadProviderTypes .~ (PactProvider <$ cids) + where + cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled, -- and with a gas fee quirk. quirkedGasInstantCpmTestVersion :: ChainGraph -> ChainwebVersion -quirkedGasInstantCpmTestVersion g = buildTestVersion $ - cpmTestVersion g +quirkedGasInstantCpmTestVersion g = + cpmTestVersion gs & versionName .~ ChainwebVersionName ("quirked-instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> onAllChains ForkAtGenesis) + _ -> ForkAtGenesis <$ cids) & versionQuirks .~ VersionQuirks { _quirkGasFees = onChain (unsafeChainId 0) $ HM.singleton (BlockHeight 2, TxBlockIdx 0) (Pact.Gas 1) } & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ [] -- TODO: PP - -- (unsafeChainId 0, IN0.payloadBlock) : - -- [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = onAllChains maxTarget - , _genesisTime = onAllChains $ BlockCreationTime epoch + { _genesisBlockPayload = onChains $ + (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : + [ (n, _payloadWithOutputsPayloadHash INN.payloadBlock) + | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g) + ] + , _genesisBlockTarget = maxTarget <$ cids + , _genesisTime = BlockCreationTime epoch <$ cids } - & versionUpgrades .~ onAllChains mempty - & versionVerifierPluginNames .~ onAllChains (Bottom (minBound, mempty)) + & versionUpgrades .~ (mempty <$ cids) + & versionVerifierPluginNames .~ (Bottom (minBound, mempty) <$ cids) + where + gs = Bottom (minBound, g) + cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled, -- and with a gas fee quirk. quirkedGasPact5InstantCpmTestVersion :: ChainGraph -> ChainwebVersion -quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ - cpmTestVersion g +quirkedGasPact5InstantCpmTestVersion g = + cpmTestVersion gs & versionName .~ ChainwebVersionName ("quirked-pact5-instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> onAllChains ForkAtGenesis) + _ -> ForkAtGenesis <$ cids) & versionQuirks .~ VersionQuirks { _quirkGasFees = onChain (unsafeChainId 0) $ HM.singleton (BlockHeight 1, TxBlockIdx 0) (Pact.Gas 1) @@ -283,65 +258,73 @@ quirkedGasPact5InstantCpmTestVersion g = buildTestVersion $ { _genesisBlockPayload = onChains $ (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : [(n, _payloadWithOutputsPayloadHash INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = onAllChains maxTarget - , _genesisTime = onAllChains $ BlockCreationTime epoch + , _genesisBlockTarget = maxTarget <$ cids + , _genesisTime = BlockCreationTime epoch <$ cids } - & versionUpgrades .~ onAllChains mempty - & versionVerifierPluginNames .~ onAllChains (Bottom (minBound, mempty)) + & versionUpgrades .~ (mempty <$ cids) + & versionVerifierPluginNames .~ (Bottom (minBound, mempty) <$ cids) + where + gs = Bottom (minBound, g) + cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled -- at genesis EXCEPT Pact 5. instantCpmTestVersion :: ChainGraph -> ChainwebVersion -instantCpmTestVersion g = buildTestVersion $ - cpmTestVersion g +instantCpmTestVersion g = + cpmTestVersion gs & versionName .~ ChainwebVersionName ("instant-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - _ -> onAllChains ForkAtGenesis + _ -> ForkAtGenesis <$ cids ) - & versionQuirks .~ noQuirks + & versionQuirks .~ VersionQuirks + { _quirkGasFees = mempty <$ cids + } & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ (unsafeChainId 0, _payloadWithOutputsPayloadHash IN0.payloadBlock) : [(n, _payloadWithOutputsPayloadHash INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = onAllChains maxTarget - , _genesisTime = onAllChains $ BlockCreationTime epoch + , _genesisBlockTarget = maxTarget <$ cids + , _genesisTime = BlockCreationTime epoch <$ cids } - & versionUpgrades .~ onAllChains mempty - & versionVerifierPluginNames .~ onAllChains + & versionUpgrades .~ ChainMap mempty + & versionVerifierPluginNames .~ (Bottom ( minBound , Set.fromList $ map Pact.VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] ) - ) + <$ cids) + where + gs = Bottom (minBound, g) + cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs -- -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled -- -- at genesis. We also have an upgrade after genesis that redeploys Coin v5 as -- -- a Pact 5 module. -- pact5SlowCpmTestVersion :: ChainGraph -> ChainwebVersion --- pact5SlowCpmTestVersion g = buildTestVersion $ \v -> v +-- pact5SlowCpmTestVersion g = -- & cpmTestVersion g -- & versionName .~ ChainwebVersionName ("pact5-slow-CPM-" <> toText g) -- & versionForks .~ tabulateHashMap (\case -- -- genesis blocks are not ever run with Pact 5 -- Pact5Fork -> onChains [ (cid, ForkAtBlockHeight (succ $ genesisBlockHeight v cid)) | cid <- HS.toList $ graphChainIds g ] -- -- SPV Bridge is not in effect for Pact 5 yet. --- SPVBridge -> onAllChains ForkNever --- _ -> onAllChains ForkAtGenesis +-- SPVBridge -> ChainMap ForkNever +-- _ -> ChainMap ForkAtGenesis -- ) -- & versionQuirks .~ noQuirks -- & versionGenesis .~ VersionGenesis -- { _genesisBlockPayload = onChains $ [] -- TODO: PP -- -- (unsafeChainId 0, IN0.payloadBlock) : -- -- [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] --- , _genesisBlockTarget = onAllChains maxTarget --- , _genesisTime = onAllChains $ BlockCreationTime epoch +-- , _genesisBlockTarget = ChainMap maxTarget +-- , _genesisTime = ChainMap $ BlockCreationTime epoch -- } -- & versionUpgrades .~ indexByForkHeights v -- -- TODO: PP --- -- [ (Pact5Fork, onAllChains (Pact5Upgrade (List.map pactTxFrom4To5 CoinV6.transactions))) +-- -- [ (Pact5Fork, ChainMap (Pact5Upgrade (List.map pactTxFrom4To5 CoinV6.transactions))) -- [ -- ] --- & versionVerifierPluginNames .~ onAllChains +-- & versionVerifierPluginNames .~ ChainMap -- (Bottom -- ( minBound -- , Set.fromList $ map Pact.VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] diff --git a/test/multinode/MultiNodeNetworkTests.hs b/test/multinode/MultiNodeNetworkTests.hs index bd99e7f52e..57b5a3487d 100644 --- a/test/multinode/MultiNodeNetworkTests.hs +++ b/test/multinode/MultiNodeNetworkTests.hs @@ -21,6 +21,7 @@ import System.LogLevel import Test.Tasty import Test.Tasty.HUnit import qualified Chainweb.Test.MultiNode +import Chainweb.Version (withVersion) main :: IO () main = defaultMain suite @@ -34,7 +35,8 @@ suite = independentSequentialTestGroup "MultiNodeNetworkTests" [ testCaseSteps "ConsensusNetwork - TimedConsensus - 10 nodes - 30 seconds" $ \step -> withTempRocksDb "multinode-tests-timedconsensus-petersen-twenty-rocks" $ \rdb -> withSystemTempDirectory "multinode-tests-timedconsensus-petersen-twenty-pact" $ \pactDbDir -> - Chainweb.Test.MultiNode.test loglevel (timedConsensusVersion petersenChainGraph twentyChainGraph) 10 30 rdb pactDbDir step + withVersion (timedConsensusVersion petersenChainGraph twentyChainGraph) $ + Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step -- , testCaseSteps "ConsensusNetwork - InstantTimedCPM singleChainGraph - 10 nodes - 30 seconds" $ \step -> -- withTempRocksDb "multinode-tests-instantcpm-single-rocks" $ \rdb -> -- withSystemTempDirectory "multinode-tests-instantcpm-single-pact" $ \pactDbDir -> From c6ba1ba93f095f81890f42b7979549737a039ebf Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 14:09:04 -0400 Subject: [PATCH 148/378] Fix mining coordination to deal with chain graph upgrades properly Change-Id: Id00000008b60d15828a2d14317b6859d90e4dd0b --- src/Chainweb/Miner/Coordinator.hs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 90f0997620..33c9568e8a 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -277,10 +277,10 @@ updateForCut -> Cut -> IO () updateForCut lf hdb ms c = do - iforM_ ms $ \cid var -> - forChain cid var + forM_ (HM.keys (c ^. cutMap)) forChain where - forChain cid var = do + forChain cid = do + let var = ms ^?! atChain cid maybeNewParents <- workParents hdb c cid atomically $ do maybeOldParentState <- readTVar var @@ -472,15 +472,15 @@ runCoordination mr = do -- STM variable, too. -- TODO: is there still? - -- FIXME: this is probably more aggressive than needed initializeState = do lf Info $ "initialize mining state" - forConcurrently_ (itoList caches) $ \(cid, cache) -> do + curCut <- _cut $ cdb + forConcurrently_ (HM.keys (curCut ^. cutMap)) $ \cid -> do + let cache = caches ^?! atChain cid lf Info $ "initialize mining state for chain " <> brief cid pld <- withProvider cid latestPayloadIO lf Info $ "got latest payload for chain " <> brief cid insertIO cache pld - curCut <- _cut $ cdb updateForCut lf f state curCut lf Info "done initializing mining state for all chains" @@ -563,7 +563,7 @@ awaitEvent cdb caches c p = -- 4. some payload providers are very slow in producing new payloads. -- randomWork :: HasVersion => LogFunction -> PayloadCaches -> ChainMap (TVar (Maybe ParentState)) -> IO MiningWork -randomWork logFun caches state = do +randomWork logFun caches parentStateVars = do -- Pick a random chain. -- @@ -577,7 +577,7 @@ randomWork logFun caches state = do -- that has work ready. We could prune the random range and search space, -- but at this, point the slight, temporary overhead seems acceptable. -- - n <- randomRIO (0, length state) + n <- randomRIO (0, length parentStateVars) -- NOTE: it is tempting to search for a matching chain within a single STM -- transaction. However, that is problematic: the search restarts when the @@ -590,7 +590,7 @@ randomWork logFun caches state = do -- towards chains for which block are produced more quickly, but we think, -- that it is negligible. -- - let (s0, s1) = splitAt n (itoList state) + let (s0, s1) = splitAt n (itoList parentStateVars) go (s1 <> s0) where awaitWorkReady :: ChainId -> TVar (Maybe ParentState) -> STM (WorkParents, NewPayload) @@ -632,7 +632,7 @@ randomWork logFun caches state = do -- timeoutVar <- registerDelay (int staleMiningStateDelay) w <- atomically $ - Right <$> msum (imap awaitWorkReady state) <|> awaitTimeout timeoutVar + Right <$> msum (imap awaitWorkReady parentStateVars) <|> awaitTimeout timeoutVar case w of Right (ps, npld) -> do ct <- BlockCreationTime <$> getCurrentTimeIntegral From 2e1faad050cf1ac4b044b6cd658a99041c77a339 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 14:09:04 -0400 Subject: [PATCH 149/378] Use more debug and less info logging in miner coordinator Change-Id: Id00000005ff623054b0e5d209928232292d6ebe6 --- src/Chainweb/Miner/Coordinator.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 33c9568e8a..3756746d32 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -453,7 +453,7 @@ runCoordination mr = do withProvider cid $ \provider -> runForever lf label $ do payloadStream provider - & S.chain (\_ -> lf Info $ "update cache on chain " <> toText cid) + & S.chain (\_ -> lf Debug $ "update cache on chain " <> toText cid) & S.mapM_ (insertIO cache) where label = "miningCoordination.updateCache." <> toText cid @@ -461,9 +461,9 @@ runCoordination mr = do -- Update the work state -- updateWork = runForever lf "miningCoordination" $ do - lf Info "start updateWork event stream" + lf Debug "start updateWork event stream" eventStream cdb caches - & S.chain (\e -> lf Info $ "coordination event: " <> brief e) + & S.chain (\e -> lf Debug $ "coordination event: " <> brief e) & S.mapM_ \case CutEvent c -> updateForCut lf f state c NewPayloadEvent _ -> return () @@ -473,16 +473,16 @@ runCoordination mr = do -- TODO: is there still? initializeState = do - lf Info $ "initialize mining state" + lf Debug $ "initialize mining state" curCut <- _cut $ cdb forConcurrently_ (HM.keys (curCut ^. cutMap)) $ \cid -> do let cache = caches ^?! atChain cid - lf Info $ "initialize mining state for chain " <> brief cid + lf Debug $ "initialize mining state for chain " <> brief cid pld <- withProvider cid latestPayloadIO - lf Info $ "got latest payload for chain " <> brief cid + lf Debug $ "got latest payload for chain " <> brief cid insertIO cache pld updateForCut lf f state curCut - lf Info "done initializing mining state for all chains" + lf Debug "done initializing mining state for all chains" -- | Note that this stream is lossy. It always delivers the latest available -- item and skips over any previous items that have not been consumed. @@ -648,7 +648,7 @@ randomWork logFun caches parentStateVars = do logFun @T.Text Debug $ "randomWork: picked chain " <> brief cid return $ newWork ct parents (_newPayloadBlockPayloadHash payload) Nothing -> do - logFun @T.Text Info $ "randomWork: not ready for " <> brief cid + logFun @T.Text Debug $ "randomWork: not ready for " <> brief cid go t awaitTimeout var = do From 0830eb43928ac67b9e540abecd80014d63a6266a Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 20:04:29 -0400 Subject: [PATCH 150/378] Start and sync payload providers for future chains Change-Id: Id000000071126e9351ea71f9a02ac92af035845d --- src/Chainweb/Chainweb.hs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 406a6faaae..1e54b07cfe 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -519,7 +519,10 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir synchronizeProviders :: WebBlockHeaderDb -> ChainMap ConfiguredPayloadProvider -> Cut -> IO () synchronizeProviders wbh providers c = do - mapConcurrently_ syncOne (_cutHeaders c) + let startHeaders = HM.unionWith (\startHeader _genesisHeader -> startHeader) + (_cutHeaders c) + (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) + mapConcurrently_ syncOne startHeaders where syncOne hdr = forM_ (providers ^? atChain (_chainId hdr)) $ \case ConfiguredPayloadProvider provider -> do From 6bdf654551e48e2159586adecf247f7080c9a518 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 22:42:17 -0400 Subject: [PATCH 151/378] Fix consensusState function to work for future chains Change-Id: Id0000000151078b0a82a6c4f23a05ef30688ce37 --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 22eddcfce8..e812f5d999 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -214,8 +214,9 @@ consensusState wdb hdr = do } where WindowWidth w = _versionWindow implicitVersion - finalHeight = int @Int @_ $ max 0 (int height - int w * 4) - safeHeight = int @Int @_ $ max 0 (int height - 6 * int diam) + cid = _chainId hdr + finalHeight = int @Int @_ $ max (int $ genesisHeight cid) (int height - int w * 4) + safeHeight = int @Int @_ $ max (int $ genesisHeight cid) (int height - 6 * int diam) height = view blockHeight hdr diam = diameterAt height From 3391c0642d4bc33ffb0ae8a5a5437dd5eabf7dcc Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 21 May 2025 14:49:56 -0400 Subject: [PATCH 152/378] Move Chainweb.Cut code to Chainweb.Cut.Create Change-Id: Id000000022763a2b1146bc6db6c4dda6fb21f309 --- src/Chainweb/Cut.hs | 477 ----------------------------------- src/Chainweb/Cut/Create.hs | 499 ++++++++++++++++++++++++++++++++++++- 2 files changed, 493 insertions(+), 483 deletions(-) diff --git a/src/Chainweb/Cut.hs b/src/Chainweb/Cut.hs index 68204bfcf7..d1946f8734 100644 --- a/src/Chainweb/Cut.hs +++ b/src/Chainweb/Cut.hs @@ -59,10 +59,6 @@ module Chainweb.Cut , cutAdjPairs , cutAdjs , lookupCutM -, forkDepth -, limitCut -, tryLimitCut -, limitCutHeaders , unsafeMkCut , chainHeights @@ -78,23 +74,6 @@ module Chainweb.Cut , checkBraidingOfCutPair , isBraidingOfCutPair --- * Extending Cuts -, isMonotonicCutExtension -, monotonicCutExtension -, tryMonotonicCutExtension - --- * Join -, Join(..) -, join -, applyJoin -, prioritizeHeavier -, prioritizeHeavier_ -, joinIntoHeavier -, joinIntoHeavier_ - --- * Meet -, meet - ) where import Control.DeepSeq @@ -110,14 +89,12 @@ import Data.Function import Data.Functor.Of import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS -import qualified Data.Heap as H import qualified Data.List as List import Data.Maybe (fromMaybe) import Data.Monoid import Data.Ord import Data.Text (Text) import qualified Data.Text as T -import Data.These import GHC.Generics (Generic) import GHC.Stack @@ -331,175 +308,6 @@ chainHeights :: Cut -> [BlockHeight] chainHeights = fmap (view blockHeight) . toList . _cutHeaders {-# INLINE chainHeights #-} --- -------------------------------------------------------------------------- -- --- Tools for Graph Transitions --- --- These functions are used to adjust the available chains during construction --- of new cuts: --- --- * 'monotonicCutExtension' and 'tryMonotonicCutExtension': extend the --- resulting cut with genesis headers of new chains. --- --- * 'limitCut': project out chains that don't exist in the result cut. --- --- * 'join' and 'applyJoin': add chains from both cuts to the input cuts, so --- that all chains are available in the join base and can be restored during --- 'applyJoin'; project out non-existing chains in the result. --- --- The graph is determined by the /minimum/ height of the blocks in the cut. --- --- The minimum is used to ensure that cuts in a new graph only exist when /all/ --- blocks are on the new cut. This means the new chains are included only if all --- old chains have transitioned to the minimum block height of the new graph. - -cutHeadersMinHeight :: HM.HashMap ChainId BlockHeader -> BlockHeight -cutHeadersMinHeight = minimum . fmap (view blockHeight) -{-# INLINE cutHeadersMinHeight #-} - --- | The function projects onto the chains available at the minimum block height --- in input headers. --- --- At a graph change chains are considered blocked until "all" chains performed --- the transition to the new graph. Thus, a block in the new graph has all of --- its dependencies available. --- --- This an internal function. The result is meaningful only if the input headers --- form a valid cut. In particular, the input must not be empty. --- -projectChains - :: HasVersion - => HM.HashMap ChainId BlockHeader - -> HM.HashMap ChainId BlockHeader -projectChains m = HM.intersection m - $ HS.toMap - $ chainIdsAt (cutHeadersMinHeight m) -{-# INLINE projectChains #-} - -cutProjectChains :: HasVersion => Cut -> Cut -cutProjectChains c = unsafeMkCut $ projectChains $ _cutHeaders c - where -{-# INLINE cutProjectChains #-} - --- | Extend the chains for the graph at the minimum block height of the input --- headers. If a header for a chain is missing the genesis block header for that --- chain is added. --- --- This an internal function. The result is meaningful only if the input headers --- form a valid cut. In particular, the input must not be empty. --- -extendChains - :: HasVersion - => HM.HashMap ChainId BlockHeader - -> HM.HashMap ChainId BlockHeader -extendChains m = HM.union m - $ genesisBlockHeadersAtHeight (cutHeadersMinHeight m) -{-# INLINE extendChains #-} - --- | This function adds all chains that are available in either of the input --- headers. It is assumed that both input header maps are contain headers for --- all chains for the graph at the respective minimum height of the headers. --- --- This function is used when dealing with joins that internally compute an --- intersection on the blocks on all chains, but the goal is to preserve blocks --- from all chains. --- --- This an internal function. The result is meaningful only if the input headers --- form a valid cut. In particular, the input must not be empty. --- -joinChains - :: HasVersion - => HM.HashMap ChainId BlockHeader - -> HM.HashMap ChainId BlockHeader - -> (HM.HashMap ChainId BlockHeader, HM.HashMap ChainId BlockHeader) -joinChains a b = (HM.union a c, HM.union b c) - where - c = genesisBlockHeader <$> a <> b -{-# INLINE joinChains #-} - --- -------------------------------------------------------------------------- -- --- Limit Cut Hashes By Height - --- | Find a `Cut` that is a predecessor of the given one, and that has a block --- height that is smaller or equal the given height. --- --- If the requested limit is larger or equal to the current height, the given --- cut is returned. --- --- Otherwise, the predecessor of the given cut at the given height on each chain --- is returned. --- -limitCut - :: (HasCallStack, HasVersion) - => WebBlockHeaderDb - -> BlockHeight - -- ^ upper bound for the block height of each chain. This is not a tight - -- bound. - -> Cut - -> IO Cut -limitCut wdb h c - | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = - return c - | otherwise = do - hdrs <- itraverse go $ view cutHeaders c - return $! unsafeMkCut $ projectChains $ HM.mapMaybe id hdrs - where - - go :: ChainId -> BlockHeader -> IO (Maybe BlockHeader) - go cid bh = do - if h >= view blockHeight bh - then return (Just bh) - else do - !db <- getWebBlockHeaderDb wdb cid - seekAncestor db bh (min (int $ view blockHeight bh) (int h)) - -- this is safe because it's guaranteed that the requested rank is - -- smaller then the block height of the argument - --- | Find a `Cut` that is a predecessor of the given one, and that has a block --- height that is as low as possible while not exceeding the given height and --- including all of the chains in the given cut. --- --- If the requested limit is larger or equal to the current height, the given --- cut is returned. --- -tryLimitCut - :: (HasCallStack, HasVersion) - => WebBlockHeaderDb - -> BlockHeight - -- upper bound for the block height of each chain. This is not a tight bound. - -> Cut - -> IO Cut -tryLimitCut wdb h c - | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = - return c - | otherwise = do - hdrs <- itraverse go $ view cutHeaders c - return $! unsafeMkCut hdrs - where - go :: ChainId -> BlockHeader -> IO BlockHeader - go cid bh = do - if h >= view blockHeight bh - then return bh - else do - !db <- getWebBlockHeaderDb wdb cid - -- this is safe because it's guaranteed that the requested rank is - -- smaller then the block height of the argument - let ancestorHeight = min (int $ view blockHeight bh) (int h) - if ancestorHeight <= fromIntegral (genesisHeight cid) - then return $ genesisBlockHeader cid - else fromJuste <$> seekAncestor db bh ancestorHeight - --- | The resulting headers are valid cut headers only if the input headers are --- valid cut headers, too. The inverse is not true. --- -limitCutHeaders - :: (HasCallStack, HasVersion) - => WebBlockHeaderDb - -> BlockHeight - -- ^ upper bound for the block height of each chain. This is not a tight bound. - -> HM.HashMap ChainId BlockHeader - -> IO (HM.HashMap ChainId BlockHeader) -limitCutHeaders whdb h ch = _cutHeaders <$> limitCut whdb h (unsafeMkCut ch) - -- -------------------------------------------------------------------------- -- -- Genesis Cut @@ -620,291 +428,6 @@ isBraidingOfCutPair a b = do || (view blockHeight a > view blockHeight b) && ab == Parent (view blockHash b) || (view blockHeight a < view blockHeight b) && True {- if same graph: ba == view blockHash a -} --- -------------------------------------------------------------------------- -- --- Extending Cuts - --- | Extends a Cut monotonically, i.e. the replaced block header is the parent --- of the added block header. --- --- Checks --- --- * block header is from the ChainGraph of the Cut --- * result has valid braiding --- * result is a cut --- * update is monotonic --- --- This includes a check that inductively maintains 'checkBraidingOfCut'. --- --- FIXME: this must conform with 'isBraidingOfCutPair'. Double check that we --- have test for this or check if the implementation can be shared. --- --- TODO: do we have to check that the correct graph is used? --- -isMonotonicCutExtension - :: (HasCallStack, HasVersion) - => MonadThrow m - => Cut - -> BlockHeader - -> m Bool -isMonotonicCutExtension c h = do - checkBlockHeaderGraph h - return $! monotonic && validBraiding - where - monotonic = view blockParent h == case c ^? ixg (_chainId h) . blockHash of - Nothing -> error $ T.unpack $ "isMonotonicCutExtension.monotonic: missing parent in cut. " <> encodeToText h - Just x -> Parent x - validBraiding = getAll $ ifoldMap - (\cid -> All . validBraidingCid cid) - (_getBlockHashRecord $ view blockAdjacentHashes h) - - validBraidingCid cid a - | Just b <- c ^? ixg cid = Parent (view blockHash b) == a || view blockParent b == a - | view blockHeight h == genesisHeight cid = a == genesisParentBlockHash cid - | otherwise = error $ T.unpack $ "isMonotonicCutExtension.validBraiding: missing adjacent parent on chain " <> toText cid <> " in cut. " <> encodeToText h - --- | Extend a cut with a block header. Throws 'InvalidCutExtension' if the block --- header isn't a monotonic cut extension. --- -monotonicCutExtension - :: (MonadThrow m, HasVersion) - => Cut - -> BlockHeader - -> m Cut -monotonicCutExtension c h = tryMonotonicCutExtension c h >>= \case - Nothing -> throwM $ InvalidCutExtension h - Just x -> return x - --- | Extend a cut with a block header. Returns 'Nothing' the block header isn't --- a monotonic cut extension. --- -tryMonotonicCutExtension - :: (MonadThrow m, HasVersion) - => Cut - -> BlockHeader - -> m (Maybe Cut) -tryMonotonicCutExtension c h = isMonotonicCutExtension c h >>= \case - False -> return Nothing - True -> return $! Just - $! unsafeMkCut - $ extendChains - $ set (ix' (_chainId h)) h - $ _cutHeaders c - --- -------------------------------------------------------------------------- -- --- Join - -type DiffItem a = These a a - -type JoinQueue a = H.Heap (H.Entry (BlockHeight, a) BlockHeader) - --- | This represents the Join of two cuts in an algorithmically convenient way. --- -data Join a = Join - { _joinBase :: !Cut - -- ^ the base of the join, the largest cut that is contained in both - -- cuts, or when viewed as sets, the intersection. - , _joinQueue :: !(JoinQueue a) - -- ^ a queue of block headers from both cuts that allows construct - -- the join cut from the join base. - } - --- | This computes the join for cuts across all chains. --- --- If you want to compute a join for cuts that include only a subset of all --- chains. --- -join - :: (Ord a, HasVersion) - => WebBlockHeaderDb - -> (DiffItem BlockHeader -> DiffItem (Maybe a)) - -> Cut - -> Cut - -> IO (Join a) -join wdb f = join_ wdb f `on` _cutHeaders - --- | This merges two maps from ChainIds to BlockHeaders such that the result is --- a Cut. Note, however, that the resulting cut contains only the chain ids from --- the intersection of the input maps. --- --- NOTE: For this to work as expected make sure that both inputs contain all --- chains that should be present in the output. --- --- Adds genesis blocks for chains that are not yet active. This purpose of this --- is to make sure that all chains of both inputs are preserved in the join, so --- that the result of the join contains all chains of the original cuts. --- Otherwise the join would contain only the intersection of all chains and any --- information/blocks in the other chains would be lost when applying the join. --- -join_ - :: forall a - . (Ord a, HasVersion) - => WebBlockHeaderDb - -> (DiffItem BlockHeader -> DiffItem (Maybe a)) - -> HM.HashMap ChainId BlockHeader - -> HM.HashMap ChainId BlockHeader - -> IO (Join a) -join_ wdb prioFun a b = do - (m, h) <- runStateT (HM.traverseWithKey f (HM.intersectionWith (,) a' b')) mempty - return $! Join (unsafeMkCut m) h - where - (a', b') = joinChains a b - - f - :: ChainId - -> (BlockHeader, BlockHeader) - -> StateT (JoinQueue a) IO BlockHeader - f cid (x, y) = do - !q <- get - db <- getWebBlockHeaderDb wdb cid - (q' :> !h) <- liftIO $ S.fold g q id $ branchDiff_ db x y - put q' - return h - - g :: JoinQueue a -> DiffItem BlockHeader -> JoinQueue a - g q x = foldl' maybeInsert q $ zip (biList x) (biList (prioFun x)) - - maybeInsert - :: H.Heap (H.Entry (BlockHeight, a) BlockHeader) - -> (BlockHeader, Maybe a) - -> H.Heap (H.Entry (BlockHeight, a) BlockHeader) - maybeInsert !q (_, Nothing) = q - maybeInsert !q (!h, (Just !p)) = H.insert (H.Entry (view blockHeight h, p) h) q - --- This can't fail because of missing dependencies. It can't fail because --- of conflict. --- --- Non-existing chains are stripped from the result. --- -applyJoin :: (MonadThrow m, HasVersion) => Join a -> m Cut -applyJoin m = cutProjectChains - <$> foldM - (\c b -> fromMaybe c <$> tryMonotonicCutExtension c (H.payload b)) - (_joinBase m) - (_joinQueue m) - --- | Merge two Cuts. If at least one of the input cuts had a valid braiding the --- result is guaranteed to have a valid braiding for all blocks included in cut --- and their ancestors. --- --- This is because the merge starts with the intersection of both cuts, using --- 'branchDiff_' on each chain, and constructs the merge cut using --- 'tryMonotonicCutExtension'. If one of the inputs is correctly braided, so is --- the intersection. 'tryMonotonicCutExtension' is guaranteed to maintain that --- property. --- --- Chains that aren't yet initialized are included in the join and later --- stripped from the result. --- --- If you want to compute a join for cuts that include only a subset of all --- chains, make sure that @genesisBlockHeaders v@ only returns genesis headers --- for those chains that you care about. --- -joinIntoHeavier - :: HasVersion - => WebBlockHeaderDb - -> Cut - -> Cut - -> IO Cut -joinIntoHeavier wdb = joinIntoHeavier_ wdb `on` _cutHeaders - --- | Chains that aren't yet initialized are included in the join and later --- stripped from the result. --- --- If you want to compute a join for cuts that include only a subset of all --- chains, make sure that @genesisBlockHeaders v@ only returns genesis headers --- for those chains that you care about. --- -joinIntoHeavier_ - :: HasVersion - => WebBlockHeaderDb - -> HM.HashMap ChainId BlockHeader - -> HM.HashMap ChainId BlockHeader - -> IO Cut -joinIntoHeavier_ wdb a b = do - m <- join_ wdb (prioritizeHeavier_ a b) a b - applyJoin m - -prioritizeHeavier :: Cut -> Cut -> DiffItem BlockHeader -> DiffItem (Maybe Int) -prioritizeHeavier = prioritizeHeavier_ `on` _cutHeaders - --- | Note: consider the weight of the recursive dependencies for the --- priority of a block header. For that we would have to annotate the --- block headers before putting them in the queue. To traverse only once, --- we'd have to traverse the zipped cuts by height and not by chainid, which --- could easily be done by merging/zipping the branch-diff streams. --- -prioritizeHeavier_ - :: Foldable f - => Eq (f BlockHeader) - => f BlockHeader - -> f BlockHeader - -> DiffItem BlockHeader - -> DiffItem (Maybe Int) -prioritizeHeavier_ a b = f - where - heaviest = maxBy (compare `on` weight) a b - w c = if c == heaviest then 0 else 1 - - f (This _) = This (Just $ w a) - f (That _) = That (Just $ w b) - f (These _ _) - | heaviest == a = These (Just 0) Nothing - | otherwise = These Nothing (Just 0) - - weight c = - ( sumOf (folded . blockWeight) c - -- first sort by weight - , sumOf (folded . blockHeight) c - -- for scenarios with trivial difficulty height is added as - -- secondary metrics - - -- NOTE: - -- We could consider prioritizing the latest block in the cut here as - -- first-level tie breaker. That would further incentivize miners to use - -- a block creation time that is close to the real world time (note that - -- blocks from the future are rejected, so post-dating blocks is risky - -- for miners.) - - , List.sort (toList c) - -- the block hashes of the cut are added as tie breaker in order - -- to guarantee commutativity. - -- - ) - --- -------------------------------------------------------------------------- -- --- Cut Meet - --- | Intersection of cuts --- -meet - :: HasVersion - => WebBlockHeaderDb - -> Cut - -> Cut - -> IO Cut -meet wdb a b = do - !r <- imapM f $ HM.intersectionWith (,) (_cutHeaders a) (_cutHeaders b) - return $! unsafeMkCut r - where - f !cid (!x, !y) = do - db <- getWebBlockHeaderDb wdb cid - forkEntry db x y - -forkDepth - :: HasVersion - => WebBlockHeaderDb - -> Cut - -> Cut - -> IO Natural -forkDepth wdb a b = do - m <- meet wdb a b - return $! int $ max (maxDepth m a) (maxDepth m b) - where - maxDepth l u = maximum $ HM.intersectionWith - (\x y -> view blockHeight y - view blockHeight x) - (_cutHeaders l) - (_cutHeaders u) - cutToTextShort :: Cut -> [Text] cutToTextShort c = [ blockHeaderShortDescription bh diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 112bfa9186..cc7f027fa3 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -50,6 +50,29 @@ module Chainweb.Cut.Create , cutExtensionAdjacentHashes , getCutExtension +-- * Limit cuts +, limitCut +, tryLimitCut +, limitCutHeaders + +-- * Predicates +, isMonotonicCutExtension +, monotonicCutExtension +, tryMonotonicCutExtension + +-- * Join +, Join(..) +, join +, applyJoin +, prioritizeHeavier +, prioritizeHeavier_ +, joinIntoHeavier +, joinIntoHeavier_ + +-- * Meet +, meet +, forkDepth + -- * WorkParents , WorkParents(..) , workParents @@ -76,18 +99,18 @@ module Chainweb.Cut.Create import Control.DeepSeq import Control.Lens -import Control.Monad +import Control.Monad hiding (join) import Control.Monad.Catch - +import Control.Monad.State.Strict import Data.ByteString.Short qualified as SB import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS +import Data.Heap qualified as H import Data.Text qualified as T - +import Data.These import GHC.Generics (Generic) import GHC.Stack - --- internal modules +import Numeric.Natural import Chainweb.BlockCreationTime import Chainweb.BlockHash @@ -100,11 +123,20 @@ import Chainweb.Cut import Chainweb.Cut.CutHashes import Chainweb.Difficulty import Chainweb.Parent -import Chainweb.PayloadProvider(EncodedPayloadData(..), EncodedPayloadOutputs) +import Chainweb.PayloadProvider (EncodedPayloadData (..), EncodedPayloadOutputs) import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Utils +import Chainweb.WebBlockHeaderDB +import Data.Monoid +import Chainweb.TreeDB ( branchDiff_, forkEntry, seekAncestor ) +import Data.Function +import qualified Streaming.Prelude as S +import Data.Bifoldable +import Data.Foldable +import Data.Maybe +import qualified Data.List as List -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes @@ -597,3 +629,458 @@ extendCut c ps s = do (bh,) <$> tryMonotonicCutExtension c bh where bh = newHeader ps s + +-- -------------------------------------------------------------------------- -- +-- Limit Cut Hashes By Height + +-- | Find a `Cut` that is a predecessor of the given one, and that has a block +-- height that is smaller or equal the given height. +-- +-- If the requested limit is larger or equal to the current height, the given +-- cut is returned. +-- +-- Otherwise, the predecessor of the given cut at the given height on each chain +-- is returned. +-- +limitCut + :: (HasCallStack, HasVersion) + => WebBlockHeaderDb + -> BlockHeight + -- ^ upper bound for the block height of each chain. This is not a tight + -- bound. + -> Cut + -> IO Cut +limitCut wdb h c + | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = + return c + | otherwise = do + hdrs <- itraverse go $ view cutHeaders c + return $! unsafeMkCut $ projectChains $ HM.mapMaybe id hdrs + where + + go :: ChainId -> BlockHeader -> IO (Maybe BlockHeader) + go cid bh = do + if h >= view blockHeight bh + then return (Just bh) + else do + !db <- getWebBlockHeaderDb wdb cid + seekAncestor db bh (min (int $ view blockHeight bh) (int h)) + -- this is safe because it's guaranteed that the requested rank is + -- smaller then the block height of the argument + +-- | Find a `Cut` that is a predecessor of the given one, and that has a block +-- height that is as low as possible while not exceeding the given height and +-- including all of the chains in the given cut. +-- +-- If the requested limit is larger or equal to the current height, the given +-- cut is returned. +-- +tryLimitCut + :: (HasCallStack, HasVersion) + => WebBlockHeaderDb + -> BlockHeight + -- upper bound for the block height of each chain. This is not a tight bound. + -> Cut + -> IO Cut +tryLimitCut wdb h c + | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = + return c + | otherwise = do + hdrs <- itraverse go $ view cutHeaders c + return $! unsafeMkCut hdrs + where + go :: ChainId -> BlockHeader -> IO BlockHeader + go cid bh = do + if h >= view blockHeight bh + then return bh + else do + !db <- getWebBlockHeaderDb wdb cid + -- this is safe because it's guaranteed that the requested rank is + -- smaller then the block height of the argument + let ancestorHeight = min (int $ view blockHeight bh) (int h) + if ancestorHeight <= fromIntegral (genesisHeight cid) + then return $ genesisBlockHeader cid + else fromJuste <$> seekAncestor db bh ancestorHeight + +-- | The resulting headers are valid cut headers only if the input headers are +-- valid cut headers, too. The inverse is not true. +-- +limitCutHeaders + :: (HasCallStack, HasVersion) + => WebBlockHeaderDb + -> BlockHeight + -- ^ upper bound for the block height of each chain. This is not a tight bound. + -> HM.HashMap ChainId BlockHeader + -> IO (HM.HashMap ChainId BlockHeader) +limitCutHeaders whdb h ch = _cutHeaders <$> limitCut whdb h (unsafeMkCut ch) + +-- -------------------------------------------------------------------------- -- +-- Tools for Graph Transitions +-- +-- These functions are used to adjust the available chains during construction +-- of new cuts: +-- +-- * 'monotonicCutExtension' and 'tryMonotonicCutExtension': extend the +-- resulting cut with genesis headers of new chains. +-- +-- * 'limitCut': project out chains that don't exist in the result cut. +-- +-- * 'join' and 'applyJoin': add chains from both cuts to the input cuts, so +-- that all chains are available in the join base and can be restored during +-- 'applyJoin'; project out non-existing chains in the result. +-- +-- The graph is determined by the /minimum/ height of the blocks in the cut. +-- +-- The minimum is used to ensure that cuts in a new graph only exist when /all/ +-- blocks are on the new cut. This means the new chains are included only if all +-- old chains have transitioned to the minimum block height of the new graph. + +cutHeadersMinHeight :: HM.HashMap ChainId BlockHeader -> BlockHeight +cutHeadersMinHeight = minimum . fmap (view blockHeight) +{-# INLINE cutHeadersMinHeight #-} + + +-- | The function projects onto the chains available at the minimum block height +-- in input headers. +-- +-- At a graph change chains are considered blocked until "all" chains performed +-- the transition to the new graph. Thus, a block in the new graph has all of +-- its dependencies available. +-- +-- This an internal function. The result is meaningful only if the input headers +-- form a valid cut. In particular, the input must not be empty. +-- +projectChains + :: HasVersion + => HM.HashMap ChainId BlockHeader + -> HM.HashMap ChainId BlockHeader +projectChains m = HM.intersection m + $ HS.toMap + $ chainIdsAt (cutHeadersMinHeight m) +{-# INLINE projectChains #-} + +cutProjectChains :: HasVersion => Cut -> Cut +cutProjectChains c = unsafeMkCut $ projectChains $ _cutHeaders c +{-# INLINE cutProjectChains #-} + +-- | Extend the chains for the graph at the minimum block height of the input +-- headers. If a header for a chain is missing the genesis block header for that +-- chain is added. +-- +-- This an internal function. The result is meaningful only if the input headers +-- form a valid cut. In particular, the input must not be empty. +-- +extendChains + :: HasVersion + => HM.HashMap ChainId BlockHeader + -> HM.HashMap ChainId BlockHeader +extendChains m = HM.union m + $ genesisBlockHeadersAtHeight (cutHeadersMinHeight m) +{-# INLINE extendChains #-} + +-- | This function adds all chains that are available in either of the input +-- headers. It is assumed that both input header maps are contain headers for +-- all chains for the graph at the respective minimum height of the headers. +-- +-- This function is used when dealing with joins that internally compute an +-- intersection on the blocks on all chains, but the goal is to preserve blocks +-- from all chains. +-- +-- This an internal function. The result is meaningful only if the input headers +-- form a valid cut. In particular, the input must not be empty. +-- +joinChains + :: HasVersion + => HM.HashMap ChainId BlockHeader + -> HM.HashMap ChainId BlockHeader + -> (HM.HashMap ChainId BlockHeader, HM.HashMap ChainId BlockHeader) +joinChains a b = (HM.union a c, HM.union b c) + where + c = genesisBlockHeader <$> a <> b +{-# INLINE joinChains #-} + +-- -------------------------------------------------------------------------- -- +-- Extending Cuts + +-- | Extends a Cut monotonically, i.e. the replaced block header is the parent +-- of the added block header. +-- +-- Checks +-- +-- * block header is from the ChainGraph of the Cut +-- * result has valid braiding +-- * result is a cut +-- * update is monotonic +-- +-- This includes a check that inductively maintains 'checkBraidingOfCut'. +-- +-- FIXME: this must conform with 'isBraidingOfCutPair'. Double check that we +-- have test for this or check if the implementation can be shared. +-- +-- TODO: do we have to check that the correct graph is used? +-- +isMonotonicCutExtension + :: (HasCallStack, HasVersion) + => MonadThrow m + => Cut + -> BlockHeader + -> m Bool +isMonotonicCutExtension c h = do + checkBlockHeaderGraph h + return $! monotonic && validBraiding + where + monotonic = view blockParent h == case c ^? ixg (_chainId h) . blockHash of + Nothing -> error $ T.unpack $ "isMonotonicCutExtension.monotonic: missing parent in cut. " <> encodeToText h + Just x -> Parent x + validBraiding = getAll $ ifoldMap + (\cid -> All . validBraidingCid cid) + (_getBlockHashRecord $ view blockAdjacentHashes h) + + validBraidingCid cid a + | Just b <- c ^? ixg cid = Parent (view blockHash b) == a || view blockParent b == a + | view blockHeight h == genesisHeight cid = a == genesisParentBlockHash cid + | otherwise = error $ T.unpack $ "isMonotonicCutExtension.validBraiding: missing adjacent parent on chain " <> toText cid <> " in cut. " <> encodeToText h + + +-- | Extend a cut with a block header. Throws 'InvalidCutExtension' if the block +-- header isn't a monotonic cut extension. +-- +monotonicCutExtension + :: (MonadThrow m, HasVersion) + => Cut + -> BlockHeader + -> m Cut +monotonicCutExtension c h = tryMonotonicCutExtension c h >>= \case + Nothing -> throwM $ InvalidCutExtension h + Just x -> return x + +-- | Extend a cut with a block header. Returns 'Nothing' the block header isn't +-- a monotonic cut extension. +-- +tryMonotonicCutExtension + :: (MonadThrow m, HasVersion) + => Cut + -> BlockHeader + -> m (Maybe Cut) +tryMonotonicCutExtension c h = isMonotonicCutExtension c h >>= \case + False -> return Nothing + True -> return $! Just + $! unsafeMkCut + $ extendChains + $ set (ix' (_chainId h)) h + $ _cutHeaders c + +-- -------------------------------------------------------------------------- -- +-- Join + +type DiffItem a = These a a + +type JoinQueue a = H.Heap (H.Entry (BlockHeight, a) BlockHeader) + +-- | This represents the Join of two cuts in an algorithmically convenient way. +-- +data Join a = Join + { _joinBase :: !Cut + -- ^ the base of the join, the largest cut that is contained in both + -- cuts, or when viewed as sets, the intersection. + , _joinQueue :: !(JoinQueue a) + -- ^ a queue of block headers from both cuts that allows construct + -- the join cut from the join base. + } + +-- | This computes the join for cuts across all chains. +-- +-- If you want to compute a join for cuts that include only a subset of all +-- chains. +-- +join + :: (Ord a, HasVersion) + => WebBlockHeaderDb + -> (DiffItem BlockHeader -> DiffItem (Maybe a)) + -> Cut + -> Cut + -> IO (Join a) +join wdb f = join_ wdb f `on` _cutHeaders + +-- | This merges two maps from ChainIds to BlockHeaders such that the result is +-- a Cut. Note, however, that the resulting cut contains only the chain ids from +-- the intersection of the input maps. +-- +-- NOTE: For this to work as expected make sure that both inputs contain all +-- chains that should be present in the output. +-- +-- Adds genesis blocks for chains that are not yet active. This purpose of this +-- is to make sure that all chains of both inputs are preserved in the join, so +-- that the result of the join contains all chains of the original cuts. +-- Otherwise the join would contain only the intersection of all chains and any +-- information/blocks in the other chains would be lost when applying the join. +-- +join_ + :: forall a + . (Ord a, HasVersion) + => WebBlockHeaderDb + -> (DiffItem BlockHeader -> DiffItem (Maybe a)) + -> HM.HashMap ChainId BlockHeader + -> HM.HashMap ChainId BlockHeader + -> IO (Join a) +join_ wdb prioFun a b = do + (m, h) <- runStateT (HM.traverseWithKey f (HM.intersectionWith (,) a' b')) mempty + return $! Join (unsafeMkCut m) h + where + (a', b') = joinChains a b + + f + :: ChainId + -> (BlockHeader, BlockHeader) + -> StateT (JoinQueue a) IO BlockHeader + f cid (x, y) = do + !q <- get + db <- getWebBlockHeaderDb wdb cid + (q' S.:> !h) <- liftIO $ S.fold g q id $ branchDiff_ db x y + put q' + return h + + g :: JoinQueue a -> DiffItem BlockHeader -> JoinQueue a + g q x = foldl' maybeInsert q $ zip (biList x) (biList (prioFun x)) + + maybeInsert + :: H.Heap (H.Entry (BlockHeight, a) BlockHeader) + -> (BlockHeader, Maybe a) + -> H.Heap (H.Entry (BlockHeight, a) BlockHeader) + maybeInsert !q (_, Nothing) = q + maybeInsert !q (!h, (Just !p)) = H.insert (H.Entry (view blockHeight h, p) h) q + +-- This can't fail because of missing dependencies. It can't fail because +-- of conflict. +-- +-- Non-existing chains are stripped from the result. +-- +applyJoin :: (MonadThrow m, HasVersion) => Join a -> m Cut +applyJoin m = cutProjectChains + <$> foldM + (\c b -> fromMaybe c <$> tryMonotonicCutExtension c (H.payload b)) + (_joinBase m) + (_joinQueue m) + +-- | Merge two Cuts. If at least one of the input cuts had a valid braiding the +-- result is guaranteed to have a valid braiding for all blocks included in cut +-- and their ancestors. +-- +-- This is because the merge starts with the intersection of both cuts, using +-- 'branchDiff_' on each chain, and constructs the merge cut using +-- 'tryMonotonicCutExtension'. If one of the inputs is correctly braided, so is +-- the intersection. 'tryMonotonicCutExtension' is guaranteed to maintain that +-- property. +-- +-- Chains that aren't yet initialized are included in the join and later +-- stripped from the result. +-- +-- If you want to compute a join for cuts that include only a subset of all +-- chains, make sure that @genesisBlockHeaders v@ only returns genesis headers +-- for those chains that you care about. +-- +joinIntoHeavier + :: HasVersion + => WebBlockHeaderDb + -> Cut + -> Cut + -> IO Cut +joinIntoHeavier wdb = joinIntoHeavier_ wdb `on` _cutHeaders + +-- | Chains that aren't yet initialized are included in the join and later +-- stripped from the result. +-- +-- If you want to compute a join for cuts that include only a subset of all +-- chains, make sure that @genesisBlockHeaders v@ only returns genesis headers +-- for those chains that you care about. +-- +joinIntoHeavier_ + :: HasVersion + => WebBlockHeaderDb + -> HM.HashMap ChainId BlockHeader + -> HM.HashMap ChainId BlockHeader + -> IO Cut +joinIntoHeavier_ wdb a b = do + m <- join_ wdb (prioritizeHeavier_ a b) a b + applyJoin m + +prioritizeHeavier :: Cut -> Cut -> DiffItem BlockHeader -> DiffItem (Maybe Int) +prioritizeHeavier = prioritizeHeavier_ `on` _cutHeaders + +-- | Note: consider the weight of the recursive dependencies for the +-- priority of a block header. For that we would have to annotate the +-- block headers before putting them in the queue. To traverse only once, +-- we'd have to traverse the zipped cuts by height and not by chainid, which +-- could easily be done by merging/zipping the branch-diff streams. +-- +prioritizeHeavier_ + :: Foldable f + => Eq (f BlockHeader) + => f BlockHeader + -> f BlockHeader + -> DiffItem BlockHeader + -> DiffItem (Maybe Int) +prioritizeHeavier_ a b = f + where + heaviest = maxBy (compare `on` weight) a b + w c = if c == heaviest then 0 else 1 + + f (This _) = This (Just $ w a) + f (That _) = That (Just $ w b) + f (These _ _) + | heaviest == a = These (Just 0) Nothing + | otherwise = These Nothing (Just 0) + + weight c = + ( sumOf (folded . blockWeight) c + -- first sort by weight + , sumOf (folded . blockHeight) c + -- for scenarios with trivial difficulty height is added as + -- secondary metrics + + -- NOTE: + -- We could consider prioritizing the latest block in the cut here as + -- first-level tie breaker. That would further incentivize miners to use + -- a block creation time that is close to the real world time (note that + -- blocks from the future are rejected, so post-dating blocks is risky + -- for miners.) + + , List.sort (toList c) + -- the block hashes of the cut are added as tie breaker in order + -- to guarantee commutativity. + -- + ) + +-- -------------------------------------------------------------------------- -- +-- Cut Meet + +-- | Intersection of cuts +-- +meet + :: HasVersion + => WebBlockHeaderDb + -> Cut + -> Cut + -> IO Cut +meet wdb a b = do + !r <- imapM f $ HM.intersectionWith (,) (_cutHeaders a) (_cutHeaders b) + return $! unsafeMkCut r + where + f !cid (!x, !y) = do + db <- getWebBlockHeaderDb wdb cid + forkEntry db x y + +forkDepth + :: HasVersion + => WebBlockHeaderDb + -> Cut + -> Cut + -> IO Natural +forkDepth wdb a b = do + m <- meet wdb a b + return $! int $ max (maxDepth m a) (maxDepth m b) + where + maxDepth l u = maximum $ HM.intersectionWith + (\x y -> view blockHeight y - view blockHeight x) + (_cutHeaders l) + (_cutHeaders u) From 1a48a59bec4c072dae9b71b75748e7d56e0d8841 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 21 May 2025 13:45:11 -0400 Subject: [PATCH 153/378] Fix isMonotonicCutExtension to use getCutExtension Change-Id: Id000000009af5d9ff63ae6c1c9093ee9e369669d --- src/Chainweb/Cut/Create.hs | 24 +++++++++++++----------- src/Chainweb/CutDB.hs | 1 + src/Chainweb/CutDB/RestAPI/Server.hs | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index cc7f027fa3..365f609db5 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -214,7 +214,7 @@ instance HasChainId CutExtension where -- blocks for the new chains and move ahead. So steps in the new graph are not -- allowed. -- --- TODO: it is important that the semantics of this function corresponds to the +-- NB: it is important that the semantics of this function corresponds to the -- respective validation in the module "Chainweb.Cut", in particular -- 'isMonotonicCutExtension'. It must not happen, that a cut passes validation -- that can't be further extended. @@ -229,9 +229,9 @@ getCutExtension -> Maybe CutExtension getCutExtension c cid = do - -- In a graph transition we wait for all chains to do the transition to the - -- new graph before moving ahead. Blocks chains that reach the new graph - -- until all chains have reached the new graph. + -- If ANY chains in the graph have completed the graph transition (i.e. + -- reached the transition height) then we wait for ALL chains to complete + -- the transition before moving ahead on those chains. -- guard (not $ isGraphTransitionCut && isGraphTransitionPost) @@ -817,8 +817,6 @@ joinChains a b = (HM.union a c, HM.union b c) -- FIXME: this must conform with 'isBraidingOfCutPair'. Double check that we -- have test for this or check if the implementation can be shared. -- --- TODO: do we have to check that the correct graph is used? --- isMonotonicCutExtension :: (HasCallStack, HasVersion) => MonadThrow m @@ -826,12 +824,15 @@ isMonotonicCutExtension -> BlockHeader -> m Bool isMonotonicCutExtension c h = do - checkBlockHeaderGraph h - return $! monotonic && validBraiding + case getCutExtension c (_chainId h) of + Just ext -> do + let cutParent = _cutExtensionParent' ext + let monotonic = view blockParent h == fmap (view blockHash) cutParent + checkBlockHeaderGraph h + return $! monotonic && validBraiding + Nothing -> return False + where - monotonic = view blockParent h == case c ^? ixg (_chainId h) . blockHash of - Nothing -> error $ T.unpack $ "isMonotonicCutExtension.monotonic: missing parent in cut. " <> encodeToText h - Just x -> Parent x validBraiding = getAll $ ifoldMap (\cid -> All . validBraidingCid cid) (_getBlockHashRecord $ view blockAdjacentHashes h) @@ -842,6 +843,7 @@ isMonotonicCutExtension c h = do | otherwise = error $ T.unpack $ "isMonotonicCutExtension.validBraiding: missing adjacent parent on chain " <> toText cid <> " in cut. " <> encodeToText h + -- | Extend a cut with a block header. Throws 'InvalidCutExtension' if the block -- header isn't a monotonic cut extension. -- diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index e6bd533851..36d7e1ae96 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -140,6 +140,7 @@ import Chainweb.BlockWeight import Chainweb.ChainId import Chainweb.Cut import Chainweb.Cut.CutHashes +import Chainweb.Cut.Create import Chainweb.Graph import Chainweb.PayloadProvider import Chainweb.Storage.Table diff --git a/src/Chainweb/CutDB/RestAPI/Server.hs b/src/Chainweb/CutDB/RestAPI/Server.hs index 5867693fe6..ecee58fb7f 100644 --- a/src/Chainweb/CutDB/RestAPI/Server.hs +++ b/src/Chainweb/CutDB/RestAPI/Server.hs @@ -48,7 +48,7 @@ import Servant.Server -- internal modules import Chainweb.BlockHeight -import Chainweb.Cut +import Chainweb.Cut.Create import Chainweb.Cut.CutHashes import Chainweb.CutDB import Chainweb.CutDB.RestAPI From 0287dc2db00211e645ecc1448258ca96e4ecfad1 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 21 May 2025 19:32:15 -0400 Subject: [PATCH 154/378] Minor cleanup to Chainweb.Test.Cut --- test/lib/Chainweb/Test/Cut.hs | 90 +++++++++++++++++------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index dc409b3e2c..1e38d484b3 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -530,35 +530,35 @@ prop_meetJoinAbsorption wdb = do properties_lattice :: HasVersion - => RocksDb -> ChainwebVersion -> [(String, T.Property)] -properties_lattice db v = - [ ("joinIdemPotent", ioTest db v prop_joinIdempotent) - , ("joinCommutative", ioTest db v prop_joinCommutative) - , ("joinAssociative", ioTest db v prop_joinAssociative) -- Fails - , ("joinIdentity", ioTest db v prop_joinIdentity) - - , ("meetIdemPotent", ioTest db v prop_meetIdempotent) - , ("meetCommutative", ioTest db v prop_meetCommutative) - , ("meetAssociative", ioTest db v prop_meetAssociative) - , ("meetZeroAbsorption", ioTest db v prop_meetZeroAbsorption) -- Fails - - , ("joinMeetAbsorption", ioTest db v prop_joinMeetAbsorption) - , ("meetJoinAbsorption", ioTest db v prop_meetJoinAbsorption) -- Fails + => RocksDb -> [(String, T.Property)] +properties_lattice db = + [ ("joinIdemPotent", ioTest db prop_joinIdempotent) + , ("joinCommutative", ioTest db prop_joinCommutative) + , ("joinAssociative", ioTest db prop_joinAssociative) -- Fails + , ("joinIdentity", ioTest db prop_joinIdentity) + + , ("meetIdemPotent", ioTest db prop_meetIdempotent) + , ("meetCommutative", ioTest db prop_meetCommutative) + , ("meetAssociative", ioTest db prop_meetAssociative) + , ("meetZeroAbsorption", ioTest db prop_meetZeroAbsorption) -- Fails + + , ("joinMeetAbsorption", ioTest db prop_joinMeetAbsorption) + , ("meetJoinAbsorption", ioTest db prop_meetJoinAbsorption) -- Fails ] properties_lattice_passing :: HasVersion - => RocksDb -> ChainwebVersion -> [(String, T.Property)] -properties_lattice_passing db v = - [ ("joinIdemPotent", ioTest db v prop_joinIdempotent) - , ("joinCommutative", ioTest db v prop_joinCommutative) - , ("joinIdentity", ioTest db v prop_joinIdentity) + => RocksDb -> [(String, T.Property)] +properties_lattice_passing db = + [ ("joinIdemPotent", ioTest db prop_joinIdempotent) + , ("joinCommutative", ioTest db prop_joinCommutative) + , ("joinIdentity", ioTest db prop_joinIdentity) - , ("meetIdemPotent", ioTest db v prop_meetIdempotent) - , ("meetCommutative", ioTest db v prop_meetCommutative) - , ("meetAssociative", ioTest db v prop_meetAssociative) + , ("meetIdemPotent", ioTest db prop_meetIdempotent) + , ("meetCommutative", ioTest db prop_meetCommutative) + , ("meetAssociative", ioTest db prop_meetAssociative) - , ("joinMeetAbsorption", ioTest db v prop_joinMeetAbsorption) + , ("joinMeetAbsorption", ioTest db prop_joinMeetAbsorption) ] -- -------------------------------------------------------------------------- -- @@ -598,8 +598,8 @@ prop_meetGenesisCut wdb = liftIO $ prop_arbitraryForkBraiding :: HasVersion - => RocksDb -> ChainwebVersion -> T.Property -prop_arbitraryForkBraiding db v = ioTest db v $ \wdb -> do + => RocksDb -> T.Property +prop_arbitraryForkBraiding db = ioTest db $ \wdb -> do TestFork b cl cr <- arbitraryFork wdb T.assert (prop_cutBraiding b) T.assert (prop_cutBraiding cl) @@ -608,16 +608,16 @@ prop_arbitraryForkBraiding db v = ioTest db v $ \wdb -> do prop_joinBase :: HasVersion - => RocksDb -> ChainwebVersion -> T.Property -prop_joinBase db v = ioTest db v $ \wdb -> do + => RocksDb -> T.Property +prop_joinBase db = ioTest db $ \wdb -> do TestFork b cl cr <- arbitraryFork wdb m <- liftIO $ join wdb (prioritizeHeavier cl cr) cl cr return (_joinBase m == b) prop_joinBaseMeet :: HasVersion - => RocksDb -> ChainwebVersion -> T.Property -prop_joinBaseMeet db v = ioTest db v $ \wdb -> do + => RocksDb -> T.Property +prop_joinBaseMeet db = ioTest db $ \wdb -> do TestFork _ a b <- arbitraryFork wdb liftIO $ (==) <$> meet wdb a b @@ -625,18 +625,18 @@ prop_joinBaseMeet db v = ioTest db v $ \wdb -> do properties_testMining :: HasVersion - => RocksDb -> ChainwebVersion -> [(String, T.Property)] -properties_testMining db v = - [ ("Cuts of arbitrary fork have valid braiding", prop_arbitraryForkBraiding db v)] + => RocksDb -> [(String, T.Property)] +properties_testMining db = + [ ("Cuts of arbitrary fork have valid braiding", prop_arbitraryForkBraiding db)] properties_miscCut :: HasVersion - => RocksDb -> ChainwebVersion -> [(String, T.Property)] -properties_miscCut db v = - [ ("prop_joinBase", prop_joinBase db v) - , ("prop_joinBaseMeet", prop_joinBaseMeet db v) - , ("prop_meetGenesisCut", ioTest db v prop_meetGenesisCut) - , ("Cuts of arbitrary fork have valid braiding", prop_arbitraryForkBraiding db v) + => RocksDb -> [(String, T.Property)] +properties_miscCut db = + [ ("prop_joinBase", prop_joinBase db) + , ("prop_joinBaseMeet", prop_joinBaseMeet db) + , ("prop_meetGenesisCut", ioTest db prop_meetGenesisCut) + , ("Cuts of arbitrary fork have valid braiding", prop_arbitraryForkBraiding db) ] -- -------------------------------------------------------------------------- -- @@ -675,10 +675,10 @@ properties_misc = properties :: RocksDb -> [(String, T.Property)] properties db = withVersion v - $ properties_lattice_passing db v + $ properties_lattice_passing db <> withVersion v properties_cut - <> properties_testMining db v - <> properties_miscCut db v + <> properties_testMining db + <> properties_miscCut db <> properties_misc where v = barebonesTestVersion pairChainGraph @@ -687,13 +687,13 @@ properties db -- TestTools ioTest - :: RocksDb - -> ChainwebVersion + :: HasVersion + => RocksDb -> (WebBlockHeaderDb -> T.PropertyM IO Bool) -> T.Property -ioTest baseDb v f = T.monadicIO $ do +ioTest baseDb f = T.monadicIO $ do db' <- liftIO $ testRocksDb "Chainweb.Test.Cut" baseDb - liftIO (withVersion v initWebBlockHeaderDb db') >>= f >>= T.assert + liftIO (initWebBlockHeaderDb db') >>= f >>= T.assert liftIO $ deleteNamespaceRocksDb db' pickBlind :: T.Gen a -> T.PropertyM IO a From 827a7fb0c99e1055bbddf25c31d4f60110ddd648 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 21 May 2025 20:31:01 -0700 Subject: [PATCH 155/378] Remove old version registry from chainweb-node --- node/src/ChainwebNode.hs | 44 ++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 36febaa0f2..727be169ee 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -98,7 +98,6 @@ import Chainweb.Utils.RequestLog import Chainweb.Version import Chainweb.Version.Mainnet import Chainweb.Version.Testnet04 (testnet04) -import Chainweb.Version.Registry import Chainweb.Storage.Table.RocksDB @@ -198,7 +197,12 @@ runMonitorLoop actionLabel logger = runForeverThrottled 10 -- 10 bursts in case of failure (10 * mega) -- allow restart every 10 seconds in case of failure -runCutMonitor :: Logger logger => logger -> CutDb -> IO () +runCutMonitor + :: HasVersion + => Logger logger + => logger + -> CutDb + -> IO () runCutMonitor logger db = L.withLoggerLabel ("component", "cut-monitor") logger $ \l -> runMonitorLoop "ChainwebNode.runCutMonitor" l $ do logFunctionJson l Info . cutToCutHashes Nothing @@ -213,7 +217,7 @@ data BlockUpdate = BlockUpdate } deriving (Show, Eq, Ord, Generic, NFData) -instance ToJSON BlockUpdate where +instance HasVersion => ToJSON BlockUpdate where toEncoding o = pairs $ "header" .= _blockUpdateBlockHeader o <> "orphaned" .= _blockUpdateOrphaned o @@ -308,7 +312,7 @@ runDatabaseMonitor logger rocksDbDir pactDbDir = L.withLoggerLabel ("component", -- -------------------------------------------------------------------------- -- -- Run Node -node :: HasCallStack => Logger logger => ChainwebNodeConfiguration -> logger -> IO () +node :: HasCallStack => HasVersion => Logger logger => ChainwebNodeConfiguration -> logger -> IO () node conf logger = do rocksDbDir <- getRocksDbDir conf pactDbDir <- getPactDbDir conf @@ -345,7 +349,8 @@ node conf logger = do cwConf = _nodeConfigChainweb conf withNodeLogger - :: LogConfig + :: HasVersion + => LogConfig -> ChainwebConfiguration -> ChainwebVersion -> (L.Logger SomeLogMessage -> IO ()) @@ -530,21 +535,20 @@ main = do installFatalSignalHandlers [ sigHUP, sigTERM, sigXCPU, sigXFSZ ] checkRLimits runWithPkgInfoConfiguration mainInfo pkgInfo $ \conf -> do - let v = _configChainwebVersion $ _nodeConfigChainweb conf - registerVersion v - hSetBuffering stderr LineBuffering - withNodeLogger (_nodeConfigLog conf) (_nodeConfigChainweb conf) v $ \logger -> do - logFunctionJson logger Info ProcessStarted - handles - [ Handler $ \(e :: SomeAsyncException) -> - logFunctionJson logger Info (ProcessDied $ show e) >> throwIO e - , Handler $ \(e :: SomeException) -> - logFunctionJson logger Error (ProcessDied $ show e) >> throwIO e - ] $ do - kt <- mapM iso8601ParseM (_versionServiceDate v) - withServiceDate (_configChainwebVersion (_nodeConfigChainweb conf)) (logFunctionText logger) kt $ void $ - race (node conf logger) (gcRunner (logFunctionText logger)) - where + withVersion (_configChainwebVersion $ _nodeConfigChainweb conf) $ do + hSetBuffering stderr LineBuffering + withNodeLogger (_nodeConfigLog conf) (_nodeConfigChainweb conf) implicitVersion $ \logger -> do + logFunctionJson logger Info ProcessStarted + handles + [ Handler $ \(e :: SomeAsyncException) -> + logFunctionJson logger Info (ProcessDied $ show e) >> throwIO e + , Handler $ \(e :: SomeException) -> + logFunctionJson logger Error (ProcessDied $ show e) >> throwIO e + ] $ do + kt <- mapM iso8601ParseM (_versionServiceDate implicitVersion) + withServiceDate (_configChainwebVersion (_nodeConfigChainweb conf)) (logFunctionText logger) kt $ void $ + race (node conf logger) (gcRunner (logFunctionText logger)) + where gcRunner lf = runForever lf "GarbageCollect" $ do performMajorGC threadDelay (30 * 1_000_000) From e78e752a0f052a2449bae9f599d5f07f2c50c027 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 21 May 2025 20:05:01 -0400 Subject: [PATCH 156/378] test for ability to create mixed transitional cuts --- test/lib/Chainweb/Test/Cut.hs | 86 ++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index 1e38d484b3..a29c87a2b7 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -14,6 +14,7 @@ {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE PartialTypeSignatures #-} -- | -- Module: Chainweb.Test.Cut @@ -66,12 +67,15 @@ import Control.Lens hiding ((:>), (??)) import Control.Monad hiding (join) import Control.Monad.Catch import Control.Monad.IO.Class +import Control.Monad.State.Strict import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Short as BS import Data.Foldable import Data.Function import qualified Data.HashMap.Strict as HM +import qualified Data.HashSet as HS +import Data.Maybe (isNothing) import Data.Monoid import Data.Ord import qualified Data.Text as T @@ -97,12 +101,14 @@ import Chainweb.Cut import Chainweb.Cut.Create import Chainweb.Graph import Chainweb.Payload +import Chainweb.Parent import Chainweb.Test.TestVersions import Chainweb.Test.Utils.BlockHeader import Chainweb.Test.Utils -import Chainweb.Time (Micros(..), Time, TimeSpan) +import Chainweb.Time (Micros(..), Time, TimeSpan, epoch) import qualified Chainweb.Time as Time (second) import Chainweb.Utils +import Chainweb.Utils.Rule import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Utils @@ -544,8 +550,85 @@ properties_lattice db = , ("joinMeetAbsorption", ioTest db prop_joinMeetAbsorption) , ("meetJoinAbsorption", ioTest db prop_meetJoinAbsorption) -- Fails + , ("noMixedTransitionalCuts", prop_noMixedTransitionalCuts db) ] +prop_noMixedTransitionalCuts + :: RocksDb + -> T.Property +prop_noMixedTransitionalCuts baseDb = + T.monadicIO $ withVersion v $ case implicitVersion ^. versionGraphs of + (transitionHeight, transitionGraph) `Above` Bottom (_, startGraph) -> do + db' <- liftIO $ testRocksDb "Chainweb.Test.Cut" baseDb + wdb <- liftIO (initWebBlockHeaderDb db') + let startCut = genesisCut + let startCids = graphChainIds startGraph + -- to start, mine until we are one block behind the transition on all chains. + preTransitionCut <- flip execStateT startCut $ + replicateM_ (int $ transitionHeight - 1) $ do + forM_ startCids $ \cid -> do + c <- get + Right (T2 _ c') <- lift $ mine wdb 0 c cid + put c' + -- then, pick a breakout chain which we will attempt to mine beyond + -- the transition before the rest of the chains reach the + -- transition. + -- note that in order to do this, the transition chain *must* have + -- adjacent chains post-transition that all exist pre-transition, + -- otherwise we cannot continue to mine it past the transition. + (breakoutChain, breakoutChainAdjacents) <- T.pick $ do + T.suchThat + (do + breakoutChain <- T.oneof (return <$> toList startCids) + let breakoutChainAdjacents = adjacentChainIds transitionGraph breakoutChain + return (breakoutChain, breakoutChainAdjacents) + ) + (\(_, breakoutChainAdjacents) -> + breakoutChainAdjacents `HS.isSubsetOf` graphChainIds startGraph) + -- now we set up a dangerous situation: we mine the breakout chain + -- and its adjacents to get them to the transition height. unless + -- it's prevented, the breakout chain should be able to progress + -- beyond the transition. + dangerousCut <- flip execStateT preTransitionCut $ + forM_ (breakoutChain : toList breakoutChainAdjacents) $ \cid -> do + c <- get + Right (T2 _ c') <- lift $ mine wdb 0 c cid + put c' + let adjacentBlocks = HM.mapWithKey + (\acid () -> Parent $ dangerousCut ^?! ixg acid) + (HS.toMap breakoutChainAdjacents) + (_, ext) <- liftIO $ + extend dangerousCut Nothing Nothing + WorkParents + { _workParent' = Parent $ dangerousCut ^?! ixg breakoutChain + , _workAdjacentParents' = adjacentBlocks + } + SolvedWork + { _solvedAdjacentHash = + adjacentsHash $ BlockHashRecord (fmap (view blockHash) <$> adjacentBlocks) + , _solvedChainId = breakoutChain + , _solvedParentHash = Parent $ + dangerousCut ^?! ixg breakoutChain . blockHash + , _solvedPayloadHash = _payloadWithOutputsPayloadHash $ testPayload + $ B8.intercalate "," [ sshow (_versionName implicitVersion), sshow breakoutChain, "TEST PAYLOAD"] + , _solvedWorkNonce = Nonce 0 + , _solvedWorkCreationTime = BlockCreationTime epoch + } + liftIO $ deleteNamespaceRocksDb db' + -- there should be no such legal cut extension. + T.assert (isNothing ext) + _ -> error "timedConsensusVersion graphs have changed" + where + v = timedConsensusVersion petersenChainGraph twentyChainGraph + mine :: HasVersion => WebBlockHeaderDb -> Int -> Cut -> ChainId -> T.PropertyM IO (Either MineFailure (T2 BlockHeader Cut)) + mine wdb seed c cid = do + n' <- T.pick $ Nonce . int . (* seed) <$> T.arbitrary + delay <- pickBlind $ arbitraryBlockTimeOffset Time.second (plus Time.second Time.second) + liftIO (testMine' wdb n' delay pay cid c) + where + pay = _payloadWithOutputsPayloadHash $ testPayload + $ B8.intercalate "," [ sshow (_versionName implicitVersion), sshow cid, "TEST PAYLOAD"] + properties_lattice_passing :: HasVersion => RocksDb -> [(String, T.Property)] @@ -637,6 +720,7 @@ properties_miscCut db = , ("prop_joinBaseMeet", prop_joinBaseMeet db) , ("prop_meetGenesisCut", ioTest db prop_meetGenesisCut) , ("Cuts of arbitrary fork have valid braiding", prop_arbitraryForkBraiding db) + , ("noMixedTransitionalCuts", prop_noMixedTransitionalCuts db) ] -- -------------------------------------------------------------------------- -- From 1c1a97e4561df01eb15385bcdd794a81bfcd125b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 21 May 2025 22:21:33 -0700 Subject: [PATCH 157/378] fix initialization of pact databases --- src/Chainweb/Chainweb.hs | 15 +++++++-------- src/Chainweb/Chainweb/ChainResources.hs | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index ca0cf2d2e6..48b5c03591 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -61,6 +61,7 @@ module Chainweb.Chainweb , chainwebConfig , chainwebServiceSocket , chainwebBackup +, chainwebManager , StartedChainweb(..) , ChainwebStatus(..) , NowServing(..) @@ -211,7 +212,7 @@ withChainweb -> FilePath -> (StartedChainweb logger -> IO ()) -> IO () -withChainweb c logger rocksDb pactDbDir backupDir inner = +withChainweb c logger rocksDb defaultPactDbDir backupDir inner = withVersion (c ^. configChainwebVersion) $ withPeerResources (view configP2p confWithBootstraps) logger $ \logger' peerRes -> withSocket serviceApiPort serviceApiHost $ \serviceSock -> do @@ -224,7 +225,7 @@ withChainweb c logger rocksDb pactDbDir backupDir inner = peerRes serviceSock rocksDb - pactDbDir + defaultPactDbDir backupDir inner where @@ -273,7 +274,7 @@ withChainwebInternal -> FilePath -> (StartedChainweb logger -> IO ()) -> IO () -withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir inner = do +withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir backupDir inner = do logFunctionJson logger Info InitializingChainResources txFailuresCounter <- newCounter @"txFailures" let monitorTxFailuresCounter = @@ -282,7 +283,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir logFunctionCounter logger Info . (:[]) =<< roll txFailuresCounter logg Debug "start initializing chain resources" - logFunctionText logger Info $ "opening pact db in directory " <> sshow pactDbDir + logFunctionText logger Info $ "opening pact db in directory " <> sshow defaultPactDbDir withAsync monitorTxFailuresCounter $ \_ -> concurrentWith -- initialize chains concurrently @@ -294,7 +295,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir cid rocksDb (_peerResManager peerRes) - pactDbDir + defaultPactDbDir (_peerResConfig peerRes) myInfo peerDb @@ -511,7 +512,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb pactDbDir backupDir , _chainwebBackup = BackupEnv { _backupRocksDb = rocksDb , _backupDir = backupDir - , _backupPactDbDir = pactDbDir + , _backupPactDbDir = defaultPactDbDir , _backupChainIds = cids , _backupLogger = backupLogger } @@ -701,8 +702,6 @@ runChainweb cw nowServing = do -- I.e. the handler would be created in the chain resource. -- Similar to how it is done with the payload provider APIs. -- - -- chainDbsToServe :: [(ChainId, BlockHeaderDb)] - -- chainDbsToServe = proj _chainResBlockHeaderDb chainDbsToServe :: ChainMap BlockHeaderDb chainDbsToServe = _chainResBlockHeaderDb <$> _chainwebChains cw diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 6ce0a34ae2..95f18f74f0 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -57,7 +57,6 @@ module Chainweb.Chainweb.ChainResources , payloadServiceApiResources ) where -import Control.Exception(evaluate) import Control.Lens hiding ((.=), (<.>)) import Control.Monad.IO.Class import Control.Monad.Trans.Resource @@ -233,9 +232,13 @@ withPayloadProviderResources -- ^ the reorg limit for the payload providers -> Bool -- ^ whether to allow unlimited rewind on startup + -> FilePath + -- ^ default database directory for pact databases. As long as Pact + -- payload providers live within chainweb-consensus they inherit the + -- default db location from the chainweb configuration. -> PayloadProviderConfig -> ResourceT IO ProviderResources -withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs = do +withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind defaultPactDbDir configs = do SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal SomeChainIdT @c' _ <- return $ someChainIdVal cid withSomeSing provider $ \case @@ -294,7 +297,9 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi } let pdb = newPayloadDb rdb - pactDbDir <- liftIO $ evaluate $ fromJuste $ _pactConfigDatabaseDirectory conf + let pactDbDir = case _pactConfigDatabaseDirectory conf of + Just x -> x + Nothing -> defaultPactDbDir rec pp <- withPactPayloadProvider @@ -400,7 +405,9 @@ withChainResources -> RocksDb -> HTTP.Manager -> FilePath - -- ^ database directory for pact databases + -- ^ default database directory for pact databases. As long as Pact + -- payload providers live within chainweb-consensus they inherit the + -- default db location from the chainweb configuration. -> P2pConfiguration -> PeerInfo -> PeerDb @@ -410,7 +417,7 @@ withChainResources -- ^ whether to allow unlimited rewind on startup -> PayloadProviderConfig -> ResourceT IO (ChainResources logger) -withChainResources logger cid rdb mgr _pactDbDir p2pConf myInfo peerDb rewindLimit initialUnlimitedRewind configs = do +withChainResources logger cid rdb mgr defaultPactDbDir p2pConf myInfo peerDb rewindLimit initialUnlimitedRewind configs = do -- This uses the the CutNetwork for fetching block headers. cdb <- withBlockHeaderDb rdb cid @@ -418,7 +425,7 @@ withChainResources logger cid rdb mgr _pactDbDir p2pConf myInfo peerDb rewindLim -- Payload Providers are using per chain payload networks for fetching -- block headers. provider <- withPayloadProviderResources - providerLogger cid p2pConf myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind configs + providerLogger cid p2pConf myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind defaultPactDbDir configs return ChainResources { _chainResBlockHeaderDb = cdb From 5010abe91c69bbcb1f249c8cdcc2fd2311b1eb7a Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 14:09:04 -0400 Subject: [PATCH 158/378] Use minimal payload provider in timedConsensusVersion Change-Id: Id0000000224d219eeea207814eda62d817083e15 --- test/lib/Chainweb/Test/TestVersions.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index c298c3eca9..14bd63c61a 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -152,7 +152,7 @@ timedConsensusVersion g1 g2 = , _genesisBlockTarget = maxTarget <$ cids , _genesisTime = BlockCreationTime epoch <$ cids } - & versionPayloadProviderTypes .~ (PactProvider <$ cids) + & versionPayloadProviderTypes .~ (MinimalProvider <$ cids) where gs = (BlockHeight 8, g2) `Above` Bottom (minBound, g1) cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs From 6b8645c6206e5f2fc8a14e5d2e2d0574eab973ff Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 21 May 2025 22:38:47 -0700 Subject: [PATCH 159/378] Enable pact-5 on evm-development as required by genesis blocks --- src/Chainweb/Version/EvmDevelopment.hs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 0e462c0320..59fc42ff9a 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -55,11 +55,7 @@ evmDevnet :: ChainwebVersion evmDevnet = withVersion evmDevnet $ ChainwebVersion { _versionCode = ChainwebVersionCode 0x0000_000a , _versionName = ChainwebVersionName "evm-development" - , _versionForks = tabulateHashMap $ \case - -- TODO: for now, Pact 5 is never enabled on EVM devnet. - -- this will change as it stabilizes. - Pact5Fork -> onAllChains ForkNever - _ -> onAllChains ForkAtGenesis + , _versionForks = tabulateHashMap $ const $ onAllChains ForkAtGenesis , _versionUpgrades = onAllChains mempty , _versionGraphs = Bottom (minBound, d4k4ChainGraph) , _versionBlockDelay = BlockDelay 30_000_000 From 1ef91e012480584cb0c8754474ed81cccddf427a Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 22 May 2025 13:47:45 -0400 Subject: [PATCH 160/378] set consensus state in savepoint --- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 25 +++++++++++---------- src/Chainweb/Pact/Backend/Utils.hs | 3 +++ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 16a687f063..ec63098c03 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -751,18 +751,19 @@ createVersionedTable tablename db = do setConsensusState :: SQ3.Database -> ConsensusState -> ExceptT LocatedSQ3Error IO () setConsensusState db cs = do - exec' db - "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ - \(?, ?, ?, ?);" - (toRow "final" $ _consensusStateFinal cs) - exec' db - "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ - \(?, ?, ?, ?);" - (toRow "safe" $ _consensusStateSafe cs) - exec' db - "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ - \(?, ?, ?, ?);" - (toRow "latest" $ _consensusStateLatest cs) + withSavepoint db SetConsensusSavePoint $ do + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "final" $ _consensusStateFinal cs) + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "safe" $ _consensusStateSafe cs) + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "latest" $ _consensusStateLatest cs) where toRow safety SyncState {..} = [ SInt $ fromIntegral @BlockHeight @Int64 _syncStateHeight diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 44b1b0cd74..e1c9aab4f3 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -206,6 +206,7 @@ data SavepointName | RewindSavePoint | InitSchemaSavePoint | ValidateBlockSavePoint + | SetConsensusSavePoint deriving (Eq, Ord, Enum, Bounded) instance Show SavepointName where @@ -218,6 +219,7 @@ instance HasTextRepresentation SavepointName where toText RewindSavePoint = "rewind" toText InitSchemaSavePoint = "init-schema" toText ValidateBlockSavePoint = "validate-block" + toText SetConsensusSavePoint = "set-consensus" {-# INLINE toText #-} fromText "read-from" = pure ReadFromSavepoint @@ -226,6 +228,7 @@ instance HasTextRepresentation SavepointName where fromText "rewind" = pure RewindSavePoint fromText "init-schema" = pure InitSchemaSavePoint fromText "validate-block" = pure ValidateBlockSavePoint + fromText "set-consensus" = pure SetConsensusSavePoint fromText t = throwM $ TextFormatException $ "failed to decode SavepointName " <> t <> ". Valid names are " <> T.intercalate ", " (toText @SavepointName <$> [minBound .. maxBound]) From e5977dc3ebc8720d572598ccb6f4cc6db2d74e53 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 21 May 2025 20:10:18 -0700 Subject: [PATCH 161/378] update EvmDevelopment version to use only 5 chains. Also update evm-genesis tool --- cwtools/evm-genesis/Main.hs | 276 +++++++++++++++++++- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 32 +-- src/Chainweb/Version/EvmDevelopment.hs | 42 +-- 3 files changed, 292 insertions(+), 58 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 526d8f87f3..3270a828ef 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -40,15 +40,40 @@ import Control.Retry -- -------------------------------------------------------------------------- -- -- Main +-- | This program generates chain-spec files, genesis blocks, and the respective +-- block hashes for EVMs on Chainweb networks. +-- +-- The results can be used to define the genesis information for the respective +-- networks in the chainweb-node code basis. +-- main :: IO () main = do - cids <- traverse (fromText . T.pack) =<< getArgs + + -- parse command line + (n, cids, spec) <- getArgs >>= \case + [] -> error "No argument for the chainweb version provided. The version must be one of: 'mainnet', 'testnet', 'evm-testnet', or 'evm-development'." + ["mainnet"] -> do + let cids = [20..25] + return ("mainnet", cids, mainnetSpecFile) + ["testnet"] -> do + let cids = [20..25] + return ("testnet", cids, testnetSpecFile) + ["evm-testnet"] -> do + let cids = [20..25] + return ("evm-testnet", cids, evmTestnetSpecFile) + ["evm-development"] -> do + let cids = [20..25] + return ("evm-development", cids, evmDevnetSpecFile) + _ -> error "Invalid argument for the chainweb version provided. The version must be one of: 'mainnet', 'testnet', 'evm-testnet', or 'evm-development'." + hdrs <- forM cids $ \cid -> do - createDirectoryIfMissing True "./chain-specs" - let specFileName = "./chain-specs/chain-spec-" <> show cid <> ".json" - encodeFile specFileName $ specFile cid + let specFileDir = "./chain-specs/" <> n + createDirectoryIfMissing True specFileDir + let specFileName = specFileDir <> "/chain-spec-" <> show cid <> ".json" + encodeFile specFileName $ spec cid hdr <- queryNode cid specFileName return (cid, hdr) + T.putStrLn $ encodeToText [ object [ "chainId" .= cid @@ -128,8 +153,23 @@ mkRpcCtx u = do -- -------------------------------------------------------------------------- -- -- Spec File For EVM Devnet -specFile :: Natural -> Value -specFile cid = object [ +-- | Intended only for local development and testing. It is not intended for use +-- with public networks. +-- +-- The keys for all allocations are publicly known. +-- +-- The Ethereum network chain ids are not officially registered and overlap with +-- the chain ids of other networks. +-- +-- The configuration of the network may change at any time. +-- +-- EVMs are available at block height 0. +-- +evmDevnetSpecFile + :: Natural + -- numeric chainweb chain id + -> Value +evmDevnetSpecFile cid = object [ "config" .= object [ "chainId" .= (1789 + cid - 20), "daoForkSupport" .= True, @@ -166,6 +206,7 @@ specFile cid = object [ "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) ] ], + -- TODO: add native-x-chain system contract "0x8849BAbdDcfC1327Ad199877861B577cEBd8A7b6".= object [ "balance" .= t "0xd3c21bcecceda1000000" ], @@ -240,3 +281,226 @@ specFile cid = object [ i :: Natural -> Natural i = id +-- -------------------------------------------------------------------------- -- +-- Spec File For EVM Testnet + +-- | Used with the public Kadena EVM testnet. This is a temporary feature +-- testnet in preparation for the launch of EVM chains on the Kadena mainet and +-- the regular permanent Kadena testnet. +-- +-- The network is expected to be decommissioned after EVM chains have been +-- launched on the Kadena mainnet. +-- +-- Funds on the network have no economic value. +-- +-- The keys for the genesis allocations are held by the Kadena team. +-- +-- The EVM chains are available at block height 0. +-- +evmTestnetSpecFile + :: Natural + -- ^ numeric chainweb chain id + -> Value +evmTestnetSpecFile cid = object [ + "config" .= object [ + "chainId" .= (1789 + cid - 20), + "daoForkSupport" .= True, + "terminalTotalDifficultyPassed" .= True, + "terminalTotalDifficulty" .= i 0, + "daoForkBlock" .= i 0, + "homesteadBlock" .= i 0, + "eip150Block" .= i 0, + "eip155Block" .= i 0, + "eip158Block" .= i 0, + "byzantiumBlock" .= i 0, + "constantinopleBlock" .= i 0, + "petersburgBlock" .= i 0, + "istanbulBlock" .= i 0, + "muirGlacierBlock" .= i 0, + "berlinBlock" .= i 0, + "londonBlock" .= i 0, + "arrowGlacierBlock" .= i 0, + "graphGlacierBlock" .= i 0, + "mergeForkBlock" .= i 0, + "mergeNetsplitBlock" .= i 0, + "shanghaiTime" .= i 0, + "cancunTime" .= i 0, + "pragueTime" .= i 0 + ], + "timestamp".= t "0x6490fdd2", + "extraData".= t "0x", + "gasLimit".= t "0x1c9c380", + "alloc".= object [ + "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ + "balance".= t "0x0", + "code".= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", + "storage".= object [ + "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) + ] + ] + -- TODO: Native-X-Chain System contract + -- TODO: funding of faucet + -- TODO: other allocations. + ], + + "number" .= t "0x0", + "nonce" .= t "0x0", + "difficulty" .= t "0x0", + "mixHash" .= t "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase" .= t "0x0000000000000000000000000000000000000000" + ] + where + t :: T.Text -> T.Text + t = id + + i :: Natural -> Natural + i = id + +-- -------------------------------------------------------------------------- -- +-- Spec File For Kadena Mainnet + +-- | Used with the public Kadena Mainnet. +-- +-- Allocations are funded out of the platform share of the Kadena mainnet. +-- The keys for the Allocations are not publicly known. +-- +-- The EVM chains are launched at block height TODO. +-- +-- TODO +-- +mainnetSpecFile + :: Natural + -- ^ numeric chainweb chain id + -> Value +mainnetSpecFile cid = object [ + "config" .= object [ + "chainId" .= (3789 + cid - 20), + "daoForkSupport" .= True, + "terminalTotalDifficultyPassed" .= True, + "terminalTotalDifficulty" .= i 0, + "daoForkBlock" .= i 0, + "homesteadBlock" .= i 0, + "eip150Block" .= i 0, + "eip155Block" .= i 0, + "eip158Block" .= i 0, + "byzantiumBlock" .= i 0, + "constantinopleBlock" .= i 0, + "petersburgBlock" .= i 0, + "istanbulBlock" .= i 0, + "muirGlacierBlock" .= i 0, + "berlinBlock" .= i 0, + "londonBlock" .= i 0, + "arrowGlacierBlock" .= i 0, + "graphGlacierBlock" .= i 0, + "mergeForkBlock" .= i 0, + "mergeNetsplitBlock" .= i 0, + "shanghaiTime" .= i 0, + "cancunTime" .= i 0, + "pragueTime" .= i 0 + ], + "timestamp".= t "0x6490fdd2", + "extraData".= t "0x", + "gasLimit".= t "0x1c9c380", + "alloc".= object [ + "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ + "balance".= t "0x0", + "code".= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", + "storage".= object [ + "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) + ] + ], + error "mainnetSpecFile: the EVM genesis allocations for mainnet are TBD" + -- TODO: Native-X-Chain System contract + -- TODO: other allocations. + ], + + "number" .= error @_ @() "mainnetSpecFile: the initial block height is TBD", -- t "0x0", + "nonce" .= t "0x0", + "difficulty" .= t "0x0", + "mixHash" .= t "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase" .= t "0x0000000000000000000000000000000000000000" + ] + where + t :: T.Text -> T.Text + t = id + + i :: Natural -> Natural + i = id + + +-- -------------------------------------------------------------------------- -- +-- Spec File For Kadena Testnet + +-- | Used with the public Kadena testnet. This is a permament testnet that +-- has the same features and properties of the Kadena mainnet. It has the +-- purpose to facilitate testing of services and applications under the same +-- conditions as on the Kadena mainnet. +-- +-- Funds on the network have no economic value. +-- +-- The keys for the genesis allocations are held by the Kadena team. +-- +-- The EVM chains are launched at block height TODO. +-- +-- TODO +-- +testnetSpecFile + :: Natural + -- ^ numeric chainweb chain id + -> Value +testnetSpecFile cid = object [ + "config" .= object [ + "chainId" .= (2789 + cid - 20), + "daoForkSupport" .= True, + "terminalTotalDifficultyPassed" .= True, + "terminalTotalDifficulty" .= i 0, + "daoForkBlock" .= i 0, + "homesteadBlock" .= i 0, + "eip150Block" .= i 0, + "eip155Block" .= i 0, + "eip158Block" .= i 0, + "byzantiumBlock" .= i 0, + "constantinopleBlock" .= i 0, + "petersburgBlock" .= i 0, + "istanbulBlock" .= i 0, + "muirGlacierBlock" .= i 0, + "berlinBlock" .= i 0, + "londonBlock" .= i 0, + "arrowGlacierBlock" .= i 0, + "graphGlacierBlock" .= i 0, + "mergeForkBlock" .= i 0, + "mergeNetsplitBlock" .= i 0, + "shanghaiTime" .= i 0, + "cancunTime" .= i 0, + "pragueTime" .= i 0 + ], + "timestamp".= t "0x6490fdd2", + "extraData".= t "0x", + "gasLimit".= t "0x1c9c380", + "alloc".= object [ + "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ + "balance".= t "0x0", + "code".= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", + "storage".= object [ + "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) + ] + ], + error "mainnetSpecFile: the EVM genesis allocations for mainnet are TBD" + -- TODO: Native-X-Chain System contract + -- TODO: other allocations. + ], + + "number" .= error @_ @() "mainnetSpecFile: the initial block height is TBD", -- t "0x0", + "nonce" .= t "0x0", + "difficulty" .= t "0x0", + "mixHash" .= t "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase" .= t "0x0000000000000000000000000000000000000000" + ] + where + t :: T.Text -> T.Text + t = id + + i :: Natural -> Natural + i = id + + diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index bb2eacb283..29327c1458 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -68,7 +68,7 @@ import Data.Text qualified as T -- 1. Query the EVM genesis header and compute block payload hash and header: -- -- @ --- cabal run cwtools:exe:evm-genesis +-- cabal run cwtools:exe:evm-genesis evm-development -- @ -- genesisBlocks @@ -90,36 +90,6 @@ genesisBlocks c = go (_versionCode implicitVersion) (_chainId c) "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOG8PeZNPmM3lrR8_XYZiV77h9JISltItpJrMg3uWzISoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 24 -> f "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoL_zw0Kq1MfRVs38rgQLmxq1zFk8ac976rfmBmNiPvc0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 25 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAR82Mmf_nzgqOuk-JuK2c6y4cUsnjj_mthufymqwKl4oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 26 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoLIoqmCAur1Y7jpeSGyRpx71kbUqXT-R5dTjIswNvgyXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 27 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoEa3cdjo4w54WX1slUf3N6RgIFddjDWTfYNUg4mMF_ExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 28 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJGErv8Pw9R_vy2jSuU8h22F9iPtZMzBDYhF33EA0PGhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 29 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMh5sX_kYQkGl1nZ-RHbBlbBwbp-VcznAOB0nAVnXOzroFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 30 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGcOIoUzg5FTvy0MC0EZJsxo030YmtHVbISpQi0iaD3poFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 31 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNkhLvaaRHuHwXutFpApXpQMuKIUXeN0riiBvHCjpezvoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 32 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAZn-r-AVUqdftFL0aAYgnXg3Ihh3TpvNHF1Lk2f5dO7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 33 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoI5MZQFQY_vcdKf1l4cilyj0fjzDsAGNw5ZFew7mLNB7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 34 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFIGyZq62efyMMlN-YowvgItKWe3F4BY3CG2g_9q7jfRoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 35 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBV-hCyAvRGG3t7rNKZd1fKF1ZuQjEBKezJpX2508UDioFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 36 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFH9aUrJ9ByuptHRBLi9Qz5ZfxB9PHQo49SlwaBIgb6ZoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 37 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNMYg9Jdt7H6aQ8qvbWwbcqKrAYx8dFuK4ZQjkUPo-jNoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 38 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoArbi8-FGeh8uGxs7MxDXJUwVMznEnD_W-imVRojX9PnoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" - ChainId 39 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoPitbBNoKjvjSCImGCqCmwST-kMGThKeiK5WSDMw-CfqoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | otherwise = error "requested genesis block for unsupported chain" diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 59fc42ff9a..2f4fc6a9ce 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -38,13 +38,13 @@ pattern EvmDevelopment <- ((== evmDevnet) -> True) where -- import Chainweb.Version.EvmDevelopment -- -- registerVersion EvmDevelopment --- mapM_ (\i -> T.putStrLn (sshow i <> " " <> encodeToText (view payloadHash $ genesisPayload EvmDevelopment $ unsafeChainId i))) [40..97] +-- mapM_ (\i -> T.putStrLn (sshow i <> " " <> encodeToText (view payloadHash $ genesisPayload EvmDevelopment $ unsafeChainId i))) [25..97] -- @ -- -- EVM Payload Provider: -- -- @ --- cabal run evm-genesis -- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 +-- cabal run evm-genesis -- evm-development -- @ -- -- Pact Provider: @@ -66,8 +66,8 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 500_000) , _genesisTime = onChains $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0..19] ] - <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..39] ] - <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [40..97] ] + <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..24] ] + <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [25..97] ] , _genesisBlockPayload = onChains $ -- Pact Payload Provider [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") @@ -96,22 +96,22 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion , (unsafeChainId 22, unsafeFromText "IQLMke3si3QrlqKRyesUJr0iOdYFawl0UhPVXHYc6-M") , (unsafeChainId 23, unsafeFromText "-dc_2udXDNRodCsLAX02kKVsnI-gQMeBZdsZHjxEkbw") , (unsafeChainId 24, unsafeFromText "nWj_l1UK6k9hdMRV53WfNPEIHmUW2NFDpv0-iI2SnPQ") - , (unsafeChainId 25, unsafeFromText "8OH3La_FkKuK91jQZETYp_QnE2UhQHJnlyZdSql6nhs") - , (unsafeChainId 26, unsafeFromText "tHw2yo16N5wEyz2jsd53kplg2xeIi-5PwdzY0KlzzSM") - , (unsafeChainId 27, unsafeFromText "20Rw_Wl_AZl0BmsYPYkv6ghIL8jqGCUeOpUiLhCuS84") - , (unsafeChainId 28, unsafeFromText "_ThaCzgNd-zBRfzz3l-ggZT_XWPwR0OTrolGSUexdsA") - , (unsafeChainId 29, unsafeFromText "vi1Pgfd1Uyio0OUi1RHCHvRNNYIjEX9Z4-YY9Hkrjo4") - , (unsafeChainId 30, unsafeFromText "a0cPOU3F0WTHWrQXPJIGToEpVETRetRM4-FabZ3WhfU") - , (unsafeChainId 31, unsafeFromText "gRs2a2_sBlxwVABhjLkPqdBGY4jSOI-9FsYeLYZX42s") - , (unsafeChainId 32, unsafeFromText "-IFOzOxVR2-yusLt_W9ns_eURYgFsEYTmWBeqCiWowo") - , (unsafeChainId 33, unsafeFromText "_yCbWuqwwYEX_YbGxH8XJ5ZmCWoobO7WUyyMt1MGgxE") - , (unsafeChainId 34, unsafeFromText "cv9ZuWQvqVkPZAyaaVX-NUPpgrwxg23_K7vtD3CRqB8") - , (unsafeChainId 35, unsafeFromText "iNZJV9TWAEOB9W_4bCrEB0tpvSOcEz63K3NfSFbiDXw") - , (unsafeChainId 36, unsafeFromText "e4PE6KrZkxtncGRGS4sscjuq75JZ1S798-TJHja__Kg") - , (unsafeChainId 37, unsafeFromText "gj4cGxxI_maEK2yIXTE1JW-s10W8291mAZiEQQHevcs") - , (unsafeChainId 38, unsafeFromText "miWz2MqGFUUx_KsbYUHWmJ6HMEP0w5UlT83m6r7onLY") - , (unsafeChainId 39, unsafeFromText "KfnCJ-BsVoG7ae42M9STk2Y6FO8LKdsijDklbDhyUfo") -- Minimal Payload Provider + , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") + , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") + , (unsafeChainId 27, unsafeFromText "xAOBFMKZ_lSHNVhHW-GhbhiWh6sX48S2KPyyPMjAnZM") + , (unsafeChainId 28, unsafeFromText "eav79QetdNuKo1HDW27Aeqbxr8oAt6Fh2U6ZtSfVyYU") + , (unsafeChainId 29, unsafeFromText "jlcxQ4wXUrApJDUQRS8KxuSyoG7ZFEwLV4-92wmqLOQ") + , (unsafeChainId 30, unsafeFromText "odUxYpZ8ZeW0WuQcibJH3isuI045MuEEeQqLrkivWEk") + , (unsafeChainId 31, unsafeFromText "poJ65aDiZYYthbhrgUI2jJS_8vK1CTHRE2C6dLbjTXA") + , (unsafeChainId 32, unsafeFromText "SC3ol1uFOHAewfQrMQczKrvhE8Dw1Bp9fBzNi9l_zTw") + , (unsafeChainId 33, unsafeFromText "p38GsNdY-T8ULYN1OTpYyO1E7WOGwzE2g92aIPereUw") + , (unsafeChainId 34, unsafeFromText "1V0SzqA9fHDOMnfvXvSd57H0r-iycjjLu3CKkRLcjRk") + , (unsafeChainId 35, unsafeFromText "SeiRMENBv5XqR7wXUYSR2orjGHSUrtawx5gLrDCfZYA") + , (unsafeChainId 36, unsafeFromText "JpsiIjm8aZEbyrcsMqLDneT0BNoJAJunk4BYqDO_1Y0") + , (unsafeChainId 37, unsafeFromText "0yx4OFT3sbLS_wrqpULgPpgzzxmMRwM6VTmkasitTlA") + , (unsafeChainId 38, unsafeFromText "Z3LE9JHGpSYuR3SvoYWPae7zB9-X05vFLjwD4GnXX_A") + , (unsafeChainId 39, unsafeFromText "9ZHBlxeYPWjhvMySgVx3ThEyooYx724zjORClgmq8Lk") , (unsafeChainId 40, unsafeFromText "H3VBsNGh-SQE-0d_qlYSHnS2obzUeo6Zi1XDDvhndYo") , (unsafeChainId 41, unsafeFromText "N6hVHz6vo0frpS3eyqvtMeZg1eFbAMJ1CS315M-JpWw") , (unsafeChainId 42, unsafeFromText "9mo8CRwvTLLJ4cSQtErBfOIxzwpale-AwnbXPWQd184") @@ -193,6 +193,6 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion -- FIXME make this safe for graph changes , _versionPayloadProviderTypes = onChains $ [ (unsafeChainId i, PactProvider) | i <- [0..19] ] - <> [ (unsafeChainId i, EvmProvider (1789 - 20 + int i)) | i <- [20..39] ] - <> [ (unsafeChainId i, MinimalProvider) | i <- [40..97] ] + <> [ (unsafeChainId i, EvmProvider (1789 - 20 + int i)) | i <- [20..24] ] + <> [ (unsafeChainId i, MinimalProvider) | i <- [25..97] ] } From eede31aeeb186348470af332ed8bde4224eeffd6 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 11:54:30 -0400 Subject: [PATCH 162/378] Optimize getBranch slightly Change-Id: Id000000036f42381e5e2e3404260570c5f194682 --- src/Chainweb/TreeDB.hs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Chainweb/TreeDB.hs b/src/Chainweb/TreeDB.hs index 14a1f3b291..d68c33fc0b 100644 --- a/src/Chainweb/TreeDB.hs +++ b/src/Chainweb/TreeDB.hs @@ -523,15 +523,17 @@ getBranch -> HS.HashSet (UpperBound (DbKey db)) -> S.Stream (Of (DbEntry db)) IO () getBranch db lowerBounds upperBounds = do - lowers <- getEntriesHs $ HS.map _getLowerBound lowerBounds - uppers <- getEntriesHs $ HS.map _getUpperBound upperBounds + lowers <- liftIO $ getEntriesHs _getLowerBound lowerBounds + uppers <- liftIO $ getEntriesHs _getUpperBound upperBounds - let mar = L.maximum $ HS.map rank (lowers <> uppers) + let mar = getMax $ fromJuste $ + foldMap' (foldMap' (Just . Max . rank)) [lowers, uppers] go mar (active mar lowers mempty) (active mar uppers mempty) where - getEntriesHs = lift . streamToHashSet_ . lookupStream db . S.each - getParentsHs = lift . streamToHashSet_ . lookupParentStreamM GenesisParentNone db . S.each + getEntriesHs :: (a -> Key (DbEntry db)) -> HS.HashSet a -> IO (HS.HashSet (DbEntry db)) + getEntriesHs f = streamToHashSet_ . lookupStream db . S.map f . S.each + getParentsHs = streamToHashSet_ . lookupParentStreamM GenesisParentNone db . S.each -- prop> all ((==) r . rank) $ snd (active r s c) -- @@ -557,8 +559,8 @@ getBranch db lowerBounds upperBounds = do | otherwise = do let us1' = us1 `HS.difference` ls1 mapM_ S.yield us1' - us1p <- getParentsHs us1' - ls1p <- getParentsHs ls1 + us1p <- liftIO $ getParentsHs us1' + ls1p <- liftIO $ getParentsHs ls1 let r' = pred r go r' (active r' ls0 ls1p) (active r' us0 us1p) From f717e86ef2f045723e953c766e6a551e0f032475 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 5 May 2025 11:54:30 -0400 Subject: [PATCH 163/378] payloadprovider fork detection --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 36 ++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index e812f5d999..28325817f1 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -54,6 +54,7 @@ import Chainweb.MinerReward (blockMinerReward) import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.PayloadProvider +import Chainweb.Ranked import Chainweb.Storage.Table import Chainweb.Time import Chainweb.TreeDB @@ -68,6 +69,7 @@ import Control.Monad import Control.Monad.Catch import Data.Foldable import Data.Hashable +import Data.HashSet qualified as HS import Data.LogMessage import Data.PQueue import Data.TaskMap @@ -81,6 +83,7 @@ import Servant.Client import System.LogLevel import Utils.Logging.Trace import Chainweb.Parent +import Streaming.Prelude qualified as S -- -------------------------------------------------------------------------- -- -- Response Timeout Constants @@ -462,12 +465,35 @@ getBlockHeaderInternal case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do - logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation for " <> sshow h <> " failed with :" <> sshow e + logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation for " <> sshow h <> " failed with : " <> sshow e throwM e - unless (r == _forkInfoTargetState finfo) $ do - throwM $ GetBlockHeaderFailure $ "unexpected result state" - <> "; expected: " <> sshow (_forkInfoTargetState finfo) - <> "; actual: " <> sshow r + if r /= _forkInfoTargetState finfo + then do + let ppBlock = _syncStateRankedBlockHash $ _consensusStateLatest r + let targetBlock = _syncStateRankedBlockHash $ _consensusStateLatest $ _forkInfoTargetState finfo + bhdb <- getWebBlockHeaderDb wdb cid + let forkBlocksDescendingStream = getBranch bhdb + (HS.singleton $ LowerBound (_ranked ppBlock)) + (HS.singleton $ UpperBound (_ranked targetBlock)) + -- TODO: what if this is empty? + forkBlocksAscending <- fmap reverse $ S.toList_ forkBlocksDescendingStream + let newTrace = + zipWith + (\prent child -> + ConsensusPayload (view blockPayloadHash child) Nothing <$ + blockHeaderToEvaluationCtx (Parent prent)) + forkBlocksAscending + (tail forkBlocksAscending) + let newForkInfo = finfo { _forkInfoTrace = newTrace } + r' <- syncToBlock provider hints newForkInfo `catch` \(e :: SomeException) -> do + logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation retry for " <> sshow h <> " failed with: " <> sshow e + throwM e + unless (r' == _forkInfoTargetState finfo) $ do + throwM $ GetBlockHeaderFailure $ "unexpected result state" + <> "; expected: " <> sshow (_forkInfoTargetState finfo) + <> "; actual: " <> sshow r + else + return () DisabledPayloadProvider -> do logg Debug $ taskMsg k $ "getBlockHeaderInternal payload provider disabled" From 27ef710210ab1db5e7d1d193eee6a0c5adbfe6c6 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 21 May 2025 20:31:01 -0700 Subject: [PATCH 164/378] Fix ea (Remove old version registry from chainweb-node) Change-Id: Id000000039d19a1e43d9193f3d3776bdb3234d73 --- cwtools/ea/Ea.hs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index fbaed6def8..89030f4519 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -45,7 +45,6 @@ import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Development (pattern Development) import Chainweb.Version.RecapDevelopment (pattern RecapDevelopment) -import Chainweb.Version.Registry (registerVersion) import Control.Concurrent.Async import Control.Exception import Control.Monad.Trans.Resource @@ -81,8 +80,6 @@ import Pact.Core.StableEncoding main :: IO () main = do - registerVersion RecapDevelopment - registerVersion Development mapConcurrently_ id [ devnet @@ -141,7 +138,7 @@ mkPayload :: Genesis -> IO Text mkPayload gen@(Genesis v _ cidr@(ChainIdRange l u) c k a ns cc) = do -- printf ("Generating Genesis Payload for %s on " <> show_ cidr <> "...\n") $ show v payloadModules <- for [l..u] $ \cid -> - genPayloadModule v (fullGenesisTag gen) (unsafeChainId cid) =<< mkChainwebTxs txs + withVersion v $ genPayloadModule (fullGenesisTag gen) (unsafeChainId cid) =<< mkChainwebTxs txs -- checks that the modules on each chain are the same evaluate $ the payloadModules where @@ -154,14 +151,14 @@ mkPayload gen@(Genesis v _ cidr@(ChainIdRange l u) c k a ns cc) = do -- Payload Generation --------------------- -genPayloadModule :: ChainwebVersion -> Text -> Chainweb.ChainId -> [Pact.Transaction] -> IO Text -genPayloadModule v tag cid cwTxs = do +genPayloadModule :: HasVersion => Text -> Chainweb.ChainId -> [Pact.Transaction] -> IO Text +genPayloadModule tag cid cwTxs = do let logger = genericLogger Warn TIO.putStrLn pdb <- newPayloadDb withSystemTempDirectory "ea-pact-db" $ \pactDbDir -> runResourceT $ do readWriteSql <- withSqliteDb cid logger pactDbDir False roPool <- withReadSqlitePool cid pactDbDir - serviceEnv <- withPactService v cid Nothing mempty logger Nothing pdb roPool readWriteSql defaultPactServiceConfig GeneratingGenesis + serviceEnv <- withPactService cid Nothing mempty logger Nothing pdb roPool readWriteSql defaultPactServiceConfig GeneratingGenesis payloadWO <- liftIO $ execNewGenesisBlock logger serviceEnv (V.fromList cwTxs) return $ TL.toStrict $ TB.toLazyText $ payloadModuleCode tag payloadWO From ea2e80fc1f81a67851e61a7abbd4cd18ff97eeab Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 22 May 2025 12:57:19 -0400 Subject: [PATCH 165/378] start running multinode with pact --- src/Chainweb/ChainId.hs | 11 +++++-- src/Chainweb/Chainweb/ChainResources.hs | 7 +++-- src/Chainweb/Chainweb/Configuration.hs | 12 ++++---- .../PayloadProvider/Pact/Configuration.hs | 5 ++++ src/Chainweb/PayloadProvider/Pact/Genesis.hs | 30 ++++++++++++++----- src/Chainweb/RestAPI/Config.hs | 5 ++-- test/lib/Chainweb/Test/MultiNode.hs | 14 +++++++++ test/lib/Chainweb/Test/Utils.hs | 2 +- test/multinode/MultiNodeNetworkTests.hs | 11 +++---- .../Chainweb/Test/Pact/PactServiceTest.hs | 2 +- 10 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index 7854979d4f..3f2d4b9ffb 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -101,7 +101,7 @@ import Chainweb.Utils import Chainweb.Utils.Serialization import Data.Singletons hiding (Index) -import Configuration.Utils +import Configuration.Utils hiding (Lens') -- -------------------------------------------------------------------------- -- -- Exceptions @@ -302,6 +302,8 @@ instance FunctorWithIndex ChainId ChainMap where instance FoldableWithIndex ChainId ChainMap where ifoldMap f (ChainMap a) = ifoldMap f a +instance TraversableWithIndex ChainId ChainMap where + itraverse f (ChainMap a) = ChainMap <$> itraverse f a instance Semialign ChainMap where align (ChainMap l) (ChainMap r) = ChainMap $ align l r @@ -339,7 +341,12 @@ type instance Index (ChainMap a) = ChainId type instance IxValue (ChainMap a) = a instance IxedGet (ChainMap a) where - ixg i = atChain i + ixg = atChain +instance Ixed (ChainMap a) where + ix i = at i . _Just +instance At (ChainMap a) where + at cid f = \case + ChainMap m -> ChainMap <$> at cid f m -- -------------------------------------------------------------------------- -- -- Configuration Utils diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 95f18f74f0..e0819a897a 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -57,6 +57,7 @@ module Chainweb.Chainweb.ChainResources , payloadServiceApiResources ) where +import Control.Applicative ((<|>)) import Control.Lens hiding ((.=), (<.>)) import Control.Monad.IO.Class import Control.Monad.Trans.Resource @@ -264,7 +265,7 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi , _providerResP2pApiResources = Just p2pRes } - SPactProvider -> case HM.lookup cid (_payloadProviderConfigPact configs) of + SPactProvider -> case _payloadProviderConfigPact configs ^. at cid of Just conf -> do -- , _pactGenesisPayload = Pact.genesisPayload v ^?! atChain chain @@ -311,7 +312,7 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi pdb pactDbDir pactConfig - (Pact.genesisPayload ^? atChain cid) + (Pact.genesisPayload cid <|> _pactConfigGenesisPayload conf) let mempoolConfig = Mempool.validatingMempoolConfig cid @@ -348,7 +349,7 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi _ -> return $ ProviderResources DisabledPayloadProvider Nothing Nothing - SEvmProvider @n _ -> case HM.lookup cid (_payloadProviderConfigEvm configs) of + SEvmProvider @n _ -> case _payloadProviderConfigEvm configs ^. at cid of Just config -> do -- This assumes that the respective execution client is available -- and answering API requests. diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index c46c2d661b..3202790fbd 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -131,8 +131,8 @@ import System.Directory -- data PayloadProviderConfig = PayloadProviderConfig { _payloadProviderConfigMinimal :: !MinimalProviderConfig - , _payloadProviderConfigPact :: !(HM.HashMap ChainId PactProviderConfig) - , _payloadProviderConfigEvm :: !(HM.HashMap ChainId EvmProviderConfig) + , _payloadProviderConfigPact :: !(ChainMap PactProviderConfig) + , _payloadProviderConfigEvm :: !(ChainMap EvmProviderConfig) } deriving (Show, Eq, Generic) @@ -150,8 +150,8 @@ defaultPayloadProviderConfig = PayloadProviderConfig validatePayloadProviderConfig :: HasVersion => ConfigValidation PayloadProviderConfig [] validatePayloadProviderConfig conf = do - void $ HM.traverseWithKey checkPactProvider $ _payloadProviderConfigPact conf - void $ HM.traverseWithKey checkEvmProvider $ _payloadProviderConfigEvm conf + void $ itraverse checkPactProvider $ _payloadProviderConfigPact conf + void $ itraverse checkEvmProvider $ _payloadProviderConfigEvm conf where checkPactProvider cid _conf = case payloadProviderTypeForChain cid of PactProvider -> return () -- FIXME implement validation @@ -179,11 +179,11 @@ instance ToJSON PayloadProviderConfig where where pacts = [ key c .= tag "pact" v - | (c, v) <- HM.toList (_payloadProviderConfigPact o) + | (c, v) <- itoList (_payloadProviderConfigPact o) ] evms = [ key c .= tag "evm" v - | (c, v) <- HM.toList (_payloadProviderConfigEvm o) + | (c, v) <- itoList (_payloadProviderConfigEvm o) ] others = L.sort $ pacts <> evms diff --git a/src/Chainweb/PayloadProvider/Pact/Configuration.hs b/src/Chainweb/PayloadProvider/Pact/Configuration.hs index d72e2c6ff7..599cfb110a 100644 --- a/src/Chainweb/PayloadProvider/Pact/Configuration.hs +++ b/src/Chainweb/PayloadProvider/Pact/Configuration.hs @@ -35,6 +35,7 @@ import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Pact (Miner(..), MinerGuard(..), MinerId(..)) import Chainweb.Pact.Types (defaultPreInsertCheckTimeout) +import Chainweb.Payload (PayloadWithOutputs) import Chainweb.Time hiding (second) import Chainweb.Utils import Chainweb.Version @@ -90,6 +91,7 @@ data PactProviderConfig = PactProviderConfig , _pactConfigEnableLocalTimeout :: !Bool , _pactConfigMiner :: !(Maybe Miner) , _pactConfigDatabaseDirectory :: !(Maybe FilePath) + , _pactConfigGenesisPayload :: !(Maybe PayloadWithOutputs) } deriving (Show, Eq, Generic) @@ -109,6 +111,7 @@ instance ToJSON PactProviderConfig where , "enableLocalTimeout" .= _pactConfigEnableLocalTimeout o , "miner" .= J.toJsonViaEncode (_pactConfigMiner o) , "databaseDirectory" .= _pactConfigDatabaseDirectory o + , "genesisPayload" .= _pactConfigGenesisPayload o ] instance FromJSON (PactProviderConfig -> PactProviderConfig) where @@ -125,6 +128,7 @@ instance FromJSON (PactProviderConfig -> PactProviderConfig) where <*< pactConfigEnableLocalTimeout ..: "enableLocalTimeout" % o <*< pactConfigMiner ..: "miner" % o <*< pactConfigDatabaseDirectory ..: "databaseDirectory" % o + <*< pactConfigGenesisPayload ..: "genesisPayload" % o defaultPactProviderConfig :: PactProviderConfig defaultPactProviderConfig = PactProviderConfig @@ -140,6 +144,7 @@ defaultPactProviderConfig = PactProviderConfig , _pactConfigEnableLocalTimeout = False , _pactConfigMiner = Nothing , _pactConfigDatabaseDirectory = Nothing + , _pactConfigGenesisPayload = Nothing } pPactProviderConfig :: ChainId -> MParser PactProviderConfig diff --git a/src/Chainweb/PayloadProvider/Pact/Genesis.hs b/src/Chainweb/PayloadProvider/Pact/Genesis.hs index 39a9c35573..762cb37214 100644 --- a/src/Chainweb/PayloadProvider/Pact/Genesis.hs +++ b/src/Chainweb/PayloadProvider/Pact/Genesis.hs @@ -18,7 +18,9 @@ import Chainweb.Version.Mainnet import Chainweb.Version.RecapDevelopment import Chainweb.Version.Testnet04 import Chainweb.Payload +import Control.Lens import Data.HashMap.Strict qualified as HM +import GHC.Stack import qualified Chainweb.BlockHeader.Genesis.Mainnet0Payload as MN0 import qualified Chainweb.BlockHeader.Genesis.Mainnet1Payload as MN1 @@ -40,9 +42,23 @@ import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment1to9Payload as RDN import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload as RDNKAD import Chainweb.Utils -genesisPayload :: HasVersion => ChainMap PayloadWithOutputs genesisPayload - | _versionCode implicitVersion == _versionCode Mainnet01 = ChainMap $ HM.fromList $ concat + :: (HasCallStack, HasVersion) + => ChainId -> Maybe PayloadWithOutputs +genesisPayload cid + | _versionCode implicitVersion == _versionCode Mainnet01 = + mainnetPayloads ^. at cid + | _versionCode implicitVersion == _versionCode Testnet04 = + testnet04Payloads ^. at cid + | _versionCode implicitVersion == _versionCode Development = + devnetPayloads ^. at cid + | _versionCode implicitVersion == _versionCode RecapDevelopment = + recapDevnetPayloads ^. at cid + | _versionCode implicitVersion == _versionCode EvmDevelopment = + evmDevnetPayloads ^. at cid + | otherwise = Nothing + where + mainnetPayloads = ChainMap $ HM.fromList $ concat [ [ (unsafeChainId 0, MN0.payloadBlock) , (unsafeChainId 1, MN1.payloadBlock) @@ -57,22 +73,20 @@ genesisPayload ] , [(unsafeChainId i, MNKAD.payloadBlock) | i <- [10..19]] ] - | _versionCode implicitVersion == _versionCode Testnet04 = ChainMap $ HM.fromList $ concat + testnet04Payloads = ChainMap $ HM.fromList $ concat [ [(unsafeChainId 0, T04N0.payloadBlock)] , [(unsafeChainId i, T04NN.payloadBlock) | i <- [1..19]] ] - | _versionCode implicitVersion == _versionCode Development = ChainMap $ HM.fromList $ concat + devnetPayloads = ChainMap $ HM.fromList $ concat [ [(unsafeChainId 0, DN0.payloadBlock)] , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] ] - | _versionCode implicitVersion == _versionCode RecapDevelopment = ChainMap $ HM.fromList $ concat + recapDevnetPayloads = ChainMap $ HM.fromList $ concat [ [(unsafeChainId 0, RDN0.payloadBlock)] , [(unsafeChainId i, RDNN.payloadBlock) | i <- [1..9]] , [(unsafeChainId i, RDNKAD.payloadBlock) | i <- [10..19]] ] - | _versionCode implicitVersion == _versionCode EvmDevelopment = ChainMap $ HM.fromList $ concat + evmDevnetPayloads = ChainMap $ HM.fromList $ concat [ [(unsafeChainId 0, DN0.payloadBlock)] , [(unsafeChainId i, DNN.payloadBlock) | i <- [1..19]] ] - | otherwise = error $ - "Chainweb.PayloadProvider.Pact.Genesis.genesisPayload: unsupported chainweb version: " <> sshow implicitVersion diff --git a/src/Chainweb/RestAPI/Config.hs b/src/Chainweb/RestAPI/Config.hs index e885bd82f2..d6e855b0c9 100644 --- a/src/Chainweb/RestAPI/Config.hs +++ b/src/Chainweb/RestAPI/Config.hs @@ -58,12 +58,11 @@ someGetConfigServer config = SomeServer (Proxy @GetConfigApi) $ return -- Miner Info $ set (configPayloadProviders . payloadProviderConfigMinimal . mpcRedeemAccount) invalidAccount - $ set (configPayloadProviders . payloadProviderConfigPact . each . pactConfigMiner) Nothing - $ set (configPayloadProviders . payloadProviderConfigEvm . each . evmConfMinerAddress) Nothing + $ set (configPayloadProviders . payloadProviderConfigPact . traversed . pactConfigMiner) Nothing + $ set (configPayloadProviders . payloadProviderConfigEvm . traversed . evmConfMinerAddress) Nothing -- Service API port $ set (configServiceApi . serviceApiConfigPort) 0 $ set (configServiceApi . serviceApiConfigInterface) "invalid" $ set configBackup defaultBackupConfig config - diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index a760652b84..da8620e0af 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -48,6 +48,8 @@ module Chainweb.Test.MultiNode import Control.Concurrent import Chainweb.BlockHash import Chainweb.BlockHeader +import Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload qualified as IN0 +import Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload qualified as INN import Chainweb.BlockHeight import Chainweb.Chainweb import Chainweb.Chainweb.Configuration @@ -64,6 +66,7 @@ import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (ChainGrandHash(..)) import Chainweb.Pact.Backend.PactState.GrandHash.Calc qualified as GrandHash.Calc import Chainweb.Pact.Backend.PactState.GrandHash.Import qualified as GrandHash.Import import Chainweb.Pact.Backend.PactState.GrandHash.Utils qualified as GrandHash.Utils +import Chainweb.PayloadProvider.Pact.Configuration (defaultPactProviderConfig, PactProviderConfig (_pactConfigGenesisPayload), _pactConfigMiner) import Chainweb.Storage.Table.RocksDB import Chainweb.Test.P2P.Peer.BootstrapConfig import Chainweb.Test.Utils @@ -106,6 +109,7 @@ import System.IO.Temp import System.LogLevel import System.Timeout import Test.Tasty.HUnit +import Chainweb.Miner.Pact -- -------------------------------------------------------------------------- -- -- * Configuration @@ -164,6 +168,16 @@ multiConfig n = defaultChainwebConfiguration implicitVersion & set (configServiceApi . serviceApiConfigPort) 0 & set (configServiceApi . serviceApiConfigInterface) interface & set (configCuts . cutFetchTimeout) 10_000_000 + & set (configPayloadProviders . payloadProviderConfigPact) + (onChains $ + [(cid, defaultPactProviderConfig + { _pactConfigGenesisPayload = Just gp + , _pactConfigMiner = Just noMiner + }) + | (cid, gp) <- + (unsafeChainId 0, IN0.payloadBlock) + : [(unsafeChainId cid, INN.payloadBlock) | cid <- [1..19]] + ]) where miner = NodeMiningConfig { _nodeMiningEnabled = True diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index ef0b2ee1ef..bf47ef46d3 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -1092,7 +1092,7 @@ config ver n = defaultChainwebConfiguration ver & set ( configPayloadProviders . payloadProviderConfigPact - . each + . traversed . pactConfigBlockGasLimit ) (Pact.GasLimit $ Pact.Gas 1_000_000) diff --git a/test/multinode/MultiNodeNetworkTests.hs b/test/multinode/MultiNodeNetworkTests.hs index 57b5a3487d..abf6c99d69 100644 --- a/test/multinode/MultiNodeNetworkTests.hs +++ b/test/multinode/MultiNodeNetworkTests.hs @@ -27,7 +27,7 @@ main :: IO () main = defaultMain suite loglevel :: LogLevel -loglevel = Debug +loglevel = Warn -- note that because these tests run in parallel they must all use distinct rocksdb and sqlite dirs. suite :: TestTree @@ -37,10 +37,11 @@ suite = independentSequentialTestGroup "MultiNodeNetworkTests" withSystemTempDirectory "multinode-tests-timedconsensus-petersen-twenty-pact" $ \pactDbDir -> withVersion (timedConsensusVersion petersenChainGraph twentyChainGraph) $ Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step - -- , testCaseSteps "ConsensusNetwork - InstantTimedCPM singleChainGraph - 10 nodes - 30 seconds" $ \step -> - -- withTempRocksDb "multinode-tests-instantcpm-single-rocks" $ \rdb -> - -- withSystemTempDirectory "multinode-tests-instantcpm-single-pact" $ \pactDbDir -> - -- Chainweb.Test.MultiNode.test loglevel (instantCpmTestVersion singletonChainGraph) 10 30 rdb pactDbDir step + , testCaseSteps "ConsensusNetwork - InstantTimedCPM singleChainGraph - 10 nodes - 30 seconds" $ \step -> + withTempRocksDb "multinode-tests-instantcpm-single-rocks" $ \rdb -> + withSystemTempDirectory "multinode-tests-instantcpm-single-pact" $ \pactDbDir -> + withVersion (instantCpmTestVersion singletonChainGraph) $ + Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step -- , testCaseSteps "Replay - InstantTimedCPM - 6 nodes" $ \step -> -- withTempRocksDb "replay-test-instantcpm-pair-rocks" $ \rdb -> -- withSystemTempDirectory "replay-test-instantcpm-pair-pact" $ \pactDbDir -> diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index 6eda32ec1b..b7084194da 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -66,8 +66,8 @@ import Test.Tasty import Test.Tasty.HUnit (assertBool, assertEqual, testCase) import Text.Printf (printf) import qualified Data.Pool as Pool -import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 +import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN import Chainweb.PayloadProvider.Pact (pactMemPoolAccess) import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer import Chainweb.Pact.Backend.Types (throwIfNoHistory, Historical) From 40ed9976774d70dbeb81818510739cefb32f7401 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 22 May 2025 13:35:16 -0400 Subject: [PATCH 166/378] formatting --- test/unit/Chainweb/Test/Pact/RemotePactTest.hs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index 81880f9882..816eb238b9 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -1127,7 +1127,10 @@ instantCpmTestVersionGenesis chain mkFixture :: HasVersion => RocksDb -> ResourceT IO Fixture mkFixture baseRdb = do - fx <- CutFixture.mkFixture instantCpmTestVersionGenesis defaultPactServiceConfig { _pactBlockRefreshInterval = 10_000 } baseRdb + fx <- CutFixture.mkFixture + instantCpmTestVersionGenesis + defaultPactServiceConfig { _pactBlockRefreshInterval = 10_000 } + baseRdb logger <- liftIO getTestLogger let mkSomePactServerData cid = PactServerData From 45e9d219c7e893dce04777566bf7f51a7f4da5ce Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 14:09:04 -0400 Subject: [PATCH 167/378] Add missing space Change-Id: Id0000000d50850aa2448a794800daefb480fe8b4 --- src/Chainweb/HostAddress.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/HostAddress.hs b/src/Chainweb/HostAddress.hs index caf6eee025..555f259bb6 100644 --- a/src/Chainweb/HostAddress.hs +++ b/src/Chainweb/HostAddress.hs @@ -470,7 +470,7 @@ hostAddressProperties o = {-# INLINE hostAddressProperties #-} instance ToJSON HostAddress where - toJSON = object. hostAddressProperties + toJSON = object . hostAddressProperties toEncoding = pairs . mconcat . hostAddressProperties {-# INLINE toJSON #-} {-# INLINE toEncoding #-} From 9f1707c060c17f34642d51b9599f541c45fa11a3 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 14:09:04 -0400 Subject: [PATCH 168/378] Log when minimal PP is told not to make a new payload Change-Id: Id0000000772ba6ef61844e5a038e8a0fa94e0382 --- src/Chainweb/PayloadProvider/Minimal.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index f589a4a0be..4ff7e19829 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -425,7 +425,9 @@ minimalSyncToBlock p h i = do -- Produce new block case _forkInfoNewBlockCtx i of - Nothing -> return () + Nothing -> do + logg p Info $ "no new payload for sync state: " <> sshow latestState + return () Just ctx -> do logg p Info $ "create new payload for sync state: " <> sshow latestState atomically From 12eeb8c80cbd0d6790e5ffe353de935b097d11cf Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 14:28:43 -0400 Subject: [PATCH 169/378] Lower log severities for some warnings Change-Id: Id0000000aebf7ae3cc0eb8286d23f53bb364b238 --- src/Chainweb/Chainweb/PeerResources.hs | 2 +- src/Chainweb/CutDB.hs | 2 +- src/Chainweb/Miner/Coordinator.hs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/Chainweb/PeerResources.hs b/src/Chainweb/Chainweb/PeerResources.hs index 43535622e0..fbd14f1ae9 100644 --- a/src/Chainweb/Chainweb/PeerResources.hs +++ b/src/Chainweb/Chainweb/PeerResources.hs @@ -173,7 +173,7 @@ withHost -> IO a withHost mgr conf logger f | null peers = do - logFunctionText logger Warn + logFunctionText logger Info $ "Unable to verify configured host " <> toText confHost <> ": No peers are available." f (set (p2pConfigPeer . peerConfigHost) confHost conf) | anyIpv4 == confHost = do diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 36d7e1ae96..6a1bc951bd 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -477,7 +477,7 @@ readHighestCutHeaders logg wbhdb cutHashesStore = withTableIterator (unCasify cu -- or iterate in increasinly larger steps? go it = iterValue it >>= \case Nothing -> do - logg Warn "No initial cut found in database or configuration, falling back to genesis cut" + logg Info "No initial cut found in database or configuration, falling back to genesis cut" return $ view cutMap genesisCut Just ch -> try (lookupCutHashes wbhdb ch) >>= \case Left (e@(TreeDbKeyNotFound _) :: TreeDbException BlockHeaderDb) -> do diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 3756746d32..b28e318a78 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -602,7 +602,7 @@ randomWork logFun caches parentStateVars = do go [] = do - logFun @T.Text Warn $ "randomWork: no work is ready. Awaiting work" + logFun @T.Text Info $ "randomWork: no work is ready. Awaiting work" -- We shall check for the following conditions: -- From f19554eb8b559f8cb39961406a54dc50e9256b13 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 22:42:17 -0400 Subject: [PATCH 170/378] Comment on mystery error Change-Id: Id000000078c7d90332931ede1f45bda0b34ebc26 --- src/Chainweb/Miner/Miners.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index d204870395..5e4e488f95 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -96,6 +96,7 @@ localTest lf coord cdb gen miners = runForever lf "Chainweb.Miner.Miners.localTest" $ do c <- _cut cdb wh <- work coord + -- TODO: I've seen this error, why? let height = c ^?! ixg (_miningWorkChainId wh) . blockHeight race (awaitNewCutByChainId cdb (_miningWorkChainId wh) c) (go height wh) >>= \case From 93a7d503c06ba62ee88083745cc138ae6b2c6bc3 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 14:09:04 -0400 Subject: [PATCH 171/378] Log work status summary on work timeout Change-Id: Id0000000e704c091b31601d3960b358f2e927450 --- src/Chainweb/Miner/Coordinator.hs | 32 ++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 3756746d32..abff65b0e7 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -107,6 +107,9 @@ import Numeric.Natural import Streaming.Prelude qualified as S import System.LogLevel (LogLevel(..)) import System.Random (randomRIO) +import qualified Data.Aeson as Aeson +import qualified Data.List as List +import qualified Pact.JSON.Encode as J -- -------------------------------------------------------------------------- -- -- Utils @@ -562,8 +565,8 @@ awaitEvent cdb caches c p = -- 3. some payload providers are deadlocked, or -- 4. some payload providers are very slow in producing new payloads. -- -randomWork :: HasVersion => LogFunction -> PayloadCaches -> ChainMap (TVar (Maybe ParentState)) -> IO MiningWork -randomWork logFun caches parentStateVars = do +randomWork :: HasVersion => LogFunction -> CutDb -> PayloadCaches -> ChainMap (TVar (Maybe ParentState)) -> IO MiningWork +randomWork logFun cdb caches parentStateVars = do -- Pick a random chain. -- @@ -637,7 +640,7 @@ randomWork logFun caches parentStateVars = do Right (ps, npld) -> do ct <- BlockCreationTime <$> getCurrentTimeIntegral return $ newWork ct ps (_newPayloadBlockPayloadHash npld) - Left e -> error e -- FIXME: throw a proper exception and log what is going on + Left e -> error (T.unpack e) -- FIXME: throw a proper exception and log what is going on go ((cid, var):t) = do readyCheck <- atomically $ @@ -666,7 +669,26 @@ randomWork logFun caches parentStateVars = do -- This is clearly an error, probably even a bug. -- We should try to find out what happend. -- - return $ Left "Chainweb.Miner.Coordinator.randomWork: timeout while waiting for work to become ready" + workStatusSummary <- summarizeWorkStatus + return $ Left $ "Chainweb.Miner.Coordinator.randomWork: timeout while waiting for work to become ready. " <> workStatusSummary + + summarizeWorkStatus = do + parentStates <- forM parentStateVars readTVar + let chainsWithMissingParents = + [ cid | (cid, Nothing) <- itoList parentStates ] + let chainsWithParents = onChains + [ (cid, p) | (cid, Just p) <- itoList parentStates ] + payloads <- forM + (chainIntersect (,) chainsWithParents caches) + (\(parent, cache) -> Just <$> awaitLatestPayloadForParentStateSTM cache parent <|> return Nothing) + let chainsWithMissingPayloads = + [ cid | (cid, Nothing) <- itoList payloads ] + c <- _cutStm cdb + return $ J.encodeText $ J.object + [ "blocked" J..= J.array (List.sort (toText <$> chainsWithMissingParents)) + , "stale" J..= J.array (List.sort (toText <$> chainsWithMissingPayloads)) + , "cut" J..= brief c + ] staleMiningStateDelay :: Micros staleMiningStateDelay = 2_000_000 @@ -679,7 +701,7 @@ work => HasVersion => MiningCoordination l -> IO MiningWork -work mr = randomWork lf (_coordPayloadCache mr) (_coordParentState mr) +work mr = randomWork lf (_coordCutDb mr) (_coordPayloadCache mr) (_coordParentState mr) where lf :: LogFunction lf = logFunction $ _coordLogger mr From 593b445ed0e6ec96a61ab76852c4a032bb446820 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 14:28:43 -0400 Subject: [PATCH 172/378] idk: Bind to 127.0.0.1 instead of localhost to avoid a warning Change-Id: Id000000059f93cee943b0b754833081a3c00bea5 --- test/lib/Chainweb/Test/Utils.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index ef0b2ee1ef..0b43b1ffd9 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -1120,7 +1120,7 @@ setBootstrapPeerInfo = host :: Hostname -- host = unsafeHostnameFromText "::1" -host = unsafeHostnameFromText "localhost" +host = unsafeHostnameFromText "127.0.0.1" interface :: W.HostPreference -- interface = "::1" From 40cf85592195c5dc38d11d511ccb5ffbdc829bd6 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 20:03:10 -0400 Subject: [PATCH 173/378] Rename variable Change-Id: Id00000002d4d715d1734b2b6c776646f19a1c563 --- src/Chainweb/Miner/Coordinator.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 3756746d32..6e5e7e8963 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -280,18 +280,18 @@ updateForCut lf hdb ms c = do forM_ (HM.keys (c ^. cutMap)) forChain where forChain cid = do - let var = ms ^?! atChain cid + let parentStateVar = ms ^?! atChain cid maybeNewParents <- workParents hdb c cid atomically $ do - maybeOldParentState <- readTVar var + maybeOldParentState <- readTVar parentStateVar case (maybeOldParentState, maybeNewParents) of (_, Nothing) -> - writeTVar var Nothing + writeTVar parentStateVar Nothing (Just oldParentState, Just newParents) | parentStateParents oldParentState == newParents -> return () (_, Just newParents) -> - writeTVar var $ Just + writeTVar parentStateVar $ Just ParentState { parentStateParents = newParents , parentStateSolved = Nothing @@ -456,7 +456,7 @@ runCoordination mr = do & S.chain (\_ -> lf Debug $ "update cache on chain " <> toText cid) & S.mapM_ (insertIO cache) where - label = "miningCoordination.updateCache." <> toText cid + label = "miningCoordination.updateCache." <> toText cid -- Update the work state -- From baf2b04b134f132dbb10eb41dc7c9f5e80d5c089 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 20:03:52 -0400 Subject: [PATCH 174/378] Delete unnecessary initialization Change-Id: Id0000000c2a18ed0264cf053f2fb152c14bfc555 --- src/Chainweb/Miner/Coordinator.hs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 3756746d32..7540760382 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -473,14 +473,8 @@ runCoordination mr = do -- TODO: is there still? initializeState = do - lf Debug $ "initialize mining state" + lf Debug "initialize mining state" curCut <- _cut $ cdb - forConcurrently_ (HM.keys (curCut ^. cutMap)) $ \cid -> do - let cache = caches ^?! atChain cid - lf Debug $ "initialize mining state for chain " <> brief cid - pld <- withProvider cid latestPayloadIO - lf Debug $ "got latest payload for chain " <> brief cid - insertIO cache pld updateForCut lf f state curCut lf Debug "done initializing mining state for all chains" From 818fd64d1a0814f43c96bbff256f1037e2259346 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 21 May 2025 14:10:42 -0400 Subject: [PATCH 175/378] Document invariant of joinIntoHeavier Change-Id: Id00000004259561c41050ff0e716f036af6f94aa --- src/Chainweb/Cut/Create.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 365f609db5..18bafc9c4a 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -981,6 +981,11 @@ applyJoin m = cutProjectChains -- chains, make sure that @genesisBlockHeaders v@ only returns genesis headers -- for those chains that you care about. -- +-- This invariant is required during a chain graph transition to avoid producing +-- an invalid cut that straddles the transition line: A cut that is ahead or +-- equal in height on all chains and ahead in height on at least one chain must +-- have a higher total weight. +-- joinIntoHeavier :: HasVersion => WebBlockHeaderDb From 3251ea49920264cbb5c5de24ca286ef18c398d94 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 21 May 2025 13:27:41 -0400 Subject: [PATCH 176/378] Document invalid cut errors Change-Id: Id0000000bda2a42d01a04b222ddbf299b21ab252 --- src/Chainweb/Cut/Create.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 365f609db5..7faa7cf4fa 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -292,12 +292,15 @@ getCutExtension c cid = do $ "getAdjacentParents: detected invalid cut (adjacent parent too far ahead)." <> "\n Parent: " <> encodeToText (ObjectEncoded p) <> "\n Conflict: " <> encodeToText (ObjectEncoded b) + <> "\n Cut: " <> brief c | view blockHeight b + 1 < parentHeight = error $ T.unpack $ "getAdjacentParents: detected invalid cut (adjacent parent too far behind)." <> "\n Parent: " <> encodeToText (ObjectEncoded p) <> "\n Conflict: " <> encodeToText (ObjectEncoded b) - | otherwise = error - $ "Chainweb.Miner.Coordinator.getAdjacentParents: internal code invariant violation" + <> "\n Cut: " <> brief c + | otherwise = error $ T.unpack + $ "Chainweb.Miner.Coordinator.getAdjacentParents: internal code invariant violation:" + <> "\n Cut: " <> brief c -- -------------------------------------------------------------------------- -- -- Mining Work From 460de3409d0cbfc9712087d51c389252abf518ca Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 21 May 2025 15:17:37 -0400 Subject: [PATCH 177/378] NOTESTS: sync pps with merged cut Change-Id: Id0000000b1143f50249dcd9b1c67fa301b3d1a4a --- src/Chainweb/CutDB.hs | 12 ++++++++++++ src/Chainweb/PayloadProvider.hs | 7 +++++++ src/Chainweb/Sync/WebBlockHeaderStore.hs | 4 +++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 6a1bc951bd..4f2ec6f03d 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -572,6 +572,18 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do loggCutId logFun Debug newCut "writing cut" casInsert cutHashesStore (cutToCutHashes Nothing resultCut) atomically $ writeTVar cutVar resultCut + -- ensure that payload providers are in sync with the *merged* + -- cut, so that they produce payloads on the correct parents. + iforM_ (_cutMap resultCut) $ \cid bh -> do + case providers ^?! atChain cid of + ConfiguredPayloadProvider provider -> do + finfo <- forkInfoForHeader hdrStore bh Nothing + r <- syncToBlock provider Nothing finfo + unless (r == _forkInfoTargetState finfo) $ do + error $ "unexpected result state" + <> "; expected: " <> sshow (_forkInfoTargetState finfo) + <> "; actual: " <> sshow r + _ -> return () let cutDiff = cutDiffToTextShort curCut resultCut let currentCutIdMsg = T.unwords [ "current cut is now" diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index b8dde221c1..70550297c0 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -9,6 +9,7 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE TemplateHaskell #-} -- | -- Module: Chainweb.PayloadProvider @@ -49,6 +50,10 @@ module Chainweb.PayloadProvider -- * Fork Info , ForkInfo(..) +, forkInfoNewBlockCtx +, forkInfoBasePayloadHash +, forkInfoTargetState +, forkInfoTrace , PayloadProvider(..) , EncodedPayloadData(..) , EncodedPayloadOutputs(..) @@ -1003,3 +1008,5 @@ genesisState c = ConsensusState , _syncStateBlockPayloadHash = view blockPayloadHash hdr } hdr = genesisBlockHeader c + +makeLenses ''ForkInfo diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 28325817f1..3c9e97244a 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -397,7 +397,9 @@ getBlockHeaderInternal -- Get the Payload Provider and let hints = Hints <$> maybeOrigin' pld <- tableLookup candidatePldTbl (view blockPayloadHash header) - finfo <- forkInfoForHeader wdb header pld + -- Do not produce payloads at this point; we may not stick around at + -- this block. + finfo <- forkInfoForHeader wdb header pld <&> forkInfoNewBlockCtx .~ Nothing let prefetchProviderPayloads = case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> prefetchPayloads provider hints finfo From 6a520726b10f4693f73aecd463cd7b555fa0b83f Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 23 May 2025 10:35:35 -0400 Subject: [PATCH 178/378] Stop using fork info to prefetch payloads Change-Id: Id0000000c18ed2d3b9c3f5da8059fc1f8dd97fcf --- src/Chainweb/Chainweb.hs | 2 +- src/Chainweb/CutDB.hs | 4 ++-- src/Chainweb/Pact/PactService.hs | 4 +++- src/Chainweb/PayloadProvider.hs | 7 ++---- src/Chainweb/PayloadProvider/Minimal.hs | 20 +++++++++-------- src/Chainweb/Ranked.hs | 3 ++- src/Chainweb/Sync/WebBlockHeaderStore.hs | 28 ++++++++++++++---------- src/P2P/TaskQueue.hs | 4 ++-- test/multinode/MultiNodeNetworkTests.hs | 12 +++++----- 9 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 1606df282a..370d514ddf 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -532,7 +532,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba "sync payload provider to " <> sshow (view blockHeight hdr) <> ":" <> sshow (view blockHash hdr) - finfo <- forkInfoForHeader wbh hdr Nothing + finfo <- forkInfoForHeader wbh hdr Nothing Nothing logFunctionText loggr Debug $ "syncToBlock with fork info " <> sshow finfo r <- syncToBlock provider Nothing finfo `catch` \(e :: SomeException) -> do logFunctionText loggr Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 4f2ec6f03d..168451869b 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -571,13 +571,12 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do maybePrune rng (cutAvgBlockHeight curCut) loggCutId logFun Debug newCut "writing cut" casInsert cutHashesStore (cutToCutHashes Nothing resultCut) - atomically $ writeTVar cutVar resultCut -- ensure that payload providers are in sync with the *merged* -- cut, so that they produce payloads on the correct parents. iforM_ (_cutMap resultCut) $ \cid bh -> do case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do - finfo <- forkInfoForHeader hdrStore bh Nothing + finfo <- forkInfoForHeader hdrStore bh Nothing Nothing r <- syncToBlock provider Nothing finfo unless (r == _forkInfoTargetState finfo) $ do error $ "unexpected result state" @@ -595,6 +594,7 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do then T.unwords (x : xs) else T.intercalate "\n" (x : (map (" " <>) xs)) logFun @T.Text Info $ catOverflowing currentCutIdMsg cutDiff + atomically $ writeTVar cutVar resultCut ) where diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 78a8949293..715015456c 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -517,7 +517,9 @@ syncToFork logger serviceEnv hints forkInfo = do <> " using trace blocks " <> brief traceBlockHashes findForkChain (zip forkInfo._forkInfoTrace traceBlockHashes) >>= \case Nothing -> do - logFunctionText logger Error $ "impossible to move to " <> brief forkInfo._forkInfoTargetState + logFunctionText logger Warn $ + "impossible to move to " <> brief forkInfo._forkInfoTargetState + <> " from " <> brief pactConsensusState -- error: we have no way to get to the target block. just report -- our current state and do nothing else. return (mempty, mempty, pactConsensusState) diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 70550297c0..7a5452209a 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -125,6 +125,7 @@ import Streaming.Prelude qualified as S import Data.Function import Data.Hashable import Data.Maybe +import Chainweb.Ranked -- -------------------------------------------------------------------------- -- -- Exceptions @@ -769,11 +770,7 @@ class (HasChainId p) => PayloadProvider p where :: HasVersion => p -> Maybe Hints - -> ForkInfo - -- ^ TODO: do we really want to pass the full ForkInfo here? What is - -- the purpose of passing the full Consensus State? Maybe resolving - -- forks? Maybe a list of RankedBlockPayloadHashes (or - -- EvaluationCtx) would be sufficient here? + -> [Ranked ConsensusPayload] -> IO () -- | Request that the payload provider updates its internal state to diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index 4ff7e19829..cb086e77b6 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -85,6 +85,7 @@ module Chainweb.PayloadProvider.Minimal ) where import Configuration.Utils +import Control.Concurrent import Control.Concurrent.Async import Control.Concurrent.STM import Control.Lens hiding ((.=)) @@ -350,16 +351,16 @@ instance PayloadProvider MinimalPayloadProvider where getPayloadForContext :: MinimalPayloadProvider -> Maybe Hints - -> EvaluationCtx ConsensusPayload + -> Ranked ConsensusPayload -> IO Payload getPayloadForContext p h ctx = do - insertPayloadData (_consensusPayloadData $ _evaluationCtxPayload ctx) + insertPayloadData (_consensusPayloadData $ _ranked ctx) pld <- Rest.getPayload (_minimalPayloadStore p) (_minimalCandidatePayloads p) - (Priority $ negate $ int $ unwrapParent $ _evaluationCtxParentHeight ctx) + (Priority $ negate $ int $ _rankedHeight ctx) (_hintsOrigin <$> h) - (_evaluationCtxRankedPayloadHash ctx) + (_consensusPayloadHash <$> ctx) casInsert (_minimalCandidatePayloads p) pld return pld where @@ -383,11 +384,11 @@ minimalPrefetchPayloads :: HasVersion => MinimalPayloadProvider -> Maybe Hints - -> ForkInfo + -> [Ranked ConsensusPayload] -> IO () -minimalPrefetchPayloads p h i = do +minimalPrefetchPayloads p h ps = do logg p Info "prefetch payloads" - mapConcurrently_ (getPayloadForContext p h) $ _forkInfoTrace i + mapConcurrently_ (try @_ @TaskException . getPayloadForContext p h) ps -- | -- @@ -482,10 +483,11 @@ validatePayloads -> Maybe Hints -> ForkInfo -> IO () -validatePayloads p h i= mapConcurrently_ go (_forkInfoTrace i) +validatePayloads p h i = mapConcurrently_ go (_forkInfoTrace i) where go ctx = do - pld <- getPayloadForContext p h ctx + pld <- getPayloadForContext p h + (Ranked (_evaluationCtxCurrentHeight ctx) (_evaluationCtxPayload ctx)) validatePayload p pld ctx casInsert (_minimalPayloadStore p) pld diff --git a/src/Chainweb/Ranked.hs b/src/Chainweb/Ranked.hs index 4c5713382d..712c81406a 100644 --- a/src/Chainweb/Ranked.hs +++ b/src/Chainweb/Ranked.hs @@ -10,6 +10,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DeriveFunctor #-} -- | -- Module: Chainweb.Ranked @@ -66,7 +67,7 @@ data Ranked a = Ranked { _rankedHeight :: !BlockHeight , _ranked :: !a } - deriving (Show, Eq, Ord, Generic) + deriving stock (Functor, Show, Eq, Ord, Generic) deriving anyclass (Hashable, NFData) makeLenses ''Ranked diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 3c9e97244a..f22a1506ff 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -233,8 +233,9 @@ forkInfoForHeader => WebBlockHeaderDb -> BlockHeader -> Maybe EncodedPayloadData + -> Maybe (Parent BlockHeader) -> IO ForkInfo -forkInfoForHeader wdb hdr pldData +forkInfoForHeader wdb hdr pldData parentHdr -- FIXME do we need this case??? We never add genesis headers... | isGenesisBlockHeader hdr = do @@ -247,7 +248,7 @@ forkInfoForHeader wdb hdr pldData } | otherwise = do - phdr <- lookupParentHeader wdb hdr + phdr <- maybe (lookupParentHeader wdb hdr) return parentHdr let consensusPayload = ConsensusPayload { _consensusPayloadHash = pld , _consensusPayloadData = pldData @@ -378,11 +379,11 @@ getBlockHeaderInternal -- header. There's another complete pass of block header validations -- after payload validation when the header is finally added to the db. -- - queryParent p = Concurrently $ void $ do + queryParent p = Concurrently $ do logg Debug $ taskMsg k $ "getBlockHeaderInternal.getPrerequisiteHeader (parent) for " <> sshow h <> ": " <> sshow p - void $ getBlockHeaderInternal + parentHdr <- Parent <$> getBlockHeaderInternal headerStore candidateHeaderCas candidatePldTbl @@ -393,27 +394,26 @@ getBlockHeaderInternal (unwrapParent <$> p) chainDb <- getWebBlockHeaderDb wdb header validateInductiveChainM (tableLookup chainDb) header + return $ fmap _chainValueValue parentHdr -- Get the Payload Provider and let hints = Hints <$> maybeOrigin' pld <- tableLookup candidatePldTbl (view blockPayloadHash header) - -- Do not produce payloads at this point; we may not stick around at - -- this block. - finfo <- forkInfoForHeader wdb header pld <&> forkInfoNewBlockCtx .~ Nothing let prefetchProviderPayloads = case providers ^?! atChain cid of - ConfiguredPayloadProvider provider -> - prefetchPayloads provider hints finfo - -- TODO (_rankedBlockPayloadHash header) + ConfiguredPayloadProvider provider -> return () + -- TODO PP + -- prefetchPayloads provider hints + -- [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] DisabledPayloadProvider -> return () - runConcurrently + parentHdr <- runConcurrently -- instruct the payload provider to fetch payload data and prepare -- validation. $ Concurrently prefetchProviderPayloads -- query parent (recursively) -- - <* queryParent (view blockParent <$> chainValue header) + *> queryParent (view blockParent <$> chainValue header) -- query adjacent parents (recursively) <* mconcat (queryAdjacentParent <$> adjParents header) @@ -462,6 +462,10 @@ getBlockHeaderInternal -- validate the payload for this block header. -- + -- Do not produce payloads at this point; we may not stick around at + -- this block. + finfo <- forkInfoForHeader wdb header pld (Just parentHdr) <&> forkInfoNewBlockCtx .~ Nothing + logg Debug $ taskMsg k $ "getBlockHeaderInternal validate payload for " <> sshow h case providers ^?! atChain cid of diff --git a/src/P2P/TaskQueue.hs b/src/P2P/TaskQueue.hs index d7ad02dae4..5206f2e57a 100644 --- a/src/P2P/TaskQueue.hs +++ b/src/P2P/TaskQueue.hs @@ -204,8 +204,8 @@ session_ limit q logFun env = E.mask $ \restore -> do return False | otherwise -> do logg task' Warn - $ "task finally failed: " <> sshow attempts - <> ", limit: " <> sshow limit + $ "task finally failed with: " <> sshow e <> " after " <> sshow attempts <> " attempts, limit: " + <> sshow limit putResult (_taskResult task') $! Left $! _taskFailures task' logg task l m = logFun @T.Text l $ sshow (_taskId task) <> ": " <> m diff --git a/test/multinode/MultiNodeNetworkTests.hs b/test/multinode/MultiNodeNetworkTests.hs index abf6c99d69..fa4fb8763c 100644 --- a/test/multinode/MultiNodeNetworkTests.hs +++ b/test/multinode/MultiNodeNetworkTests.hs @@ -32,12 +32,12 @@ loglevel = Warn -- note that because these tests run in parallel they must all use distinct rocksdb and sqlite dirs. suite :: TestTree suite = independentSequentialTestGroup "MultiNodeNetworkTests" - [ testCaseSteps "ConsensusNetwork - TimedConsensus - 10 nodes - 30 seconds" $ \step -> - withTempRocksDb "multinode-tests-timedconsensus-petersen-twenty-rocks" $ \rdb -> - withSystemTempDirectory "multinode-tests-timedconsensus-petersen-twenty-pact" $ \pactDbDir -> - withVersion (timedConsensusVersion petersenChainGraph twentyChainGraph) $ - Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step - , testCaseSteps "ConsensusNetwork - InstantTimedCPM singleChainGraph - 10 nodes - 30 seconds" $ \step -> + -- [ testCaseSteps "ConsensusNetwork - TimedConsensus - 10 nodes - 30 seconds" $ \step -> + -- withTempRocksDb "multinode-tests-timedconsensus-petersen-twenty-rocks" $ \rdb -> + -- withSystemTempDirectory "multinode-tests-timedconsensus-petersen-twenty-pact" $ \pactDbDir -> + -- withVersion (timedConsensusVersion petersenChainGraph twentyChainGraph) $ + -- Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step + [ testCaseSteps "ConsensusNetwork - InstantTimedCPM singleChainGraph - 10 nodes - 30 seconds" $ \step -> withTempRocksDb "multinode-tests-instantcpm-single-rocks" $ \rdb -> withSystemTempDirectory "multinode-tests-instantcpm-single-pact" $ \pactDbDir -> withVersion (instantCpmTestVersion singletonChainGraph) $ From 62c643cbbcca77db9ad0d5bb24ceb6f3c15864a4 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 23 May 2025 14:59:03 -0400 Subject: [PATCH 179/378] Fix fork resolution --- src/Chainweb/Core/Brief.hs | 6 ++++++ src/Chainweb/Pact/PactService.hs | 2 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 23 +++++++++++++---------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index b8fb039722..81556d379b 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -99,6 +99,12 @@ instance Brief (Parent BlockHeader) where brief = brief . unwrapParent deriving via (CryptoHash AdjacentsHashAlgorithm) instance Brief AdjacentsHash +instance Brief ConsensusPayload where + brief = brief . _consensusPayloadHash +instance Brief p => Brief (EvaluationCtx p) where + brief ec = + "payload:" <> brief (_evaluationCtxPayload ec) + <> ":parent:" <> brief (_evaluationCtxRankedParentHash ec) deriving via (BriefText (CryptoHash a)) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 715015456c..1bc3d934cd 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -517,7 +517,7 @@ syncToFork logger serviceEnv hints forkInfo = do <> " using trace blocks " <> brief traceBlockHashes findForkChain (zip forkInfo._forkInfoTrace traceBlockHashes) >>= \case Nothing -> do - logFunctionText logger Warn $ + logFunctionText logger Info $ "impossible to move to " <> brief forkInfo._forkInfoTargetState <> " from " <> brief pactConsensusState -- error: we have no way to get to the target block. just report diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index f22a1506ff..e9073dda09 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -84,6 +84,8 @@ import System.LogLevel import Utils.Logging.Trace import Chainweb.Parent import Streaming.Prelude qualified as S +import Data.These (partitionHereThere) +import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- -- Response Timeout Constants @@ -475,29 +477,30 @@ getBlockHeaderInternal throwM e if r /= _forkInfoTargetState finfo then do - let ppBlock = _syncStateRankedBlockHash $ _consensusStateLatest r - let targetBlock = _syncStateRankedBlockHash $ _consensusStateLatest $ _forkInfoTargetState finfo bhdb <- getWebBlockHeaderDb wdb cid - let forkBlocksDescendingStream = getBranch bhdb - (HS.singleton $ LowerBound (_ranked ppBlock)) - (HS.singleton $ UpperBound (_ranked targetBlock)) - -- TODO: what if this is empty? - forkBlocksAscending <- fmap reverse $ S.toList_ forkBlocksDescendingStream + let ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest r + let targetRBH = _syncStateRankedBlockHash $ _consensusStateLatest $ _forkInfoTargetState finfo + ppBlock <- lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH) + targetBlock <- lookupRankedM bhdb (int $ _rankedHeight targetRBH) (_ranked targetRBH) + (forkBlocksDescendingStream S.:> forkPoint) <- + S.toList $ branchDiff_ bhdb ppBlock targetBlock + let forkBlocksAscending = reverse $ snd $ partitionHereThere forkBlocksDescendingStream let newTrace = zipWith (\prent child -> ConsensusPayload (view blockPayloadHash child) Nothing <$ blockHeaderToEvaluationCtx (Parent prent)) + (forkPoint : forkBlocksAscending) forkBlocksAscending - (tail forkBlocksAscending) let newForkInfo = finfo { _forkInfoTrace = newTrace } r' <- syncToBlock provider hints newForkInfo `catch` \(e :: SomeException) -> do logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation retry for " <> sshow h <> " failed with: " <> sshow e throwM e unless (r' == _forkInfoTargetState finfo) $ do throwM $ GetBlockHeaderFailure $ "unexpected result state" - <> "; expected: " <> sshow (_forkInfoTargetState finfo) - <> "; actual: " <> sshow r + <> "; expected: " <> brief (_forkInfoTargetState finfo) + <> "; actual: " <> brief r + <> "fork blocks: " <> brief forkBlocksAscending else return () DisabledPayloadProvider -> do From 2f6c1d90aef48ac7e981c02e9e7af8ecf8f486d0 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 23 May 2025 15:30:30 -0400 Subject: [PATCH 180/378] Insert payloads on-demand instead of from the stream --- src/Chainweb/Miner/Coordinator.hs | 20 ++++++-------------- src/Chainweb/Pact/Backend/Utils.hs | 2 +- test/multinode/MultiNodeNetworkTests.hs | 12 ++++++------ 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 553525b70e..8fd55d17cc 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -425,9 +425,7 @@ runCoordination mr = do -- fail to start mining. initializeState - concurrentlies_ - $ updateWork - : toList (imap updateCache caches) + updateWork where lf :: LogFunctionText lf = logFunction $ _coordLogger mr @@ -450,17 +448,6 @@ runCoordination mr = do DisabledPayloadProvider -> error $ "payload provider disabled on chain " <> sshow cid <> ", which is illegal for miners" - -- Update the payload cache with the latest payloads from the the provider - -- - updateCache cid cache = - withProvider cid $ \provider -> - runForever lf label $ do - payloadStream provider - & S.chain (\_ -> lf Debug $ "update cache on chain " <> toText cid) - & S.mapM_ (insertIO cache) - where - label = "miningCoordination.updateCache." <> toText cid - -- Update the work state -- updateWork = runForever lf "miningCoordination" $ do @@ -594,6 +581,11 @@ randomWork logFun cdb caches parentStateVars = do awaitWorkReady cid var = do parentState <- maybe retry return =<< readTVar var guard (isNothing $ parentStateSolved parentState) + case cdb ^?! cutDbPayloadProviders . atChain cid of + ConfiguredPayloadProvider pp -> + latestPayloadSTM pp >>= insertSTM (caches ^?! atChain cid) + DisabledPayloadProvider -> + error $ "payload provider disabled on chain " <> sshow cid <> ", which is illegal for miners" payload <- awaitLatestPayloadForParentStateSTM (caches ^?! atChain cid) parentState return (parentStateParents parentState, payload) diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index e1c9aab4f3..ab3bb98bb3 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -194,7 +194,7 @@ rollbackSavepoint db name = -- | @abortSavepoint n@ rolls back all database updates since the most recent -- savepoint with the name @n@ and removes it from the savepoint stack. -abortSavepoint :: SQLiteEnv -> SavepointName -> IO () +abortSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO () abortSavepoint db name = do rollbackSavepoint db name commitSavepoint db name diff --git a/test/multinode/MultiNodeNetworkTests.hs b/test/multinode/MultiNodeNetworkTests.hs index fa4fb8763c..abf6c99d69 100644 --- a/test/multinode/MultiNodeNetworkTests.hs +++ b/test/multinode/MultiNodeNetworkTests.hs @@ -32,12 +32,12 @@ loglevel = Warn -- note that because these tests run in parallel they must all use distinct rocksdb and sqlite dirs. suite :: TestTree suite = independentSequentialTestGroup "MultiNodeNetworkTests" - -- [ testCaseSteps "ConsensusNetwork - TimedConsensus - 10 nodes - 30 seconds" $ \step -> - -- withTempRocksDb "multinode-tests-timedconsensus-petersen-twenty-rocks" $ \rdb -> - -- withSystemTempDirectory "multinode-tests-timedconsensus-petersen-twenty-pact" $ \pactDbDir -> - -- withVersion (timedConsensusVersion petersenChainGraph twentyChainGraph) $ - -- Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step - [ testCaseSteps "ConsensusNetwork - InstantTimedCPM singleChainGraph - 10 nodes - 30 seconds" $ \step -> + [ testCaseSteps "ConsensusNetwork - TimedConsensus - 10 nodes - 30 seconds" $ \step -> + withTempRocksDb "multinode-tests-timedconsensus-petersen-twenty-rocks" $ \rdb -> + withSystemTempDirectory "multinode-tests-timedconsensus-petersen-twenty-pact" $ \pactDbDir -> + withVersion (timedConsensusVersion petersenChainGraph twentyChainGraph) $ + Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step + , testCaseSteps "ConsensusNetwork - InstantTimedCPM singleChainGraph - 10 nodes - 30 seconds" $ \step -> withTempRocksDb "multinode-tests-instantcpm-single-rocks" $ \rdb -> withSystemTempDirectory "multinode-tests-instantcpm-single-pact" $ \pactDbDir -> withVersion (instantCpmTestVersion singletonChainGraph) $ From c5b0041e59e04b8dbb6dfde4f476ab88c0e80802 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Sat, 24 May 2025 19:05:37 -0400 Subject: [PATCH 181/378] Properly log when continuing a genesis block --- src/Chainweb/Pact/PactService/ExecBlock.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index aacc7748c6..9f9731704b 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -119,16 +119,17 @@ continueBlock logger serviceEnv dbEnv blockInProgress = do (blockInProgress', _newHandle) <- flip runStateT (_blockInProgressHandle blockInProgress) $ do -- update the mempool, ensuring that we reintroduce any transactions that -- were removed due to being completed in a block on a different fork. - liftIO $ case _bctxIsGenesis blockCtx of - True -> do + liftIO $ + if _bctxIsGenesis blockCtx + then do + logFunctionText logger Info + "Continuing genesis block" + else do logFunctionText logger Info $ T.unwords [ "(parent height = " <> sshow (_bctxParentHash blockCtx) <> ")" , "(parent hash = " <> sshow (_bctxParentHeight blockCtx) <> ")" ] - False -> - logFunctionText logger Info - "Continuing genesis block" let blockGasLimit = view psNewBlockGasLimit serviceEnv let mTxTimeLimit = view psNewPayloadTxTimeLimit serviceEnv From 4ec58ad0cae022fdcac380132ce6fd16a28686e6 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Sun, 25 May 2025 16:59:26 -0400 Subject: [PATCH 182/378] More Brief instances Change-Id: Id000000062e3454effa9e5b6051d04779da27a9a --- src/Chainweb/Core/Brief.hs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index 81556d379b..1aefb761a5 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -65,6 +65,9 @@ toTextShort = T.take 6 . toText instance (Brief a, Brief b) => Brief (a,b) where brief (a,b) = "(" <> brief a <> "," <> brief b <> ")" +instance (Brief a, Brief b, Brief c) => Brief (a,b,c) where + brief (a,b,c) = "(" <> brief a <> "," <> brief b <> "," <> brief c <> ")" + instance (Brief a, Brief b) => Brief (Either a b) where brief (Left a) = "left:" <> brief a brief (Right b) = "right:" <> brief b @@ -76,6 +79,9 @@ instance Brief a => Brief (Maybe a) where instance Brief a => Brief [a] where brief l = "[" <> (T.intercalate "," $ brief <$> l) <> "]" +instance Brief Int where + brief = sshow + instance Brief a => Brief (NonEmpty a) where brief = brief . toList @@ -94,7 +100,11 @@ instance Brief CutId where brief = toTextShort instance Brief ChainId where brief = toText instance Brief BlockHash where brief = toTextShort instance Brief BlockPayloadHash where brief = toTextShort -instance Brief BlockHeader where brief = brief . view blockHash +instance Brief BlockHeader where + brief bh = + brief (view chainId bh) <> "@" + <> brief (view blockHeight bh) + <> ":" <> brief (view blockHash bh) instance Brief (Parent BlockHeader) where brief = brief . unwrapParent deriving via (CryptoHash AdjacentsHashAlgorithm) From a37992f1a2a6dad033391959fedf1915fec600d2 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Sun, 25 May 2025 14:21:58 -0400 Subject: [PATCH 183/378] Fix offsetBlockTime to correctly deal with transitions Change-Id: Id0000000c3cad3b6ca921bf132c849cdae31ef78 --- test/lib/Chainweb/Test/Cut.hs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index a29c87a2b7..c77131f4de 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -135,15 +135,18 @@ type GenBlockTime = Cut -> ChainId -> Time Micros -- | Block time generation that offsets from previous chain block in cut. -- -offsetBlockTime :: TimeSpan Micros -> GenBlockTime +offsetBlockTime :: HasVersion => TimeSpan Micros -> GenBlockTime offsetBlockTime offset cut cid = add offset $ maximum $ fmap (_bct . view blockCreationTime) $ HM.insert cid (cut ^?! ixg cid) - $ cutAdjs cut cid + $ HM.intersection + (cut ^. cutMap) + (HS.toMap $ adjacentChainIds (chainGraphAt (cut ^?! ixg cid . blockHeight)) cid) arbitraryBlockTimeOffset - :: TimeSpan Micros + :: HasVersion + => TimeSpan Micros -> TimeSpan Micros -> T.Gen GenBlockTime arbitraryBlockTimeOffset lower upper = do From d0f58b2b7f0fbe105e0bc2312a49eff07fd8a03d Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Sun, 25 May 2025 16:57:23 -0400 Subject: [PATCH 184/378] wip: Test for mixed transitional cuts being produced by join Change-Id: Id0000000b9d65b327a462fb7b9648d3751306c6d --- src/Chainweb/Cut/Create.hs | 14 +++++++ test/lib/Chainweb/Test/Cut.hs | 75 ++++++++++++++++++++++++++++++----- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 98f96fa5b4..cdf4bd8672 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -14,6 +14,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ViewPatterns #-} -- | -- Module: Chainweb.Cut.Create @@ -49,6 +50,7 @@ module Chainweb.Cut.Create , _cutExtensionAdjacentHashes , cutExtensionAdjacentHashes , getCutExtension +, cutMixedTransition -- * Limit cuts , limitCut @@ -137,6 +139,7 @@ import Data.Bifoldable import Data.Foldable import Data.Maybe import qualified Data.List as List +import qualified Data.Set as Set -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes @@ -1014,6 +1017,17 @@ joinIntoHeavier_ wdb a b = do m <- join_ wdb (prioritizeHeavier_ a b) a b applyJoin m +cutMixedTransition :: HasVersion => CutHashes -> Bool +cutMixedTransition (view cutHashes -> c) = + let + cutChainGraphs = + foldMap (Set.singleton . chainGraphAt . _bhwhHeight) c + allChainsTransitioned = + all (isGraphChange . _bhwhHeight) c + in + any ((> 8) . _bhwhHeight) c && any ((< 8) . _bhwhHeight) c + -- Set.size cutChainGraphs > 1 && not allChainsTransitioned + prioritizeHeavier :: Cut -> Cut -> DiffItem BlockHeader -> DiffItem (Maybe Int) prioritizeHeavier = prioritizeHeavier_ `on` _cutHeaders diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index c77131f4de..b3d241c50f 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -118,6 +118,7 @@ import Chainweb.Storage.Table.RocksDB import Numeric.Additive import Numeric.AffineSpace +import Chainweb.Cut.CutHashes (cutToCutHashes) -- -------------------------------------------------------------------------- -- -- Utils @@ -553,13 +554,68 @@ properties_lattice db = , ("joinMeetAbsorption", ioTest db prop_joinMeetAbsorption) , ("meetJoinAbsorption", ioTest db prop_meetJoinAbsorption) -- Fails - , ("noMixedTransitionalCuts", prop_noMixedTransitionalCuts db) ] -prop_noMixedTransitionalCuts +prop_joinNoMixedTransitionalCuts :: RocksDb -> T.Property +prop_joinNoMixedTransitionalCuts baseDb = + T.monadicIO $ withVersion v $ case implicitVersion ^. versionGraphs of + (transitionHeight, transitionGraph) `Above` Bottom (_, startGraph) -> do + db' <- liftIO $ testRocksDb "Chainweb.Test.Cut" baseDb + wdb <- liftIO (initWebBlockHeaderDb db') + let startCut = genesisCut + let startCids = graphChainIds startGraph + -- to start, mine until we are one block behind the transition on + -- all chains. + preTransitionCut <- flip execStateT startCut $ + replicateM_ (int $ transitionHeight - 1) $ do + forM_ startCids $ \cid -> do + c <- get + c' <- lift $ mine wdb 0 cid c + put c' + -- then, pick a chain on which each cut will differ, and mine all + -- others into the transition. + differingChain <- T.pick $ T.oneof (return <$> toList startCids) + meetCut <- flip execStateT preTransitionCut $ + forM_ (differingChain `HS.delete` startCids) $ \cid -> do + c <- get + c' <- lift $ mine wdb 0 cid c + put c' + -- then make two cuts on top of meetCut which differ on one chain, + -- at the transition height. + oneTransitionedCut <- mine wdb 0 differingChain meetCut + otherTransitionedCut <- mine wdb 0 differingChain meetCut + -- now that both cuts are transitioned, we can mine each on one + -- extra chain. the most important case here is that the differing + -- chain is an adjacent to this chain pre-transition and not + -- post-transition. + minedChain <- T.pick + $ T.oneof + (return <$> toList + (adjacentChainIds startGraph differingChain `HS.difference` adjacentChainIds transitionGraph differingChain) + ) + oneDangerousCut <- mine wdb 0 minedChain oneTransitionedCut + let otherDangerousCut = unsafeMkCut ((otherTransitionedCut ^. cutMap) & at minedChain .~ Just (oneDangerousCut ^?! ixg minedChain)) + m <- liftIO $ joinIntoHeavier wdb + oneDangerousCut otherDangerousCut + T.assert (not $ cutMixedTransition $ cutToCutHashes Nothing m) + _ -> error "timedConsensusVersion graphs have changed" + where + v = timedConsensusVersion petersenChainGraph twentyChainGraph + mine :: HasVersion => WebBlockHeaderDb -> Int -> ChainId -> Cut -> T.PropertyM IO Cut + mine wdb seed cid c = do + n' <- T.pick $ Nonce . int . (* seed) <$> T.arbitrary + delay <- pickBlind $ arbitraryBlockTimeOffset Time.second (plus Time.second Time.second) + Right (T2 _bh c') <- liftIO (testMine' wdb n' delay pay cid c) + return c' + where + pay = _payloadWithOutputsPayloadHash $ testPayload + $ B8.intercalate "," [ sshow (_versionName implicitVersion), sshow cid, "TEST PAYLOAD"] + + +prop_extendNoMixedTransitionalCuts :: RocksDb -> T.Property -prop_noMixedTransitionalCuts baseDb = +prop_extendNoMixedTransitionalCuts baseDb = T.monadicIO $ withVersion v $ case implicitVersion ^. versionGraphs of (transitionHeight, transitionGraph) `Above` Bottom (_, startGraph) -> do db' <- liftIO $ testRocksDb "Chainweb.Test.Cut" baseDb @@ -571,7 +627,7 @@ prop_noMixedTransitionalCuts baseDb = replicateM_ (int $ transitionHeight - 1) $ do forM_ startCids $ \cid -> do c <- get - Right (T2 _ c') <- lift $ mine wdb 0 c cid + Right (T2 _ c') <- lift $ mine wdb 0 cid c put c' -- then, pick a breakout chain which we will attempt to mine beyond -- the transition before the rest of the chains reach the @@ -595,12 +651,12 @@ prop_noMixedTransitionalCuts baseDb = dangerousCut <- flip execStateT preTransitionCut $ forM_ (breakoutChain : toList breakoutChainAdjacents) $ \cid -> do c <- get - Right (T2 _ c') <- lift $ mine wdb 0 c cid + Right (T2 _ c') <- lift $ mine wdb 0 cid c put c' let adjacentBlocks = HM.mapWithKey (\acid () -> Parent $ dangerousCut ^?! ixg acid) (HS.toMap breakoutChainAdjacents) - (_, ext) <- liftIO $ + (_, ext) <- T.run $ extend dangerousCut Nothing Nothing WorkParents { _workParent' = Parent $ dangerousCut ^?! ixg breakoutChain @@ -623,8 +679,8 @@ prop_noMixedTransitionalCuts baseDb = _ -> error "timedConsensusVersion graphs have changed" where v = timedConsensusVersion petersenChainGraph twentyChainGraph - mine :: HasVersion => WebBlockHeaderDb -> Int -> Cut -> ChainId -> T.PropertyM IO (Either MineFailure (T2 BlockHeader Cut)) - mine wdb seed c cid = do + mine :: HasVersion => WebBlockHeaderDb -> Int -> ChainId -> Cut -> T.PropertyM IO (Either MineFailure (T2 BlockHeader Cut)) + mine wdb seed cid c = do n' <- T.pick $ Nonce . int . (* seed) <$> T.arbitrary delay <- pickBlind $ arbitraryBlockTimeOffset Time.second (plus Time.second Time.second) liftIO (testMine' wdb n' delay pay cid c) @@ -723,7 +779,8 @@ properties_miscCut db = , ("prop_joinBaseMeet", prop_joinBaseMeet db) , ("prop_meetGenesisCut", ioTest db prop_meetGenesisCut) , ("Cuts of arbitrary fork have valid braiding", prop_arbitraryForkBraiding db) - , ("noMixedTransitionalCuts", prop_noMixedTransitionalCuts db) + , ("extendNoMixedTransitionalCuts", prop_extendNoMixedTransitionalCuts db) + , ("joinNoMixedTransitionalCuts", prop_joinNoMixedTransitionalCuts db) ] -- -------------------------------------------------------------------------- -- From 3d576ebe473cdc391e621ff334ed4be97bec163f Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 26 May 2025 15:25:22 -0400 Subject: [PATCH 185/378] Push back join base to avoid mixed transitional cuts Change-Id: Id000000010ecac51961bf77f2deb5aa8fcc0099d Change-Id: Id0000000ad2b50b7fa2708c80463473a29140d42 --- src/Chainweb/Cut/Create.hs | 300 +++++++++++++++++++--------------- test/lib/Chainweb/Test/Cut.hs | 2 +- 2 files changed, 168 insertions(+), 134 deletions(-) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index cdf4bd8672..091c68241f 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -132,14 +132,17 @@ import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB import Data.Monoid -import Chainweb.TreeDB ( branchDiff_, forkEntry, seekAncestor ) +import Chainweb.TreeDB hiding (parent) import Data.Function import qualified Streaming.Prelude as S -import Data.Bifoldable import Data.Foldable import Data.Maybe import qualified Data.List as List -import qualified Data.Set as Set +import Data.List.NonEmpty (NonEmpty) +import qualified Data.List.NonEmpty as NE +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as Map +import qualified Streaming as S -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes @@ -636,89 +639,6 @@ extendCut c ps s = do where bh = newHeader ps s --- -------------------------------------------------------------------------- -- --- Limit Cut Hashes By Height - --- | Find a `Cut` that is a predecessor of the given one, and that has a block --- height that is smaller or equal the given height. --- --- If the requested limit is larger or equal to the current height, the given --- cut is returned. --- --- Otherwise, the predecessor of the given cut at the given height on each chain --- is returned. --- -limitCut - :: (HasCallStack, HasVersion) - => WebBlockHeaderDb - -> BlockHeight - -- ^ upper bound for the block height of each chain. This is not a tight - -- bound. - -> Cut - -> IO Cut -limitCut wdb h c - | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = - return c - | otherwise = do - hdrs <- itraverse go $ view cutHeaders c - return $! unsafeMkCut $ projectChains $ HM.mapMaybe id hdrs - where - - go :: ChainId -> BlockHeader -> IO (Maybe BlockHeader) - go cid bh = do - if h >= view blockHeight bh - then return (Just bh) - else do - !db <- getWebBlockHeaderDb wdb cid - seekAncestor db bh (min (int $ view blockHeight bh) (int h)) - -- this is safe because it's guaranteed that the requested rank is - -- smaller then the block height of the argument - --- | Find a `Cut` that is a predecessor of the given one, and that has a block --- height that is as low as possible while not exceeding the given height and --- including all of the chains in the given cut. --- --- If the requested limit is larger or equal to the current height, the given --- cut is returned. --- -tryLimitCut - :: (HasCallStack, HasVersion) - => WebBlockHeaderDb - -> BlockHeight - -- upper bound for the block height of each chain. This is not a tight bound. - -> Cut - -> IO Cut -tryLimitCut wdb h c - | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = - return c - | otherwise = do - hdrs <- itraverse go $ view cutHeaders c - return $! unsafeMkCut hdrs - where - go :: ChainId -> BlockHeader -> IO BlockHeader - go cid bh = do - if h >= view blockHeight bh - then return bh - else do - !db <- getWebBlockHeaderDb wdb cid - -- this is safe because it's guaranteed that the requested rank is - -- smaller then the block height of the argument - let ancestorHeight = min (int $ view blockHeight bh) (int h) - if ancestorHeight <= fromIntegral (genesisHeight cid) - then return $ genesisBlockHeader cid - else fromJuste <$> seekAncestor db bh ancestorHeight - --- | The resulting headers are valid cut headers only if the input headers are --- valid cut headers, too. The inverse is not true. --- -limitCutHeaders - :: (HasCallStack, HasVersion) - => WebBlockHeaderDb - -> BlockHeight - -- ^ upper bound for the block height of each chain. This is not a tight bound. - -> HM.HashMap ChainId BlockHeader - -> IO (HM.HashMap ChainId BlockHeader) -limitCutHeaders whdb h ch = _cutHeaders <$> limitCut whdb h (unsafeMkCut ch) -- -------------------------------------------------------------------------- -- -- Tools for Graph Transitions @@ -883,15 +803,15 @@ tryMonotonicCutExtension c h = isMonotonicCutExtension c h >>= \case type DiffItem a = These a a -type JoinQueue a = H.Heap (H.Entry (BlockHeight, a) BlockHeader) +type JoinQueue = H.Heap (H.Entry (BlockHeight, ChainId) (NonEmpty BlockHeader)) -- | This represents the Join of two cuts in an algorithmically convenient way. -- -data Join a = Join +data Join = Join { _joinBase :: !Cut -- ^ the base of the join, the largest cut that is contained in both -- cuts, or when viewed as sets, the intersection. - , _joinQueue :: !(JoinQueue a) + , _joinQueue :: !JoinQueue -- ^ a queue of block headers from both cuts that allows construct -- the join cut from the join base. } @@ -902,12 +822,12 @@ data Join a = Join -- chains. -- join - :: (Ord a, HasVersion) + :: (HasVersion) => WebBlockHeaderDb - -> (DiffItem BlockHeader -> DiffItem (Maybe a)) + -> (DiffItem BlockHeader -> NonEmpty BlockHeader) -> Cut -> Cut - -> IO (Join a) + -> IO Join join wdb f = join_ wdb f `on` _cutHeaders -- | This merges two maps from ChainIds to BlockHeaders such that the result is @@ -924,49 +844,56 @@ join wdb f = join_ wdb f `on` _cutHeaders -- information/blocks in the other chains would be lost when applying the join. -- join_ - :: forall a - . (Ord a, HasVersion) + :: (HasVersion) => WebBlockHeaderDb - -> (DiffItem BlockHeader -> DiffItem (Maybe a)) + -> (DiffItem BlockHeader -> NonEmpty BlockHeader) -> HM.HashMap ChainId BlockHeader -> HM.HashMap ChainId BlockHeader - -> IO (Join a) + -> IO Join join_ wdb prioFun a b = do - (m, h) <- runStateT (HM.traverseWithKey f (HM.intersectionWith (,) a' b')) mempty - return $! Join (unsafeMkCut m) h + (base, queue) <- flip runStateT mempty $ do + unsafeJoinBase <- unsafeMkCut <$> HM.traverseWithKey joinChain (HM.intersectionWith (,) a' b') + if cutMixedTransition (cutToCutHashes Nothing unsafeJoinBase) + then + -- mixed transition cuts cannot be extended safely. so, we push back the + -- join base in that case, until it's not post-transition on any chains. + limitCut_ wdb (lastGraphChangeInCut $ cutToCutHashes Nothing unsafeJoinBase) unsafeJoinBase + else + return unsafeJoinBase + + return $! Join base queue where (a', b') = joinChains a b - f + joinChain :: ChainId -> (BlockHeader, BlockHeader) - -> StateT (JoinQueue a) IO BlockHeader - f cid (x, y) = do - !q <- get + -> StateT JoinQueue IO BlockHeader + joinChain cid (x, y) = do db <- getWebBlockHeaderDb wdb cid - (q' S.:> !h) <- liftIO $ S.fold g q id $ branchDiff_ db x y - put q' - return h - - g :: JoinQueue a -> DiffItem BlockHeader -> JoinQueue a - g q x = foldl' maybeInsert q $ zip (biList x) (biList (prioFun x)) - - maybeInsert - :: H.Heap (H.Entry (BlockHeight, a) BlockHeader) - -> (BlockHeader, Maybe a) - -> H.Heap (H.Entry (BlockHeight, a) BlockHeader) - maybeInsert !q (_, Nothing) = q - maybeInsert !q (!h, (Just !p)) = H.insert (H.Entry (view blockHeight h, p) h) q + branchDiff_ db x y + & S.hoist liftIO + & S.mapM_ (enqueueHeaders . prioFun) + +enqueueHeaders :: NonEmpty BlockHeader -> StateT JoinQueue IO () +enqueueHeaders hs = + modify' (\q -> + H.insert (H.Entry (height, chain) hs) q + ) + where + height = view blockHeight (NE.head hs) + chain = view chainId (NE.head hs) -- This can't fail because of missing dependencies. It can't fail because -- of conflict. -- -- Non-existing chains are stripped from the result. -- -applyJoin :: (MonadThrow m, HasVersion) => Join a -> m Cut +applyJoin :: (MonadThrow m, HasVersion) => Join -> m Cut applyJoin m = cutProjectChains - <$> foldM - (\c b -> fromMaybe c <$> tryMonotonicCutExtension c (H.payload b)) + <$> foldlMOf (folded . folded . folded) + (\c b -> + fromMaybe c <$> tryMonotonicCutExtension c b) (_joinBase m) (_joinQueue m) @@ -1017,18 +944,126 @@ joinIntoHeavier_ wdb a b = do m <- join_ wdb (prioritizeHeavier_ a b) a b applyJoin m +-- -------------------------------------------------------------------------- -- +-- Limit Cut Hashes By Height + +limitCut_ + :: (HasCallStack, HasVersion) + => WebBlockHeaderDb + -> BlockHeight + -> Cut + -> StateT JoinQueue IO Cut +limitCut_ wdb h c + | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = + return c + | otherwise = do + hdrs <- itraverse go $ view cutHeaders c + return $! unsafeMkCut $ projectChains $ HM.mapMaybe id hdrs + where + go :: ChainId -> BlockHeader -> StateT JoinQueue IO (Maybe BlockHeader) + go cid bh = do + if h < genesisBlockHeight cid + then return Nothing + else if h >= view blockHeight bh + then return (Just bh) + else do + !db <- getWebBlockHeaderDb wdb cid + ancestor <- + S.mapM_ (enqueueHeaders . NE.singleton) $ + -- this is safe because it's guaranteed that the requested rank is + -- smaller then the block height of the argument + getAncestors db (min (int $ view blockHeight bh) (int h)) (ancestors db (key bh)) + return (Just ancestor) + getAncestors db b strm = liftIO (S.next strm) >>= \case + Left () -> error "reached end" + Right (anc, strm') + | rank anc == b -> + return anc + | otherwise -> do + S.yield anc + getAncestors db b strm' + +-- | Find a `Cut` that is a predecessor of the given one, and that has a block +-- height that is smaller or equal the given height. +-- +-- If the requested limit is larger or equal to the current height, the given +-- cut is returned. +-- +-- Otherwise, the predecessor of the given cut at the given height on each chain +-- is returned. +-- +limitCut + :: (HasCallStack, HasVersion) + => WebBlockHeaderDb + -> BlockHeight + -- ^ upper bound for the block height of each chain. This is not a tight + -- bound. + -> Cut + -> IO Cut +limitCut wdb h c = evalStateT (limitCut_ wdb h c) mempty + +-- | Find a `Cut` that is a predecessor of the given one, and that has a block +-- height that is as low as possible while not exceeding the given height and +-- including all of the chains in the given cut. +-- +-- If the requested limit is larger or equal to the current height, the given +-- cut is returned. +-- +tryLimitCut + :: (HasCallStack, HasVersion) + => WebBlockHeaderDb + -> BlockHeight + -- upper bound for the block height of each chain. This is not a tight bound. + -> Cut + -> IO Cut +tryLimitCut wdb h c + | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = + return c + | otherwise = do + hdrs <- itraverse go $ view cutHeaders c + return $! unsafeMkCut hdrs + where + go :: ChainId -> BlockHeader -> IO BlockHeader + go cid bh = do + if h >= view blockHeight bh + then return bh + else do + !db <- getWebBlockHeaderDb wdb cid + -- this is safe because it's guaranteed that the requested rank is + -- smaller then the block height of the argument + let ancestorHeight = min (int $ view blockHeight bh) (int h) + if ancestorHeight <= fromIntegral (genesisHeight cid) + then return $ genesisBlockHeader cid + else fromJuste <$> seekAncestor db bh ancestorHeight + +-- | The resulting headers are valid cut headers only if the input headers are +-- valid cut headers, too. The inverse is not true. +-- +limitCutHeaders + :: (HasCallStack, HasVersion) + => WebBlockHeaderDb + -> BlockHeight + -- ^ upper bound for the block height of each chain. This is not a tight bound. + -> HM.HashMap ChainId BlockHeader + -> IO (HM.HashMap ChainId BlockHeader) +limitCutHeaders whdb h ch = _cutHeaders <$> limitCut whdb h (unsafeMkCut ch) + +lastGraphChangeInCut :: HasVersion => CutHashes -> BlockHeight +lastGraphChangeInCut c = + maximum $ + lastGraphChange . _bhwhHeight <$> (_cutHashes c) + +-- | This is a mixed transition cut, and thus has no unique chain graph. Such a +-- cut may have invalid braiding according to *either* the pre- or post- +-- transition graph. cutMixedTransition :: HasVersion => CutHashes -> Bool -cutMixedTransition (view cutHashes -> c) = +cutMixedTransition c = let - cutChainGraphs = - foldMap (Set.singleton . chainGraphAt . _bhwhHeight) c - allChainsTransitioned = - all (isGraphChange . _bhwhHeight) c + lgc = lastGraphChangeInCut c in - any ((> 8) . _bhwhHeight) c && any ((< 8) . _bhwhHeight) c - -- Set.size cutChainGraphs > 1 && not allChainsTransitioned + any ((< lgc) . _bhwhHeight) (_cutHashes c) -prioritizeHeavier :: Cut -> Cut -> DiffItem BlockHeader -> DiffItem (Maybe Int) +prioritizeHeavier :: Cut -> Cut -> DiffItem BlockHeader -> NonEmpty BlockHeader prioritizeHeavier = prioritizeHeavier_ `on` _cutHeaders -- | Note: consider the weight of the recursive dependencies for the @@ -1043,17 +1078,16 @@ prioritizeHeavier_ => f BlockHeader -> f BlockHeader -> DiffItem BlockHeader - -> DiffItem (Maybe Int) + -> NonEmpty BlockHeader prioritizeHeavier_ a b = f where heaviest = maxBy (compare `on` weight) a b - w c = if c == heaviest then 0 else 1 - f (This _) = This (Just $ w a) - f (That _) = That (Just $ w b) - f (These _ _) - | heaviest == a = These (Just 0) Nothing - | otherwise = These Nothing (Just 0) + f (This bh) = NE.singleton bh + f (That bh) = NE.singleton bh + f (These bha bhb ) + | heaviest == a = bha NE.:| [bhb] + | otherwise = bhb NE.:| [bha] weight c = ( sumOf (folded . blockWeight) c diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index b3d241c50f..d4943ee161 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -376,7 +376,7 @@ data TestFork = TestFork arbitraryJoin :: HasVersion => WebBlockHeaderDb - -> T.PropertyM IO (Join Int) + -> T.PropertyM IO Join arbitraryJoin wdb = do TestFork _ cl cr <- arbitraryFork wdb liftIO $ join wdb (prioritizeHeavier cl cr) cl cr From 819de26241c122d023c225e3a907abf8392f66f0 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 27 May 2025 16:01:53 -0700 Subject: [PATCH 186/378] Improve error message in mining coordinator. --- src/Chainweb/Miner/Coordinator.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 8fd55d17cc..8bb5ebbb76 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -621,12 +621,15 @@ randomWork logFun cdb caches parentStateVars = do -- timeoutVar <- registerDelay (int staleMiningStateDelay) w <- atomically $ - Right <$> msum (imap awaitWorkReady parentStateVars) <|> awaitTimeout timeoutVar + Right <$> msum (imap awaitWorkReady parentStateVars) + <|> awaitTimeout timeoutVar case w of Right (ps, npld) -> do ct <- BlockCreationTime <$> getCurrentTimeIntegral return $ newWork ct ps (_newPayloadBlockPayloadHash npld) - Left e -> error (T.unpack e) -- FIXME: throw a proper exception and log what is going on + Left e -> error $ + "Chainweb.Miner.Coordinator.randomWork: " <> T.unpack e + -- FIXME: throw a proper exception and log what is going on go ((cid, var):t) = do readyCheck <- atomically $ From 857eca38f733b4dcce20a48e811821451b8735e1 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 27 May 2025 16:03:57 -0700 Subject: [PATCH 187/378] Reduce initial difficulty of evm-development to accomodate larger number chains --- src/Chainweb/Version/EvmDevelopment.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 2f4fc6a9ce..2c21adcd79 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -63,8 +63,9 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion , _versionHeaderBaseSizeBytes = 318 - 110 , _versionBootstraps = [] , _versionGenesis = VersionGenesis - { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 500_000) + { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 100_000) , _genesisTime = onChains + -- FIXME: is the creation time for the pact headers correct? $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0..19] ] <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..24] ] <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [25..97] ] From f533056ab8dd424821f29c4db1a0aea2049f1851 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 27 May 2025 16:20:06 -0700 Subject: [PATCH 188/378] Cleanup EVM payload listener --- src/Chainweb/PayloadProvider/EVM.hs | 73 +++++++++++++++++++---------- 1 file changed, 49 insertions(+), 24 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index bed60bb04c..c54b8a82d5 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -532,17 +532,6 @@ withEvmPayloadProvider logger c rdb mgr conf where pldStoreLogger = addLabel ("sub-component", "payloadStore") logger -payloadListener :: (Logger logger, HasVersion) => EvmPayloadProvider logger -> IO () -payloadListener p = case (_evmMinerAddress p) of - Nothing -> do - lf Info "New payload creation is disabled." - return () - Just addr -> runForeverThrottled lf "EVM Provider Payload Listener" 5 10000 $ do - lf Info $ "Start payload listener with miner address " <> toText addr - awaitNewPayload p - where - lf = loggS p "payloadListener" - -- | Checks the availability of the Execution Client -- -- - asserts API availability @@ -582,6 +571,48 @@ checkExecutionClient logger c ctx expectedEcid = do where expectedGenesisHeader = genesisBlockPayloadHash (_chainId c) +-- -------------------------------------------------------------------------- -- +-- Payload Listener + +-- | Base rate for new payload requests. +-- +newPayloadRate :: Int +newPayloadRate = 1_000_000 + +newPayloadBurst :: Int +newPayloadBurst = 4 + +-- | minimum delay between requests for new payloads +-- +newPayloadMinDelay :: Int +newPayloadMinDelay = 10_000 + +-- | If we don't receive a new payload ID with this time, we assume that the +-- EVM is not available and log an error. This is implemented in the +-- 'awaitNewPayload' function. +-- +-- FIXME: consider raising an actual error. +-- +newPayloadTimeout :: Int +newPayloadTimeout = 30_000_000 + +-- | Scheduler for listening for new payloads. +-- +payloadListener :: (Logger logger, HasVersion) => EvmPayloadProvider logger -> IO () +payloadListener p = case (_evmMinerAddress p) of + Nothing -> do + lf Info "New payload creation is disabled." + return () + Just addr -> runForeverThrottled lf "EVM Provider Payload Listener" 5 (int newPayloadRate) $ do + lf Info $ "Start payload listener with miner address " <> toText addr + + -- This is small delay compared to the base rate. So we just always add + -- it. It is only relevant during bursts. + threadDelay $ int newPayloadMinDelay + awaitNewPayload p + where + lf = loggS p "payloadListener" + -- -------------------------------------------------------------------------- -- -- Engine Calls @@ -827,12 +858,6 @@ data EvmNewPayloadExeception instance Exception EvmNewPayloadExeception -newPayloadRate :: Int -newPayloadRate = 1_000_000 - -newPayloadTimeout :: Int -newPayloadTimeout = 30_000_000 - -- | If a payload id is available, new payloads for it. -- -- This is called only if payload creation is enabled in the configuration. @@ -842,14 +867,16 @@ awaitNewPayload p = do lf Debug "await new payload ID" awaitPid >>= \case Nothing -> do - lf Warn "timeout while waiting for new payloadID" + lf Error "timeout while waiting for new payloadID" -- DEBUG T2 state bctx <- stateIO p pld <- latestPayloadIO p - lf Warn $ briefJson state - lf Warn $ briefJson bctx - lf Warn $ briefJson pld + + lf Warn $ "timeout while waiting for new payloadID" + <> "consensus state: " <> briefJson state + <> "new block ctx: " <> briefJson bctx + <> "latest payload: " <> briefJson pld -- FIXME -- We are probalby stuck. What can we do? Call forkchoiceUpdate -- again? Or we just do nothing? Maybe it should be the job of @@ -909,7 +936,6 @@ awaitNewPayload p = do Just (T2 curEvmBlockHash _) | curEvmBlockHash == newEvmBlockHash -> do lf Info $ "the new execution payload is the same as the current payload. No update." - return () x -> do lf Debug $ "checking new execution payload for " <> toText pid <> "; execution payload: " <> briefJson resp @@ -949,7 +975,7 @@ awaitNewPayload p = do lf Info $ "got new payload " <> briefJson pld -- The actual payload header is included in the NewBlock - -- structure in as EncodedPayloadData. + -- structure as EncodedPayloadData. lf Debug $ "write new payload to payload var for evm hash " <> briefJson newEvmBlockHash atomically $ writeTMVar (_evmPayloadVar p) $ T2 newEvmBlockHash NewPayload { _newPayloadTxCount = int $ length (_executionPayloadV1Transactions v1) @@ -965,7 +991,6 @@ awaitNewPayload p = do , _newPayloadEncodedPayloadData = Just (EncodedPayloadData $ putRlpByteString pld) , _newPayloadChainId = cid } - threadDelay newPayloadRate executionPayloadV3ToHeader :: Chainweb.BlockHash From dcfe346e489257aa8d8edf9b7538025a4064c121 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 27 May 2025 19:01:28 -0400 Subject: [PATCH 189/378] Use sqlite readonly flag Change-Id: Id0000000ca97549d1c6aeb0c7a7a638aa9ddfcb9 --- src/Chainweb/Pact/Backend/Utils.hs | 57 +++++++--------------------- src/Chainweb/PayloadProvider/Pact.hs | 10 +---- test/lib/Chainweb/Test/Pact/Utils.hs | 8 ---- 3 files changed, 14 insertions(+), 61 deletions(-) diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index ab3bb98bb3..98e4195874 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -51,15 +51,12 @@ module Chainweb.Pact.Backend.Utils , convSavepointName -- * SQLite runners , withSqliteDb - , withReadSqliteDb , withReadSqlitePool , startSqliteDb - , startReadSqliteDb , stopSqliteDb , withSQLiteConnection , openSQLiteConnection , closeSQLiteConnection - , withTempSQLiteConnection -- * SQLite , chainwebPragmas , LocatedSQ3Error(..) @@ -164,6 +161,7 @@ withSavepoint db name action = fmap fst $ generalBracket (liftIO $ beginSavepoint db name) (\_ -> liftIO . \case ExitCaseSuccess {} -> commitSavepoint db name + ExitCaseException e -> appendFile "BAD" (show (name, e) <> "\n") >> abortSavepoint db name _ -> abortSavepoint db name ) $ \_ -> action @@ -263,20 +261,10 @@ withSqliteDb cid logger dbDir resetDb = snd <$> allocate (startSqliteDb cid logger dbDir resetDb) stopSqliteDb -withReadSqliteDb - :: Logger logger - => ChainId - -> logger - -> FilePath - -> ResourceT IO SQLiteEnv -withReadSqliteDb cid logger dbDir = snd <$> allocate - (startReadSqliteDb cid logger dbDir) - stopSqliteDb - withReadSqlitePool :: ChainId -> FilePath -> ResourceT IO (Pool.Pool SQLiteEnv) withReadSqlitePool cid pactDbDir = snd <$> allocate (Pool.newPool $ Pool.defaultPoolConfig - (openSQLiteConnection (pactDbDir chainDbFileName cid) chainwebPragmas) + (openSQLiteConnection (pactDbDir chainDbFileName cid) [sqlite_open_readonly, sqlite_open_fullmutex] chainwebPragmas) stopSqliteDb 30 -- seconds to keep them around unused 2 -- connections at most @@ -294,23 +282,11 @@ startSqliteDb cid logger dbDir doResetDb = do when doResetDb resetDb createDirectoryIfMissing True dbDir logFunctionText logger Debug $ "opening sqlitedb named " <> T.pack sqliteFile - openSQLiteConnection sqliteFile chainwebPragmas + openSQLiteConnection sqliteFile [sqlite_open_readwrite , sqlite_open_create , sqlite_open_fullmutex] chainwebPragmas where resetDb = removeDirectoryRecursive dbDir sqliteFile = dbDir chainDbFileName cid -startReadSqliteDb - :: Logger logger - => ChainId - -> logger - -> FilePath - -> IO SQLiteEnv -startReadSqliteDb cid logger dbDir = do - logFunctionText logger Debug $ "(read-only) opening sqlitedb named " <> T.pack sqliteFile - openSQLiteConnection sqliteFile chainwebPragmas - where - sqliteFile = dbDir chainDbFileName cid - chainDbFileName :: ChainId -> FilePath chainDbFileName cid = fold [ "pact-v1-chain-" @@ -321,12 +297,12 @@ chainDbFileName cid = fold stopSqliteDb :: SQLiteEnv -> IO () stopSqliteDb = closeSQLiteConnection -withSQLiteConnection :: String -> [Pact4.Pragma] -> (SQLiteEnv -> IO c) -> IO c +withSQLiteConnection :: String -> [Pact4.Pragma] -> ResourceT IO SQLiteEnv withSQLiteConnection file ps = - bracket (openSQLiteConnection file ps) closeSQLiteConnection + snd <$> allocate (openSQLiteConnection file [sqlite_open_readwrite , sqlite_open_create , sqlite_open_fullmutex] ps) closeSQLiteConnection -openSQLiteConnection :: String -> [Pact4.Pragma] -> IO SQLiteEnv -openSQLiteConnection file ps = open2 file >>= \case +openSQLiteConnection :: String -> [SQLiteFlag] -> [Pact4.Pragma] -> IO SQLiteEnv +openSQLiteConnection file flags ps = open2 file flags >>= \case Left (err, msg) -> error $ "withSQLiteConnection: Can't open db with " @@ -338,19 +314,10 @@ openSQLiteConnection file ps = open2 file >>= \case closeSQLiteConnection :: SQLiteEnv -> IO () closeSQLiteConnection c = void $ close_v2 c --- passing the empty string as filename causes sqlite to use a temporary file --- that is deleted when the connection is closed. In practice, unless the database becomes --- very large, the database will reside memory and no data will be written to disk. --- --- Cf. https://www.sqlite.org/inmemorydb.html --- -withTempSQLiteConnection :: [Pact4.Pragma] -> (SQLiteEnv -> IO c) -> IO c -withTempSQLiteConnection = withSQLiteConnection "" - -open2 :: String -> IO (Either (SQ3.Error, SQ3.Utf8) SQ3.Database) -open2 file = open_v2 +open2 :: String -> [SQLiteFlag] -> IO (Either (SQ3.Error, SQ3.Utf8) SQ3.Database) +open2 file flags = open_v2 (fromString file) - (collapseFlags [sqlite_open_readwrite , sqlite_open_create , sqlite_open_fullmutex]) + (collapseFlags flags) Nothing -- Nothing corresponds to the nullPtr collapseFlags :: [SQLiteFlag] -> SQLiteFlag @@ -358,9 +325,11 @@ collapseFlags xs = if Prelude.null xs then error "collapseFlags: You must pass a non-empty list" else Prelude.foldr1 (.|.) xs -sqlite_open_readwrite, sqlite_open_create, sqlite_open_fullmutex :: SQLiteFlag +sqlite_open_readwrite, sqlite_open_readonly, sqlite_open_create, sqlite_open_fullmutex, sqlite_open_nomutex :: SQLiteFlag +sqlite_open_readonly = 0x00000001 sqlite_open_readwrite = 0x00000002 sqlite_open_create = 0x00000004 +sqlite_open_nomutex = 0x00008000 sqlite_open_fullmutex = 0x00010000 tbl :: HasCallStack => Utf8 -> Utf8 diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index fc4a0fe9ae..5fe5d98b1c 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -117,15 +117,7 @@ withPactPayloadProvider -> ResourceT IO (PactPayloadProvider logger tbl) withPactPayloadProvider cid http logger txFailuresCounter mp pdb pactDbDir config maybeGenesisPayload = do readWriteSqlenv <- withSqliteDb cid logger pactDbDir False - (_, readOnlySqlPool) <- allocate - (Pool.newPool $ Pool.defaultPoolConfig - (startReadSqliteDb cid logger pactDbDir) - stopSqliteDb - 10 -- seconds to keep them around unused - 2 -- connections at most - & Pool.setNumStripes (Just 2) -- two stripes, one connection per stripe - ) - Pool.destroyAllResources + readOnlySqlPool <- withReadSqlitePool cid pactDbDir PactPayloadProvider logger <$> PactService.withPactService cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config (maybe GenesisNotNeeded GenesisPayload maybeGenesisPayload) diff --git a/test/lib/Chainweb/Test/Pact/Utils.hs b/test/lib/Chainweb/Test/Pact/Utils.hs index eefd637a7b..caf43f8777 100644 --- a/test/lib/Chainweb/Test/Pact/Utils.hs +++ b/test/lib/Chainweb/Test/Pact/Utils.hs @@ -83,14 +83,6 @@ withBlockDbs rdb = do let payloadDb = newPayloadDb rdb return (payloadDb, webBHDb) --- | Internal. See https://www.sqlite.org/c3ref/open.html -withSQLiteResource - :: String - -> ResourceT IO SQLiteEnv -withSQLiteResource file = snd <$> allocate - (openSQLiteConnection file chainwebPragmas) - closeSQLiteConnection - withMempool :: (Logger logger) => HasVersion From 576a13db192f803e119752afbe1a489d5196ad14 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 27 May 2025 16:20:29 -0700 Subject: [PATCH 190/378] improve logging for EVM forkChoiceUpdate --- src/Chainweb/PayloadProvider/EVM.hs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index c54b8a82d5..8fd5900b17 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -649,6 +649,7 @@ forkchoiceUpdate -> IO (Maybe PayloadId) forkchoiceUpdate p t fcs attr = go t where + request = ForkchoiceUpdatedV3Request fcs attr lf = loggS p "forkchoiceUpdate" waitTime = Micros 500_000 go remaining @@ -656,10 +657,13 @@ forkchoiceUpdate p t fcs attr = go t lf Warn $ "forkchoiceUpdate timed out while EVM is syncing" throwM $ ForkchoiceUpdatedTimeoutException t | otherwise = do - lf Info $ briefJson (ForkchoiceUpdatedV3Request fcs attr) + lf Info $ briefJson $ object + [ "remainingTime" .= remaining + , "request" .= request + ] r <- try @_ @(RPC.Error EngineServerErrors EngineErrors) $ RPC.callMethodHttp @Engine_ForkchoiceUpdatedV3 (_evmEngineCtx p) - (ForkchoiceUpdatedV3Request fcs attr) + request case r of Right s -> case _forkchoiceUpdatedV1ResponsePayloadStatus s of @@ -692,7 +696,8 @@ forkchoiceUpdate p t fcs attr = go t -- latest hash will be the requested hash and payload production -- is initiated if it was requested. -- - PayloadStatusV1 Valid (Just _) Nothing -> + PayloadStatusV1 Valid (Just _) Nothing -> do + lf Info "forkchoiceUpdate succeeded with VALID status" return (_forkchoiceUpdatedV1ResponsePayloadId s) -- FIXME: From 81496c392ab0a5e055af0c43b6bfbd65f952d0ad Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 27 May 2025 19:22:53 -0400 Subject: [PATCH 191/378] Remove debugging code --- src/Chainweb/Pact/Backend/Utils.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 98e4195874..5cad56698f 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -161,7 +161,6 @@ withSavepoint db name action = fmap fst $ generalBracket (liftIO $ beginSavepoint db name) (\_ -> liftIO . \case ExitCaseSuccess {} -> commitSavepoint db name - ExitCaseException e -> appendFile "BAD" (show (name, e) <> "\n") >> abortSavepoint db name _ -> abortSavepoint db name ) $ \_ -> action From 0ebe6372b69e16ef7fe8ba5c83538feef3566cb1 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 23:05:41 -0400 Subject: [PATCH 192/378] Fix error in localTest during graph transition Change-Id: Id000000004e39a54679998f7b920c749a7e42e6d --- src/Chainweb/Miner/Miners.hs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index 5e4e488f95..d651dbf348 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -74,6 +74,7 @@ import Chainweb.Version import Data.LogMessage (LogFunction, LogFunctionText) import System.LogLevel import Control.Concurrent.STM +import Data.Maybe (fromMaybe) -------------------------------------------------------------------------------- -- Local Mining @@ -96,8 +97,9 @@ localTest lf coord cdb gen miners = runForever lf "Chainweb.Miner.Miners.localTest" $ do c <- _cut cdb wh <- work coord - -- TODO: I've seen this error, why? - let height = c ^?! ixg (_miningWorkChainId wh) . blockHeight + let height = fromMaybe + (genesisHeight (_miningWorkChainId wh)) + (c ^? ixg (_miningWorkChainId wh) . blockHeight) race (awaitNewCutByChainId cdb (_miningWorkChainId wh) c) (go height wh) >>= \case Left _ -> return () From 0a2b61e656b2be93ea7cfdcee587bf472c24e27e Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 22 May 2025 15:21:05 -0400 Subject: [PATCH 193/378] replace BlockHashWithHeight with RankedBlockHash and cut logging Change-Id: Id000000040e59a03a994a5cc211940e914e62a66 --- src/Chainweb/BlockHash.hs | 23 +++++++++++-- src/Chainweb/Core/Brief.hs | 3 -- src/Chainweb/Cut/Create.hs | 5 +-- src/Chainweb/Cut/CutHashes.hs | 39 ++++------------------ src/Chainweb/CutDB.hs | 9 ++--- src/Chainweb/Ranked.hs | 1 + src/Chainweb/RestAPI/NodeInfo.hs | 3 +- test/lib/Chainweb/Test/Orphans/Internal.hs | 5 +-- test/lib/Chainweb/Test/RestAPI/Utils.hs | 5 +-- test/lib/Chainweb/Test/Utils.hs | 5 +-- test/unit/Chainweb/Test/Roundtrips.hs | 2 +- 11 files changed, 48 insertions(+), 52 deletions(-) diff --git a/src/Chainweb/BlockHash.hs b/src/Chainweb/BlockHash.hs index 417239e873..454f009a7e 100644 --- a/src/Chainweb/BlockHash.hs +++ b/src/Chainweb/BlockHash.hs @@ -77,12 +77,12 @@ module Chainweb.BlockHash ) where import Control.DeepSeq -import Control.Lens +import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Catch (MonadThrow, throwM) import Data.Aeson - (FromJSON(..), FromJSONKey(..), ToJSON(..), ToJSONKey(..), withText) + (FromJSON(..), FromJSONKey(..), ToJSON(..), ToJSONKey(..), withText, object, KeyValue, withObject, (.=), pairs, (.:)) import Data.Aeson.Types (FromJSONKeyFunction(..), toJSONKeyText) import Data.Bifoldable import Data.ByteString.Short qualified as SB @@ -336,3 +336,22 @@ encodeRankedBlockHash = encodeRanked encodeBlockHash decodeRankedBlockHash :: Get RankedBlockHash decodeRankedBlockHash = decodeRanked decodeBlockHash + +blockHashWithHeightProperties :: KeyValue e kv => RankedBlockHash -> [kv] +blockHashWithHeightProperties o = + [ "height" .= _rankedBlockHashHeight o + , "hash" .= _rankedBlockHashHash o + ] +{-# INLINE blockHashWithHeightProperties #-} + +instance ToJSON RankedBlockHash where + toJSON = object . blockHashWithHeightProperties + toEncoding = pairs . mconcat . blockHashWithHeightProperties + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance FromJSON RankedBlockHash where + parseJSON = withObject "HashWithHeight" $ \o -> RankedBlockHash + <$> o .: "height" + <*> o .: "hash" + {-# INLINE parseJSON #-} diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index 1aefb761a5..fd17ddeb74 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -120,9 +120,6 @@ deriving via (BriefText (CryptoHash a)) instance (IncrementalHash a, Coercible a BS.ShortByteString) => Brief (CryptoHash a) -instance Brief BlockHashWithHeight where - brief a = brief (_bhwhHeight a) <> ":" <> brief (_bhwhHash a) - instance Brief CutHashes where brief c = T.intercalate ":" [ brief (_cutHashesId c) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index 091c68241f..ba3a6deda0 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -143,6 +143,7 @@ import qualified Data.List.NonEmpty as NE import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import qualified Streaming as S +import Chainweb.Ranked (Ranked(_rankedHeight)) -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes @@ -1051,7 +1052,7 @@ limitCutHeaders whdb h ch = _cutHeaders <$> limitCut whdb h (unsafeMkCut ch) lastGraphChangeInCut :: HasVersion => CutHashes -> BlockHeight lastGraphChangeInCut c = maximum $ - lastGraphChange . _bhwhHeight <$> (_cutHashes c) + lastGraphChange . _rankedHeight <$> (_cutHashes c) -- | This is a mixed transition cut, and thus has no unique chain graph. Such a -- cut may have invalid braiding according to *either* the pre- or post- @@ -1061,7 +1062,7 @@ cutMixedTransition c = let lgc = lastGraphChangeInCut c in - any ((< lgc) . _bhwhHeight) (_cutHashes c) + any ((< lgc) ._rankedHeight) (_cutHashes c) prioritizeHeavier :: Cut -> Cut -> DiffItem BlockHeader -> NonEmpty BlockHeader prioritizeHeavier = prioritizeHeavier_ `on` _cutHeaders diff --git a/src/Chainweb/Cut/CutHashes.hs b/src/Chainweb/Cut/CutHashes.hs index 0bdc61ff70..df24059f3f 100644 --- a/src/Chainweb/Cut/CutHashes.hs +++ b/src/Chainweb/Cut/CutHashes.hs @@ -41,7 +41,6 @@ module Chainweb.Cut.CutHashes , HasCutId(..) -- * CutHashes -, BlockHashWithHeight(..) , CutHashes(..) , cutHashes , cutHashesId @@ -95,12 +94,12 @@ import Chainweb.BlockHeight import Chainweb.BlockWeight import Chainweb.ChainId import Chainweb.Cut +import Chainweb.PayloadProvider(EncodedPayloadData(..), EncodedPayloadOutputs(..)) +import Chainweb.Ranked import Chainweb.Storage.Table import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version -import Chainweb.Version.Registry -import Chainweb.PayloadProvider(EncodedPayloadData(..), EncodedPayloadOutputs(..)) import P2P.Peer @@ -222,32 +221,6 @@ instance HasCutId CutId where -- -------------------------------------------------------------------------- -- -- Cut Hashes -data BlockHashWithHeight = BlockHashWithHeight - { _bhwhHeight :: !BlockHeight - , _bhwhHash :: !BlockHash - } - deriving (Show, Eq, Ord, Generic) - deriving anyclass (NFData) - -blockHashWithHeightProperties :: KeyValue e kv => BlockHashWithHeight -> [kv] -blockHashWithHeightProperties o = - [ "height" .= _bhwhHeight o - , "hash" .= _bhwhHash o - ] -{-# INLINE blockHashWithHeightProperties #-} - -instance ToJSON BlockHashWithHeight where - toJSON = object . blockHashWithHeightProperties - toEncoding = pairs . mconcat . blockHashWithHeightProperties - {-# INLINE toJSON #-} - {-# INLINE toEncoding #-} - -instance FromJSON BlockHashWithHeight where - parseJSON = withObject "HashWithHeight" $ \o -> BlockHashWithHeight - <$> o .: "height" - <*> o .: "hash" - {-# INLINE parseJSON #-} - -- | This data structure is used to inform other components and chainweb nodes -- about new cuts along with some properties of the cut. -- @@ -269,7 +242,7 @@ instance FromJSON BlockHashWithHeight where -- though. -- data CutHashes = CutHashes - { _cutHashes :: !(HM.HashMap ChainId BlockHashWithHeight) + { _cutHashes :: !(HM.HashMap ChainId RankedBlockHash) , _cutOrigin :: !(Maybe PeerInfo) -- ^ 'Nothing' is used for locally mined Cuts , _cutHashesWeight :: !BlockWeight @@ -300,7 +273,7 @@ makeLenses ''CutHashes -- | Complexity is linear in the number of chains -- _cutHashesMaxHeight :: CutHashes -> BlockHeight -_cutHashesMaxHeight = maximum . fmap _bhwhHeight . _cutHashes +_cutHashesMaxHeight = maximum . fmap _rankedHeight . _cutHashes {-# INLINE _cutHashesMaxHeight #-} cutHashesMaxHeight :: Getter CutHashes BlockHeight @@ -310,7 +283,7 @@ cutHashesMaxHeight = to _cutHashesMaxHeight -- | Complexity is linear in the number of chains -- _cutHashesMinHeight :: CutHashes -> BlockHeight -_cutHashesMinHeight = minimum . fmap _bhwhHeight . _cutHashes +_cutHashesMinHeight = minimum . fmap _rankedHeight . _cutHashes {-# INLINE _cutHashesMinHeight #-} cutHashesMinHeight :: Getter CutHashes BlockHeight @@ -381,7 +354,7 @@ instance HasVersion => FromJSON CutHashes where -- cutToCutHashes :: Maybe PeerInfo -> Cut -> CutHashes cutToCutHashes p c = CutHashes - { _cutHashes = (\x -> BlockHashWithHeight (view blockHeight x) (view blockHash x)) <$> _cutMap c + { _cutHashes = view rankedBlockHash <$> _cutMap c , _cutOrigin = p , _cutHashesWeight = _cutWeight c , _cutHashesHeight = _cutHeight c diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 168451869b..3b4e964b3a 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -160,6 +160,7 @@ import Data.TaskMap qualified as TM import P2P.TaskQueue import Utils.Logging.Trace +import Chainweb.Ranked -- -------------------------------------------------------------------------- -- -- Cut DB Configuration @@ -520,7 +521,7 @@ lookupCutHashes -> CutHashes -> IO (HM.HashMap ChainId BlockHeader) lookupCutHashes wbhdb hs = - flip itraverse (_cutHashes hs) $ \cid (BlockHashWithHeight _ h) -> + flip itraverse (_cutHashes hs) $ \cid (Ranked _ h) -> lookupWebBlockHeaderDb wbhdb cid h cutAvgBlockHeight :: HasVersion => Cut -> BlockHeight @@ -653,7 +654,7 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do -- isOld x = do curHashes <- cutToCutHashes Nothing <$> readTVarIO cutVar - let r = all (>= (0 :: Int)) $ (HM.unionWith (-) `on` (fmap (int . _bhwhHeight) . _cutHashes)) curHashes x + let r = all (>= (0 :: Int)) $ (HM.unionWith (-) `on` (fmap (int . _rankedHeight) . _cutHashes)) curHashes x when r $ loggCutId logFun Debug x "skip old cut" return r @@ -810,8 +811,7 @@ cutHashesToBlockHeaderMap conf logfun headerStore providers hs = let localPayload = _cutHashesLocalPayload hs (headers :> missing) <- S.each (HM.toList $ _cutHashes hs) - & S.map (fmap _bhwhHash) - & S.mapM (tryGetBlockHeader hdrs plds localPayload) + & S.mapM (\(cid, bh) -> tryGetBlockHeader hdrs plds localPayload $ (cid, bh)) & S.partitionEithers & S.fold_ (\x (cid, h) -> HM.insert cid h x) mempty id & S.fold (\x (cid, h) -> HM.insert cid h x) mempty id @@ -844,6 +844,7 @@ cutHashesToBlockHeaderMap conf logfun headerStore providers hs = cid priority origin + . _ranked `catch` \case (TreeDbKeyNotFound{} :: TreeDbException BlockHeaderDb) -> return (Left cv) diff --git a/src/Chainweb/Ranked.hs b/src/Chainweb/Ranked.hs index 712c81406a..0f873f925c 100644 --- a/src/Chainweb/Ranked.hs +++ b/src/Chainweb/Ranked.hs @@ -71,6 +71,7 @@ data Ranked a = Ranked deriving anyclass (Hashable, NFData) makeLenses ''Ranked + encodeRanked :: (a -> Put) -> Ranked a -> Put encodeRanked putA (Ranked r a) = do encodeBlockHeightBe r -- big endian encoding for lexicographical order diff --git a/src/Chainweb/RestAPI/NodeInfo.hs b/src/Chainweb/RestAPI/NodeInfo.hs index 00816ce375..5a2c525349 100644 --- a/src/Chainweb/RestAPI/NodeInfo.hs +++ b/src/Chainweb/RestAPI/NodeInfo.hs @@ -35,6 +35,7 @@ import Chainweb.Cut.CutHashes import Chainweb.CutDB import Chainweb.Difficulty (BlockDelay) import Chainweb.Graph +import Chainweb.Ranked import Chainweb.RestAPI.Utils import Chainweb.Utils import Chainweb.Utils.Rule @@ -82,7 +83,7 @@ nodeInfoHandler :: HasVersion => SomeCutDb -> Server NodeInfoApi nodeInfoHandler (SomeCutDb (CutDbT db :: CutDbT v)) = do curCut <- liftIO $ _cut db let ch = cutToCutHashes Nothing curCut - let curHeight = maximum $ map _bhwhHeight $ HM.elems $ _cutHashes ch + let curHeight = maximum $ map _rankedHeight $ HM.elems $ _cutHashes ch let graphs = unpackGraphs let curGraph = unsafeHead "Chainweb.RestAPI.NodeInfo.nodeInfoHandler.curGraph" $ dropWhile (\(h,_) -> h > curHeight) graphs let curChains = map fst $ snd curGraph diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index d066777d29..e8bb8ccdb7 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -171,6 +171,7 @@ import Pact.Core.Errors (pactErrorToOnChainError) import Pact.Core.Info (spanInfoToLineInfo) import Chainweb.PayloadProvider import Chainweb.Parent +import Chainweb.Ranked -- -------------------------------------------------------------------------- -- -- Utils @@ -400,8 +401,8 @@ instance HasVersion => Arbitrary HeaderUpdate where <*> arbitrary <*> arbitrary -instance Arbitrary BlockHashWithHeight where - arbitrary = BlockHashWithHeight <$> arbitrary <*> arbitrary +instance Arbitrary (Ranked BlockHash) where + arbitrary = Ranked <$> arbitrary <*> arbitrary -- -------------------------------------------------------------------------- -- -- Arbitrary CutHashes diff --git a/test/lib/Chainweb/Test/RestAPI/Utils.hs b/test/lib/Chainweb/Test/RestAPI/Utils.hs index 9e0828e8e0..cc7e0d6302 100644 --- a/test/lib/Chainweb/Test/RestAPI/Utils.hs +++ b/test/lib/Chainweb/Test/RestAPI/Utils.hs @@ -45,11 +45,12 @@ import Servant.Client import Chainweb.BlockHeight import Chainweb.ChainId -import Chainweb.Cut.CutHashes (_cutHashes, _bhwhHeight) +import Chainweb.Cut.CutHashes (_cutHashes) import Chainweb.CutDB.RestAPI.Client import Chainweb.Pact.RestAPI.Client import Chainweb.Pact.RestAPI.EthSpv import Chainweb.Pact.Types +import Chainweb.Ranked import Chainweb.Version import Chainweb.Test.Utils @@ -257,7 +258,7 @@ getCurrentBlockHeight :: HasVersion => ClientEnv -> ChainId -> IO BlockHeight getCurrentBlockHeight cenv cid = runClientM cutGetClient cenv >>= \case Left e -> throwM $ GetBlockHeightFailure $ "Failed to get cuts: " ++ show e - Right cuts -> return $ fromJust $ _bhwhHeight <$> HM.lookup cid (_cutHashes cuts) + Right cuts -> return $ fromJust $ _rankedHeight <$> HM.lookup cid (_cutHashes cuts) clientErrorStatusCode :: ClientError -> Maybe Int clientErrorStatusCode = \case diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index d2165b72bf..c9e0145a7e 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -246,6 +246,7 @@ import qualified Pact.Core.Command.Types as Pact5 import qualified Pact.Core.Errors as Pact5 import qualified Pact.Core.Hash as Pact5 import qualified Pact.Core.Gas as Pact +import Chainweb.Ranked (Ranked(_rankedHeight)) -- -------------------------------------------------------------------------- -- @@ -964,7 +965,7 @@ awaitBlockHeight cenv i = do case result of Left e -> throwM e Right x - | all (\bh -> _bhwhHeight bh >= i) (_cutHashes x) -> return () + | all (\bh -> _rankedHeight bh >= i) (_cutHashes x) -> return () | otherwise -> error $ "retries exhausted: waiting for cut height " <> sshow i <> " but only got " <> sshow (_cutHashesHeight x) @@ -972,7 +973,7 @@ awaitBlockHeight cenv i = do checkRetry _ (Left _) = return True checkRetry _ (Right c) - = return $ any (\bh -> _bhwhHeight bh < i) (_cutHashes c) + = return $ any (\bh -> _rankedHeight bh < i) (_cutHashes c) runTestNodes :: Logger logger diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index 7879216d1e..7ba462b2f6 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -297,7 +297,7 @@ jsonTestCases f = , testProperty "Probability" $ f @Probability , testProperty "LogFilterRule" $ f @LogFilterRule , testProperty "LogFilter" $ f @LogFilter - , testProperty "BlockHashWithHeight" $ f @BlockHashWithHeight + , testProperty "Ranked BlockHash" $ f @(Ranked BlockHash) , testProperty "CutId" $ f @CutId -- , testProperty "CutHashes" $ withVersion mainnet $ f @CutHashes , testProperty "NodeVersion" $ f @NodeVersion From e8c66f46ebf4a4a913716c0f791f9e12f283415a Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 28 May 2025 13:31:47 -0400 Subject: [PATCH 194/378] Disable some unusable tests Change-Id: Id0000000c710b3254c28490df4102788ad05b899 --- chainweb.cabal | 8 +- test/lib/Chainweb/Test/Cut.hs | 4 +- test/lib/Chainweb/Test/Orphans/Internal.hs | 236 +++++------ test/lib/Chainweb/Test/Orphans/Pact.hs | 94 ++--- .../Chainweb/Test/BlockHeaderDB/PruneForks.hs | 370 ------------------ test/unit/Chainweb/Test/Mining.hs | 69 ++-- test/unit/Chainweb/Test/Misc.hs | 102 ++--- test/unit/Chainweb/Test/Roundtrips.hs | 44 ++- test/unit/ChainwebTests.hs | 69 +--- 9 files changed, 302 insertions(+), 694 deletions(-) delete mode 100644 test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs diff --git a/chainweb.cabal b/chainweb.cabal index 2edab1329a..801239f828 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -712,12 +712,12 @@ test-suite chainweb-tests Chainweb.Test.BlockHeader.Genesis Chainweb.Test.BlockHeader.Validation Chainweb.Test.BlockHeaderDB - Chainweb.Test.BlockHeaderDB.PruneForks + -- Chainweb.Test.BlockHeaderDB.PruneForks Chainweb.Test.Chainweb.Utils.Paging Chainweb.Test.CutDB Chainweb.Test.Difficulty Chainweb.Test.Mempool - Chainweb.Test.Mempool.Consensus + -- Chainweb.Test.Mempool.Consensus Chainweb.Test.Mempool.InMem Chainweb.Test.Mempool.RestAPI Chainweb.Test.Mempool.Sync @@ -734,8 +734,8 @@ test-suite chainweb-tests Chainweb.Test.Pact.TransactionTests Chainweb.Test.RestAPI Chainweb.Test.Roundtrips - Chainweb.Test.SPV - Chainweb.Test.SPV.EventProof + -- Chainweb.Test.SPV + -- Chainweb.Test.SPV.EventProof Chainweb.Test.Sync.WebBlockHeaderStore Chainweb.Test.TreeDB Chainweb.Test.TreeDB.RemoteDB diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index d4943ee161..3f505309d8 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -779,8 +779,8 @@ properties_miscCut db = , ("prop_joinBaseMeet", prop_joinBaseMeet db) , ("prop_meetGenesisCut", ioTest db prop_meetGenesisCut) , ("Cuts of arbitrary fork have valid braiding", prop_arbitraryForkBraiding db) - , ("extendNoMixedTransitionalCuts", prop_extendNoMixedTransitionalCuts db) - , ("joinNoMixedTransitionalCuts", prop_joinNoMixedTransitionalCuts db) + -- , ("extendNoMixedTransitionalCuts", prop_extendNoMixedTransitionalCuts db) + -- , ("joinNoMixedTransitionalCuts", prop_joinNoMixedTransitionalCuts db) ] -- -------------------------------------------------------------------------- -- diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index e8bb8ccdb7..ae99fc4991 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -42,18 +42,18 @@ module Chainweb.Test.Orphans.Internal , arbitraryMerkleHeaderProof , arbitraryMerkleBodyProof --- ** Output Proofs -, arbitraryOutputMerkleProof -, arbitraryOutputProof -, mkTestOutputProof -, arbitraryOutputEvents -, arbitraryPayloadWithStructuredOutputs +-- -- ** Output Proofs +-- , arbitraryOutputMerkleProof +-- , arbitraryOutputProof +-- , mkTestOutputProof +-- , arbitraryOutputEvents +-- -- , arbitraryPayloadWithStructuredOutputs -- ** Events Proofs -, mkTestEventsProof -, arbitraryEventsProof -, EventPactValue(..) -, ProofPactEvent(..) +-- , mkTestEventsProof +-- , arbitraryEventsProof +-- , EventPactValue(..) +-- , ProofPactEvent(..) -- ** Misc , arbitraryPage @@ -691,69 +691,69 @@ hasHeader mlog = case go (sing @N @i) mlog of -- -------------------------------------------------------------------------- -- -- Output MerkleProofs / Netsted Merkle Proof -arbitraryPayloadWithStructuredOutputs :: Gen (V.Vector RequestKey, PayloadWithOutputs) -arbitraryPayloadWithStructuredOutputs = resize 10 $ do - txs <- V.fromList . L.nubBy ((==) `on` _crReqKey . snd) - <$> listOf ((,) <$> arbitrary @Transaction <*> genResult) - payloads <- newPayloadWithOutputs - <$> arbitrary - <*> arbitrary - -- TODO: PP - <*> pure (fmap (TransactionOutput . J.encodeStrict . fmap (pactErrorToOnChainError . fmap spanInfoToLineInfo)) <$> txs) - return (_crReqKey . snd <$> txs, payloads) - where - genResult = arbitraryCommandResultWithEvents arbitraryProofPactEvent - --- | This creates proof over payloads that contain arbitrary bytestrings. --- -arbitraryOutputMerkleProof - :: forall a - . MerkleHashAlgorithm a - => Gen (MerkleProof a) -arbitraryOutputMerkleProof = do - (p, s) <- genPayload - idx <- choose (0, s - 1) - case outputMerkleProofByIdx @a p idx of - Left e -> error $ "Chainweb.Test.Orphans.Internal.arbitraryBlockOutputsMerkleProof: " <> show e - Right x -> return x - where - -- this uses the default chainweb hash - genPayload = suchThatMap (arbitrary @PayloadWithOutputs) $ \p -> - let s = V.length (_payloadWithOutputsTransactions p) - in (p, s) <$ guard (s > 0) - -arbitraryOutputProof - :: forall a - . MerkleHashAlgorithm a - => Gen (PayloadProof a) -arbitraryOutputProof = do - (ks, p) <- genPayload - k <- elements $ V.toList ks - return $ mkTestOutputProof p k - where - -- this uses the default chainweb hash - genPayload = suchThat arbitraryPayloadWithStructuredOutputs $ \(_, p) -> - V.length (_payloadWithOutputsTransactions p) > 0 - -mkTestOutputProof - :: forall a - . MerkleHashAlgorithm a - => HasCallStack - => PayloadWithOutputs - -> RequestKey - -> PayloadProof a -mkTestOutputProof p reqKey = unsafePerformIO $ createOutputProof_ @a p reqKey - -instance MerkleHashAlgorithm a => Arbitrary (PayloadProof a) where - arbitrary = arbitraryOutputProof - --- | This creates proof over payloads that contain arbitrary bytestrings. --- --- TODO: use a more complex nested proof here, like the ones that occur in --- cross chain SPV proofs. --- -instance MerkleHashAlgorithm a => Arbitrary (MerkleProof a) where - arbitrary = arbitraryOutputMerkleProof +-- arbitraryPayloadWithStructuredOutputs :: Gen (V.Vector RequestKey, PayloadWithOutputs) +-- arbitraryPayloadWithStructuredOutputs = resize 10 $ do +-- txs <- V.fromList . L.nubBy ((==) `on` _crReqKey . snd) +-- <$> listOf ((,) <$> arbitrary @Transaction <*> genResult) +-- payloads <- newPayloadWithOutputs +-- <$> arbitrary +-- <*> arbitrary +-- -- TODO: PP +-- <*> pure (fmap (TransactionOutput . J.encodeStrict . fmap (pactErrorToOnChainError . fmap spanInfoToLineInfo)) <$> txs) +-- return (_crReqKey . snd <$> txs, payloads) +-- where +-- genResult = arbitraryCommandResultWithEvents arbitraryProofPactEvent + +-- -- | This creates proof over payloads that contain arbitrary bytestrings. +-- -- +-- arbitraryOutputMerkleProof +-- :: forall a +-- . MerkleHashAlgorithm a +-- => Gen (MerkleProof a) +-- arbitraryOutputMerkleProof = do +-- (p, s) <- genPayload +-- idx <- choose (0, s - 1) +-- case outputMerkleProofByIdx @a p idx of +-- Left e -> error $ "Chainweb.Test.Orphans.Internal.arbitraryBlockOutputsMerkleProof: " <> show e +-- Right x -> return x +-- where +-- -- this uses the default chainweb hash +-- genPayload = suchThatMap (arbitrary @PayloadWithOutputs) $ \p -> +-- let s = V.length (_payloadWithOutputsTransactions p) +-- in (p, s) <$ guard (s > 0) + +-- arbitraryOutputProof +-- :: forall a +-- . MerkleHashAlgorithm a +-- => Gen (PayloadProof a) +-- arbitraryOutputProof = do +-- (ks, p) <- genPayload +-- k <- elements $ V.toList ks +-- return $ mkTestOutputProof p k +-- where +-- -- this uses the default chainweb hash +-- genPayload = suchThat arbitraryPayloadWithStructuredOutputs $ \(_, p) -> +-- V.length (_payloadWithOutputsTransactions p) > 0 + +-- mkTestOutputProof +-- :: forall a +-- . MerkleHashAlgorithm a +-- => HasCallStack +-- => PayloadWithOutputs +-- -> RequestKey +-- -> PayloadProof a +-- mkTestOutputProof p reqKey = unsafePerformIO $ createOutputProof_ @a p reqKey + +-- instance MerkleHashAlgorithm a => Arbitrary (PayloadProof a) where +-- arbitrary = arbitraryOutputProof + +-- -- | This creates proof over payloads that contain arbitrary bytestrings. +-- -- +-- -- TODO: use a more complex nested proof here, like the ones that occur in +-- -- cross chain SPV proofs. +-- -- +-- instance MerkleHashAlgorithm a => Arbitrary (MerkleProof a) where +-- arbitrary = arbitraryOutputMerkleProof -- -------------------------------------------------------------------------- -- -- Events Merkle Proofs @@ -767,18 +767,18 @@ mkTestEventsProof -> PayloadProof a mkTestEventsProof p reqKey = unsafePerformIO $ createEventsProof_ @a p reqKey -arbitraryEventsProof - :: forall a - . MerkleHashAlgorithm a - => Gen (PayloadProof a) -arbitraryEventsProof = do - (ks, p) <- genPayload - k <- elements $ V.toList ks - return $ mkTestEventsProof p k - where - -- this uses the default chainweb hash - genPayload = suchThat arbitraryPayloadWithStructuredOutputs $ \(_, p) -> - V.length (_payloadWithOutputsTransactions p) > 0 +-- arbitraryEventsProof +-- :: forall a +-- . MerkleHashAlgorithm a +-- => Gen (PayloadProof a) +-- arbitraryEventsProof = do +-- (ks, p) <- genPayload +-- k <- elements $ V.toList ks +-- return $ mkTestEventsProof p k +-- where +-- -- this uses the default chainweb hash +-- genPayload = suchThat arbitraryPayloadWithStructuredOutputs $ \(_, p) -> +-- V.length (_payloadWithOutputsTransactions p) > 0 -- -------------------------------------------------------------------------- -- -- Misc @@ -851,22 +851,22 @@ arbitraryEventPactValue = oneof , PLiteral . LInteger <$> (int256ToInteger <$> arbitrary) ] --- | Arbitrary Pact events that are supported in events proofs --- -arbitraryProofPactEvent :: Gen (PactEvent PactValue) -arbitraryProofPactEvent = PactEvent - <$> arbitrary - <*> listOf arbitraryEventPactValue - <*> arbitrary - <*> arbitrary +-- -- | Arbitrary Pact events that are supported in events proofs +-- -- +-- arbitraryProofPactEvent :: Gen (PactEvent PactValue) +-- arbitraryProofPactEvent = PactEvent +-- <$> arbitrary +-- <*> listOf arbitraryEventPactValue +-- <*> arbitrary +-- <*> arbitrary -arbitraryOutputEvents :: Gen OutputEvents -arbitraryOutputEvents = OutputEvents - <$> arbitrary - <*> (V.fromList <$> listOf arbitraryProofPactEvent) +-- arbitraryOutputEvents :: Gen OutputEvents +-- arbitraryOutputEvents = OutputEvents +-- <$> arbitrary +-- <*> (V.fromList <$> listOf arbitraryProofPactEvent) -instance Arbitrary OutputEvents where - arbitrary = arbitraryOutputEvents +-- instance Arbitrary OutputEvents where +-- arbitrary = arbitraryOutputEvents -- | Events that are supported in proofs -- @@ -880,8 +880,8 @@ instance ToJSON ProofPactEvent where toJSON = J.toJsonViaEncode . getProofPactEvent {-# INLINEABLE toJSON #-} -instance Arbitrary ProofPactEvent where - arbitrary = ProofPactEvent . StableEncoding <$> arbitraryProofPactEvent +-- instance Arbitrary ProofPactEvent where +-- arbitrary = ProofPactEvent . StableEncoding <$> arbitraryProofPactEvent instance MerkleHashAlgorithm a => Arbitrary (BlockEventsHash_ a) where arbitrary = BlockEventsHash <$> arbitrary @@ -903,26 +903,26 @@ instance Arbitrary EventPactValue where instance Arbitrary SpvAlgorithm where arbitrary = elements [SpvSHA512t_256, SpvKeccak_256] -instance Arbitrary SpvSubjectIdentifier where - arbitrary = SpvSubjectIdentifier <$> arbitrary <*> arbitrary <*> arbitrary +-- instance Arbitrary SpvSubjectIdentifier where +-- arbitrary = SpvSubjectIdentifier <$> arbitrary <*> arbitrary <*> arbitrary instance Arbitrary SpvSubjectType where arbitrary = elements [SpvSubjectResult, SpvSubjectEvents] -instance Arbitrary SpvRequest where - arbitrary = SpvRequest <$> arbitrary <*> arbitrary +-- instance Arbitrary SpvRequest where +-- arbitrary = SpvRequest <$> arbitrary <*> arbitrary -instance Arbitrary Spv2Request where - arbitrary = Spv2Request <$> arbitrary <*> arbitrary <*> arbitrary +-- instance Arbitrary Spv2Request where +-- arbitrary = Spv2Request <$> arbitrary <*> arbitrary <*> arbitrary -instance Arbitrary (TransactionOutputProof ChainwebMerkleHashAlgorithm) where - arbitrary = TransactionOutputProof <$> arbitrary <*> arbitrary +-- instance Arbitrary (TransactionOutputProof ChainwebMerkleHashAlgorithm) where +-- arbitrary = TransactionOutputProof <$> arbitrary <*> arbitrary -instance Arbitrary SomePayloadProof where - arbitrary = oneof - [ SomePayloadProof <$> arbitrary @(PayloadProof ChainwebMerkleHashAlgorithm) - , SomePayloadProof <$> arbitrary @(PayloadProof Keccak256) - ] +-- instance Arbitrary SomePayloadProof where +-- arbitrary = oneof +-- [ SomePayloadProof <$> arbitrary @(PayloadProof ChainwebMerkleHashAlgorithm) +-- , SomePayloadProof <$> arbitrary @(PayloadProof Keccak256) +-- ] -- Equality for SomePayloadProof is only used for testing. Using 'unsafeCoerce' -- is a bit ugly, but efficient and doesn't require changing production code. @@ -939,11 +939,11 @@ instance Eq SomePayloadProof where instance Arbitrary MinerId where arbitrary = MinerId <$> arbitrary -instance Arbitrary MinerGuard where - arbitrary = MinerGuard <$> arbitrary +-- instance Arbitrary MinerGuard where +-- arbitrary = MinerGuard <$> arbitrary -instance Arbitrary Miner where - arbitrary = Miner <$> arbitrary <*> arbitrary +-- instance Arbitrary Miner where +-- arbitrary = Miner <$> arbitrary <*> arbitrary -- -------------------------------------------------------------------------- -- -- Mempool diff --git a/test/lib/Chainweb/Test/Orphans/Pact.hs b/test/lib/Chainweb/Test/Orphans/Pact.hs index 07c438461d..0f2a34b10f 100644 --- a/test/lib/Chainweb/Test/Orphans/Pact.hs +++ b/test/lib/Chainweb/Test/Orphans/Pact.hs @@ -19,11 +19,11 @@ -- -- Orphand Arbitrary Instances for Pact Types -- -module Chainweb.Test.Orphans.Pact +module Chainweb.Test.Orphans.Pact where -- ( arbitraryJsonValue -( arbitraryCommandResultWithEvents -, arbitraryMaybe -) where +-- ( arbitraryCommandResultWithEvents +-- , arbitraryMaybe +-- ) where -- import qualified Data.Aeson as A -- import qualified Data.Vector as V @@ -60,46 +60,46 @@ import qualified Pact.Core.Guards as Pact -- , A.Number <$> arbitrary -- ] --- | Generates only successful results without continuations. --- -arbitraryCommandResultWithEvents :: Gen (PactEvent PactValue) -> Gen (CommandResult Hash PactErrorI) -arbitraryCommandResultWithEvents genEvent = CommandResult - <$> undefined -- (RequestKey <$> arbitrary) -- _crReqKey - <*> (fmap TxId <$> arbitrary) -- _crTxId - <*> undefined -- arbitrary -- _crResult -- TODO: PP - <*> undefined -- (Gas <$> arbitrary) -- _crGas - <*> undefined -- arbitrary -- _crLogs - <*> pure Nothing -- _crContinuation - <*> undefined -- arbitraryMaybe (resize 5 arbitraryJsonValue) -- _crMetaData - <*> (resize 10 (listOf genEvent)) -- _crEvents - -instance Arbitrary ModuleName where - -- TODO: PP - arbitrary = undefined - -instance Arbitrary ModuleHash where - -- TODO: PP - arbitrary = undefined - -instance Arbitrary RequestKey where - -- TODO: PP - arbitrary = undefined - -instance Arbitrary Pact.ChainId where - -- TODO: PP - arbitrary = undefined - -instance Arbitrary QualifiedName where - -- TODO: PP - arbitrary = undefined - -instance Arbitrary PactValue where - -- TODO: PP - arbitrary = undefined - -instance (Arbitrary n, Arbitrary v) => Arbitrary (Pact.Guard n v) where - -- TODO: PP - arbitrary = undefined - -arbitraryMaybe :: Gen a -> Gen (Maybe a) -arbitraryMaybe gen = arbitrary >>= mapM (const @_ @() gen) +-- -- | Generates only successful results without continuations. +-- -- +-- arbitraryCommandResultWithEvents :: Gen (PactEvent PactValue) -> Gen (CommandResult Hash PactErrorI) +-- arbitraryCommandResultWithEvents genEvent = CommandResult +-- <$> undefined -- (RequestKey <$> arbitrary) -- _crReqKey +-- <*> (fmap TxId <$> arbitrary) -- _crTxId +-- <*> undefined -- arbitrary -- _crResult -- TODO: PP +-- <*> undefined -- (Gas <$> arbitrary) -- _crGas +-- <*> undefined -- arbitrary -- _crLogs +-- <*> pure Nothing -- _crContinuation +-- <*> undefined -- arbitraryMaybe (resize 5 arbitraryJsonValue) -- _crMetaData +-- <*> (resize 10 (listOf genEvent)) -- _crEvents + +-- instance Arbitrary ModuleName where +-- -- TODO: PP +-- arbitrary = undefined + +-- instance Arbitrary ModuleHash where +-- -- TODO: PP +-- arbitrary = undefined + +-- instance Arbitrary RequestKey where +-- -- TODO: PP +-- arbitrary = undefined + +-- instance Arbitrary Pact.ChainId where +-- -- TODO: PP +-- arbitrary = undefined + +-- instance Arbitrary QualifiedName where +-- -- TODO: PP +-- arbitrary = undefined + +-- instance Arbitrary PactValue where +-- -- TODO: PP +-- arbitrary = undefined + +-- instance (Arbitrary n, Arbitrary v) => Arbitrary (Pact.Guard n v) where +-- -- TODO: PP +-- arbitrary = undefined + +-- arbitraryMaybe :: Gen a -> Gen (Maybe a) +-- arbitraryMaybe gen = arbitrary >>= mapM (const @_ @() gen) diff --git a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs deleted file mode 100644 index 5fb76f8b88..0000000000 --- a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs +++ /dev/null @@ -1,370 +0,0 @@ -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} - --- | --- Module: Chainweb.Test.BlockHeaderDB.PruneForks --- Copyright: Copyright © 2020 Kadena LLC. --- License: MIT --- Maintainer: Lars Kuhtz --- Stability: experimental --- --- TODO --- -module Chainweb.Test.BlockHeaderDB.PruneForks -( tests -) where - -import Control.Lens (view, (.~)) -import Control.Monad -import Control.Monad.Catch - -import qualified Data.Text as T - -import Numeric.Natural - -import System.LogLevel -import System.Random - -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Chainweb.BlockHeader.Internal -import Chainweb.BlockHeader.Validation -import Chainweb.BlockHeaderDB -import Chainweb.BlockHeaderDB.Internal -import Chainweb.BlockHeaderDB.PruneForks --- import Chainweb.Chainweb.PruneChainDatabase -import Chainweb.Logger -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB -import Chainweb.Test.Utils -import Chainweb.Test.Utils.BlockHeader -import Chainweb.Utils -import Chainweb.Version -import Chainweb.Version.RecapDevelopment - -import Chainweb.Storage.Table -import Chainweb.Storage.Table.RocksDB -import Chainweb.BlockHeight - --- -------------------------------------------------------------------------- -- --- Utils - --- | Log level for the tests in this module. Set to 'Debug' for debugging any --- issues with the test. --- -testLogLevel :: LogLevel -testLogLevel = Warn - --- logg :: Show a => (String -> t) -> a -> T.Text -> t --- logg s l msg --- | l >= testLogLevel = s $ "[" <> sshow l <> "] " <> T.unpack msg --- | otherwise = return () - -withDbs - :: HasVersion - => IO RocksDb - -> (RocksDb -> BlockHeaderDb -> PayloadDb RocksDbTable -> BlockHeader -> IO ()) - -> IO () -withDbs rio inner = do - -- create unique namespace for each test so that they so that test can - -- run in parallel. - x <- randomIO :: IO Int - rdb <- rio >>= testRocksDb (sshow x) - - let pdb = newPayloadDb rdb - initializePayloadDb pdb - bracket - (initBlockHeaderDb (Configuration h rdb)) - closeBlockHeaderDb - (\bdb -> inner rdb bdb pdb h) - where - h = toyGenesis cid - -createForks - :: HasVersion - => BlockHeaderDb - -> PayloadDb RocksDbTable - -> BlockHeader - -> IO ([BlockHeader], [BlockHeader]) -createForks bdb pdb h = (,) - <$> insertWithPayloads bdb pdb h (Nonce 1) 10 - <*> insertWithPayloads bdb pdb h (Nonce 2) 5 - -insertWithPayloads - :: HasVersion - => BlockHeaderDb - -> PayloadDb RocksDbTable - -> BlockHeader - -> Nonce - -> Natural - -> IO [BlockHeader] -insertWithPayloads bdb pdb h n l = do - hdrs <- insertN_ n l h bdb - forM_ hdrs $ \hd -> - let payload = testBlockPayload_ hd - in addNewPayload pdb (view blockHeight hd) payload - return hdrs - -cid :: ChainId -cid = unsafeChainId 0 - -delHdr :: BlockHeaderDb -> BlockHeader -> IO () -delHdr cdb k = do - tableDelete (_chainDbCas cdb) (casKey $ RankedBlockHeader k) - tableDelete (_chainDbRankTable cdb) (view blockHash k) - --- -------------------------------------------------------------------------- -- --- Test cases - -tests :: TestTree -tests = withVersion toyVersion $ withResourceT withRocksResource $ \rio -> - testGroup "Chainweb.BlockHeaderDb.PruneForks" - [ testCaseSteps "simple 1" (test0 rio) - , testCaseSteps "simple 2" (test1 rio) - , testCaseSteps "simple 3" (test2 rio) - , testCaseSteps "simple 5" (test3 rio) - , testCaseSteps "Skippping: max bound 1" $ test4 rio - , testCaseSteps "Skippping: depth 10" $ test5 rio - , testCaseSteps "fail on missing header 5" $ failTest rio 5 - , testCaseSteps "fail on missing header 6" $ failTest rio 6 - -- failTest <= 4: succeeds because of second branch - -- failTest 7: empty upper bound warning - -- failTest 8: succeeds because deleted block at height 8 is above upper pruning bound - - , pruneWithChecksTests rio - , failPruningChecksTests rio - , testCaseSteps "full gc" $ testFullGc rio - ] - -pruneWithChecksTests :: HasVersion => IO RocksDb -> TestTree -pruneWithChecksTests rio = testGroup "prune with checks" $ go <$> - [ [CheckPayloads] - , [CheckPayloadsExist] - , [CheckIntrinsic] - , [CheckInductive] - , [CheckFull] - , [minBound .. maxBound] - ] - where - go checks = testCaseSteps (sshow checks) $ testPruneWithChecks rio checks - -failPruningChecksTests :: HasVersion => IO RocksDb -> TestTree -failPruningChecksTests rio = testGroup "fail pruning checks" - [ testCaseSteps "CheckPayloadExists" $ failPayloadCheck rio [CheckPayloadsExist] 7 - , testCaseSteps "CheckPayload" $ failPayloadCheck rio [CheckPayloads] 7 - - -- deleted transactions from payload - -- CheckPayloadsExist succeeds for this scenario - , testCaseSteps "CheckPayload2" $ failPayloadCheck2 rio [CheckPayloads] 7 - - , testCaseSteps "CheckIntrinsic" $ failIntrinsicCheck rio [CheckIntrinsic] 7 - , testCaseSteps "CheckInductive" $ failIntrinsicCheck rio [CheckInductive] 7 - , testCaseSteps "CheckFull" $ failIntrinsicCheck rio [CheckFull] 7 - ] - -singleForkTest - :: HasVersion - => IO RocksDb - -> (String -> IO ()) - -> Natural - -> Int - -> String - -> IO () -singleForkTest rio step d expect msg = - withDbs rio $ \_rdb db pdb h -> do - (f0, f1) <- createForks db pdb h - n <- pruneForks logg db d $ \_ x -> - logg Info (sshow $ view blockHeight x) - assertHeaders db f0 - when (expect > 0) $ assertPrunedHeaders db f1 - assertEqual msg expect n - where - logg = logFunctionText $ genericLogger testLogLevel (step . T.unpack) - -assertHeaders :: HasVersion => BlockHeaderDb -> [BlockHeader] -> IO () -assertHeaders db f = - unlessM (fmap and $ mapM (tableMember db) $ view blockHash <$> f) $ - assertFailure "missing block header that should not have been pruned" - -assertPrunedHeaders :: HasVersion => BlockHeaderDb -> [BlockHeader] -> IO () -assertPrunedHeaders db f = - whenM (fmap or $ mapM (tableMember db) $ view blockHash <$> f) $ - assertFailure "failed to prune some block header" - -assertPayloads :: PayloadDb RocksDbTable -> [BlockHeader] -> IO () -assertPayloads db f = do - let fs = (\h -> (Just $ view blockHeight h, view blockPayloadHash h)) <$> f - unlessM (and <$> mapM (uncurry $ lookupPayloadWithHeightExists db) fs) $ - assertFailure "missing block payload that should not have been garbage collected" - --- | This can fail due to the probabilistic nature of the GC algorithms --- -assertPrunedPayloads :: PayloadDb RocksDbTable -> [BlockHeader] -> IO () -assertPrunedPayloads db f = do - let fs = (\h -> (Just $ view blockHeight h, view blockPayloadHash h)) <$> f - results <- mapM (uncurry $ lookupPayloadWithHeightExists db) fs - let remained = length (filter id results) - when (remained > 1) $ - assertFailure $ "failed to garage collect some block payloads" - <> ". " <> sshow remained <> " remaining" - <> ". Since can happen due to the probabilistic natures of the garabage collection algorithm" - <> ". But it should very rare. Try to rerun the test." - -lookupPayloadWithHeightExists - :: PayloadDb RocksDbTable - -> Maybe BlockHeight - -> BlockPayloadHash - -> IO Bool -lookupPayloadWithHeightExists db h k = lookupPayloadWithHeight db h k >>= \case - Nothing -> pure False - Just _ -> pure True - --- -------------------------------------------------------------------------- -- --- Header Pruning Tests - -test0 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () -test0 rio step = singleForkTest rio step 1 5 "5 block headers pruned" - -test1 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () -test1 rio step = singleForkTest rio step 2 5 "5 block headers pruned" - -test2 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () -test2 rio step = singleForkTest rio step 4 5 "5 block headers pruned" - -test3 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () -test3 rio step = singleForkTest rio step 5 0 "0 block headers pruned" - -test4 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () -test4 rio step = singleForkTest rio step 9 0 "Skipping: max bound 1" - -test5 :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () -test5 rio step = singleForkTest rio step 10 0 - "Skipping: depth == max block height" - -failTest :: HasVersion => IO RocksDb -> Natural -> (String -> IO ()) -> IO () -failTest rio n step = withDbs rio $ \_rdb db pdb h -> do - (f0, _) <- createForks db pdb h - delHdr db $ f0 !! (int n) - try (prune db 2) >>= \case - Left (InternalInvariantViolation{}) -> return () - Right x -> assertFailure - $ "missing expected InternalInvariantViolation" - <> ". Instead pruning succeeded and deleted " - <> sshow x <> " headers" - return () - where - prune db d = pruneForks logg db d $ \_ h -> - logg Info (sshow $ view blockHeight h) - - logg = logFunctionText $ genericLogger testLogLevel (step . T.unpack) - --- -------------------------------------------------------------------------- -- --- GC Tests - -testFullGc :: HasVersion => IO RocksDb -> (String -> IO ()) -> IO () -testFullGc rio step = withDbs rio $ \rdb db pdb h -> do - (f0, f1) <- createForks db pdb h - fullGc logger rdb - assertHeaders db f0 - assertPrunedHeaders db f1 - assertPayloads pdb f0 - assertPrunedPayloads pdb f1 - where - logger = genericLogger testLogLevel (step . T.unpack) - -testPruneWithChecks :: HasVersion => IO RocksDb -> [PruningChecks] -> (String -> IO ()) -> IO () -testPruneWithChecks rio checks step = withDbs rio $ \rdb db pdb h -> do - (f0, f1) <- createForks db pdb h - pruneAllChains logger rdb checks - assertHeaders db f0 - assertPrunedHeaders db f1 - where - logger = genericLogger testLogLevel (step . T.unpack) - --- | Remove BlockPayload from the Payload. --- -failIntrinsicCheck :: HasVersion => IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () -failIntrinsicCheck rio checks n step = withDbs rio $ \rdb bdb pdb h -> do - (f0, _) <- createForks bdb pdb h - let b = f0 !! int n - delHdr bdb b - unsafeInsertBlockHeaderDb bdb $ b - & blockChainwebVersion .~ _versionCode RecapDevelopment - try (pruneAllChains logger rdb checks) >>= \case - Left e - | CheckFull `elem` checks - && VersionMismatch `elem` _validationFailureFailures e - && IncorrectHash `elem` _validationFailureFailures e - && AdjacentChainMismatch `elem` _validationFailureFailures e -> return () - | CheckInductive `elem` checks - && VersionMismatch `elem` _validationFailureFailures e -> return () - | CheckIntrinsic `elem` checks - && IncorrectHash `elem` _validationFailureFailures e - && AdjacentChainMismatch `elem` _validationFailureFailures e -> return () - | otherwise -> - assertFailure $ "test failed with unexpected validation failure: " <> sshow e - Right x -> assertFailure - $ "missing expected ValidationFailure" - <> ". Instead pruning succeeded and deleted " - <> sshow x <> " headers" - return () - where - logger = genericLogger testLogLevel (step . T.unpack) - --- | Remove BlockPayload from the Payload. --- --- CheckPayloadsExist and CheckPayload fail for this scenario --- -failPayloadCheck :: HasVersion => IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () -failPayloadCheck rio checks n step = withDbs rio $ \rdb bdb pdb h -> do - (f0, _) <- createForks bdb pdb h - let b = f0 !! int n - p <- lookupPayloadDataWithHeight pdb (Just $ view blockHeight b) (view blockPayloadHash b) >>= \case - Nothing -> assertFailure "missing payload" - Just x -> return x - deletePayload pdb (payloadDataToBlockPayload p) - - try (pruneAllChains logger rdb checks) >>= \case - Left (MissingPayloadException{}) -> return () - Left e -> assertFailure - $ "Expected MissingPayloadException but got: " - <> sshow e - Right x -> assertFailure - $ "missing expected MissingPayloadException" - <> ". Instead pruning succeeded and deleted " - <> sshow x <> " headers" - return () - where - logger = genericLogger testLogLevel (step . T.unpack) - --- | Remove the Transactions from the Payload. --- --- CheckPayloadsExist succeeds for this scenario. CheckPayload fails. --- -failPayloadCheck2 :: HasVersion => IO RocksDb -> [PruningChecks] -> Natural -> (String -> IO ()) -> IO () -failPayloadCheck2 rio checks n step = withDbs rio $ \rdb bdb pdb h -> do - (f0, _) <- createForks bdb pdb h - let b = f0 !! int n - payload <- lookupPayloadWithHeight pdb (Just $ view blockHeight b) (view blockPayloadHash b) >>= \case - Nothing -> assertFailure "missing payload" - Just x -> return x - tableDelete (_newTransactionDbBlockTransactionsTbl $ _transactionDb pdb) - (view blockHeight b, _payloadWithOutputsTransactionsHash payload) - try (pruneAllChains logger rdb checks) >>= \case - Left (MissingPayloadException{}) -> return () - Left e -> assertFailure - $ "Expected MissingPayloadException but got: " - <> sshow e - Right x -> assertFailure - $ "missing expected MissingPayloadException" - <> ". Instead pruning succeeded and deleted " - <> sshow x <> " headers" - return () - where - logger = genericLogger testLogLevel (step . T.unpack) diff --git a/test/unit/Chainweb/Test/Mining.hs b/test/unit/Chainweb/Test/Mining.hs index 419a5755be..27452d2113 100644 --- a/test/unit/Chainweb/Test/Mining.hs +++ b/test/unit/Chainweb/Test/Mining.hs @@ -53,45 +53,46 @@ import Chainweb.Version (withVersion) tests :: RocksDb -> TestTree tests rdb = testGroup "Mining" - [ testCaseSteps "Miner account names are not empty strings" (nonEmptyMiningAccount rdb) - ] + [ ] + -- testCaseSteps "Miner account names are not empty strings" (nonEmptyMiningAccount rdb) + -- ] -- -------------------------------------------------------------------------- -- -- Test Mining Coordinator -withTestCoordinator - :: HasCallStack - => RocksDb - -> (String -> IO ()) - -> Maybe MiningConfig - -- ^ Custom Mining configuration. If coordination is disabled it will be - -- set to enabled before the coordinator is initialized. - -> (forall tbl logger . Logger logger => logger -> MiningCoordination logger tbl -> IO ()) - -> IO () -withTestCoordinator rdb logg maybeConf a = withVersion v $ do - var <- newEmptyMVar - x <- race (takeMVar var) $ - withTestCutDb rdb id 0 (\_ _ -> return fakePact) (logFunction logger) $ \_ cdb -> - withMiningCoordination logger conf cdb $ \case - Nothing -> error "nonEmptyMiningAccount: Bug in the mining Code" - Just coord -> do - a logger coord - putMVar var () - case x of - Left () -> logFunctionText logger Info "withTestCoordinator: action finished" - Right () -> logFunctionText logger Info "withTestCoordinator: coordinator service stopped" - - where - v = barebonesTestVersion pairChainGraph - logger = genericLogger Warn (logg . T.unpack) - conf = fromMaybe defaultMining maybeConf - & miningCoordination . coordinationEnabled .~ True +-- withTestCoordinator +-- :: HasCallStack +-- => RocksDb +-- -> (String -> IO ()) +-- -> Maybe MiningConfig +-- -- ^ Custom Mining configuration. If coordination is disabled it will be +-- -- set to enabled before the coordinator is initialized. +-- -> (forall tbl logger . Logger logger => logger -> MiningCoordination logger tbl -> IO ()) +-- -> IO () +-- withTestCoordinator rdb logg maybeConf a = withVersion v $ do +-- var <- newEmptyMVar +-- x <- race (takeMVar var) $ +-- withTestCutDb rdb id 0 (\_ _ -> return fakePact) (logFunction logger) $ \_ cdb -> +-- withMiningCoordination logger conf cdb $ \case +-- Nothing -> error "nonEmptyMiningAccount: Bug in the mining Code" +-- Just coord -> do +-- a logger coord +-- putMVar var () +-- case x of +-- Left () -> logFunctionText logger Info "withTestCoordinator: action finished" +-- Right () -> logFunctionText logger Info "withTestCoordinator: coordinator service stopped" + +-- where +-- v = barebonesTestVersion pairChainGraph +-- logger = genericLogger Warn (logg . T.unpack) +-- conf = fromMaybe defaultMining maybeConf +-- & miningCoordination . coordinationEnabled .~ True -- -------------------------------------------------------------------------- -- -- Tests -nonEmptyMiningAccount :: HasCallStack => RocksDb -> (String -> IO ()) -> Assertion -nonEmptyMiningAccount rdb logg = withTestCoordinator rdb logg Nothing $ \_logger coord -> do - PrimedWork w <- readTVarIO (_coordPrimedWork coord) - forM_ (HM.keys w) $ \(MinerId k) -> - assertBool "miner account name must not be the empty string" (not (T.null k)) +-- nonEmptyMiningAccount :: HasCallStack => RocksDb -> (String -> IO ()) -> Assertion +-- nonEmptyMiningAccount rdb logg = withTestCoordinator rdb logg Nothing $ \_logger coord -> do +-- PrimedWork w <- readTVarIO (_coordPrimedWork coord) +-- forM_ (HM.keys w) $ \(MinerId k) -> +-- assertBool "miner account name must not be the empty string" (not (T.null k)) diff --git a/test/unit/Chainweb/Test/Misc.hs b/test/unit/Chainweb/Test/Misc.hs index 6d652615e9..f63945d04b 100644 --- a/test/unit/Chainweb/Test/Misc.hs +++ b/test/unit/Chainweb/Test/Misc.hs @@ -1,6 +1,7 @@ {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} -- | -- Module: Chainweb.Test.Misc @@ -36,7 +37,11 @@ import Data.Function ((&)) import Chainweb.Utils (HasTextRepresentation(..)) import Chainweb.MerkleLogHash (unsafeMerkleLogHash) import qualified Data.ByteString as BS +import Control.Lens import Control.Monad +import Chainweb.Parent +import Chainweb.Utils.Serialization +import Chainweb.MerkleUniverse --- @@ -54,9 +59,9 @@ tests = testGroup "Misc. Unit Tests" ] , testGroup "hashed adjacent block hash record" [ testCase "smoke test" hashedAdjacentBlockHashRecordSmokeTest - , testCase "null padding" hashedAdjacentBlockHashRecordNullPadding - , testCase "truncation" hashedAdjacentBlockHashRecordTruncation - , testCase "bit flipping" hashedAdjacentBlockHashRecordBitFlipping + -- , testCase "null padding" hashedAdjacentBlockHashRecordNullPadding + -- , testCase "truncation" hashedAdjacentBlockHashRecordTruncation + -- , testCase "bit flipping" hashedAdjacentBlockHashRecordBitFlipping ] ] @@ -99,49 +104,48 @@ hashedAdjacentBlockHashRecordSmokeTest :: IO () hashedAdjacentBlockHashRecordSmokeTest = do -- smoke test blockHashHash <- fromText "rxPASJkSJKXkxmREa2iKr0j7VFbbNilgGwDsFgx05VQ" - convertBlockHashRecordForMining - (BlockHashRecord (HM.fromList [(unsafeChainId 0, nullBlockHash)])) - & P.equals - ? BlockHashRecord - ? HM.fromList [(unsafeChainId 0, BlockHash blockHashHash)] - -hashedAdjacentBlockHashRecordNullPadding :: IO () -hashedAdjacentBlockHashRecordNullPadding = do - -- check that the first chain has a null block hash with a multi-chain record - blockHashHash <- fromText "iu7PoLnyrHgYhjsTYiQeTzLQaxAK6dHA-8xO1huRsXo" - convertBlockHashRecordForMining - (BlockHashRecord (HM.fromList [(unsafeChainId 0, nullBlockHash), (unsafeChainId 1, nullBlockHash)])) - & P.equals - ? BlockHashRecord - ? HM.fromList [(unsafeChainId 0, nullBlockHash), (unsafeChainId 1, BlockHash blockHashHash)] - -hashedAdjacentBlockHashRecordTruncation :: IO () -hashedAdjacentBlockHashRecordTruncation = do - -- check that more than 3 chains gets truncated - blockHashHash <- fromText "tnka1yzuG3ury0o80ccea3EiVWDwOL0pYITwsA2du7Q" - let nullBlockHashRecord = HM.fromList $ [(unsafeChainId cid, nullBlockHash) | cid <- [0..5]] - convertBlockHashRecordForMining (BlockHashRecord nullBlockHashRecord) - & P.equals - ? BlockHashRecord - ? HM.fromList ([(unsafeChainId cid, nullBlockHash) | cid <- [0..1]] ++ [(unsafeChainId 2, blockHashHash)]) - -hashedAdjacentBlockHashRecordBitFlipping :: IO () -hashedAdjacentBlockHashRecordBitFlipping = do - -- these two must be the same as previous test - blockHashHash <- fromText "tnka1yzuG3ury0o80ccea3EiVWDwOL0pYITwsA2du7Q" - let nullBlockHashRecord = HM.fromList $ [(unsafeChainId cid, nullBlockHash) | cid <- [0..5]] - -- check that flipping a bit relative to the previous test in *any* hash in - -- the record changes the output hash - let flippedBitNullBlockHash = - BlockHash $ unsafeMerkleLogHash $ BS.replicate 31 0x00 <> BS.singleton 0x01 - forM_ [0..5] $ \flippedCid -> do - let flippedRecord = - BlockHashRecord (HM.insert (unsafeChainId flippedCid) flippedBitNullBlockHash nullBlockHashRecord) - convertBlockHashRecordForMining flippedRecord - -- TODO: replace when property-matchers has notEquals check - & (\expected actual -> - if expected /= actual - then P.succeed actual - else P.fail "equal, should not be" actual) - ? BlockHashRecord - ? HM.fromList ([(unsafeChainId cid, nullBlockHash) | cid <- [0..1]] ++ [(unsafeChainId 2, blockHashHash)]) + let hashRecord = BlockHashRecord (HM.fromList [(unsafeChainId 0, Parent nullBlockHash)]) + encodeAdjacentsHash (adjacentsHash hashRecord) + & runPutS + & P.equals (runPutS $ encodeBlockHash $ BlockHash @ChainwebMerkleHashAlgorithm blockHashHash) + +-- hashedAdjacentBlockHashRecordNullPadding :: IO () +-- hashedAdjacentBlockHashRecordNullPadding = do +-- -- check that the first chain has a null block hash with a multi-chain record +-- blockHashHash <- fromText "iu7PoLnyrHgYhjsTYiQeTzLQaxAK6dHA-8xO1huRsXo" +-- convertBlockHashRecordForMining +-- (BlockHashRecord (HM.fromList [(unsafeChainId 0, nullBlockHash), (unsafeChainId 1, nullBlockHash)])) +-- & P.equals +-- ? BlockHashRecord +-- ? HM.fromList [(unsafeChainId 0, nullBlockHash), (unsafeChainId 1, BlockHash blockHashHash)] + +-- hashedAdjacentBlockHashRecordTruncation :: IO () +-- hashedAdjacentBlockHashRecordTruncation = do +-- -- check that more than 3 chains gets truncated +-- blockHashHash <- fromText "tnka1yzuG3ury0o80ccea3EiVWDwOL0pYITwsA2du7Q" +-- let nullBlockHashRecord = HM.fromList $ [(unsafeChainId cid, nullBlockHash) | cid <- [0..5]] +-- convertBlockHashRecordForMining (BlockHashRecord nullBlockHashRecord) +-- & P.equals +-- ? BlockHashRecord +-- ? HM.fromList ([(unsafeChainId cid, nullBlockHash) | cid <- [0..1]] ++ [(unsafeChainId 2, blockHashHash)]) + +-- hashedAdjacentBlockHashRecordBitFlipping :: IO () +-- hashedAdjacentBlockHashRecordBitFlipping = do +-- -- these two must be the same as previous test +-- blockHashHash <- fromText "tnka1yzuG3ury0o80ccea3EiVWDwOL0pYITwsA2du7Q" +-- let nullBlockHashRecord = HM.fromList $ [(unsafeChainId cid, nullBlockHash) | cid <- [0..5]] +-- -- check that flipping a bit relative to the previous test in *any* hash in +-- -- the record changes the output hash +-- let flippedBitNullBlockHash = +-- BlockHash $ unsafeMerkleLogHash $ BS.replicate 31 0x00 <> BS.singleton 0x01 +-- forM_ [0..5] $ \flippedCid -> do +-- let flippedRecord = +-- BlockHashRecord (HM.insert (unsafeChainId flippedCid) flippedBitNullBlockHash nullBlockHashRecord) +-- convertBlockHashRecordForMining flippedRecord +-- -- TODO: replace when property-matchers has notEquals check +-- & (\expected actual -> +-- if expected /= actual +-- then P.succeed actual +-- else P.fail "equal, should not be" actual) +-- ? BlockHashRecord +-- ? HM.fromList ([(unsafeChainId cid, nullBlockHash) | cid <- [0..1]] ++ [(unsafeChainId 2, blockHashHash)]) diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index 7ba462b2f6..129959459e 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -67,8 +67,9 @@ import Chainweb.RestAPI.NodeInfo import Chainweb.SPV import Chainweb.SPV.EventProof import Chainweb.SPV.PayloadProof -import Chainweb.Test.Orphans.Internal (EventPactValue(..), ProofPactEvent(..), arbitraryBlockHeaderVersion, arbitraryBlockHeaderVersionHeight, arbitraryBlockHashRecordVersionHeightChain, arbitraryBlockHeaderVersionHeightChain) -import Chainweb.Test.SPV.EventProof hiding (tests) +-- import Chainweb.Test.Orphans.Internal (EventPactValue(..), ProofPactEvent(..), arbitraryBlockHeaderVersion, arbitraryBlockHeaderVersionHeight, arbitraryBlockHashRecordVersionHeightChain, arbitraryBlockHeaderVersionHeightChain) +import Chainweb.Test.Orphans.Internal (arbitraryBlockHeaderVersion, arbitraryBlockHeaderVersionHeight, arbitraryBlockHashRecordVersionHeightChain, arbitraryBlockHeaderVersionHeightChain) +-- import Chainweb.Test.SPV.EventProof hiding (tests) import Chainweb.Test.Utils import Chainweb.Time import Chainweb.Utils @@ -175,16 +176,17 @@ encodeDecodeTests = testGroup "Encode-Decode roundtrips" $ prop_encodeDecode decodeBytes encodeBytes , testProperty "String" $ prop_encodeDecode decodeString encodeString - , testProperty "ModuleName" - $ prop_encodeDecode decodeModuleName encodeModuleName + -- , testProperty "ModuleName" + -- $ prop_encodeDecode decodeModuleName encodeModuleName -- FIXME limit to empty spec and info -- TODO PP -- , testProperty "ModRef" -- $ prop_encodeDecode (PactEventModRef <$> decodeModRef) (encodeModRef . _getPactEventModRef) , testProperty "Integer" $ prop_encodeDecode decodeInteger encodeInteger - , testProperty "Decimal" - $ prop_encodeDecode (PactEventDecimal <$> decodeDecimal) (encodeDecimal . _getPactEventDecimal) + -- TODO PP + -- , testProperty "Decimal" + -- $ prop_encodeDecode (PactEventDecimal <$> decodeDecimal) (encodeDecimal . _getPactEventDecimal) -- TODO PP -- , testProperty "Hash" -- $ prop_encodeDecode decodeHash encodeHash @@ -198,12 +200,12 @@ encodeDecodeTests = testGroup "Encode-Decode roundtrips" -- , testProperty "PactEvent" -- $ prop_encodeDecode (ProofPactEvent <$> decodePactEvent) (encodePactEvent . getProofPactEvent) -- FIXME "pending bytes" - , testProperty "PactParam" - $ prop_encodeDecode (EventPactValue <$> decodeParam) (encodeParam . getEventPactValue) + -- , testProperty "PactParam" + -- $ prop_encodeDecode (EventPactValue <$> decodeParam) (encodeParam . getEventPactValue) -- FIXME "pending bytes" - , testProperty "OutputEvents" - $ prop_encodeDecode decodeOutputEvents encodeOutputEvents + -- , testProperty "OutputEvents" + -- $ prop_encodeDecode decodeOutputEvents encodeOutputEvents ] -- Mining @@ -233,12 +235,12 @@ pactJsonTestCases :: (forall a . Arbitrary a => Show a => J.Encode a => FromJSON a => Eq a => a -> Property) -> [TestTree] pactJsonTestCases f = - [ testGroup "SPV" - [ testProperty "SpvRequest" $ f @SpvRequest - ] + [ testGroup "SPV" [] + -- [ testProperty "SpvRequest" $ f @SpvRequest + -- ] , testGroup "Miner" [ testProperty "MinerId" $ f @MinerId - , testProperty "Miner" $ f @Miner + -- , testProperty "Miner" $ f @Miner ] , testGroup "Mempool" -- [ testProperty "GasLimit" $ f @GasLimit @@ -312,12 +314,12 @@ jsonTestCases f = , testGroup "SPV" [ testProperty "SpvAlgorithm" $ f @SpvAlgorithm , testProperty "SpvSubjectType" $ f @SpvAlgorithm - , testProperty "SpvSubjectIdentifier" $ f @SpvSubjectIdentifier - , testProperty "Spv2Request" $ f @Spv2Request - -- , testProperty "TransactionProof" $ f @(TransactionProof ChainwebMerkleHashAlgorithm) - , testProperty "TransactionOutputProof" $ f @(TransactionOutputProof ChainwebMerkleHashAlgorithm) - , testProperty "PayloadProof" $ f @(PayloadProof ChainwebMerkleHashAlgorithm) - , testProperty "SomePayloadProof" $ f @(SomePayloadProof) + -- , testProperty "SpvSubjectIdentifier" $ f @SpvSubjectIdentifier + -- , testProperty "Spv2Request" $ f @Spv2Request + -- -- , testProperty "TransactionProof" $ f @(TransactionProof ChainwebMerkleHashAlgorithm) + -- , testProperty "TransactionOutputProof" $ f @(TransactionOutputProof ChainwebMerkleHashAlgorithm) + -- , testProperty "PayloadProof" $ f @(PayloadProof ChainwebMerkleHashAlgorithm) + -- , testProperty "SomePayloadProof" $ f @(SomePayloadProof) ] , testGroup "Miner" @@ -351,7 +353,7 @@ jsonTestCases f = , testProperty "OutputTree" $ f @OutputTree , testProperty "PayloadData" $ f @PayloadData , testProperty "PayloadWithOutputs" $ f @PayloadWithOutputs - , testProperty "PayloadOutputProof" $ f @(PayloadProof ChainwebMerkleHashAlgorithm) + -- , testProperty "PayloadOutputProof" $ f @(PayloadProof ChainwebMerkleHashAlgorithm) , testProperty "BlockEventsHash" $ f @(BlockEventsHash_ ChainwebMerkleHashAlgorithm) ] diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index 22e58feac9..b3b7defbb1 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -43,13 +43,13 @@ import Chainweb.Test.Utils import qualified Chainweb.Test.BlockHeader.Genesis (tests) import qualified Chainweb.Test.BlockHeader.Validation (tests) import qualified Chainweb.Test.BlockHeaderDB (tests) -import qualified Chainweb.Test.BlockHeaderDB.PruneForks (tests) +-- import qualified Chainweb.Test.BlockHeaderDB.PruneForks (tests) import qualified Chainweb.Test.Chainweb.Utils.Paging (properties) import qualified Chainweb.Test.Cut (properties) import qualified Chainweb.Test.CutDB (tests) import qualified Chainweb.Test.Difficulty (properties) import qualified Chainweb.Test.HostAddress (properties) -import qualified Chainweb.Test.Mempool.Consensus (tests) +-- import qualified Chainweb.Test.Mempool.Consensus (tests) import qualified Chainweb.Test.Mempool.InMem (tests) import qualified Chainweb.Test.Mempool.RestAPI (tests) import qualified Chainweb.Test.Mempool.Sync (tests) @@ -60,13 +60,13 @@ import qualified Chainweb.Test.Pact.CheckpointerTest import qualified Chainweb.Test.Pact.HyperlanePluginTests import qualified Chainweb.Test.Pact.PactServiceTest import qualified Chainweb.Test.Pact.RemotePactTest -import qualified Chainweb.Test.Pact.SPVTest +-- import qualified Chainweb.Test.Pact.SPVTest import qualified Chainweb.Test.Pact.TransactionExecTest import qualified Chainweb.Test.Pact.TransactionTests import qualified Chainweb.Test.RestAPI (tests) import qualified Chainweb.Test.Roundtrips (tests) -import qualified Chainweb.Test.SPV (tests) -import qualified Chainweb.Test.SPV.EventProof (properties) +-- import qualified Chainweb.Test.SPV (tests) +-- import qualified Chainweb.Test.SPV.EventProof (properties) import qualified Chainweb.Test.Sync.WebBlockHeaderStore (properties) import qualified Chainweb.Test.TreeDB (properties) import qualified Chainweb.Test.TreeDB.RemoteDB @@ -82,52 +82,23 @@ setTestLogLevel l = setEnv "CHAINWEB_TEST_LOG_LEVEL" (show l) main :: IO () main = do - registerVersion RecapDevelopment - registerVersion Development withTempRocksDb "chainweb-tests" $ \rdb -> runResourceT $ do - (h0, db) <- withToyDB rdb toyChainId + -- (h0, db) <- withToyDB rdb toyChainId liftIO $ defaultMainWithIngredients (consoleAndJsonReporter : defaultIngredients) $ adjustOption adj $ testGroup "Chainweb Tests" - $ pactTestSuite rdb - : mempoolTestSuite db h0 - : nodeTestSuite rdb - : suite rdb -- Coinbase Vuln Fix Tests are broken, waiting for Jose loadScript + $ suite rdb + -- : mempoolTestSuite db h0 where adj NoTimeout = Timeout (1_000_000 * 60 * 10) "10m" adj x = x -mempoolTestSuite :: BlockHeaderDb -> BlockHeader -> TestTree -mempoolTestSuite db genesisBlock = testGroup "Mempool Consensus Tests" - [withVersion toyVersion $ Chainweb.Test.Mempool.Consensus.tests db genesisBlock] - -pactTestSuite :: RocksDb -> TestTree -pactTestSuite rdb = testGroup "Chainweb-Pact Tests" - [ Chainweb.Test.Pact4.PactExec.tests -- OK: but need fixes (old broken tests) - , Chainweb.Test.Pact4.DbCacheTest.tests - , Chainweb.Test.Pact4.Checkpointer.tests - - , Chainweb.Test.Pact4.PactMultiChainTest.tests rdb -- BROKEN few tests - - , Chainweb.Test.Pact4.PactSingleChainTest.tests rdb - - , Chainweb.Test.Pact4.VerifierPluginTest.tests rdb -- BROKEN - - , Chainweb.Test.Pact4.PactReplay.tests rdb - , Chainweb.Test.Pact4.ModuleCacheOnRestart.tests rdb - , Chainweb.Test.Pact4.TTL.tests rdb - , Chainweb.Test.Pact4.RewardsTest.tests - , Chainweb.Test.Pact4.NoCoinbase.tests - , Chainweb.Test.Pact4.GrandHash.tests - ] - -nodeTestSuite :: RocksDb -> TestTree -nodeTestSuite rdb = independentSequentialTestGroup "Tests starting nodes" - [ Chainweb.Test.Pact4.RemotePactTest.tests rdb -- BROKEN - ] +-- mempoolTestSuite :: BlockHeaderDb -> BlockHeader -> TestTree +-- mempoolTestSuite db genesisBlock = testGroup "Mempool Consensus Tests" +-- [withVersion toyVersion $ Chainweb.Test.Mempool.Consensus.tests db genesisBlock] suite :: RocksDb -> [TestTree] suite rdb = @@ -136,26 +107,26 @@ suite rdb = , testGroup "BlockHeaderDb" [ Chainweb.Test.BlockHeaderDB.tests rdb , Chainweb.Test.TreeDB.RemoteDB.tests - , Chainweb.Test.BlockHeaderDB.PruneForks.tests + -- , Chainweb.Test.BlockHeaderDB.PruneForks.tests , testProperties "Chainweb.Test.TreeDB" Chainweb.Test.TreeDB.properties ] - , Chainweb.Test.Pact4.SQLite.tests , Chainweb.Test.CutDB.tests rdb - , Chainweb.Test.Pact4.TransactionTests.tests -- TODO: fix, awaiting for Jose to add loadScript function , Chainweb.Test.Pact.CheckpointerTest.tests , Chainweb.Test.Pact.TransactionExecTest.tests rdb , Chainweb.Test.Pact.PactServiceTest.tests rdb - , Chainweb.Test.Pact.SPVTest.tests rdb + -- TODO: PP + -- , Chainweb.Test.Pact.SPVTest.tests rdb , Chainweb.Test.Pact.RemotePactTest.tests rdb , Chainweb.Test.Pact.HyperlanePluginTests.tests rdb , Chainweb.Test.Pact.TransactionTests.tests , Chainweb.Test.Roundtrips.tests , Chainweb.Test.RestAPI.tests rdb - , testGroup "SPV" - [ Chainweb.Test.SPV.tests rdb - , Chainweb.Test.Pact4.SPV.tests rdb - , Chainweb.Test.SPV.EventProof.properties - ] + -- TODO: PP + -- , testGroup "SPV" + -- [ Chainweb.Test.SPV.tests rdb + -- , Chainweb.Test.Pact4.SPV.tests rdb + -- , Chainweb.Test.SPV.EventProof.properties + -- ] , Chainweb.Test.Mempool.InMem.tests , Chainweb.Test.Mempool.Sync.tests , Chainweb.Test.Mempool.RestAPI.tests From 1d9060e3448a5dffc6b9d7549d99ae11144ab475 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 29 May 2025 11:43:36 -0400 Subject: [PATCH 195/378] small test fixes Change-Id: Id0000000e62e4d92520fbb52730df5ff49dbace1 --- test/lib/Chainweb/Test/TestVersions.hs | 1 + test/lib/Chainweb/Test/Utils.hs | 2 +- test/unit/Chainweb/Test/Mempool.hs | 21 ++- test/unit/Chainweb/Test/Mempool/InMem.hs | 2 +- test/unit/Chainweb/Test/Mempool/RestAPI.hs | 8 +- .../Test/Pact/HyperlanePluginTests.hs | 128 +++++++++--------- .../unit/Chainweb/Test/Pact/RemotePactTest.hs | 22 +-- test/unit/Chainweb/Test/Roundtrips.hs | 1 + test/unit/Chainweb/Test/TreeDB.hs | 2 +- 9 files changed, 99 insertions(+), 88 deletions(-) diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index 14bd63c61a..ad09d01d77 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -114,6 +114,7 @@ barebonesTestVersion g = { _quirkGasFees = HM.empty <$ cids } & versionUpgrades .~ (HM.empty <$ cids) + & versionPayloadProviderTypes .~ (MinimalProvider <$ cids) where gs = Bottom (minBound, g) cids = ChainMap $ HS.toMap $ graphChainIds $ snd $ ruleHead gs diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index c9e0145a7e..5e591b40bc 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -671,7 +671,7 @@ clientEnvWithChainwebTestServer => HasVersion => ShouldValidateSpec -> Bool - -> ChainwebServerDbs + -> ChainwebServerDbs t -> ResourceT IO (TestClientEnv t) clientEnvWithChainwebTestServer shouldValidateSpec tls dbs = do -- FIXME: Hashes API got removed from the P2P API. We use an application that diff --git a/test/unit/Chainweb/Test/Mempool.hs b/test/unit/Chainweb/Test/Mempool.hs index 71df03deaa..18166d8def 100644 --- a/test/unit/Chainweb/Test/Mempool.hs +++ b/test/unit/Chainweb/Test/Mempool.hs @@ -48,12 +48,18 @@ import Test.Tasty.QuickCheck hiding ((.&.)) -- internal modules import Pact.Parse (ParsedDecimal(..)) +import Pact.Core.Gas.Types import Chainweb.BlockHash import Chainweb.Mempool.Mempool import Chainweb.Test.Utils import qualified Chainweb.Time as Time import Chainweb.Utils (T2(..)) +import Chainweb.PayloadProvider +import Chainweb.MinerReward +import Chainweb.Parent +import Chainweb.BlockCreationTime +import Control.Lens (from, view) ------------------------------------------------------------------------------ -- | Several operations (reintroduce, validate, confirm) can only be performed @@ -122,7 +128,7 @@ instance Arbitrary MockTx where arbitrary = MockTx <$> chooseAny <*> arbitraryGasPrice - <*> pure (mockBlockGasLimit `div` 50_000) + <*> pure (GasLimit $ Gas $ view (_GasLimit . _Gas) mockBlockGasLimit `div` 50_000) <*> pure emptyMeta where emptyMeta = TransactionMetadata zero Time.maxTime @@ -187,7 +193,7 @@ propOverlarge (txs, overlarge0) _ mempool = runExceptT $ do insert v = mempoolInsert mempool CheckedInsert $ V.fromList v lookup = mempoolLookup mempool . V.fromList . map hash overlarge = setOverlarge overlarge0 - setOverlarge = map (\x -> x { mockGasLimit = mockBlockGasLimit + 100 }) + setOverlarge = map (\x -> x { mockGasLimit = GasLimit $ Gas $ view (_GasLimit . _Gas) mockBlockGasLimit + 100 }) propBadlistPreblock :: ([MockTx], [MockTx]) @@ -202,7 +208,8 @@ propBadlistPreblock (txs, badTxs) _ mempool = runExceptT $ do liftIO (lookup badTxs) >>= V.mapM_ lookupIsPending -- once we call mempoolGetBlock, the bad txs should be badlisted - liftIO $ void $ mempoolGetBlock mempool mockBlockFill preblockCheck (EvaluationCtx 1 nullBlockHash) + liftIO $ void $ mempoolGetBlock mempool mockBlockFill preblockCheck + (EvaluationCtx (Parent $ BlockCreationTime Time.epoch) (Parent nullBlockHash) (Parent 1) (MinerReward 0) ()) liftIO (lookup txs) >>= V.mapM_ lookupIsPending liftIO (lookup badTxs) >>= V.mapM_ lookupIsMissing liftIO $ insert badTxs @@ -211,7 +218,7 @@ propBadlistPreblock (txs, badTxs) _ mempool = runExceptT $ do where badHashes = HashSet.fromList $ map hash badTxs - preblockCheck _ _ ts = return $ + preblockCheck _ ts = return $ V.map (\tx -> if hash tx `HashSet.member` badHashes then Left InsertErrorBadlisted else Right tx) ts @@ -246,7 +253,8 @@ propAddToBadList tx _ mempool = runExceptT $ do insert v = mempoolInsert mempool CheckedInsert $ V.fromList v lookup = mempoolLookup mempool . V.fromList . map hash getBlock = liftIO - $ V.toList <$> mempoolGetBlock mempool mockBlockFill noopMempoolPreBlockCheck 1 nullBlockHash + $ V.toList <$> mempoolGetBlock mempool mockBlockFill noopMempoolPreBlockCheck + (EvaluationCtx (Parent $ BlockCreationTime Time.epoch) (Parent nullBlockHash) (Parent 1) (MinerReward 0) ()) -- TODO Does this need to be updated? propPreInsert @@ -303,7 +311,8 @@ propTrivial txs _ mempool = runExceptT $ do insert v = mempoolInsert mempool CheckedInsert $ V.fromList v lookup = mempoolLookup mempool . V.fromList . map hash - getBlock = mempoolGetBlock mempool mockBlockFill noopMempoolPreBlockCheck 0 nullBlockHash + getBlock = mempoolGetBlock mempool mockBlockFill noopMempoolPreBlockCheck + (EvaluationCtx (Parent $ BlockCreationTime Time.epoch) (Parent nullBlockHash) (Parent 0) (MinerReward 0) ()) onFees x = (Down (mockGasPrice x)) diff --git a/test/unit/Chainweb/Test/Mempool/InMem.hs b/test/unit/Chainweb/Test/Mempool/InMem.hs index f1ebc2ac57..698754614f 100644 --- a/test/unit/Chainweb/Test/Mempool/InMem.hs +++ b/test/unit/Chainweb/Test/Mempool/InMem.hs @@ -23,7 +23,7 @@ tests = testGroup "Chainweb.Test.Mempool" wf :: (InsertCheck -> MempoolBackend MockTx -> IO a) -> IO a wf f = do mv <- newMVar (pure . V.map Right) - let cfg = InMemConfig txcfg mockBlockGasLimit 0 2048 Right (checkMv mv) (1024 * 10) + let cfg = InMemConfig txcfg mockBlockGasLimit (GasPrice 0) 2048 Right (checkMv mv) (1024 * 10) mp <- InMem.startInMemoryMempoolTest cfg f mv mp diff --git a/test/unit/Chainweb/Test/Mempool/RestAPI.hs b/test/unit/Chainweb/Test/Mempool/RestAPI.hs index 3264058036..158c2b3bac 100644 --- a/test/unit/Chainweb/Test/Mempool/RestAPI.hs +++ b/test/unit/Chainweb/Test/Mempool/RestAPI.hs @@ -54,7 +54,7 @@ newTestServer :: IO TestServer newTestServer = withVersion version $ mask_ $ do let chain = someChainId checkMv <- newMVar (pure . V.map Right) - let inMemCfg = InMemConfig txcfg mockBlockGasLimit 0 2048 Right (checkMvFunc checkMv) (1024 * 10) + let inMemCfg = InMemConfig txcfg mockBlockGasLimit (GasPrice 0) 2048 Right (checkMvFunc checkMv) (1024 * 10) inmemMv <- newEmptyMVar envMv <- newEmptyMVar tid <- forkIOWithUnmask $ \u -> server chain inMemCfg inmemMv envMv u @@ -83,7 +83,7 @@ newTestServer = withVersion version $ mask_ $ do host = "127.0.0.1" mkApp :: ChainId -> MempoolBackend MockTx -> Application - mkApp chain mp = withVersion version $ chainwebApplication conf (serverMempools [(chain, mp)]) + mkApp chain mp = withVersion version $ chainwebApplication conf (serverMempools (onChain chain mp)) conf = defaultChainwebConfiguration version @@ -106,8 +106,8 @@ newPool = Pool.newPool $ Pool.defaultPoolConfig ------------------------------------------------------------------------------ serverMempools - :: [(ChainId, MempoolBackend t)] - -> ChainwebServerDbs t RocksDbTable {- ununsed -} + :: ChainMap (MempoolBackend t) + -> ChainwebServerDbs t serverMempools mempools = emptyChainwebServerDbs { _chainwebServerMempools = mempools } diff --git a/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs b/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs index 22648aa4db..034c323b8b 100644 --- a/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs +++ b/test/unit/Chainweb/Test/Pact/HyperlanePluginTests.hs @@ -61,19 +61,19 @@ tests baseRdb = testGroup "Pact5 HyperlanePluginTests" ] v :: ChainwebVersion -v = pact5InstantCpmTestVersion petersenChainGraph +v = instantCpmTestVersion petersenChainGraph chain0 :: ChainId chain0 = unsafeChainId 0 hyperlaneValidatorAnnouncementTest :: RocksDb -> Step -> IO () -hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneValidatorAnnouncementTest baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do let pluginName = "hyperlane_v3_announcement" step "deploy contract" - deploy <- buildTextCmd v + deploy <- buildTextCmd $ set cbGasLimit (GasLimit (Gas 100000)) $ set cbRPC (mkExec' $ mconcat [ "(namespace 'free)" @@ -83,16 +83,16 @@ hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do , "(defun x () (with-capability (K \"storagelocation\" \"0x6c414e7a15088023e28af44ad0e1d593671e4b15\" \"kb-mailbox\") 1)))" ]) $ defaultCmd chain0 - send fx v chain0 [deploy] + send fx chain0 [deploy] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey deploy] + poll fx chain0 [cmdToRequestKey deploy] >>= P.list [P.match _Just successfulTx] step "use successfully" let cap = CapToken (QualifiedName "K" (ModuleName "m" (Just (NamespaceName "free"))) ) [PString "storagelocation", PString "0x6c414e7a15088023e28af44ad0e1d593671e4b15", PString "kb-mailbox"] - usePlugin <- buildTextCmd v + usePlugin <- buildTextCmd $ set cbRPC (mkExec' "(free.m.x)") $ set cbVerifiers [Verifier @@ -108,9 +108,9 @@ hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do [SigCapability cap]] $ set cbGasLimit (GasLimit (Gas 100000)) $ defaultCmd chain0 - send fx v chain0 [usePlugin] + send fx chain0 [usePlugin] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey usePlugin] + poll fx chain0 [cmdToRequestKey usePlugin] >>= P.list [P.match _Just ? P.checkAll [ successfulTx @@ -119,7 +119,7 @@ hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do ] step "use with bad signature" - useBadSignature <- buildTextCmd v + useBadSignature <- buildTextCmd $ set cbRPC (mkExec' "(free.m.x)") $ set cbVerifiers [Verifier @@ -135,9 +135,9 @@ hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do [SigCapability cap]] $ set cbGasLimit (GasLimit (Gas 100000)) $ defaultCmd chain0 - send fx v chain0 [useBadSignature] + send fx chain0 [useBadSignature] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey useBadSignature] + poll fx chain0 [cmdToRequestKey useBadSignature] >>= P.list [ P.match _Just ? P.checkAll [ P.fun _crResult ? P.match _PactResultErr ? P.checkAll @@ -149,7 +149,7 @@ hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do ] step "deploy with different signer" - deployDifferentSigner <- buildTextCmd v + deployDifferentSigner <- buildTextCmd $ set cbGasLimit (GasLimit (Gas 100000)) $ set cbRPC (mkExec' $ mconcat [ "(namespace 'free)" @@ -159,9 +159,9 @@ hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do , "(defun x () (with-capability (K \"storagelocation\" \"0x5c414e7a15088023e28af44ad0e1d593671e4b15\" \"kb-mailbox\") 1)))" ]) $ defaultCmd chain0 - send fx v chain0 [deployDifferentSigner] + send fx chain0 [deployDifferentSigner] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey deployDifferentSigner] + poll fx chain0 [cmdToRequestKey deployDifferentSigner] >>= P.list [P.match _Just successfulTx] let capWrongSigner = @@ -170,7 +170,7 @@ hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do -- bad signer (same as from the previous test but the different first symbol) step "use with wrong signer" - useWrongSigner <- buildTextCmd v + useWrongSigner <- buildTextCmd $ set cbRPC (mkExec' "(free.m.x)") $ set cbVerifiers [Verifier @@ -186,9 +186,9 @@ hyperlaneValidatorAnnouncementTest baseRdb step = runResourceT $ do [SigCapability capWrongSigner]] $ set cbGasLimit (GasLimit (Gas 100000)) $ defaultCmd chain0 - send fx v chain0 [useWrongSigner] + send fx chain0 [useWrongSigner] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey useWrongSigner] + poll fx chain0 [cmdToRequestKey useWrongSigner] >>= P.list [ P.match _Just ? P.checkAll [ P.fun _crResult ? P.match _PactResultErr ? P.checkAll @@ -255,7 +255,7 @@ validSignature :: T.Text validSignature = "0xfabe80dd5bf4440e5e7fbc3cdf12325df9c00beb1281c5ddf12e77177046790c49f531ccebb29ba9c9664a581ed1870873850e0cf0c231b779e21f48a1d0dcea1b" -- | Deploys a contract with a valid signer -deployContractWith :: Fixture -> [T.Text] -> Integer -> IO () +deployContractWith :: HasVersion => Fixture -> [T.Text] -> Integer -> IO () deployContractWith fx signers threshold = do let deployCode = [ "(namespace 'free)" @@ -303,18 +303,18 @@ deployContractWith fx signers threshold = do , ")" , " \"succeeded\")))" ] - deployCmd <- buildTextCmd v + deployCmd <- buildTextCmd $ set cbRPC (mkExec' $ mconcat deployCode) $ set cbGasLimit (GasLimit $ Gas 70000) $ defaultCmd chain0 - send fx v chain0 [deployCmd] + send fx chain0 [deployCmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey deployCmd] >>= + poll fx chain0 [cmdToRequestKey deployCmd] >>= P.list [P.match _Just ? successfulTx] -- | Calls '(free.m.x)' from 'deployContractWithValidSigner' -mkMerkleMetadataCallWithGas :: GasLimit -> B.ByteString -> [T.Text] -> [T.Text] -> Integer -> IO (Command T.Text) -mkMerkleMetadataCallWithGas gas merkleProof signatures signersText threshold = buildTextCmd v +mkMerkleMetadataCallWithGas :: HasVersion => GasLimit -> B.ByteString -> [T.Text] -> [T.Text] -> Integer -> IO (Command T.Text) +mkMerkleMetadataCallWithGas gas merkleProof signatures signersText threshold = buildTextCmd $ set cbGasLimit gas $ set cbVerifiers [Verifier @@ -345,15 +345,15 @@ mkMerkleMetadataCallWithGas gas merkleProof signatures signersText threshold = b $ CapToken (QualifiedName "K" (ModuleName "m" (Just (NamespaceName "free"))) ) [messageId, message, signers, PInteger threshold] -mkMerkleMetadataCall :: B.ByteString -> [T.Text] -> [T.Text] -> Integer -> IO (Command T.Text) +mkMerkleMetadataCall :: HasVersion => B.ByteString -> [T.Text] -> [T.Text] -> Integer -> IO (Command T.Text) mkMerkleMetadataCall = mkMerkleMetadataCallWithGas (GasLimit $ Gas 20000) -checkVerifierNotInTx :: Fixture -> T.Text -> IO () +checkVerifierNotInTx :: HasVersion => Fixture -> T.Text -> IO () checkVerifierNotInTx fx pluginName = do - cmd <- buildTextCmd v $ set cbRPC (mkExec' "(free.m.x)") $ defaultCmd chain0 - send fx v chain0 [cmd] + cmd <- buildTextCmd $ set cbRPC (mkExec' "(free.m.x)") $ defaultCmd chain0 + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [ P.match _Just ? P.fun _crResult ? P.match _PactResultErr @@ -365,8 +365,8 @@ checkVerifierNotInTx fx pluginName = do ] hyperlaneVerifySuccess :: RocksDb -> Step -> IO () -hyperlaneVerifySuccess baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneVerifySuccess baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do let threshold = 1 step "deploy contract" @@ -374,9 +374,9 @@ hyperlaneVerifySuccess baseRdb step = runResourceT $ do checkVerifierNotInTx fx "hyperlane_v3_message" step "use verifier" cmd <- mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [validSignature] [validSigner] threshold - send fx v chain0 [cmd] + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [P.match _Just ? P.checkAll [ successfulTx @@ -386,8 +386,8 @@ hyperlaneVerifySuccess baseRdb step = runResourceT $ do hyperlaneVerifyMoreValidatorsSuccess :: RocksDb -> Step -> IO () -hyperlaneVerifyMoreValidatorsSuccess baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneVerifyMoreValidatorsSuccess baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do step "deploy contract" let threshold = 1 @@ -396,9 +396,9 @@ hyperlaneVerifyMoreValidatorsSuccess baseRdb step = runResourceT $ do checkVerifierNotInTx fx "hyperlane_v3_message" step "use verifier" cmd <- mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [validSignature] signers threshold - send fx v chain0 [cmd] + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [ P.match _Just ? P.checkAll [ successfulTx , P.fun _crGas ? P.equals (Gas 16398) @@ -406,8 +406,8 @@ hyperlaneVerifyMoreValidatorsSuccess baseRdb step = runResourceT $ do ] hyperlaneVerifyThresholdZeroError :: RocksDb -> Step -> IO () -hyperlaneVerifyThresholdZeroError baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneVerifyThresholdZeroError baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do step "deploy contract" let code = mconcat @@ -433,20 +433,20 @@ hyperlaneVerifyThresholdZeroError baseRdb step = runResourceT $ do , ")" , " \"succeeded\")))" ] - deployCmd <- buildTextCmd v + deployCmd <- buildTextCmd $ set cbRPC (mkExec' code) $ set cbGasLimit (GasLimit $ Gas 70000) $ defaultCmd chain0 - send fx v chain0 [deployCmd] + send fx chain0 [deployCmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey deployCmd] >>= P.list + poll fx chain0 [cmdToRequestKey deployCmd] >>= P.list [ P.match _Just ? successfulTx ] checkVerifierNotInTx fx "hyperlane_v3_message" step "use verifier" cmd <- mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [] [] 0 - send fx v chain0 [cmd] + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [ P.match _Just ? P.checkAll [ P.fun _crResult @@ -460,8 +460,8 @@ hyperlaneVerifyThresholdZeroError baseRdb step = runResourceT $ do ] hyperlaneVerifyWrongSignersFailure :: RocksDb -> Step -> IO () -hyperlaneVerifyWrongSignersFailure baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneVerifyWrongSignersFailure baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do step "deploy contract" let threshold = 1 @@ -469,9 +469,9 @@ hyperlaneVerifyWrongSignersFailure baseRdb step = runResourceT $ do checkVerifierNotInTx fx "hyperlane_v3_message" step "use verifier" cmd <- mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [validSignature] ["wrongSigner"] threshold - send fx v chain0 [cmd] + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [ P.match _Just ? P.checkAll [ P.fun _crResult @@ -485,8 +485,8 @@ hyperlaneVerifyWrongSignersFailure baseRdb step = runResourceT $ do ] hyperlaneVerifyNotEnoughRecoveredSignaturesFailure :: RocksDb -> Step -> IO () -hyperlaneVerifyNotEnoughRecoveredSignaturesFailure baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneVerifyNotEnoughRecoveredSignaturesFailure baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do step "deploy contract" let threshold = 1 @@ -496,9 +496,9 @@ hyperlaneVerifyNotEnoughRecoveredSignaturesFailure baseRdb step = runResourceT $ step "use verifier" cmd <- mkMerkleMetadataCall hyperlaneMerkleTreeCorrectProof [] ["wrongSigner"] threshold - send fx v chain0 [cmd] + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [ P.match _Just ? P.checkAll [ P.fun _crResult @@ -512,8 +512,8 @@ hyperlaneVerifyNotEnoughRecoveredSignaturesFailure baseRdb step = runResourceT $ ] hyperlaneVerifyNotEnoughCapabilitySignaturesFailure :: RocksDb -> Step -> IO () -hyperlaneVerifyNotEnoughCapabilitySignaturesFailure baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneVerifyNotEnoughCapabilitySignaturesFailure baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do step "deploy contract" let threshold = 2 @@ -526,9 +526,9 @@ hyperlaneVerifyNotEnoughCapabilitySignaturesFailure baseRdb step = runResourceT [validSignature, validSignature] [validSigner] threshold - send fx v chain0 [cmd] + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [ P.match _Just ? P.checkAll [ P.fun _crResult @@ -542,8 +542,8 @@ hyperlaneVerifyNotEnoughCapabilitySignaturesFailure baseRdb step = runResourceT ] hyperlaneVerifyMerkleIncorrectProofFailure :: RocksDb -> Step -> IO () -hyperlaneVerifyMerkleIncorrectProofFailure baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneVerifyMerkleIncorrectProofFailure baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do step "deploy contract" let threshold = 1 @@ -555,9 +555,9 @@ hyperlaneVerifyMerkleIncorrectProofFailure baseRdb step = runResourceT $ do [validSignature] [validSigner] threshold - send fx v chain0 [cmd] + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [ P.match _Just ? P.checkAll [ P.fun _crResult @@ -573,8 +573,8 @@ hyperlaneVerifyMerkleIncorrectProofFailure baseRdb step = runResourceT $ do -- | We pass 2 signatures, 1st one matches to the correct validator, -- but there is no second valid validator for the 2nd signature, and the verification fails. hyperlaneVerifyFailureNotEnoughSignaturesToPassThreshold :: RocksDb -> Step -> IO () -hyperlaneVerifyFailureNotEnoughSignaturesToPassThreshold baseRdb step = runResourceT $ do - fx <- mkFixture v baseRdb +hyperlaneVerifyFailureNotEnoughSignaturesToPassThreshold baseRdb step = withVersion v $ runResourceT $ do + fx <- mkFixture baseRdb liftIO $ do step "deploy contract" let threshold = 2 @@ -587,9 +587,9 @@ hyperlaneVerifyFailureNotEnoughSignaturesToPassThreshold baseRdb step = runResou (GasLimit $ Gas 40000) hyperlaneMerkleTreeCorrectProof [validSignature, validSignature] signers threshold - send fx v chain0 [cmd] + send fx chain0 [cmd] advanceAllChains_ fx - poll fx v chain0 [cmdToRequestKey cmd] >>= P.list + poll fx chain0 [cmdToRequestKey cmd] >>= P.list [ P.match _Just ? P.checkAll [ P.fun _crResult diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index 816eb238b9..10829b1884 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -30,17 +30,17 @@ module Chainweb.Test.Pact.RemotePactTest ( tests - -- , mkFixture - -- , Fixture(..) - -- , HasFixture(..) - -- , poll - -- , pollWithDepth - -- , PollException(..) - -- , ClientException(..) - -- , _FailureResponse - -- , send - -- , local - -- , textContains + , mkFixture + , Fixture(..) + , HasFixture(..) + , poll + , pollWithDepth + , PollException(..) + , ClientException(..) + , _FailureResponse + , send + , local + , textContains ) where import Control.Concurrent.Async hiding (poll) diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index 129959459e..e98bb01067 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -87,6 +87,7 @@ import Utils.Logging import Control.Lens (view) import Chainweb.Version.Mainnet (mainnet) import Pact.Core.Gas +import Chainweb.Ranked -- -------------------------------------------------------------------------- -- -- Roundrip Tests diff --git a/test/unit/Chainweb/Test/TreeDB.hs b/test/unit/Chainweb/Test/TreeDB.hs index e6581a5cbd..f2bb1d86e7 100644 --- a/test/unit/Chainweb/Test/TreeDB.hs +++ b/test/unit/Chainweb/Test/TreeDB.hs @@ -376,7 +376,7 @@ prop_getBranchIncreasing_parents f (SparseTree t0) = forAll (int <$> choose (0,m ioProperty $ withTreeDb f t $ \db _ -> do e <- maxEntry db branch <- getBranchIncreasing db e i $ \s -> P.toList_ $ P.map (view isoBH) s - return $ and $ zipWith (\a b -> view blockHash a == view blockParent b) branch (drop 1 branch) + return $ and $ zipWith (\a b -> Parent (view blockHash a) == view blockParent b) branch (drop 1 branch) where m = length $ levels t0 t = fmap (^. from isoBH) t0 From 326027fc2cee6436ba5e603038b7cd13ed6b6aff Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 29 May 2025 16:15:06 -0400 Subject: [PATCH 196/378] Fix rest api tests Change-Id: Id0000000253662d2498dc6582f9a1782d2f2fb79 --- src/Chainweb/Chainweb.hs | 2 +- src/Chainweb/RestAPI.hs | 36 +++++++++++++++++----------- test/unit/Chainweb/Test/RestAPI.hs | 38 ++++++++++++++++++++++++++---- 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 370d514ddf..9bfe9d08c6 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -758,7 +758,7 @@ runChainweb cw nowServing = do logFunctionCounter (_chainwebLogger cw) Info . (:[]) =<< roll clientClosedConnectionsCounter - chainwebServerDbs :: ChainwebServerDbs + chainwebServerDbs :: ChainwebServerDbs Pact.Transaction chainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Just cutDb , _chainwebServerBlockHeaderDbs = chainDbsToServe diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index 76651c2b92..67c0ee50da 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -135,16 +135,16 @@ serveSocketTls settings certChain key = runTLSSocket tlsSettings settings -- | Datatype for collectively passing all storage backends to -- functions that run a chainweb server. -- -data ChainwebServerDbs = ChainwebServerDbs +data ChainwebServerDbs t = ChainwebServerDbs { _chainwebServerCutDb :: !(Maybe CutDb) , _chainwebServerBlockHeaderDbs :: !(ChainMap BlockHeaderDb) - , _chainwebServerMempools :: !(ChainMap (MempoolBackend Pact.Transaction)) + , _chainwebServerMempools :: !(ChainMap (MempoolBackend t)) , _chainwebServerPayloads :: !(ChainMap SomeServer) , _chainwebServerPeerDbs :: ![(NetworkId, PeerDb)] } deriving (Generic) -emptyChainwebServerDbs :: ChainwebServerDbs +emptyChainwebServerDbs :: ChainwebServerDbs t emptyChainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Nothing , _chainwebServerBlockHeaderDbs = mempty @@ -201,8 +201,9 @@ chainwebServiceMiddlewares someChainwebServer :: HasVersion + => Show t => ChainwebConfiguration - -> ChainwebServerDbs + -> ChainwebServerDbs t -> SomeServer someChainwebServer config dbs = maybe mempty (someCutServer cutPeerDb) cuts @@ -225,8 +226,9 @@ someChainwebServer config dbs = -- someChainwebServerWithHashesAndSpvApi :: HasVersion + => Show t => ChainwebConfiguration - -> ChainwebServerDbs + -> ChainwebServerDbs t -> SomeServer someChainwebServerWithHashesAndSpvApi config dbs = maybe mempty (someCutServer cutPeerDb) cuts @@ -249,8 +251,9 @@ someChainwebServerWithHashesAndSpvApi config dbs = chainwebApplication :: HasVersion + => Show t => ChainwebConfiguration - -> ChainwebServerDbs + -> ChainwebServerDbs t -> Application chainwebApplication config dbs = chainwebP2pMiddlewares @@ -263,8 +266,9 @@ chainwebApplication config dbs -- chainwebApplicationWithHashesAndSpvApi :: HasVersion + => Show t => ChainwebConfiguration - -> ChainwebServerDbs + -> ChainwebServerDbs t -> Application chainwebApplicationWithHashesAndSpvApi config dbs = chainwebP2pMiddlewares @@ -273,26 +277,29 @@ chainwebApplicationWithHashesAndSpvApi config dbs serveChainwebOnPort :: HasVersion + => Show t => Port -> ChainwebConfiguration - -> ChainwebServerDbs + -> ChainwebServerDbs t -> IO () serveChainwebOnPort p c dbs = run (int p) $ chainwebApplication c dbs serveChainweb :: HasVersion + => Show t => Settings -> ChainwebConfiguration - -> ChainwebServerDbs + -> ChainwebServerDbs t -> IO () serveChainweb s c dbs = runSettings s $ chainwebApplication c dbs serveChainwebSocket :: HasVersion + => Show t => Settings -> Socket -> ChainwebConfiguration - -> ChainwebServerDbs + -> ChainwebServerDbs t -> Middleware -> IO () serveChainwebSocket settings sock c dbs m = @@ -300,12 +307,13 @@ serveChainwebSocket settings sock c dbs m = serveChainwebSocketTls :: HasVersion + => Show t => Settings -> X509CertChainPem -> X509KeyPem -> Socket -> ChainwebConfiguration - -> ChainwebServerDbs + -> ChainwebServerDbs t -> Middleware -> IO () serveChainwebSocketTls settings certChain key sock c dbs m = @@ -338,7 +346,7 @@ servePeerDbSocketTls settings certChain key sock nid pdb m = someServiceApiServer :: Logger logger => HasVersion - => ChainwebServerDbs + => ChainwebServerDbs t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) @@ -364,7 +372,7 @@ someServiceApiServer dbs mr (HeaderStream hs) backupEnv pbl = serviceApiApplication :: Logger logger => HasVersion - => ChainwebServerDbs + => ChainwebServerDbs t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) @@ -380,7 +388,7 @@ serveServiceApiSocket => HasVersion => Settings -> Socket - -> ChainwebServerDbs + -> ChainwebServerDbs t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) diff --git a/test/unit/Chainweb/Test/RestAPI.hs b/test/unit/Chainweb/Test/RestAPI.hs index 4e9cc2934d..9afa512dce 100644 --- a/test/unit/Chainweb/Test/RestAPI.hs +++ b/test/unit/Chainweb/Test/RestAPI.hs @@ -6,6 +6,8 @@ {-# options_ghc -fno-warn-unused-local-binds -fno-warn-unused-imports #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeAbstractions #-} -- | -- Module: Chainweb.Test.RestAPI @@ -73,6 +75,14 @@ import Chainweb.Storage.Table.RocksDB import Servant.Client_ import Chainweb.Parent +import Chainweb.PayloadProvider.Minimal +import qualified Network.HTTP.Client as HTTP +import Chainweb.PayloadProvider.P2P.RestAPI.Server (somePayloadServer) +import Chainweb.PayloadProvider.P2P.RestAPI (PayloadBatchLimit(PayloadBatchLimit)) +import Chainweb.Logger +import Chainweb.Chainweb.ChainResources (payloadP2pResources) +import P2P.Node.Configuration (defaultP2pConfiguration) +import System.LogLevel -- -------------------------------------------------------------------------- -- -- BlockHeaderDb queries @@ -131,18 +141,36 @@ version = barebonesTestVersion singletonChainGraph -- type TestClientEnv_ = TestClientEnv MockTx -mkEnv :: HasVersion => RocksDb -> Bool -> ChainMap BlockHeaderDb -> ResourceT IO TestClientEnv_ -mkEnv rdb tls dbs = do - let pdb = newPayloadDb rdb +mkEnv + :: (Logger logger, HasVersion) + => logger + -> HTTP.Manager + -> RocksDb + -> Bool + -> ChainMap BlockHeaderDb + -> ResourceT IO TestClientEnv_ +mkEnv logger mgr rdb tls dbs = do + let mkPayloadServer cid = do + pp <- newMinimalPayloadProvider logger cid rdb (Just mgr) defaultMinimalProviderConfig + let pdb = view minimalPayloadDb pp + let queue = view minimalPayloadQueue pp + SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal + SomeChainIdT @c' _ <- return $ someChainIdVal cid + -- let serv = liftIO $ somePayloadServer @_ @v' @c' @'MinimalProvider + -- logger defaultP2pConfiguration myInfo peerDb pdb queue mgr + return $! somePayloadServer @_ @v' @c' @'MinimalProvider (PayloadBatchLimit 1000) pdb + payloadServers <- liftIO $ tabulateChainsM mkPayloadServer clientEnvWithChainwebTestServer ValidateSpec tls emptyChainwebServerDbs { _chainwebServerBlockHeaderDbs = dbs - , _chainwebServerPayloads = undefined -- onAllChains pdb + , _chainwebServerPayloads = payloadServers } simpleSessionTests :: RocksDb -> Bool -> TestTree simpleSessionTests rdb tls = withVersion version $ + withResource' (HTTP.newManager HTTP.defaultManagerSettings) $ \mgrIO -> withResource' (testBlockHeaderDbs rdb) $ \dbsIO -> - withResourceT (mkEnv rdb tls =<< liftIO dbsIO) + let neverLogger = genericLogger Error (\_ -> return ()) + in withResourceT (do { mgr <- liftIO mgrIO; dbs <- liftIO dbsIO; mkEnv neverLogger mgr rdb tls dbs}) $ \env -> testGroup "client session tests" $ [httpHeaderTests env (head $ toList chainIds)] -- : (simpleClientSession env <$> toList chainIds) From e787c72f0adf146f3dd959b6332cf28fceab1ec5 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 29 May 2025 11:40:27 -0400 Subject: [PATCH 197/378] idk: try to fix headerSizeBytes Change-Id: Id000000030b06b731faa8c6e47ff1b891945b720 --- src/Chainweb/BlockHeader/Internal.hs | 3 ++- test/unit/Chainweb/Test/Version.hs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index 22f52ee773..efa2bfa784 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -1214,7 +1214,8 @@ workSizeBytes -> Natural workSizeBytes h | hashedAdjacentRecord (unsafeChainId 0) h = - headerSizeBytes (unsafeChainId 0) 0 - 32 + _versionHeaderBaseSizeBytes implicitVersion + 36 * 3 + 2 - 32 + | otherwise = headerSizeBytes (unsafeChainId 0) h - 32 _rankedBlockHash :: BlockHeader -> RankedBlockHash _rankedBlockHash h = RankedBlockHash diff --git a/test/unit/Chainweb/Test/Version.hs b/test/unit/Chainweb/Test/Version.hs index c78a1d940c..1825dfd425 100644 --- a/test/unit/Chainweb/Test/Version.hs +++ b/test/unit/Chainweb/Test/Version.hs @@ -143,7 +143,7 @@ prop_workSizeBytes = property $ do if (view blockHeight h == genesisHeight (_chainId h)) then discard else do - let l = int $ B.length $ runPutS $ encodeBlockHeaderWithoutHash h + let expectedSize = int $ B.length $ runPutS (encodeAsMiningWork h) return $ counterexample ("header: " <> sshow h) - $ workSizeBytes (view blockHeight h) === l + $ workSizeBytes (view blockHeight h) === expectedSize From b2088adae70448a8f75ecd30c1aa456c11c71741 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 10:20:39 -0400 Subject: [PATCH 198/378] Fix cutdb tests --- src/Chainweb/Chainweb.hs | 4 +- src/Chainweb/Chainweb/ChainResources.hs | 38 +++++++------- test/unit/Chainweb/Test/CutDB.hs | 70 ++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 28 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 9bfe9d08c6..35b1e3cc5a 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -524,10 +524,10 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba (_cutHeaders c) (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) mapConcurrently_ syncOne startHeaders - where + where syncOne hdr = forM_ (providers ^? atChain (_chainId hdr)) $ \case ConfiguredPayloadProvider provider -> do - let loggr = (providerLogger provider(chainLogger hdr)) + let loggr = (providerLogger provider (chainLogger hdr)) logFunctionText loggr Info $ "sync payload provider to " <> sshow (view blockHeight hdr) diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index e0819a897a..99fe28ebce 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -16,6 +16,7 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE RecursiveDo #-} +{-# LANGUAGE TupleSections #-} -- | -- Module: Chainweb.Chainweb.ChainResources @@ -108,6 +109,7 @@ import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Guards (maxBlockGasLimit) import Pact.Core.Gas qualified as Pact +import Control.Monad (forM) -- -------------------------------------------------------------------------- -- -- Payload P2P Network Resources @@ -224,11 +226,8 @@ withPayloadProviderResources => HasVersion => logger -> ChainId - -> P2pConfiguration - -> PeerInfo - -> PeerDb + -> Maybe (P2pConfiguration, PeerInfo, PeerDb, HTTP.Manager) -> RocksDb - -> HTTP.Manager -> RewindLimit -- ^ the reorg limit for the payload providers -> Bool @@ -239,7 +238,7 @@ withPayloadProviderResources -- default db location from the chainweb configuration. -> PayloadProviderConfig -> ResourceT IO ProviderResources -withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind defaultPactDbDir configs = do +withPayloadProviderResources logger cid peerStuff rdb rewindLimit initialUnlimitedRewind defaultPactDbDir configs = do SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal SomeChainIdT @c' _ <- return $ someChainIdVal cid withSomeSing provider $ \case @@ -254,15 +253,16 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi -- provider. let config = _payloadProviderConfigMinimal configs - p <- liftIO $ newMinimalPayloadProvider logger cid rdb (Just mgr) config + p <- liftIO $ newMinimalPayloadProvider logger cid rdb (view _4 <$> peerStuff) config let pdb = view minimalPayloadDb p let queue = view minimalPayloadQueue p - p2pRes <- liftIO $ payloadP2pResources @v' @c' @'MinimalProvider - logger p2pConfig myInfo peerDb pdb queue mgr + p2pRes <- liftIO $ forM peerStuff $ \(p2pConfig, myPeerInfo, peerDb, mgr) -> + payloadP2pResources @v' @c' @'MinimalProvider + logger p2pConfig myPeerInfo peerDb pdb queue mgr return ProviderResources { _providerResPayloadProvider = ConfiguredPayloadProvider p , _providerResServiceApi = Nothing - , _providerResP2pApiResources = Just p2pRes + , _providerResP2pApiResources = p2pRes } SPactProvider -> case _payloadProviderConfigPact configs ^. at cid of @@ -305,7 +305,7 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi pp <- withPactPayloadProvider cid - (Just mgr) + (view _4 <$> peerStuff) logger Nothing mempool @@ -325,8 +325,9 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi ) mempool <- Mempool.withInMemoryMempool (setComponent "mempool" logger) mempoolConfig let queue = _payloadStoreQueue $ _psPdb $ pactPayloadProviderServiceEnv pp - p2pRes <- liftIO $ payloadP2pResources @v' @c' @'PactProvider - logger p2pConfig myInfo peerDb pdb queue mgr + p2pRes <- liftIO $ forM peerStuff $ \(p2pConfig, myPeerInfo, peerDb, mgr) -> + payloadP2pResources @v' @c' @'PactProvider + logger p2pConfig myPeerInfo peerDb pdb queue mgr let pactServerData = Pact.PactServerData { Pact._pactServerDataLogger = pactPayloadProviderLogger pp @@ -344,7 +345,7 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi { _payloadResServiceApi = Pact.somePactServiceApi cid , _payloadResServiceServer = pactServer } - , _providerResP2pApiResources = Just p2pRes + , _providerResP2pApiResources = p2pRes } _ -> return $ ProviderResources DisabledPayloadProvider Nothing Nothing @@ -355,15 +356,16 @@ withPayloadProviderResources logger cid p2pConfig myInfo peerDb rdb mgr rewindLi -- and answering API requests. -- It also starts to awaiting and devlivering new payloads if mining -- is enabled. - p <- withEvmPayloadProvider logger cid rdb (Just mgr) config + p <- withEvmPayloadProvider logger cid rdb (view _4 <$> peerStuff) config let pdb = view evmPayloadDb p let queue = view evmPayloadQueue p - p2pRes <- liftIO $ payloadP2pResources @v' @c' @('EvmProvider n) - logger p2pConfig myInfo peerDb pdb queue mgr + p2pRes <- liftIO $ forM peerStuff $ \(p2pConfig, myPeerInfo, peerDb, mgr) -> + payloadP2pResources @v' @c' @('EvmProvider n) + logger p2pConfig myPeerInfo peerDb pdb queue mgr return ProviderResources { _providerResPayloadProvider = ConfiguredPayloadProvider p , _providerResServiceApi = Nothing - , _providerResP2pApiResources = Just p2pRes + , _providerResP2pApiResources = p2pRes } _ -> return $ ProviderResources DisabledPayloadProvider Nothing Nothing @@ -426,7 +428,7 @@ withChainResources logger cid rdb mgr defaultPactDbDir p2pConf myInfo peerDb rew -- Payload Providers are using per chain payload networks for fetching -- block headers. provider <- withPayloadProviderResources - providerLogger cid p2pConf myInfo peerDb rdb mgr rewindLimit initialUnlimitedRewind defaultPactDbDir configs + providerLogger cid (Just (p2pConf, myInfo, peerDb, mgr)) rdb rewindLimit initialUnlimitedRewind defaultPactDbDir configs return ChainResources { _chainResBlockHeaderDb = cdb diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index bb3e159061..40ff69e514 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -96,6 +96,11 @@ import Data.TaskMap import Test.Tasty.HUnit import Chainweb.Logger import System.LogLevel +import Chainweb.Chainweb.ChainResources (withPayloadProviderResources, providerResPayloadProvider) +import P2P.Node.Configuration (defaultP2pConfiguration) +import Chainweb.Chainweb.Configuration (PayloadProviderConfig(PayloadProviderConfig), defaultPayloadProviderConfig) +import Chainweb.Test.Pact.Utils (getTestLogger) +import qualified Data.HashSet as HS -- -------------------------------------------------------------------------- -- -- Create a random Cut DB with the respective Payload Store @@ -138,9 +143,32 @@ withTestCutDb rdb conf n providers logfun = do mgr <- liftIO $ HTTP.newManager HTTP.defaultManagerSettings headerStore <- withLocalWebBlockHeaderStore mgr webDb cutDb <- withCutDb (conf $ defaultCutDbParams cutFetchTimeout) logfun headerStore providers cutHashesDb + liftIO $ synchronizeProviders webDb genesisCut + liftIO $ logfun @Text Debug "GOING TO MINE AT THE START" - liftIO $ foldM_ (\c _ -> view _1 <$> mine cutDb c) genesisCut [1..n] + liftIO $ foldM_ (\c _ -> view _1 <$> mine logfun cutDb c) genesisCut [1..n] return (cutHashesDb, cutDb) + where + synchronizeProviders :: WebBlockHeaderDb -> Cut -> IO () + synchronizeProviders wbh c = do + let startHeaders = HM.unionWith (\startHeader _genesisHeader -> startHeader) + (_cutHeaders c) + (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) + mapConcurrently_ syncOne startHeaders + where + syncOne hdr = forM_ (providers ^? atChain (_chainId hdr)) $ \case + ConfiguredPayloadProvider provider -> do + finfo <- forkInfoForHeader wbh hdr Nothing Nothing + r <- syncToBlock provider Nothing finfo `catch` \(e :: SomeException) -> do + throwM e + unless (r == _forkInfoTargetState finfo) $ do + error "Chainweb.Test.CutDB.synchronizeProviders: unexpected result state" + logfun Debug $ "payload provider synced, on chain: " <> toText (_chainId hdr) + -- FIXME + DisabledPayloadProvider -> do + logfun Debug $ + "payload provider disabled, not synced, on chain: " <> toText (_chainId hdr) + -- -- | Adds the requested number of new blocks to the given 'CutDb'. -- -- @@ -270,7 +298,7 @@ startTestPayload rdb logfun n = do (hserver, hstore) <- startLocalWebBlockHeaderStore mgr webDb let disabledPayloadProviders = onAllChains DisabledPayloadProvider cutDb <- startCutDb (defaultCutDbParams cutFetchTimeout) logfun hstore disabledPayloadProviders cutHashesDb - foldM_ (\c _ -> view _1 <$> mine cutDb c) genesisCut [0..n] + foldM_ (\c _ -> view _1 <$> mine logfun cutDb c) genesisCut [0..n] return (hserver, cutDb) stopTestPayload :: (Async (), CutDb) -> IO () @@ -302,11 +330,12 @@ startLocalWebBlockHeaderStore mgr webDb = do mine :: HasCallStack => HasVersion + => LogFunctionText -- ^ The miner. For testing you may use 'defaultMiner'. - => CutDb + -> CutDb -> Cut -> IO (Cut, ChainId, NewPayload) -mine cutDb c = do +mine logger cutDb c = do -- Pick a chain that isn't blocked. With that mining is guaranteed to -- succeed if @@ -315,12 +344,15 @@ mine cutDb c = do -- - the chainweb is in a consistent state, -- - the pact execution service is synced with the cutdb, and -- - the transaction generator produces valid blocks. + logger Debug "going to mine" cid <- getRandomUnblockedChain c + logger Debug "got unblocked chain" tryMineForChain cutDb c cid >>= \case Left _ -> throwM $ InternalInvariantViolation "Failed to create new cut. This is a bug in Chainweb.Test.CutDB or one of it's users" Right x -> do + logger Debug "awaiting cut with block" void $ awaitCut cutDb $ ((<=) `on` _cutHeight) (view _1 x) return x @@ -473,9 +505,21 @@ tests rdb = testGroup "CutDB" testCutPruning :: RocksDb -> TestTree testCutPruning rdb = testCase "cut pruning" $ runResourceT $ withVersion (barebonesTestVersion pairChainGraph) $ do -- initialize cut DB and mine enough to trigger pruning - (cutHashesStore, _) <- withTestCutDbWithoutPact rdb alterPruningSettings + testLogger <- liftIO getTestLogger + tmp <- withTempDir "donotuse" + pps <- tabulateChainsM $ \cid -> view providerResPayloadProvider <$> withPayloadProviderResources + testLogger + cid + Nothing + rdb + (RewindLimit 10) + False + tmp + defaultPayloadProviderConfig + (cutHashesStore, _) <- withTestCutDb rdb alterPruningSettings (int $ avgCutHeightAt minedBlockHeight) - (\_ _ -> return ()) + pps + (logFunction testLogger) liftIO $ do -- peek inside the cut DB's store to find the oldest and newest cuts let table = unCasify cutHashesStore @@ -499,8 +543,18 @@ testCutGet rdb = testCase "cut get" $ withVersion (barebonesTestVersion pairChai let bh = BlockHeight 300 let ch = avgCutHeightAt bh let halfCh = ch `div` 2 - - (_, cutDb) <- withTestCutDbWithoutPact rdb id (2 * int ch) (\_ _ -> return ()) + tmp <- withTempDir "donotuse" + + pps <- tabulateChainsM $ \cid -> view providerResPayloadProvider <$> withPayloadProviderResources + (genericLogger Error (\_ -> return ())) + cid + Nothing + rdb + (RewindLimit 10) + False + tmp + defaultPayloadProviderConfig + (_, cutDb) <- withTestCutDb rdb id (2 * int ch) pps (\_ _ -> return ()) liftIO $ do curHeight <- _cutHeight <$> _cut cutDb assertGe "cut height is large enough" (Actual curHeight) (Expected $ 2 * int ch) From 5ab1ed235a119ed5ac9bcb3a6688104ebc78de51 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 13:09:18 -0400 Subject: [PATCH 199/378] Less syncToBlock calls in cut pipeline --- src/Chainweb/CutDB.hs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 3b4e964b3a..1d9575ae41 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -575,15 +575,18 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do -- ensure that payload providers are in sync with the *merged* -- cut, so that they produce payloads on the correct parents. iforM_ (_cutMap resultCut) $ \cid bh -> do - case providers ^?! atChain cid of - ConfiguredPayloadProvider provider -> do - finfo <- forkInfoForHeader hdrStore bh Nothing Nothing - r <- syncToBlock provider Nothing finfo - unless (r == _forkInfoTargetState finfo) $ do - error $ "unexpected result state" - <> "; expected: " <> sshow (_forkInfoTargetState finfo) - <> "; actual: " <> sshow r - _ -> return () + -- avoid asking for syncToBlock when we know that we're already + -- in sync, otherwise some payload providers misbehave. + when (Just bh /= curCut ^? ixg cid) $ + case providers ^?! atChain cid of + ConfiguredPayloadProvider provider -> do + finfo <- forkInfoForHeader hdrStore bh Nothing Nothing + r <- syncToBlock provider Nothing finfo + unless (r == _forkInfoTargetState finfo) $ do + error $ "unexpected result state" + <> "; expected: " <> sshow (_forkInfoTargetState finfo) + <> "; actual: " <> sshow r + _ -> return () let cutDiff = cutDiffToTextShort curCut resultCut let currentCutIdMsg = T.unwords [ "current cut is now" From bc6c14e23b6136be63ba76234476d1ea8ecec41e Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 13:32:03 -0400 Subject: [PATCH 200/378] Add evm-development-singleton Change-Id: Id0000000c7ecef14f070061716476b1aff90d751 --- .../chain-spec-20.json | 1 + chainweb.cabal | 1 + cwtools/evm-genesis/Main.hs | 3 + src/Chainweb/Version/EvmDevelopment.hs | 6 +- .../Version/EvmDevelopmentSingleton.hs | 99 +++++++++++++++++++ src/Chainweb/Version/Registry.hs | 3 +- 6 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 chain-specs/evm-development-singleton/chain-spec-20.json create mode 100644 src/Chainweb/Version/EvmDevelopmentSingleton.hs diff --git a/chain-specs/evm-development-singleton/chain-spec-20.json b/chain-specs/evm-development-singleton/chain-spec-20.json new file mode 100644 index 0000000000..449e61bfe5 --- /dev/null +++ b/chain-specs/evm-development-singleton/chain-spec-20.json @@ -0,0 +1 @@ +{"alloc":{"0x03e95Af0fC4971EdCa12E6d2d1540c28314d15d5":{"balance":"0xd3c21bcecceda1000000"},"0x28f2d8ef4e0fe6B2E945cF5C33a0118a30a62354":{"balance":"0xd3c21bcecceda1000000"},"0x33018A42499f10B54d9dBCeBB71831C805D64cE3":{"balance":"0xd3c21bcecceda1000000"},"0x3492DA004098d728201fD82657f1207a6E5426bd":{"balance":"0xd3c21bcecceda1000000"},"0x47fAE86F6416e6115a80635238AFd2F18D69926B":{"balance":"0xd3c21bcecceda1000000"},"0x7e99c2f1731D3750b74A2a0623C1F1DcB8cCa45e":{"balance":"0xd3c21bcecceda1000000"},"0x87466A8266b9DFB3Dc9180a9c43946c4AB2c2cb2":{"balance":"0xd3c21bcecceda1000000"},"0x8849BAbdDcfC1327Ad199877861B577cEBd8A7b6":{"balance":"0xd3c21bcecceda1000000"},"0x99b832eb3F76ac3277b00beADC1e487C594ffb4c":{"balance":"0xd3c21bcecceda1000000"},"0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc":{"balance":"0x0","code":"0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000014"}},"0xA310Df9740eb6CC2F5E41C59C87e339142834eA4":{"balance":"0xd3c21bcecceda1000000"},"0xD4EECE51cf451b60F59b271c5a748A8a9F16bC01":{"balance":"0xd3c21bcecceda1000000"},"0xE08643a1C4786b573d739625FD268732dBB3d033":{"balance":"0xd3c21bcecceda1000000"},"0xEE2722c39db6014Eacc5FBe43601136825b00977":{"balance":"0xd3c21bcecceda1000000"},"0xFB8Fb7f9bdc8951040a6D195764905138F7462Ed":{"balance":"0xd3c21bcecceda1000000"},"0xFd70Bef78778Ce8554e79D97521b69183960C574":{"balance":"0xd3c21bcecceda1000000"},"0xa24a79678c9fffEF3E9A1f3cb7e51f88F173B3D5":{"balance":"0xd3c21bcecceda1000000"},"0xa3659D39C901d5985450eE18a63B5b0811fDa521":{"balance":"0xd3c21bcecceda1000000"},"0xc201d4A5E6De676938533A0997802634E859e78b":{"balance":"0xd3c21bcecceda1000000"},"0xda1380825f827C6Ea92DFB547EF0a341Cbe21d77":{"balance":"0xd3c21bcecceda1000000"},"0xeDD5a9185F9F1C04a011117ad61564415057bf8F":{"balance":"0xd3c21bcecceda1000000"}},"coinbase":"0x0000000000000000000000000000000000000000","config":{"arrowGlacierBlock":0,"berlinBlock":0,"byzantiumBlock":0,"cancunTime":0,"chainId":1789,"constantinopleBlock":0,"daoForkBlock":0,"daoForkSupport":true,"eip150Block":0,"eip155Block":0,"eip158Block":0,"graphGlacierBlock":0,"homesteadBlock":0,"istanbulBlock":0,"londonBlock":0,"mergeForkBlock":0,"mergeNetsplitBlock":0,"muirGlacierBlock":0,"petersburgBlock":0,"pragueTime":0,"shanghaiTime":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true},"difficulty":"0x0","extraData":"0x","gasLimit":"0x1c9c380","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0","number":"0x0","timestamp":"0x6490fdd2"} \ No newline at end of file diff --git a/chainweb.cabal b/chainweb.cabal index 801239f828..9d40ee6391 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -286,6 +286,7 @@ library , Chainweb.Version , Chainweb.Version.Development , Chainweb.Version.EvmDevelopment + , Chainweb.Version.EvmDevelopmentSingleton , Chainweb.Version.Guards , Chainweb.Version.Mainnet , Chainweb.Version.RecapDevelopment diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 3270a828ef..99600eda1c 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -64,6 +64,9 @@ main = do ["evm-development"] -> do let cids = [20..25] return ("evm-development", cids, evmDevnetSpecFile) + ["evm-development-singleton"] -> do + let cids = [20] + return ("evm-development-singleton", cids, evmDevnetSpecFile) _ -> error "Invalid argument for the chainweb version provided. The version must be one of: 'mainnet', 'testnet', 'evm-testnet', or 'evm-development'." hdrs <- forM cids $ \cid -> do diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 2c21adcd79..f9183ffb6c 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -33,12 +33,10 @@ pattern EvmDevelopment <- ((== evmDevnet) -> True) where -- -- @ -- -- create dummy payload hashes --- import Chainweb.Payload.Provider.Minimal.Payload --- import Chainweb.Version.Registry +-- import Chainweb.PayloadProvider.Minimal.Payload -- import Chainweb.Version.EvmDevelopment -- --- registerVersion EvmDevelopment --- mapM_ (\i -> T.putStrLn (sshow i <> " " <> encodeToText (view payloadHash $ genesisPayload EvmDevelopment $ unsafeChainId i))) [25..97] +-- mapM_ (\i -> T.putStrLn (sshow i <> " " <> encodeToText (view payloadHash $ genesisPayload EvmDevelopment $ unsafeChainId i))) [25..97] -- @ -- -- EVM Payload Provider: diff --git a/src/Chainweb/Version/EvmDevelopmentSingleton.hs b/src/Chainweb/Version/EvmDevelopmentSingleton.hs new file mode 100644 index 0000000000..f1e91406b6 --- /dev/null +++ b/src/Chainweb/Version/EvmDevelopmentSingleton.hs @@ -0,0 +1,99 @@ +{-# language LambdaCase #-} +{-# language NumericUnderscores #-} +{-# language OverloadedStrings #-} +{-# language PatternSynonyms #-} +{-# language QuasiQuotes #-} +{-# language ViewPatterns #-} + +module Chainweb.Version.EvmDevelopmentSingleton +( evmDevnetSingleton +, pattern EvmDevelopmentSingleton +) where + +import qualified Data.Set as Set + +import Chainweb.BlockCreationTime +import Chainweb.ChainId +import Chainweb.Difficulty +import Chainweb.Graph +import Chainweb.Time +import Chainweb.Utils +import Chainweb.Utils.Rule +import Chainweb.Version + +import Pact.Core.Names + +pattern EvmDevelopmentSingleton :: ChainwebVersion +pattern EvmDevelopmentSingleton <- ((== evmDevnetSingleton) -> True) where + EvmDevelopmentSingleton = evmDevnetSingleton + +-- How to compute the hashes: +-- +-- Mininal Payload Provider: +-- +-- @ +-- -- create dummy payload hashes +-- import Chainweb.PayloadProvider.Minimal.Payload +-- import Chainweb.Version.EvmDevelopmentSingleton +-- +-- mapM_ (\i -> T.putStrLn (sshow i <> " " <> encodeToText (view payloadHash $ genesisPayload EvmDevelopment $ unsafeChainId i))) [25..97] +-- @ +-- +-- EVM Payload Provider: +-- +-- @ +-- cabal run evm-genesis -- evm-development +-- @ +-- +-- Pact Provider: +-- +-- TODO (use ea?) + +evmDevnetSingleton :: ChainwebVersion +evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion + { _versionCode = ChainwebVersionCode 0x0000_000b + , _versionName = ChainwebVersionName "evm-development-singleton" + , _versionForks = tabulateHashMap $ const $ onAllChains ForkAtGenesis + , _versionUpgrades = onAllChains mempty + , _versionGraphs = Bottom (minBound, pairChainGraph) + , _versionBlockDelay = BlockDelay 30_000_000 + , _versionWindow = WindowWidth 120 + , _versionHeaderBaseSizeBytes = 318 - 110 + , _versionBootstraps = [] + , _versionGenesis = VersionGenesis + { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 100_000) + , _genesisTime = onChains + -- FIXME: is the creation time for the pact headers correct? + $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0..19] ] + <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..24] ] + <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [25..97] ] + , _genesisBlockPayload = onChains $ + -- Pact Payload Provider + [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") + -- EVM Payload Provider + , (unsafeChainId 1, unsafeFromText "FAxLDjtb8r_0S0Rfr8rD47EQwO-Ma-fmEynZccHvn5o") + ] + } + + -- still the *default* block gas limit is set, see + -- defaultChainwebConfiguration._configBlockGasLimit + , _versionMaxBlockGasLimit = Bottom (minBound, Nothing) + , _versionCheats = VersionCheats + { _disablePow = True + , _fakeFirstEpochStart = True + , _disablePact = False + } + , _versionDefaults = VersionDefaults + { _disablePeerValidation = True + , _disableMempoolSync = False + } + , _versionVerifierPluginNames = onAllChains $ Bottom + (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) + , _versionQuirks = noQuirks + , _versionServiceDate = Nothing + + -- FIXME make this safe for graph changes + , _versionPayloadProviderTypes = onChains + $ [ (unsafeChainId i, PactProvider) | i <- [0] ] + <> [ (unsafeChainId i, EvmProvider (1789 - 20 + int i)) | i <- [1] ] + } diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index 0f3e8e9785..7fc8bc49f7 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -42,6 +42,7 @@ import GHC.Stack import Chainweb.Version import Chainweb.Version.Development import Chainweb.Version.EvmDevelopment +import Chainweb.Version.EvmDevelopmentSingleton import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.Testnet04 @@ -80,7 +81,7 @@ validateVersion v = do -- | Versions known to us by name. knownVersions :: [ChainwebVersion] -knownVersions = [mainnet, testnet04, recapDevnet, devnet, evmDevnet] +knownVersions = [mainnet, testnet04, recapDevnet, devnet, evmDevnet, evmDevnetSingleton] -- | Look up a known version by name, usually with `m` instantiated to some -- configuration parser monad. From 152c09d1f157fb6c473086d7f3481e8fba3c74c7 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 13:32:35 -0400 Subject: [PATCH 201/378] Use kadena-reth instead of evm-devnet-kadena-reth for evm genesis --- cwtools/evm-genesis/Main.hs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 99600eda1c..d055f9dff1 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -118,12 +118,12 @@ withRethNode cid specfile act = , "-t" , "-p", rethPort <> ":" <> rethPort , "--volume=" <> specfile <> ":/spec.json" - , "ghcr.io/kadena-io/evm-devnet-kadena-reth" + , "ghcr.io/kadena-io/kadena-reth:latest" ] rethArgs = - [ "--chain=/spec.json" - , "-q" + [ "-q" , "node" + , "--chain=/spec.json" , "--http" , "--http.addr=0.0.0.0" , "--http.port=" <> rethPort @@ -505,5 +505,3 @@ testnetSpecFile cid = object [ i :: Natural -> Natural i = id - - From 9b9a4b319cc708fd57a9ba54d52181a44a10d14a Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 13:33:54 -0400 Subject: [PATCH 202/378] Correct genesisTime --- src/Chainweb/Version/EvmDevelopmentSingleton.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/Version/EvmDevelopmentSingleton.hs b/src/Chainweb/Version/EvmDevelopmentSingleton.hs index f1e91406b6..5c9d656dd6 100644 --- a/src/Chainweb/Version/EvmDevelopmentSingleton.hs +++ b/src/Chainweb/Version/EvmDevelopmentSingleton.hs @@ -64,9 +64,8 @@ evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 100_000) , _genesisTime = onChains -- FIXME: is the creation time for the pact headers correct? - $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0..19] ] - <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..24] ] - <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [25..97] ] + $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0] ] + <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [1] ] , _genesisBlockPayload = onChains $ -- Pact Payload Provider [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") From af072df4a99018228660e3504b58083022eee0f0 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 27 May 2025 20:34:31 -0700 Subject: [PATCH 203/378] Some more EVM provider cleanup --- src/Chainweb/PayloadProvider/EVM.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 8fd5900b17..891fb82e07 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -603,7 +603,7 @@ payloadListener p = case (_evmMinerAddress p) of Nothing -> do lf Info "New payload creation is disabled." return () - Just addr -> runForeverThrottled lf "EVM Provider Payload Listener" 5 (int newPayloadRate) $ do + Just addr -> runForeverThrottled lf "EVM Provider Payload Listener" (int newPayloadBurst) (int newPayloadRate) $ do lf Info $ "Start payload listener with miner address " <> toText addr -- This is small delay compared to the base rate. So we just always add From 8ef8271f48c1a24c1658fc3fe579d0c345c095e5 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 27 May 2025 20:30:17 -0700 Subject: [PATCH 204/378] Cleanup formatting of Miner.Coordinator a bit --- src/Chainweb/Miner/Coordinator.hs | 32 +++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 8bb5ebbb76..efbb6abcbe 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -246,8 +246,8 @@ awaitLatestPayloadForParentStateSTM payloadCache parentState = do instance Brief ParentState where brief ParentState{..} = - "ParentState:" <> brief parentStateParents - <> ":solved=" <> brief parentStateSolved + "ParentState:" <> brief parentStateParents + <> ":solved=" <> brief parentStateSolved -- -------------------------------------------------------------------------- -- -- Mining State @@ -300,7 +300,14 @@ updateForCut lf hdb ms c = do , parentStateSolved = Nothing } -updateForSolved :: HasVersion => LogFunction -> CutDb -> PayloadCache -> TVar (Maybe ParentState) -> SolvedWork -> IO () +updateForSolved + :: HasVersion + => LogFunction + -> CutDb + -> PayloadCache + -> TVar (Maybe ParentState) + -> SolvedWork + -> IO () updateForSolved lf cdb payloadCache var sw = do stateOrErr <- runExceptT $ do solvedParentState <- mapExceptT atomically $ do @@ -542,11 +549,17 @@ awaitEvent cdb caches c p = -- -- 1. mining is disableled for some payload providers, -- 2. consensus did not request new payloads from providers in the latest --- 'syncToBlock' calls, --- 3. some payload providers are deadlocked, or --- 4. some payload providers are very slow in producing new payloads. +-- 'syncToBlock' calls, +-- 3. some payload providers are deadlocked, or +-- 4. some payload providers are very slow in producing new payloads. -- -randomWork :: HasVersion => LogFunction -> CutDb -> PayloadCaches -> ChainMap (TVar (Maybe ParentState)) -> IO MiningWork +randomWork + :: HasVersion + => LogFunction + -> CutDb + -> PayloadCaches + -> ChainMap (TVar (Maybe ParentState)) + -> IO MiningWork randomWork logFun cdb caches parentStateVars = do -- Pick a random chain. @@ -577,7 +590,10 @@ randomWork logFun cdb caches parentStateVars = do let (s0, s1) = splitAt n (itoList parentStateVars) go (s1 <> s0) where - awaitWorkReady :: ChainId -> TVar (Maybe ParentState) -> STM (WorkParents, NewPayload) + awaitWorkReady + :: ChainId + -> TVar (Maybe ParentState) + -> STM (WorkParents, NewPayload) awaitWorkReady cid var = do parentState <- maybe retry return =<< readTVar var guard (isNothing $ parentStateSolved parentState) From 6dcdf85311ce1708aad921acce42c8535d063a50 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 27 May 2025 20:31:40 -0700 Subject: [PATCH 205/378] Some logging improvements for the EVM provider --- src/Chainweb/PayloadProvider/EVM.hs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 891fb82e07..f18cf4ee75 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -524,7 +524,7 @@ withEvmPayloadProvider logger c rdb mgr conf liftIO $ link listenerAsync liftIO $ logg p Info $ - "EVM payload provider started for Ethereum network id " <> sshow ecid + "EVM payload provider started for Ethereum network id " <> sshow (fromSNat ecid) return p | otherwise = @@ -659,7 +659,8 @@ forkchoiceUpdate p t fcs attr = go t | otherwise = do lf Info $ briefJson $ object [ "remainingTime" .= remaining - , "request" .= request + , "forkchoiceState" .= fcs + , "payloadAttributes" .= attr ] r <- try @_ @(RPC.Error EngineServerErrors EngineErrors) $ RPC.callMethodHttp @Engine_ForkchoiceUpdatedV3 (_evmEngineCtx p) @@ -743,9 +744,9 @@ forkchoiceUpdate p t fcs attr = go t Left e -> throwM e --- | Calls forkchoiceUpdate and update the provider state. +-- | Calls forkchoiceUpdate and updates the provider state. -- --- NOTE: This must be called only for consensus state that have been validated +-- NOTE: This must be called only for consensus states that have been validated -- against the evaluation context. In particular, this is the case for the -- @_evmState@ and for any payload in the payload db. -- @@ -774,6 +775,8 @@ updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case lf Info $ "Consensus state lookup returned nothing for" <> briefJson state return () Just fcs -> do + lf Info $ "Calling forkChoiceUpdate on state " + <> briefJson state <> " with " <> briefJson fcs pt <- parentTimestamp pid <- forkchoiceUpdate p forkchoiceUpdatedTimeout fcs (attr pt) @@ -787,7 +790,12 @@ updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case -- There is a race here: If we fail updating the variable -- the EVM is ahead. That could cause payload updates to -- fail and possibly stale mining. - lf Info "update state and payload ID variable" + lf Info $ "update state and payload ID variable" + lf Debug $ briefJson $ object + [ "newState" .= state + , "newBlockCtx" .= nctx + , "newPayloadId" .= pid + ] void $ atomically $ do -- update state writeTVar (_evmState p) (T2 state nctx) From bbe220f9c984a3d21978b7250adfe47ec5dc956b Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 27 May 2025 20:33:37 -0700 Subject: [PATCH 206/378] Handle unexpected binary data in responses from EVM gracefully --- src/Chainweb/PayloadProvider/EVM/JsonRPC.hs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/JsonRPC.hs b/src/Chainweb/PayloadProvider/EVM/JsonRPC.hs index 098d4e0e30..57640ba678 100644 --- a/src/Chainweb/PayloadProvider/EVM/JsonRPC.hs +++ b/src/Chainweb/PayloadProvider/EVM/JsonRPC.hs @@ -64,7 +64,7 @@ import GHC.TypeLits import qualified Network.HTTP.Client as HTTP import qualified Network.HTTP.Types as HTTP import qualified Data.ByteString.Lazy as BL -import Chainweb.Utils (EncodingException(DecodeException)) +import Chainweb.Utils (EncodingException(DecodeException), encodeB64UrlNoPaddingText) import Data.Typeable (Typeable) import System.Random (randomRIO) import Network.URI @@ -365,7 +365,9 @@ callMethodHttp ctx m = do (BL.toStrict $ HTTP.responseBody resp) case eitherDecode $ HTTP.responseBody resp of Left e -> throwM $ DecodeException - $ T.pack e <> " -- " <> T.decodeUtf8 (BL.toStrict (HTTP.responseBody resp)) + $ T.pack e <> " -- " <> case T.decodeUtf8' (BL.toStrict (HTTP.responseBody resp)) of + Left _ -> "binary response body: " <> encodeB64UrlNoPaddingText (BL.toStrict $ HTTP.responseBody resp) + Right t -> "response body: " <> t Right (Response _ r :: JsonRpcResponse m) -> case r of Left e -> throwM e Right v -> return v From 42dbebac8118563b568664018b755b9c848f12b9 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 13:05:28 -0400 Subject: [PATCH 207/378] Fix some benchmark compilation --- bench/Chainweb/Utils/Bench.hs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bench/Chainweb/Utils/Bench.hs b/bench/Chainweb/Utils/Bench.hs index 6ed5215478..5a78ded068 100644 --- a/bench/Chainweb/Utils/Bench.hs +++ b/bench/Chainweb/Utils/Bench.hs @@ -24,7 +24,6 @@ import Chainweb.WebBlockHeaderDB (WebBlockHeaderDb) import Chainweb.Pact.Types (ServiceEnv) import Control.DeepSeq (NFData(..)) import Chainweb.Mempool.Mempool (MempoolBackend) -import Chainweb.Pact.Service.PactQueue (PactQueue) import Control.Monad.IO.Class (liftIO) import Data.Text.IO qualified as Text @@ -49,7 +48,7 @@ instance NFData (NoopNFData a) where deriving newtype instance NFData Database -instance NFData (ServiceEnv logger tbl) where +instance NFData (ServiceEnv tbl) where rnf !_ = () instance NFData WebBlockHeaderDb where @@ -61,9 +60,6 @@ instance NFData TestBlockDb where instance NFData (MempoolBackend a) where rnf !_ = () -instance NFData PactQueue where - rnf !_ = () - instance NFData LegacyPactErrorType where rnf !_ = () From a5c0f5783f8c8abfda168d78722d7d223823ab14 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 12:48:34 -0400 Subject: [PATCH 208/378] Re-enable failing replay tests --- test/multinode/MultiNodeNetworkTests.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/multinode/MultiNodeNetworkTests.hs b/test/multinode/MultiNodeNetworkTests.hs index abf6c99d69..52b8de7942 100644 --- a/test/multinode/MultiNodeNetworkTests.hs +++ b/test/multinode/MultiNodeNetworkTests.hs @@ -41,9 +41,10 @@ suite = independentSequentialTestGroup "MultiNodeNetworkTests" withTempRocksDb "multinode-tests-instantcpm-single-rocks" $ \rdb -> withSystemTempDirectory "multinode-tests-instantcpm-single-pact" $ \pactDbDir -> withVersion (instantCpmTestVersion singletonChainGraph) $ - Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step - -- , testCaseSteps "Replay - InstantTimedCPM - 6 nodes" $ \step -> - -- withTempRocksDb "replay-test-instantcpm-pair-rocks" $ \rdb -> - -- withSystemTempDirectory "replay-test-instantcpm-pair-pact" $ \pactDbDir -> - -- Chainweb.Test.MultiNode.replayTest loglevel (instantCpmTestVersion pairChainGraph) 6 rdb pactDbDir step + Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step + , testCaseSteps "Replay - InstantTimedCPM - 6 nodes" $ \step -> + withTempRocksDb "replay-test-instantcpm-pair-rocks" $ \rdb -> + withSystemTempDirectory "replay-test-instantcpm-pair-pact" $ \pactDbDir -> + withVersion (instantCpmTestVersion pairChainGraph) $ + Chainweb.Test.MultiNode.replayTest loglevel 6 rdb pactDbDir step ] From 13bff458f8e96b21b48584dfdcc1b6b36ea38436 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 14:26:24 -0400 Subject: [PATCH 209/378] Put Payload Provider tests back into main test suite Change-Id: Id000000003df0e6228643907b06fda6e13d2dea2 --- chainweb.cabal | 102 +----------------- test/payload-provider/PayloadProviderTests.hs | 48 --------- test/unit/ChainwebTests.hs | 4 + .../Test/Chainweb/SPV/Argument.hs | 37 ++++--- 4 files changed, 26 insertions(+), 165 deletions(-) delete mode 100644 test/payload-provider/PayloadProviderTests.hs rename test/{payload-provider => unit}/Test/Chainweb/SPV/Argument.hs (96%) diff --git a/chainweb.cabal b/chainweb.cabal index 9d40ee6391..813d1aaa5a 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -584,106 +584,6 @@ library chainweb-test-utils if flag(ed25519) cpp-options: -DWITH_ED25519=1 --- Temporary for payload provider development. --- Merge with unit tests as soon as those are fixed. -test-suite payload-provider-tests - import: warning-flags, debugging-flags - default-language: Haskell2010 - ghc-options: - -threaded - -Wno-x-partial -Wno-unrecognised-warning-flags - type: exitcode-stdio-1.0 - hs-source-dirs: test/payload-provider - main-is: PayloadProviderTests.hs - other-modules: - Test.Chainweb.SPV.Argument - build-depends: - -- internal - , chainweb - , chainweb:chainweb-test-utils - - -- external - , aeson >= 2.2 - , base >= 4.12 && < 5 - , bytestring >= 0.10.12 - , chainweb-storage >= 0.1 - , ethereum - , hashes >=0.2.2.0 - , lens >= 4.17 - , merkle-log >=0.2 - , raw-strings-qq >=1.1 - , tasty >= 1.0 - , tasty-hunit >= 0.9 - , tasty-json >= 0.1 - , text >=2.0 - , vector >= 0.12.2 - - -- , tasty-json >= 0.1 - -- , tasty-quickcheck >= 0.9 - -- , Decimal >= 0.4.2 - -- , QuickCheck >= 2.14 - -- , async >= 2.2 - -- , base >= 4.12 && < 5 - -- , base16-bytestring >= 1.0 - -- , base64-bytestring-kadena == 0.1 - -- , byteslice >= 0.2.12 - -- , bytesmith >= 0.3.10 - -- , cassava >= 0.5.1 - -- , containers >= 0.5 - -- , crypton >= 0.31 - -- , crypton-connection >=0.4 - -- , data-dword >= 0.3 - -- , data-ordlist >= 0.4.7 - -- , deepseq >= 1.4 - -- , direct-sqlite >= 2.3.27 - -- , exceptions - -- , ghc-compact >= 0.1 - -- , hashable >= 1.3 - -- , hedgehog >= 1.4 - -- , http-client >= 0.5 - -- , http-client-tls >=0.3 - -- , http-types >= 0.12 - -- , lens-aeson >= 1.2.2 - -- , loglevel >= 0.1 - -- , memory >=0.14 - -- , mtl >= 2.3 - -- , network >= 3.1.2 - -- , pact - -- , pact-json >= 0.1 - -- , pact-time:numeric >=0.3.0.1 - -- , pact-tng - -- , pact-tng:pact-request-api - -- , pact-tng:test-utils - -- , pact-tng:pact-repl - -- , patience >= 0.3 - -- , prettyprinter - -- , property-matchers ^>= 0.4 - -- , pretty-show - -- , quickcheck-instances >= 0.3 - -- , random >= 1.2 - -- , resource-pool >= 0.4 - -- , resourcet >= 1.3 - -- , safe-exceptions >= 0.1 - -- , scheduler >= 1.4 - -- , servant-client >= 0.18.2 - -- , sha-validation >=0.1 - -- , statistics >= 0.15 - -- , stm - -- , streaming >= 0.2.2 - -- , strict-concurrency >= 0.2 - -- , tasty >= 1.0 - -- , tasty-hedgehog >= 1.4.0.2 - -- , tasty-hunit >= 0.9 - -- , tasty-quickcheck >= 0.9 - -- , time >= 1.12.2 - -- , tls >=2.1.4 - -- , transformers >= 0.5 - -- , unordered-containers >= 0.2.20 - -- , wai >= 3.2 - -- , warp >= 3.3.6 - -- , warp-tls >= 3.4 - -- , yaml >= 0.11 - -- Chainweb Unit tests -- -- Tests in this test-suite @@ -741,6 +641,7 @@ test-suite chainweb-tests Chainweb.Test.TreeDB Chainweb.Test.TreeDB.RemoteDB Chainweb.Test.Version + Test.Chainweb.SPV.Argument -- Data Data.Test.PQueue @@ -804,6 +705,7 @@ test-suite chainweb-tests , pretty-show , quickcheck-instances >= 0.3 , random >= 1.3 + , raw-strings-qq >=1.1 , resource-pool >= 0.4 , resourcet >= 1.3 , safe-exceptions >= 0.1 diff --git a/test/payload-provider/PayloadProviderTests.hs b/test/payload-provider/PayloadProviderTests.hs deleted file mode 100644 index e73ead741e..0000000000 --- a/test/payload-provider/PayloadProviderTests.hs +++ /dev/null @@ -1,48 +0,0 @@ -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE NumericUnderscores #-} - --- | --- Module: PayloadProviderPTests --- Copyright: Copyright © 2025 Kadena LLC. --- License: MIT --- Maintainer: Lars Kuhtz --- Stability: experimental --- -module Main -( main -) where - -import Test.Tasty -import Test.Tasty.JsonReporter - --- chainweb modules - -import Chainweb.Storage.Table.RocksDB -import Chainweb.Version.Development -import Chainweb.Version.RecapDevelopment -import Chainweb.Version.Registry - --- chainweb-test-tools modules - -import Test.Chainweb.SPV.Argument qualified - -main :: IO () -main = do - registerVersion RecapDevelopment - registerVersion Development - withTempRocksDb "payload-provider-tests" $ \rdb -> - defaultMainWithIngredients (consoleAndJsonReporter : defaultIngredients) - $ adjustOption adj - $ testGroup "Chainweb Payload Provider Tests" - $ suite rdb - - where - adj NoTimeout = Timeout (1_000_000 * 60 * 10) "10m" - adj x = x - -suite :: RocksDb -> [TestTree] -suite rdb = - [ testGroup "Chainweb Payload Provider Unit Tests" - [ Test.Chainweb.SPV.Argument.tests - ] - ] diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index b3b7defbb1..cc2312bc22 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -76,6 +76,7 @@ import qualified Data.Test.Word.Encoding (properties) import qualified P2P.Test.Node (properties) import qualified P2P.Test.TaskQueue (properties) import Chainweb.Version (withVersion) +import qualified Test.Chainweb.SPV.Argument setTestLogLevel :: LogLevel -> IO () setTestLogLevel l = setEnv "CHAINWEB_TEST_LOG_LEVEL" (show l) @@ -145,4 +146,7 @@ suite rdb = , testProperties "Chainweb.Test.Difficulty" Chainweb.Test.Difficulty.properties , testProperties "Data.Test.Word.Encoding" Data.Test.Word.Encoding.properties ] + , testGroup "Chainweb Payload Provider Unit Tests" + [ Test.Chainweb.SPV.Argument.tests + ] ] diff --git a/test/payload-provider/Test/Chainweb/SPV/Argument.hs b/test/unit/Test/Chainweb/SPV/Argument.hs similarity index 96% rename from test/payload-provider/Test/Chainweb/SPV/Argument.hs rename to test/unit/Test/Chainweb/SPV/Argument.hs index 0a567206eb..5045fd76a6 100644 --- a/test/payload-provider/Test/Chainweb/SPV/Argument.hs +++ b/test/unit/Test/Chainweb/SPV/Argument.hs @@ -81,8 +81,7 @@ test_jsonRoundtrip a = assertEqual (eitherDecode (encode a)) test_jsonRoundtrips :: IO () -test_jsonRoundtrips = do - registerVersion EvmDevelopment +test_jsonRoundtrips = withVersion evmDevnet $ do test_jsonRoundtrip testHeader test_jsonRoundtrip testPayload test_jsonRoundtrip testRpcReceipt @@ -90,10 +89,13 @@ test_jsonRoundtrips = do -- -------------------------------------------------------------------------- -- -- Test Data -genesisData :: ChainwebVersion -> ChainId -> IO (BlockHeader, PayloadWithOutputs) -genesisData version cid = do - let hdr = genesisBlockHeader version cid - pwo <- case preview (ixg cid) (Pact.genesisPayload Mainnet01) of +pactMainnetGenesisPayload :: ChainId -> Maybe PayloadWithOutputs +pactMainnetGenesisPayload = withVersion Mainnet01 Pact.genesisPayload + +genesisData :: HasVersion => ChainId -> IO (BlockHeader, PayloadWithOutputs) +genesisData cid = do + let hdr = genesisBlockHeader cid + pwo <- case pactMainnetGenesisPayload cid of Just x -> return x Nothing -> error "genesis block for chain 0 not found" assertEqual "block payload hash of header and payload match" @@ -117,8 +119,8 @@ genesisData version cid = do -- Tests test_pactOutputArgument :: IO () -test_pactOutputArgument = do - (hdr, pwo) <- genesisData Mainnet01 (unsafeChainId 0) +test_pactOutputArgument = withVersion mainnet $ do + (hdr, pwo) <- genesisData (unsafeChainId 0) hdrArg <- test_hdr hdr let txCount = length $ _payloadWithOutputsTransactions pwo @@ -141,8 +143,8 @@ test_pactOutputArgument = do claim test_legacyPactOutputArgument :: IO () -test_legacyPactOutputArgument = do - (hdr, pwo) <- genesisData Mainnet01 (unsafeChainId 0) +test_legacyPactOutputArgument = withVersion mainnet $ do + (hdr, pwo) <- genesisData (unsafeChainId 0) -- create tx output proof for tx 0 let outs = snd $ payloadWithOutputsToBlockObjects pwo @@ -179,7 +181,8 @@ test_legacyPactOutputArgument = do claim test_hdr - :: BlockHeader + :: HasVersion + => BlockHeader -> IO (Argument BlockPayloadHash BlockHash) test_hdr hdr = do hdrProof <- headerProofV2 @BlockPayloadHash @ChainwebMerkleHashAlgorithm hdr @@ -240,7 +243,7 @@ data ReceiptTestData = ReceiptTestData } deriving (Show, Eq) -simpleTestData :: ReceiptTestData +simpleTestData :: HasVersion => ReceiptTestData simpleTestData = ReceiptTestData { _receiptTestDataHeader = testHeader , _receiptTestDataPayload = testPayload @@ -249,9 +252,9 @@ simpleTestData = ReceiptTestData test_evmHeaderArguments :: IO () test_evmHeaderArguments = - test_evmHeaderArgument simpleTestData 0 + withVersion evmDevnet $ test_evmHeaderArgument simpleTestData 0 -test_evmHeaderArgument :: ReceiptTestData -> Natural -> IO () +test_evmHeaderArgument :: HasVersion => ReceiptTestData -> Natural -> IO () test_evmHeaderArgument d idx = do let trieProof = EVM.rpcReceiptTrieProof rs (EVM.TransactionIndex 0) trieProofRoot <- validateTrieProof trieProof @@ -383,6 +386,7 @@ testPayload = case eitherDecodeStrictText payloadStr of Left err -> error $ "failed to decode payload: " <> show err Right x -> x where + -- TODO: the below is not a valid Pectra header, so the test fails payloadStr :: T.Text payloadStr = [r| { @@ -406,11 +410,11 @@ testPayload = case eitherDecodeStrictText payloadStr of "blobGasUsed": "0x0", "excessBlobGas": "0x0", "parentBeaconBlockRoot": "0x80af8b91b32cae2e3c3b17ef0b6ce9124e01176bf953f192633a6cc718f129ba", - "hash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f" + "hash": "0x47c1944c382527f82044276c04a9e0d642da58316d1b963e7a623e03ac085d4f", } |] -testHeader :: BlockHeader +testHeader :: HasVersion => BlockHeader testHeader = case eitherDecodeStrictText headerStr of Left err -> error $ "failed to decode header: " <> show err Right (ObjectEncoded x) -> x @@ -534,4 +538,3 @@ testHeader = case eitherDecodeStrictText headerStr of -- "featureFlags": 0, -- "hash": "9NDFjt_VVE2_fJD54eLcybRfbp_BAOR2T2HxavWe7ho" -- } - From c1c313c6e47f8100a4609ac712691f76dfab14b0 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 30 May 2025 16:58:09 -0400 Subject: [PATCH 210/378] Fix mempoolbench applycmdbench jsonencoding bench Change-Id: Id00000001b24cc3d269cffcf1b3a67d230747c18 --- bench/Chainweb/MempoolBench.hs | 61 +++++----- bench/Chainweb/Pact/Backend/ApplyCmd.hs | 154 +++++++----------------- bench/JSONEncoding.hs | 4 +- chainweb.cabal | 2 + 4 files changed, 80 insertions(+), 141 deletions(-) diff --git a/bench/Chainweb/MempoolBench.hs b/bench/Chainweb/MempoolBench.hs index ae79979249..ae9b5cc0a9 100644 --- a/bench/Chainweb/MempoolBench.hs +++ b/bench/Chainweb/MempoolBench.hs @@ -20,25 +20,32 @@ import Chainweb.Mempool.Mempool qualified as Mempool import Chainweb.Mempool.InMem qualified as InMem import Chainweb.Mempool.InMemTypes qualified as InMem import Chainweb.Pact.Transaction -import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions import Chainweb.Utils import Chainweb.Utils.Bench import Chainweb.Version -import Pact.Types.ChainMeta (getCurrentCreationTime) -import Pact.Types.Command import System.IO.Unsafe (unsafePerformIO) import qualified Data.Set as S import Data.IORef import Data.List (unfoldr) +import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Time (epoch) +import Chainweb.Pact.Utils (toTxCreationTime) +import Chainweb.PayloadProvider (EvaluationCtx(..)) +import Chainweb.Parent +import Chainweb.MinerReward +import Chainweb.BlockCreationTime -txCfg :: Mempool.TransactionConfig UnparsedTransaction -txCfg = Mempool.pact4TransactionConfig +import Pact.Core.Command.Types +import qualified Pact.Core.Gas as Pact -inmemCfg :: InMem.InMemConfig UnparsedTransaction +txCfg :: Mempool.TransactionConfig Transaction +txCfg = Mempool.pactTransactionConfig + +inmemCfg :: InMem.InMemConfig Transaction inmemCfg = InMem.InMemConfig { InMem._inmemTxCfg = txCfg - , InMem._inmemTxBlockSizeLimit = Mempool.GasLimit 150_000 + , InMem._inmemTxBlockSizeLimit = Mempool.GasLimit (Pact.Gas 150_000) , InMem._inmemTxMinGasPrice = Mempool.GasPrice (0.0000000001) , InMem._inmemMaxRecentItems = 2048 , InMem._inmemPreInsertPureChecks = return @@ -49,39 +56,38 @@ inmemCfg = InMem.InMemConfig v :: ChainwebVersion v = instantCpmTestVersion singletonChainGraph -setup :: V.Vector UnparsedTransaction -> IO (NoopNFData (Mempool.MempoolBackend UnparsedTransaction)) +setup :: V.Vector Transaction -> IO (NoopNFData (Mempool.MempoolBackend Transaction)) setup txs = do mp <- InMem.startInMemoryMempoolTest inmemCfg Mempool.mempoolInsert mp Mempool.UncheckedInsert txs return (NoopNFData mp) -cmds :: V.Vector UnparsedTransaction +cmds :: V.Vector Transaction cmds = withVersion v $ unsafePerformIO $ V.generateM 4096 $ \i -> do - now <- getCurrentCreationTime - fmap unparseTransaction - $ buildCwCmd (sshow i) - $ set cbCreationTime now - $ set cbGasLimit (Mempool.GasLimit 1) - $ defaultCmd - -txHash :: UnparsedTransaction -> Mempool.TransactionHash + buildCwCmd + $ set cbGasLimit (Mempool.GasLimit $ Pact.Gas 1) + $ set cbNonce (Just (sshow i)) + $ defaultCmd (unsafeChainId 0) + +txHash :: Transaction -> Mempool.TransactionHash txHash = Mempool.txHasher txCfg -expiredCmds :: V.Vector UnparsedTransaction -expiredCmds = withVersion v $ unsafePerformIO $ V.generateM 4096 $ \i -> do - fmap unparseTransaction - $ buildCwCmd (sshow i) - $ set cbGasLimit (Mempool.GasLimit 1) - $ defaultCmd +expiredCmds :: V.Vector Transaction +expiredCmds = withVersion v $ unsafePerformIO $ V.generateM 4096 $ \i -> + buildCwCmd + $ set cbGasLimit (Mempool.GasLimit $ Pact.Gas 1) + $ set cbCreationTime (Just $ toTxCreationTime epoch) + $ set cbNonce (Just (sshow i)) + $ defaultCmd (unsafeChainId 0) -setupMakeTxs :: V.Vector UnparsedTransaction -> IO (NoopNFData (Mempool.MempoolBackend UnparsedTransaction, V.Vector UnparsedTransaction)) +setupMakeTxs :: V.Vector Transaction -> IO (NoopNFData (Mempool.MempoolBackend Transaction, V.Vector Transaction)) setupMakeTxs txs = do mp <- InMem.startInMemoryMempoolTest inmemCfg return $ NoopNFData (mp, txs) bfEmpty :: Mempool.BlockFill bfEmpty = Mempool.BlockFill - { Mempool._bfGasLimit = Mempool.GasLimit 150_000 + { Mempool._bfGasLimit = Mempool.GasLimit $ Pact.Gas 150_000 -- ^ Fetch pending transactions up to this limit. , Mempool._bfTxHashes = mempty -- ^ Fetch only transactions not in set. @@ -96,7 +102,8 @@ bfWithNHashes n = bfEmpty mempoolGetBlockBench :: _ => _ mempoolGetBlockBench name p bf n = C.bench name $ C.perRunEnv (setup (V.take n cmds)) $ \(NoopNFData mp) -> do - Mempool.mempoolGetBlock mp bf Mempool.noopMempoolPreBlockCheck (BlockHeight 1) nullBlockHash + Mempool.mempoolGetBlock mp bf Mempool.noopMempoolPreBlockCheck + (EvaluationCtx (Parent $ BlockCreationTime epoch) (Parent nullBlockHash) (Parent $ BlockHeight 0) (MinerReward 0) ()) >>= p allPendingTxHashes :: Mempool.MempoolBackend t -> IO [Mempool.TransactionHash] @@ -169,7 +176,7 @@ bench = C.bgroup "mempool" $ concat >>= P.alignExact (V.replicate 2000 (P.equals True)) ) $ \(NoopNFData ~(mp, txs)) -> do - Mempool.mempoolAddToBadList mp (Mempool.pact4RequestKeyToTransactionHash . cmdToRequestKey <$> txs) + Mempool.mempoolAddToBadList mp (Mempool.pactRequestKeyToTransactionHash . cmdToRequestKey <$> txs) , C.bench "mempoolPrune" $ C.perRunEnvWithCleanup (setup (V.take 2000 cmds)) (\(NoopNFData mp) -> diff --git a/bench/Chainweb/Pact/Backend/ApplyCmd.hs b/bench/Chainweb/Pact/Backend/ApplyCmd.hs index 7e3385792d..45d6ba4fb0 100644 --- a/bench/Chainweb/Pact/Backend/ApplyCmd.hs +++ b/bench/Chainweb/Pact/Backend/ApplyCmd.hs @@ -29,154 +29,88 @@ import Chainweb.BlockHeaderDB (BlockHeaderDb) import Chainweb.Graph (singletonChainGraph) import Chainweb.Logger import Chainweb.Miner.Pact (noMiner) -import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) -import Chainweb.Pact.PactService (initialPayloadState, withPactService) -import Chainweb.Pact.PactService.Checkpointer (readFrom, SomeBlockM(..)) +import Chainweb.Pact.PactService (withPactService) +import Chainweb.Pact.PactService.Checkpointer (readFrom) import Chainweb.Pact.Types -import Chainweb.Pact4.Backend.ChainwebPactDb qualified as Pact -import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Pact.TransactionExec qualified as Pact -import Chainweb.Pact4.Types qualified as Pact import Chainweb.Pact.Transaction import Chainweb.Pact.TransactionExec qualified as Pact5 import Chainweb.Pact.Types qualified as Pact5 import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Cut.TestBlockDb (TestBlockDb(..), mkTestBlockDbIO) -import Chainweb.Test.Pact4.Utils qualified as Pact +import Chainweb.Test.Cut.TestBlockDb (TestBlockDb(..), mkTestBlockDb) import Chainweb.Test.Pact.CmdBuilder qualified as Pact5 import Chainweb.Test.TestVersions -import Chainweb.Utils (T2(..), T3(..)) import Chainweb.Utils.Bench -import Chainweb.Pact.Backend.Types (SQLiteEnv) +import Chainweb.Pact.Backend.Types (SQLiteEnv, BlockHandle, throwIfNoHistory) import Chainweb.Version import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb) -import Control.Concurrent (ThreadId, forkIO, throwTo) -import Control.Concurrent.MVar (newEmptyMVar, putMVar, readMVar) import Control.DeepSeq -import Control.Exception (AsyncException(..)) import Control.Lens hiding (only) -import Control.Monad (void) import Control.Monad.IO.Class -import Control.Monad.Reader import Criterion.Main qualified as C import Data.ByteString (ByteString) -import Data.Functor.Product import Pact.Core.Command.Types qualified as Pact5 import Pact.Core.Errors qualified as Pact5 import Pact.Core.Evaluate qualified as Pact5 import Pact.Core.Gas.Types qualified as Pact5 import Pact.Core.Persistence qualified as Pact5 -import Pact.Core.SPV qualified as Pact5 -import Pact.Types.Command qualified as Pact -import Pact.Types.Gas qualified as Pact -import Pact.Types.Runtime qualified as Pact -import Pact.Types.SPV qualified as Pact +import Control.Monad.State.Strict +import Chainweb.Test.Utils (withTempChainSqlite) +import qualified Control.Monad.Trans.Resource as ResourceT +import Data.IORef +import Chainweb.Parent +import Chainweb.Time +import Chainweb.BlockCreationTime bench :: RocksDb -> C.Benchmark bench rdb = C.bgroup "applyCmd" - [ C.bench "Pact5" $ withVersion pact5Version benchApplyCmd rdb (SomeBlockM $ Pair (error "Pact4") applyCmd5) - , C.bench "Pact4" $ withVersion pact4Version benchApplyCmd rdb (SomeBlockM $ Pair applyCmd4 (error "Pact5")) + [ C.bench "Pact5" $ withVersion pact5Version $ benchApplyCmd rdb applyCmd ] data Env = Env { sqlite :: !SQLiteEnv , testBlockDb :: !TestBlockDb - , testBlockDbRocksDb :: !RocksDb , blockHeaderDb :: !BlockHeaderDb , logger :: !GenericLogger - , pactServiceThreadId :: !ThreadId - , pactServiceEnv :: !(ServiceEnv GenericLogger RocksDbTable) + , pactServiceEnv :: !(ServiceEnv RocksDbTable) } instance NFData Env where rnf !_ = () -benchApplyCmd :: HasVersion => RocksDb -> SomeBlockM GenericLogger RocksDbTable a -> C.Benchmarkable +benchApplyCmd :: HasVersion => RocksDb -> (BlockEnv -> BlockHandle -> IO a) -> C.Benchmarkable benchApplyCmd rdb act = - let setupEnv _ = do - sql <- openSQLiteConnection "" chainwebPragmas - T2 tdb tdbRdb <- mkTestBlockDbIO rdb + let setupEnv _ = ResourceT.runResourceT $ do + (sql, sqlPool) <- withTempChainSqlite chain0 + tdb <- mkTestBlockDb rdb bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb tdb) chain0 - lgr <- testLogger - - psEnvVar <- newEmptyMVar --- <<<<<<< Conflict 1 of 1 --- %%%%%%% Changes from base to side #1 --- - tid <- forkIO $ void $ withPactService ver chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql testPactServiceConfig $ do --- + tid <- forkIO $ void $ withPactService ver chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql defaultPactServiceConfig $ do --- initialPayloadState ver chain0 --- +++++++ Contents of side #2 --- tid <- forkIO $ void $ withPactService chain0 lgr Nothing bhdb (_bdbPayloadDb tdb) sql testPactServiceConfig $ do --- initialPayloadState chain0 --- >>>>>>> Conflict 1 of 1 ends - psEnv <- ask - liftIO $ putMVar psEnvVar psEnv - psEnv <- readMVar psEnvVar - - pure $ Env + lgr <- liftIO testLogger + + serviceEnv <- withPactService chain0 Nothing mempty lgr Nothing (_bdbPayloadDb tdb) sqlPool sql defaultPactServiceConfig GeneratingGenesis + + sRef <- ResourceT.getInternalState + s <- liftIO $ readIORef sRef + liftIO $ writeIORef sRef =<< readIORef =<< ResourceT.createInternalState + pure $ (Env { sqlite = sql , testBlockDb = tdb - , testBlockDbRocksDb = tdbRdb , blockHeaderDb = bhdb , logger = lgr - , pactServiceThreadId = tid - , pactServiceEnv = psEnv - } - - cleanupEnv _ env = do - closeSQLiteConnection env.sqlite - throwTo env.pactServiceThreadId ThreadKilled - deleteNamespaceRocksDb env.testBlockDbRocksDb + , pactServiceEnv = serviceEnv + }, NoopNFData s) + + cleanupEnv _ (_, NoopNFData s) = ResourceT.closeInternalState =<< newIORef s in - C.perBatchEnvWithCleanup setupEnv cleanupEnv $ \ ~env -> do - T2 a _finalPactState <- runPactServiceM (PactServiceState mempty) env.pactServiceEnv $ do - throwIfNoHistory =<< - readFrom - (Just $ ParentHeader (gh chain0)) - act + C.perBatchEnvWithCleanup setupEnv cleanupEnv $ \ ~(env, _) -> do + a <- do + (throwIfNoHistory =<<) $ readFrom + env.logger chain0 env.sqlite + (Parent $ BlockCreationTime epoch) (Parent (view rankedBlockHash $ gh chain0)) + act return (NoopNFData a) -applyCmd4 :: Pact4.PactBlockM GenericLogger RocksDbTable (Pact4.CommandResult [Pact4.TxLogJson]) --(CommandResult [TxLog ByteString] (PactError Info)) -applyCmd4 = withVersion pact4Version $ do - lgr <- view (psServiceEnv . psLogger) - let txCtx = Pact4.TxContext - { Pact4._tcParentHeader = ParentHeader (gh chain0) - , Pact4._tcPublicMeta = Pact4.noPublicMeta - , Pact4._tcMiner = noMiner - } - let gasModel = Pact4.getGasModel txCtx - pactDbEnv <- view (psBlockDbEnv . Pact4.cpPactDbEnv) - - cmd <- liftIO $ Pact4.buildCwCmd "fakeNonce" - $ set Pact4.cbSigners - [ Pact4.mkEd25519Signer' Pact4.sender00 [] - ] - $ set Pact4.cbChainId chain0 - $ set Pact4.cbRPC (Pact4.mkExec' "(fold + 0 [1 2 3 4 5])") - $ Pact4.defaultCmd - - T3 cmdResult _moduleCache _warnings <- liftIO $ - Pact4.applyCmd - lgr - Nothing - Nothing - pactDbEnv - noMiner - gasModel - txCtx - (TxBlockIdx 0) - Pact4.noSPVSupport - (fmap Pact4.payloadObj cmd) - (Pact4.Gas 1) - mempty -- module cache - ApplySend - - pure cmdResult -{-# noinline applyCmd4 #-} - -applyCmd5 :: Pact5.PactBlockM GenericLogger RocksDbTable (Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info)) -applyCmd5 = withVersion pact5Version $ do +applyCmd :: HasVersion => BlockEnv -> BlockHandle -> IO (Pact5.CommandResult [Pact5.TxLog ByteString] (Pact5.PactError Pact5.Info)) +applyCmd bEnv bHandle = do cmd <- liftIO $ Pact5.buildCwCmd (Pact5.defaultCmd chain0) { Pact5._cbRPC = Pact5.mkExec' "(fold + 0 [1 2 3 4 5])" , Pact5._cbGasPrice = Pact5.GasPrice 2 @@ -184,15 +118,14 @@ applyCmd5 = withVersion pact5Version $ do -- no caps should be equivalent to the GAS cap , Pact5._cbSigners = [Pact5.mkEd25519Signer' Pact5.sender00 []] } - lgr <- view (psServiceEnv . psLogger) - let txCtx = Pact5.TxContext {Pact5._tcParentHeader = ParentHeader (gh chain0), Pact5._tcMiner = noMiner} + lgr <- testLogger - Pact5.pactTransaction Nothing $ \pactDb -> do - r <- Pact5.applyCmd lgr Nothing pactDb txCtx (TxBlockIdx 0) Pact5.noSPVSupport (Pact5.Gas 1) (view payloadObj <$> cmd) + flip evalStateT bHandle $ Pact5.pactTransaction bEnv Nothing $ \pactDb spvSupport -> do + r <- Pact5.applyCmd lgr Nothing pactDb noMiner (_psBlockCtx bEnv) (TxBlockIdx 0) spvSupport (Pact5.Gas 1) (view payloadObj <$> cmd) case r of Left err -> error $ show err Right a -> pure a -{-# noinline applyCmd5 #-} +{-# noinline applyCmd #-} chain0 :: ChainId chain0 = unsafeChainId 0 @@ -200,8 +133,5 @@ chain0 = unsafeChainId 0 gh :: HasVersion => ChainId -> BlockHeader gh = genesisBlockHeader -pact4Version :: ChainwebVersion -pact4Version = instantCpmTestVersion singletonChainGraph - pact5Version :: ChainwebVersion -pact5Version = pact5InstantCpmTestVersion singletonChainGraph +pact5Version = instantCpmTestVersion singletonChainGraph diff --git a/bench/JSONEncoding.hs b/bench/JSONEncoding.hs index 81e6d9c329..68b7071ae1 100644 --- a/bench/JSONEncoding.hs +++ b/bench/JSONEncoding.hs @@ -61,7 +61,7 @@ benchmarks = bgroup "JSONEncoding" , group "1000" (payloadPage 1000) , group "5000" (payloadPage 5000) ] - , bgroup "header page" + , withVersion mainnet $ bgroup "header page" [ group "5" (headerPage 5) , group "10" (headerPage 10) , group "50" (headerPage 50) @@ -70,7 +70,7 @@ benchmarks = bgroup "JSONEncoding" , group "1000" (headerPage 1000) , group "5000" (headerPage 5000) ] - , bgroup "object encoded header page" + , withVersion mainnet $ bgroup "object encoded header page" [ group "5" (objHeaderPage 5) , group "10" (objHeaderPage 10) , group "50" (objHeaderPage 50) diff --git a/chainweb.cabal b/chainweb.cabal index 813d1aaa5a..16789d3a79 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -872,6 +872,8 @@ benchmark bench , pact-tng:pact-request-api , property-matchers ^>= 0.7 , random >= 1.3 + , resource-pool >= 0.4 + , resourcet >= 1.3 , safe-exceptions , streaming , tasty-hunit From 843ceb8befada9cfe63c9fe3491244c7dde971aa Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 30 May 2025 20:05:06 -0700 Subject: [PATCH 211/378] Implement EVM engine_NewPaylaodV4 engine call --- src/Chainweb/PayloadProvider/EVM/EngineAPI.hs | 112 +++++++++++++++++- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs index 7b5a2cad09..7a4cc4839e 100644 --- a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs +++ b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs @@ -24,6 +24,8 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE StandaloneKindSignatures #-} +{-# LANGUAGE TypeOperators #-} -- | -- Module: Chainweb.PayloadProvider.EVM.EngineAPI @@ -105,6 +107,9 @@ module Chainweb.PayloadProvider.EVM.EngineAPI , GetPayloadV3Response(..) , GetPayloadV4Response(..) +-- * New Paylaod Request +, NewPayloadV4Request(..) + -- * Authentication and Client Context , JwtSecret(..) , jwtToken @@ -117,6 +122,7 @@ module Chainweb.PayloadProvider.EVM.EngineAPI , type Engine_GetPayloadV3 , type Engine_GetPayloadV4 , type Engine_ForkchoiceUpdatedV3 +, type Engine_NewPayloadV4 ) where import Chainweb.PayloadProvider.EVM.Header @@ -135,10 +141,11 @@ import Data.Hashable (Hashable) import Data.Text qualified as T import Data.Text.Encoding qualified as T import Data.Time.Clock.POSIX (getPOSIXTime, POSIXTime) +import Data.Word import Ethereum.Misc qualified as E import Ethereum.RLP (RLP (..), putRlpByteString, getRlpL, putRlpL, label) import Ethereum.Trie -import Ethereum.Utils hiding (int) +import Ethereum.Utils hiding (int, natVal_) import Foreign.Storable (Storable) import GHC.Generics (Generic) import GHC.TypeLits @@ -146,7 +153,6 @@ import Network.HTTP.Client qualified as HTTP import Network.URI import Network.URI.Static (uri) import System.IO.Unsafe (unsafePerformIO) -import Data.Word -- -------------------------------------------------------------------------- -- -- Forkchoice State V1 @@ -348,6 +354,22 @@ withdrawlsRoot l = unsafePerformIO $ do -- -------------------------------------------------------------------------- -- -- Blobs Bundle V1 +type BYTES_PER_FIELD_ELEMENT :: Natural +type BYTES_PER_FIELD_ELEMENT = 32 + +type FIELD_ELEMENTS_PER_BLOB :: Natural +type FIELD_ELEMENTS_PER_BLOB = 4096 + +type BLOB_SIZE :: Natural +type BLOB_SIZE = FIELD_ELEMENTS_PER_BLOB GHC.TypeLits.* BYTES_PER_FIELD_ELEMENT + +-- | EIP-4844 Versioned Hash +-- +newtype VersionedHash = VersionedHash { _versionedHash :: E.BytesN 32 } + deriving (Show, Eq, Ord) + deriving newtype (RLP, E.Bytes, Storable, Hashable) + deriving (ToJSON, FromJSON) via (HexBytes (E.BytesN 32)) + -- | EIP-4844 KZG Commitment -- newtype KzgCommitment = KzgCommitment { _kzgCommitment :: E.BytesN 48 } @@ -364,12 +386,10 @@ newtype KzgProof = KzgProof { _kzgProof :: E.BytesN 48 } -- | EIP-4844 Blob -- --- size: FIELD_ELEMENTS_PER_BLOB * BYTES_PER_FIELD_ELEMENT = 4096 * 32 = 131072 --- -newtype Blob = Blob { _blob :: E.BytesN 131072 } +newtype Blob = Blob { _blob :: E.BytesN BLOB_SIZE } deriving (Show, Eq, Ord) deriving newtype (RLP, E.Bytes, Storable, Hashable) - deriving (ToJSON, FromJSON) via (HexBytes (E.BytesN 131072)) + deriving (ToJSON, FromJSON) via (HexBytes (E.BytesN BLOB_SIZE)) -- | Blobs Bundle V1 -- @@ -1209,6 +1229,86 @@ instance FromJSON GetPayloadV4Response where -- -- timeout: 8s +-- -------------------------------------------------------------------------- -- +-- Engine New Payload V4 (Prague / Pectra) + +type Engine_NewPayloadV4 = "engine_newPayloadV4" + +-- | Engine New Payload V4 Response +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#response +-- +-- This is the same type as PayloadStatusV1, with: +-- +-- result: PayloadStatusV1, values of the status field are restricted in the +-- following way: INVALID_BLOCK_HASH status value is supplanted by INVALID. +-- +-- error: code and message set in case an exception happens while processing the +-- payload. +-- +instance JsonRpcMethod "engine_newPayloadV4" where + type MethodRequest "engine_newPayloadV4" = NewPayloadV4Request + type MethodResponse "engine_newPayloadV4" = PayloadStatusV1 + type ServerErrors "engine_newPayloadV4" = EngineServerErrors + type ApplicationErrors "engine_newPayloadV4" = EngineErrors + responseTimeoutMs = Just 8000 + methodErrors = + [ InvalidParams + , ApplicationError UnsupportedFork + ] + +-- | Engine New Payload V4 Request +-- +-- Method parameter list is extended with executionRequests. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#request +-- +data NewPayloadV4Request = NewPayloadV4Request + { _newPayloadV4RequestExecutionPayload :: !ExecutionPayloadV3 + , _newPayloadV4RequestExpectedBlobVersionedHashes :: ![VersionedHash] + -- ^ Array of DATA, 32 Bytes - Array of expected blob versioned hashes + -- to validate. + , _newPayloadV4RequestParentBeaconBlockRoot :: !ParentBeaconBlockRoot + -- ^ DATA, 32 Bytes - Root of the parent beacon block. + , _newPayloadV4RequestExecutionRequests :: ![ExecutionRequest] + -- ^ Array of DATA - List of execution layer triggered requests. Each + -- list element is a requests byte array as defined by EIP-7685. The + -- first byte of each element is the request_type and the remaining + -- bytes are the request_data. Elements of the list MUST be ordered by + -- request_type in ascending order. Elements with empty request_data + -- MUST be excluded from the list. If the list has no elements, the + -- expected array MUST be []. If any element is out of order, has a + -- length of 1-byte or shorter, or more than one element has the same + -- type byte, or the param is null, client software MUST return -32602: + -- Invalid params error. + } + deriving (Show, Eq, Generic) + +newPayloadV4RequestProperties + :: KeyValue e kv + => NewPayloadV4Request + -> [kv] +newPayloadV4RequestProperties o = + [ "executionPayload" .= _newPayloadV4RequestExecutionPayload o + , "expectedBlobVersionedHashes" .= _newPayloadV4RequestExpectedBlobVersionedHashes o + , "parentPeaconBlockRoot" .= _newPayloadV4RequestParentBeaconBlockRoot o + , "executionRequests" .= _newPayloadV4RequestExecutionRequests o + ] + +instance ToJSON NewPayloadV4Request where + toEncoding = pairs . mconcat . newPayloadV4RequestProperties + toJSON = object . newPayloadV4RequestProperties + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON NewPayloadV4Request where + parseJSON = withObject "NewPayloadV4Request" $ \o -> NewPayloadV4Request + <$> o .: "executionPayload" + <*> o .: "expectedBlobVersionedHashes" + <*> o .: "parentBeaconBlockRoot" + <*> o .: "executionRequests" + {-# INLINE parseJSON #-} + -- -------------------------------------------------------------------------- -- -- Authentication From 45bb7d0d34b1d5565f9056cb0b9f603cbb0dc200 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 30 May 2025 20:07:33 -0700 Subject: [PATCH 212/378] implement newPayload function in EVM payload provider --- src/Chainweb/PayloadProvider/EVM.hs | 198 +++++++++++++++++++++++++--- 1 file changed, 180 insertions(+), 18 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index f18cf4ee75..cbbe4d198a 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -457,10 +457,32 @@ instance Exception ForkchoiceUpdatedTimeoutException -- | Thrown on an invalid payload status. -- -data InvalidPayloadException = InvalidPayloadException !EVM.BlockHash !(Maybe T.Text) +-- The semantics of the first paramter depends on the context: +-- +-- In case block validation was attempted and failed, the second parameter is +-- the latest valid payload hash that is an ancestor of the payload hash for +-- which validation failed. +-- +-- In case of a newPayload request, if the new payload is not on the canonical +-- chain, no validation is attempted and the latest valid hash parameter is +-- 'Nothing'. +-- +data InvalidPayloadException = InvalidPayloadException + { _invalidPayloadExceptionLatestValidHash :: !(Maybe EVM.BlockHash) + , _invalidPayloadExceptionMessage :: !(Maybe T.Text) + } deriving (Eq, Show, Generic) instance Exception InvalidPayloadException +newtype UnexpectedNewPayloadResponseException + = UnexpectedNewPayloadResponseException PayloadStatusV1 + deriving (Eq, Show, Generic) +instance Exception UnexpectedNewPayloadResponseException + +newtype NewPayloadTimeoutException = NewPayloadTimeoutException Micros + deriving (Eq, Show, Generic) +instance Exception NewPayloadTimeoutException + -- -------------------------------------------------------------------------- -- -- | Initializes a new EVM Payload provider @@ -576,16 +598,16 @@ checkExecutionClient logger c ctx expectedEcid = do -- | Base rate for new payload requests. -- -newPayloadRate :: Int -newPayloadRate = 1_000_000 +getPayloadRate :: Int +getPayloadRate = 1_000_000 -newPayloadBurst :: Int -newPayloadBurst = 4 +getPayloadBurst :: Int +getPayloadBurst = 4 -- | minimum delay between requests for new payloads -- -newPayloadMinDelay :: Int -newPayloadMinDelay = 10_000 +getPayloadMinDelay :: Int +getPayloadMinDelay = 10_000 -- | If we don't receive a new payload ID with this time, we assume that the -- EVM is not available and log an error. This is implemented in the @@ -593,8 +615,8 @@ newPayloadMinDelay = 10_000 -- -- FIXME: consider raising an actual error. -- -newPayloadTimeout :: Int -newPayloadTimeout = 30_000_000 +getPayloadTimeout :: Int +getPayloadTimeout = 30_000_000 -- | Scheduler for listening for new payloads. -- @@ -603,12 +625,12 @@ payloadListener p = case (_evmMinerAddress p) of Nothing -> do lf Info "New payload creation is disabled." return () - Just addr -> runForeverThrottled lf "EVM Provider Payload Listener" (int newPayloadBurst) (int newPayloadRate) $ do + Just addr -> runForeverThrottled lf "EVM Provider Payload Listener" (int getPayloadBurst) (int getPayloadRate) $ do lf Info $ "Start payload listener with miner address " <> toText addr -- This is small delay compared to the base rate. So we just always add -- it. It is only relevant during bursts. - threadDelay $ int newPayloadMinDelay + threadDelay $ int getPayloadMinDelay awaitNewPayload p where lf = loggS p "payloadListener" @@ -701,16 +723,33 @@ forkchoiceUpdate p t fcs attr = go t lf Info "forkchoiceUpdate succeeded with VALID status" return (_forkchoiceUpdatedV1ResponsePayloadId s) - -- FIXME: + -- Validation failed permenently. -- - -- If the status is INVALID the latest valid hash is always - -- returned. The validationError message is optional. + -- FIXME: list precise list of possible reasons. -- PayloadStatusV1 Invalid (Just h) e -> - throwM $ InvalidPayloadException h e + throwM $ InvalidPayloadException (Just h) e - -- This includes - -- - PayloadStatusV1 Invalid Nothing _ + + -- If the status is INVALID and no latest valid predecessor + -- can be determined. + -- + -- This means probably means that the block for which + -- the upddate is requested is detached from the canonical + -- history. + -- + -- FIXME: We should probably point that out in the exception + -- and possibly react to this scenario by banning the source + -- of the block. + -- + -- (Note this could possibly also happen for shallow nodes + -- that are not yet fully synced.) + -- + PayloadStatusV1 Invalid Nothing e -> + throwM $ UnexpectedForkchoiceUpdatedResponseException e + + -- Something else when wrong. + -- e -> throwM $ UnexpectedForkchoiceUpdatedResponseException e @@ -744,6 +783,129 @@ forkchoiceUpdate p t fcs attr = go t Left e -> throwM e +-- | Engine NewPayloadV4 +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_newpayloadv1 +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_newpayloadv2 +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_newpayloadv3 +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#engine_newpayloadv4 +-- +newPayload + :: Logger logger + => EvmPayloadProvider logger + -> Micros + -- ^ Global Timeout in microseconds + -> Payload + -> IO () +newPayload p t pld = go t + where + request = NewPayloadV4Request + { _newPayloadV4RequestExecutionPayload = error "TODO" + , _newPayloadV4RequestExpectedBlobVersionedHashes = error "TODO" + , _newPayloadV4RequestParentBeaconBlockRoot = error "TODO" + , _newPayloadV4RequestExecutionRequests = error "TODO" + } + lf = loggS p "forkchoiceUpdate" + waitTime = Micros 500_000 + go remaining + | remaining <= 0 = do + lf Warn $ "newPayload timed out while EVM is syncing" + throwM $ NewPayloadTimeoutException t + | otherwise = do + lf Info $ briefJson $ object + [ "remainingTime" .= remaining + , "request" .= request + ] + r <- try @_ @(RPC.Error EngineServerErrors EngineErrors) $ + RPC.callMethodHttp @Engine_NewPayloadV4 (_evmEngineCtx p) + request + case r of + -- If the status is VALID, the latest hash is what was requested. + -- + -- In particular, if the status is VALID the return latest hash + -- is never an ancestor of the requested hash. + -- + -- The payload *MUST* be validated if the payload extends the + -- canonical chain and all requisite data is available. + -- + -- IMPORTANT NOTE: + -- + -- The Engine API specification demands that, if the requested + -- hash was not the latest hash on the (selected) canonical + -- chain this returns the latest hash on the canonical chain. It + -- will not initiate payload creation. This behavior is not + -- acceptable for Chainweb consensus. + -- + -- Therefore will have to patch the EVM client such that the + -- latest hash will be the requested hash and payload production + -- is initiated if it was requested. + -- + Right (PayloadStatusV1 Valid (Just _) Nothing) -> do + lf Info "newPayload succeeded with VALID status" + + -- The following conditions are met: + -- + -- - all transactions have non-zero length + -- - the blockHash of the payload is valid + -- - the payload doesn't extend the canonical chain + -- - the payload hasn't been fully validated + -- - ancestors of a payload are known and comprise a well-formed chain. + -- - the versioned block hashes match the values in the block + -- - the request commitments match the values in the block + -- + Right (PayloadStatusV1 Accepted Nothing Nothing) -> + lf Info "newPayload succeeded with ACCEPTED status" + + -- Syncing: retry + -- + -- A sync process *MAY* be initiated if requesite data for + -- payload validation is missing. + -- + -- Certain checks are performed before a sync process is + -- initiated. However, the specification is not clear with + -- respect to this. + -- + Right (PayloadStatusV1 Syncing Nothing Nothing) -> do + -- wait 500ms + lf Warn $ "newPayload: EVM is SYNCING. Waiting for " <> sshow waitTime <> " microseconds" + threadDelay $ int waitTime + go (remaining - waitTime) + + -- The new payload is invalid. This includes + -- + -- - transactions contains zero length or invalid entries + -- - block hash validation fails + -- - the versioned blob hashes do not match + -- - the request commitments do not match + -- - Validation was attempted but failed and the EL cannot determine + -- the latest valid hash that is an ancestor of the payload. + -- + Right (PayloadStatusV1 Invalid Nothing e) -> + throwM $ InvalidPayloadException Nothing e + + -- Validation was attempted but failed. + -- + -- The latest valid hash is the latest valid ancestor of the + -- payload hash. + -- + Right (PayloadStatusV1 Invalid (Just h) e) -> + throwM $ InvalidPayloadException h e + + Right e -> + throwM $ UnexpectedNewPayloadResponseException e + + Left (RPC.Error (RPC.ApplicationError InvalidPayloadAttributes) msg Nothing) -> do + T2 cur _ <- stateIO p + lf Error $ msg <> ": newPayload succeeded but no payload is produced, because of invalid payload attributes. This is most likely due to a non-increasing timestamp" + lf Error $ encodeToText $ object + [ "method" .= "newPayloadV4" + , "request" .= request + , "response" .= r + , "currentConsensusState" .= cur + ] + + Left e -> throwM e + -- | Calls forkchoiceUpdate and updates the provider state. -- -- NOTE: This must be called only for consensus states that have been validated @@ -919,7 +1081,7 @@ awaitNewPayload p = do -- Wait for payload from the execution client -- FIXME not sure if the timeout is a good idea... awaitPid = do - timeout <- registerDelay newPayloadTimeout + timeout <- registerDelay getPayloadTimeout atomically $ Nothing <$ (readTVar timeout >>= guard) <|> From 49e69e838caedbbdb610bd9b6f9f7f29cf6a1275 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 31 May 2025 17:49:00 -0700 Subject: [PATCH 213/378] Store EVM Execution Payloads in consensus --- chainweb.cabal | 2 + src/Chainweb/PayloadProvider/EVM.hs | 228 ++++--- src/Chainweb/PayloadProvider/EVM/EngineAPI.hs | 142 +++-- .../PayloadProvider/EVM/ExecutionPayload.hs | 578 ++++++++++++++++++ src/Chainweb/PayloadProvider/EVM/HeaderDB.hs | 2 +- src/Chainweb/PayloadProvider/EVM/PayloadDB.hs | 283 +++++++++ src/Chainweb/PayloadProvider/P2P/RestAPI.hs | 18 +- 7 files changed, 1065 insertions(+), 188 deletions(-) create mode 100644 src/Chainweb/PayloadProvider/EVM/ExecutionPayload.hs create mode 100644 src/Chainweb/PayloadProvider/EVM/PayloadDB.hs diff --git a/chainweb.cabal b/chainweb.cabal index 16789d3a79..38ac6cd2ce 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -228,10 +228,12 @@ library , Chainweb.PayloadProvider.EVM , Chainweb.PayloadProvider.EVM.EngineAPI , Chainweb.PayloadProvider.EVM.EthRpcAPI + , Chainweb.PayloadProvider.EVM.ExecutionPayload , Chainweb.PayloadProvider.EVM.Genesis , Chainweb.PayloadProvider.EVM.Header , Chainweb.PayloadProvider.EVM.HeaderDB , Chainweb.PayloadProvider.EVM.JsonRPC + , Chainweb.PayloadProvider.EVM.PayloadDB , Chainweb.PayloadProvider.EVM.Receipt , Chainweb.PayloadProvider.EVM.SPV , Chainweb.PayloadProvider.EVM.Utils diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index cbbe4d198a..f2faa6d0f0 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -51,7 +51,6 @@ module Chainweb.PayloadProvider.EVM -- * Payload Provider API , evmSyncToBlock - ) where import Chainweb.BlockHash qualified as Chainweb @@ -66,8 +65,9 @@ import Chainweb.Parent import Chainweb.PayloadProvider hiding (TransactionIndex) import Chainweb.PayloadProvider.EVM.EngineAPI import Chainweb.PayloadProvider.EVM.EthRpcAPI +import Chainweb.PayloadProvider.EVM.ExecutionPayload import Chainweb.PayloadProvider.EVM.Header qualified as EVM -import Chainweb.PayloadProvider.EVM.HeaderDB qualified as EvmDB +import Chainweb.PayloadProvider.EVM.PayloadDB qualified as EvmDB import Chainweb.PayloadProvider.EVM.JsonRPC (JsonRpcHttpCtx, callMethodHttp) import Chainweb.PayloadProvider.EVM.JsonRPC qualified as RPC import Chainweb.PayloadProvider.EVM.SPV @@ -114,31 +114,21 @@ import P2P.TaskQueue import System.LogLevel -- -------------------------------------------------------------------------- -- --- Types (to keep the code clean and avoid confusion) - --- Might be a usecase for backpack, if we want to go down that route ... --- --- Though doing it directly is fine, too. +-- Payload Database -type Payload = EVM.Header -type PayloadDb tbl = EvmDB.HeaderDb tbl +type PayloadDb tbl = EvmDB.PayloadDb tbl -initPayloadDb :: EvmDB.Configuration -> IO (EvmDB.HeaderDb_ a RocksDbTable) -initPayloadDb = EvmDB.initHeaderDb +initPayloadDb :: EvmDB.Configuration -> IO (EvmDB.PayloadDb_ a RocksDbTable) +initPayloadDb = EvmDB.initPayloadDb payloadDbConfiguration :: HasVersion => HasChainId c => c -> RocksDb - -> Payload + -> EVM.Header -> EvmDB.Configuration -payloadDbConfiguration = EvmDB.configuration - --- Or maybe we could bring into scope the IsPayloadProviderClass? That would --- help with other issues, too. --- --- type Payload = PayloadType EvmPayloadProvider +payloadDbConfiguration c rdb hdr = EvmDB.configuration c rdb hdr -- -------------------------------------------------------------------------- -- -- Configuration @@ -386,9 +376,9 @@ lookupConsensusState p cs plds = do [Nothing, _, _] -> do return Nothing [Just l, Just s, Just f] -> return $ Just ForkchoiceStateV1 - { _forkchoiceHeadBlockHash = EVM._hdrHash l - , _forkchoiceSafeBlockHash = EVM._hdrHash s - , _forkchoiceFinalizedBlockHash = EVM._hdrHash f + { _forkchoiceHeadBlockHash = EVM._hdrHash $ _payloadHeader l + , _forkchoiceSafeBlockHash = EVM._hdrHash $ _payloadHeader s + , _forkchoiceFinalizedBlockHash = EVM._hdrHash $ _payloadHeader f } x -> error $ "corrupted database: " <> sshow x -- FIXME throw proper error @@ -571,7 +561,7 @@ checkExecutionClient -> JsonRpcHttpCtx -> EVM.ChainId -- ^ expected Ethereum Network ID - -> IO Payload + -> IO EVM.Header checkExecutionClient logger c ctx expectedEcid = do ecid <- try @_ @SomeException (callMethodHttp @Eth_ChainId ctx Nothing) >>= \case Left err -> do @@ -745,7 +735,7 @@ forkchoiceUpdate p t fcs attr = go t -- (Note this could possibly also happen for shallow nodes -- that are not yet fully synced.) -- - PayloadStatusV1 Invalid Nothing e -> + e@(PayloadStatusV1 Invalid Nothing _) -> throwM $ UnexpectedForkchoiceUpdatedResponseException e -- Something else when wrong. @@ -795,16 +785,11 @@ newPayload => EvmPayloadProvider logger -> Micros -- ^ Global Timeout in microseconds - -> Payload + -> NewPayloadV4Request + -- ^ The request to the EVM Engine API. -> IO () -newPayload p t pld = go t +newPayload p t request = go t where - request = NewPayloadV4Request - { _newPayloadV4RequestExecutionPayload = error "TODO" - , _newPayloadV4RequestExpectedBlobVersionedHashes = error "TODO" - , _newPayloadV4RequestParentBeaconBlockRoot = error "TODO" - , _newPayloadV4RequestExecutionRequests = error "TODO" - } lf = loggS p "forkchoiceUpdate" waitTime = Micros 500_000 go remaining @@ -889,7 +874,7 @@ newPayload p t pld = go t -- payload hash. -- Right (PayloadStatusV1 Invalid (Just h) e) -> - throwM $ InvalidPayloadException h e + throwM $ InvalidPayloadException (Just h) e Right e -> throwM $ UnexpectedNewPayloadResponseException e @@ -898,7 +883,7 @@ newPayload p t pld = go t T2 cur _ <- stateIO p lf Error $ msg <> ": newPayload succeeded but no payload is produced, because of invalid payload attributes. This is most likely due to a non-increasing timestamp" lf Error $ encodeToText $ object - [ "method" .= "newPayloadV4" + [ "method" .= ("newPayloadV4" :: T.Text) , "request" .= request , "response" .= r , "currentConsensusState" .= cur @@ -933,38 +918,38 @@ updateEvm -- -> IO () updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case - Nothing -> do - lf Info $ "Consensus state lookup returned nothing for" <> briefJson state - return () - Just fcs -> do - lf Info $ "Calling forkChoiceUpdate on state " - <> briefJson state <> " with " <> briefJson fcs - pt <- parentTimestamp - pid <- forkchoiceUpdate p forkchoiceUpdatedTimeout fcs (attr pt) - - -- forkchoiceUpdate throws if it does not succeed. - - -- add new payloads to payload database - lf Info $ "new payloads added to database: " <> sshow (length plds) - mapM_ (uncurry (tableInsert (_evmPayloadStore p))) plds - - -- Update State and Payload Id: - -- There is a race here: If we fail updating the variable - -- the EVM is ahead. That could cause payload updates to - -- fail and possibly stale mining. - lf Info $ "update state and payload ID variable" - lf Debug $ briefJson $ object - [ "newState" .= state - , "newBlockCtx" .= nctx - , "newPayloadId" .= pid - ] - void $ atomically $ do - -- update state - writeTVar (_evmState p) (T2 state nctx) - -- update payloadId - case pid of - Nothing -> void $ tryTakeTMVar (_evmPayloadId p) - Just x -> writeTMVar (_evmPayloadId p) x + Nothing -> do + lf Info $ "Consensus state lookup returned nothing for" <> briefJson state + return () + Just fcs -> do + lf Info $ "Calling forkChoiceUpdate on state " + <> briefJson state <> " with " <> briefJson fcs + pt <- parentTimestamp + pid <- forkchoiceUpdate p forkchoiceUpdatedTimeout fcs (attr pt) + + -- forkchoiceUpdate throws if it does not succeed. + + -- add new payloads to payload database + lf Info $ "new payloads added to database: " <> sshow (length plds) + mapM_ (uncurry (tableInsert (_evmPayloadStore p))) plds + + -- Update State and Payload Id: + -- There is a race here: If we fail updating the variable + -- the EVM is ahead. That could cause payload updates to + -- fail and possibly stale mining. + lf Info $ "update state and payload ID variable" + lf Debug $ briefJson $ object + [ "newState" .= state + , "newBlockCtx" .= nctx + , "newPayloadId" .= pid + ] + void $ atomically $ do + -- update state + writeTVar (_evmState p) (T2 state nctx) + -- update payloadId + case pid of + Nothing -> void $ tryTakeTMVar (_evmPayloadId p) + Just x -> writeTMVar (_evmPayloadId p) x where lf = loggS p "updateEvm" attr pt = mkPayloadAttributes (_latestBlockHash state) pt @@ -976,12 +961,12 @@ updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case -- respect to the evaluation context. parentTimestamp = do let lrh = latestRankedBlockPayloadHash state - hdr <- case lookup lrh plds of + pld <- case lookup lrh plds of Just x -> return x Nothing -> tableLookup p lrh >>= \case Nothing -> error $ "Chainweb.PayloadProvider.EVM.updateEvm: failed to find EVM payload header for target " <> sshow lrh Just x -> return x - return $ EVM._hdrTimestamp hdr + return $ EVM._hdrTimestamp $ _payloadHeader pld mkPayloadAttributes :: Chainweb.BlockHash @@ -1104,7 +1089,6 @@ awaitNewPayload p = do v1 = _executionPayloadV1 v2 h = EVM.numberToHeight $ _executionPayloadV1BlockNumber v1 newEvmBlockHash = _executionPayloadV1BlockHash v1 - reqs = _getjPayloadV4ResponseExecutionRequests resp -- check that this is a new payload atomically (tryReadTMVar (_evmPayloadVar p)) >>= \case @@ -1117,6 +1101,15 @@ awaitNewPayload p = do sstate <- latestStateIO p + -- FIXME is this ok the get the parent from the state? What if + -- the pid and the state are not in sync? Can that happen? + -- Should we store the parent along with the pid + let phdr = _syncStateBlockHash sstate + pheight = _syncStateHeight sstate + pbhdr = EVM.chainwebBlockHashToBeaconBlockRoot phdr + pld = getPayloadV4ResponseToPayload pbhdr resp + pldHdr = _payloadHeader pld + -- get revision number (zero if this is the first payload) let n = maybe 0 (succ . _newPayloadNumber . ssnd) x @@ -1137,15 +1130,13 @@ awaitNewPayload p = do -- , _inconsistentPayloadFees = fees v1 -- } - let pld = executionPayloadV3ToHeader (_syncStateBlockHash sstate) v3 reqs - -- Check that the computed block hash matches the hash from the -- response - unless (newEvmBlockHash == EVM._hdrHash pld) $ do - lf Warn $ brief (_syncStateBlockHash sstate, _syncStateHeight sstate) + unless (newEvmBlockHash == EVM._hdrHash pldHdr) $ do + lf Warn $ "Inconsitent new payload hash for " <> brief (phdr, pheight) throwM $ InconsistenNewPayloadHash (Expected newEvmBlockHash) - (Actual (EVM._hdrHash pld)) + (Actual (EVM._hdrHash pldHdr)) lf Info $ "got new payload " <> briefJson pld @@ -1158,7 +1149,7 @@ awaitNewPayload p = do <$> (_executionPayloadV1Transactions v1) , _newPayloadParentHeight = Parent $ _syncStateHeight sstate , _newPayloadParentHash = Parent $ _syncStateBlockHash sstate - , _newPayloadBlockPayloadHash = EVM._hdrPayloadHash pld + , _newPayloadBlockPayloadHash = EVM._hdrPayloadHash pldHdr , _newPayloadOutputSize = 0 , _newPayloadNumber = n , _newPayloadFees = fees v1 @@ -1167,44 +1158,6 @@ awaitNewPayload p = do , _newPayloadChainId = cid } -executionPayloadV3ToHeader - :: Chainweb.BlockHash - -> ExecutionPayloadV3 - -> [Utils.ExecutionRequest] - -> Payload -executionPayloadV3ToHeader phdr v3 reqs = hdr - { EVM._hdrHash = EVM.computeBlockHash hdr - , EVM._hdrPayloadHash = EVM.computeBlockPayloadHash hdr - } - where - v2 = _executionPayloadV2 v3 - v1 = _executionPayloadV1 v2 - hdr = EVM.Header - { _hdrParentHash = _executionPayloadV1ParentHash v1 - , _hdrOmmersHash = EVM.ommersHash - , _hdrBeneficiary = _executionPayloadV1FeeRecipient v1 - , _hdrStateRoot = _executionPayloadV1StateRoot v1 - , _hdrTransactionsRoot = transactionsRoot (_executionPayloadV1Transactions v1) - , _hdrReceiptsRoot = _executionPayloadV1ReceiptsRoot v1 - , _hdrLogsBloom = _executionPayloadV1LogsBloom v1 - , _hdrDifficulty = EVM.difficulty - , _hdrNumber = _executionPayloadV1BlockNumber v1 - , _hdrGasLimit = _executionPayloadV1GasLimit v1 - , _hdrGasUsed = _executionPayloadV1GasUsed v1 - , _hdrTimestamp = _executionPayloadV1Timestamp v1 - , _hdrExtraData = _executionPayloadV1ExtraData v1 - , _hdrPrevRandao = _executionPayloadV1PrevRandao v1 - , _hdrNonce = EVM.nonce - , _hdrBaseFeePerGas = _executionPayloadV1BaseFeePerGas v1 - , _hdrWithdrawalsRoot = withdrawlsRoot (_executionPayloadV2Withdrawals v2) - , _hdrBlobGasUsed = _executionPayloadV3BlobGasUsed v3 - , _hdrExcessBlobGas = _executionPayloadV3ExcessBlobGas v3 - , _hdrParentBeaconBlockRoot = EVM.chainwebBlockHashToBeaconBlockRoot phdr - , _hdrRequestsHash = EVM.requestsHash reqs - , _hdrHash = error "Chainweb.PayloadProvider.EVM.executionPayloadV3ToHeader: _hdrHash" - , _hdrPayloadHash = error "Chainweb.PayloadProvider.executionPayloadV3ToHeader: _hdrPayloadHash" - } - -- -------------------------------------------------------------------------- -- -- Sync To Block @@ -1439,17 +1392,52 @@ getPayloadForContext p h ctx = do lf :: LogFunctionText lf = loggS p "getPayloadForContext" +newPayloadTimeout :: Micros +newPayloadTimeout = 30_000_000 + -- | FIXME: -- -- Do the actual validation. In particular, validate the whole chain of payloads -- from the context in one go. -- validatePayload - :: EvmPayloadProvider logger + :: Logger logger + => EvmPayloadProvider logger -> Payload -> EvaluationCtx ConsensusPayload -> IO () -validatePayload p pld ctx = return () +validatePayload p pld ctx = do + + -- FIXME: this requires that we have the full evaluation context available, + -- which includes all transactions. This is potentially expensive. + -- + -- Announce the payload to the EVM via new Payload. This does some EVM + -- specific validation. + -- + -- If the new block extends the canonical chain and all requisites are + -- available the EVM will fully validate the payload. + -- + -- If the call returns and the block is *not* fully validated it is accepted + -- by the EVM which guarantees that the payload header is valid and the + -- payload is syntactically correct. + -- + -- The forkchoice state is *NOT* updated. + -- + -- This call may take a some time (the timeout is 8 seconds) + -- + -- FIXME: It is not clear if this is the best place to make this call. + -- FXIME: It may make sense to issue this call asynchronously -- but it + -- would complicate the overall validation logic. So, we keep that as future + -- work. + -- + -- For now this throws if we get a timeout or any other error. + -- + _ <- case payloadToNewPayloadV4Request pld of + Nothing -> return () + Just epld -> do + newPayload p newPayloadTimeout epld + + return () -- -------------------------------------------------------------------------- -- -- Payload Provider API Instance @@ -1581,7 +1569,7 @@ getHashByNumber ctx p = fmap EVM._hdrHash <$> getBlockByHash :: EvmPayloadProvider pdf -> EVM.BlockHash - -> IO Payload + -> IO EVM.Header getBlockByHash p h = do r <- RPC.callMethodHttp @Eth_GetBlockByHash (_evmEngineCtx p) (h, False) @@ -1603,7 +1591,7 @@ getBlockByHash p h = do getBlockAtNumber :: EvmPayloadProvider pdf -> EVM.BlockNumber - -> IO Payload + -> IO EVM.Header getBlockAtNumber p n = do r <- RPC.callMethodHttp @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockNumber n, False) @@ -1623,7 +1611,7 @@ getBlockAtNumber p n = do -- getGenesisHeader :: EvmPayloadProvider pdf - -> IO Payload + -> IO EVM.Header getGenesisHeader p = try (getBlockAtNumber p 0) >>= \case Left (EvmHeaderNotFoundByNumber _) -> throwM EvmGenesisHeaderNotFound Left e -> throwM e @@ -1647,7 +1635,7 @@ getGenesisHeader p = try (getBlockAtNumber p 0) >>= \case getLatestHdr :: EvmPayloadProvider pdb -> ConsensusState - -> IO Payload + -> IO EVM.Header getLatestHdr p s = do hdr <- getBlockAtNumber p (_latestNumber s) unless (view EVM.hdrPayloadHash hdr == _latestPayloadHash s) $ @@ -1671,7 +1659,7 @@ getLatestHdr p s = do getSafeHdr :: EvmPayloadProvider pdb -> ConsensusState - -> IO Payload + -> IO EVM.Header getSafeHdr p s = do hdr <- getBlockAtNumber p (_safeNumber s) unless (view EVM.hdrPayloadHash hdr == _safePayloadHash s) $ @@ -1695,7 +1683,7 @@ getSafeHdr p s = do getFinalHdr :: EvmPayloadProvider pdb -> ConsensusState - -> IO Payload + -> IO EVM.Header getFinalHdr p s = do hdr <- getBlockAtNumber p (_finalNumber s) unless (view EVM.hdrPayloadHash hdr == _finalPayloadHash s) $ diff --git a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs index 7a4cc4839e..7c317e8f26 100644 --- a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs +++ b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs @@ -96,20 +96,20 @@ module Chainweb.PayloadProvider.EVM.EngineAPI , ExecutionPayloadV1(..) , ExecutionPayloadV2(..) , ExecutionPayloadV3(..) +, NewPayloadV4Request(..) -- * Get Payload Response , Blob(..) , KzgProof(..) , KzgCommitment(..) , BlobsBundleV1(..) +, VersionedHash(..) +, versionedHashes , BlockValue(..) , GetPayloadV2Response(..) , GetPayloadV3Response(..) , GetPayloadV4Response(..) --- * New Paylaod Request -, NewPayloadV4Request(..) - -- * Authentication and Client Context , JwtSecret(..) , jwtToken @@ -153,6 +153,8 @@ import Network.HTTP.Client qualified as HTTP import Network.URI import Network.URI.Static (uri) import System.IO.Unsafe (unsafePerformIO) +import Data.Hash.SHA2 +import Data.Coerce -- -------------------------------------------------------------------------- -- -- Forkchoice State V1 @@ -363,6 +365,9 @@ type FIELD_ELEMENTS_PER_BLOB = 4096 type BLOB_SIZE :: Natural type BLOB_SIZE = FIELD_ELEMENTS_PER_BLOB GHC.TypeLits.* BYTES_PER_FIELD_ELEMENT +versionedHashVersionKzg :: Word8 +versionedHashVersionKzg = 0x01 + -- | EIP-4844 Versioned Hash -- newtype VersionedHash = VersionedHash { _versionedHash :: E.BytesN 32 } @@ -442,13 +447,28 @@ instance FromJSON BlobsBundleV1 where <*> o .: "blobs" {-# INLINE parseJSON #-} +-- | Compute versioned hash for a blob +-- +-- cf. https://eips.ethereum.org/EIPS/eip-4844#helpers +-- +-- VERSIONED_HASH_VERSION_KZG + sha256(commitment)[1:] +-- +versionedHash :: KzgCommitment -> VersionedHash +versionedHash (KzgCommitment c) = VersionedHash + $ E.unsafeBytesN @32 + $ BS.singleton versionedHashVersionKzg + <> BS.drop 1 (coerce $ hashShortByteString_ @Sha2_256 $ E._getBytesN c) + +versionedHashes :: BlobsBundleV1 -> [VersionedHash] +versionedHashes (BlobsBundleV1 cs _ _) = map versionedHash cs + -- -------------------------------------------------------------------------- -- -- Execution Payload V1 newtype TransactionBytes = TransactionBytes { _transactionBytes :: BS.ShortByteString } deriving (Show, Eq, Ord, Generic) - deriving newtype (Hashable, E.Bytes) + deriving newtype (Hashable, E.Bytes, RLP) instance ToJSON TransactionBytes where toEncoding (TransactionBytes a) = toEncoding (HexBytes a) @@ -660,6 +680,64 @@ instance FromJSON ExecutionPayloadV3 where <*> o .: "blobGasUsed" <*> o .: "excessBlobGas" +-- -------------------------------------------------------------------------- -- +-- Full Execution Payload + +-- | This is the "full" execution payload that is used in the engine_newPayloadV4 +-- requests. +-- +-- Method parameter list is extended with executionRequests. +-- +-- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#request +-- +data NewPayloadV4Request = NewPayloadV4Request + { _newPayloadV4RequestExecutionPayloadV3 :: !ExecutionPayloadV3 + , _newPayloadV4RequestExpectedBlobVersionedHashes :: ![VersionedHash] + -- ^ Array of DATA, 32 Bytes - Array of expected blob versioned hashes + -- to validate. + , _newPayloadV4RequestParentBeaconBlockRoot :: !ParentBeaconBlockRoot + -- ^ DATA, 32 Bytes - Root of the parent beacon block. + , _newPayloadV4RequestRequests :: ![ExecutionRequest] + -- ^ Array of DATA - List of execution layer triggered requests. Each + -- list element is a requests byte array as defined by EIP-7685. The + -- first byte of each element is the request_type and the remaining + -- bytes are the request_data. Elements of the list MUST be ordered by + -- request_type in ascending order. Elements with empty request_data + -- MUST be excluded from the list. If the list has no elements, the + -- expected array MUST be []. If any element is out of order, has a + -- length of 1-byte or shorter, or more than one element has the same + -- type byte, or the param is null, client software MUST return -32602: + -- Invalid params error. + } + deriving (Eq, Show, Generic) + +instance ToJSON NewPayloadV4Request where + toEncoding o = toEncoding + ( _newPayloadV4RequestExecutionPayloadV3 o + , _newPayloadV4RequestExpectedBlobVersionedHashes o + , _newPayloadV4RequestParentBeaconBlockRoot o + , _newPayloadV4RequestRequests o + ) + toJSON o = toJSON + ( _newPayloadV4RequestExecutionPayloadV3 o + , _newPayloadV4RequestExpectedBlobVersionedHashes o + , _newPayloadV4RequestParentBeaconBlockRoot o + , _newPayloadV4RequestRequests o + ) + {-# INLINE toEncoding #-} + {-# INLINE toJSON #-} + +instance FromJSON NewPayloadV4Request where + parseJSON = withArray "NewPayloadV4Request" $ \v -> do + case toList v of + [a, b, c, d] -> NewPayloadV4Request + <$> parseJSON a + <*> parseJSON b + <*> parseJSON c + <*> parseJSON d + l -> fail $ "invalid NewPayloadV4Request: expected 4 parameters but got " <> show (length l) + {-# INLINE parseJSON #-} + -- -------------------------------------------------------------------------- -- -- Payload Attributes V1 @@ -1183,7 +1261,7 @@ data GetPayloadV4Response = GetPayloadV4Response , _getPayloadV4ResponseShouldOverrideBuilder :: !Bool -- ^ shouldOverrideBuilder : BOOLEAN - Suggestion from the execution layer -- to use this executionPayload instead of an externally provided one - , _getjPayloadV4ResponseExecutionRequests :: ![ExecutionRequest] + , _getPayloadV4ResponseExecutionRequests :: ![ExecutionRequest] -- ^ executionRequests: Array of DATA - Execution layer triggered requests -- obtained from the executionPayload transaction execution. } @@ -1198,7 +1276,7 @@ getPayloadV4ResponseProperties o = , "blockValue" .= _getPayloadV4ResponseBlockValue o , "blobsBundle" .= _getPayloadV4ResponseBlobsBundle o , "shouldOverrideBuilder" .= _getPayloadV4ResponseShouldOverrideBuilder o - , "executionRequests" .= _getjPayloadV4ResponseExecutionRequests o + , "executionRequests" .= _getPayloadV4ResponseExecutionRequests o ] instance ToJSON GetPayloadV4Response where @@ -1257,58 +1335,6 @@ instance JsonRpcMethod "engine_newPayloadV4" where , ApplicationError UnsupportedFork ] --- | Engine New Payload V4 Request --- --- Method parameter list is extended with executionRequests. --- --- cf. https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#request --- -data NewPayloadV4Request = NewPayloadV4Request - { _newPayloadV4RequestExecutionPayload :: !ExecutionPayloadV3 - , _newPayloadV4RequestExpectedBlobVersionedHashes :: ![VersionedHash] - -- ^ Array of DATA, 32 Bytes - Array of expected blob versioned hashes - -- to validate. - , _newPayloadV4RequestParentBeaconBlockRoot :: !ParentBeaconBlockRoot - -- ^ DATA, 32 Bytes - Root of the parent beacon block. - , _newPayloadV4RequestExecutionRequests :: ![ExecutionRequest] - -- ^ Array of DATA - List of execution layer triggered requests. Each - -- list element is a requests byte array as defined by EIP-7685. The - -- first byte of each element is the request_type and the remaining - -- bytes are the request_data. Elements of the list MUST be ordered by - -- request_type in ascending order. Elements with empty request_data - -- MUST be excluded from the list. If the list has no elements, the - -- expected array MUST be []. If any element is out of order, has a - -- length of 1-byte or shorter, or more than one element has the same - -- type byte, or the param is null, client software MUST return -32602: - -- Invalid params error. - } - deriving (Show, Eq, Generic) - -newPayloadV4RequestProperties - :: KeyValue e kv - => NewPayloadV4Request - -> [kv] -newPayloadV4RequestProperties o = - [ "executionPayload" .= _newPayloadV4RequestExecutionPayload o - , "expectedBlobVersionedHashes" .= _newPayloadV4RequestExpectedBlobVersionedHashes o - , "parentPeaconBlockRoot" .= _newPayloadV4RequestParentBeaconBlockRoot o - , "executionRequests" .= _newPayloadV4RequestExecutionRequests o - ] - -instance ToJSON NewPayloadV4Request where - toEncoding = pairs . mconcat . newPayloadV4RequestProperties - toJSON = object . newPayloadV4RequestProperties - {-# INLINE toEncoding #-} - {-# INLINE toJSON #-} - -instance FromJSON NewPayloadV4Request where - parseJSON = withObject "NewPayloadV4Request" $ \o -> NewPayloadV4Request - <$> o .: "executionPayload" - <*> o .: "expectedBlobVersionedHashes" - <*> o .: "parentBeaconBlockRoot" - <*> o .: "executionRequests" - {-# INLINE parseJSON #-} - -- -------------------------------------------------------------------------- -- -- Authentication diff --git a/src/Chainweb/PayloadProvider/EVM/ExecutionPayload.hs b/src/Chainweb/PayloadProvider/EVM/ExecutionPayload.hs new file mode 100644 index 0000000000..6ae066985d --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/ExecutionPayload.hs @@ -0,0 +1,578 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE LambdaCase #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.ExecutionPayload +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.PayloadProvider.EVM.ExecutionPayload +( ExecutionPayloadData(..) +, executionPayloadDataTransactions +, executionPayloadDataWithdrawals +, executionPayloadDataExpectedBlobVersionedHashes +, executionPayloadDataRequests +, Payload(..) +, payloadHeader +, payloadData +, payloadToNewPayloadV4Request +, getPayloadV4ResponseToPayload +, executionPayloadV3ToHeader + +-- * Getters +, _pldParentHash +, pldParentHash +, _pldOmmersHash +, pldOmmersHash +, _pldBeneficiary +, pldBeneficiary +, _pldStateRoot +, pldStateRoot +, _pldTransactionsRoot +, pldTransactionsRoot +, _pldReceiptsRoot +, pldReceiptsRoot +, _pldLogsBloom +, pldLogsBloom +, _pldDifficulty +, pldDifficulty +, _pldNumber +, pldNumber +, _pldHeight +, pldHeight +, _pldGasLimit +, pldGasLimit +, _pldGasUsed +, pldGasUsed +, _pldTimestamp +, pldTimestamp +, _pldExtraData +, pldExtraData +, _pldPrevRandao +, pldPrevRandao +, _pldNonce +, pldNonce +, _pldBaseFeePerGas +, pldBaseFeePerGas +, _pldWithdrawalsRoot +, pldWithdrawalsRoot +, _pldBlobGasUsed +, pldBlobGasUsed +, _pldExcessBlobGas +, pldExcessBlobGas +, _pldParentBeaconBlockRoot +, pldParentBeaconBlockRoot +, _pldRequestsHash +, pldRequestsHash +, _pldHash +, pldHash +, _pldPayloadHash +, pldPayloadHash +, _pldTransactions +, pldTransactions +, _pldWithdrawals +, pldWithdrawals +, _pldExpectedBlobVersionedHashes +, pldExpectedBlobVersionedHashes +, _pldRequests +, pldRequests +) where + +import Chainweb.PayloadProvider.EVM.EngineAPI +import Chainweb.BlockHeight as Chainweb +import Chainweb.BlockPayloadHash +import Chainweb.PayloadProvider.EVM.Header qualified as EVM +import Chainweb.PayloadProvider.EVM.Utils qualified as EVM +import Chainweb.PayloadProvider.EVM.Utils qualified as Utils +import Control.Lens hiding ((.=)) +import Data.Aeson +import Data.Function (on) +import Data.Hashable +import Data.Word +import Ethereum.Misc +import Ethereum.RLP +import GHC.Generics (Generic) + +-- -------------------------------------------------------------------------- -- +-- | Execution Payload Data +-- +-- This data structure contains the execution payload data as it is not already +-- included in the EVM Header. Together with the EVM Header this provides all +-- information needed to make a new payload request to the EVM Engine API. +-- +-- The executiojn payload equals the execution payload header except for the +-- transactions and withdrawals fields, for which the header only contains the +-- respective Merkle roots. Internally we store +-- +-- Deneb Execution Payload: +-- +-- cf. https://github.com/ethereum/consensus-specs/blob/dev/specs/deneb/beacon-chain.md#executionpayload +-- +-- We also include the fields of the newPayloadV4 engine API call from the +-- Pectra (Prague/Electra) fork. +-- +-- cf. https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#modified-newpayloadrequest +-- +data ExecutionPayloadData = ExecutionPayloadData + { _executionPayloadDataTransactions :: ![TransactionBytes] + -- ^ transactions: Array of DATA - Array of transaction objects, each + -- object is a byte list (DATA) representing TransactionType || + -- TransactionPayload or LegacyTransaction as defined in EIP-2718 + , _executionPayloadDataWithdrawals :: ![WithdrawalV1] + -- ^ withdrawals: Array of WithdrawalV1 - Array of withdrawals, each + -- object is an OBJECT containing the fields of a WithdrawalV1 structure. + , _executionPayloadDataExpectedBlobVersionedHashes :: ![VersionedHash] + -- ^ Array of DATA, 32 Bytes - Array of expected blob versioned hashes + -- to validate. + -- + -- The actual blob data is not part of the execution paylaod data + -- (Unlike transactions and withdrawals). + , _executionPayloadDataRequests :: ![EVM.ExecutionRequest] + -- ^ Array of DATA - List of execution layer triggered requests. Each + -- list element is a requests byte array as defined by EIP-7685. The + -- first byte of each element is the request_type and the remaining + -- bytes are the request_data. Elements of the list MUST be ordered by + -- request_type in ascending order. Elements with empty request_data + -- MUST be excluded from the list. If the list has no elements, the + -- expected array MUST be []. If any element is out of order, has a + -- length of 1-byte or shorter, or more than one element has the same + -- type byte, or the param is null, client software MUST return -32602: + -- Invalid params error. + -- + -- This field is new in the Pectra (Prague/Electra) fork. + } + deriving (Show, Eq, Generic) + +makeLenses ''ExecutionPayloadData + +instance RLP ExecutionPayloadData where + putRlp o = putRlpL + [ putRlp $ _executionPayloadDataTransactions o + , putRlp $ _executionPayloadDataWithdrawals o + , putRlp $ _executionPayloadDataExpectedBlobVersionedHashes o + , putRlp $ _executionPayloadDataRequests o + ] + getRlp = label "ExcutionPayloadData" $ getRlpL $ + ExecutionPayloadData + <$> getRlp -- transactions + <*> getRlp -- withdrawals + <*> getRlp -- expectedBlobVersionedHashes + <*> getRlp -- requests + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +executionPayloadDataProperties + :: KeyValue e kv + => ExecutionPayloadData + -> [kv] +executionPayloadDataProperties o = + [ "transactions" .= _executionPayloadDataTransactions o + , "withdrawals" .= _executionPayloadDataWithdrawals o + , "expectedBlobVersionedHashes" .= _executionPayloadDataExpectedBlobVersionedHashes o + , "executionRequests" .= _executionPayloadDataRequests o + ] + +instance ToJSON ExecutionPayloadData where + toEncoding = pairs . mconcat . executionPayloadDataProperties + toJSON = object . executionPayloadDataProperties + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance FromJSON ExecutionPayloadData where + parseJSON = withObject "ExecutionPayloadData" $ \o -> ExecutionPayloadData + <$> o .: "transactions" + <*> o .: "withdrawals" + <*> o .: "expectedBlobVersionedHashes" + <*> o .: "executionRequests" + {-# INLINE parseJSON #-} + +-- -------------------------------------------------------------------------- -- +-- Payload + +data Payload = Payload + { _payloadHeader :: !EVM.Header + , _payloadData :: !(Maybe ExecutionPayloadData) + } + deriving (Show, Generic) + +makeLenses ''Payload + +instance Eq Payload where + (==) = (==) `on` _payloadHeader + +instance Ord Payload where + compare = compare `on` _payloadHeader + +instance Hashable Payload where + hashWithSalt s = hashWithSalt s . _payloadHeader + +instance RLP Payload where + putRlp (Payload hdr Nothing) = putRlpL + [ putRlp @Word8 0 + , putRlp hdr + ] + putRlp (Payload hdr (Just pld)) = putRlpL + [ putRlp @Word8 1 + , putRlp hdr + , putRlp pld + ] + getRlp = label "Payload" $ getRlpL $ do + getRlp @Word8 >>= \case + 0 -> Payload + <$> getRlp -- header + <*> pure Nothing + 1 -> Payload + <$> getRlp -- header + <*> (Just <$> getRlp) -- data + _ -> fail "Chainweb.PayloadProvider.EVM.ExecutionPayload.getRlp: invalid payload type" + {-# INLINE putRlp #-} + {-# INLINE getRlp #-} + +payloadProperties + :: KeyValue e kv + => Payload + -> [kv] +payloadProperties o = + [ "header" .= _payloadHeader o + , "data" .= _payloadData o + ] + +instance ToJSON Payload where + toEncoding = pairs . mconcat . payloadProperties + toJSON = object . payloadProperties + {-# INLINE toJSON #-} + {-# INLINE toEncoding #-} + +instance FromJSON Payload where + parseJSON = withObject "Payload" $ \o -> Payload + <$> o .: "header" + <*> o .:? "data" + {-# INLINE parseJSON #-} + +payloadToNewPayloadV4Request + :: Payload + -> Maybe NewPayloadV4Request +payloadToNewPayloadV4Request (Payload _ Nothing) = Nothing +payloadToNewPayloadV4Request (Payload hdr (Just d)) = Just $ NewPayloadV4Request + { _newPayloadV4RequestExecutionPayloadV3 = ExecutionPayloadV3 + { _executionPayloadV2 = ExecutionPayloadV2 + { _executionPayloadV1 = ExecutionPayloadV1 + { _executionPayloadV1ParentHash = EVM._hdrParentHash hdr + , _executionPayloadV1FeeRecipient = EVM._hdrBeneficiary hdr + , _executionPayloadV1StateRoot = EVM._hdrStateRoot hdr + , _executionPayloadV1ReceiptsRoot = EVM._hdrReceiptsRoot hdr + , _executionPayloadV1LogsBloom = EVM._hdrLogsBloom hdr + , _executionPayloadV1BlockNumber = EVM._hdrNumber hdr + , _executionPayloadV1GasLimit = EVM._hdrGasLimit hdr + , _executionPayloadV1GasUsed = EVM._hdrGasUsed hdr + , _executionPayloadV1Timestamp = EVM._hdrTimestamp hdr + , _executionPayloadV1ExtraData = EVM._hdrExtraData hdr + , _executionPayloadV1PrevRandao = EVM._hdrPrevRandao hdr + , _executionPayloadV1BaseFeePerGas = EVM._hdrBaseFeePerGas hdr + , _executionPayloadV1BlockHash = EVM._hdrHash hdr + , _executionPayloadV1Transactions = _executionPayloadDataTransactions d + } + , _executionPayloadV2Withdrawals = _executionPayloadDataWithdrawals d + } + , _executionPayloadV3BlobGasUsed = EVM._hdrBlobGasUsed hdr + , _executionPayloadV3ExcessBlobGas = EVM._hdrExcessBlobGas hdr + } + , _newPayloadV4RequestExpectedBlobVersionedHashes + = _executionPayloadDataExpectedBlobVersionedHashes d + , _newPayloadV4RequestParentBeaconBlockRoot = + EVM._hdrParentBeaconBlockRoot hdr + , _newPayloadV4RequestRequests = _executionPayloadDataRequests d + } + +getPayloadV4ResponseToPayload + :: EVM.ParentBeaconBlockRoot + -> GetPayloadV4Response + -> Payload +getPayloadV4ResponseToPayload pbh resp = Payload + { _payloadHeader = executionPayloadV3ToHeader pbh + (_getPayloadV4ResponseExecutionPayload resp) + (_getPayloadV4ResponseExecutionRequests resp) + , _payloadData = Just $ ExecutionPayloadData + { _executionPayloadDataTransactions = _executionPayloadV1Transactions v1 + , _executionPayloadDataWithdrawals = _executionPayloadV2Withdrawals v2 + , _executionPayloadDataExpectedBlobVersionedHashes = + versionedHashes (_getPayloadV4ResponseBlobsBundle resp) + , _executionPayloadDataRequests = _getPayloadV4ResponseExecutionRequests resp + } + } + where + v1 = _executionPayloadV1 v2 + v2 = _executionPayloadV2 v3 + v3 = _getPayloadV4ResponseExecutionPayload resp + +executionPayloadV3ToHeader + :: EVM.ParentBeaconBlockRoot + -> ExecutionPayloadV3 + -> [Utils.ExecutionRequest] + -> EVM.Header +executionPayloadV3ToHeader phdr v3 reqs = hdr + { EVM._hdrHash = EVM.computeBlockHash hdr + , EVM._hdrPayloadHash = EVM.computeBlockPayloadHash hdr + } + where + v2 = _executionPayloadV2 v3 + v1 = _executionPayloadV1 v2 + hdr = EVM.Header + { EVM._hdrParentHash = _executionPayloadV1ParentHash v1 + , EVM._hdrOmmersHash = EVM.ommersHash + , EVM._hdrBeneficiary = _executionPayloadV1FeeRecipient v1 + , EVM._hdrStateRoot = _executionPayloadV1StateRoot v1 + , EVM._hdrTransactionsRoot = transactionsRoot (_executionPayloadV1Transactions v1) + , EVM._hdrReceiptsRoot = _executionPayloadV1ReceiptsRoot v1 + , EVM._hdrLogsBloom = _executionPayloadV1LogsBloom v1 + , EVM._hdrDifficulty = EVM.difficulty + , EVM._hdrNumber = _executionPayloadV1BlockNumber v1 + , EVM._hdrGasLimit = _executionPayloadV1GasLimit v1 + , EVM._hdrGasUsed = _executionPayloadV1GasUsed v1 + , EVM._hdrTimestamp = _executionPayloadV1Timestamp v1 + , EVM._hdrExtraData = _executionPayloadV1ExtraData v1 + , EVM._hdrPrevRandao = _executionPayloadV1PrevRandao v1 + , EVM._hdrNonce = EVM.nonce + , EVM._hdrBaseFeePerGas = _executionPayloadV1BaseFeePerGas v1 + , EVM._hdrWithdrawalsRoot = withdrawlsRoot (_executionPayloadV2Withdrawals v2) + , EVM._hdrBlobGasUsed = _executionPayloadV3BlobGasUsed v3 + , EVM._hdrExcessBlobGas = _executionPayloadV3ExcessBlobGas v3 + , EVM._hdrParentBeaconBlockRoot = phdr + , EVM._hdrRequestsHash = EVM.requestsHash reqs + , EVM._hdrHash = error "Chainweb.PayloadProvider.EVM.executionPayloadV3ToHeader: _hdrHash" + , EVM._hdrPayloadHash = error "Chainweb.PayloadProvider.executionPayloadV3ToHeader: _hdrPayloadHash" + } + +-- -------------------------------------------------------------------------- -- +-- Getters + +_pldParentHash :: Payload -> ParentHash +_pldParentHash = EVM._hdrParentHash . _payloadHeader +{-# INLINE _pldParentHash #-} + +pldParentHash :: Getter Payload ParentHash +pldParentHash = payloadHeader . EVM.hdrParentHash +{-# INLINE pldParentHash #-} + +_pldOmmersHash :: Payload -> OmmersHash +_pldOmmersHash = EVM._hdrOmmersHash . _payloadHeader +{-# INLINE _pldOmmersHash #-} + +pldOmmersHash :: Getter Payload OmmersHash +pldOmmersHash = payloadHeader . EVM.hdrOmmersHash +{-# INLINE pldOmmersHash #-} + +_pldBeneficiary :: Payload -> Beneficiary +_pldBeneficiary = EVM._hdrBeneficiary . _payloadHeader +{-# INLINE _pldBeneficiary #-} + +pldBeneficiary :: Getter Payload Beneficiary +pldBeneficiary = payloadHeader . EVM.hdrBeneficiary +{-# INLINE pldBeneficiary #-} + +_pldStateRoot :: Payload -> StateRoot +_pldStateRoot = EVM._hdrStateRoot . _payloadHeader +{-# INLINE _pldStateRoot #-} + +pldStateRoot :: Getter Payload StateRoot +pldStateRoot = payloadHeader . EVM.hdrStateRoot +{-# INLINE pldStateRoot #-} + +_pldTransactionsRoot :: Payload -> TransactionsRoot +_pldTransactionsRoot = EVM._hdrTransactionsRoot . _payloadHeader +{-# INLINE _pldTransactionsRoot #-} + +pldTransactionsRoot :: Getter Payload TransactionsRoot +pldTransactionsRoot = payloadHeader . EVM.hdrTransactionsRoot +{-# INLINE pldTransactionsRoot #-} + +_pldReceiptsRoot :: Payload -> ReceiptsRoot +_pldReceiptsRoot = EVM._hdrReceiptsRoot . _payloadHeader +{-# INLINE _pldReceiptsRoot #-} + +pldReceiptsRoot :: Getter Payload ReceiptsRoot +pldReceiptsRoot = payloadHeader . EVM.hdrReceiptsRoot +{-# INLINE pldReceiptsRoot #-} + +_pldLogsBloom :: Payload -> Bloom +_pldLogsBloom = EVM._hdrLogsBloom . _payloadHeader +{-# INLINE _pldLogsBloom #-} + +pldLogsBloom :: Getter Payload Bloom +pldLogsBloom = payloadHeader . EVM.hdrLogsBloom +{-# INLINE pldLogsBloom #-} + +_pldDifficulty :: Payload -> Difficulty +_pldDifficulty = EVM._hdrDifficulty . _payloadHeader +{-# INLINE _pldDifficulty #-} + +pldDifficulty :: Getter Payload Difficulty +pldDifficulty = payloadHeader . EVM.hdrDifficulty +{-# INLINE pldDifficulty #-} + +_pldNumber :: Payload -> BlockNumber +_pldNumber = EVM._hdrNumber . _payloadHeader +{-# INLINE _pldNumber #-} + +pldNumber :: Getter Payload BlockNumber +pldNumber = payloadHeader . EVM.hdrNumber +{-# INLINE pldNumber #-} + +_pldHeight :: Payload -> Chainweb.BlockHeight +_pldHeight = EVM._hdrHeight . _payloadHeader +{-# INLINE _pldHeight #-} + +pldHeight :: Getter Payload Chainweb.BlockHeight +pldHeight = payloadHeader . EVM.hdrHeight +{-# INLINE pldHeight #-} + +_pldGasLimit :: Payload -> GasLimit +_pldGasLimit = EVM._hdrGasLimit . _payloadHeader +{-# INLINE _pldGasLimit #-} + +pldGasLimit :: Getter Payload GasLimit +pldGasLimit = payloadHeader . EVM.hdrGasLimit +{-# INLINE pldGasLimit #-} + +_pldGasUsed :: Payload -> GasUsed +_pldGasUsed = EVM._hdrGasUsed . _payloadHeader +{-# INLINE _pldGasUsed #-} + +pldGasUsed :: Getter Payload GasUsed +pldGasUsed = payloadHeader . EVM.hdrGasUsed +{-# INLINE pldGasUsed #-} + +_pldTimestamp :: Payload -> Timestamp +_pldTimestamp = EVM._hdrTimestamp . _payloadHeader +{-# INLINE _pldTimestamp #-} + +pldTimestamp :: Getter Payload Timestamp +pldTimestamp = payloadHeader . EVM.hdrTimestamp +{-# INLINE pldTimestamp #-} + +_pldExtraData :: Payload -> ExtraData +_pldExtraData = EVM._hdrExtraData . _payloadHeader +{-# INLINE _pldExtraData #-} + +pldExtraData :: Getter Payload ExtraData +pldExtraData = payloadHeader . EVM.hdrExtraData +{-# INLINE pldExtraData #-} + +_pldPrevRandao :: Payload -> EVM.Randao +_pldPrevRandao = EVM._hdrPrevRandao . _payloadHeader +{-# INLINE _pldPrevRandao #-} + +pldPrevRandao :: Getter Payload EVM.Randao +pldPrevRandao = payloadHeader . EVM.hdrPrevRandao +{-# INLINE pldPrevRandao #-} + +_pldNonce :: Payload -> Nonce +_pldNonce = EVM._hdrNonce . _payloadHeader +{-# INLINE _pldNonce #-} + +pldNonce :: Getter Payload Nonce +pldNonce = payloadHeader . EVM.hdrNonce +{-# INLINE pldNonce #-} + +_pldBaseFeePerGas :: Payload -> EVM.BaseFeePerGas +_pldBaseFeePerGas = EVM._hdrBaseFeePerGas . _payloadHeader +{-# INLINE _pldBaseFeePerGas #-} + +pldBaseFeePerGas :: Getter Payload EVM.BaseFeePerGas +pldBaseFeePerGas = payloadHeader . EVM.hdrBaseFeePerGas +{-# INLINE pldBaseFeePerGas #-} + +_pldWithdrawalsRoot :: Payload -> EVM.WithdrawalsRoot +_pldWithdrawalsRoot = EVM._hdrWithdrawalsRoot . _payloadHeader +{-# INLINE _pldWithdrawalsRoot #-} + +pldWithdrawalsRoot :: Getter Payload EVM.WithdrawalsRoot +pldWithdrawalsRoot = payloadHeader . EVM.hdrWithdrawalsRoot +{-# INLINE pldWithdrawalsRoot #-} + +_pldBlobGasUsed :: Payload -> EVM.BlobGasUsed +_pldBlobGasUsed = EVM._hdrBlobGasUsed . _payloadHeader +{-# INLINE _pldBlobGasUsed #-} + +pldBlobGasUsed :: Getter Payload EVM.BlobGasUsed +pldBlobGasUsed = payloadHeader . EVM.hdrBlobGasUsed +{-# INLINE pldBlobGasUsed #-} + +_pldExcessBlobGas :: Payload -> EVM.ExcessBlobGas +_pldExcessBlobGas = EVM._hdrExcessBlobGas . _payloadHeader +{-# INLINE _pldExcessBlobGas #-} + +pldExcessBlobGas :: Getter Payload EVM.ExcessBlobGas +pldExcessBlobGas = payloadHeader . EVM.hdrExcessBlobGas +{-# INLINE pldExcessBlobGas #-} + +_pldParentBeaconBlockRoot :: Payload -> EVM.ParentBeaconBlockRoot +_pldParentBeaconBlockRoot = EVM._hdrParentBeaconBlockRoot . _payloadHeader +{-# INLINE _pldParentBeaconBlockRoot #-} + +pldParentBeaconBlockRoot :: Getter Payload EVM.ParentBeaconBlockRoot +pldParentBeaconBlockRoot = payloadHeader . EVM.hdrParentBeaconBlockRoot +{-# INLINE pldParentBeaconBlockRoot #-} + +_pldRequestsHash :: Payload -> EVM.RequestsHash +_pldRequestsHash = EVM._hdrRequestsHash . _payloadHeader +{-# INLINE _pldRequestsHash #-} + +pldRequestsHash :: Getter Payload EVM.RequestsHash +pldRequestsHash = payloadHeader . EVM.hdrRequestsHash +{-# INLINE pldRequestsHash #-} + +_pldHash :: Payload -> BlockHash +_pldHash = EVM._hdrHash . _payloadHeader +{-# INLINE _pldHash #-} + +pldHash :: Getter Payload BlockHash +pldHash = payloadHeader . EVM.hdrHash +{-# INLINE pldHash #-} + +_pldPayloadHash :: Payload -> BlockPayloadHash +_pldPayloadHash = EVM._hdrPayloadHash . _payloadHeader +{-# INLINE _pldPayloadHash #-} + +pldPayloadHash :: Getter Payload BlockPayloadHash +pldPayloadHash = payloadHeader . EVM.hdrPayloadHash +{-# INLINE pldPayloadHash #-} + +_pldTransactions :: Payload -> Maybe [TransactionBytes] +_pldTransactions = fmap _executionPayloadDataTransactions . _payloadData +{-# INLINE _pldTransactions #-} + +pldTransactions :: Getter Payload (Maybe [TransactionBytes]) +pldTransactions = to _pldTransactions +{-# INLINE pldTransactions #-} + +_pldWithdrawals :: Payload -> Maybe [WithdrawalV1] +_pldWithdrawals = fmap _executionPayloadDataWithdrawals . _payloadData +{-# INLINE _pldWithdrawals #-} + +pldWithdrawals :: Getter Payload (Maybe [WithdrawalV1]) +pldWithdrawals = to _pldWithdrawals +{-# INLINE pldWithdrawals #-} + +_pldExpectedBlobVersionedHashes :: Payload -> Maybe [VersionedHash] +_pldExpectedBlobVersionedHashes = fmap _executionPayloadDataExpectedBlobVersionedHashes . _payloadData +{-# INLINE _pldExpectedBlobVersionedHashes #-} + +pldExpectedBlobVersionedHashes :: Getter Payload (Maybe [VersionedHash]) +pldExpectedBlobVersionedHashes = to _pldExpectedBlobVersionedHashes +{-# INLINE pldExpectedBlobVersionedHashes #-} + +_pldRequests :: Payload -> Maybe [EVM.ExecutionRequest] +_pldRequests = fmap _executionPayloadDataRequests . _payloadData +{-# INLINE _pldRequests #-} + +pldRequests :: Getter Payload (Maybe [EVM.ExecutionRequest]) +pldRequests = to _pldRequests +{-# INLINE pldRequests #-} + diff --git a/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs b/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs index 6cfd12dc65..4363d7c021 100644 --- a/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs +++ b/src/Chainweb/PayloadProvider/EVM/HeaderDB.hs @@ -17,7 +17,7 @@ {-# LANGUAGE UndecidableInstances #-} -- | --- Module: Chainweb.PayloadProvider.EVM.HeaderDB.Internal +-- Module: Chainweb.PayloadProvider.EVM.HeaderDB -- Copyright: Copyright © 2024 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz diff --git a/src/Chainweb/PayloadProvider/EVM/PayloadDB.hs b/src/Chainweb/PayloadProvider/EVM/PayloadDB.hs new file mode 100644 index 0000000000..627d9b6a08 --- /dev/null +++ b/src/Chainweb/PayloadProvider/EVM/PayloadDB.hs @@ -0,0 +1,283 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuantifiedConstraints #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} + +-- | +-- Module: Chainweb.PayloadProvider.EVM.PayloadDB +-- Copyright: Copyright © 2024 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- FIXME: this module is very similar to the respective module for the minimal +-- payload provider. Maybe we can unify both? +-- +module Chainweb.PayloadProvider.EVM.PayloadDB +( +-- * EVM Header DB + Configuration(..) +, configuration +, type PayloadDb +, PayloadDb_(..) +, initPayloadDb +, closePayloadDb +, withPayloadDb + +, type RankedPayloadCas + +-- * Insertion +, insertPayloadDb +, unsafeInsertPayloadDb + +-- * Internal Types +, RankedPayload(..) +, BlockRank(..) +, encodeRankedPayload +, decodeRankedPayload +) where + +import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash +import Chainweb.ChainId +import Chainweb.MerkleUniverse +import Chainweb.PayloadProvider.EVM.Header +import Chainweb.Storage.Table +import Chainweb.Storage.Table.RocksDB +import Chainweb.Utils hiding (Codec) +import Chainweb.Utils.Serialization +import Chainweb.Version +import Control.DeepSeq +import Control.Lens hiding (children) +import Control.Monad +import Control.Monad.Catch +import Data.Aeson +import Data.ByteString qualified as B +import Data.Function +import Data.Hashable +import Data.Text.Encoding qualified as T +import Ethereum.RLP +import GHC.Generics +import Numeric.Additive +import Prelude hiding (lookup) +import GHC.Stack +import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) +import Chainweb.PayloadProvider.EVM.ExecutionPayload + +-- -------------------------------------------------------------------------- -- +-- Exceptions + +-- type HeaderNotFoundException = HeaderNotFoundException_ ChainwebMerkleHashAlgorithm +-- +-- newtype HeaderNotFoundException_ a = HeaderNotFoundException (BlockPayloadHash_ a) +-- deriving (Show, Eq, Ord, Generic) +-- deriving anyclass (NFData, Hashable) +-- +-- instance Exception HeaderNotFoundException + +-- -------------------------------------------------------------------------- -- +-- | Configuration of the EVM Header DB. +-- + +data Configuration = Configuration + { _configChainId :: ChainId + , _configGenesis :: !Header + , _configRocksDb :: !RocksDb + } + +configuration + :: HasCallStack + => HasVersion + => HasChainId c + => c + -> RocksDb + -> Header + -> Configuration +configuration c rdb gen + | not isEvmProvider = + error "Chainweb.PayloadProvider.Evm.HeaderDB.configuration: chain does not use evm provider" + | otherwise = Configuration + { _configChainId = _chainId c + , _configRocksDb = rdb + , _configGenesis = gen + } + where + isEvmProvider = case payloadProviderTypeForChain c of + EvmProvider{} -> True + _ -> False + +instance HasChainId Configuration where + _chainId = _configChainId + +-- -------------------------------------------------------------------------- -- +-- Ranked Block Payload (only used internally) + +newtype RankedPayload = RankedPayload { _getRankedPayload :: Payload } + deriving (Show, Generic) + deriving newtype (Eq, Ord, Hashable, ToJSON, FromJSON, RLP) + +instance IsCasValue RankedPayload where + type CasKeyType RankedPayload = RankedBlockPayloadHash + casKey (RankedPayload pld) + = RankedBlockPayloadHash (view pldHeight pld) (view pldPayloadHash pld) + {-# INLINE casKey #-} + +type RankedPayloadCas tbl = Cas tbl RankedPayload + +-- -------------------------------------------------------------------------- -- +-- BlockRank (only used internally) + +newtype BlockRank = BlockRank { _getBlockRank :: BlockHeight } + deriving (Show, Generic) + deriving anyclass (NFData) + deriving newtype + ( Eq, Ord, Hashable, ToJSON, FromJSON + , AdditiveSemigroup, AdditiveAbelianSemigroup, AdditiveMonoid + , Num, Integral, Real, Enum + ) + +-- -------------------------------------------------------------------------- -- +-- Internal + +encodeRankedPayload :: RankedPayload -> B.ByteString +encodeRankedPayload = putRlpByteString . _getRankedPayload +{-# INLINE encodeRankedPayload #-} + +decodeRankedPayload :: MonadThrow m => B.ByteString -> m RankedPayload +decodeRankedPayload = decodeRlpM +{-# INLINE decodeRankedPayload #-} + +-- -------------------------------------------------------------------------- -- +-- Header DB + +type PayloadDb tbl = PayloadDb_ ChainwebMerkleHashAlgorithm tbl + +data PayloadDb_ a tbl = PayloadDb + { _payloadDbChainId :: !ChainId + , _payloadDbTable :: !(tbl RankedBlockPayloadHash RankedPayload) + } + +instance HasChainId (PayloadDb_ a tbl) where + _chainId = _payloadDbChainId + {-# INLINE _chainId #-} + +instance ReadableTable1 tbl => ReadableTable (PayloadDb_ a tbl) RankedBlockPayloadHash Payload where + tableLookup db k = fmap _getRankedPayload <$> tableLookup (_payloadDbTable db) k + {-# INLINE tableLookup #-} + +instance Table1 tbl => Table (PayloadDb_ a tbl) RankedBlockPayloadHash Payload where + tableInsert db k v = tableInsert (_payloadDbTable db) k (RankedPayload v) + tableDelete db k = tableDelete @_ @_ @RankedPayload (_payloadDbTable db) k + {-# INLINE tableInsert #-} + {-# INLINE tableDelete #-} + +-- -------------------------------------------------------------------------- -- +-- Initialization + +-- | Initialize a database handle +-- +initPayloadDb :: Configuration -> IO (PayloadDb_ a RocksDbTable) +initPayloadDb config = do + dbAddChecked db (Payload (_configGenesis config) Nothing) + return db + where + cid = _configChainId config + cidNs = T.encodeUtf8 (toText cid) + + payloadTable = newTable + (_configRocksDb config) + (Codec encodeRankedPayload decodeRankedPayload) + (Codec (runPutS . encodeRankedBlockPayloadHash) (runGetS decodeRankedBlockPayloadHash)) + ["EvmPayload", cidNs, "payload"] + + !db = PayloadDb + { _payloadDbChainId = cid + , _payloadDbTable = payloadTable + } + +-- | Close a database handle and release all resources +-- +closePayloadDb :: PayloadDb_ a tbl -> IO () +closePayloadDb _ = return () + +withPayloadDb + :: RocksDb + -> ChainId + -> Header + -> (PayloadDb_ a RocksDbTable -> IO b) + -> IO b +withPayloadDb db cid genesisHeader = bracket start closePayloadDb + where + start = initPayloadDb Configuration + { _configChainId = cid + , _configGenesis = genesisHeader + , _configRocksDb = db + } + +-- -------------------------------------------------------------------------- -- +-- Validated Payloads + +-- NOTE: the constructor of this type is intentionally NOT exported. Value of +-- this type must be only created via functions from this module. +-- +newtype ValidatedPayload = ValidatedPayload Payload + deriving (Show, Eq, Generic) + +_validatedPayload :: ValidatedPayload -> Payload +_validatedPayload (ValidatedPayload h) = h +{-# INLINE _validatedPayload #-} + +-- -------------------------------------------------------------------------- -- +-- Insertions + +insertPayloadDb :: Table1 tbl => PayloadDb_ a tbl -> ValidatedPayload -> IO () +insertPayloadDb db = dbAddChecked db . _validatedPayload +{-# INLINE insertPayloadDb #-} + +unsafeInsertPayloadDb :: Table1 tbl => PayloadDb_ a tbl -> Payload -> IO () +unsafeInsertPayloadDb = dbAddChecked +{-# INLINE unsafeInsertPayloadDb #-} + +-- -------------------------------------------------------------------------- -- +-- Insert +-- +-- Only functions in this section are allowed to modify values of the Db type. + +-- | A a new entry to the database +-- +-- Updates all indices. +-- +dbAddChecked + :: Table1 tbl -- Cas (tbl RankedBlockPayloadHash Payload) Payload + => PayloadDb_ a tbl + -> Payload + -> IO () +dbAddChecked db e = + unlessM (tableMember db ek) dbAddCheckedInternal + where + ek = RankedBlockPayloadHash (view pldHeight e) (view pldPayloadHash e) + + -- Internal helper methods + + -- TODO add validation and check that parent exists (except for height 0). + + -- | Unchecked addition + -- + -- ASSUMES that + -- + -- * Item is not yet in database + -- + dbAddCheckedInternal = casInsert (_payloadDbTable db) (RankedPayload e) diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs index 332037a2f5..37271cd577 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs @@ -56,7 +56,7 @@ import Chainweb.BlockHeight import Chainweb.BlockPayloadHash import Chainweb.ChainId import Chainweb.Payload qualified as Pact -import Chainweb.PayloadProvider.EVM.Header qualified as EVM +import Chainweb.PayloadProvider.EVM.ExecutionPayload qualified as EVM import Chainweb.PayloadProvider.Minimal.Payload qualified as Minimal import Chainweb.Ranked import Chainweb.RestAPI.Orphans () @@ -317,27 +317,27 @@ instance IsPayloadProvider PactProvider where -- | IsPayloadProvider instance for the Pact Payload provider -- instance IsPayloadProvider (EvmProvider n) where - type PayloadType (EvmProvider n) = EVM.Header - type PayloadBatchType (EvmProvider n) = HeaderList + type PayloadType (EvmProvider n) = EVM.Payload + type PayloadBatchType (EvmProvider n) = PayloadList p2pPayloadBatchLimit = 20 -- FIXME - batch = HeaderList . catMaybes + batch = PayloadList . catMaybes -newtype HeaderList = HeaderList { _headerList :: [EVM.Header] } +newtype PayloadList = PayloadList { _headerList :: [EVM.Payload] } deriving (Show, Eq, Generic) deriving newtype (ToJSON, FromJSON, EVM.RLP) -instance MimeRender OctetStream EVM.Header where +instance MimeRender OctetStream EVM.Payload where mimeRender _ = EVM.putRlpLazyByteString {-# INLINE mimeRender #-} -instance MimeUnrender OctetStream EVM.Header where +instance MimeUnrender OctetStream EVM.Payload where mimeUnrender _ = EVM.getLazy EVM.getRlp {-# INLINE mimeUnrender #-} -instance MimeRender OctetStream HeaderList where +instance MimeRender OctetStream PayloadList where mimeRender _ = EVM.putRlpLazyByteString {-# INLINE mimeRender #-} -instance MimeUnrender OctetStream HeaderList where +instance MimeUnrender OctetStream PayloadList where mimeUnrender _ = EVM.getLazy EVM.getRlp {-# INLINE mimeUnrender #-} From 28085a2ac5a225ba96368867e53bd411a63bfd14 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 31 May 2025 21:40:10 -0700 Subject: [PATCH 214/378] fix evm development versions --- cwtools/evm-genesis/Main.hs | 5 +- src/Chainweb/Chainweb/Configuration.hs | 6 +- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 9 +++ .../Version/EvmDevelopmentSingleton.hs | 68 ++++++++++++++++--- src/Chainweb/Version/Registry.hs | 2 +- 5 files changed, 78 insertions(+), 12 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index d055f9dff1..1cc7fe6fea 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -65,8 +65,11 @@ main = do let cids = [20..25] return ("evm-development", cids, evmDevnetSpecFile) ["evm-development-singleton"] -> do - let cids = [20] + let cids = [0] return ("evm-development-singleton", cids, evmDevnetSpecFile) + ["evm-development-pair"] -> do + let cids = [1] + return ("evm-development-pair", cids, evmDevnetSpecFile) _ -> error "Invalid argument for the chainweb version provided. The version must be one of: 'mainnet', 'testnet', 'evm-testnet', or 'evm-development'." hdrs <- forM cids $ \cid -> do diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 3202790fbd..1209f2795c 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -92,6 +92,7 @@ import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Development import Chainweb.Version.EvmDevelopment +import Chainweb.Version.EvmDevelopmentSingleton import Chainweb.Version.Mainnet import Chainweb.Version.RecapDevelopment import Chainweb.Version.Registry (findKnownVersion, knownVersions) @@ -547,7 +548,10 @@ validateChainwebVersion v = do , sshow (_versionName v) ] where - isDevelopment = _versionCode v `elem` [_versionCode dv | dv <- [recapDevnet, devnet, evmDevnet]] + isDevelopment = _versionCode v `elem` + [_versionCode dv | dv <- + [recapDevnet, devnet, evmDevnet, evmDevnetSingleton, evmDevnetPair] + ] validateBackupConfig :: ConfigValidation BackupConfig [] validateBackupConfig c = diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 29327c1458..5ca43bcc5f 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -16,6 +16,7 @@ module Chainweb.PayloadProvider.EVM.Genesis import Chainweb.Version import Chainweb.PayloadProvider.EVM.Header import Chainweb.Version.EvmDevelopment +import Chainweb.Version.EvmDevelopmentSingleton import Chainweb.Utils import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) import Data.Text qualified as T @@ -79,6 +80,14 @@ genesisBlocks genesisBlocks c = go (_versionCode implicitVersion) (_chainId c) where go v + | v == _versionCode EvmDevelopmentSingleton = \case + ChainId 0 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKubUszUzclOu3VLZ6Oz-L6Nph9pRr7Ilc6Sy0N9diB6oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) + | v == _versionCode EvmDevelopmentPair = \case + ChainId 1 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKtIkoG2zy4Z3rQ8TVs8INePmK-1cYAD8aBvkf-57uIVoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopment = \case ChainId 20 -> f "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGnV3kPaffv02pFz2XaK1abwEUqbp-L5K03hpucRJqlToFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" diff --git a/src/Chainweb/Version/EvmDevelopmentSingleton.hs b/src/Chainweb/Version/EvmDevelopmentSingleton.hs index 5c9d656dd6..6176e108a7 100644 --- a/src/Chainweb/Version/EvmDevelopmentSingleton.hs +++ b/src/Chainweb/Version/EvmDevelopmentSingleton.hs @@ -8,6 +8,8 @@ module Chainweb.Version.EvmDevelopmentSingleton ( evmDevnetSingleton , pattern EvmDevelopmentSingleton +, evmDevnetPair +, pattern EvmDevelopmentPair ) where import qualified Data.Set as Set @@ -27,6 +29,10 @@ pattern EvmDevelopmentSingleton :: ChainwebVersion pattern EvmDevelopmentSingleton <- ((== evmDevnetSingleton) -> True) where EvmDevelopmentSingleton = evmDevnetSingleton +pattern EvmDevelopmentPair :: ChainwebVersion +pattern EvmDevelopmentPair <- ((== evmDevnetPair) -> True) where + EvmDevelopmentPair = evmDevnetPair + -- How to compute the hashes: -- -- Mininal Payload Provider: @@ -49,10 +55,10 @@ pattern EvmDevelopmentSingleton <- ((== evmDevnetSingleton) -> True) where -- -- TODO (use ea?) -evmDevnetSingleton :: ChainwebVersion -evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion +evmDevnetPair :: ChainwebVersion +evmDevnetPair = withVersion evmDevnetPair $ ChainwebVersion { _versionCode = ChainwebVersionCode 0x0000_000b - , _versionName = ChainwebVersionName "evm-development-singleton" + , _versionName = ChainwebVersionName "evm-development-pair" , _versionForks = tabulateHashMap $ const $ onAllChains ForkAtGenesis , _versionUpgrades = onAllChains mempty , _versionGraphs = Bottom (minBound, pairChainGraph) @@ -61,16 +67,17 @@ evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion , _versionHeaderBaseSizeBytes = 318 - 110 , _versionBootstraps = [] , _versionGenesis = VersionGenesis - { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 100_000) + { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 10_000) , _genesisTime = onChains -- FIXME: is the creation time for the pact headers correct? - $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0] ] - <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [1] ] + [ (unsafeChainId 0, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) + , (unsafeChainId 1, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) + ] , _genesisBlockPayload = onChains $ -- Pact Payload Provider [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") -- EVM Payload Provider - , (unsafeChainId 1, unsafeFromText "FAxLDjtb8r_0S0Rfr8rD47EQwO-Ma-fmEynZccHvn5o") + , (unsafeChainId 1, unsafeFromText "0ewfT8sFhCsJNKAF2EqmvfNZY1LpR2swW0yYK1F4oXs") ] } @@ -93,6 +100,49 @@ evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion -- FIXME make this safe for graph changes , _versionPayloadProviderTypes = onChains - $ [ (unsafeChainId i, PactProvider) | i <- [0] ] - <> [ (unsafeChainId i, EvmProvider (1789 - 20 + int i)) | i <- [1] ] + [ (unsafeChainId 0, PactProvider) + , (unsafeChainId 1, EvmProvider 1789) + ] + } + +evmDevnetSingleton :: ChainwebVersion +evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion + { _versionCode = ChainwebVersionCode 0x0000_000c + , _versionName = ChainwebVersionName "evm-development-singleton" + , _versionForks = tabulateHashMap $ const $ onAllChains ForkAtGenesis + , _versionUpgrades = onAllChains mempty + , _versionGraphs = Bottom (minBound, singletonChainGraph) + , _versionBlockDelay = BlockDelay 30_000_000 + , _versionWindow = WindowWidth 120 + , _versionHeaderBaseSizeBytes = 318 - 110 + , _versionBootstraps = [] + , _versionGenesis = VersionGenesis + { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 10_000) + , _genesisTime = onChains + [ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) ] + , _genesisBlockPayload = onChains $ + -- EVM Payload Provider + [ (unsafeChainId 0, unsafeFromText "Gmu77QPF9qmVbEAWPiIl1sH8JfRF5nZZWlYdhoXK5aI") ] + } + + -- still the *default* block gas limit is set, see + -- defaultChainwebConfiguration._configBlockGasLimit + , _versionMaxBlockGasLimit = Bottom (minBound, Nothing) + , _versionCheats = VersionCheats + { _disablePow = True + , _fakeFirstEpochStart = True + , _disablePact = False + } + , _versionDefaults = VersionDefaults + { _disablePeerValidation = True + , _disableMempoolSync = False + } + , _versionVerifierPluginNames = onAllChains $ Bottom + (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) + , _versionQuirks = noQuirks + , _versionServiceDate = Nothing + + -- FIXME make this safe for graph changes + , _versionPayloadProviderTypes = onChains + [ (unsafeChainId 0, EvmProvider 1789) ] } diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index 7fc8bc49f7..2977c449c8 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -81,7 +81,7 @@ validateVersion v = do -- | Versions known to us by name. knownVersions :: [ChainwebVersion] -knownVersions = [mainnet, testnet04, recapDevnet, devnet, evmDevnet, evmDevnetSingleton] +knownVersions = [mainnet, testnet04, recapDevnet, devnet, evmDevnet, evmDevnetSingleton, evmDevnetPair] -- | Look up a known version by name, usually with `m` instantiated to some -- configuration parser monad. From 78fed1a7049ec0c0e6f92b7feff58884ebc4323a Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 20 May 2025 22:42:17 -0400 Subject: [PATCH 215/378] Quieter logging Change-Id: Id000000056296ea25046e7a8b9b001e84c259047 --- src/Chainweb/Utils.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index 7eea30a9a1..4f8870d6df 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -1079,7 +1079,7 @@ runForever logfun name a = mask $ \umask -> do forever (umask a) `catchAllSynchronous` \e -> logfun Error $ name <> " failed: " <> sshow e <> ". Restarting ..." go - void go `finally` logfun Info (name <> " stopped") + void go `finally` logfun Debug (name <> " stopped") -- | Repeatedly run a computation 'forever' at the given rate until it is -- stopped by receiving 'SomeAsyncException'. @@ -1107,7 +1107,7 @@ runForeverThrottled logfun name burst rate a = mask $ \umask -> do forever (umask runThrottled) `catchAllSynchronous` \e -> logfun Error $ name <> " failed: " <> sshow e <> ". Restarting ..." go - void go `finally` logfun Info (name <> " stopped") + void go `finally` logfun Debug (name <> " stopped") -- -------------------------------------------------------------------------- -- -- Configuration wrapper to enable and disable components From ccb92a6dbc4c860b264634449a364bb9a0baa28b Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 23 May 2025 12:37:13 -0400 Subject: [PATCH 216/378] add more logging to 'impossible to move' message --- src/Chainweb/Pact/PactService.hs | 2 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 1bc3d934cd..ad1265e7f3 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -519,7 +519,7 @@ syncToFork logger serviceEnv hints forkInfo = do Nothing -> do logFunctionText logger Info $ "impossible to move to " <> brief forkInfo._forkInfoTargetState - <> " from " <> brief pactConsensusState + <> " from " <> brief pactConsensusState <> " with trace " <> brief forkInfo._forkInfoTrace -- error: we have no way to get to the target block. just report -- our current state and do nothing else. return (mempty, mempty, pactConsensusState) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index e9073dda09..1795dfa9de 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -402,7 +402,7 @@ getBlockHeaderInternal let hints = Hints <$> maybeOrigin' pld <- tableLookup candidatePldTbl (view blockPayloadHash header) let prefetchProviderPayloads = case providers ^?! atChain cid of - ConfiguredPayloadProvider provider -> return () + ConfiguredPayloadProvider _provider -> return () -- TODO PP -- prefetchPayloads provider hints -- [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] From ad0c6d1bac9619b0853bd9f6fc95935b4fa49ca8 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 2 Jun 2025 14:42:26 -0400 Subject: [PATCH 217/378] Increase debugging on missing treedb keys --- src/Chainweb/BlockHeaderDB/Internal.hs | 24 +++++++----- src/Chainweb/BlockHeaderDB/RemoteDB.hs | 5 ++- src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 19 ++++++---- src/Chainweb/CutDB.hs | 26 +++---------- src/Chainweb/Sync/WebBlockHeaderStore.hs | 15 ++++++-- src/Chainweb/TreeDB.hs | 40 ++++++++++---------- 6 files changed, 65 insertions(+), 64 deletions(-) diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index 6a8aafea56..f641429c81 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -80,6 +80,8 @@ import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB import Numeric.Additive +import Control.Monad.Except +import Control.Monad.IO.Class -- -------------------------------------------------------------------------- -- -- | Configuration of the chain DB. @@ -159,7 +161,7 @@ instance HasChainId BlockHeaderDb where {-# INLINE _chainId #-} instance (k ~ CasKeyType BlockHeader, HasVersion) => ReadableTable BlockHeaderDb k BlockHeader where - tableLookup = lookup + tableLookup db k = either (\_ -> Nothing) Just <$> lookup db k {-# INLINE tableLookup #-} -- -------------------------------------------------------------------------- -- @@ -189,7 +191,7 @@ dbAddChecked db e = unlessM (tableMember (_chainDbCas db) ek) dbAddCheckedIntern dbAddCheckedInternal = case parent e of Nothing -> add Just p -> tableLookup (_chainDbCas db) (RankedBlockHash (r - 1) p) >>= \case - Nothing -> throwM $ TreeDbParentMissing @BlockHeaderDb e + Nothing -> throwM $ TreeDbParentMissing @BlockHeaderDb e "dbAddCheckedInternal" Just (RankedBlockHeader pe) -> do unless (rank e == rank pe + 1) $ throwM $ TreeDbInvalidRank @BlockHeaderDb e @@ -259,14 +261,18 @@ withBlockHeaderDb db cid = snd <$> allocate start closeBlockHeaderDb instance HasVersion => TreeDb BlockHeaderDb where type DbEntry BlockHeaderDb = BlockHeader - lookup db h = runMaybeT $ do + lookup db h = runExceptT $ do -- lookup rank - r <- MaybeT $ tableLookup (_chainDbRankTable db) h - MaybeT $ lookupRanked db (int r) h + r <- liftIO (tableLookup (_chainDbRankTable db) h) >>= \case + Nothing -> throwError "" + Just v -> return v + ExceptT $ lookupRanked db (int r) h {-# INLINEABLE lookup #-} - lookupRanked db r h = runMaybeT $ do - rh <- MaybeT $ tableLookup (_chainDbCas db) (RankedBlockHash (int r) h) + lookupRanked db r h = runExceptT $ do + rh <- liftIO (tableLookup (_chainDbCas db) (RankedBlockHash (int r) h)) >>= \case + Nothing -> throwError "" + Just v -> return v return $! _getRankedBlockHeader rh {-# INLINEABLE lookupRanked #-} @@ -343,14 +349,14 @@ seekTreeDb db k mir it = do -- Seek to cursor let x = _getNextItem a r <- tableLookup (_chainDbRankTable db) x >>= \case - Nothing -> throwM $ TreeDbKeyNotFound @BlockHeaderDb x + Nothing -> throwM $ TreeDbKeyNotFound @BlockHeaderDb x "seekTreeDb.lookup" (Just !b) -> return b iterSeek it (RankedBlockHash r x) -- if we don't find the cursor, throw exception iterKey it >>= \case Just (RankedBlockHash _ b) | b == x -> return () - _ -> throwM $ TreeDbKeyNotFound @BlockHeaderDb x + _ -> throwM $ TreeDbKeyNotFound @BlockHeaderDb x "seekTreeDb.iterKey" -- If the cursor is exclusive, then advance the iterator when (isExclusive a) $ iterNext it diff --git a/src/Chainweb/BlockHeaderDB/RemoteDB.hs b/src/Chainweb/BlockHeaderDB/RemoteDB.hs index a2184dc83f..6931a8c014 100644 --- a/src/Chainweb/BlockHeaderDB/RemoteDB.hs +++ b/src/Chainweb/BlockHeaderDB/RemoteDB.hs @@ -22,7 +22,7 @@ module Chainweb.BlockHeaderDB.RemoteDB ) where import Control.Error.Util (hush) -import Control.Lens (view) +import Control.Lens import Control.Monad.Catch (handle, throwM) import qualified Data.Text as T @@ -63,7 +63,8 @@ instance HasVersion => TreeDb RemoteDb where maxEntry = error "Chainweb.TreeDB.RemoteDB.RemoteDb.maxEntry: not implemented" -- If other default functions rely on this, it could be quite inefficient. - lookup (RemoteDb env alog cid) k = hush <$> runClientM client env + lookup (RemoteDb env alog cid) k = do + over _Left (\e -> "client error: " <> sshow e) <$> runClientM client env where client = logServantError alog "failed to query tree db entry" $ headerClient cid k diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index 1c486cf770..56bcddbd31 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -73,6 +73,7 @@ import Chainweb.RestAPI.Utils import Chainweb.TreeDB import Chainweb.Utils.Paging import Chainweb.Version +import qualified Data.Text as Text -- -------------------------------------------------------------------------- -- -- Handler Tools @@ -86,11 +87,12 @@ checkKey -> DbKey db -> m (DbKey db) checkKey !db !k = liftIO (lookup db k) >>= \case - Nothing -> throwError $ err404Msg $ object - [ "reason" .= ("key not found" :: String) - , "key" .= k + Left m -> throwError $ err404Msg $ object $ concat + [ ["reason" .= ("key not found" :: String)] + , ("details" .= m) <$ guard (not (Text.null m)) + , ["key" .= k] ] - Just _ -> pure k + Right _ -> pure k err404Msg :: ToJSON msg => msg -> ServerError err404Msg msg = setErrJSON msg err404 @@ -262,11 +264,12 @@ headerHandler -> DbKey db -> Handler (DbEntry db) headerHandler db k = liftIO (lookup db k) >>= \case - Nothing -> throwError $ err404Msg $ object - [ "reason" .= ("key not found" :: String) - , "key" .= k + Left m -> throwError $ err404Msg $ object $ concat + [ ["reason" .= ("key not found" :: String)] + , ("details" .= m) <$ guard (not (Text.null m)) + , ["key" .= k] ] - Just e -> pure e + Right e -> pure e -- -------------------------------------------------------------------------- -- -- BlockHeaderDB API Server diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 1d9575ae41..5104b33fed 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -79,7 +79,6 @@ module Chainweb.CutDB -- * Membership Queries , member , memberOfHeader -, memberOfM -- * Some CutDb , CutDbT(..) @@ -481,7 +480,7 @@ readHighestCutHeaders logg wbhdb cutHashesStore = withTableIterator (unCasify cu logg Info "No initial cut found in database or configuration, falling back to genesis cut" return $ view cutMap genesisCut Just ch -> try (lookupCutHashes wbhdb ch) >>= \case - Left (e@(TreeDbKeyNotFound _) :: TreeDbException BlockHeaderDb) -> do + Left (e@TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do logg Warn $ "Unable to load cut at height " <> sshow (_cutHashesHeight ch) <> " from database." @@ -849,8 +848,8 @@ cutHashesToBlockHeaderMap conf logfun headerStore providers hs = origin . _ranked `catch` \case - (TreeDbKeyNotFound{} :: TreeDbException BlockHeaderDb) -> - return (Left cv) + (TreeDbKeyNotFound e msg :: TreeDbException BlockHeaderDb) -> + return (Left (cv, (e, msg))) e -> throwM e -- -------------------------------------------------------------------------- -- @@ -867,28 +866,13 @@ memberOfHeader -> IO Bool memberOfHeader db cid h ctx = do lookup chainDb h >>= \case - Nothing -> return False - Just lh -> seekAncestor chainDb ctx (int $ view blockHeight lh) >>= \case + Left{} -> return False + Right lh -> seekAncestor chainDb ctx (int $ view blockHeight lh) >>= \case Nothing -> return False Just x -> return $ view blockHash x == h where chainDb = db ^?! cutDbWebBlockHeaderDb . ixg cid -memberOfM - :: HasVersion - => CutDb - -> ChainId - -> BlockHash - -- ^ the block hash to look up (the member) - -> BlockHash - -- ^ the context, i.e. the branch of the chain that contains the member - -> IO Bool -memberOfM db cid h ctx = do - th <- lookupM chainDb ctx - memberOfHeader db cid h th - where - chainDb = db ^?! cutDbWebBlockHeaderDb . ixg cid - member :: HasVersion => CutDb diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 1795dfa9de..91afd2fa74 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -16,6 +16,7 @@ {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE TupleSections #-} -- | -- Module: Chainweb.Sync.WebBlockHeaderStore @@ -582,8 +583,14 @@ getBlockHeaderInternal logg Debug $ taskMsg ck "lookup origin" !r <- trace logfun (traceLabel "pullOrigin") k 0 $ TDB.lookup (rDb cid originEnv) k - logg Debug $ taskMsg ck "received from origin" - return r + case r of + Left err -> do + logg Warn $ taskMsg k + $ "failed to pull from origin " <> sshow origin <> " with " <> err + return Nothing + Right v -> do + logg Debug $ taskMsg ck "received from origin" + return $ Just v -- pullOriginDeps _ Nothing = return () -- pullOriginDeps ck@(ChainValue cid k) (Just origin) = do @@ -635,7 +642,7 @@ getBlockHeader -> IO BlockHeader getBlockHeader headerStore candidateHeaderCas candidatePldTbl providers localPayload cid priority maybeOrigin h = ((\(ChainValue _ b) -> b) <$> go) - `catch` \(TaskFailed _es) -> throwM $ TreeDbKeyNotFound @BlockHeaderDb h + `catch` \(TaskFailed es) -> throwM $ TreeDbKeyNotFound @BlockHeaderDb h (sshow es) where go = getBlockHeaderInternal headerStore @@ -652,7 +659,7 @@ instance (HasVersion, CasKeyType (ChainValue BlockHeader) ~ k) => ReadableTable tableLookup (WebBlockHeaderCas db) (ChainValue cid h) = (Just . ChainValue cid <$> lookupWebBlockHeaderDb db cid h) `catch` \e -> case e of - TDB.TreeDbKeyNotFound _ -> return Nothing + TDB.TreeDbKeyNotFound _ _ -> return Nothing _ -> throwM @_ @(TDB.TreeDbException BlockHeaderDb) e {-# INLINE tableLookup #-} diff --git a/src/Chainweb/TreeDB.hs b/src/Chainweb/TreeDB.hs index d68c33fc0b..e298914f95 100644 --- a/src/Chainweb/TreeDB.hs +++ b/src/Chainweb/TreeDB.hs @@ -120,14 +120,14 @@ import Chainweb.Utils.Paging -- Exceptions data TreeDbException db - = TreeDbParentMissing (DbEntry db) - | TreeDbKeyNotFound (DbKey db) + = TreeDbParentMissing (DbEntry db) T.Text + | TreeDbKeyNotFound (DbKey db) T.Text | TreeDbInvalidRank (DbEntry db) | TreeDbAncestorMissing (DbEntry db) Natural T.Text instance (Show (DbEntry db), Show (DbKey db)) => Show (TreeDbException db) where - show (TreeDbParentMissing e) = "TreeDbParentMissing: " ++ show e - show (TreeDbKeyNotFound e) = "TreeDbKeyNotFound: " ++ show e + show (TreeDbParentMissing e msg) = "TreeDbParentMissing: " ++ show e ++ ". " ++ T.unpack msg + show (TreeDbKeyNotFound e msg) = "TreeDbKeyNotFound: " ++ show e <> ". " ++ T.unpack msg show (TreeDbInvalidRank e) = "TreeDbInvalidRank: " ++ show e show (TreeDbAncestorMissing e r msg) = "TreeDbAncestorMissing: entry " ++ show e @@ -242,7 +242,7 @@ class (Typeable db, TreeDbEntry (DbEntry db)) => TreeDb db where lookup :: db -> DbKey db - -> IO (Maybe (DbEntry db)) + -> IO (Either T.Text (DbEntry db)) -- | Lookup a single entry by its key and rank. For some instances of -- the lookup can be implemented more efficiently when the rank is know. @@ -253,7 +253,7 @@ class (Typeable db, TreeDbEntry (DbEntry db)) => TreeDb db where :: db -> Natural -> DbKey db - -> IO (Maybe (DbEntry db)) + -> IO (Either T.Text (DbEntry db)) lookupRanked db _ = lookup db {-# INLINEABLE lookupRanked #-} @@ -505,8 +505,8 @@ chainBranchEntries db k l mir mar@(Just (MaxRank (Max m))) lower upper f = do defaultBranchEntries db k l mir mar lower upper' f where start (UpperBound u) = lookup db u >>= \case - Nothing -> return mempty - Just e -> seekAncestor db e m >>= \case + Left{} -> return mempty + Right e -> seekAncestor db e m >>= \case Nothing -> return mempty Just x -> return $ HS.singleton (UpperBound $! key x) {-# INLINEABLE chainBranchEntries #-} @@ -642,8 +642,8 @@ lookupM -> DbKey db -> IO (DbEntry db) lookupM db k = lookup db k >>= \case - Nothing -> throwM $ TreeDbKeyNotFound @db k - (Just !x) -> return x + Left e -> throwM $ TreeDbKeyNotFound @db k $ "lookupM: " <> e + Right !x -> return x {-# INLINEABLE lookupM #-} lookupRankedM @@ -654,8 +654,8 @@ lookupRankedM -> DbKey db -> IO (DbEntry db) lookupRankedM db r k = lookupRanked db r k >>= \case - Nothing -> throwM $ TreeDbKeyNotFound @db k - (Just !x) -> return x + Left e -> throwM $ TreeDbKeyNotFound @db k $ "lookupRankedM: " <> e + Right !x -> return x {-# INLINEABLE lookupRankedM #-} -- | Lookup all entries in a stream of database keys and return the stream @@ -676,7 +676,7 @@ lookupStream => db -> S.Stream (Of (DbKey db)) IO r -> S.Stream (Of (DbEntry db)) IO r -lookupStream db = S.catMaybes . S.mapM (lookup db) +lookupStream db = S.catMaybes . S.mapM (fmap (either (\_ -> Nothing) Just) . lookup db) data GenesisParent = GenesisParentThrow @@ -702,8 +702,8 @@ lookupParentM g db e = case parent e of _ -> throwM $ InternalInvariantViolation "Chainweb.TreeDB.lookupParentM: Called getParentEntry on genesis block" Just p -> lookup db p >>= \case - Nothing -> throwM $ TreeDbParentMissing @db e - (Just !x) -> return x + Left m -> throwM $ TreeDbParentMissing @db e ("lookupParentM: " <> m) + Right !x -> return x -- | Replace all entries in the stream by their parent entries. -- If the genesis block is part of the stream it is replaced by itself. @@ -722,8 +722,8 @@ lookupParentStreamM g db = S.mapMaybeM $ \e -> case parent e of GenesisParentThrow -> throwM $ InternalInvariantViolation "Chainweb.TreeDB.lookupParentStreamM: Called getParentEntry on genesis block. Most likely this means that the genesis headers haven't been generated correctly. If you are using a development or testing chainweb version consider resetting the databases." Just p -> lookup db p >>= \case - Nothing -> throwM $ TreeDbParentMissing @db e - (Just !x) -> return (Just x) + Left m -> throwM $ TreeDbParentMissing @db e $ "lookupParentStreamM: " <> m + Right !x -> return (Just x) -- | Interpret a given `BlockHeaderDb` as a native Haskell `Tree`. Should be -- used only for debugging purposes. @@ -811,7 +811,7 @@ collectForkBlocks -> IO (DbEntry db, Vector (DbEntry db), Vector (DbEntry db)) collectForkBlocks db lastHeader newHeader = do (oldL, newL) <- go (branchDiff db lastHeader newHeader) ([], []) - when (null oldL) $ throwM $ TreeDbParentMissing @db lastHeader + when (null oldL) $ throwM $ TreeDbParentMissing @db lastHeader "collectForkBlocks: no fork point" let !common = unsafeHead "Chainweb.TreeDB.collectForkBlocks.common" oldL let !old = V.fromList $ unsafeTail "Chainweb.TreeDB.collectForkBlocks.old" oldL let !new = V.fromList $ unsafeTail "Chainweb.TreeDB.collectForkBlocks.new" newL @@ -1043,8 +1043,8 @@ ancestorOfEntry -- ^ the context, i.e. the branch of the chain that contains the member -> IO Bool ancestorOfEntry db h ctx = lookup db h >>= \case - Nothing -> return False - Just lh -> seekAncestor db ctx (rank lh) >>= \case + Left{} -> return False + Right lh -> seekAncestor db ctx (rank lh) >>= \case Nothing -> return False Just x -> return $ key x == h From bc05d1ac6c60aafcbc57dfd93780c7dc67467e17 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 2 Jun 2025 15:07:16 -0400 Subject: [PATCH 218/378] More logging --- src/Chainweb/TreeDB.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/TreeDB.hs b/src/Chainweb/TreeDB.hs index e298914f95..6db5c97000 100644 --- a/src/Chainweb/TreeDB.hs +++ b/src/Chainweb/TreeDB.hs @@ -654,7 +654,7 @@ lookupRankedM -> DbKey db -> IO (DbEntry db) lookupRankedM db r k = lookupRanked db r k >>= \case - Left e -> throwM $ TreeDbKeyNotFound @db k $ "lookupRankedM: " <> e + Left e -> throwM $ TreeDbKeyNotFound @db k $ "lookupRankedM (" <> sshow r <> "): " <> e Right !x -> return x {-# INLINEABLE lookupRankedM #-} From 5e90a780b23594720abb580c0b053ac9c937ced5 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 2 Jun 2025 15:22:29 -0700 Subject: [PATCH 219/378] small logging fix --- src/Chainweb/PayloadProvider/EVM.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index f2faa6d0f0..5da315663d 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1034,9 +1034,9 @@ awaitNewPayload p = do pld <- latestPayloadIO p lf Warn $ "timeout while waiting for new payloadID" - <> "consensus state: " <> briefJson state - <> "new block ctx: " <> briefJson bctx - <> "latest payload: " <> briefJson pld + <> ": consensus state: " <> briefJson state + <> ", new block ctx: " <> briefJson bctx + <> ", latest payload: " <> briefJson pld -- FIXME -- We are probalby stuck. What can we do? Call forkchoiceUpdate -- again? Or we just do nothing? Maybe it should be the job of @@ -1063,7 +1063,7 @@ awaitNewPayload p = do EVM.BaseFeePerGas bf = _executionPayloadV1BaseFeePerGas v1 GasUsed gu = _executionPayloadV1GasUsed v1 - -- Wait for payload from the execution client + -- Wait for payload from the exeution client -- FIXME not sure if the timeout is a good idea... awaitPid = do timeout <- registerDelay getPayloadTimeout From 6148670df1b446b781621a33d5e8ba183bd4f9cd Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 2 Jun 2025 23:25:47 -0700 Subject: [PATCH 220/378] fix ancestor lookup in consensusState --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 91afd2fa74..c5eb6e4c69 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -209,15 +209,22 @@ consensusState => WebBlockHeaderDb -> BlockHeader -> IO ConsensusState -consensusState wdb hdr = do - db <- getWebBlockHeaderDb wdb hdr - safeHdr <- fromJuste <$> seekAncestor db hdr safeHeight - finalHdr <- fromJuste <$> seekAncestor db hdr finalHeight - return ConsensusState +consensusState wdb hdr + | isGenesisBlockHeader hdr = return ConsensusState { _consensusStateLatest = syncStateOfBlockHeader hdr - , _consensusStateSafe = syncStateOfBlockHeader safeHdr - , _consensusStateFinal = syncStateOfBlockHeader finalHdr + , _consensusStateSafe = syncStateOfBlockHeader hdr + , _consensusStateFinal = syncStateOfBlockHeader hdr } + | otherwise = do + db <- getWebBlockHeaderDb wdb hdr + Parent phdr <- lookupParentHeader wdb hdr + safeHdr <- fromJuste <$> seekAncestor db phdr safeHeight + finalHdr <- fromJuste <$> seekAncestor db phdr finalHeight + return ConsensusState + { _consensusStateLatest = syncStateOfBlockHeader hdr + , _consensusStateSafe = syncStateOfBlockHeader safeHdr + , _consensusStateFinal = syncStateOfBlockHeader finalHdr + } where WindowWidth w = _versionWindow implicitVersion cid = _chainId hdr From 11d5a11f736bffa7c2e73a5c10a5a29ddb726bb0 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 3 Jun 2025 11:06:10 -0400 Subject: [PATCH 221/378] Fix ancestor lookup in consensusState v2 --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index c5eb6e4c69..b08c774786 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -218,8 +218,11 @@ consensusState wdb hdr | otherwise = do db <- getWebBlockHeaderDb wdb hdr Parent phdr <- lookupParentHeader wdb hdr - safeHdr <- fromJuste <$> seekAncestor db phdr safeHeight - finalHdr <- fromJuste <$> seekAncestor db phdr finalHeight + safeHdr <- + if int safeHeight == view blockHeight hdr then return hdr + else fromJuste <$> seekAncestor db phdr safeHeight + finalHdr <- if int finalHeight == view blockHeight hdr then return hdr + else fromJuste <$> seekAncestor db phdr finalHeight return ConsensusState { _consensusStateLatest = syncStateOfBlockHeader hdr , _consensusStateSafe = syncStateOfBlockHeader safeHdr @@ -240,6 +243,7 @@ consensusState wdb hdr -- forkInfoForHeader :: HasVersion + => HasCallStack => WebBlockHeaderDb -> BlockHeader -> Maybe EncodedPayloadData @@ -481,7 +485,7 @@ getBlockHeaderInternal case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do - logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation for " <> sshow h <> " failed with : " <> sshow e + logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation for " <> brief header <> " failed with : " <> sshow e throwM e if r /= _forkInfoTargetState finfo then do From 57adf9e472dea5605608529e57df3520facc831e Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 3 Jun 2025 11:21:16 -0400 Subject: [PATCH 222/378] Document consensusState a bit more --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index b08c774786..e0a8d7f493 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -217,8 +217,16 @@ consensusState wdb hdr } | otherwise = do db <- getWebBlockHeaderDb wdb hdr + -- `hdr` is not in the database as it's not been validated yet. + -- its parent is though, so we need to look up its ancestors via + -- its parent. Parent phdr <- lookupParentHeader wdb hdr safeHdr <- + -- seekAncestor ordinarily returns its argument if it already has + -- the requested height. however we are looking up the ancestor + -- based on the parent of the latest header, so if the safe height + -- is the height of the latest header, we would not find it. thus + -- we have an extra case for it here. if int safeHeight == view blockHeight hdr then return hdr else fromJuste <$> seekAncestor db phdr safeHeight finalHdr <- if int finalHeight == view blockHeight hdr then return hdr From 37889e8559d0e67d5029fd413f99a17aae768640 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 3 Jun 2025 14:24:02 -0400 Subject: [PATCH 223/378] Add Brief (These a b) instance Change-Id: Id0000000ed99ccb169d20e1c82392d7caeaebc07 --- src/Chainweb/Core/Brief.hs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index fd17ddeb74..065c9a8104 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -49,6 +49,7 @@ import Data.List qualified as L import Data.List.NonEmpty (NonEmpty) import Data.Text qualified as T import Numeric.Natural +import Data.These -- -------------------------------------------------------------------------- -- -- Adhoc class for brief logging output @@ -72,6 +73,11 @@ instance (Brief a, Brief b) => Brief (Either a b) where brief (Left a) = "left:" <> brief a brief (Right b) = "right:" <> brief b +instance (Brief a, Brief b) => Brief (These a b) where + brief (This a) = "this:" <> brief a + brief (That b) = "that:" <> brief b + brief (These a b) = "these:(" <> brief a <> "):(" <> brief b <> ")" + instance Brief a => Brief (Maybe a) where brief (Just a) = brief a brief Nothing = "nothing" From 04c81f1c2a8115ba0c9870b9898b626e89b21afc Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 3 Jun 2025 14:58:06 -0400 Subject: [PATCH 224/378] Fix Pact PP fork resolution Change-Id: Id00000004fff1e1eabf1711fa760fc3322b5c6d2 --- src/Chainweb/Pact/PactService.hs | 27 ++++++++++++++---------- src/Chainweb/Sync/WebBlockHeaderStore.hs | 20 ++++++++++++------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index ad1265e7f3..42c8e8ce1a 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -121,6 +121,8 @@ import qualified Data.List.NonEmpty as NEL import qualified Control.Parallel.Strategies as Strategies import qualified Chainweb.Pact.NoCoinbase as Pact import Chainweb.Core.Brief +import Data.Align +import qualified Data.List.NonEmpty as NE withPactService :: (Logger logger, CanPayloadCas tbl) @@ -509,21 +511,22 @@ syncToFork logger serviceEnv hints forkInfo = do Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) else do - let traceBlockHashes = + let traceBlockHashesAscending = drop 1 (unwrapParent . _evaluationCtxRankedParentHash <$> forkInfo._forkInfoTrace) <> [_syncStateRankedBlockHash forkInfo._forkInfoTargetState._consensusStateLatest] logFunctionText logger Debug $ "playing blocks to move to " <> brief forkInfo._forkInfoTargetState - <> " using trace blocks " <> brief traceBlockHashes - findForkChain (zip forkInfo._forkInfoTrace traceBlockHashes) >>= \case + <> " using trace blocks " <> brief traceBlockHashesAscending + findForkChainAscending (reverse $ zip forkInfo._forkInfoTrace traceBlockHashesAscending) >>= \case Nothing -> do logFunctionText logger Info $ "impossible to move to " <> brief forkInfo._forkInfoTargetState - <> " from " <> brief pactConsensusState <> " with trace " <> brief forkInfo._forkInfoTrace + <> " from " <> brief pactConsensusState <> " with trace " <> brief (align forkInfo._forkInfoTrace traceBlockHashesAscending) -- error: we have no way to get to the target block. just report -- our current state and do nothing else. return (mempty, mempty, pactConsensusState) Just forkChainBottomToTop -> do + logFunctionText logger Debug $ "fork chain found: " <> brief forkChainBottomToTop rewoundTxs <- getRewoundTxs (Parent $ forkInfo._forkInfoTargetState._consensusStateLatest._syncStateHeight) -- the happy case: we can find a way to get to the target block -- look up all of the payloads to see if we've run them before @@ -591,14 +594,16 @@ syncToFork logger serviceEnv hints forkInfo = do pdb = view psPdb serviceEnv cid = _chainId serviceEnv - findForkChain - :: [(EvaluationCtx p, RankedBlockHash)] + findForkChainAscending + :: Brief p + => [(EvaluationCtx p, RankedBlockHash)] -> IO (Maybe (NEL.NonEmpty (EvaluationCtx p, RankedBlockHash))) - findForkChain [] = return Nothing - findForkChain (tip:chain) = go [] (tip:chain) + findForkChainAscending [] = return Nothing + findForkChainAscending (tip:chain) = go [] (tip:chain) where go - :: [(EvaluationCtx p, RankedBlockHash)] + :: Brief p + => [(EvaluationCtx p, RankedBlockHash)] -> [(EvaluationCtx p, RankedBlockHash)] -> IO (Maybe (NEL.NonEmpty (EvaluationCtx p, RankedBlockHash))) go !acc (tip':chain') = do @@ -618,11 +623,11 @@ syncToFork logger serviceEnv hints forkInfo = do <> brief (printable tip') go (tip' : acc) chain' go _ [] = do - logFunctionText logger Debug $ + logFunctionText logger Info $ "no fork point found for chain: " <> brief (printable <$> (tip:chain)) return Nothing - printable (a, b) = (_evaluationCtxRankedParentHash a, b) + printable (a, b) = (a, b) -- remember to call this *before* executing the actual rewind, -- and only alter the mempool *after* the db transaction is done. diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index e0a8d7f493..329c2a9f3c 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -85,7 +85,7 @@ import System.LogLevel import Utils.Logging.Trace import Chainweb.Parent import Streaming.Prelude qualified as S -import Data.These (partitionHereThere) +import Data.These (partitionHereThere, These (..)) import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- @@ -499,12 +499,15 @@ getBlockHeaderInternal then do bhdb <- getWebBlockHeaderDb wdb cid let ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest r - let targetRBH = _syncStateRankedBlockHash $ _consensusStateLatest $ _forkInfoTargetState finfo - ppBlock <- lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH) - targetBlock <- lookupRankedM bhdb (int $ _rankedHeight targetRBH) (_ranked targetRBH) + ppBlock <- lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH) `catch` \case + e@(TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do + logfun Warn $ "PP block is missing: " <> brief ppRBH + throwM e + e -> throwM e + (forkBlocksDescendingStream S.:> forkPoint) <- - S.toList $ branchDiff_ bhdb ppBlock targetBlock - let forkBlocksAscending = reverse $ snd $ partitionHereThere forkBlocksDescendingStream + S.toList $ branchDiff_ bhdb ppBlock (unwrapParent parentHdr) + let forkBlocksAscending = reverse $ snd $ partitionHereThere (That header : forkBlocksDescendingStream) let newTrace = zipWith (\prent child -> @@ -520,7 +523,10 @@ getBlockHeaderInternal throwM $ GetBlockHeaderFailure $ "unexpected result state" <> "; expected: " <> brief (_forkInfoTargetState finfo) <> "; actual: " <> brief r - <> "fork blocks: " <> brief forkBlocksAscending + <> "; PP latest block: " <> brief ppBlock + <> "; target block: " <> brief header + <> "; target block parent: " <> brief (unwrapParent parentHdr) + <> "; fork blocks: " <> brief forkBlocksAscending else return () DisabledPayloadProvider -> do From 98894a1ea2f35ec374b429a3cd66e2c6eb857fce Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 3 Jun 2025 15:14:27 -0400 Subject: [PATCH 225/378] Revert "Fix ancestor lookup in consensusState v2" This reverts commit 11d5a11f736bffa7c2e73a5c10a5a29ddb726bb0. --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 329c2a9f3c..c5938ba9ae 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -217,20 +217,9 @@ consensusState wdb hdr } | otherwise = do db <- getWebBlockHeaderDb wdb hdr - -- `hdr` is not in the database as it's not been validated yet. - -- its parent is though, so we need to look up its ancestors via - -- its parent. Parent phdr <- lookupParentHeader wdb hdr - safeHdr <- - -- seekAncestor ordinarily returns its argument if it already has - -- the requested height. however we are looking up the ancestor - -- based on the parent of the latest header, so if the safe height - -- is the height of the latest header, we would not find it. thus - -- we have an extra case for it here. - if int safeHeight == view blockHeight hdr then return hdr - else fromJuste <$> seekAncestor db phdr safeHeight - finalHdr <- if int finalHeight == view blockHeight hdr then return hdr - else fromJuste <$> seekAncestor db phdr finalHeight + safeHdr <- fromJuste <$> seekAncestor db phdr safeHeight + finalHdr <- fromJuste <$> seekAncestor db phdr finalHeight return ConsensusState { _consensusStateLatest = syncStateOfBlockHeader hdr , _consensusStateSafe = syncStateOfBlockHeader safeHdr @@ -251,7 +240,6 @@ consensusState wdb hdr -- forkInfoForHeader :: HasVersion - => HasCallStack => WebBlockHeaderDb -> BlockHeader -> Maybe EncodedPayloadData @@ -493,7 +481,7 @@ getBlockHeaderInternal case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do - logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation for " <> brief header <> " failed with : " <> sshow e + logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation for " <> sshow h <> " failed with : " <> sshow e throwM e if r /= _forkInfoTargetState finfo then do From 74c260e9a0071742ee6b5d5bc456d19e46563608 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 3 Jun 2025 15:13:05 -0400 Subject: [PATCH 226/378] Fix consensusState safeHeight calculation --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index c5938ba9ae..b8da903df3 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -229,7 +229,7 @@ consensusState wdb hdr WindowWidth w = _versionWindow implicitVersion cid = _chainId hdr finalHeight = int @Int @_ $ max (int $ genesisHeight cid) (int height - int w * 4) - safeHeight = int @Int @_ $ max (int $ genesisHeight cid) (int height - 6 * int diam) + safeHeight = int @Int @_ $ max (int $ genesisHeight cid) (int height - 6 * (int diam + 1)) height = view blockHeight hdr diam = diameterAt height From 8270e662a2636fa702de0601a080fe24d34df56c Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 4 Jun 2025 15:11:09 -0400 Subject: [PATCH 227/378] fork resolution on startup Change-Id: Id0000000d4951d048a44b5125ed5989baa634712 --- src/Chainweb/Chainweb.hs | 44 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 35b1e3cc5a..a647a6472e 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -154,9 +154,11 @@ import Chainweb.OpenAPIValidation qualified as OpenAPIValidation import Chainweb.Pact.RestAPI.Server (PactServerData(..)) import Chainweb.Pact.Types (PactServiceConfig(..)) import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Parent import Chainweb.Payload.PayloadStore import Chainweb.Payload.PayloadStore.RocksDB import Chainweb.PayloadProvider +import Chainweb.Ranked import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID import Chainweb.Storage.Table.RocksDB @@ -173,6 +175,10 @@ import P2P.Node.PeerDB (PeerDb) import P2P.Peer import qualified Pact.Core.Gas as Pact import qualified Chainweb.PayloadProvider.Pact.Genesis as Pact +import qualified Streaming.Prelude as S +import Chainweb.TreeDB +import Data.These +import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- -- Chainweb Resources @@ -537,13 +543,43 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba r <- syncToBlock provider Nothing finfo `catch` \(e :: SomeException) -> do logFunctionText loggr Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e throwM e - unless (r == _forkInfoTargetState finfo) $ do - logFunctionText loggr Error $ "unexpectedResult" - error "Chainweb.Chainweb.synchronizeProviders: unexpected result state" - -- FIXME + when (r /= _forkInfoTargetState finfo) $ do + logFunctionText loggr Info + $ "resolving fork on startup, from " <> brief r + <> " to " <> brief (_forkInfoTargetState finfo) + bhdb <- getWebBlockHeaderDb wbh cid + let ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest r + ppBlock <- lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH) `catch` \case + e@(TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do + logFunctionText loggr Warn $ "PP block is missing: " <> brief ppRBH + throwM e + e -> throwM e + + (forkBlocksDescendingStream S.:> forkPoint) <- + S.toList $ branchDiff_ bhdb ppBlock hdr + let forkBlocksAscending = reverse $ snd $ partitionHereThere forkBlocksDescendingStream + let newTrace = + zipWith + (\prent child -> + ConsensusPayload (view blockPayloadHash child) Nothing <$ + blockHeaderToEvaluationCtx (Parent prent)) + (forkPoint : forkBlocksAscending) + forkBlocksAscending + let newForkInfo = finfo { _forkInfoTrace = newTrace } + r' <- syncToBlock provider Nothing newForkInfo + unless (r' == _forkInfoTargetState finfo) $ do + error $ T.unpack + $ "unexpected result state" + <> "; expected: " <> brief (_forkInfoTargetState finfo) + <> "; actual: " <> brief r + <> "; PP latest block: " <> brief ppBlock + <> "; target block: " <> brief hdr + <> "; fork blocks: " <> brief forkBlocksAscending DisabledPayloadProvider -> do logFunctionText logger Info $ "payload provider disabled, not synced, on chain: " <> toText (_chainId hdr) + where + cid = _chainId hdr -- synchronizePactDb :: HM.HashMap ChainId (ChainResources logger) -> Cut -> IO () -- synchronizePactDb cs targetCut = do From eb18ecd067faaad6d543f6eafb5ff000eca7d00f Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 5 Jun 2025 08:56:26 -0700 Subject: [PATCH 228/378] set withdrawalIndex in EVM payloads to block height --- src/Chainweb/PayloadProvider/EVM.hs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 5da315663d..81e479c393 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -952,7 +952,7 @@ updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case Just x -> writeTMVar (_evmPayloadId p) x where lf = loggS p "updateEvm" - attr pt = mkPayloadAttributes (_latestBlockHash state) pt + attr pt = mkPayloadAttributes (_latestHeight state) (_latestBlockHash state) pt <$> _evmMinerAddress p <*> nctx @@ -969,16 +969,19 @@ updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case return $ EVM._hdrTimestamp $ _payloadHeader pld mkPayloadAttributes - :: Chainweb.BlockHash - -- ^ ParentBeaconBlockRoo, i.e. the Chainweb block hash of the parent of + :: BlockHeight + -- ^ ParentBlockHeight, i.e. the Chainweb block height of the parent of + -- the new block. + -> Chainweb.BlockHash + -- ^ ParentBeaconBlockRoot, i.e. the Chainweb block hash of the parent of -- the new block. -> Timestamp -- ^ The timestamp of the /parent/ EVM header. -> EVM.Address -> NewBlockCtx -> PayloadAttributesV3 -mkPayloadAttributes ph parentTimestamp addr nctx = PayloadAttributesV3 - { _payloadAttributesV3parentBeaconBlockRoot = EVM.chainwebBlockHashToBeaconBlockRoot ph +mkPayloadAttributes pheight phash parentTimestamp addr nctx = PayloadAttributesV3 + { _payloadAttributesV3parentBeaconBlockRoot = EVM.chainwebBlockHashToBeaconBlockRoot phash , _payloadAttributesV2 = PayloadAttributesV2 { _payloadAttributesV2Withdrawals = [withdrawal] , _payloadAttributesV1 = PayloadAttributesV1 @@ -992,7 +995,7 @@ mkPayloadAttributes ph parentTimestamp addr nctx = PayloadAttributesV3 MinerReward reward = _newBlockCtxMinerReward nctx withdrawal = WithdrawalV1 { _withdrawalValidatorIndex = 0 - , _withdrawalIndex = 0 + , _withdrawalIndex = int pheight + 1 , _withdrawalAmount = int $ reward , _withdrawalAddress = addr } From 32264a011872ec872d4f83573f07852081f85a85 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 4 Jun 2025 20:05:54 -0700 Subject: [PATCH 229/378] Remove CI builds for 9.8.4 to speed up github actions builds --- .github/workflows/applications.yml | 10 ++++---- cabal.project.freeze | 37 +++++++++++++++--------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index 39559686c5..3062d89662 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -135,13 +135,13 @@ jobs: MATRIX="$(jq -c '.' < Date: Thu, 5 Jun 2025 09:06:11 -0700 Subject: [PATCH 230/378] update freeze file --- cabal.project.freeze | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/cabal.project.freeze b/cabal.project.freeze index e7778e3220..4bfbde3cdd 100644 --- a/cabal.project.freeze +++ b/cabal.project.freeze @@ -60,7 +60,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.binary-orphans ==1.0.5, any.bitvec ==1.1.5.0, bitvec +simd, - any.blaze-builder ==0.4.2.3, + any.blaze-builder ==0.4.3, any.blaze-html ==0.9.2.0, any.blaze-markup ==0.8.3.0, any.boring ==0.2.2, @@ -92,7 +92,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.character-ps ==0.1, any.charset ==0.3.12, any.chronos ==1.1.6.2, - any.citeproc ==0.8.1.3, + any.citeproc ==0.9.0.1, citeproc -executable -icu, any.clock ==0.8.4, clock -llvm, @@ -176,7 +176,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.fast-logger ==3.2.5, any.file-embed ==0.0.16.0, any.filepath ==1.5.4.0, - any.fingertree ==0.1.5.0, + any.fingertree ==0.1.6.1, any.finite-typelits ==0.2.1.0, any.free ==5.2, any.generic-arbitrary ==1.0.1.2, @@ -185,6 +185,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.generic-lens ==2.2.2.0, any.generic-lens-core ==2.2.1.0, any.generically ==0.1.1, + any.generics-sop ==0.5.1.4, any.ghc-bignum ==1.3, any.ghc-boot-th ==9.10.2, any.ghc-compact ==0.1.0.0, @@ -253,7 +254,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.lsp-types ==2.3.0.1, lsp-types -force-ospath, any.managed ==1.0.10, - any.massiv ==1.0.4.1, + any.massiv ==1.0.5.0, massiv -unsafe-checks, any.math-functions ==0.3.4.4, math-functions +system-erf +system-expm1, @@ -281,7 +282,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.network ==3.2.7.0, network -devel, any.network-byte-order ==0.1.7, - any.network-control ==0.1.6, + any.network-control ==0.1.7, any.network-info ==0.2.1, any.network-uri ==2.6.4.2, any.nothunks ==0.3.0.0, @@ -299,7 +300,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, pact-time -with-time, any.pact-tng ==5.2, pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, - any.pandoc ==3.6.4, + any.pandoc ==3.7.0.2, pandoc -embed_data_files, any.pandoc-types ==1.23.1, any.parallel ==3.2.2.0, @@ -328,7 +329,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.property-matchers ==0.7.0.0, any.psqueues ==0.2.8.1, any.pvar ==1.0.0.0, - any.quickcheck-instances ==0.3.32, + any.quickcheck-instances ==0.3.33, any.ralist ==0.4.0.0, any.random ==1.3.1, any.raw-strings-qq ==1.1, @@ -340,7 +341,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.regex ==1.1.0.2, any.regex-base ==0.94.0.3, any.regex-pcre-builtin ==0.95.2.3.8.44, - any.regex-tdfa ==1.3.2.3, + any.regex-tdfa ==1.3.2.4, regex-tdfa +doctest -force-o2, any.resource-pool ==0.4.0.0, any.resourcet ==1.3.0, @@ -367,10 +368,10 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, semirings +containers +unordered-containers, any.serialise ==0.2.6.1, serialise +newtime15, - any.servant ==0.20.2, - any.servant-client ==0.20.2, - any.servant-client-core ==0.20.2, - any.servant-server ==0.20.2, + any.servant ==0.20.3.0, + any.servant-client ==0.20.3.0, + any.servant-client-core ==0.20.3.0, + any.servant-server ==0.20.3.0, any.sha-validation ==0.1.0.1, any.show-combinators ==0.2.0.0, any.simple-sendfile ==0.2.32, @@ -421,7 +422,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.terminal-progress-bar ==0.4.2, any.terminal-size ==0.3.4, any.terminfo ==0.4.1.7, - any.texmath ==0.12.10, + any.texmath ==0.12.10.3, texmath -executable -server, any.text ==2.1.2, any.text-conversions ==0.3.1.1, @@ -442,8 +443,8 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.time-compat ==1.9.8, any.time-locale-compat ==0.1.1.5, time-locale-compat +old-locale, - any.time-manager ==0.2.2, - any.tls ==2.1.9, + any.time-manager ==0.2.3, + any.tls ==2.1.10, tls -devel, any.tls-session-manager ==0.0.8, any.token-bucket ==0.1.0.1, @@ -458,9 +459,9 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.trifecta ==2.1.4, any.tuples ==0.1.0.0, any.typed-process ==0.2.13.0, - any.typst ==0.7, + any.typst ==0.8.0.1, typst -executable, - any.typst-symbols ==0.1.7, + any.typst-symbols ==0.1.8.1, any.unicode-collation ==0.1.3.6, unicode-collation -doctests -executable, any.unicode-data ==0.6.0, @@ -501,7 +502,7 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, any.wai-logger ==2.5.0, any.wai-middleware-throttle ==0.3.0.1, any.wai-middleware-validation ==0.1.0.2, - any.warp ==3.4.7, + any.warp ==3.4.8, warp +allow-sendfilefd -network-bytestring -warp-debug +x509, any.warp-tls ==3.4.13, any.wherefrom-compat ==0.1.1.1, @@ -523,4 +524,4 @@ constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, zip-archive -executable, any.zlib ==0.7.1.0, zlib -bundled-c-zlib +non-blocking-ffi +pkg-config -index-state: hackage.haskell.org 2025-05-02T15:38:56Z +index-state: hackage.haskell.org 2025-06-05T15:56:06Z From 9938be7650ed0aae1acf06f1f419ed2acaa2c3df Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 5 Jun 2025 15:30:21 -0400 Subject: [PATCH 231/378] Faster genesisHeight calc Change-Id: Id00000004bbbaefabb706f8ce15ce299b916598c --- src/Chainweb/BlockHeader/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index efa2bfa784..365e9e7466 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -743,7 +743,7 @@ genesisBlockHeadersAtHeight h = -- It is enforced via the 'mkChainId' smart constructor for ChainId.) -- genesisHeight :: (HasVersion, HasCallStack) => ChainId -> BlockHeight -genesisHeight c = _blockHeight (genesisBlockHeader c) +genesisHeight c = genesisBlockHeight c instance HasVersion => HasMerkleLog ChainwebMerkleHashAlgorithm ChainwebHashTag BlockHeader where From 55de1050f448f9a4b67ab0399276cf8a0e69fcee Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 5 Jun 2025 12:33:24 -0400 Subject: [PATCH 232/378] ChainId derive newtype Change-Id: Id0000000b7c10bea6c1dd66d95c2bcba718a2e52 --- src/Chainweb/ChainId.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index 3f2d4b9ffb..2d41508ecf 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -134,7 +134,7 @@ instance Exception ChainIdException newtype ChainId :: Type where ChainId' :: Word32 -> ChainId deriving stock (Show, Read, Eq, Ord, Generic) - deriving anyclass (Hashable, ToJSON, FromJSON, NFData) + deriving newtype (Hashable, ToJSON, FromJSON, NFData) pattern ChainId :: Word32 -> ChainId pattern ChainId n <- ChainId' n From 66b20a54d3488ea0c73cffb35392ebae6771f832 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 9 Jun 2025 12:51:27 -0400 Subject: [PATCH 233/378] Fix payload refresh in EVM Change-Id: Id0000000cdc2ae11e7d1dd855f4a9d31f7a8262d --- src/Chainweb/PayloadProvider/EVM.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 81e479c393..4c6d8df8f4 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1073,7 +1073,7 @@ awaitNewPayload p = do atomically $ Nothing <$ (readTVar timeout >>= guard) <|> - Just <$> takeTMVar (_evmPayloadId p) + Just <$> readTMVar (_evmPayloadId p) -- process the new payload go pid = do From b6eb77c99448f50087319392765e390efc5dd96a Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 9 Jun 2025 13:51:15 -0400 Subject: [PATCH 234/378] Take payload ID before FCU call Change-Id: Id0000000faf6a22224ec34917461fad9062c07bd --- src/Chainweb/PayloadProvider/EVM.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 4c6d8df8f4..6b1fad7338 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -925,6 +925,7 @@ updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case lf Info $ "Calling forkChoiceUpdate on state " <> briefJson state <> " with " <> briefJson fcs pt <- parentTimestamp + _ <- atomically $ tryTakeTMVar (_evmPayloadId p) pid <- forkchoiceUpdate p forkchoiceUpdatedTimeout fcs (attr pt) -- forkchoiceUpdate throws if it does not succeed. @@ -948,7 +949,7 @@ updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case writeTVar (_evmState p) (T2 state nctx) -- update payloadId case pid of - Nothing -> void $ tryTakeTMVar (_evmPayloadId p) + Nothing -> return () Just x -> writeTMVar (_evmPayloadId p) x where lf = loggS p "updateEvm" From e37504c248231ef4dcf0f1a38100ee99e89da36b Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 10 Jun 2025 11:56:56 -0400 Subject: [PATCH 235/378] Correct payload refresher thread in PactService The old code was not safe; the thread could be lost and not stopped in theory, if PactService was interrupted. Change-Id: Id00000001a7bf815abbbedd23159bd9c11a2bd00 --- src/Chainweb/Pact/PactService.hs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 42c8e8ce1a..335f30dbd9 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -41,6 +41,7 @@ module Chainweb.Pact.PactService import Control.Concurrent.Async import Control.Concurrent.STM +import Control.Exception.Safe (mask) import Control.Lens hiding ((:>)) import Control.Monad import Control.Monad.Cont (evalContT) @@ -230,16 +231,14 @@ runGenesisIfNeeded logger serviceEnv = do (genesisHeight cid) genesisPayload Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState - -- we have to kick off payload refreshing here emptyBlock <- (throwIfNoHistory =<<) $ Checkpointer.readFrom logger cid (_psReadWriteSql serviceEnv) (Parent gTime) (Parent genesisRankedBlockHash) $ \blockEnv blockHandle -> makeEmptyBlock logger serviceEnv blockEnv blockHandle - refresherThread <- liftIO $ async (refreshPayloads logger serviceEnv) - liftIO $ - atomically $ writeTMVar (_psMiningPayloadVar serviceEnv) (refresherThread, emptyBlock) + -- we have to kick off payload refreshing here first + startPayloadRefresher logger serviceEnv emptyBlock where cid = _chainId serviceEnv @@ -580,10 +579,7 @@ syncToFork logger serviceEnv hints forkInfo = do atomically (fmap (view _1) <$> tryTakeTMVar payloadVar) >>= traverse_ cancel - refresherThread <- liftIO $ async (refreshPayloads logger serviceEnv) - - liftIO $ - atomically $ writeTMVar payloadVar (refresherThread, emptyBlock) + startPayloadRefresher logger serviceEnv emptyBlock _ -> return () return newConsensusState @@ -642,6 +638,15 @@ syncToFork logger serviceEnv hints forkInfo = do (fmap (fromRight (error "invalid payload in database")) . runExceptT . pact5TransactionsFromPayload) rewoundPayloads +-- | Start a thread that makes fresh payloads periodically +startPayloadRefresher :: Logger logger => HasVersion => logger -> ServiceEnv tbl -> BlockInProgress -> IO () +startPayloadRefresher logger serviceEnv startBlock = + mask $ \restore -> do + refresherThread <- async (restore $ refreshPayloads logger serviceEnv) + atomically $ writeTMVar payloadVar (refresherThread, startBlock) + where + payloadVar = _psMiningPayloadVar serviceEnv + refreshPayloads :: Logger logger => HasVersion From c8527420f768cb7b3a63a4d8c68b35757369a650 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 11 Jun 2025 18:47:16 -0700 Subject: [PATCH 236/378] evm-genesis: fix number of chains and offsets of network ids. --- cwtools/evm-genesis/Main.hs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 1cc7fe6fea..020664ec4e 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -53,23 +53,23 @@ main = do (n, cids, spec) <- getArgs >>= \case [] -> error "No argument for the chainweb version provided. The version must be one of: 'mainnet', 'testnet', 'evm-testnet', or 'evm-development'." ["mainnet"] -> do - let cids = [20..25] + let cids = [20..24] return ("mainnet", cids, mainnetSpecFile) ["testnet"] -> do - let cids = [20..25] + let cids = [20..24] return ("testnet", cids, testnetSpecFile) ["evm-testnet"] -> do - let cids = [20..25] + let cids = [20..24] return ("evm-testnet", cids, evmTestnetSpecFile) ["evm-development"] -> do - let cids = [20..25] - return ("evm-development", cids, evmDevnetSpecFile) + let cids = [20..24] + return ("evm-development", cids, evmDevnetSpecFile 20) ["evm-development-singleton"] -> do let cids = [0] - return ("evm-development-singleton", cids, evmDevnetSpecFile) + return ("evm-development-singleton", cids, evmDevnetSpecFile 0) ["evm-development-pair"] -> do let cids = [1] - return ("evm-development-pair", cids, evmDevnetSpecFile) + return ("evm-development-pair", cids, evmDevnetSpecFile 1) _ -> error "Invalid argument for the chainweb version provided. The version must be one of: 'mainnet', 'testnet', 'evm-testnet', or 'evm-development'." hdrs <- forM cids $ \cid -> do @@ -173,11 +173,13 @@ mkRpcCtx u = do -- evmDevnetSpecFile :: Natural + -- ^ offset for the chain id + -> Natural -- numeric chainweb chain id -> Value -evmDevnetSpecFile cid = object [ +evmDevnetSpecFile offset cid = object [ "config" .= object [ - "chainId" .= (1789 + cid - 20), + "chainId" .= (1789 + cid - offset), "daoForkSupport" .= True, "terminalTotalDifficultyPassed" .= True, "terminalTotalDifficulty" .= i 0, From dc8fd41427a9206ef04e9b6a6c90955cf831e9a5 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 11 Jun 2025 18:51:13 -0700 Subject: [PATCH 237/378] evm-genesis: fix chainweb-chain-id system contract --- cwtools/evm-genesis/Main.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 020664ec4e..244c9795c3 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -209,7 +209,7 @@ evmDevnetSpecFile offset cid = object [ "alloc".= object [ "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ "balance".= t "0x0", - "code".= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", + "code" .= t "0x5f545f526004601cf3", "storage".= object [ "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) ] From a86f9af1bf0f5cc35b743d342e16c064e16feee9 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 11 Jun 2025 18:52:12 -0700 Subject: [PATCH 238/378] evm-genesis: fix format in aeson instances --- cwtools/evm-genesis/Main.hs | 102 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 244c9795c3..be5f66ee06 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -203,76 +203,76 @@ evmDevnetSpecFile offset cid = object [ "cancunTime" .= i 0, "pragueTime" .= i 0 ], - "timestamp".= t "0x6490fdd2", - "extraData".= t "0x", - "gasLimit".= t "0x1c9c380", - "alloc".= object [ - "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ - "balance".= t "0x0", + "timestamp" .= t "0x6490fdd2", + "extraData" .= t "0x", + "gasLimit" .= t "0x1c9c380", + "alloc" .= object [ + "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ + "balance" .= t "0x0", "code" .= t "0x5f545f526004601cf3", - "storage".= object [ + "storage" .= object [ "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) ] ], -- TODO: add native-x-chain system contract - "0x8849BAbdDcfC1327Ad199877861B577cEBd8A7b6".= object [ + "0x8849BAbdDcfC1327Ad199877861B577cEBd8A7b6" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xFB8Fb7f9bdc8951040a6D195764905138F7462Ed".= object [ + "0xFB8Fb7f9bdc8951040a6D195764905138F7462Ed" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0x28f2d8ef4e0fe6B2E945cF5C33a0118a30a62354".= object [ + "0x28f2d8ef4e0fe6B2E945cF5C33a0118a30a62354" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xa24a79678c9fffEF3E9A1f3cb7e51f88F173B3D5".= object [ + "0xa24a79678c9fffEF3E9A1f3cb7e51f88F173B3D5" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0x47fAE86F6416e6115a80635238AFd2F18D69926B".= object [ + "0x47fAE86F6416e6115a80635238AFd2F18D69926B" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0x87466A8266b9DFB3Dc9180a9c43946c4AB2c2cb2".= object [ + "0x87466A8266b9DFB3Dc9180a9c43946c4AB2c2cb2" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xA310Df9740eb6CC2F5E41C59C87e339142834eA4".= object [ + "0xA310Df9740eb6CC2F5E41C59C87e339142834eA4" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xD4EECE51cf451b60F59b271c5a748A8a9F16bC01".= object [ + "0xD4EECE51cf451b60F59b271c5a748A8a9F16bC01" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xE08643a1C4786b573d739625FD268732dBB3d033".= object [ + "0xE08643a1C4786b573d739625FD268732dBB3d033" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0x33018A42499f10B54d9dBCeBB71831C805D64cE3".= object [ + "0x33018A42499f10B54d9dBCeBB71831C805D64cE3" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xa3659D39C901d5985450eE18a63B5b0811fDa521".= object [ + "0xa3659D39C901d5985450eE18a63B5b0811fDa521" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0x7e99c2f1731D3750b74A2a0623C1F1DcB8cCa45e".= object [ + "0x7e99c2f1731D3750b74A2a0623C1F1DcB8cCa45e" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xFd70Bef78778Ce8554e79D97521b69183960C574".= object [ + "0xFd70Bef78778Ce8554e79D97521b69183960C574" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xEE2722c39db6014Eacc5FBe43601136825b00977".= object [ + "0xEE2722c39db6014Eacc5FBe43601136825b00977" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xeDD5a9185F9F1C04a011117ad61564415057bf8F".= object [ + "0xeDD5a9185F9F1C04a011117ad61564415057bf8F" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0x99b832eb3F76ac3277b00beADC1e487C594ffb4c".= object [ + "0x99b832eb3F76ac3277b00beADC1e487C594ffb4c" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xda1380825f827C6Ea92DFB547EF0a341Cbe21d77".= object [ + "0xda1380825f827C6Ea92DFB547EF0a341Cbe21d77" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0xc201d4A5E6De676938533A0997802634E859e78b".= object [ + "0xc201d4A5E6De676938533A0997802634E859e78b" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0x03e95Af0fC4971EdCa12E6d2d1540c28314d15d5".= object [ + "0x03e95Af0fC4971EdCa12E6d2d1540c28314d15d5" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ], - "0x3492DA004098d728201fD82657f1207a6E5426bd".= object [ + "0x3492DA004098d728201fD82657f1207a6E5426bd" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ] ], @@ -335,14 +335,14 @@ evmTestnetSpecFile cid = object [ "cancunTime" .= i 0, "pragueTime" .= i 0 ], - "timestamp".= t "0x6490fdd2", - "extraData".= t "0x", - "gasLimit".= t "0x1c9c380", - "alloc".= object [ - "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ - "balance".= t "0x0", - "code".= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", - "storage".= object [ + "timestamp" .= t "0x6490fdd2", + "extraData" .= t "0x", + "gasLimit" .= t "0x1c9c380", + "alloc" .= object [ + "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ + "balance" .= t "0x0", + "code" .= t "0x5f545f526004601cf3", + "storage" .= object [ "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) ] ] @@ -406,14 +406,14 @@ mainnetSpecFile cid = object [ "cancunTime" .= i 0, "pragueTime" .= i 0 ], - "timestamp".= t "0x6490fdd2", - "extraData".= t "0x", - "gasLimit".= t "0x1c9c380", - "alloc".= object [ - "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ - "balance".= t "0x0", - "code".= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", - "storage".= object [ + "timestamp" .= t "0x6490fdd2", + "extraData" .= t "0x", + "gasLimit" .= t "0x1c9c380", + "alloc" .= object [ + "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ + "balance" .= t "0x0", + "code" .= t "0x5f545f526004601cf3", + "storage" .= object [ "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) ] ], @@ -482,14 +482,14 @@ testnetSpecFile cid = object [ "cancunTime" .= i 0, "pragueTime" .= i 0 ], - "timestamp".= t "0x6490fdd2", - "extraData".= t "0x", - "gasLimit".= t "0x1c9c380", - "alloc".= object [ - "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc".= object [ - "balance".= t "0x0", - "code".= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", - "storage".= object [ + "timestamp" .= t "0x6490fdd2", + "extraData" .= t "0x", + "gasLimit" .= t "0x1c9c380", + "alloc" .= object [ + "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ + "balance" .= t "0x0", + "code" .= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", + "storage" .= object [ "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) ] ], From 4a0d634dc0dec9e63369cc2400f1a82a60564242 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 11 Jun 2025 18:53:18 -0700 Subject: [PATCH 239/378] Update genesis data for EVM payload provider --- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 14 +++++++------- src/Chainweb/Version/EvmDevelopment.hs | 10 +++++----- src/Chainweb/Version/EvmDevelopmentSingleton.hs | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 5ca43bcc5f..5d2be5c76c 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -82,23 +82,23 @@ genesisBlocks c = go (_versionCode implicitVersion) (_chainId c) go v | v == _versionCode EvmDevelopmentSingleton = \case ChainId 0 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKubUszUzclOu3VLZ6Oz-L6Nph9pRr7Ilc6Sy0N9diB6oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoDNRqqHU3RpYNNK87jvHr0Nrl0zaMEylNVRiAm6cSPMAoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopmentPair = \case ChainId 1 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKtIkoG2zy4Z3rQ8TVs8INePmK-1cYAD8aBvkf-57uIVoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoO4IB2_chJ_NuzlG69CajkvHzxNR4zX19ioL4X2oKS17oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopment = \case ChainId 20 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGnV3kPaffv02pFz2XaK1abwEUqbp-L5K03hpucRJqlToFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIOOAysXKLGptB5GO1vKMMrTE7cWB14Ft14pZ7CGpFFloFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 21 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJSFmb3gp1R0kVepD6Cb9wKqd4mvZhjGCfLWOJYhfOtXoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoG85Bmgt7tyzR2m55gEFQFi_-0yVF-aBjFqWmRG8IOExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 22 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIHnBqDOlLrBUgs1mmDAGcyoZGAiI4MDWIm5TSP2DfXHoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoHm65lj2ks7TAHeTUt8sRrclRXGT2z2P-5FSk2TmLnN-oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 23 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOG8PeZNPmM3lrR8_XYZiV77h9JISltItpJrMg3uWzISoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBRUfqOU18XP_669WHx_2BouSX8LYvuO-kQRT5fQLN2ooFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 24 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoL_zw0Kq1MfRVs38rgQLmxq1zFk8ac976rfmBmNiPvc0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBOHYONSIUU7CyhAfW-z14OvuVy3OhGxgCMudl7HWsk7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | otherwise = error "requested genesis block for unsupported chain" diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index f9183ffb6c..d70552d456 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -90,11 +90,11 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider - , (unsafeChainId 20, unsafeFromText "FAxLDjtb8r_0S0Rfr8rD47EQwO-Ma-fmEynZccHvn5o") - , (unsafeChainId 21, unsafeFromText "RYPcKnqXKzSneT9zLC6OSGpQah48AeRWIVrSMbEYfcE") - , (unsafeChainId 22, unsafeFromText "IQLMke3si3QrlqKRyesUJr0iOdYFawl0UhPVXHYc6-M") - , (unsafeChainId 23, unsafeFromText "-dc_2udXDNRodCsLAX02kKVsnI-gQMeBZdsZHjxEkbw") - , (unsafeChainId 24, unsafeFromText "nWj_l1UK6k9hdMRV53WfNPEIHmUW2NFDpv0-iI2SnPQ") + , (unsafeChainId 20, unsafeFromText "JsA7DnXagbgNR_UQvlPv8juluB4bIJ1UwvnctkwxFqs") + , (unsafeChainId 21, unsafeFromText "ua1zzX1zYiGsdhgUwnJCFrpbZi3F1e0x8ZXSryRq0NE") + , (unsafeChainId 22, unsafeFromText "xgXdm3SBJ0pwcCR-3KzNGDy7VgS6YytBUZrNS2vYNOU") + , (unsafeChainId 23, unsafeFromText "7Aq2PRVKFYXyNG0a15-ZaLjPz9MTO9YWg2_c8Rhdf3c") + , (unsafeChainId 24, unsafeFromText "fpST-z8pu0KX7u-aA9ultehTlF8SdEOa4l4CyKwkG60") -- Minimal Payload Provider , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") diff --git a/src/Chainweb/Version/EvmDevelopmentSingleton.hs b/src/Chainweb/Version/EvmDevelopmentSingleton.hs index 6176e108a7..6c5fdafad0 100644 --- a/src/Chainweb/Version/EvmDevelopmentSingleton.hs +++ b/src/Chainweb/Version/EvmDevelopmentSingleton.hs @@ -77,7 +77,7 @@ evmDevnetPair = withVersion evmDevnetPair $ ChainwebVersion -- Pact Payload Provider [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") -- EVM Payload Provider - , (unsafeChainId 1, unsafeFromText "0ewfT8sFhCsJNKAF2EqmvfNZY1LpR2swW0yYK1F4oXs") + , (unsafeChainId 1, unsafeFromText "lEbS887A2BuK_Mdq6LwVmtRJAjbnq-MS6JQdUHcnOHU") ] } @@ -122,7 +122,7 @@ evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion [ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) ] , _genesisBlockPayload = onChains $ -- EVM Payload Provider - [ (unsafeChainId 0, unsafeFromText "Gmu77QPF9qmVbEAWPiIl1sH8JfRF5nZZWlYdhoXK5aI") ] + [ (unsafeChainId 0, unsafeFromText "odWrFJQGgWcWwHFpr7R5eTulJFyF8G-6eoUE6dFk4gM") ] } -- still the *default* block gas limit is set, see From 5ed0db47ab55cce2480dc022d4685831890e95ce Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 11 Jun 2025 23:06:12 -0400 Subject: [PATCH 240/378] Stop producing payloads in Pact when miner is unset Change-Id: Id0000000f9f3fb73664bc21624cfe1f5c7b73b95 --- src/Chainweb/Pact/PactService.hs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 335f30dbd9..25dfe2a05a 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -231,14 +231,15 @@ runGenesisIfNeeded logger serviceEnv = do (genesisHeight cid) genesisPayload Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState - emptyBlock <- (throwIfNoHistory =<<) $ - Checkpointer.readFrom logger cid - (_psReadWriteSql serviceEnv) - (Parent gTime) - (Parent genesisRankedBlockHash) $ - \blockEnv blockHandle -> makeEmptyBlock logger serviceEnv blockEnv blockHandle - -- we have to kick off payload refreshing here first - startPayloadRefresher logger serviceEnv emptyBlock + forM_ (_psMiner serviceEnv) $ \_ -> do + emptyBlock <- (throwIfNoHistory =<<) $ + Checkpointer.readFrom logger cid + (_psReadWriteSql serviceEnv) + (Parent gTime) + (Parent genesisRankedBlockHash) $ + \blockEnv blockHandle -> makeEmptyBlock logger serviceEnv blockEnv blockHandle + -- we have to kick off payload refreshing here first + startPayloadRefresher logger serviceEnv emptyBlock where cid = _chainId serviceEnv @@ -563,7 +564,8 @@ syncToFork logger serviceEnv hints forkInfo = do liftIO $ mpaProcessFork memPoolAccess (rewoundTxs, validatedTxs) case forkInfo._forkInfoNewBlockCtx of Just newBlockCtx - | _syncStateBlockHash (_consensusStateLatest newConsensusState) == + | Just _ <- _psMiner serviceEnv + , _syncStateBlockHash (_consensusStateLatest newConsensusState) == _latestBlockHash (forkInfo._forkInfoTargetState) -> do -- if we're at the target block we were sent, and we were -- told to start mining, we produce an empty block From 69c988e6c1f2d3cd4a5f8cc734cdeb8de9376faa Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 13 Jun 2025 14:33:33 -0700 Subject: [PATCH 241/378] update evm version and add evm-testnet --- chainweb.cabal | 1 + cwtools/evm-genesis/Main.hs | 427 ++++++------------ src/Chainweb/PayloadProvider/EVM/Genesis.hs | 27 +- src/Chainweb/Version/Development.hs | 5 +- src/Chainweb/Version/EvmDevelopment.hs | 14 +- .../Version/EvmDevelopmentSingleton.hs | 8 +- src/Chainweb/Version/EvmTestnet.hs | 197 ++++++++ src/Chainweb/Version/Registry.hs | 18 +- 8 files changed, 389 insertions(+), 308 deletions(-) create mode 100644 src/Chainweb/Version/EvmTestnet.hs diff --git a/chainweb.cabal b/chainweb.cabal index 38ac6cd2ce..f10f98432c 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -289,6 +289,7 @@ library , Chainweb.Version.Development , Chainweb.Version.EvmDevelopment , Chainweb.Version.EvmDevelopmentSingleton + , Chainweb.Version.EvmTestnet , Chainweb.Version.Guards , Chainweb.Version.Mainnet , Chainweb.Version.RecapDevelopment diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index be5f66ee06..2556db47eb 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -156,6 +156,76 @@ mkRpcCtx u = do , E._jsonRpcHttpCtxMakeBearerToken = Nothing } +-- -------------------------------------------------------------------------- -- +-- Spec file settings + +baseSpecFile + :: Natural + -- ^ Ethereum network id offset + -> Natural + -- ^ offset for the chainweb chain id + -> Natural + -- ^ numeric chainweb chain id + -> Natural + -- ^ unix timestamp of the genesis block in textual hex encoding + -> [(Key, Value)] + -- ^ Allocations + -> Value +baseSpecFile netId offset cid genesisTime allocs = object + [ "config" .= object + [ "chainId" .= (netId + cid - offset) + , "daoForkSupport" .= True + , "terminalTotalDifficultyPassed" .= True + , "terminalTotalDifficulty" .= i 0 + , "daoForkBlock" .= i 0 + , "homesteadBlock" .= i 0 + , "eip150Block" .= i 0 + , "eip155Block" .= i 0 + , "eip158Block" .= i 0 + , "byzantiumBlock" .= i 0 + , "constantinopleBlock" .= i 0 + , "petersburgBlock" .= i 0 + , "istanbulBlock" .= i 0 + , "muirGlacierBlock" .= i 0 + , "berlinBlock" .= i 0 + , "londonBlock" .= i 0 + , "arrowGlacierBlock" .= i 0 + , "graphGlacierBlock" .= i 0 + , "mergeForkBlock" .= i 0 + , "mergeNetsplitBlock" .= i 0 + , "shanghaiTime" .= i 0 + , "cancunTime" .= i 0 + , "pragueTime" .= i 0 + ] + , "timestamp" .= printf @(Natural -> String) "0x%x" genesisTime + , "extraData" .= t "0x" + , "gasLimit" .= t "0x1c9c380" + , "alloc" .= object (chainwebChainIdAlloc : allocs) + , "number" .= t "0x0" + , "nonce" .= t "0x0" + , "difficulty" .= t "0x0" + , "mixHash" .= zero32 @T.Text + , "coinbase" .= zero20 @T.Text + ] + where + i :: Natural -> Natural + i = id + + chainwebChainIdAlloc = "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object + [ "balance" .= t "0x0" + , "code" .= t "0x5f545f526004601cf3" + , "storage" .= object [ zero32 .= (printf "0x%064x" cid :: String) ] + ] + +zero32 :: IsString s => s +zero32 = "0x0000000000000000000000000000000000000000000000000000000000000000" + +zero20 :: IsString s => s +zero20 = "0x0000000000000000000000000000000000000000" + +t :: T.Text -> T.Text +t = id + -- -------------------------------------------------------------------------- -- -- Spec File For EVM Devnet @@ -177,117 +247,48 @@ evmDevnetSpecFile -> Natural -- numeric chainweb chain id -> Value -evmDevnetSpecFile offset cid = object [ - "config" .= object [ - "chainId" .= (1789 + cid - offset), - "daoForkSupport" .= True, - "terminalTotalDifficultyPassed" .= True, - "terminalTotalDifficulty" .= i 0, - "daoForkBlock" .= i 0, - "homesteadBlock" .= i 0, - "eip150Block" .= i 0, - "eip155Block" .= i 0, - "eip158Block" .= i 0, - "byzantiumBlock" .= i 0, - "constantinopleBlock" .= i 0, - "petersburgBlock" .= i 0, - "istanbulBlock" .= i 0, - "muirGlacierBlock" .= i 0, - "berlinBlock" .= i 0, - "londonBlock" .= i 0, - "arrowGlacierBlock" .= i 0, - "graphGlacierBlock" .= i 0, - "mergeForkBlock" .= i 0, - "mergeNetsplitBlock" .= i 0, - "shanghaiTime" .= i 0, - "cancunTime" .= i 0, - "pragueTime" .= i 0 - ], - "timestamp" .= t "0x6490fdd2", - "extraData" .= t "0x", - "gasLimit" .= t "0x1c9c380", - "alloc" .= object [ - "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ - "balance" .= t "0x0", - "code" .= t "0x5f545f526004601cf3", - "storage" .= object [ - "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) - ] - ], - -- TODO: add native-x-chain system contract - "0x8849BAbdDcfC1327Ad199877861B577cEBd8A7b6" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xFB8Fb7f9bdc8951040a6D195764905138F7462Ed" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0x28f2d8ef4e0fe6B2E945cF5C33a0118a30a62354" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xa24a79678c9fffEF3E9A1f3cb7e51f88F173B3D5" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0x47fAE86F6416e6115a80635238AFd2F18D69926B" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0x87466A8266b9DFB3Dc9180a9c43946c4AB2c2cb2" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xA310Df9740eb6CC2F5E41C59C87e339142834eA4" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xD4EECE51cf451b60F59b271c5a748A8a9F16bC01" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xE08643a1C4786b573d739625FD268732dBB3d033" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0x33018A42499f10B54d9dBCeBB71831C805D64cE3" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xa3659D39C901d5985450eE18a63B5b0811fDa521" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0x7e99c2f1731D3750b74A2a0623C1F1DcB8cCa45e" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xFd70Bef78778Ce8554e79D97521b69183960C574" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xEE2722c39db6014Eacc5FBe43601136825b00977" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xeDD5a9185F9F1C04a011117ad61564415057bf8F" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0x99b832eb3F76ac3277b00beADC1e487C594ffb4c" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xda1380825f827C6Ea92DFB547EF0a341Cbe21d77" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0xc201d4A5E6De676938533A0997802634E859e78b" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0x03e95Af0fC4971EdCa12E6d2d1540c28314d15d5" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ], - "0x3492DA004098d728201fD82657f1207a6E5426bd" .= object [ - "balance" .= t "0xd3c21bcecceda1000000" - ] - ], - "number" .= t "0x0", - "nonce" .= t "0x0", - "difficulty" .= t "0x0", - "mixHash" .= t "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase" .= t "0x0000000000000000000000000000000000000000" +evmDevnetSpecFile offset cid = baseSpecFile 1789 offset cid 0x684c5d2a + [ "0x8849BAbdDcfC1327Ad199877861B577cEBd8A7b6" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xFB8Fb7f9bdc8951040a6D195764905138F7462Ed" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0x28f2d8ef4e0fe6B2E945cF5C33a0118a30a62354" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xa24a79678c9fffEF3E9A1f3cb7e51f88F173B3D5" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0x47fAE86F6416e6115a80635238AFd2F18D69926B" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0x87466A8266b9DFB3Dc9180a9c43946c4AB2c2cb2" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xA310Df9740eb6CC2F5E41C59C87e339142834eA4" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xD4EECE51cf451b60F59b271c5a748A8a9F16bC01" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xE08643a1C4786b573d739625FD268732dBB3d033" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0x33018A42499f10B54d9dBCeBB71831C805D64cE3" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xa3659D39C901d5985450eE18a63B5b0811fDa521" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0x7e99c2f1731D3750b74A2a0623C1F1DcB8cCa45e" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xFd70Bef78778Ce8554e79D97521b69183960C574" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xEE2722c39db6014Eacc5FBe43601136825b00977" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xeDD5a9185F9F1C04a011117ad61564415057bf8F" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0x99b832eb3F76ac3277b00beADC1e487C594ffb4c" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xda1380825f827C6Ea92DFB547EF0a341Cbe21d77" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0xc201d4A5E6De676938533A0997802634E859e78b" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0x03e95Af0fC4971EdCa12E6d2d1540c28314d15d5" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + , "0x3492DA004098d728201fD82657f1207a6E5426bd" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] ] - where - t :: T.Text -> T.Text - t = id - - i :: Natural -> Natural - i = id -- -------------------------------------------------------------------------- -- -- Spec File For EVM Testnet @@ -307,206 +308,68 @@ evmDevnetSpecFile offset cid = object [ -- evmTestnetSpecFile :: Natural - -- ^ numeric chainweb chain id + -- numeric chainweb chain id -> Value -evmTestnetSpecFile cid = object [ - "config" .= object [ - "chainId" .= (1789 + cid - 20), - "daoForkSupport" .= True, - "terminalTotalDifficultyPassed" .= True, - "terminalTotalDifficulty" .= i 0, - "daoForkBlock" .= i 0, - "homesteadBlock" .= i 0, - "eip150Block" .= i 0, - "eip155Block" .= i 0, - "eip158Block" .= i 0, - "byzantiumBlock" .= i 0, - "constantinopleBlock" .= i 0, - "petersburgBlock" .= i 0, - "istanbulBlock" .= i 0, - "muirGlacierBlock" .= i 0, - "berlinBlock" .= i 0, - "londonBlock" .= i 0, - "arrowGlacierBlock" .= i 0, - "graphGlacierBlock" .= i 0, - "mergeForkBlock" .= i 0, - "mergeNetsplitBlock" .= i 0, - "shanghaiTime" .= i 0, - "cancunTime" .= i 0, - "pragueTime" .= i 0 - ], - "timestamp" .= t "0x6490fdd2", - "extraData" .= t "0x", - "gasLimit" .= t "0x1c9c380", - "alloc" .= object [ - "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ - "balance" .= t "0x0", - "code" .= t "0x5f545f526004601cf3", - "storage" .= object [ - "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) - ] - ] - -- TODO: Native-X-Chain System contract - -- TODO: funding of faucet - -- TODO: other allocations. - ], - - "number" .= t "0x0", - "nonce" .= t "0x0", - "difficulty" .= t "0x0", - "mixHash" .= t "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase" .= t "0x0000000000000000000000000000000000000000" +evmTestnetSpecFile cid = baseSpecFile 5920 20 cid 0x684c5d2a + -- faucet deployer address that corresponds to the DEPLOYER_PRIVATE_KEY + [ "0x9440d8ff19D278F401f49080BEfdDEFbE54F0eF2" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + -- is the faucet wallet address that correspondesds to the FAUCET_PRIVATE_KEY + , "0xE482e4F590D4155B51F4Fc21d64823f4d7854397" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] + -- additional platform funds that are separate from the faucet accounts + , "0xeC1B36992C3c7d0f7AbB8EDA43EEbC9A418c0A1e" .= object + [ "balance" .= t "0xd3c21bcecceda1000000" ] ] - where - t :: T.Text -> T.Text - t = id - - i :: Natural -> Natural - i = id + -- TODO: Native-X-Chain System contract -- -------------------------------------------------------------------------- -- --- Spec File For Kadena Mainnet +-- Spec File For Kadena Testnet --- | Used with the public Kadena Mainnet. +-- | Used with the public Kadena testnet. This is a permament testnet that +-- has the same features and properties of the Kadena mainnet. It has the +-- purpose to facilitate testing of services and applications under the same +-- conditions as on the Kadena mainnet. -- --- Allocations are funded out of the platform share of the Kadena mainnet. --- The keys for the Allocations are not publicly known. +-- Funds on the network have no economic value. +-- +-- The keys for the genesis allocations are held by the Kadena team. -- -- The EVM chains are launched at block height TODO. -- --- TODO +-- TODO: update genesis timestamp and block number -- -mainnetSpecFile +testnetSpecFile :: Natural - -- ^ numeric chainweb chain id + -- numeric chainweb chain id -> Value -mainnetSpecFile cid = object [ - "config" .= object [ - "chainId" .= (3789 + cid - 20), - "daoForkSupport" .= True, - "terminalTotalDifficultyPassed" .= True, - "terminalTotalDifficulty" .= i 0, - "daoForkBlock" .= i 0, - "homesteadBlock" .= i 0, - "eip150Block" .= i 0, - "eip155Block" .= i 0, - "eip158Block" .= i 0, - "byzantiumBlock" .= i 0, - "constantinopleBlock" .= i 0, - "petersburgBlock" .= i 0, - "istanbulBlock" .= i 0, - "muirGlacierBlock" .= i 0, - "berlinBlock" .= i 0, - "londonBlock" .= i 0, - "arrowGlacierBlock" .= i 0, - "graphGlacierBlock" .= i 0, - "mergeForkBlock" .= i 0, - "mergeNetsplitBlock" .= i 0, - "shanghaiTime" .= i 0, - "cancunTime" .= i 0, - "pragueTime" .= i 0 - ], - "timestamp" .= t "0x6490fdd2", - "extraData" .= t "0x", - "gasLimit" .= t "0x1c9c380", - "alloc" .= object [ - "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ - "balance" .= t "0x0", - "code" .= t "0x5f545f526004601cf3", - "storage" .= object [ - "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) - ] - ], +testnetSpecFile cid = baseSpecFile 5910 20 cid 0x684c5d2a + [ error "mainnetSpecFile: the EVM genesis allocations for mainnet are TBD" -- TODO: Native-X-Chain System contract -- TODO: other allocations. - ], - - "number" .= error @_ @() "mainnetSpecFile: the initial block height is TBD", -- t "0x0", - "nonce" .= t "0x0", - "difficulty" .= t "0x0", - "mixHash" .= t "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase" .= t "0x0000000000000000000000000000000000000000" ] - where - t :: T.Text -> T.Text - t = id - - i :: Natural -> Natural - i = id - -- -------------------------------------------------------------------------- -- --- Spec File For Kadena Testnet +-- Spec File For Kadena Mainnet --- | Used with the public Kadena testnet. This is a permament testnet that --- has the same features and properties of the Kadena mainnet. It has the --- purpose to facilitate testing of services and applications under the same --- conditions as on the Kadena mainnet. --- --- Funds on the network have no economic value. +-- | Used with the public Kadena Mainnet. -- --- The keys for the genesis allocations are held by the Kadena team. +-- Allocations are funded out of the platform share of the Kadena mainnet. +-- The keys for the Allocations are not publicly known. -- -- The EVM chains are launched at block height TODO. -- --- TODO +-- TODO: update genesis timestamp and block number -- -testnetSpecFile +mainnetSpecFile :: Natural - -- ^ numeric chainweb chain id + -- numeric chainweb chain id -> Value -testnetSpecFile cid = object [ - "config" .= object [ - "chainId" .= (2789 + cid - 20), - "daoForkSupport" .= True, - "terminalTotalDifficultyPassed" .= True, - "terminalTotalDifficulty" .= i 0, - "daoForkBlock" .= i 0, - "homesteadBlock" .= i 0, - "eip150Block" .= i 0, - "eip155Block" .= i 0, - "eip158Block" .= i 0, - "byzantiumBlock" .= i 0, - "constantinopleBlock" .= i 0, - "petersburgBlock" .= i 0, - "istanbulBlock" .= i 0, - "muirGlacierBlock" .= i 0, - "berlinBlock" .= i 0, - "londonBlock" .= i 0, - "arrowGlacierBlock" .= i 0, - "graphGlacierBlock" .= i 0, - "mergeForkBlock" .= i 0, - "mergeNetsplitBlock" .= i 0, - "shanghaiTime" .= i 0, - "cancunTime" .= i 0, - "pragueTime" .= i 0 - ], - "timestamp" .= t "0x6490fdd2", - "extraData" .= t "0x", - "gasLimit" .= t "0x1c9c380", - "alloc" .= object [ - "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ - "balance" .= t "0x0", - "code" .= t "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063973e55d414602d575b600080fd5b600054603c9063ffffffff1681565b60405163ffffffff909116815260200160405180910390f3fea2646970667358221220b716cf70992d0b5a77124b3da9b37629f5625bf265c121cfb76f9714f249119b64736f6c634300081c0033", - "storage" .= object [ - "0x0000000000000000000000000000000000000000000000000000000000000000" .= (printf "0x%064x" cid :: String) - ] - ], +mainnetSpecFile cid = baseSpecFile 5900 20 cid 0x684c5d2a + [ error "mainnetSpecFile: the EVM genesis allocations for mainnet are TBD" -- TODO: Native-X-Chain System contract -- TODO: other allocations. - ], - - "number" .= error @_ @() "mainnetSpecFile: the initial block height is TBD", -- t "0x0", - "nonce" .= t "0x0", - "difficulty" .= t "0x0", - "mixHash" .= t "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase" .= t "0x0000000000000000000000000000000000000000" ] - where - t :: T.Text -> T.Text - t = id - i :: Natural -> Natural - i = id diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 5d2be5c76c..9b62551878 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -15,6 +15,7 @@ module Chainweb.PayloadProvider.EVM.Genesis import Chainweb.Version import Chainweb.PayloadProvider.EVM.Header +import Chainweb.Version.EvmTestnet import Chainweb.Version.EvmDevelopment import Chainweb.Version.EvmDevelopmentSingleton import Chainweb.Utils @@ -82,23 +83,35 @@ genesisBlocks c = go (_versionCode implicitVersion) (_chainId c) go v | v == _versionCode EvmDevelopmentSingleton = \case ChainId 0 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoDNRqqHU3RpYNNK87jvHr0Nrl0zaMEylNVRiAm6cSPMAoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoDNRqqHU3RpYNNK87jvHr0Nrl0zaMEylNVRiAm6cSPMAoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopmentPair = \case ChainId 1 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoO4IB2_chJ_NuzlG69CajkvHzxNR4zX19ioL4X2oKS17oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoO4IB2_chJ_NuzlG69CajkvHzxNR4zX19ioL4X2oKS17oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopment = \case ChainId 20 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIOOAysXKLGptB5GO1vKMMrTE7cWB14Ft14pZ7CGpFFloFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIOOAysXKLGptB5GO1vKMMrTE7cWB14Ft14pZ7CGpFFloFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 21 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoG85Bmgt7tyzR2m55gEFQFi_-0yVF-aBjFqWmRG8IOExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoG85Bmgt7tyzR2m55gEFQFi_-0yVF-aBjFqWmRG8IOExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 22 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoHm65lj2ks7TAHeTUt8sRrclRXGT2z2P-5FSk2TmLnN-oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoHm65lj2ks7TAHeTUt8sRrclRXGT2z2P-5FSk2TmLnN-oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 23 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBRUfqOU18XP_669WHx_2BouSX8LYvuO-kQRT5fQLN2ooFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBRUfqOU18XP_669WHx_2BouSX8LYvuO-kQRT5fQLN2ooFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 24 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBOHYONSIUU7CyhAfW-z14OvuVy3OhGxgCMudl7HWsk7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGSQ_dKAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBOHYONSIUU7CyhAfW-z14OvuVy3OhGxgCMudl7HWsk7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) + | v == _versionCode EvmTestnet = \case + ChainId 20 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoB9BSbN9UripcrPku1xhdtVlXJckS0SD-Qgb7xB_YqfPoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 21 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoH7quUdgaBXGTFCA4UdvHP0KbHArLiqGIXn1Myj4kVC6oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 22 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBvsYLx03DzyEMz0Y0sbzES1GzyumtqOWOqwb7cafeYyoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 23 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMG5jOwlX3xJyphhtrn2Ng10bz0c6BiCt6x2LdgsQmvKoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + ChainId 24 -> f + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoClOoTcss2dq2iPBfMhtScQdj1v74lREk_HlAeTxckMzoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | otherwise = error "requested genesis block for unsupported chain" diff --git a/src/Chainweb/Version/Development.hs b/src/Chainweb/Version/Development.hs index 69ff8e4d3c..8b9b170e7b 100644 --- a/src/Chainweb/Version/Development.hs +++ b/src/Chainweb/Version/Development.hs @@ -5,7 +5,10 @@ {-# language QuasiQuotes #-} {-# language ViewPatterns #-} -module Chainweb.Version.Development(devnet, pattern Development) where +module Chainweb.Version.Development +( devnet +, pattern Development +) where import qualified Data.Set as Set diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index d70552d456..607cc8f0bd 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -42,7 +42,7 @@ pattern EvmDevelopment <- ((== evmDevnet) -> True) where -- EVM Payload Provider: -- -- @ --- cabal run evm-genesis -- evm-development +-- $(cabal list-bin evm-genesis) evm-development -- @ -- -- Pact Provider: @@ -65,7 +65,7 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion , _genesisTime = onChains -- FIXME: is the creation time for the pact headers correct? $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0..19] ] - <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) | i <- [20..24] ] + <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 0x684c5d2a))) | i <- [20..24] ] <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [25..97] ] , _genesisBlockPayload = onChains $ -- Pact Payload Provider @@ -90,11 +90,11 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider - , (unsafeChainId 20, unsafeFromText "JsA7DnXagbgNR_UQvlPv8juluB4bIJ1UwvnctkwxFqs") - , (unsafeChainId 21, unsafeFromText "ua1zzX1zYiGsdhgUwnJCFrpbZi3F1e0x8ZXSryRq0NE") - , (unsafeChainId 22, unsafeFromText "xgXdm3SBJ0pwcCR-3KzNGDy7VgS6YytBUZrNS2vYNOU") - , (unsafeChainId 23, unsafeFromText "7Aq2PRVKFYXyNG0a15-ZaLjPz9MTO9YWg2_c8Rhdf3c") - , (unsafeChainId 24, unsafeFromText "fpST-z8pu0KX7u-aA9ultehTlF8SdEOa4l4CyKwkG60") + , (unsafeChainId 20, unsafeFromText "XEv4ey1P7XOu63vMZgH01ANKKUoEWuo3-PWOaHfOJfA") + , (unsafeChainId 21, unsafeFromText "FRCRkgrvBv7BgzahtJynhYLAh-ZSMTgNvpyHJIyAr-c") + , (unsafeChainId 22, unsafeFromText "c5Dg1YhC3Yafl_uMrlift9_f_UU0pHT2VEv70RAJQ4A") + , (unsafeChainId 23, unsafeFromText "pCCVAAxn7zBggAxaXqp5cqStrs62FKfE0--VsK3OWIk") + , (unsafeChainId 24, unsafeFromText "h93bpavjG94oytkKCe1yJUn4AU-PwW7dLsLeu-6LDwo") -- Minimal Payload Provider , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") diff --git a/src/Chainweb/Version/EvmDevelopmentSingleton.hs b/src/Chainweb/Version/EvmDevelopmentSingleton.hs index 6c5fdafad0..b66aa63b32 100644 --- a/src/Chainweb/Version/EvmDevelopmentSingleton.hs +++ b/src/Chainweb/Version/EvmDevelopmentSingleton.hs @@ -71,13 +71,13 @@ evmDevnetPair = withVersion evmDevnetPair $ ChainwebVersion , _genesisTime = onChains -- FIXME: is the creation time for the pact headers correct? [ (unsafeChainId 0, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) - , (unsafeChainId 1, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) + , (unsafeChainId 1, BlockCreationTime (Time (secondsToTimeSpan 0x684c5d2a))) ] , _genesisBlockPayload = onChains $ -- Pact Payload Provider [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") -- EVM Payload Provider - , (unsafeChainId 1, unsafeFromText "lEbS887A2BuK_Mdq6LwVmtRJAjbnq-MS6JQdUHcnOHU") + , (unsafeChainId 1, unsafeFromText "Uww99ycc-FPrNroeVFkf3azD3nG2gNDO2_HnxcNjHb8") ] } @@ -119,10 +119,10 @@ evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion , _versionGenesis = VersionGenesis { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 10_000) , _genesisTime = onChains - [ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 1687223762))) ] + [ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 0x684c5d2a))) ] , _genesisBlockPayload = onChains $ -- EVM Payload Provider - [ (unsafeChainId 0, unsafeFromText "odWrFJQGgWcWwHFpr7R5eTulJFyF8G-6eoUE6dFk4gM") ] + [ (unsafeChainId 0, unsafeFromText "gY8WCR0oQbwRgQK2QAvD-0aB0Xp27SahdlldrMVwnb4") ] } -- still the *default* block gas limit is set, see diff --git a/src/Chainweb/Version/EvmTestnet.hs b/src/Chainweb/Version/EvmTestnet.hs new file mode 100644 index 0000000000..dca7817392 --- /dev/null +++ b/src/Chainweb/Version/EvmTestnet.hs @@ -0,0 +1,197 @@ +{-# language LambdaCase #-} +{-# language NumericUnderscores #-} +{-# language OverloadedStrings #-} +{-# language PatternSynonyms #-} +{-# language QuasiQuotes #-} +{-# language ViewPatterns #-} + +module Chainweb.Version.EvmTestnet +( evmTestnet +, pattern EvmTestnet +) where + +import qualified Data.Set as Set + +import Chainweb.BlockCreationTime +import Chainweb.ChainId +import Chainweb.Difficulty +import Chainweb.Graph +import Chainweb.Time +import Chainweb.Utils +import Chainweb.Utils.Rule +import Chainweb.Version + +import Pact.Core.Names + +pattern EvmTestnet :: ChainwebVersion +pattern EvmTestnet <- ((== evmTestnet) -> True) where + EvmTestnet = evmTestnet + +-- How to compute the hashes: +-- +-- Mininal Payload Provider: +-- +-- @ +-- -- create dummy payload hashes +-- import Chainweb.PayloadProvider.Minimal.Payload +-- import Chainweb.Version.EvmTestnet +-- +-- mapM_ (\i -> T.putStrLn (sshow i <> " " <> encodeToText (view payloadHash $ genesisPayload EvmTestnet $ unsafeChainId i))) [25..97] +-- @ +-- +-- EVM Payload Provider: +-- +-- @ +-- $(cabal list-bin evm-genesis) evm-testnet +-- @ +-- +-- Pact Provider: +-- +-- TODO (use ea?) + +evmTestnet :: ChainwebVersion +evmTestnet = withVersion evmTestnet $ ChainwebVersion + { _versionCode = ChainwebVersionCode 0x0000_000a + , _versionName = ChainwebVersionName "evm-testnet" + , _versionForks = tabulateHashMap $ const $ onAllChains ForkAtGenesis + , _versionUpgrades = onAllChains mempty + , _versionGraphs = Bottom (minBound, d4k4ChainGraph) + , _versionBlockDelay = BlockDelay 30_000_000 + , _versionWindow = WindowWidth 120 + , _versionHeaderBaseSizeBytes = 318 - 110 + , _versionBootstraps = [] + , _versionGenesis = VersionGenesis + { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 100_000) + , _genesisTime = onChains + -- FIXME: is the creation time for the pact headers correct? + $ [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [0..19] ] + <> [ (unsafeChainId i, BlockCreationTime (Time (secondsToTimeSpan 0x684c5d2a))) | i <- [20..24] ] + <> [ (unsafeChainId i, BlockCreationTime [timeMicrosQQ| 2025-01-01T00:00:00.000000 |]) | i <- [25..97] ] + , _genesisBlockPayload = onChains $ + -- Pact Payload Provider + [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") + , (unsafeChainId 1, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 2, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 3, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 4, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 5, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 6, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 7, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 8, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 9, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 10, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 11, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 12, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 13, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 14, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 15, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 16, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 17, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") + -- EVM Payload Provider + , (unsafeChainId 20, unsafeFromText "zjxImGB3TlrwP23JPAhA8naoW2BcBcCz5-x8ZkxPMgI") + , (unsafeChainId 21, unsafeFromText "LHOE9UrFkPs_9YIrI5yv-2wNX-XOYu9ypYZqP_wJdGY") + , (unsafeChainId 22, unsafeFromText "mmEOxyCfuHs_Z7ews_AP5iCpEi_P_CwhUPc_MIhMvGw") + , (unsafeChainId 23, unsafeFromText "pTMEi19DTelaeeJtMyn9TCee02GnilIGNoTCTmD6HyA") + , (unsafeChainId 24, unsafeFromText "PstzPAbKeFLk4QuncYB_UfC4Qa4Tonui7AY1S2YZbUY") + -- Minimal Payload Provider + , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") + , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") + , (unsafeChainId 27, unsafeFromText "xAOBFMKZ_lSHNVhHW-GhbhiWh6sX48S2KPyyPMjAnZM") + , (unsafeChainId 28, unsafeFromText "eav79QetdNuKo1HDW27Aeqbxr8oAt6Fh2U6ZtSfVyYU") + , (unsafeChainId 29, unsafeFromText "jlcxQ4wXUrApJDUQRS8KxuSyoG7ZFEwLV4-92wmqLOQ") + , (unsafeChainId 30, unsafeFromText "odUxYpZ8ZeW0WuQcibJH3isuI045MuEEeQqLrkivWEk") + , (unsafeChainId 31, unsafeFromText "poJ65aDiZYYthbhrgUI2jJS_8vK1CTHRE2C6dLbjTXA") + , (unsafeChainId 32, unsafeFromText "SC3ol1uFOHAewfQrMQczKrvhE8Dw1Bp9fBzNi9l_zTw") + , (unsafeChainId 33, unsafeFromText "p38GsNdY-T8ULYN1OTpYyO1E7WOGwzE2g92aIPereUw") + , (unsafeChainId 34, unsafeFromText "1V0SzqA9fHDOMnfvXvSd57H0r-iycjjLu3CKkRLcjRk") + , (unsafeChainId 35, unsafeFromText "SeiRMENBv5XqR7wXUYSR2orjGHSUrtawx5gLrDCfZYA") + , (unsafeChainId 36, unsafeFromText "JpsiIjm8aZEbyrcsMqLDneT0BNoJAJunk4BYqDO_1Y0") + , (unsafeChainId 37, unsafeFromText "0yx4OFT3sbLS_wrqpULgPpgzzxmMRwM6VTmkasitTlA") + , (unsafeChainId 38, unsafeFromText "Z3LE9JHGpSYuR3SvoYWPae7zB9-X05vFLjwD4GnXX_A") + , (unsafeChainId 39, unsafeFromText "9ZHBlxeYPWjhvMySgVx3ThEyooYx724zjORClgmq8Lk") + , (unsafeChainId 40, unsafeFromText "H3VBsNGh-SQE-0d_qlYSHnS2obzUeo6Zi1XDDvhndYo") + , (unsafeChainId 41, unsafeFromText "N6hVHz6vo0frpS3eyqvtMeZg1eFbAMJ1CS315M-JpWw") + , (unsafeChainId 42, unsafeFromText "9mo8CRwvTLLJ4cSQtErBfOIxzwpale-AwnbXPWQd184") + , (unsafeChainId 43, unsafeFromText "SotTkMw2Eq5UbOObv5kyaUlMqHp6-PUSKDeLHWYCr2s") + , (unsafeChainId 44, unsafeFromText "1IB27CAQ6MpXFV-OiyDVIxbXh91BK2Bl6PYiAwvyg-E") + , (unsafeChainId 45, unsafeFromText "ou9ns1_Q72IsaXoVjCErGGGmzsI07IIx6Vo14gU0Fl8") + , (unsafeChainId 46, unsafeFromText "dZsEBdKKdeLkTS35IV54npY01mB9HOWO3TvoXE0xoWg") + , (unsafeChainId 47, unsafeFromText "oLbpBWhnhCdKHy_q009-06PYug12KMtA5u9mv82_I8s") + , (unsafeChainId 48, unsafeFromText "coBTWu6iFvyDX_3W2dSnuwK9WheRa9_40kh564myiXw") + , (unsafeChainId 49, unsafeFromText "fiOvn4JEAf7NXGAP3YkXhygalCnKCwzhe2dC1VO4YiU") + , (unsafeChainId 50, unsafeFromText "UL6_gjNoxuryRd3xBj3OU00A6IjvHtdnggucOISDgc4") + , (unsafeChainId 51, unsafeFromText "Wtu06D5r5DcNSZ8ZxxPv3Jvq4dtYW1PnHJtrxmdVSzw") + , (unsafeChainId 52, unsafeFromText "yrSDpQnAtnw-WYVrXMd-zAt2ZCOBNz7mACG-UjX5Tl0") + , (unsafeChainId 53, unsafeFromText "c743P-dTKKA6PpKCJ6ZC9im7bo1BpJWViZ6xjZJokkw") + , (unsafeChainId 54, unsafeFromText "qlj-G-PO_TtM5mp2C6UI6GgVWR2H1um2v6VOMrXjX4M") + , (unsafeChainId 55, unsafeFromText "XO6ZWLmRlyiGygt2pdDZpxZfwHrmkLsBM99rJSxa31M") + , (unsafeChainId 56, unsafeFromText "jiXkFn_Nv73-X8d3xUtsY25lNN2g35sjSsu43X1pOEM") + , (unsafeChainId 57, unsafeFromText "Dr15tRuU6JSXOARB46_r1DGb9e2WX4a61BoiJ9Uq5p0") + , (unsafeChainId 58, unsafeFromText "KYlhzAW01sBwnO2dXl9_0BuRNV64nJJCSSo6JdDNMZA") + , (unsafeChainId 59, unsafeFromText "ywo1yl72s89SQibkkKuRm4tmBnp8guONArOLa03lETU") + , (unsafeChainId 60, unsafeFromText "t4u3_IuTXANdi2NrM2prmWCOFSc3AkrwHYziL1LSsEU") + , (unsafeChainId 61, unsafeFromText "Ucp32OmDetbZPozGHES6F7HKqbAnIfynOsfCzUo3lDI") + , (unsafeChainId 62, unsafeFromText "VEsZDVjM1lJkfJUWTXEyC7wH27vgDoviFD2Rt22vJ1I") + , (unsafeChainId 63, unsafeFromText "NDsftZSHa8N1yDdkgQJQ1rk1J3vRFFxFzCSrd8SzEUo") + , (unsafeChainId 64, unsafeFromText "80yIgBalnINZyprtYhZVCgOgMbB2DoW5Xq42FJf7nKk") + , (unsafeChainId 65, unsafeFromText "BMi5YZ0GpYTemGe6x_FtXEI5JTO9rinekaNnhEV87Jc") + , (unsafeChainId 66, unsafeFromText "uAIl8FGVbOPzitFHovTPJtPHaCQzYA8ZOaE6PxBx3Ng") + , (unsafeChainId 67, unsafeFromText "tmzmkzngbTHNfywBySBDE-OLXwhjgn2gNhqK86uFRXk") + , (unsafeChainId 68, unsafeFromText "MNkeZut1raJk7-Vh6Yf2HR-Lhf7x97ZYqtZvM-czHmA") + , (unsafeChainId 69, unsafeFromText "BMa_Ucv2c0Q9TU9wE8HZaX5hFv919yqh57s_ijfb0aA") + , (unsafeChainId 70, unsafeFromText "YZlG4QzDr285OQMCs5k9ZS6SNYSNjq2gr9RnJ1DmyBw") + , (unsafeChainId 71, unsafeFromText "ZDwNBLBZSTRertPlENXC6CUj6StykJLucUnN_DydnLc") + , (unsafeChainId 72, unsafeFromText "BtxzFZrXiRYOMouRmU7eA9pohvahn_GdKPREhcuGCY4") + , (unsafeChainId 73, unsafeFromText "cGIf74TXx8V_XrUUr2B9MU8adtQeQc1hpk6XOey1GOU") + , (unsafeChainId 74, unsafeFromText "j3hvD0Yjztjc_trqX4OMHPOqEWTd04GKvPfmt4r92D8") + , (unsafeChainId 75, unsafeFromText "eSIZD24zvKNw-2OJtWJujayy3AKU2h11RhRWZUC6MtM") + , (unsafeChainId 76, unsafeFromText "P3H3_4I6vJTQOszcrYreI6LOhSRVgVv7Hb0nFawzbsM") + , (unsafeChainId 77, unsafeFromText "CKomlT6tiEs6DCa2VNy3519TjAiwFNx8EkkI54JKY0I") + , (unsafeChainId 78, unsafeFromText "2NldG3su7R_RXpes25X09t8evnjNMuZoL8j3PcNMfXE") + , (unsafeChainId 79, unsafeFromText "fMlZOHs-mzu5u2DXAiCKzhZNOlCMROY2YXxHMCdiHyo") + , (unsafeChainId 80, unsafeFromText "mDocxA63bstQQ-vqzM02_avZmSjrPFFxcvcVa6ZCYMU") + , (unsafeChainId 81, unsafeFromText "hqkNDhLpy-9usTAyu77mvwoCD8YDlW9O66EFQ98ARsg") + , (unsafeChainId 82, unsafeFromText "jSGKQqn_KP4RbFsbTT_6VWFTj9WOqAqv-INoMyJntAw") + , (unsafeChainId 83, unsafeFromText "fvOd-k-4j_OFmAQo1M2Vy1T2O18UkpcjRYlSMVcjxXo") + , (unsafeChainId 84, unsafeFromText "s26jheDzrGXuWZ8mddMkIDU9UICfsRg9z-TQwDCRWos") + , (unsafeChainId 85, unsafeFromText "ZTR2dcyy-ZgOX5OVsKMV0t4Bkp4mf2ocvUs8KGZVw74") + , (unsafeChainId 86, unsafeFromText "AnmY1tCYiwIf35bZZXXdx3ZVOfmwsT0jvOlLA0s0NHY") + , (unsafeChainId 87, unsafeFromText "aBL54Dj7EkUqrrWykyQfFGa9vCf3nS34QogArm33188") + , (unsafeChainId 88, unsafeFromText "YGs9zbwtzR0FqoVwAITceumfKfCMilFYfSxkJU6pf70") + , (unsafeChainId 89, unsafeFromText "J1CdN2TlCux7xAoX8m7fdGyJHV_IeEgboyQMvI2ToO8") + , (unsafeChainId 90, unsafeFromText "_mphcuK5KOq2_-DQB9bqlM6C4f6eAnO7cCLSK--gLsE") + , (unsafeChainId 91, unsafeFromText "TvoF0OcZ86zj_C9nExaubCzXRgGTGMQ9wViM1Zm0f-A") + , (unsafeChainId 92, unsafeFromText "dAAvn6c3IkClNgEpjSTt-ZrPCG8YcqBsma_vvLnRji4") + , (unsafeChainId 93, unsafeFromText "RBJolQY0GKyqcGBUef18tAr51aRS52IQ8HJoJX6EPR8") + , (unsafeChainId 94, unsafeFromText "lPLkYdoQHw_auHSFnlcNL3fI_oI2b5jCCpaSXbw5rZ4") + , (unsafeChainId 95, unsafeFromText "M4Webe0zta7bJ_53pHTIjU5d25TLG6FISfiLw1eFFgg") + , (unsafeChainId 96, unsafeFromText "jiQb8cx7bl48fvqA6QeLXh_YXP2Bzg8gSroKGfceqUk") + , (unsafeChainId 97, unsafeFromText "ZE5xfgDK6KW4q8o98qCWZ4NJL74NiMG1hu3DUZrHatI") + ] + } + + -- still the *default* block gas limit is set, see + -- defaultChainwebConfiguration._configBlockGasLimit + , _versionMaxBlockGasLimit = Bottom (minBound, Nothing) + , _versionCheats = VersionCheats + { _disablePow = True + , _fakeFirstEpochStart = True + , _disablePact = False + } + , _versionDefaults = VersionDefaults + { _disablePeerValidation = True + , _disableMempoolSync = False + } + , _versionVerifierPluginNames = onAllChains $ Bottom + (minBound, Set.fromList $ map VerifierName ["hyperlane_v3_message", "allow"]) + , _versionQuirks = noQuirks + , _versionServiceDate = Nothing + + -- FIXME make this safe for graph changes + , _versionPayloadProviderTypes = onChains + $ [ (unsafeChainId i, PactProvider) | i <- [0..19] ] + <> [ (unsafeChainId i, EvmProvider (1789 - 20 + int i)) | i <- [20..24] ] + <> [ (unsafeChainId i, MinimalProvider) | i <- [25..97] ] + } diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index 2977c449c8..9d4e99946b 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -26,16 +26,10 @@ module Chainweb.Version.Registry import Control.DeepSeq import Control.Exception -import Control.Lens import Control.Monad import Data.Foldable -import Data.HashMap.Strict (HashMap) -import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS -import Data.IORef -import Data.Maybe import qualified Data.Text as T -import System.IO.Unsafe import GHC.Stack @@ -43,6 +37,7 @@ import Chainweb.Version import Chainweb.Version.Development import Chainweb.Version.EvmDevelopment import Chainweb.Version.EvmDevelopmentSingleton +import Chainweb.Version.EvmTestnet import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.Testnet04 @@ -81,7 +76,16 @@ validateVersion v = do -- | Versions known to us by name. knownVersions :: [ChainwebVersion] -knownVersions = [mainnet, testnet04, recapDevnet, devnet, evmDevnet, evmDevnetSingleton, evmDevnetPair] +knownVersions = + [ mainnet + , testnet04 + , evmTestnet + , recapDevnet + , devnet + , evmDevnet + , evmDevnetSingleton + , evmDevnetPair + ] -- | Look up a known version by name, usually with `m` instantiated to some -- configuration parser monad. From 56833d2c5644163fa5f736bfada684f2f6a5ee94 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 13 Jun 2025 18:33:40 -0700 Subject: [PATCH 242/378] fix ethereum network id in EvmTestnet version --- src/Chainweb/Version/EvmTestnet.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/Version/EvmTestnet.hs b/src/Chainweb/Version/EvmTestnet.hs index dca7817392..0d88acf380 100644 --- a/src/Chainweb/Version/EvmTestnet.hs +++ b/src/Chainweb/Version/EvmTestnet.hs @@ -192,6 +192,6 @@ evmTestnet = withVersion evmTestnet $ ChainwebVersion -- FIXME make this safe for graph changes , _versionPayloadProviderTypes = onChains $ [ (unsafeChainId i, PactProvider) | i <- [0..19] ] - <> [ (unsafeChainId i, EvmProvider (1789 - 20 + int i)) | i <- [20..24] ] + <> [ (unsafeChainId i, EvmProvider (5920 - 20 + int i)) | i <- [20..24] ] <> [ (unsafeChainId i, MinimalProvider) | i <- [25..97] ] } From 635729a66a611f2114c7d16cbce4a94069a08f4b Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 9 Jun 2025 15:01:27 -0400 Subject: [PATCH 243/378] Delete freeze Change-Id: Id0000000ae2299dd7f76fd25dce0ab4605c49ab5 --- cabal.project.freeze | 527 ------------------------------------------- 1 file changed, 527 deletions(-) delete mode 100644 cabal.project.freeze diff --git a/cabal.project.freeze b/cabal.project.freeze deleted file mode 100644 index 4bfbde3cdd..0000000000 --- a/cabal.project.freeze +++ /dev/null @@ -1,527 +0,0 @@ -active-repositories: hackage.haskell.org:merge -constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, - any.Cabal-syntax ==3.12.1.0 || ==3.14.2.0, - any.Decimal ==0.5.2, - any.Diff ==1.0.2, - any.Glob ==0.10.2, - any.HUnit ==1.6.2.0, - any.JuicyPixels ==3.3.9, - JuicyPixels -mmap, - any.OneTuple ==0.4.2, - any.Only ==0.1, - any.QuickCheck ==2.15.0.1, - QuickCheck -old-random +templatehaskell, - any.RSA ==2.4.1, - any.SHA ==1.6.4.4, - SHA -exe, - any.StateVar ==1.2.2, - any.adjunctions ==4.4.3, - any.aeson ==2.2.3.0, - aeson +ordered-keymap, - any.aeson-pretty ==0.8.10, - aeson-pretty -lib-only, - any.alex ==3.5.3.0, - any.ansi-terminal ==1.1.2, - ansi-terminal -example, - any.ansi-terminal-types ==1.1, - any.ap-normalize ==0.1.0.1, - ap-normalize -test-with-clang, - any.appar ==0.1.8, - any.array ==0.5.8.0, - any.asn1-encoding ==0.9.6, - any.asn1-parse ==0.9.5, - any.asn1-types ==0.3.4, - any.assoc ==1.1.1, - assoc -tagged, - any.async ==2.2.5, - async -bench, - any.atomic-primops ==0.8.8, - atomic-primops -debug, - any.attoparsec ==0.14.4, - attoparsec -developer, - any.attoparsec-aeson ==2.2.2.0, - any.authenticate-oauth ==1.7, - any.auto-update ==0.2.6, - any.barbies ==2.1.1.0, - any.base ==4.20.1.0, - any.base-compat ==0.14.1, - any.base-compat-batteries ==0.14.1, - any.base-orphans ==0.9.3, - any.base-unicode-symbols ==0.2.4.2, - base-unicode-symbols +base-4-8 -old-base, - any.base16 ==1.0, - any.base16-bytestring ==1.0.2.0, - any.base64-bytestring ==1.2.1.0, - any.base64-bytestring-kadena ==0.1, - any.basement ==0.0.16, - any.bifunctors ==5.6.2, - bifunctors +tagged, - any.binary ==0.8.9.3, - any.binary-orphans ==1.0.5, - any.bitvec ==1.1.5.0, - bitvec +simd, - any.blaze-builder ==0.4.3, - any.blaze-html ==0.9.2.0, - any.blaze-markup ==0.8.3.0, - any.boring ==0.2.2, - boring +tagged, - any.bound ==2.0.7, - bound +template-haskell, - any.bsb-http-chunked ==0.0.0.4, - any.bytebuild ==0.3.16.3, - bytebuild -checked, - any.byteorder ==1.0.4, - any.bytes ==0.17.4, - any.byteslice ==0.2.14.0, - byteslice +avoid-rawmemchr, - any.bytesmith ==0.3.11.1, - any.bytestring ==0.12.2.0, - any.bytestring-builder ==0.10.8.2.0, - bytestring-builder +bytestring_has_builder, - any.cabal-doctest ==1.0.11, - any.cache ==0.1.3.0, - any.call-stack ==0.4.0, - any.case-insensitive ==1.2.1.0, - any.cassava ==0.5.3.2, - any.cborg ==0.2.10.0, - cborg +optimize-gmp, - any.cereal ==0.5.8.3, - cereal -bytestring-builder, - chainweb -debug -ed25519 -ghc-flags, - chainweb-node -debug -ed25519 -ghc-flags, - any.character-ps ==0.1, - any.charset ==0.3.12, - any.chronos ==1.1.6.2, - any.citeproc ==0.9.0.1, - citeproc -executable -icu, - any.clock ==0.8.4, - clock -llvm, - any.cmdargs ==0.10.22, - cmdargs +quotation -testprog, - any.co-log-core ==0.3.2.5, - any.code-page ==0.2.1, - any.colour ==2.3.6, - any.commonmark ==0.2.6.1, - any.commonmark-extensions ==0.2.6, - any.commonmark-pandoc ==0.2.3, - any.comonad ==5.0.9, - comonad +containers +distributive +indexed-traversable, - any.concurrent-output ==1.10.21, - any.conduit ==1.3.6.1, - any.conduit-extra ==1.3.7, - any.configuration-tools ==0.7.1, - configuration-tools -remote-configs, - any.constraints ==0.14.2, - any.containers ==0.7, - any.contiguous ==0.6.4.2, - any.contravariant ==1.5.5, - contravariant +semigroups +statevar +tagged, - any.cookie ==0.5.1, - any.criterion ==1.6.4.0, - criterion -embed-data-files -fast, - any.criterion-measurement ==0.2.3.0, - criterion-measurement -fast, - any.crypto-api ==0.13.3, - crypto-api -all_cpolys, - any.crypto-pubkey-types ==0.4.3, - any.crypto-token ==0.1.2, - any.cryptohash-md5 ==0.11.101.0, - any.cryptohash-sha1 ==0.11.101.0, - any.crypton ==1.0.4, - crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes, - any.crypton-connection ==0.4.4, - any.crypton-x509 ==1.7.7, - any.crypton-x509-store ==1.6.10, - any.crypton-x509-system ==1.6.7, - any.crypton-x509-validation ==1.6.14, - any.cryptonite ==0.30, - cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes, - cwtools -debug -ed25519 -ghc-flags -remote-db, - any.data-bword ==0.1.0.2, - any.data-default ==0.8.0.1, - any.data-default-class ==0.2.0.0, - any.data-dword ==0.3.2.1, - any.data-fix ==0.3.4, - any.data-ordlist ==0.4.7.0, - any.dec ==0.0.6, - any.deepseq ==1.5.0.0, - any.dense-linear-algebra ==0.1.0.0, - any.deriving-compat ==0.6.7, - any.digest ==0.0.2.1, - digest -have_arm64_crc32c -have_builtin_prefetch -have_mm_prefetch -have_sse42 -have_strong_getauxval -have_weak_getauxval +pkg-config, - any.digraph ==0.3.2, - any.direct-sqlite ==2.3.29, - direct-sqlite +dbstat +fulltextsearch +haveusleep +json1 -mathfunctions -systemlib +urifilenames, - any.directory ==1.3.8.5, - any.distributive ==0.6.2.1, - distributive +semigroups +tagged, - any.djot ==0.1.2.2, - any.dlist ==1.0, - dlist -werror, - any.doclayout ==0.5, - any.doctemplates ==0.11.0.1, - any.easy-file ==0.2.5, - any.ech-config ==0.0.1, - ech-config -devel, - any.emojis ==0.1.4.1, - any.enclosed-exceptions ==1.0.3, - any.entropy ==0.4.1.11, - entropy -donotgetentropy, - any.erf ==2.0.0.0, - any.errors ==2.3.0, - any.ethereum ==0.1.0.2, - ethereum -ethhash -openssl-use-pkg-config, - any.exceptions ==0.10.9, - any.extra ==1.8, - any.fast-logger ==3.2.5, - any.file-embed ==0.0.16.0, - any.filepath ==1.5.4.0, - any.fingertree ==0.1.6.1, - any.finite-typelits ==0.2.1.0, - any.free ==5.2, - any.generic-arbitrary ==1.0.1.2, - any.generic-data ==1.1.0.2, - generic-data -enable-inspect, - any.generic-lens ==2.2.2.0, - any.generic-lens-core ==2.2.1.0, - any.generically ==0.1.1, - any.generics-sop ==0.5.1.4, - any.ghc-bignum ==1.3, - any.ghc-boot-th ==9.10.2, - any.ghc-compact ==0.1.0.0, - any.ghc-heap ==9.10.2, - any.ghc-internal ==9.1002.0, - any.ghc-prim ==0.12.0, - any.gridtables ==0.1.0.0, - any.groups ==0.5.3, - any.growable-vector ==0.1, - any.haddock-library ==1.11.0, - any.half ==0.3.2, - any.happy ==2.1.5, - any.happy-lib ==2.1.5, - any.hashable ==1.5.0.0, - hashable -arch-native -random-initial-seed, - any.hashes ==0.3.0.1, - hashes -benchmark-cryptonite -openssl-use-pkg-config -test-cryptonite +with-openssl, - any.haskeline ==0.8.2.1, - any.haskell-lexer ==1.2.1, - any.haskell-src-exts ==1.23.1, - any.haskell-src-meta ==0.8.15, - any.heaps ==0.4.1, - any.hedgehog ==1.5, - any.hourglass ==0.2.12, - any.hpke ==0.0.0, - any.hsc2hs ==0.68.10, - hsc2hs -in-ghc-tree, - any.http-api-data ==0.6.2, - http-api-data -use-text-show, - any.http-client ==0.7.19, - http-client +network-uri, - any.http-client-tls ==0.3.6.4, - any.http-date ==0.0.11, - any.http-media ==0.8.1.1, - any.http-semantics ==0.3.0, - any.http-types ==0.12.4, - any.http2 ==5.3.9, - http2 -devel -h2spec, - any.indexed-list-literals ==0.2.1.3, - any.indexed-profunctors ==0.1.1.1, - any.indexed-traversable ==0.1.4, - any.indexed-traversable-instances ==0.1.2, - any.integer-conversion ==0.1.1, - any.integer-gmp ==1.1, - any.integer-logarithms ==1.0.4, - integer-logarithms -check-bounds +integer-gmp, - any.invariant ==0.6.4, - any.iproute ==1.7.15, - any.ipynb ==0.2, - any.ixset-typed ==0.5.1.0, - any.jira-wiki-markup ==1.5.1, - any.js-chart ==2.9.4.1, - any.kan-extensions ==5.2.6, - any.lens ==5.3.4, - lens -benchmark-uniplate -dump-splices +inlining -j +test-hunit +test-properties +test-templates +trustworthy, - any.lens-aeson ==1.2.3, - any.libyaml ==0.1.4, - libyaml -no-unicode -system-libyaml, - any.libyaml-clib ==0.2.5, - any.lifted-async ==0.10.2.7, - any.lifted-base ==0.2.3.12, - any.loglevel ==0.1.0.0, - any.lrucaching ==0.3.4, - any.lsp ==2.7.0.1, - lsp -demo, - any.lsp-types ==2.3.0.1, - lsp-types -force-ospath, - any.managed ==1.0.10, - any.massiv ==1.0.5.0, - massiv -unsafe-checks, - any.math-functions ==0.3.4.4, - math-functions +system-erf +system-expm1, - any.megaparsec ==9.7.0, - megaparsec -dev, - any.memory ==0.18.0, - memory +support_bytestring +support_deepseq, - any.merkle-log ==0.2.0, - any.microlens ==0.4.14.0, - any.microstache ==1.0.3, - any.mime-types ==0.1.2.0, - any.mmorph ==1.2.0, - any.mod ==0.2.0.1, - mod +semirings +vector, - any.monad-control ==1.0.3.1, - any.mono-traversable ==1.0.21.0, - any.mtl ==2.3.1, - any.mtl-compat ==0.2.2, - mtl-compat -two-point-one -two-point-two, - any.mwc-probability ==2.3.1, - any.mwc-random ==0.15.2.0, - mwc-random -benchpapi, - any.natural-arithmetic ==0.2.2.0, - any.neat-interpolation ==0.5.1.4, - any.network ==3.2.7.0, - network -devel, - any.network-byte-order ==0.1.7, - any.network-control ==0.1.7, - any.network-info ==0.2.1, - any.network-uri ==2.6.4.2, - any.nothunks ==0.3.0.0, - nothunks +bytestring +text +vector, - any.old-locale ==1.0.0.7, - any.old-time ==1.1.0.4, - any.optparse-applicative ==0.18.1.0, - optparse-applicative +process, - any.ordered-containers ==0.2.4, - any.os-string ==2.0.4, - any.pact ==4.13.2, - pact -build-tool +cryptonite-ed25519 -tests-in-lib, - any.pact-json ==0.1.0.0, - any.pact-time ==0.3.0.1, - pact-time -with-time, - any.pact-tng ==5.2, - pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, - any.pandoc ==3.7.0.2, - pandoc -embed_data_files, - any.pandoc-types ==1.23.1, - any.parallel ==3.2.2.0, - any.parsec ==3.1.18.0, - any.parser-combinators ==1.3.0, - parser-combinators -dev, - any.parsers ==0.12.12, - parsers +attoparsec +binary +parsec, - any.patience ==0.3, - any.pem ==0.2.4, - any.poly ==0.5.1.0, - poly +sparse, - any.pretty ==1.1.3.6, - any.pretty-show ==1.10, - any.pretty-simple ==4.1.3.0, - pretty-simple -buildexample +buildexe, - any.prettyprinter ==1.7.1, - prettyprinter -buildreadme +text, - any.prettyprinter-ansi-terminal ==1.1.3, - any.primitive ==0.9.1.0, - any.primitive-addr ==0.1.0.3, - any.primitive-offset ==0.2.0.1, - any.primitive-unlifted ==2.1.0.0, - any.process ==1.6.25.0, - any.profunctors ==5.6.2, - any.property-matchers ==0.7.0.0, - any.psqueues ==0.2.8.1, - any.pvar ==1.0.0.0, - any.quickcheck-instances ==0.3.33, - any.ralist ==0.4.0.0, - any.random ==1.3.1, - any.raw-strings-qq ==1.1, - any.recover-rtti ==0.5.0, - any.recv ==0.1.1, - any.reducers ==3.12.5, - any.reflection ==2.1.9, - reflection -slow +template-haskell, - any.regex ==1.1.0.2, - any.regex-base ==0.94.0.3, - any.regex-pcre-builtin ==0.95.2.3.8.44, - any.regex-tdfa ==1.3.2.4, - regex-tdfa +doctest -force-o2, - any.resource-pool ==0.4.0.0, - any.resourcet ==1.3.0, - any.retry ==0.9.3.1, - retry -lib-werror, - any.rocksdb-haskell-kadena ==1.1.0, - rocksdb-haskell-kadena -with-tbb, - any.row-types ==1.0.1.2, - any.rts ==1.0.2, - any.run-st ==0.1.3.3, - any.safe ==0.3.21, - any.safe-exceptions ==0.1.7.4, - any.safecopy ==0.10.4.2, - any.scheduler ==2.0.1.0, - any.scientific ==0.3.8.0, - scientific -integer-simple, - any.semialign ==1.3.1, - semialign +semigroupoids, - any.semigroupoids ==6.0.1, - semigroupoids +comonad +containers +contravariant +distributive +tagged +unordered-containers, - any.semigroups ==0.20, - semigroups +binary +bytestring -bytestring-builder +containers +deepseq +hashable +tagged +template-haskell +text +transformers +unordered-containers, - any.semirings ==0.7, - semirings +containers +unordered-containers, - any.serialise ==0.2.6.1, - serialise +newtime15, - any.servant ==0.20.3.0, - any.servant-client ==0.20.3.0, - any.servant-client-core ==0.20.3.0, - any.servant-server ==0.20.3.0, - any.sha-validation ==0.1.0.1, - any.show-combinators ==0.2.0.0, - any.simple-sendfile ==0.2.32, - simple-sendfile +allow-bsd -fallback, - any.singleton-bool ==0.1.8, - any.skylighting ==0.14.6, - skylighting -executable, - any.skylighting-core ==0.14.6, - skylighting-core -executable, - any.skylighting-format-ansi ==0.1, - any.skylighting-format-blaze-html ==0.1.1.3, - any.skylighting-format-context ==0.1.0.2, - any.skylighting-format-latex ==0.1, - any.skylighting-format-typst ==0.1, - any.socks ==0.6.1, - any.some ==1.0.6, - some +newtype-unsafe, - any.sop-core ==0.5.0.2, - any.sorted-list ==0.2.3.1, - any.split ==0.2.5, - any.splitmix ==0.1.1, - splitmix -optimised-mixer, - any.statistics ==0.16.3.0, - statistics -benchpapi, - any.stm ==2.5.3.1, - any.stm-chans ==3.0.0.9, - any.stopwatch ==0.1.0.6, - stopwatch -test_delay_upper_bound -test_threaded, - any.streaming ==0.2.4.0, - any.streaming-commons ==0.2.3.0, - streaming-commons -use-bytestring-builder, - any.strict ==0.5.1, - any.strict-concurrency ==0.2.4.3, - any.syb ==0.7.2.4, - any.tagged ==0.8.9, - tagged +deepseq +transformers, - any.tagsoup ==0.14.8, - any.tasty ==1.5.3, - tasty +unix, - any.tasty-golden ==2.3.5, - tasty-golden -build-example, - any.tasty-hedgehog ==1.4.0.2, - any.tasty-hunit ==0.10.2, - any.tasty-json ==0.1.0.0, - any.tasty-quickcheck ==0.11.1, - any.template-haskell ==2.22.0.0, - any.temporary ==1.3, - any.terminal-progress-bar ==0.4.2, - any.terminal-size ==0.3.4, - any.terminfo ==0.4.1.7, - any.texmath ==0.12.10.3, - texmath -executable -server, - any.text ==2.1.2, - any.text-conversions ==0.3.1.1, - any.text-iso8601 ==0.1.1, - any.text-rope ==0.3, - text-rope -debug, - any.text-short ==0.1.6, - text-short -asserts, - any.th-abstraction ==0.7.1.0, - any.th-compat ==0.1.6, - any.th-expand-syns ==0.4.12.0, - any.th-lift ==0.8.6, - any.th-lift-instances ==0.1.20, - any.th-orphans ==0.13.16, - any.th-reify-many ==0.1.10, - any.these ==1.2.1, - any.time ==1.12.2, - any.time-compat ==1.9.8, - any.time-locale-compat ==0.1.1.5, - time-locale-compat +old-locale, - any.time-manager ==0.2.3, - any.tls ==2.1.10, - tls -devel, - any.tls-session-manager ==0.0.8, - any.token-bucket ==0.1.0.1, - token-bucket +use-cbits, - any.toml-parser ==2.0.1.2, - any.torsor ==0.1.0.1, - any.transformers ==0.6.1.1, - any.transformers-base ==0.4.6, - transformers-base +orphaninstances, - any.transformers-compat ==0.7.2, - transformers-compat -five +five-three -four +generic-deriving +mtl -three -two, - any.trifecta ==2.1.4, - any.tuples ==0.1.0.0, - any.typed-process ==0.2.13.0, - any.typst ==0.8.0.1, - typst -executable, - any.typst-symbols ==0.1.8.1, - any.unicode-collation ==0.1.3.6, - unicode-collation -doctests -executable, - any.unicode-data ==0.6.0, - unicode-data -dev-has-icu, - any.unicode-transforms ==0.4.0.1, - unicode-transforms -bench-show -dev -has-icu -has-llvm -use-gauge, - any.uniplate ==1.6.13, - any.unix ==2.8.6.0, - any.unix-compat ==0.7.4, - any.unix-time ==0.4.16, - any.unlifted ==0.2.3.0, - any.unliftio ==0.2.25.1, - any.unliftio-core ==0.2.1.0, - any.unordered-containers ==0.2.20, - unordered-containers -debug, - any.utf8-string ==1.0.2, - any.uuid ==1.3.16, - any.uuid-types ==1.0.6, - any.validation ==1.1.3, - any.vault ==0.3.1.5, - vault +useghc, - any.vector ==0.13.2.0, - vector +boundschecks -internalchecks -unsafechecks -wall, - any.vector-algorithms ==0.9.1.0, - vector-algorithms +bench +boundschecks -internalchecks -llvm -unsafechecks, - any.vector-binary-instances ==0.2.5.2, - any.vector-sized ==1.6.1, - any.vector-stream ==0.1.0.1, - any.vector-th-unbox ==0.2.2, - any.void ==0.7.3, - void -safe, - any.wai ==3.2.4, - any.wai-app-static ==3.1.9, - wai-app-static +crypton -print, - any.wai-cors ==0.2.7, - any.wai-extra ==3.1.17, - wai-extra -build-example, - any.wai-logger ==2.5.0, - any.wai-middleware-throttle ==0.3.0.1, - any.wai-middleware-validation ==0.1.0.2, - any.warp ==3.4.8, - warp +allow-sendfilefd -network-bytestring -warp-debug +x509, - any.warp-tls ==3.4.13, - any.wherefrom-compat ==0.1.1.1, - any.wide-word ==0.1.7.0, - any.witherable ==0.5, - any.wl-pprint-annotated ==0.1.0.1, - any.word8 ==0.1.3, - any.wreq ==0.5.4.3, - wreq -aws -developer +doctest -httpbin, - any.xml ==1.3.14, - any.xml-conduit ==1.10.0.0, - any.xml-types ==0.3.8, - any.yaml ==0.11.11.2, - yaml +no-examples +no-exe, - any.yet-another-logger ==0.4.2, - yet-another-logger -tbmqueue, - any.zigzag ==0.1.0.0, - any.zip-archive ==0.4.3.2, - zip-archive -executable, - any.zlib ==0.7.1.0, - zlib -bundled-c-zlib +non-blocking-ffi +pkg-config -index-state: hackage.haskell.org 2025-06-05T15:56:06Z From ceba9de53bdc377c7bc6f30b1ab21ea257ea6123 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 11 Jun 2025 15:22:40 -0400 Subject: [PATCH 244/378] Add Pact genesis savepoint Change-Id: Id0000000fba0e729c2697b5a610ceb85ade53624 --- src/Chainweb/Pact/Backend/Utils.hs | 3 ++ src/Chainweb/Pact/PactService.hs | 77 +++++++++++++++--------------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 5cad56698f..be1f9d0232 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -204,6 +204,7 @@ data SavepointName | InitSchemaSavePoint | ValidateBlockSavePoint | SetConsensusSavePoint + | RunGenesisSavePoint deriving (Eq, Ord, Enum, Bounded) instance Show SavepointName where @@ -217,6 +218,7 @@ instance HasTextRepresentation SavepointName where toText InitSchemaSavePoint = "init-schema" toText ValidateBlockSavePoint = "validate-block" toText SetConsensusSavePoint = "set-consensus" + toText RunGenesisSavePoint = "run-genesis" {-# INLINE toText #-} fromText "read-from" = pure ReadFromSavepoint @@ -226,6 +228,7 @@ instance HasTextRepresentation SavepointName where fromText "init-schema" = pure InitSchemaSavePoint fromText "validate-block" = pure ValidateBlockSavePoint fromText "set-consensus" = pure SetConsensusSavePoint + fromText "run-genesis" = pure RunGenesisSavePoint fromText t = throwM $ TextFormatException $ "failed to decode SavepointName " <> t <> ". Valid names are " <> T.intercalate ", " (toText @SavepointName <$> [minBound .. maxBound]) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 25dfe2a05a..5c7da964c5 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -202,44 +202,45 @@ runGenesisIfNeeded -> ServiceEnv tbl -> IO () runGenesisIfNeeded logger serviceEnv = do - latestBlock <- fmap _consensusStateLatest <$> Checkpointer.getConsensusState (_psReadWriteSql serviceEnv) - when (maybe True (isGenesisBlockHeader' cid . Parent . _syncStateBlockHash) latestBlock) $ do - logFunctionText logger Debug "running genesis" - let genesisBlockHash = genesisBlockHeader cid ^. blockHash - let genesisPayloadHash = genesisBlockPayloadHash cid - let gTime = implicitVersion ^?! versionGenesis . genesisTime . atChain cid - let targetSyncState = genesisConsensusState cid - let genesisRankedBlockHash = RankedBlockHash (genesisHeight cid) genesisBlockHash - let evalCtx = genesisEvaluationCtx serviceEnv - let blockCtx = blockCtxOfEvaluationCtx cid evalCtx - let !genesisPayload = case _psGenesisPayload serviceEnv of - Nothing -> error "genesis needs to be run, but the genesis payload is missing!" - Just p -> p - - maybeErr <- runExceptT $ Checkpointer.restoreAndSave logger cid (_psReadWriteSql serviceEnv) - $ NEL.singleton - $ (blockCtx, \blockEnv -> do - _ <- Pact.execExistingBlock logger serviceEnv blockEnv - (CheckablePayloadWithOutputs genesisPayload) - return ((), (genesisBlockHash, genesisPayloadHash)) - ) - case maybeErr of - Left err -> error $ "genesis block invalid: " <> sshow err - Right () -> do - addNewPayload - (_payloadStoreTable $ _psPdb serviceEnv) - (genesisHeight cid) - genesisPayload - Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState - forM_ (_psMiner serviceEnv) $ \_ -> do - emptyBlock <- (throwIfNoHistory =<<) $ - Checkpointer.readFrom logger cid - (_psReadWriteSql serviceEnv) - (Parent gTime) - (Parent genesisRankedBlockHash) $ - \blockEnv blockHandle -> makeEmptyBlock logger serviceEnv blockEnv blockHandle - -- we have to kick off payload refreshing here first - startPayloadRefresher logger serviceEnv emptyBlock + withSavepoint (_psReadWriteSql serviceEnv) RunGenesisSavePoint $ do + latestBlock <- fmap _consensusStateLatest <$> Checkpointer.getConsensusState (_psReadWriteSql serviceEnv) + when (maybe True (isGenesisBlockHeader' cid . Parent . _syncStateBlockHash) latestBlock) $ do + logFunctionText logger Debug "running genesis" + let genesisBlockHash = genesisBlockHeader cid ^. blockHash + let genesisPayloadHash = genesisBlockPayloadHash cid + let gTime = implicitVersion ^?! versionGenesis . genesisTime . atChain cid + let targetSyncState = genesisConsensusState cid + let genesisRankedBlockHash = RankedBlockHash (genesisHeight cid) genesisBlockHash + let evalCtx = genesisEvaluationCtx serviceEnv + let blockCtx = blockCtxOfEvaluationCtx cid evalCtx + let !genesisPayload = case _psGenesisPayload serviceEnv of + Nothing -> error "genesis needs to be run, but the genesis payload is missing!" + Just p -> p + + maybeErr <- runExceptT $ Checkpointer.restoreAndSave logger cid (_psReadWriteSql serviceEnv) + $ NEL.singleton + $ (blockCtx, \blockEnv -> do + _ <- Pact.execExistingBlock logger serviceEnv blockEnv + (CheckablePayloadWithOutputs genesisPayload) + return ((), (genesisBlockHash, genesisPayloadHash)) + ) + case maybeErr of + Left err -> error $ "genesis block invalid: " <> sshow err + Right () -> do + addNewPayload + (_payloadStoreTable $ _psPdb serviceEnv) + (genesisHeight cid) + genesisPayload + Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState + forM_ (_psMiner serviceEnv) $ \_ -> do + emptyBlock <- (throwIfNoHistory =<<) $ + Checkpointer.readFrom logger cid + (_psReadWriteSql serviceEnv) + (Parent gTime) + (Parent genesisRankedBlockHash) $ + \blockEnv blockHandle -> makeEmptyBlock logger serviceEnv blockEnv blockHandle + -- we have to kick off payload refreshing here first + startPayloadRefresher logger serviceEnv emptyBlock where cid = _chainId serviceEnv From 0bb946fe067e73494fccd5410c7d3860f212e778 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 17 Jun 2025 11:49:36 -0400 Subject: [PATCH 245/378] Revert "Delete freeze" This reverts commit 635729a66a611f2114c7d16cbce4a94069a08f4b. --- cabal.project.freeze | 527 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 cabal.project.freeze diff --git a/cabal.project.freeze b/cabal.project.freeze new file mode 100644 index 0000000000..4bfbde3cdd --- /dev/null +++ b/cabal.project.freeze @@ -0,0 +1,527 @@ +active-repositories: hackage.haskell.org:merge +constraints: any.Cabal ==3.12.1.0 || ==3.14.2.0, + any.Cabal-syntax ==3.12.1.0 || ==3.14.2.0, + any.Decimal ==0.5.2, + any.Diff ==1.0.2, + any.Glob ==0.10.2, + any.HUnit ==1.6.2.0, + any.JuicyPixels ==3.3.9, + JuicyPixels -mmap, + any.OneTuple ==0.4.2, + any.Only ==0.1, + any.QuickCheck ==2.15.0.1, + QuickCheck -old-random +templatehaskell, + any.RSA ==2.4.1, + any.SHA ==1.6.4.4, + SHA -exe, + any.StateVar ==1.2.2, + any.adjunctions ==4.4.3, + any.aeson ==2.2.3.0, + aeson +ordered-keymap, + any.aeson-pretty ==0.8.10, + aeson-pretty -lib-only, + any.alex ==3.5.3.0, + any.ansi-terminal ==1.1.2, + ansi-terminal -example, + any.ansi-terminal-types ==1.1, + any.ap-normalize ==0.1.0.1, + ap-normalize -test-with-clang, + any.appar ==0.1.8, + any.array ==0.5.8.0, + any.asn1-encoding ==0.9.6, + any.asn1-parse ==0.9.5, + any.asn1-types ==0.3.4, + any.assoc ==1.1.1, + assoc -tagged, + any.async ==2.2.5, + async -bench, + any.atomic-primops ==0.8.8, + atomic-primops -debug, + any.attoparsec ==0.14.4, + attoparsec -developer, + any.attoparsec-aeson ==2.2.2.0, + any.authenticate-oauth ==1.7, + any.auto-update ==0.2.6, + any.barbies ==2.1.1.0, + any.base ==4.20.1.0, + any.base-compat ==0.14.1, + any.base-compat-batteries ==0.14.1, + any.base-orphans ==0.9.3, + any.base-unicode-symbols ==0.2.4.2, + base-unicode-symbols +base-4-8 -old-base, + any.base16 ==1.0, + any.base16-bytestring ==1.0.2.0, + any.base64-bytestring ==1.2.1.0, + any.base64-bytestring-kadena ==0.1, + any.basement ==0.0.16, + any.bifunctors ==5.6.2, + bifunctors +tagged, + any.binary ==0.8.9.3, + any.binary-orphans ==1.0.5, + any.bitvec ==1.1.5.0, + bitvec +simd, + any.blaze-builder ==0.4.3, + any.blaze-html ==0.9.2.0, + any.blaze-markup ==0.8.3.0, + any.boring ==0.2.2, + boring +tagged, + any.bound ==2.0.7, + bound +template-haskell, + any.bsb-http-chunked ==0.0.0.4, + any.bytebuild ==0.3.16.3, + bytebuild -checked, + any.byteorder ==1.0.4, + any.bytes ==0.17.4, + any.byteslice ==0.2.14.0, + byteslice +avoid-rawmemchr, + any.bytesmith ==0.3.11.1, + any.bytestring ==0.12.2.0, + any.bytestring-builder ==0.10.8.2.0, + bytestring-builder +bytestring_has_builder, + any.cabal-doctest ==1.0.11, + any.cache ==0.1.3.0, + any.call-stack ==0.4.0, + any.case-insensitive ==1.2.1.0, + any.cassava ==0.5.3.2, + any.cborg ==0.2.10.0, + cborg +optimize-gmp, + any.cereal ==0.5.8.3, + cereal -bytestring-builder, + chainweb -debug -ed25519 -ghc-flags, + chainweb-node -debug -ed25519 -ghc-flags, + any.character-ps ==0.1, + any.charset ==0.3.12, + any.chronos ==1.1.6.2, + any.citeproc ==0.9.0.1, + citeproc -executable -icu, + any.clock ==0.8.4, + clock -llvm, + any.cmdargs ==0.10.22, + cmdargs +quotation -testprog, + any.co-log-core ==0.3.2.5, + any.code-page ==0.2.1, + any.colour ==2.3.6, + any.commonmark ==0.2.6.1, + any.commonmark-extensions ==0.2.6, + any.commonmark-pandoc ==0.2.3, + any.comonad ==5.0.9, + comonad +containers +distributive +indexed-traversable, + any.concurrent-output ==1.10.21, + any.conduit ==1.3.6.1, + any.conduit-extra ==1.3.7, + any.configuration-tools ==0.7.1, + configuration-tools -remote-configs, + any.constraints ==0.14.2, + any.containers ==0.7, + any.contiguous ==0.6.4.2, + any.contravariant ==1.5.5, + contravariant +semigroups +statevar +tagged, + any.cookie ==0.5.1, + any.criterion ==1.6.4.0, + criterion -embed-data-files -fast, + any.criterion-measurement ==0.2.3.0, + criterion-measurement -fast, + any.crypto-api ==0.13.3, + crypto-api -all_cpolys, + any.crypto-pubkey-types ==0.4.3, + any.crypto-token ==0.1.2, + any.cryptohash-md5 ==0.11.101.0, + any.cryptohash-sha1 ==0.11.101.0, + any.crypton ==1.0.4, + crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes, + any.crypton-connection ==0.4.4, + any.crypton-x509 ==1.7.7, + any.crypton-x509-store ==1.6.10, + any.crypton-x509-system ==1.6.7, + any.crypton-x509-validation ==1.6.14, + any.cryptonite ==0.30, + cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes, + cwtools -debug -ed25519 -ghc-flags -remote-db, + any.data-bword ==0.1.0.2, + any.data-default ==0.8.0.1, + any.data-default-class ==0.2.0.0, + any.data-dword ==0.3.2.1, + any.data-fix ==0.3.4, + any.data-ordlist ==0.4.7.0, + any.dec ==0.0.6, + any.deepseq ==1.5.0.0, + any.dense-linear-algebra ==0.1.0.0, + any.deriving-compat ==0.6.7, + any.digest ==0.0.2.1, + digest -have_arm64_crc32c -have_builtin_prefetch -have_mm_prefetch -have_sse42 -have_strong_getauxval -have_weak_getauxval +pkg-config, + any.digraph ==0.3.2, + any.direct-sqlite ==2.3.29, + direct-sqlite +dbstat +fulltextsearch +haveusleep +json1 -mathfunctions -systemlib +urifilenames, + any.directory ==1.3.8.5, + any.distributive ==0.6.2.1, + distributive +semigroups +tagged, + any.djot ==0.1.2.2, + any.dlist ==1.0, + dlist -werror, + any.doclayout ==0.5, + any.doctemplates ==0.11.0.1, + any.easy-file ==0.2.5, + any.ech-config ==0.0.1, + ech-config -devel, + any.emojis ==0.1.4.1, + any.enclosed-exceptions ==1.0.3, + any.entropy ==0.4.1.11, + entropy -donotgetentropy, + any.erf ==2.0.0.0, + any.errors ==2.3.0, + any.ethereum ==0.1.0.2, + ethereum -ethhash -openssl-use-pkg-config, + any.exceptions ==0.10.9, + any.extra ==1.8, + any.fast-logger ==3.2.5, + any.file-embed ==0.0.16.0, + any.filepath ==1.5.4.0, + any.fingertree ==0.1.6.1, + any.finite-typelits ==0.2.1.0, + any.free ==5.2, + any.generic-arbitrary ==1.0.1.2, + any.generic-data ==1.1.0.2, + generic-data -enable-inspect, + any.generic-lens ==2.2.2.0, + any.generic-lens-core ==2.2.1.0, + any.generically ==0.1.1, + any.generics-sop ==0.5.1.4, + any.ghc-bignum ==1.3, + any.ghc-boot-th ==9.10.2, + any.ghc-compact ==0.1.0.0, + any.ghc-heap ==9.10.2, + any.ghc-internal ==9.1002.0, + any.ghc-prim ==0.12.0, + any.gridtables ==0.1.0.0, + any.groups ==0.5.3, + any.growable-vector ==0.1, + any.haddock-library ==1.11.0, + any.half ==0.3.2, + any.happy ==2.1.5, + any.happy-lib ==2.1.5, + any.hashable ==1.5.0.0, + hashable -arch-native -random-initial-seed, + any.hashes ==0.3.0.1, + hashes -benchmark-cryptonite -openssl-use-pkg-config -test-cryptonite +with-openssl, + any.haskeline ==0.8.2.1, + any.haskell-lexer ==1.2.1, + any.haskell-src-exts ==1.23.1, + any.haskell-src-meta ==0.8.15, + any.heaps ==0.4.1, + any.hedgehog ==1.5, + any.hourglass ==0.2.12, + any.hpke ==0.0.0, + any.hsc2hs ==0.68.10, + hsc2hs -in-ghc-tree, + any.http-api-data ==0.6.2, + http-api-data -use-text-show, + any.http-client ==0.7.19, + http-client +network-uri, + any.http-client-tls ==0.3.6.4, + any.http-date ==0.0.11, + any.http-media ==0.8.1.1, + any.http-semantics ==0.3.0, + any.http-types ==0.12.4, + any.http2 ==5.3.9, + http2 -devel -h2spec, + any.indexed-list-literals ==0.2.1.3, + any.indexed-profunctors ==0.1.1.1, + any.indexed-traversable ==0.1.4, + any.indexed-traversable-instances ==0.1.2, + any.integer-conversion ==0.1.1, + any.integer-gmp ==1.1, + any.integer-logarithms ==1.0.4, + integer-logarithms -check-bounds +integer-gmp, + any.invariant ==0.6.4, + any.iproute ==1.7.15, + any.ipynb ==0.2, + any.ixset-typed ==0.5.1.0, + any.jira-wiki-markup ==1.5.1, + any.js-chart ==2.9.4.1, + any.kan-extensions ==5.2.6, + any.lens ==5.3.4, + lens -benchmark-uniplate -dump-splices +inlining -j +test-hunit +test-properties +test-templates +trustworthy, + any.lens-aeson ==1.2.3, + any.libyaml ==0.1.4, + libyaml -no-unicode -system-libyaml, + any.libyaml-clib ==0.2.5, + any.lifted-async ==0.10.2.7, + any.lifted-base ==0.2.3.12, + any.loglevel ==0.1.0.0, + any.lrucaching ==0.3.4, + any.lsp ==2.7.0.1, + lsp -demo, + any.lsp-types ==2.3.0.1, + lsp-types -force-ospath, + any.managed ==1.0.10, + any.massiv ==1.0.5.0, + massiv -unsafe-checks, + any.math-functions ==0.3.4.4, + math-functions +system-erf +system-expm1, + any.megaparsec ==9.7.0, + megaparsec -dev, + any.memory ==0.18.0, + memory +support_bytestring +support_deepseq, + any.merkle-log ==0.2.0, + any.microlens ==0.4.14.0, + any.microstache ==1.0.3, + any.mime-types ==0.1.2.0, + any.mmorph ==1.2.0, + any.mod ==0.2.0.1, + mod +semirings +vector, + any.monad-control ==1.0.3.1, + any.mono-traversable ==1.0.21.0, + any.mtl ==2.3.1, + any.mtl-compat ==0.2.2, + mtl-compat -two-point-one -two-point-two, + any.mwc-probability ==2.3.1, + any.mwc-random ==0.15.2.0, + mwc-random -benchpapi, + any.natural-arithmetic ==0.2.2.0, + any.neat-interpolation ==0.5.1.4, + any.network ==3.2.7.0, + network -devel, + any.network-byte-order ==0.1.7, + any.network-control ==0.1.7, + any.network-info ==0.2.1, + any.network-uri ==2.6.4.2, + any.nothunks ==0.3.0.0, + nothunks +bytestring +text +vector, + any.old-locale ==1.0.0.7, + any.old-time ==1.1.0.4, + any.optparse-applicative ==0.18.1.0, + optparse-applicative +process, + any.ordered-containers ==0.2.4, + any.os-string ==2.0.4, + any.pact ==4.13.2, + pact -build-tool +cryptonite-ed25519 -tests-in-lib, + any.pact-json ==0.1.0.0, + any.pact-time ==0.3.0.1, + pact-time -with-time, + any.pact-tng ==5.2, + pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, + any.pandoc ==3.7.0.2, + pandoc -embed_data_files, + any.pandoc-types ==1.23.1, + any.parallel ==3.2.2.0, + any.parsec ==3.1.18.0, + any.parser-combinators ==1.3.0, + parser-combinators -dev, + any.parsers ==0.12.12, + parsers +attoparsec +binary +parsec, + any.patience ==0.3, + any.pem ==0.2.4, + any.poly ==0.5.1.0, + poly +sparse, + any.pretty ==1.1.3.6, + any.pretty-show ==1.10, + any.pretty-simple ==4.1.3.0, + pretty-simple -buildexample +buildexe, + any.prettyprinter ==1.7.1, + prettyprinter -buildreadme +text, + any.prettyprinter-ansi-terminal ==1.1.3, + any.primitive ==0.9.1.0, + any.primitive-addr ==0.1.0.3, + any.primitive-offset ==0.2.0.1, + any.primitive-unlifted ==2.1.0.0, + any.process ==1.6.25.0, + any.profunctors ==5.6.2, + any.property-matchers ==0.7.0.0, + any.psqueues ==0.2.8.1, + any.pvar ==1.0.0.0, + any.quickcheck-instances ==0.3.33, + any.ralist ==0.4.0.0, + any.random ==1.3.1, + any.raw-strings-qq ==1.1, + any.recover-rtti ==0.5.0, + any.recv ==0.1.1, + any.reducers ==3.12.5, + any.reflection ==2.1.9, + reflection -slow +template-haskell, + any.regex ==1.1.0.2, + any.regex-base ==0.94.0.3, + any.regex-pcre-builtin ==0.95.2.3.8.44, + any.regex-tdfa ==1.3.2.4, + regex-tdfa +doctest -force-o2, + any.resource-pool ==0.4.0.0, + any.resourcet ==1.3.0, + any.retry ==0.9.3.1, + retry -lib-werror, + any.rocksdb-haskell-kadena ==1.1.0, + rocksdb-haskell-kadena -with-tbb, + any.row-types ==1.0.1.2, + any.rts ==1.0.2, + any.run-st ==0.1.3.3, + any.safe ==0.3.21, + any.safe-exceptions ==0.1.7.4, + any.safecopy ==0.10.4.2, + any.scheduler ==2.0.1.0, + any.scientific ==0.3.8.0, + scientific -integer-simple, + any.semialign ==1.3.1, + semialign +semigroupoids, + any.semigroupoids ==6.0.1, + semigroupoids +comonad +containers +contravariant +distributive +tagged +unordered-containers, + any.semigroups ==0.20, + semigroups +binary +bytestring -bytestring-builder +containers +deepseq +hashable +tagged +template-haskell +text +transformers +unordered-containers, + any.semirings ==0.7, + semirings +containers +unordered-containers, + any.serialise ==0.2.6.1, + serialise +newtime15, + any.servant ==0.20.3.0, + any.servant-client ==0.20.3.0, + any.servant-client-core ==0.20.3.0, + any.servant-server ==0.20.3.0, + any.sha-validation ==0.1.0.1, + any.show-combinators ==0.2.0.0, + any.simple-sendfile ==0.2.32, + simple-sendfile +allow-bsd -fallback, + any.singleton-bool ==0.1.8, + any.skylighting ==0.14.6, + skylighting -executable, + any.skylighting-core ==0.14.6, + skylighting-core -executable, + any.skylighting-format-ansi ==0.1, + any.skylighting-format-blaze-html ==0.1.1.3, + any.skylighting-format-context ==0.1.0.2, + any.skylighting-format-latex ==0.1, + any.skylighting-format-typst ==0.1, + any.socks ==0.6.1, + any.some ==1.0.6, + some +newtype-unsafe, + any.sop-core ==0.5.0.2, + any.sorted-list ==0.2.3.1, + any.split ==0.2.5, + any.splitmix ==0.1.1, + splitmix -optimised-mixer, + any.statistics ==0.16.3.0, + statistics -benchpapi, + any.stm ==2.5.3.1, + any.stm-chans ==3.0.0.9, + any.stopwatch ==0.1.0.6, + stopwatch -test_delay_upper_bound -test_threaded, + any.streaming ==0.2.4.0, + any.streaming-commons ==0.2.3.0, + streaming-commons -use-bytestring-builder, + any.strict ==0.5.1, + any.strict-concurrency ==0.2.4.3, + any.syb ==0.7.2.4, + any.tagged ==0.8.9, + tagged +deepseq +transformers, + any.tagsoup ==0.14.8, + any.tasty ==1.5.3, + tasty +unix, + any.tasty-golden ==2.3.5, + tasty-golden -build-example, + any.tasty-hedgehog ==1.4.0.2, + any.tasty-hunit ==0.10.2, + any.tasty-json ==0.1.0.0, + any.tasty-quickcheck ==0.11.1, + any.template-haskell ==2.22.0.0, + any.temporary ==1.3, + any.terminal-progress-bar ==0.4.2, + any.terminal-size ==0.3.4, + any.terminfo ==0.4.1.7, + any.texmath ==0.12.10.3, + texmath -executable -server, + any.text ==2.1.2, + any.text-conversions ==0.3.1.1, + any.text-iso8601 ==0.1.1, + any.text-rope ==0.3, + text-rope -debug, + any.text-short ==0.1.6, + text-short -asserts, + any.th-abstraction ==0.7.1.0, + any.th-compat ==0.1.6, + any.th-expand-syns ==0.4.12.0, + any.th-lift ==0.8.6, + any.th-lift-instances ==0.1.20, + any.th-orphans ==0.13.16, + any.th-reify-many ==0.1.10, + any.these ==1.2.1, + any.time ==1.12.2, + any.time-compat ==1.9.8, + any.time-locale-compat ==0.1.1.5, + time-locale-compat +old-locale, + any.time-manager ==0.2.3, + any.tls ==2.1.10, + tls -devel, + any.tls-session-manager ==0.0.8, + any.token-bucket ==0.1.0.1, + token-bucket +use-cbits, + any.toml-parser ==2.0.1.2, + any.torsor ==0.1.0.1, + any.transformers ==0.6.1.1, + any.transformers-base ==0.4.6, + transformers-base +orphaninstances, + any.transformers-compat ==0.7.2, + transformers-compat -five +five-three -four +generic-deriving +mtl -three -two, + any.trifecta ==2.1.4, + any.tuples ==0.1.0.0, + any.typed-process ==0.2.13.0, + any.typst ==0.8.0.1, + typst -executable, + any.typst-symbols ==0.1.8.1, + any.unicode-collation ==0.1.3.6, + unicode-collation -doctests -executable, + any.unicode-data ==0.6.0, + unicode-data -dev-has-icu, + any.unicode-transforms ==0.4.0.1, + unicode-transforms -bench-show -dev -has-icu -has-llvm -use-gauge, + any.uniplate ==1.6.13, + any.unix ==2.8.6.0, + any.unix-compat ==0.7.4, + any.unix-time ==0.4.16, + any.unlifted ==0.2.3.0, + any.unliftio ==0.2.25.1, + any.unliftio-core ==0.2.1.0, + any.unordered-containers ==0.2.20, + unordered-containers -debug, + any.utf8-string ==1.0.2, + any.uuid ==1.3.16, + any.uuid-types ==1.0.6, + any.validation ==1.1.3, + any.vault ==0.3.1.5, + vault +useghc, + any.vector ==0.13.2.0, + vector +boundschecks -internalchecks -unsafechecks -wall, + any.vector-algorithms ==0.9.1.0, + vector-algorithms +bench +boundschecks -internalchecks -llvm -unsafechecks, + any.vector-binary-instances ==0.2.5.2, + any.vector-sized ==1.6.1, + any.vector-stream ==0.1.0.1, + any.vector-th-unbox ==0.2.2, + any.void ==0.7.3, + void -safe, + any.wai ==3.2.4, + any.wai-app-static ==3.1.9, + wai-app-static +crypton -print, + any.wai-cors ==0.2.7, + any.wai-extra ==3.1.17, + wai-extra -build-example, + any.wai-logger ==2.5.0, + any.wai-middleware-throttle ==0.3.0.1, + any.wai-middleware-validation ==0.1.0.2, + any.warp ==3.4.8, + warp +allow-sendfilefd -network-bytestring -warp-debug +x509, + any.warp-tls ==3.4.13, + any.wherefrom-compat ==0.1.1.1, + any.wide-word ==0.1.7.0, + any.witherable ==0.5, + any.wl-pprint-annotated ==0.1.0.1, + any.word8 ==0.1.3, + any.wreq ==0.5.4.3, + wreq -aws -developer +doctest -httpbin, + any.xml ==1.3.14, + any.xml-conduit ==1.10.0.0, + any.xml-types ==0.3.8, + any.yaml ==0.11.11.2, + yaml +no-examples +no-exe, + any.yet-another-logger ==0.4.2, + yet-another-logger -tbmqueue, + any.zigzag ==0.1.0.0, + any.zip-archive ==0.4.3.2, + zip-archive -executable, + any.zlib ==0.7.1.0, + zlib -bundled-c-zlib +non-blocking-ffi +pkg-config +index-state: hackage.haskell.org 2025-06-05T15:56:06Z From a3156e3685dabbd51d00fc807860dcbd8d9d4d30 Mon Sep 17 00:00:00 2001 From: chessai Date: Tue, 17 Jun 2025 13:44:20 -0700 Subject: [PATCH 246/378] update to version 2.29.1 --- chainweb.cabal | 2 +- cwtools/cwtools.cabal | 2 +- node/chainweb-node.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index 771f2f474a..10da248eb6 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -1,7 +1,7 @@ cabal-version: 3.8 name: chainweb -version: 2.29 +version: 2.29.1 synopsis: A Proof-of-Work Parallel-Chain Architecture for Massive Throughput description: A Proof-of-Work Parallel-Chain Architecture for Massive Throughput. homepage: https://github.com/kadena-io/chainweb diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index 6389372245..12d48d7340 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -1,7 +1,7 @@ cabal-version: 3.8 name: cwtools -version: 2.29 +version: 2.29.1 synopsis: A collection of various tools for Chainweb users and developers. description: A collection of various tools for Chainweb users and developers. homepage: https://github.com/kadena-io/chainweb diff --git a/node/chainweb-node.cabal b/node/chainweb-node.cabal index 0ad7cdf648..fb5f5aa8e5 100644 --- a/node/chainweb-node.cabal +++ b/node/chainweb-node.cabal @@ -1,7 +1,7 @@ cabal-version: 3.8 name: chainweb-node -version: 2.29 +version: 2.29.1 synopsis: A Proof-of-Work Parallel-Chain Architecture for Massive Throughput description: A Proof-of-Work Parallel-Chain Architecture for Massive Throughput. homepage: https://github.com/kadena-io/chainweb From 5e2d640549ff25e6a687e0368b00072e582cd70b Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 17 Jun 2025 16:52:28 -0400 Subject: [PATCH 247/378] test for sigcaps with spaces --- .../Chainweb/Test/Pact5/RemotePactTest.hs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs index 71de36f680..0cdebf9524 100644 --- a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs @@ -151,6 +151,7 @@ tests rdb = withResource' (evaluate httpManager >> evaluate cert) $ \_ -> , testCaseSteps "transition occurs" (transitionOccurs rdb) , testCaseSteps "transition crosschain" (transitionCrosschain rdb) , testCaseSteps "upgradeNamespaceTests" (upgradeNamespaceTests rdb) + , testCaseSteps "invalidSigCapNameTest" (invalidSigCapNameTest rdb) , localTests rdb ] @@ -1166,6 +1167,27 @@ upgradeNamespaceTests baseRdb _step = runResourceT $ do ? P.match _PString ? textContains "Loaded module ns" +invalidSigCapNameTest :: RocksDb -> Step -> IO () +invalidSigCapNameTest baseRdb _step = runResourceT $ do + let v = pact5InstantCpmTestVersion singletonChainGraph + let cid = unsafeChainId 0 + fx <- mkFixture v baseRdb + + liftIO $ do + badCmd <- buildTextCmd v $ + set cbSigners + [mkEd25519Signer' sender00 + -- sender00 controls ns, but module upgrades require unscoped signatures + [CapToken (QualifiedName "GAS " (ModuleName "coin" Nothing)) []] + ] $ + defaultCmd cid + send fx v cid [badCmd] + & P.fails + ? P.match _FailureResponse + ? P.succeed + advanceAllChains_ fx + poll fx v cid [cmdToRequestKey badCmd] + >>= P.match (_head . _Nothing) P.succeed ---------------------------------------------------- From cd2944c570e2bb91e585afa29b090f9b1cf13605 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 17 Jun 2025 16:43:20 -0400 Subject: [PATCH 248/378] Unify Pact 5 command parsing between NewBlock and ValidateBlock Change-Id: Id00000009b01c5fb85a475eb6233cc3b175cc4ea --- src/Chainweb/Pact/PactService.hs | 16 +- .../Pact/PactService/Pact5/ExecBlock.hs | 2 +- src/Chainweb/Pact5/Transaction.hs | 156 +----------------- 3 files changed, 25 insertions(+), 149 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index b39fcc6bb8..795b4095b8 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -862,7 +862,21 @@ execLocal cwtx preflight sigVerify rdepth = pactLabel "execLocal" $ do let pact5RequestKey = Pact5.RequestKey (Pact5.Hash $ Pact4.unHash $ Pact4.toUntypedHash $ Pact4._cmdHash cwtx) evalContT $ withEarlyReturn $ \earlyReturn -> do pact5Cmd <- case Pact5.parsePact4Command cwtx of - Left (fmap Pact5.spanInfoToLineInfo -> parseError) -> + Left (Left errText) -> do + earlyReturn $ Pact5LocalResultLegacy Pact5.CommandResult + { _crReqKey = pact5RequestKey + , _crTxId = Nothing + , _crResult = Pact5.PactResultErr $ + Pact5.pactErrorToOnChainError $ Pact5.PEParseError + (Pact5.ParsingError $ "pact 4/5 parsing compatibility mismatch: " <> errText) + (Pact5.LineInfo 0) + , _crGas = Pact5.Gas $ fromIntegral $ cmd ^. Pact4.cmdPayload . Pact4.pMeta . Pact4.pmGasLimit + , _crLogs = Nothing + , _crContinuation = Nothing + , _crMetaData = Nothing + , _crEvents = [] + } + Left (Right (fmap Pact5.spanInfoToLineInfo -> parseError)) -> earlyReturn $ Pact5LocalResultLegacy Pact5.CommandResult { _crReqKey = pact5RequestKey , _crTxId = Nothing diff --git a/src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs index 5154c08d7f..433e0c66c0 100644 --- a/src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/Pact5/ExecBlock.hs @@ -563,7 +563,7 @@ validateRawChainwebTx -> Pact4.UnparsedTransaction -> ExceptT InsertError IO Pact5.Transaction validateRawChainwebTx logger v cid db blockHandle parentTime bh isGenesis tx = do - tx' <- either (throwError . InsertErrorPactParseError . Pact5.renderText) return $ Pact5.parsePact4Command tx + tx' <- either (throwError . InsertErrorPactParseError . either id Pact5.renderText) return $ Pact5.parsePact4Command tx liftIO $ do logDebug_ logger $ "validateRawChainwebTx: parse succeeded" validateParsedChainwebTx logger v cid db blockHandle parentTime bh isGenesis tx' diff --git a/src/Chainweb/Pact5/Transaction.hs b/src/Chainweb/Pact5/Transaction.hs index d9a8aa8714..39bf94c9ce 100644 --- a/src/Chainweb/Pact5/Transaction.hs +++ b/src/Chainweb/Pact5/Transaction.hs @@ -21,50 +21,22 @@ module Chainweb.Pact5.Transaction ) where import "aeson" Data.Aeson qualified as Aeson -import "base" Data.Coerce (coerce) import "base" Data.Function import "base" GHC.Generics (Generic) import "bytestring" Data.ByteString.Char8 (ByteString) import "bytestring" Data.ByteString.Short qualified as SB import "deepseq" Control.DeepSeq import "lens" Control.Lens -import "pact" Pact.Parse qualified as Pact4 -import "pact" Pact.Types.Capability qualified as Pact4 -import "pact" Pact.Types.ChainId qualified as Pact4 -import "pact" Pact.Types.ChainMeta qualified as Pact4 -import "pact" Pact.Types.Command qualified as Pact4 -import "pact" Pact.Types.Crypto qualified as Pact4 -import "pact" Pact.Types.Gas qualified as Pact4 -import "pact" Pact.Types.Hash qualified as Pact4 -import "pact" Pact.Types.PactValue qualified as Pact4 -import "pact" Pact.Types.RPC qualified as Pact4 -import "pact" Pact.Types.SPV qualified as Pact4 -import "pact" Pact.Types.Term.Internal (PactId(..)) -import "pact" Pact.Types.Verifier (VerifierName(..)) -import "pact" Pact.Types.Verifier qualified as Pact4 import "pact-json" Pact.JSON.Encode qualified as J import "pact-json" Pact.JSON.Encode (Encode(..)) -import "pact-json" Pact.JSON.Legacy.Value qualified as J -import "pact-tng" Pact.Core.Capabilities import "pact-tng" Pact.Core.ChainData -import "pact-tng" Pact.Core.Command.Crypto -import "pact-tng" Pact.Core.Command.RPC import "pact-tng" Pact.Core.Command.Types -import "pact-tng" Pact.Core.Gas.Types -import "pact-tng" Pact.Core.Hash -import "pact-tng" Pact.Core.Names -import "pact-tng" Pact.Core.PactValue -import "pact-tng" Pact.Core.SPV import "pact-tng" Pact.Core.StableEncoding -import "pact-tng" Pact.Core.Verifiers -import "pact-tng" Pact.Core.Verifiers qualified as Pact5 -import "pact-tng" Pact.Core.Signer import "pact-tng" Pact.Core.Errors import "pact-tng" Pact.Core.Info import "pact-tng" Pact.Core.Pretty qualified as Pact5 import "text" Data.Text (Text) import "text" Data.Text.Encoding (decodeUtf8, encodeUtf8) -import Chainweb.Pact.Conversion import Chainweb.Pact4.Transaction qualified as Pact4 import Chainweb.Utils @@ -109,7 +81,7 @@ payloadCodec = Codec enc dec Just (cmd :: Command Text) -> over _Left Pact5.renderCompactString $ parseCommand cmd Nothing -> Left "decode PayloadWithText failed" -parseCommand :: Command Text -> Either (PactError SpanInfo) (Command (PayloadWithText PublicMeta ParsedCode)) +parseCommand :: Command Text -> Either (PactError SpanInfo) Transaction parseCommand cmd = do let cmd' = fmap encodeUtf8 cmd let code = SB.toShort (_cmdPayload cmd') @@ -119,124 +91,14 @@ parseCommand cmd = do encodePayload :: PayloadWithText meta code -> ByteString encodePayload = SB.fromShort . _payloadBytes -parsePact4Command :: Pact4.UnparsedTransaction -> Either (PactError SpanInfo) Transaction -parsePact4Command cmd4 = do - let cmd = fromPact4Command cmd4 - payloadWithParsedCode :: Payload PublicMeta ParsedCode <- - (pPayload . _Exec . pmCode) parsePact (cmd ^. cmdPayload . payloadObj) - let payloadWithTextWithParsedCode = - UnsafePayloadWithText (cmd ^. cmdPayload . payloadBytes) payloadWithParsedCode - return $ cmd & cmdPayload .~ payloadWithTextWithParsedCode - -fromPact4Command :: Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text) -> Command (PayloadWithText PublicMeta Text) -fromPact4Command cmd4 = Command - { _cmdPayload = fromPact4PayloadWithText (Pact4._cmdPayload cmd4) - , _cmdSigs = map fromPact4UserSig (Pact4._cmdSigs cmd4) - , _cmdHash = fromPact4Hash (Pact4._cmdHash cmd4) - } - where - fromPact4PayloadWithText :: Pact4.PayloadWithText Pact4.PublicMeta Text -> PayloadWithText PublicMeta Text - fromPact4PayloadWithText payload4 = UnsafePayloadWithText - { _payloadBytes = Pact4.payloadBytes payload4 - , _payloadObj = fromPact4Payload (Pact4.payloadObj payload4) - } - - fromPact4Payload :: Pact4.Payload Pact4.PublicMeta Text -> Payload PublicMeta Text - fromPact4Payload payload4 = Payload - { _pPayload = fromPact4RPC (Pact4._pPayload payload4) - , _pNonce = Pact4._pNonce payload4 - , _pMeta = fromPact4PublicMeta (Pact4._pMeta payload4) - , _pSigners = map fromPact4Signer (Pact4._pSigners payload4) - , _pVerifiers = map fromPact4Verifier <$> Pact4._pVerifiers payload4 - , _pNetworkId = fromPact4NetworkId <$> Pact4._pNetworkId payload4 - } - - fromPact4RPC :: Pact4.PactRPC c -> PactRPC c - fromPact4RPC = \case - Pact4.Exec execMsg -> Exec $ ExecMsg - { _pmCode = Pact4._pmCode execMsg - , _pmData = legacyJsonToPactValue (Pact4._pmData execMsg) - } - Pact4.Continuation contMsg -> Continuation $ ContMsg - { _cmPactId = coerce Pact4._cmPactId contMsg - , _cmStep = Pact4._cmStep contMsg - , _cmRollback = Pact4._cmRollback contMsg - , _cmData = legacyJsonToPactValue (Pact4._cmData contMsg) - , _cmProof = ContProof . Pact4._contProof <$> Pact4._cmProof contMsg - } - - fromPact4PublicMeta :: Pact4.PublicMeta -> PublicMeta - fromPact4PublicMeta pm4 = PublicMeta - { _pmChainId = coerce (Pact4._pmChainId pm4) - , _pmSender = Pact4._pmSender pm4 - , _pmGasLimit = fromPact4GasLimit (Pact4._pmGasLimit pm4) - , _pmGasPrice = fromPact4GasPrice (Pact4._pmGasPrice pm4) - , _pmTTL = fromPact4TTLSeconds (Pact4._pmTTL pm4) - , _pmCreationTime = fromPact4TxCreationTime (Pact4._pmCreationTime pm4) - } - - fromPact4Signer :: Pact4.Signer -> Signer - fromPact4Signer signer4 = Signer - { _siScheme = Pact4._siScheme signer4 <&> \case { Pact4.ED25519 -> ED25519; Pact4.WebAuthn -> WebAuthn; } - , _siPubKey = Pact4._siPubKey signer4 - , _siAddress = Pact4._siAddress signer4 - , _siCapList = map fromPact4SigCapability (Pact4._siCapList signer4) - } - - fromPact4SigCapability :: Pact4.SigCapability -> SigCapability - fromPact4SigCapability cap4 = SigCapability $ CapToken - { _ctName = fromLegacyQualifiedName (Pact4._scName cap4) - , _ctArgs = fromPact4PactValue <$> Pact4._scArgs cap4 - } - - fromPact4Verifier :: Pact4.Verifier Pact4.ParsedVerifierProof -> Verifier Pact5.ParsedVerifierProof - fromPact4Verifier verifier4 = Verifier - { _verifierName = coerce (Pact4._verifierName verifier4) - , _verifierProof = Pact5.ParsedVerifierProof - $ fromPact4PactValue - $ case Pact4._verifierProof verifier4 of { Pact4.ParsedVerifierProof pv -> pv; } - , _verifierCaps = fromPact4SigCapability <$> Pact4._verifierCaps verifier4 - } - - fromPact4NetworkId :: Pact4.NetworkId -> NetworkId - fromPact4NetworkId = NetworkId . Pact4._networkId - - legacyJsonToPactValue :: J.LegacyValue -> PactValue - legacyJsonToPactValue lv = case eitherDecodeStable @PactValue (J.encodeStrict lv) of - Right pv -> pv - Left msg -> error $ "TODO: don't throw an error here, use Either: " <> sshow (J.encodeStrict lv, msg) - - fromPact4PactValue :: Pact4.PactValue -> PactValue - fromPact4PactValue pv4 = case fromLegacyPactValue pv4 of - Right pv -> pv - Left err -> error $ "TODO: don't throw an error here: " ++ show err - - fromPact4UserSig :: Pact4.UserSig -> UserSig - fromPact4UserSig = \case - Pact4.ED25519Sig txt -> ED25519Sig txt - Pact4.WebAuthnSig webAuthnSig4 -> WebAuthnSig $ WebAuthnSignature - { clientDataJSON = Pact4.clientDataJSON webAuthnSig4 - , authenticatorData = Pact4.authenticatorData webAuthnSig4 - , signature = Pact4.signature webAuthnSig4 - } - - fromPact4Hash :: Pact4.PactHash -> Hash - fromPact4Hash (Pact4.TypedHash sbs) = Hash sbs - - fromPact4ParsedInteger :: Pact4.ParsedInteger -> SatWord - fromPact4ParsedInteger (Pact4.ParsedInteger i) = fromIntegral i - - fromPact4GasLimit :: Pact4.GasLimit -> GasLimit - fromPact4GasLimit (Pact4.GasLimit i) = GasLimit (Gas (fromPact4ParsedInteger i)) - - fromPact4GasPrice :: Pact4.GasPrice -> GasPrice - fromPact4GasPrice (Pact4.GasPrice (Pact4.ParsedDecimal d)) = GasPrice d - - fromPact4TTLSeconds :: Pact4.TTLSeconds -> TTLSeconds - fromPact4TTLSeconds (Pact4.TTLSeconds (Pact4.ParsedInteger i)) = TTLSeconds i - - fromPact4TxCreationTime :: Pact4.TxCreationTime -> TxCreationTime - fromPact4TxCreationTime (Pact4.TxCreationTime (Pact4.ParsedInteger i)) = TxCreationTime i +parsePact4Command :: Pact4.UnparsedTransaction -> Either (Either Text (PactError SpanInfo)) Transaction +parsePact4Command bs = + case Aeson.decodeStrict' (codecEncode Pact4.rawCommandCodec bs) of + -- Note: this can only ever emit a `ParseError`, which by default are quite small. + -- Still, `pretty` instances are scary, but this cannot make it into block outputs so this should + -- be okay + Just (cmd :: Command Text) -> over _Left Right $ parseCommand cmd + Nothing -> Left $ Left "decode PayloadWithText failed" -- decodePayload -- :: ByteString From 9b5a0562284d62bac3b561467e181214696d6619 Mon Sep 17 00:00:00 2001 From: chessai Date: Tue, 17 Jun 2025 14:33:00 -0700 Subject: [PATCH 249/378] update CHANGELOG for version 2.29.1 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b424742308..bb59162eb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.29.1 (2025-06-17) +This is a minor point release. Upgrading is **strongly recommended**. + +To upgrade, pull the latest docker image, or download the binary and +restart the node with the same configuration file as before. + +### Changes +- Unify Pact 5 command parsing between NewBlock and ValidateBlock [`055567d`](https://github.com/kadena-io/chainweb-node/commit/055567d00eef48d295cc35c510d1316e400073e8) +- Add a test for malformed capability names [`9f0b409`](https://github.com/kadena-io/chainweb-node/commit/9f0b40936770e1a67ac45ecd81087ec2e66d09d7) + ## 2.29 (2025-04-16) This is a major version update. This release replaces all previous versions. From af2271b08e0345cb3e86b04e2e3dd22ac686937e Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 22 Jun 2025 23:52:40 -0700 Subject: [PATCH 250/378] add xchan-redeem system contract account and larger testnet allocations --- cwtools/evm-genesis/Main.hs | 52 +++++++++++++++++-- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 24 ++++----- src/Chainweb/Version/EvmDevelopment.hs | 10 ++-- .../Version/EvmDevelopmentSingleton.hs | 4 +- src/Chainweb/Version/EvmTestnet.hs | 10 ++-- 5 files changed, 71 insertions(+), 29 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 2556db47eb..d2bcc7462a 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -159,6 +159,22 @@ mkRpcCtx u = do -- -------------------------------------------------------------------------- -- -- Spec file settings +-- | +-- +-- System contract addresses: +-- +-- * Chainweb-chainId system contract: +-- 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc +-- Keccak256("/Chainweb/Chain/Id/") +-- +-- * Native X-Chan redeem system contract: +-- 0x49eed2ac33f09e931bd660f0168417b9614485b6 +-- Keccack256("/Chainweb/XChan/Redeem/") +-- +-- * ERC-20 x-chain SPV Precompile address from KIP-34 (EthDenver 2025 demo): +-- 48c3b4d2757447601776837b6a85f31ef88a87bf +-- Keccak256("/Chainweb/KIP-34/VERIFY/SVP/") +-- baseSpecFile :: Natural -- ^ Ethereum network id offset @@ -196,11 +212,26 @@ baseSpecFile netId offset cid genesisTime allocs = object , "shanghaiTime" .= i 0 , "cancunTime" .= i 0 , "pragueTime" .= i 0 + , "blobSchedule" .= object + [ "cancun" .= object + [ "target" .= i 0 + , "max" .= i 0 + , "baseFeeUpdateFraction" .= i 3338477 + ] + , "prague" .= object + [ "target" .= i 0 + , "max" .= i 0 + , "baseFeeUpdateFraction" .= i 5007716 + ] + ] ] , "timestamp" .= printf @(Natural -> String) "0x%x" genesisTime , "extraData" .= t "0x" , "gasLimit" .= t "0x1c9c380" - , "alloc" .= object (chainwebChainIdAlloc : allocs) + , "alloc" .= object + ( chainwebChainIdAlloc + : allocs + ) , "number" .= t "0x0" , "nonce" .= t "0x0" , "difficulty" .= t "0x0" @@ -288,6 +319,12 @@ evmDevnetSpecFile offset cid = baseSpecFile 1789 offset cid 0x684c5d2a [ "balance" .= t "0xd3c21bcecceda1000000" ] , "0x3492DA004098d728201fD82657f1207a6E5426bd" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ] + + -- Native X-Chan redeem system contract: Keccack256("/Chainweb/XChan/Redeem/") + -- TODO: code + , "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object + [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ] ] -- -------------------------------------------------------------------------- -- @@ -313,15 +350,20 @@ evmTestnetSpecFile evmTestnetSpecFile cid = baseSpecFile 5920 20 cid 0x684c5d2a -- faucet deployer address that corresponds to the DEPLOYER_PRIVATE_KEY [ "0x9440d8ff19D278F401f49080BEfdDEFbE54F0eF2" .= object - [ "balance" .= t "0xd3c21bcecceda1000000" ] + [ "balance" .= t "0x422ca8b0a00a425000000" ] -- is the faucet wallet address that correspondesds to the FAUCET_PRIVATE_KEY , "0xE482e4F590D4155B51F4Fc21d64823f4d7854397" .= object - [ "balance" .= t "0xd3c21bcecceda1000000" ] + [ "balance" .= t "0x422ca8b0a00a425000000" ] -- additional platform funds that are separate from the faucet accounts , "0xeC1B36992C3c7d0f7AbB8EDA43EEbC9A418c0A1e" .= object - [ "balance" .= t "0xd3c21bcecceda1000000" ] + [ "balance" .= t "0x422ca8b0a00a425000000" ] + + -- Native X-Chan redeem system contract: Keccack256("/Chainweb/XChan/Redeem/") + -- TODO: code + , "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object + [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ] ] - -- TODO: Native-X-Chain System contract -- -------------------------------------------------------------------------- -- -- Spec File For Kadena Testnet diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 9b62551878..2e2bd711a6 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -83,35 +83,35 @@ genesisBlocks c = go (_versionCode implicitVersion) (_chainId c) go v | v == _versionCode EvmDevelopmentSingleton = \case ChainId 0 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoDNRqqHU3RpYNNK87jvHr0Nrl0zaMEylNVRiAm6cSPMAoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBZOJ7w1YV19TPPPqu_4esGETJ95T1gtq4RUblQVIm0foFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopmentPair = \case ChainId 1 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoO4IB2_chJ_NuzlG69CajkvHzxNR4zX19ioL4X2oKS17oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKavrxhzoUUknAFRgCni8ZYTYWQs-2t3D9YaU8MDe2szoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopment = \case ChainId 20 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoIOOAysXKLGptB5GO1vKMMrTE7cWB14Ft14pZ7CGpFFloFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMMZYU59v3qqr57B_oZNDrWJr44u4_bGEKcPTF-DF4GtoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 21 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoG85Bmgt7tyzR2m55gEFQFi_-0yVF-aBjFqWmRG8IOExoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGmYqAqdbvdkTWumqiLP71cqmqyAVRkooXMCsjcM1nzgoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 22 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoHm65lj2ks7TAHeTUt8sRrclRXGT2z2P-5FSk2TmLnN-oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoARztiiha2rdixkEAW7d-R3-7QNCRrFyhRprB3tfy1W7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 23 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBRUfqOU18XP_669WHx_2BouSX8LYvuO-kQRT5fQLN2ooFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOzaiCzWA-FrPi-xoEn33hMmm5t8bHT7A-bAOGd63TvyoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 24 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBOHYONSIUU7CyhAfW-z14OvuVy3OhGxgCMudl7HWsk7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoN1tpEmdfWt7M0qaa35WbNa4vdmit6i4I35JxRs8TBsKoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmTestnet = \case ChainId 20 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoB9BSbN9UripcrPku1xhdtVlXJckS0SD-Qgb7xB_YqfPoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKbDZWZrFFqWPU3bezQI5R_qPD3-3X34MMgXKzdvcXi0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 21 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoH7quUdgaBXGTFCA4UdvHP0KbHArLiqGIXn1Myj4kVC6oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGLMUuszrb8PwGI6UataLWkRYy1gg1T5GdD259GhltLioFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 22 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBvsYLx03DzyEMz0Y0sbzES1GzyumtqOWOqwb7cafeYyoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMpjjCvbdyvJ3FfSW-YSPOGwCEEBytSjDZUEJfQLPHDWoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 23 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMG5jOwlX3xJyphhtrn2Ng10bz0c6BiCt6x2LdgsQmvKoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoDowccPnGmALtoavlrHGPC7oI5adHM-YlihcTftWgyoKoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 24 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoClOoTcss2dq2iPBfMhtScQdj1v74lREk_HlAeTxckMzoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoPqG_AF2PsNCMT1yzVN2ojlHJWs5JZejHkYmlkQoOKs3oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | otherwise = error "requested genesis block for unsupported chain" diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 607cc8f0bd..6a0b0f1ad0 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -90,11 +90,11 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider - , (unsafeChainId 20, unsafeFromText "XEv4ey1P7XOu63vMZgH01ANKKUoEWuo3-PWOaHfOJfA") - , (unsafeChainId 21, unsafeFromText "FRCRkgrvBv7BgzahtJynhYLAh-ZSMTgNvpyHJIyAr-c") - , (unsafeChainId 22, unsafeFromText "c5Dg1YhC3Yafl_uMrlift9_f_UU0pHT2VEv70RAJQ4A") - , (unsafeChainId 23, unsafeFromText "pCCVAAxn7zBggAxaXqp5cqStrs62FKfE0--VsK3OWIk") - , (unsafeChainId 24, unsafeFromText "h93bpavjG94oytkKCe1yJUn4AU-PwW7dLsLeu-6LDwo") + , (unsafeChainId 20, unsafeFromText "9tTN4gIlX0Ejv5SQkcDLtLA6Ud_8AQGmy0YWOvEvRuA") + , (unsafeChainId 21, unsafeFromText "yGNn2Y7PMu3necKVlrr0gsET_VDVSPRtWmG11lMpDb4") + , (unsafeChainId 22, unsafeFromText "Rto2JWTsPeaCtLrkC0On_tSqw-5MGCjxbfPrV3hJOeU") + , (unsafeChainId 23, unsafeFromText "qKEsJPiIE5pVeei4luptp8kFFOsGGvFYmTUfQVGd1tw") + , (unsafeChainId 24, unsafeFromText "vQJsV5sOVKzUgTQz9kzPDRwaqxVehqhO847CukxeQ2w") -- Minimal Payload Provider , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") diff --git a/src/Chainweb/Version/EvmDevelopmentSingleton.hs b/src/Chainweb/Version/EvmDevelopmentSingleton.hs index b66aa63b32..e958c8a717 100644 --- a/src/Chainweb/Version/EvmDevelopmentSingleton.hs +++ b/src/Chainweb/Version/EvmDevelopmentSingleton.hs @@ -77,7 +77,7 @@ evmDevnetPair = withVersion evmDevnetPair $ ChainwebVersion -- Pact Payload Provider [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") -- EVM Payload Provider - , (unsafeChainId 1, unsafeFromText "Uww99ycc-FPrNroeVFkf3azD3nG2gNDO2_HnxcNjHb8") + , (unsafeChainId 1, unsafeFromText "SjLt6XGe5lo_1AC03lF9k2mQec-caOo-3Gp8OQJZ-j8") ] } @@ -122,7 +122,7 @@ evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion [ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 0x684c5d2a))) ] , _genesisBlockPayload = onChains $ -- EVM Payload Provider - [ (unsafeChainId 0, unsafeFromText "gY8WCR0oQbwRgQK2QAvD-0aB0Xp27SahdlldrMVwnb4") ] + [ (unsafeChainId 0, unsafeFromText "_2PRy30tARJ_JjJkgGIKMaFVnxUgkDoQY2ETLQQp_ak") ] } -- still the *default* block gas limit is set, see diff --git a/src/Chainweb/Version/EvmTestnet.hs b/src/Chainweb/Version/EvmTestnet.hs index 0d88acf380..5b8bef2b32 100644 --- a/src/Chainweb/Version/EvmTestnet.hs +++ b/src/Chainweb/Version/EvmTestnet.hs @@ -90,11 +90,11 @@ evmTestnet = withVersion evmTestnet $ ChainwebVersion , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider - , (unsafeChainId 20, unsafeFromText "zjxImGB3TlrwP23JPAhA8naoW2BcBcCz5-x8ZkxPMgI") - , (unsafeChainId 21, unsafeFromText "LHOE9UrFkPs_9YIrI5yv-2wNX-XOYu9ypYZqP_wJdGY") - , (unsafeChainId 22, unsafeFromText "mmEOxyCfuHs_Z7ews_AP5iCpEi_P_CwhUPc_MIhMvGw") - , (unsafeChainId 23, unsafeFromText "pTMEi19DTelaeeJtMyn9TCee02GnilIGNoTCTmD6HyA") - , (unsafeChainId 24, unsafeFromText "PstzPAbKeFLk4QuncYB_UfC4Qa4Tonui7AY1S2YZbUY") + , (unsafeChainId 20, unsafeFromText "alGF_--FuFzVcFCR6JbGv0DYwiHTH57maa0K6m3-7-w") + , (unsafeChainId 20, unsafeFromText "Q39zqJqrJhlL16fZR2v8QDEqAmf4dqkqLJX--prSa10") + , (unsafeChainId 20, unsafeFromText "fFvj_sA6jD2FlDbRDb25bLPSMBfwIZQeJCRcwEUFsps") + , (unsafeChainId 20, unsafeFromText "bo4xDNcaPh8iYCtWx8vMLHEQ2Q7OSFQFQpOt_Dfb2LM") + , (unsafeChainId 20, unsafeFromText "RO-CWlqduZRJLc57vj1_l_sHlqUjTmtXSAXMHvuRtwU") -- Minimal Payload Provider , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") From 80c8bb04c50bd3e2245d2c7bb9d8de18c8471a17 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 23 Jun 2025 12:18:40 -0700 Subject: [PATCH 251/378] fix evm-testnet genesis block payload hashes --- src/Chainweb/Version/EvmTestnet.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/Version/EvmTestnet.hs b/src/Chainweb/Version/EvmTestnet.hs index 5b8bef2b32..b281192684 100644 --- a/src/Chainweb/Version/EvmTestnet.hs +++ b/src/Chainweb/Version/EvmTestnet.hs @@ -91,10 +91,10 @@ evmTestnet = withVersion evmTestnet $ ChainwebVersion , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider , (unsafeChainId 20, unsafeFromText "alGF_--FuFzVcFCR6JbGv0DYwiHTH57maa0K6m3-7-w") - , (unsafeChainId 20, unsafeFromText "Q39zqJqrJhlL16fZR2v8QDEqAmf4dqkqLJX--prSa10") - , (unsafeChainId 20, unsafeFromText "fFvj_sA6jD2FlDbRDb25bLPSMBfwIZQeJCRcwEUFsps") - , (unsafeChainId 20, unsafeFromText "bo4xDNcaPh8iYCtWx8vMLHEQ2Q7OSFQFQpOt_Dfb2LM") - , (unsafeChainId 20, unsafeFromText "RO-CWlqduZRJLc57vj1_l_sHlqUjTmtXSAXMHvuRtwU") + , (unsafeChainId 21, unsafeFromText "Q39zqJqrJhlL16fZR2v8QDEqAmf4dqkqLJX--prSa10") + , (unsafeChainId 22, unsafeFromText "fFvj_sA6jD2FlDbRDb25bLPSMBfwIZQeJCRcwEUFsps") + , (unsafeChainId 23, unsafeFromText "bo4xDNcaPh8iYCtWx8vMLHEQ2Q7OSFQFQpOt_Dfb2LM") + , (unsafeChainId 24, unsafeFromText "RO-CWlqduZRJLc57vj1_l_sHlqUjTmtXSAXMHvuRtwU") -- Minimal Payload Provider , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") From d992e32c800cf04d5266c28ac35f13dca5926b40 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 11 Jun 2025 22:46:39 -0400 Subject: [PATCH 252/378] Stop erroring on failed initial PP sync PayloadProviders failing their initial syncToBlock call is fine, so we comment this. If the sync fails, the way we want to recover is via the ordinary cut pipeline. Change-Id: Id00000001c9ac9aa065fc61bee9a7865f43b7f7c --- src/Chainweb/Chainweb.hs | 98 +--------------- src/Chainweb/Chainweb/CutResources.hs | 2 +- src/Chainweb/CutDB.hs | 129 ++++++++++++++++++--- test/unit/Chainweb/Test/CutDB.hs | 51 ++++---- test/unit/Chainweb/Test/Pact/CutFixture.hs | 2 +- 5 files changed, 146 insertions(+), 136 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index a647a6472e..cdb30df5c1 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -98,12 +98,11 @@ import Configuration.Utils hiding (Error, Lens', disabled) import Control.Concurrent (threadDelay) import Control.Concurrent.Async import Control.DeepSeq -import Control.Exception +import Control.Exception.Safe import Control.Lens hiding ((.=), (<.>)) import Control.Monad -import Control.Monad.Catch (MonadThrow (throwM)) import Control.Monad.IO.Class -import Control.Monad.Trans.Resource +import Control.Monad.Trans.Resource hiding (throwM) import Data.Foldable import Data.HashMap.Strict qualified as HM @@ -133,7 +132,6 @@ import System.LogLevel -- internal modules import Chainweb.Backup -import Chainweb.BlockHeader import Chainweb.BlockHeaderDB (BlockHeaderDb) import Chainweb.ChainId import Chainweb.Chainweb.ChainResources @@ -148,37 +146,21 @@ import Chainweb.HostAddress import Chainweb.Logger import Chainweb.Mempool.InMem.ValidatingConfig import Chainweb.Mempool.Mempool qualified as Mempool -import Chainweb.Mempool.P2pConfig import Chainweb.Miner.Config import Chainweb.OpenAPIValidation qualified as OpenAPIValidation import Chainweb.Pact.RestAPI.Server (PactServerData(..)) -import Chainweb.Pact.Types (PactServiceConfig(..)) import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Parent -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB -import Chainweb.PayloadProvider -import Chainweb.Ranked import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID import Chainweb.Storage.Table.RocksDB -import Chainweb.Sync.WebBlockHeaderStore -import Chainweb.Time import Chainweb.Utils import Chainweb.Utils.RequestLog import Chainweb.Version -import Chainweb.Version.Guards import Chainweb.WebBlockHeaderDB import P2P.Node.Configuration import P2P.Node.PeerDB (PeerDb) import P2P.Peer -import qualified Pact.Core.Gas as Pact -import qualified Chainweb.PayloadProvider.Pact.Genesis as Pact -import qualified Streaming.Prelude as S -import Chainweb.TreeDB -import Data.These -import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- -- Chainweb Resources @@ -366,10 +348,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba initLogger :: logger initLogger = setComponent "init" logger - providerLogger :: HasPayloadProviderType p => p -> logger -> logger - providerLogger p = - addLabel ("provider", toText (_payloadProviderType p)) - logg :: LogFunctionText logg = logFunctionText initLogger @@ -472,21 +450,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba -- logFunctionJson logger Info PactReplaySuccessful -- inner $ Replayed initialCut (Just newCut) else do - initialCut <- _cut mCutDb - - -- synchronize payload providers. this also initializes - -- mining. - synchronizeProviders webchain providers initialCut - - -- FIXME: synchronize all payload providers - -- logg Info "start synchronizing Pact DBs to initial cut" - -- logFunctionJson logger Info InitialSyncInProgress - -- synchronizePactDb pactSyncChains initialCut - -- logg Info "finished synchronizing Pact DBs to initial cut" - - - -- withPactData cs cuts $ \pactData -> do - do logg Debug "start initializing miner resources" logFunctionJson logger Info InitializingMinerResources @@ -524,63 +487,6 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba } } - synchronizeProviders :: WebBlockHeaderDb -> ChainMap ConfiguredPayloadProvider -> Cut -> IO () - synchronizeProviders wbh providers c = do - let startHeaders = HM.unionWith (\startHeader _genesisHeader -> startHeader) - (_cutHeaders c) - (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) - mapConcurrently_ syncOne startHeaders - where - syncOne hdr = forM_ (providers ^? atChain (_chainId hdr)) $ \case - ConfiguredPayloadProvider provider -> do - let loggr = (providerLogger provider (chainLogger hdr)) - logFunctionText loggr Info $ - "sync payload provider to " - <> sshow (view blockHeight hdr) - <> ":" <> sshow (view blockHash hdr) - finfo <- forkInfoForHeader wbh hdr Nothing Nothing - logFunctionText loggr Debug $ "syncToBlock with fork info " <> sshow finfo - r <- syncToBlock provider Nothing finfo `catch` \(e :: SomeException) -> do - logFunctionText loggr Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e - throwM e - when (r /= _forkInfoTargetState finfo) $ do - logFunctionText loggr Info - $ "resolving fork on startup, from " <> brief r - <> " to " <> brief (_forkInfoTargetState finfo) - bhdb <- getWebBlockHeaderDb wbh cid - let ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest r - ppBlock <- lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH) `catch` \case - e@(TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do - logFunctionText loggr Warn $ "PP block is missing: " <> brief ppRBH - throwM e - e -> throwM e - - (forkBlocksDescendingStream S.:> forkPoint) <- - S.toList $ branchDiff_ bhdb ppBlock hdr - let forkBlocksAscending = reverse $ snd $ partitionHereThere forkBlocksDescendingStream - let newTrace = - zipWith - (\prent child -> - ConsensusPayload (view blockPayloadHash child) Nothing <$ - blockHeaderToEvaluationCtx (Parent prent)) - (forkPoint : forkBlocksAscending) - forkBlocksAscending - let newForkInfo = finfo { _forkInfoTrace = newTrace } - r' <- syncToBlock provider Nothing newForkInfo - unless (r' == _forkInfoTargetState finfo) $ do - error $ T.unpack - $ "unexpected result state" - <> "; expected: " <> brief (_forkInfoTargetState finfo) - <> "; actual: " <> brief r - <> "; PP latest block: " <> brief ppBlock - <> "; target block: " <> brief hdr - <> "; fork blocks: " <> brief forkBlocksAscending - DisabledPayloadProvider -> do - logFunctionText logger Info $ - "payload provider disabled, not synced, on chain: " <> toText (_chainId hdr) - where - cid = _chainId hdr - -- synchronizePactDb :: HM.HashMap ChainId (ChainResources logger) -> Cut -> IO () -- synchronizePactDb cs targetCut = do -- mapConcurrently_ syncOne $ diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 12496c8697..f04aa3fac2 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -95,7 +95,7 @@ withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain provide -- initialize cutHashes store let cutHashesStore = cutHashesTable rdb - cutDb <- withCutDb cutDbParams (logFunction logger) headerStore providers cutHashesStore + cutDb <- withCutDb cutDbParams logger headerStore providers cutHashesStore cutP2pNode <- liftIO $ mkP2pNode True "cut" $ C.syncSession myInfo cutDb headerP2pNode <- liftIO $ mkP2pNode False "header" $ diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 5104b33fed..bf80149556 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -94,14 +94,13 @@ import Control.Applicative import Control.Concurrent.Async import Control.Concurrent.STM.TVar import Control.DeepSeq -import Control.Exception +import Control.Exception.Safe import Control.Lens hiding ((:>)) import Control.Monad -import Control.Monad.Catch (throwM) import Control.Monad.IO.Class import Control.Monad.Morph import Control.Monad.STM -import Control.Monad.Trans.Resource +import Control.Monad.Trans.Resource hiding (throwM) import Data.Aeson (ToJSON) import Data.Foldable @@ -160,6 +159,11 @@ import P2P.TaskQueue import Utils.Logging.Trace import Chainweb.Ranked +import Control.Monad.Trans.Maybe +import Chainweb.Logger +import Chainweb.Core.Brief +import Chainweb.Parent +import Control.Exception (asyncExceptionFromException, asyncExceptionToException) -- -------------------------------------------------------------------------- -- -- Cut DB Configuration @@ -392,15 +396,16 @@ cutDbQueueSize = pQueueSize . _cutDbQueue withCutDb :: HasVersion + => Logger logger => CutDbParams - -> LogFunction + -> logger -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes -> ResourceT IO CutDb -withCutDb config logfun headerStore providers cutHashesStore +withCutDb config logger headerStore providers cutHashesStore = snd <$> allocate - (startCutDb config logfun headerStore providers cutHashesStore) + (startCutDb config logger headerStore providers cutHashesStore) stopCutDb -- | Start a CutDB. This loads the initial cut from the database (falling back @@ -413,21 +418,24 @@ withCutDb config logfun headerStore providers cutHashesStore -- read-only version of the payload store. -- startCutDb - :: HasVersion + :: Logger logger + => HasVersion => CutDbParams - -> LogFunction + -> logger -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes -> IO CutDb -startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do +startCutDb config logger headerStore providers cutHashesStore = mask_ $ do logg Debug "obtain initial cut" initialCut <- readInitialCut + recoveryCut <- synchronizeProviders logger wbhdb providers initialCut unless (_cutDbParamsReadOnly config) $ deleteRangeRocksDb (unCasify cutHashesStore) + -- intentionally don't delete up to recovery cut, the initial cut could be useful later (Just $ over _1 succ $ casKey $ cutToCutHashes Nothing initialCut, Nothing) - cutVar <- newTVarIO initialCut + cutVar <- newTVarIO recoveryCut c <- readTVarIO cutVar logg Info $ T.unlines $ "got initial cut:" : [" " <> block | block <- cutToTextShort c] @@ -437,7 +445,7 @@ startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do { _cutDbCut = cutVar , _cutDbQueue = queue , _cutDbAsync = cutAsync - , _cutDbLogFunction = logfun + , _cutDbLogFunction = logFunction logger , _cutDbHeaderStore = headerStore , _cutDbPayloadProviders = providers , _cutDbQueueSize = _cutDbParamsBufferSize config @@ -446,12 +454,12 @@ startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do , _cutDbFastForwardHeightLimit = _cutDbParamsFastForwardHeightLimit config } where - logg = logfun @T.Text + logg = logFunctionText logger wbhdb = _webBlockHeaderStoreCas headerStore processor :: PQueue (Down CutHashes) -> TVar Cut -> IO () - processor queue cutVar = runForever logfun "CutDB" $ - processCuts config logfun headerStore providers cutHashesStore queue cutVar + processor queue cutVar = runForever (logFunction logger) "CutDB" $ + processCuts config (logFunction logger) headerStore providers cutHashesStore queue cutVar readInitialCut :: IO Cut readInitialCut = do @@ -466,6 +474,99 @@ startCutDb config logfun headerStore providers cutHashesStore = mask_ $ do casInsert cutHashesStore (cutToCutHashes Nothing limitedCut) return limitedCutHeaders +synchronizeProviders + :: (Logger logger, HasVersion) + => logger -> WebBlockHeaderDb -> ChainMap ConfiguredPayloadProvider -> Cut -> IO Cut +synchronizeProviders logger wbh providers c = do + let startHeaders = HM.unionWith + (\startHeader _genesisHeader -> startHeader) + (_cutHeaders c) + (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) + syncsSuccessful <- mapConcurrently (runMaybeT . syncOne False) startHeaders + if all isJust syncsSuccessful + then return c + else do + -- try to recover from the fork automatically by removing ~`diameter` + -- blocks from the cut + let recoveryHeight = + _cutMinHeight c - max (_cutMinHeight c) (int (diameter (chainGraphAt maxBound))) + recoveryCut <- limitCut wbh recoveryHeight c + let recoveryHeaders = HM.unionWith + (\recoveryHeader _genesisHeader -> recoveryHeader) + (_cutHeaders recoveryCut) + (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) + mapConcurrently_ (runMaybeT . syncOne True) recoveryHeaders + return recoveryCut + where + syncOne :: Bool -> BlockHeader -> MaybeT IO () + syncOne recovery hdr = case providers ^?! atChain (_chainId hdr) of + ConfiguredPayloadProvider provider -> do + let loggr = logger & providerLogger provider . chainLogger hdr + liftIO $ logFunctionText loggr Info $ + (if recovery then "recover" else "sync") <> " payload provider to " + <> sshow (view blockHeight hdr) + <> ":" <> sshow (view blockHash hdr) + finfo <- liftIO $ forkInfoForHeader wbh hdr Nothing Nothing + liftIO $ logFunctionText loggr Debug $ "syncToBlock with fork info " <> sshow finfo + r <- liftIO (syncToBlock provider Nothing finfo) `catch` \(e :: SomeException) -> do + liftIO $ logFunctionText loggr Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e + empty + if r == _forkInfoTargetState finfo + then return () + else do + liftIO $ logFunctionText loggr Info + $ "resolving fork on startup, from " <> brief r + <> " to " <> brief (_forkInfoTargetState finfo) + bhdb <- liftIO $ getWebBlockHeaderDb wbh cid + let ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest r + ppBlock <- liftIO (lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH)) `catch` \case + e@(TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do + liftIO $ logFunctionText loggr Warn $ "PP block is missing: " <> brief ppRBH <> ", error: " <> sshow e + MaybeT $ return Nothing + _ -> empty + + (forkBlocksDescendingStream S.:> forkPoint) <- liftIO $ + S.toList $ branchDiff_ bhdb ppBlock hdr + let forkBlocksAscending = reverse $ snd $ partitionHereThere forkBlocksDescendingStream + let newTrace = + zipWith + (\prent child -> + ConsensusPayload (view blockPayloadHash child) Nothing <$ + blockHeaderToEvaluationCtx (Parent prent)) + (forkPoint : forkBlocksAscending) + forkBlocksAscending + let newForkInfo = finfo { _forkInfoTrace = newTrace } + -- if this fails, there is no way for the payload provider + -- to sync to the block without using the ordinary cut pipeline. + -- so, we don't care. + r' <- liftIO $ syncToBlock provider Nothing newForkInfo + let syncSucceeded = _forkInfoTargetState finfo == r' + when (not syncSucceeded) $ do + liftIO $ logFunctionText logger (if recovery then Error else Warn) + $ "unexpected " <> (if recovery then "recovery" else "initial sync") <> " result state" + <> "; expected: " <> brief (_forkInfoTargetState finfo) + <> "; actual: " <> brief r + <> "; PP latest block: " <> brief ppBlock + <> "; target block: " <> brief hdr + <> "; fork blocks: " <> brief forkBlocksAscending + <> if recovery + then ". recommend using --initial-block-height-limit to recover from fork manually." + else "" + empty + DisabledPayloadProvider -> do + liftIO $ logFunctionText logger Info $ + "payload provider disabled, not synced, on chain: " <> toText (_chainId hdr) + where + cid = _chainId hdr + + +chainLogger :: Logger logger => HasChainId c => c -> logger -> logger +chainLogger cid = addLabel ("chain", toText (_chainId cid)) + +providerLogger :: Logger logger => HasPayloadProviderType p => p -> logger -> logger +providerLogger p = + addLabel ("provider", toText (_payloadProviderType p)) + readHighestCutHeaders :: HasVersion => LogFunctionText -> WebBlockHeaderDb -> Casify RocksDbTable CutHashes -> IO (HM.HashMap ChainId BlockHeader) diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 40ff69e514..70e78fb7d4 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -118,6 +118,7 @@ cutFetchTimeout = 3_000_000 withTestCutDb :: HasCallStack => HasVersion + => Logger logger => RocksDb -- ^ the chainweb version -> (CutDbParams -> CutDbParams) @@ -133,20 +134,19 @@ withTestCutDb -- service that can be given a transaction generator, that allows to -- create blocks with a well-defined set of test transactions. -- - -> LogFunction - -- ^ a logg function (use @\_ _ -> return ()@ turn of logging) + -> logger -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb) -withTestCutDb rdb conf n providers logfun = do +withTestCutDb rdb conf n providers logger = do rocksDb <- liftIO $ testRocksDb "withTestCutDb" rdb let cutHashesDb = cutHashesTable rocksDb webDb <- liftIO $ initWebBlockHeaderDb rocksDb mgr <- liftIO $ HTTP.newManager HTTP.defaultManagerSettings headerStore <- withLocalWebBlockHeaderStore mgr webDb - cutDb <- withCutDb (conf $ defaultCutDbParams cutFetchTimeout) logfun headerStore providers cutHashesDb + cutDb <- withCutDb (conf $ defaultCutDbParams cutFetchTimeout) logger headerStore providers cutHashesDb liftIO $ synchronizeProviders webDb genesisCut - liftIO $ logfun @Text Debug "GOING TO MINE AT THE START" - liftIO $ foldM_ (\c _ -> view _1 <$> mine logfun cutDb c) genesisCut [1..n] + liftIO $ logFunctionText logger Debug "GOING TO MINE AT THE START" + liftIO $ foldM_ (\c _ -> view _1 <$> mine logger cutDb c) genesisCut [1..n] return (cutHashesDb, cutDb) where synchronizeProviders :: WebBlockHeaderDb -> Cut -> IO () @@ -163,10 +163,10 @@ withTestCutDb rdb conf n providers logfun = do throwM e unless (r == _forkInfoTargetState finfo) $ do error "Chainweb.Test.CutDB.synchronizeProviders: unexpected result state" - logfun Debug $ "payload provider synced, on chain: " <> toText (_chainId hdr) + logFunctionText logger Debug $ "payload provider synced, on chain: " <> toText (_chainId hdr) -- FIXME DisabledPayloadProvider -> do - logfun Debug $ + logFunctionText logger Debug $ "payload provider disabled, not synced, on chain: " <> toText (_chainId hdr) @@ -256,14 +256,14 @@ awaitBlockHeight cdb bh cid = atomically $ do withTestCutDbWithoutPact :: HasCallStack => HasVersion + => Logger logger => RocksDb -- ^ the chainweb version -> (CutDbParams -> CutDbParams) -- ^ any alterations to the CutDB's configuration -> Int -- ^ number of blocks in the chainweb in addition to the genesis blocks - -> LogFunction - -- ^ a logg function (use @\_ _ -> return ()@ turn of logging) + -> logger -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb) withTestCutDbWithoutPact rdb conf n = withTestCutDb rdb conf n (onAllChains DisabledPayloadProvider) @@ -272,33 +272,35 @@ withTestCutDbWithoutPact rdb conf n = -- withTestPayloadResource :: HasVersion + => Logger logger => RocksDb -> Int - -> LogFunction + -> logger -> ResourceT IO CutDb -withTestPayloadResource rdb n logfun +withTestPayloadResource rdb n logger = view _2 . snd <$> allocate start stopTestPayload where - start = startTestPayload rdb logfun n + start = startTestPayload rdb logger n -- -------------------------------------------------------------------------- -- -- Internal Utils for mocking up the backends startTestPayload :: HasVersion + => Logger logger => RocksDb - -> LogFunction + -> logger -> Int -> IO (Async (), CutDb) -startTestPayload rdb logfun n = do +startTestPayload rdb logger n = do rocksDb <- testRocksDb "startTestPayload" rdb let cutHashesDb = cutHashesTable rocksDb webDb <- initWebBlockHeaderDb rocksDb mgr <- HTTP.newManager HTTP.defaultManagerSettings (hserver, hstore) <- startLocalWebBlockHeaderStore mgr webDb let disabledPayloadProviders = onAllChains DisabledPayloadProvider - cutDb <- startCutDb (defaultCutDbParams cutFetchTimeout) logfun hstore disabledPayloadProviders cutHashesDb - foldM_ (\c _ -> view _1 <$> mine logfun cutDb c) genesisCut [0..n] + cutDb <- startCutDb (defaultCutDbParams cutFetchTimeout) logger hstore disabledPayloadProviders cutHashesDb + foldM_ (\c _ -> view _1 <$> mine logger cutDb c) genesisCut [0..n] return (hserver, cutDb) stopTestPayload :: (Async (), CutDb) -> IO () @@ -330,8 +332,8 @@ startLocalWebBlockHeaderStore mgr webDb = do mine :: HasCallStack => HasVersion - => LogFunctionText - -- ^ The miner. For testing you may use 'defaultMiner'. + => Logger logger + => logger -> CutDb -> Cut -> IO (Cut, ChainId, NewPayload) @@ -344,15 +346,15 @@ mine logger cutDb c = do -- - the chainweb is in a consistent state, -- - the pact execution service is synced with the cutdb, and -- - the transaction generator produces valid blocks. - logger Debug "going to mine" + logFunctionText logger Debug "going to mine" cid <- getRandomUnblockedChain c - logger Debug "got unblocked chain" + logFunctionText logger Debug "got unblocked chain" tryMineForChain cutDb c cid >>= \case Left _ -> throwM $ InternalInvariantViolation "Failed to create new cut. This is a bug in Chainweb.Test.CutDB or one of it's users" Right x -> do - logger Debug "awaiting cut with block" + logFunctionText logger Debug "awaiting cut with block" void $ awaitCut cutDb $ ((<=) `on` _cutHeight) (view _1 x) return x @@ -519,7 +521,7 @@ testCutPruning rdb = testCase "cut pruning" $ runResourceT $ withVersion (barebo (cutHashesStore, _) <- withTestCutDb rdb alterPruningSettings (int $ avgCutHeightAt minedBlockHeight) pps - (logFunction testLogger) + testLogger liftIO $ do -- peek inside the cut DB's store to find the oldest and newest cuts let table = unCasify cutHashesStore @@ -544,6 +546,7 @@ testCutGet rdb = testCase "cut get" $ withVersion (barebonesTestVersion pairChai let ch = avgCutHeightAt bh let halfCh = ch `div` 2 tmp <- withTempDir "donotuse" + testLogger <- liftIO getTestLogger pps <- tabulateChainsM $ \cid -> view providerResPayloadProvider <$> withPayloadProviderResources (genericLogger Error (\_ -> return ())) @@ -554,7 +557,7 @@ testCutGet rdb = testCase "cut get" $ withVersion (barebonesTestVersion pairChai False tmp defaultPayloadProviderConfig - (_, cutDb) <- withTestCutDb rdb id (2 * int ch) pps (\_ _ -> return ()) + (_, cutDb) <- withTestCutDb rdb id (2 * int ch) pps testLogger liftIO $ do curHeight <- _cutHeight <$> _cut cutDb assertGe "cut height is large enough" (Actual curHeight) (Expected $ 2 * int ch) diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index c4d3120605..1b270be08c 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -109,7 +109,7 @@ mkFixture genesisPayloadFor pactServiceConfig baseRdb = do let pacts = snd <$> perChain let mempools = fst <$> perChain let providers = ConfiguredPayloadProvider . PactPayloadProvider logger <$> pacts - (_, cutDb) <- withTestCutDb testRdb id 0 providers (logFunction logger) + (_, cutDb) <- withTestCutDb testRdb id 0 providers logger let fixture = Fixture { _fixtureCutDb = cutDb , _fixtureLogger = logger From 6193f276f2a238ec39f16cd97f622b54563691b1 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 25 Jun 2025 12:17:07 -0400 Subject: [PATCH 253/378] remove unused deps Change-Id: Id0000000be12bd14f33e382871851ba9ea2b2233 --- chainweb.cabal | 5 ----- 1 file changed, 5 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index f10f98432c..1767823ddb 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -375,7 +375,6 @@ library , chainweb-storage >= 0.1 , chronos >= 1.1 , clock >= 0.7 - , comonad , configuration-tools >= 0.6 , containers >= 0.5 , crypton >= 0.31 @@ -394,9 +393,7 @@ library , exceptions >= 0.8 , file-embed >= 0.0 , filepath >= 1.4 - , free , ghc-compact >= 0.1 - , growable-vector >= 0.1 , hashable >= 1.4 , hashes >=0.2.2.0 , heaps >= 0.3 @@ -452,10 +449,8 @@ library , tls-session-manager >= 0.0 , token-bucket >= 0.1 , transformers >= 0.5 - , trifecta >= 2.1 , unliftio >= 0.2 , unordered-containers >= 0.2.20 - , uuid >= 1.3.16 , validation , vector >= 0.12.2 , vector-algorithms >= 0.7 From e673635ed5ed5124ba8629e8c39b85b7f3a0ff37 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 20 Jun 2025 11:08:58 -0400 Subject: [PATCH 254/378] poll eth_syncing instead of just FCU --- src/Chainweb/PayloadProvider/EVM.hs | 223 ++++++++++++++++------------ 1 file changed, 125 insertions(+), 98 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 6b1fad7338..62286d0e88 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -92,6 +92,7 @@ import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Trans.Resource hiding (throwM) import Control.Monad.Writer +import Data.Aeson qualified as Aeson import Data.ByteString.Short qualified as BS import Data.List qualified as L import Data.LogMessage @@ -445,6 +446,10 @@ newtype ForkchoiceUpdatedTimeoutException = ForkchoiceUpdatedTimeoutException Mi deriving (Eq, Show, Generic) instance Exception ForkchoiceUpdatedTimeoutException +newtype ForkchoiceSyncFailedException = ForkchoiceSyncFailedException ForkchoiceStateV1 + deriving (Eq, Show, Generic) +instance Exception ForkchoiceSyncFailedException + -- | Thrown on an invalid payload status. -- -- The semantics of the first paramter depends on the context: @@ -659,7 +664,101 @@ forkchoiceUpdate -- ^ the requested fork choice state -> Maybe PayloadAttributesV3 -> IO (Maybe PayloadId) -forkchoiceUpdate p t fcs attr = go t +forkchoiceUpdate p t fcs attr = do + try @_ @(RPC.Error EngineServerErrors EngineErrors) + (RPC.callMethodHttp @Engine_ForkchoiceUpdatedV3 (_evmEngineCtx p) request) + >>= \r -> case r of + Right s -> case _forkchoiceUpdatedV1ResponsePayloadStatus s of + -- Syncing: we need to call eth_syncing to find out when the sync is + -- done, then check that we're where we expect. + -- if we are where we expect, we still need to request a payload ID + -- with a subsequent FCU. + PayloadStatusV1 Syncing Nothing Nothing -> go t + + -- If the status is VALID, the latest hash is what was requested. + -- + -- In particular, if the status is VALID the return latest hash + -- is never an ancestor of the requested hash. + -- + -- If payload creation was requested the payload id is null only + -- if no payload attributes were provided. (If the payload + -- attributes are invalid an error is returned, even thought the + -- forkchoice state *is* updated.) + -- + -- IMPORTANT NOTE: + -- + -- The Engine API specification demands that, if the requested + -- hash was not the latest hash on the (selected) canonical + -- chain this returns the latest hash on the canonical chain. It + -- will not initiate payload creation. This behavior is not + -- acceptable for Chainweb consensus. + -- + -- Therefore will have to patch the EVM client such that the + -- latest hash will be the requested hash and payload production + -- is initiated if it was requested. + -- + PayloadStatusV1 Valid (Just _) Nothing -> do + lf Info "forkchoiceUpdate succeeded with VALID status" + return (_forkchoiceUpdatedV1ResponsePayloadId s) + + -- Validation failed permenently. + -- + -- FIXME: list precise list of possible reasons. + -- + PayloadStatusV1 Invalid (Just h) e -> + throwM $ InvalidPayloadException (Just h) e + + -- If the status is INVALID and no latest valid predecessor + -- can be determined. + -- + -- This means probably means that the block for which + -- the upddate is requested is detached from the canonical + -- history. + -- + -- FIXME: We should probably point that out in the exception + -- and possibly react to this scenario by banning the source + -- of the block. + -- + -- (Note this could possibly also happen for shallow nodes + -- that are not yet fully synced.) + -- + e@(PayloadStatusV1 Invalid Nothing _) -> + throwM $ UnexpectedForkchoiceUpdatedResponseException e + + -- Something else when wrong. + -- + e -> + throwM $ UnexpectedForkchoiceUpdatedResponseException e + + -- According to Engine Spec [8, point 7] + -- {error: {code: -38003, message: "Invalid payload attributes"}} + -- is returned even when forkchoiceState has been updated, but + -- payload attributes were invalid. + -- + -- NOTE: we must MUST address this case and update the state! + -- + Left (RPC.Error (RPC.ApplicationError InvalidPayloadAttributes) msg Nothing) -> do + + -- FIXME: + -- how to we signal this to caller, in particular, + -- consensus? If this goes undetected mining may get stuck. + -- If this is due to a timestamp being the same as the + -- previous block, we'll have to figure out what to do about + -- it. Maybe patching reth? + -- + -- For now we log an Error. + -- + T2 cur _ <- stateIO p + lf Error $ msg <> ": forkchoiceUpdate succeeded but no payload is produced, because of invalid payload attributes. This is most likely due to a non-increasing timestamp" + lf Error $ encodeToText $ object + [ "forkchoiceState" .= fcs + , "payloadAttributes" .= attr + , "previousState" .= cur + , "response" .= r + ] + return Nothing + + Left e -> throwM e where request = ForkchoiceUpdatedV3Request fcs attr lf = loggS p "forkchoiceUpdate" @@ -674,104 +773,32 @@ forkchoiceUpdate p t fcs attr = go t , "forkchoiceState" .= fcs , "payloadAttributes" .= attr ] - r <- try @_ @(RPC.Error EngineServerErrors EngineErrors) $ - RPC.callMethodHttp @Engine_ForkchoiceUpdatedV3 (_evmEngineCtx p) - request - case r of - Right s -> case _forkchoiceUpdatedV1ResponsePayloadStatus s of - - -- Syncing: retry - PayloadStatusV1 Syncing Nothing Nothing -> do - -- wait 500ms - lf Warn $ "EVM is SYNCING. Waiting for " <> sshow waitTime <> " microseconds" - threadDelay $ int waitTime - go (remaining - waitTime) - - -- If the status is VALID, the latest hash is what was requested. - -- - -- In particular, if the status is VALID the return latest hash - -- is never an ancestor of the requested hash. - -- - -- If payload creation was requested the payload id is null only - -- if no payload attributes were provided. (If the payload - -- attributes are invalid an error is returned, even thought the - -- forkchoice state *is* updated.) - -- - -- IMPORTANT NOTE: - -- - -- The Engine API specification demands that, if the requested - -- hash was not the latest hash on the (selected) canonical - -- chain this returns the latest hash on the canonical chain. It - -- will not initiate payload creation. This behavior is not - -- acceptable for Chainweb consensus. - -- - -- Therefore will have to patch the EVM client such that the - -- latest hash will be the requested hash and payload production - -- is initiated if it was requested. - -- - PayloadStatusV1 Valid (Just _) Nothing -> do - lf Info "forkchoiceUpdate succeeded with VALID status" - return (_forkchoiceUpdatedV1ResponsePayloadId s) - - -- Validation failed permenently. - -- - -- FIXME: list precise list of possible reasons. - -- - PayloadStatusV1 Invalid (Just h) e -> - throwM $ InvalidPayloadException (Just h) e - - - -- If the status is INVALID and no latest valid predecessor - -- can be determined. - -- - -- This means probably means that the block for which - -- the upddate is requested is detached from the canonical - -- history. - -- - -- FIXME: We should probably point that out in the exception - -- and possibly react to this scenario by banning the source - -- of the block. - -- - -- (Note this could possibly also happen for shallow nodes - -- that are not yet fully synced.) - -- - e@(PayloadStatusV1 Invalid Nothing _) -> - throwM $ UnexpectedForkchoiceUpdatedResponseException e - - -- Something else when wrong. - -- - e -> - throwM $ UnexpectedForkchoiceUpdatedResponseException e - - -- According to Engine Spec [8, point 7] - -- {error: {code: -38003, message: "Invalid payload attributes"}} - -- is returned even when forkchoiceState has been updated, but - -- payload attributes were invalid. - -- - -- NOTE: we must MUST address this case and update the state! - -- - Left (RPC.Error (RPC.ApplicationError InvalidPayloadAttributes) msg Nothing) -> do - - -- FIXME: - -- how to we signal this to caller, in particular, - -- consensus? If this goes undetected mining may get stuck. - -- If this is due to a timestamp being the same as the - -- previous block, we'll have to figure out what to do about - -- it. Maybe patching reth? - -- - -- For now we log an Error. - -- - T2 cur _ <- stateIO p - lf Error $ msg <> ": forkchoiceUpdate succeeded but no payload is produced, because of invalid payload attributes. This is most likely due to a non-increasing timestamp" - lf Error $ encodeToText $ object - [ "forkchoiceState" .= fcs - , "payloadAttributes" .= attr - , "previousState" .= cur - , "response" .= r - ] - return Nothing - Left e -> throwM e + r <- RPC.callMethodHttp @Eth_Syncing (_evmEngineCtx p) Nothing + case r of + SyncingStatusFalse -> do + -- we're not syncing anymore, but that doesn't guarantee that we're at our target. + -- poll the current consensus state of the EVM. + Just evmLatest <- callMethodHttp @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockLatest, False) + Just evmSafe <- callMethodHttp @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockSafe, False) + Just evmFinalized <- callMethodHttp @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockFinalized, False) + if EVM._hdrHash evmLatest == _forkchoiceHeadBlockHash fcs + && EVM._hdrHash evmSafe == _forkchoiceSafeBlockHash fcs + && EVM._hdrHash evmFinalized == _forkchoiceFinalizedBlockHash fcs + then do + -- we're synced! do another FCU to get the payload ID if needed. + -- make sure not to reset the timeout by accident :) + case attr of + Nothing -> return Nothing + Just _ -> forkchoiceUpdate p remaining fcs attr + else do + -- we're not synced, but the sync is done! sync must + -- have failed, report an error. + throwM $ ForkchoiceSyncFailedException fcs + SyncingStatus {} -> do + lf Warn $ "EVM is SYNCING. Waiting for " <> sshow waitTime <> " microseconds" + threadDelay $ int waitTime + go (remaining - waitTime) -- | Engine NewPayloadV4 -- From 9d81b831b8f8e96dc024f85f0c54b3d47201aa70 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 23 Jun 2025 20:07:45 -0400 Subject: [PATCH 255/378] stop comparing and fetching safe --- src/Chainweb/PayloadProvider/EVM.hs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 62286d0e88..34ed040e1d 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -780,11 +780,7 @@ forkchoiceUpdate p t fcs attr = do -- we're not syncing anymore, but that doesn't guarantee that we're at our target. -- poll the current consensus state of the EVM. Just evmLatest <- callMethodHttp @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockLatest, False) - Just evmSafe <- callMethodHttp @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockSafe, False) - Just evmFinalized <- callMethodHttp @Eth_GetBlockByNumber (_evmEngineCtx p) (DefaultBlockFinalized, False) if EVM._hdrHash evmLatest == _forkchoiceHeadBlockHash fcs - && EVM._hdrHash evmSafe == _forkchoiceSafeBlockHash fcs - && EVM._hdrHash evmFinalized == _forkchoiceFinalizedBlockHash fcs then do -- we're synced! do another FCU to get the payload ID if needed. -- make sure not to reset the timeout by accident :) From 8416a32410c925b6c8ed882bb0e02a82327d3d36 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 24 Jun 2025 13:45:19 -0400 Subject: [PATCH 256/378] make SYNCING log message Info --- src/Chainweb/PayloadProvider/EVM.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 34ed040e1d..680bc8099d 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -792,7 +792,7 @@ forkchoiceUpdate p t fcs attr = do -- have failed, report an error. throwM $ ForkchoiceSyncFailedException fcs SyncingStatus {} -> do - lf Warn $ "EVM is SYNCING. Waiting for " <> sshow waitTime <> " microseconds" + lf Info $ "EVM is SYNCING. Waiting for " <> sshow waitTime <> " microseconds" threadDelay $ int waitTime go (remaining - waitTime) From 1ff088739e3e40f549e22de4f834878f7a6695f0 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 23 Jun 2025 20:40:45 -0400 Subject: [PATCH 257/378] Include actual header in FCS exn --- src/Chainweb/PayloadProvider/EVM.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 680bc8099d..0120a20c99 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -446,7 +446,10 @@ newtype ForkchoiceUpdatedTimeoutException = ForkchoiceUpdatedTimeoutException Mi deriving (Eq, Show, Generic) instance Exception ForkchoiceUpdatedTimeoutException -newtype ForkchoiceSyncFailedException = ForkchoiceSyncFailedException ForkchoiceStateV1 +data ForkchoiceSyncFailedException = ForkchoiceSyncFailedException + { forkchoiceSyncFailedTarget :: ForkchoiceStateV1 + , forkchoiceSyncFailedActual :: EVM.Header + } deriving (Eq, Show, Generic) instance Exception ForkchoiceSyncFailedException @@ -790,7 +793,7 @@ forkchoiceUpdate p t fcs attr = do else do -- we're not synced, but the sync is done! sync must -- have failed, report an error. - throwM $ ForkchoiceSyncFailedException fcs + throwM $ ForkchoiceSyncFailedException fcs evmLatest SyncingStatus {} -> do lf Info $ "EVM is SYNCING. Waiting for " <> sshow waitTime <> " microseconds" threadDelay $ int waitTime From 2089aff1a5856708d97b682963cd1ab416bdda58 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 23 Jun 2025 20:07:27 -0400 Subject: [PATCH 258/378] decrease waitTime Change-Id: Id0000000a9b88a0a982a0416b76bdbc7acc47e11 --- src/Chainweb/PayloadProvider/EVM.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 0120a20c99..964041a6e8 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -765,7 +765,7 @@ forkchoiceUpdate p t fcs attr = do where request = ForkchoiceUpdatedV3Request fcs attr lf = loggS p "forkchoiceUpdate" - waitTime = Micros 500_000 + waitTime = Micros 100_000 go remaining | remaining <= 0 = do lf Warn $ "forkchoiceUpdate timed out while EVM is syncing" From 39e02913acdd94fe02e926420d7bfbebda0d559b Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 25 Jun 2025 17:39:12 -0400 Subject: [PATCH 259/378] Censor jwt secret --- src/Chainweb/RestAPI/Config.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/RestAPI/Config.hs b/src/Chainweb/RestAPI/Config.hs index d6e855b0c9..426a66e797 100644 --- a/src/Chainweb/RestAPI/Config.hs +++ b/src/Chainweb/RestAPI/Config.hs @@ -28,7 +28,7 @@ import Servant -- internal modules import Chainweb.Chainweb.Configuration -import Chainweb.PayloadProvider.EVM (evmConfMinerAddress) +import Chainweb.PayloadProvider.EVM import Chainweb.PayloadProvider.Minimal (mpcRedeemAccount) import Chainweb.PayloadProvider.Minimal.Payload (invalidAccount) import Chainweb.PayloadProvider.Pact.Configuration (pactConfigMiner) @@ -60,6 +60,7 @@ someGetConfigServer config = SomeServer (Proxy @GetConfigApi) $ return $ set (configPayloadProviders . payloadProviderConfigMinimal . mpcRedeemAccount) invalidAccount $ set (configPayloadProviders . payloadProviderConfigPact . traversed . pactConfigMiner) Nothing $ set (configPayloadProviders . payloadProviderConfigEvm . traversed . evmConfMinerAddress) Nothing + $ set (configPayloadProviders . payloadProviderConfigEvm . traversed . evmConfEngineJwtSecret) (_evmConfEngineJwtSecret defaultEvmProviderConfig) -- Service API port $ set (configServiceApi . serviceApiConfigPort) 0 From bf140fa8d14cd8e2250809dd738625cca09cc05a Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 25 Jun 2025 21:31:26 -0700 Subject: [PATCH 260/378] add EVM system contracts to evm-genesis --- cwtools/evm-genesis/Main.hs | 43 +++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index d2bcc7462a..76df35bd4e 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -230,6 +230,9 @@ baseSpecFile netId offset cid genesisTime allocs = object , "gasLimit" .= t "0x1c9c380" , "alloc" .= object ( chainwebChainIdAlloc + : xChanRedeemAlloc + : eip4788Alloc + : eip2935Alloc : allocs ) , "number" .= t "0x0" @@ -242,12 +245,41 @@ baseSpecFile netId offset cid genesisTime allocs = object i :: Natural -> Natural i = id + -- Chainweb System Contracts: Keccak256("/Chainweb/Chain/Id/") + -- chainwebChainIdAlloc = "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ "balance" .= t "0x0" , "code" .= t "0x5f545f526004601cf3" , "storage" .= object [ zero32 .= (printf "0x%064x" cid :: String) ] ] + -- Native X-Chan redeem system contract: Keccack256("/Chainweb/XChan/Redeem/") + -- TODO: cleanup and optimize the code + xChanRedeemAlloc = "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object + [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + , "code" .= t "0x3460f15773ad9923c37370bcbcf00ed194506d8950848956965f9060209160c060e0916101409361016090609a9560e3360360eb575f359760ea6022351c9660606026351c98603635976056359960c060d7351c986004815f80739b02c3e2df42533e0fd166798b5a616f59dbd2cc5afa1560e557510360df5760408593602060018580896080985f869c372086528181601f8601370191013760015afa1560d957510360d357528154809103841160cd575f84819482948284950190555af11560c7575f80f35b600960f5565b600860f5565b600560f5565b600460f5565b600360f5565b600260f5565b600160f5565b5f80fd5b5f5260205ffd" + ] + + -- Official Ethreum System Contracts: + + -- EIP-4788 BeaconRoot Oracle. Included in Cancun fork + eip4788Alloc = "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" .= object + [ "balance" .= t "0x0" + , "code" .= t "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500" + ] + + -- EIP-2935 Historical Block Hashes. Included in the Prague fork + eip2935Alloc = "0x0000F90827F1C53a10cb7A02335B175320002935" .= object + [ "balance" .= t "0x0" + , "code" .= t "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500" + ] + + -- EIP-7002 EL Triggered Withdrawal Requests. Included in Prague fork + -- Not available on Kadena EVM networks + + -- EIP-7251 EL triggered Consolidations. Included in Prague fork + -- Not available on Kadena EVM networks + zero32 :: IsString s => s zero32 = "0x0000000000000000000000000000000000000000000000000000000000000000" @@ -320,11 +352,6 @@ evmDevnetSpecFile offset cid = baseSpecFile 1789 offset cid 0x684c5d2a , "0x3492DA004098d728201fD82657f1207a6E5426bd" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ] - -- Native X-Chan redeem system contract: Keccack256("/Chainweb/XChan/Redeem/") - -- TODO: code - , "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object - [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ] ] -- -------------------------------------------------------------------------- -- @@ -357,12 +384,6 @@ evmTestnetSpecFile cid = baseSpecFile 5920 20 cid 0x684c5d2a -- additional platform funds that are separate from the faucet accounts , "0xeC1B36992C3c7d0f7AbB8EDA43EEbC9A418c0A1e" .= object [ "balance" .= t "0x422ca8b0a00a425000000" ] - - -- Native X-Chan redeem system contract: Keccack256("/Chainweb/XChan/Redeem/") - -- TODO: code - , "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object - [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - ] ] -- -------------------------------------------------------------------------- -- From 47e321804b68fc42415836763224c376900fc724 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 25 Jun 2025 21:31:54 -0700 Subject: [PATCH 261/378] update evm-development EVM genesis info --- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 14 +++++++------- src/Chainweb/Version/EvmDevelopment.hs | 10 +++++----- src/Chainweb/Version/EvmDevelopmentSingleton.hs | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 2e2bd711a6..110d65c635 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -83,23 +83,23 @@ genesisBlocks c = go (_versionCode implicitVersion) (_chainId c) go v | v == _versionCode EvmDevelopmentSingleton = \case ChainId 0 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBZOJ7w1YV19TPPPqu_4esGETJ95T1gtq4RUblQVIm0foFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAA2crMmbXJmDV0kYzNgUxxb81_98dF7x7DpLpiCCncKoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopmentPair = \case ChainId 1 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKavrxhzoUUknAFRgCni8ZYTYWQs-2t3D9YaU8MDe2szoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGdrdw1X0NGnQo7tjWXxmEH4FaZ2kxNUS4wjFHK32jf0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopment = \case ChainId 20 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMMZYU59v3qqr57B_oZNDrWJr44u4_bGEKcPTF-DF4GtoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGh5aF5SSPtKSwZF-XodozKEGk1zR-cTbejDMP79xBpgoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 21 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGmYqAqdbvdkTWumqiLP71cqmqyAVRkooXMCsjcM1nzgoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoHt-qa_7PESYpnnNe3DA1R3dqQyHl--cKixUD_npu2iZoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 22 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoARztiiha2rdixkEAW7d-R3-7QNCRrFyhRprB3tfy1W7oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoP31_evK0ZUY9-eRgBIzXNggLwzmGgDotU7lq-qVwAoSoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 23 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOzaiCzWA-FrPi-xoEn33hMmm5t8bHT7A-bAOGd63TvyoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFgLbJIr0oBjh6uu27NuCBURIohpdDOQvLkUCz0bL2LzoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 24 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoN1tpEmdfWt7M0qaa35WbNa4vdmit6i4I35JxRs8TBsKoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOC5mgvpXJ_mGQ_a7iM8YJNaZKW1zeKOmu7wmhdRdf7eoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmTestnet = \case ChainId 20 -> f diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index 6a0b0f1ad0..ade1007b19 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -90,11 +90,11 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider - , (unsafeChainId 20, unsafeFromText "9tTN4gIlX0Ejv5SQkcDLtLA6Ud_8AQGmy0YWOvEvRuA") - , (unsafeChainId 21, unsafeFromText "yGNn2Y7PMu3necKVlrr0gsET_VDVSPRtWmG11lMpDb4") - , (unsafeChainId 22, unsafeFromText "Rto2JWTsPeaCtLrkC0On_tSqw-5MGCjxbfPrV3hJOeU") - , (unsafeChainId 23, unsafeFromText "qKEsJPiIE5pVeei4luptp8kFFOsGGvFYmTUfQVGd1tw") - , (unsafeChainId 24, unsafeFromText "vQJsV5sOVKzUgTQz9kzPDRwaqxVehqhO847CukxeQ2w") + , (unsafeChainId 20, unsafeFromText "QKbnNzpnlw4UJLGEzbqrQyUoQFU-9eztSgUiD-V0X3k") + , (unsafeChainId 21, unsafeFromText "euSvOK8XmhIUlYvxPgtul2fvvooOHHmljZQkgXGy13M") + , (unsafeChainId 22, unsafeFromText "OTUtmgGTX2vfphUw-dv0_xS1Fucd-iVO_-bvmDq0_cM") + , (unsafeChainId 23, unsafeFromText "Wth4RDfphibr5Y5kIb14zlqZlXwWlX1zJ3kmNTy-E_c") + , (unsafeChainId 24, unsafeFromText "DO__J4SPz20hmeq_bEiHmRUnoIpk1vAE40USFZeFpgo") -- Minimal Payload Provider , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") diff --git a/src/Chainweb/Version/EvmDevelopmentSingleton.hs b/src/Chainweb/Version/EvmDevelopmentSingleton.hs index e958c8a717..4e2fd3f0f7 100644 --- a/src/Chainweb/Version/EvmDevelopmentSingleton.hs +++ b/src/Chainweb/Version/EvmDevelopmentSingleton.hs @@ -77,7 +77,7 @@ evmDevnetPair = withVersion evmDevnetPair $ ChainwebVersion -- Pact Payload Provider [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") -- EVM Payload Provider - , (unsafeChainId 1, unsafeFromText "SjLt6XGe5lo_1AC03lF9k2mQec-caOo-3Gp8OQJZ-j8") + , (unsafeChainId 1, unsafeFromText "MlhkFoanwAk9wn1yl2ol7GlYq6A0pTharRhqEPH4jRg") ] } @@ -122,7 +122,7 @@ evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion [ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 0x684c5d2a))) ] , _genesisBlockPayload = onChains $ -- EVM Payload Provider - [ (unsafeChainId 0, unsafeFromText "_2PRy30tARJ_JjJkgGIKMaFVnxUgkDoQY2ETLQQp_ak") ] + [ (unsafeChainId 0, unsafeFromText "a4zEmOvuUN4yE8dKUmLRE86TrChu9_lnZya4mEWa-Sc") ] } -- still the *default* block gas limit is set, see From a3a5435d6c2aea4f7f153633e13c4752ef2b3a40 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 26 Jun 2025 00:38:47 -0700 Subject: [PATCH 262/378] update EVM xchan redeem contract allocation --- cwtools/evm-genesis/Main.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 76df35bd4e..97b1b3c5b9 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -257,7 +257,7 @@ baseSpecFile netId offset cid genesisTime allocs = object -- TODO: cleanup and optimize the code xChanRedeemAlloc = "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - , "code" .= t "0x3460f15773ad9923c37370bcbcf00ed194506d8950848956965f9060209160c060e0916101409361016090609a9560e3360360eb575f359760ea6022351c9660606026351c98603635976056359960c060d7351c986004815f80739b02c3e2df42533e0fd166798b5a616f59dbd2cc5afa1560e557510360df5760408593602060018580896080985f869c372086528181601f8601370191013760015afa1560d957510360d357528154809103841160cd575f84819482948284950190555af11560c7575f80f35b600960f5565b600860f5565b600560f5565b600460f5565b600360f5565b600260f5565b600160f5565b5f80fd5b5f5260205ffd" + , "code" .= t "0x346101535773ad9923c37370bcbcf00ed194506d8950848956965f9060209160c060e091610140946101609061018091609a9660e3360361014c575f359860203560f01c9360223560ea1c9860263560601c9a603a3599605a359b607a359a5f60db3560c01c9a03610145576004815f80739b02c3e2df42533e0fd166798b5a616f59dbd2cc5afa1561013e5751036101375760408593602060018580896080985f869c372086528181601f8601370191013760015afa1561013057510361012957602083918193720f3df6d732807ef1319fb7b8bb8522d0beac029082525afa1561012257510361011b5781548091038411610114575f84819482948284950190555af11561010d575f80f35b600a610157565b6009610157565b6008610157565b6007610157565b6006610157565b6005610157565b6004610157565b6003610157565b6002610157565b6001610157565b5f80fd5b5f5260205ffd" ] -- Official Ethreum System Contracts: From 5c0d473e347dbc2fc56827117de972ca89aed6f8 Mon Sep 17 00:00:00 2001 From: jmcardon Date: Wed, 11 Jun 2025 15:40:57 -0400 Subject: [PATCH 263/378] update pact --- cabal.project | 2 +- cabal.project.freeze | 2 +- src/Chainweb/Pact5/TransactionExec.hs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cabal.project b/cabal.project index 18e096ff4a..1ecad1eaec 100644 --- a/cabal.project +++ b/cabal.project @@ -101,7 +101,7 @@ source-repository-package source-repository-package type: git location: https://github.com/kadena-io/pact-5.git - tag: 06f5d75996eba48e4ee165a993326606baaea98c + tag: 78c5458fe186338f7b03c2e63f1aa5dc263e863a --sha256: 0wmipbxws45d1axplqx6q4naq0sm3vzh3r354706wiar6a1f556q source-repository-package diff --git a/cabal.project.freeze b/cabal.project.freeze index 92b56ea816..e7f9cad5cf 100644 --- a/cabal.project.freeze +++ b/cabal.project.freeze @@ -231,7 +231,7 @@ constraints: any.Cabal ==3.12.1.0, any.pact-json ==0.1.0.0, any.pact-time ==0.3.0.1, pact-time -with-time, - any.pact-tng ==5.1, + any.pact-tng ==5.2, pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, any.pandoc ==3.6.4, any.pandoc-types ==1.23.1, diff --git a/src/Chainweb/Pact5/TransactionExec.hs b/src/Chainweb/Pact5/TransactionExec.hs index 2d3baf47d1..ef1a2219c3 100644 --- a/src/Chainweb/Pact5/TransactionExec.hs +++ b/src/Chainweb/Pact5/TransactionExec.hs @@ -337,6 +337,7 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do , FlagDisableHistoryInTransactionalMode , FlagEnforceKeyFormats , FlagRequireKeysetNs + , FlagDisablePact52 ] `Set.union` guardDisablePact51Flags txCtx let gasLogsEnabled = maybe GasLogsDisabled (const GasLogsEnabled) maybeGasLogger gasEnv <- mkTableGasEnv (MilliGasLimit $ gasToMilliGas $ gasLimit ^. _GasLimit) gasLogsEnabled From 78ac78593fe0631a159c578285bd1b68fedbc62b Mon Sep 17 00:00:00 2001 From: jmcardon Date: Thu, 12 Jun 2025 17:55:15 -0400 Subject: [PATCH 264/378] add forking flag for 5.3 to chainweb --- cabal.project | 4 +-- src/Chainweb/Pact5/TransactionExec.hs | 38 ++++++++++++++++++++------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/cabal.project b/cabal.project index 1ecad1eaec..07535dd4f9 100644 --- a/cabal.project +++ b/cabal.project @@ -101,8 +101,8 @@ source-repository-package source-repository-package type: git location: https://github.com/kadena-io/pact-5.git - tag: 78c5458fe186338f7b03c2e63f1aa5dc263e863a - --sha256: 0wmipbxws45d1axplqx6q4naq0sm3vzh3r354706wiar6a1f556q + tag: 7e908efd74482cce7721731f8ba43e37fc025ac5 + --sha256: 016k1si0wvibdz4b3kz02sqj72fah8q9g5kfalykl9s04plr3a8w source-repository-package type: git diff --git a/src/Chainweb/Pact5/TransactionExec.hs b/src/Chainweb/Pact5/TransactionExec.hs index ef1a2219c3..96bf99c752 100644 --- a/src/Chainweb/Pact5/TransactionExec.hs +++ b/src/Chainweb/Pact5/TransactionExec.hs @@ -332,13 +332,14 @@ applyCmd -> IO (Either Pact5GasPurchaseFailure (CommandResult [TxLog ByteString] (Pact5.PactError Info))) applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do logDebug_ logger $ "applyCmd: " <> sshow (_cmdHash cmd) - let flags = Set.fromList + let defaultFlags = Set.fromList [ FlagDisableRuntimeRTC , FlagDisableHistoryInTransactionalMode , FlagEnforceKeyFormats , FlagRequireKeysetNs - , FlagDisablePact52 - ] `Set.union` guardDisablePact51Flags txCtx + ] + let flags = Set.unions [defaultFlags, guardDisablePact51Flags txCtx, guardDisablePact52Flags txCtx] + let gasLogsEnabled = maybe GasLogsDisabled (const GasLogsEnabled) maybeGasLogger gasEnv <- mkTableGasEnv (MilliGasLimit $ gasToMilliGas $ gasLimit ^. _GasLimit) gasLogsEnabled let !requestKey = cmdToRequestKey cmd @@ -473,9 +474,11 @@ applyCoinbase logger db reward txCtx = do freeGasEnv <- mkFreeGasEnv GasLogsDisabled let (coinbaseTerm, coinbaseData) = mkCoinbaseTerm mid mks reward + defaultFlags = Set.singleton FlagDisableRuntimeRTC + flags = Set.unions [defaultFlags, guardDisablePact52Flags txCtx] eCoinbaseTxResult <- evalExecTerm Transactional - db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + db noSPVSupport freeGasEnv flags SimpleNamespacePolicy (ctxToPublicData noPublicMeta txCtx) MsgData { mdHash = coinbaseHash @@ -561,12 +564,14 @@ runGenesisPayload logger db spv ctx cmd = do let txEnv = TransactionEnv logger gasEnv -- Todo gas logs freeGasEnv <- mkFreeGasEnv GasLogsDisabled + let defaultFlags = Set.fromList [FlagDisableRuntimeRTC] + flags = Set.union defaultFlags (guardDisablePact52Flags ctx) runExceptT (runReaderT (runTransactionM (runPayload Transactional - (Set.singleton FlagDisableRuntimeRTC) + flags db spv [ CapToken (QualifiedName "GENESIS" (ModuleName "coin" Nothing)) [] @@ -673,8 +678,12 @@ runUpgrade runUpgrade _logger db txContext cmd = case payload ^. pPayload of Exec pm -> do freeGasEnv <- mkFreeGasEnv GasLogsDisabled + let flags = Set.unions + [ Set.singleton FlagDisableRuntimeRTC + , guardDisablePact52Flags txContext + ] evalExec (RawCode (_pcCode (_pmCode pm))) Transactional - db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) + db noSPVSupport freeGasEnv flags -- allow installing to root namespace SimpleNamespacePolicy (ctxToPublicData publicMeta txContext) @@ -752,6 +761,7 @@ buyGas logger origGasEnv db txCtx cmd = do if isChainweb224Pact then mkBuyGasTerm sender supply else mkFundTxTerm mid mks sender supply + flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52Flags txCtx] runExceptT $ do gasPayerCap <- case gasPayerCaps of @@ -764,7 +774,7 @@ buyGas logger origGasEnv db txCtx cmd = do -- It's taking an `Expr Info` instead of `Expr SpanInfo` which is making this code not compile e <- liftIO $ case gasPayerCap of Just cap -> - evalGasPayerCap cap db noSPVSupport gasEnv (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + evalGasPayerCap cap db noSPVSupport gasEnv flags SimpleNamespacePolicy (ctxToPublicData publicMeta txCtx) MsgData -- Note: in the case of gaspayer, buyGas is given extra metadata that comes from @@ -782,7 +792,8 @@ buyGas logger origGasEnv db txCtx cmd = do db noSPVSupport gasEnv - (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + flags + SimpleNamespacePolicy (ctxToPublicData publicMeta txCtx) MsgData -- Note: in the case of gaspayer, buyGas is given extra metadata that comes from @@ -850,12 +861,13 @@ redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd "redeeming gas (post-2.24) for " <> sshow (_cmdHash cmd) -- if we're past chainweb 2.24, we don't use defpacts for gas; see 'pact/coin-contract/coin.pact#redeem-gas' let (redeemGasTerm, redeemGasData) = mkRedeemGasTerm mid mks sender gasTotal gasFee + flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52Flags txCtx] -- todo: gas logs freeGasEnv <- mkFreeGasEnv GasLogsDisabled evalExecTerm Transactional -- TODO: more execution flags? - db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + db noSPVSupport freeGasEnv flags SimpleNamespacePolicy (ctxToPublicData publicMeta txCtx) MsgData { mdData = redeemGasData @@ -876,9 +888,10 @@ redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd "redeeming gas (pre-2.24) for " <> sshow (_cmdHash cmd) -- before chainweb 2.24, we use defpacts for gas; see: 'pact/coin-contract/coin.pact#fund-tx' let redeemGasData = PObject $ Map.singleton "fee" (PDecimal $ _pact5GasSupply gasFee) + flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52Flags txCtx] evalContinuation Transactional - db noSPVSupport freeGasEnv (Set.fromList [FlagDisableRuntimeRTC]) SimpleNamespacePolicy + db noSPVSupport freeGasEnv flags SimpleNamespacePolicy (ctxToPublicData publicMeta txCtx) MsgData { mdData = redeemGasData @@ -990,3 +1003,8 @@ guardDisablePact51Flags :: TxContext -> Set ExecutionFlag guardDisablePact51Flags txCtx | guardCtx chainweb228Pact txCtx = Set.empty | otherwise = Set.singleton FlagDisablePact51 + +guardDisablePact52Flags :: TxContext -> Set ExecutionFlag +guardDisablePact52Flags txCtx + | guardCtx chainweb230Pact txCtx = Set.empty + | otherwise = Set.singleton FlagDisablePact52 From 07dcafd14f0ecc97e482aba3de41e013fd01ccd2 Mon Sep 17 00:00:00 2001 From: jmcardon Date: Tue, 24 Jun 2025 14:57:00 -0400 Subject: [PATCH 265/378] add tests for pre/post fork behavior to RemotePactTests --- .../Genesis/Development0Payload.hs | 6 +- .../Genesis/Development1to19Payload.hs | 6 +- .../Genesis/Pact5InstantTimedCPM0Payload.hs | 6 +- .../Pact5InstantTimedCPM1to9Payload.hs | 6 +- .../QuirkedGasPact5InstantTimedCPM0Payload.hs | 6 +- ...irkedGasPact5InstantTimedCPM1to9Payload.hs | 6 +- src/Chainweb/Pact5/TransactionExec.hs | 2 +- test/lib/Chainweb/Test/TestVersions.hs | 29 ++++++ .../Chainweb/Test/Pact4/PactMultiChainTest.hs | 1 + .../Chainweb/Test/Pact5/RemotePactTest.hs | 97 +++++++++++++++++++ 10 files changed, 146 insertions(+), 19 deletions(-) diff --git a/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs index 78cc1a8492..509d4d187e 100644 --- a/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "S7PsDlRGDjPYLdeM3eeEZdvuYtdAthe3_LIVVwlDOaU" + expectedHash = unsafeFromText "GB8YsdVgd50IaJ_shYGN3pnzOohPi-gE3YaP2pvhw0w" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs index d00700cff7..10c6b8fbc4 100644 --- a/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "yHOBLd5WogOZQno-TBaG3gFePMVDvY1RPFuAib4SyWY" + expectedHash = unsafeFromText "tL8Um8Zql3uKtYX5yo9-YoW_cKGf_BFBONzVD5JDd5k" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs index 812911c6e1..73223246a0 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "S7PsDlRGDjPYLdeM3eeEZdvuYtdAthe3_LIVVwlDOaU" + expectedHash = unsafeFromText "GB8YsdVgd50IaJ_shYGN3pnzOohPi-gE3YaP2pvhw0w" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs index d2ab4f4ffb..731b94558e 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "yHOBLd5WogOZQno-TBaG3gFePMVDvY1RPFuAib4SyWY" + expectedHash = unsafeFromText "tL8Um8Zql3uKtYX5yo9-YoW_cKGf_BFBONzVD5JDd5k" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs index acf2763895..ee608e1470 100644 --- a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "svZ-z0prqOd5zB6JgkFc-owzjgXrpicuewSsMbkcjkg" + expectedHash = unsafeFromText "xkWm_gAq2hok8YgyecdTx7tkwQ7rzu7O6bEeSuDk1ac" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjEsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs index 91f9a7a569..b15f37daf4 100644 --- a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "yHOBLd5WogOZQno-TBaG3gFePMVDvY1RPFuAib4SyWY" + expectedHash = unsafeFromText "tL8Um8Zql3uKtYX5yo9-YoW_cKGf_BFBONzVD5JDd5k" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/Pact5/TransactionExec.hs b/src/Chainweb/Pact5/TransactionExec.hs index 96bf99c752..9c5a1fa801 100644 --- a/src/Chainweb/Pact5/TransactionExec.hs +++ b/src/Chainweb/Pact5/TransactionExec.hs @@ -1007,4 +1007,4 @@ guardDisablePact51Flags txCtx guardDisablePact52Flags :: TxContext -> Set ExecutionFlag guardDisablePact52Flags txCtx | guardCtx chainweb230Pact txCtx = Set.empty - | otherwise = Set.singleton FlagDisablePact52 + | otherwise = Set.fromList [FlagDisablePact52, FlagDisableReentrancyCheck] diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index aae3c22335..ec2a5f8e2f 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -20,6 +20,7 @@ module Chainweb.Test.TestVersions , pact5CheckpointerTestVersion , pact5SlowCpmTestVersion , instantCpmTransitionTestVersion + , pact53TransitionCpmTestVersion ) where import Control.Lens hiding (elements) @@ -140,6 +141,9 @@ testVersions = _versionName <$> concat , [ instantCpmTransitionTestVersion (knownChainGraph g) | g :: KnownGraph <- [minBound..maxBound] ] + , [ pact53TransitionCpmTestVersion (knownChainGraph g) + | g :: KnownGraph <- [minBound..maxBound] + ] ] -- | Details common to all test versions thus far. @@ -478,6 +482,31 @@ pact5InstantCpmTestVersion g = buildTestVersion $ \v -> v ) ) +pact53TransitionCpmTestVersion :: ChainGraph -> ChainwebVersion +pact53TransitionCpmTestVersion g = buildTestVersion $ \v -> v + & cpmTestVersion g + & versionName .~ ChainwebVersionName ("pact53-transition-CPM-" <> toText g) + & versionForks .~ tabulateHashMap (\case + -- SPV Bridge is not in effect for Pact 5 yet. + Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 5) + _ -> AllChains ForkAtGenesis + ) + & versionQuirks .~ noQuirks + & versionGenesis .~ VersionGenesis + { _genesisBlockPayload = onChains $ + (unsafeChainId 0, PIN0.payloadBlock) : + [(n, PINN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + , _genesisBlockTarget = AllChains maxTarget + , _genesisTime = AllChains $ BlockCreationTime epoch + } + & versionUpgrades .~ AllChains mempty + & versionVerifierPluginNames .~ AllChains + (Bottom + ( minBound + , Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] + ) + ) + -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled -- at genesis. We also have an upgrade after genesis that redeploys Coin v5 as -- a Pact 5 module. diff --git a/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs b/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs index b0dd4ff8f1..b5eff22709 100644 --- a/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs +++ b/test/unit/Chainweb/Test/Pact4/PactMultiChainTest.hs @@ -1512,6 +1512,7 @@ pact4coin3UpgradeTest = do return $ buildBasic $ mkCont (mkContMsg pid 1) + -- ========================================================= -- -- Fixture diff --git a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs index 0cdebf9524..39aebe2e0c 100644 --- a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs @@ -152,6 +152,7 @@ tests rdb = withResource' (evaluate httpManager >> evaluate cert) $ \_ -> , testCaseSteps "transition crosschain" (transitionCrosschain rdb) , testCaseSteps "upgradeNamespaceTests" (upgradeNamespaceTests rdb) , testCaseSteps "invalidSigCapNameTest" (invalidSigCapNameTest rdb) + , testCaseSteps "pact53TransitionTest" (pact53TransitionTest rdb) , localTests rdb ] @@ -560,6 +561,102 @@ caplistTest baseRdb step = runResourceT $ do , P.fun _crMetaData ? P.match (_Just . A._Object . at "blockHash") ? P.match _Just P.succeed ] +pact53TransitionTest :: RocksDb -> Step -> IO () +pact53TransitionTest baseRdb step = runResourceT $ do + let v = pact53TransitionCpmTestVersion petersenChainGraph + fx <- mkFixture v baseRdb + + let cid = unsafeChainId 0 + assertTxFailure tx msg = poll fx v cid [cmdToRequestKey tx] + >>= P.alignExact ? List.singleton ? P.match _Just ? + P.checkAll + [ P.fun _crResult ? P.match _PactResultErr ? P.fun _peMsg ? P.equals msg + ] + assertTxSuccess tx resultVal = poll fx v cid [cmdToRequestKey tx] + >>= P.alignExact ? List.singleton ? P.match _Just ? + P.checkAll + [ P.fun _crResult ? P.match _PactResultOk ? P.equals resultVal + ] + + liftIO $ do + txErr0 <- buildTextCmd v + $ set cbRPC errorTx + $ defaultCmd cid + txPure0 <- buildTextCmd v + $ set cbRPC pureTx + $ defaultCmd cid + txReadOnlyGuardSetup <- buildTextCmd v + $ set cbRPC readOnlyGuardSetupTx + $ defaultCmd cid + txReadOnly0 <- buildTextCmd v + $ set cbRPC readOnlyGuardTx + $ defaultCmd cid + + step "sending" + send fx v cid [txErr0, txPure0, txReadOnlyGuardSetup] + + step "advancing chains" + + advanceAllChains_ fx + + step "sending read only guard tx" + + send fx v cid [txReadOnly0] + + advanceAllChains_ fx + + step "polling" + + assertTxFailure txErr0 "Cannot find module: error" + assertTxFailure txPure0 "Cannot find module: pure" + assertTxSuccess txReadOnlyGuardSetup (PString "Write succeeded") + assertTxFailure txReadOnly0 "Error during database operation: Operation is not allowed in read-only or system-only mode." + + step "advancing past the fork" + + replicateM_ 5 (advanceAllChains_ fx) + + txErr1 <- buildTextCmd v + $ set cbRPC errorTx + $ defaultCmd cid + txPure1 <- buildTextCmd v + $ set cbRPC pureTx + $ defaultCmd cid + txReadOnly1 <- buildTextCmd v + $ set cbRPC readOnlyGuardTx + $ defaultCmd cid + + step "Sending post fork txs" + + send fx v cid [txErr1, txPure1, txReadOnly1] + + step "advancing chains again" + + advanceAllChains_ fx + + step "polling post-fork results" + + -- Note: correct output from calling (error "test error") + assertTxFailure txErr1 "test error" + + assertTxSuccess txPure1 (PInteger 1) + + assertTxSuccess txReadOnly1 (PBool True) + where + errorTx = mkExec' "(error \"test error\")" + pureTx = mkExec' "(pure 1)" + readOnlyGuardSetupTx = mkExec' $ T.concat + [ "(namespace 'free)" + , "(module test-read-only g (defcap g () true)" + , " (defschema my-schema key:integer)" + , " (deftable my-table:{my-schema})" + , " (defun read-only-fn () (read my-table 'key))" + , " (defun mk-ug () (create-user-guard (read-only-fn)))" + , ")" + , "(create-table my-table)" + , "(insert my-table 'key {'key:1})" + ] + readOnlyGuardTx = mkExec' "(enforce-guard (free.test-read-only.mk-ug))" allocation01KeyPair :: (Text, Text) allocation01KeyPair = From 5b1172569c0bb087c8a368b7070e718c7d67461a Mon Sep 17 00:00:00 2001 From: chessai Date: Tue, 24 Jun 2025 15:12:15 -0700 Subject: [PATCH 266/378] add SPVBridge -> AllChains ForkNever to pact53 transition test version --- test/lib/Chainweb/Test/TestVersions.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index ec2a5f8e2f..bcf0456f92 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -488,6 +488,7 @@ pact53TransitionCpmTestVersion g = buildTestVersion $ \v -> v & versionName .~ ChainwebVersionName ("pact53-transition-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case -- SPV Bridge is not in effect for Pact 5 yet. + SPVBridge -> AllChains ForkNever Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 5) _ -> AllChains ForkAtGenesis ) From bcd90baf39748bf6ce41d242c65884cfec05f2f0 Mon Sep 17 00:00:00 2001 From: chessai Date: Tue, 24 Jun 2025 20:07:06 -0700 Subject: [PATCH 267/378] make pact53 transition chainweb version use pact4 genesis headers --- .../Genesis/Pact5InstantTimedCPM0Payload.hs | 6 +-- .../Pact5InstantTimedCPM1to9Payload.hs | 6 +-- test/lib/Chainweb/Test/TestVersions.hs | 52 ++++++++++++++++++- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs index 73223246a0..812911c6e1 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "GB8YsdVgd50IaJ_shYGN3pnzOohPi-gE3YaP2pvhw0w" + expectedHash = unsafeFromText "S7PsDlRGDjPYLdeM3eeEZdvuYtdAthe3_LIVVwlDOaU" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs index 731b94558e..d2ab4f4ffb 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "tL8Um8Zql3uKtYX5yo9-YoW_cKGf_BFBONzVD5JDd5k" + expectedHash = unsafeFromText "yHOBLd5WogOZQno-TBaG3gFePMVDvY1RPFuAib4SyWY" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index bcf0456f92..995905970e 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -464,6 +464,9 @@ pact5InstantCpmTestVersion g = buildTestVersion $ \v -> v & versionForks .~ tabulateHashMap (\case -- SPV Bridge is not in effect for Pact 5 yet. SPVBridge -> AllChains ForkNever + + Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 100) + _ -> AllChains ForkAtGenesis ) & versionQuirks .~ noQuirks @@ -487,12 +490,44 @@ pact53TransitionCpmTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("pact53-transition-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case + -- pact 5 is off until here + Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 + + -- SPV Bridge is not in effect for Pact 5 yet. + SPVBridge -> AllChains ForkNever + + Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 100) + + _ -> AllChains ForkAtGenesis + ) + & versionQuirks .~ noQuirks + & versionGenesis .~ VersionGenesis + { _genesisBlockPayload = onChains $ + (unsafeChainId 0, IN0.payloadBlock) : + [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + , _genesisBlockTarget = AllChains maxTarget + , _genesisTime = AllChains $ BlockCreationTime epoch + } + & versionUpgrades .~ AllChains mempty + & versionVerifierPluginNames .~ AllChains + (Bottom + ( minBound + , Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] + ) + ) + +{- + & cpmTestVersion g + & versionName .~ ChainwebVersionName ("pact53-transition-CPM-" <> toText g) + & versionForks .~ tabulateHashMap (\case + Pact5Fork -> onChains [ (cid, ForkAtBlockHeight (succ $ genesisBlockHeight v cid)) | cid <- HS.toList $ graphChainIds g ] -- SPV Bridge is not in effect for Pact 5 yet. SPVBridge -> AllChains ForkNever Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 5) _ -> AllChains ForkAtGenesis ) & versionQuirks .~ noQuirks + {- & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ (unsafeChainId 0, PIN0.payloadBlock) : @@ -500,6 +535,14 @@ pact53TransitionCpmTestVersion g = buildTestVersion $ \v -> v , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } + -} + & versionGenesis .~ VersionGenesis + { _genesisBlockPayload = onChains $ + (unsafeChainId 0, IN0.payloadBlock) : + [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + , _genesisBlockTarget = AllChains maxTarget + , _genesisTime = AllChains $ BlockCreationTime epoch + } & versionUpgrades .~ AllChains mempty & versionVerifierPluginNames .~ AllChains (Bottom @@ -507,6 +550,7 @@ pact53TransitionCpmTestVersion g = buildTestVersion $ \v -> v , Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] ) ) +-} -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled -- at genesis. We also have an upgrade after genesis that redeploys Coin v5 as @@ -546,8 +590,14 @@ instantCpmTransitionTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("instant-CPM-transition-" <> toText g) & versionForks .~ tabulateHashMap (\case - -- pact 5 is off + -- pact 5 is off until here Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 + + -- SPV Bridge is not in effect for Pact 5 yet. + SPVBridge -> AllChains ForkNever + + --Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 100) + _ -> AllChains ForkAtGenesis ) & versionQuirks .~ noQuirks From 41eea40dd4a6c9ac80597d831a27a3fb69484285 Mon Sep 17 00:00:00 2001 From: chessai Date: Tue, 24 Jun 2025 20:07:30 -0700 Subject: [PATCH 268/378] add advanceToForkHeight --- test/unit/Chainweb/Test/Pact5/CutFixture.hs | 48 ++++++++++++++++++- .../Chainweb/Test/Pact5/RemotePactTest.hs | 10 ++-- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/test/unit/Chainweb/Test/Pact5/CutFixture.hs b/test/unit/Chainweb/Test/Pact5/CutFixture.hs index c1f972b64a..4ee40eaafa 100644 --- a/test/unit/Chainweb/Test/Pact5/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact5/CutFixture.hs @@ -17,6 +17,7 @@ , RecordWildCards , ScopedTypeVariables , TemplateHaskell + , TupleSections , TypeApplications , ViewPatterns #-} @@ -37,15 +38,16 @@ module Chainweb.Test.Pact5.CutFixture , fixturePactQueues , advanceAllChains , advanceAllChains_ + , advanceToForkHeight , withTestCutDb ) where -import Chainweb.Storage.Table (Casify) import Chainweb.BlockCreationTime (BlockCreationTime(..)) import Chainweb.BlockHash (BlockHash) import Chainweb.BlockHeader hiding (blockCreationTime, blockNonce) import Chainweb.BlockHeader.Internal (blockCreationTime, blockNonce) +import Chainweb.BlockHeight (BlockHeight) import Chainweb.ChainId import Chainweb.ChainValue (ChainValue(..), ChainValueCasLookup, chainLookupM) import Chainweb.Cut @@ -61,6 +63,7 @@ import Chainweb.Pact.Types import Chainweb.Pact4.Transaction qualified as Pact4 import Chainweb.Payload import Chainweb.Payload.PayloadStore +import Chainweb.Storage.Table (Casify) import Chainweb.Storage.Table.RocksDB import Chainweb.Sync.WebBlockHeaderStore import Chainweb.Test.Pact5.Utils @@ -83,6 +86,7 @@ import Data.ByteString.Short qualified as SBS import Data.Function import Data.HashMap.Strict qualified as HashMap import Data.HashSet qualified as HashSet +import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as Text import Data.Vector (Vector) @@ -99,6 +103,48 @@ data Fixture = Fixture } makeLenses ''Fixture +-- Advance to the forkheight of a fork. +-- Throws an error if the fork is not found in the ChainwebVersion '_versionForks', or if +-- the fork height is ever 'ForkNever' on any chain. +advanceToForkHeight :: (HasFixture fx) => fx -> Fork -> IO () +advanceToForkHeight fx fork = do + Fixture{..} <- cutFixture fx + let v = _chainwebVersion _fixtureCutDb + latestCut <- liftIO $ _fixtureCutDb ^. cut + let blockHeights = fmap (view blockHeight) $ latestCut ^. cutMap + let latestBlockHeight = maximum blockHeights + + let targetHeight = 1 + getMaxForkHeight v fork latestBlockHeight + when (targetHeight > latestBlockHeight) $ do + -- advance all chains to the target height + replicateM_ (int (targetHeight - latestBlockHeight)) $ advanceAllChains_ fx + + -- check if we reached the target height + latestCut' <- _fixtureCutDb ^. cut + let newHeights = fmap (view blockHeight) $ latestCut' ^. cutMap + when (maximum newHeights < targetHeight) $ + throwM $ InternalInvariantViolation $ "advanceToForkHeight: did not reach target height " <> sshow targetHeight + + where + -- get the max fork height for a given fork in the ChainwebVersion. + -- this will let us safely advance to where the unlocked feature(s) are available + -- on all chains. + getMaxForkHeight :: ChainwebVersion -> Fork -> BlockHeight -> BlockHeight + getMaxForkHeight v frk latestBlockHeight = + fromMaybe (error "getMaxForkHeight: no forks") $ + maximumOf (versionForks . ix frk . to expand . folded . to (uncurry forkHeightAt)) v + where + expand :: ChainMap a -> [(ChainId, a)] + expand = \case + AllChains fh -> (,fh) <$> HashSet.toList (chainIdsAt v latestBlockHeight) + OnChains m -> m ^@.. ifolded + + forkHeightAt :: ChainId -> ForkHeight -> BlockHeight + forkHeightAt cid = \case + ForkAtBlockHeight bh -> bh + ForkAtGenesis -> genesisHeight v cid + ForkNever -> error "ForkNever encountered" + class HasFixture a where cutFixture :: a -> IO Fixture instance HasFixture Fixture where diff --git a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs index 39aebe2e0c..060db0d314 100644 --- a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs @@ -1,3 +1,5 @@ +{-# options_ghc -fno-warn-unused-local-binds -fno-warn-unused-matches #-} + {-# language ConstraintKinds , DataKinds @@ -114,7 +116,7 @@ import Chainweb.Pact.Types import Chainweb.RestAPI.Utils (someServerApplication) import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Pact5.CmdBuilder -import Chainweb.Test.Pact5.CutFixture (advanceAllChains, advanceAllChains_) +import Chainweb.Test.Pact5.CutFixture (advanceAllChains, advanceAllChains_, advanceToForkHeight) import Chainweb.Test.Pact5.CutFixture qualified as CutFixture import Chainweb.Test.Pact5.Utils import Chainweb.Test.TestVersions @@ -567,12 +569,12 @@ pact53TransitionTest baseRdb step = runResourceT $ do fx <- mkFixture v baseRdb let cid = unsafeChainId 0 - assertTxFailure tx msg = poll fx v cid [cmdToRequestKey tx] + let assertTxFailure tx msg = poll fx v cid [cmdToRequestKey tx] >>= P.alignExact ? List.singleton ? P.match _Just ? P.checkAll [ P.fun _crResult ? P.match _PactResultErr ? P.fun _peMsg ? P.equals msg ] - assertTxSuccess tx resultVal = poll fx v cid [cmdToRequestKey tx] + let assertTxSuccess tx resultVal = poll fx v cid [cmdToRequestKey tx] >>= P.alignExact ? List.singleton ? P.match _Just ? P.checkAll [ P.fun _crResult ? P.match _PactResultOk ? P.equals resultVal @@ -614,7 +616,7 @@ pact53TransitionTest baseRdb step = runResourceT $ do step "advancing past the fork" - replicateM_ 5 (advanceAllChains_ fx) + advanceToForkHeight fx Chainweb230Pact txErr1 <- buildTextCmd v $ set cbRPC errorTx From d62e35c808e6cd2ff5a93b49371c7cc755703340 Mon Sep 17 00:00:00 2001 From: chessai Date: Tue, 24 Jun 2025 20:08:12 -0700 Subject: [PATCH 269/378] remove redundant comments in TestVersions.hs --- test/lib/Chainweb/Test/TestVersions.hs | 36 -------------------------- 1 file changed, 36 deletions(-) diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index 995905970e..8736e8e952 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -516,42 +516,6 @@ pact53TransitionCpmTestVersion g = buildTestVersion $ \v -> v ) ) -{- - & cpmTestVersion g - & versionName .~ ChainwebVersionName ("pact53-transition-CPM-" <> toText g) - & versionForks .~ tabulateHashMap (\case - Pact5Fork -> onChains [ (cid, ForkAtBlockHeight (succ $ genesisBlockHeight v cid)) | cid <- HS.toList $ graphChainIds g ] - -- SPV Bridge is not in effect for Pact 5 yet. - SPVBridge -> AllChains ForkNever - Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 5) - _ -> AllChains ForkAtGenesis - ) - & versionQuirks .~ noQuirks - {- - & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, PIN0.payloadBlock) : - [(n, PINN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch - } - -} - & versionGenesis .~ VersionGenesis - { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] - , _genesisBlockTarget = AllChains maxTarget - , _genesisTime = AllChains $ BlockCreationTime epoch - } - & versionUpgrades .~ AllChains mempty - & versionVerifierPluginNames .~ AllChains - (Bottom - ( minBound - , Set.fromList $ map VerifierName ["allow", "hyperlane_v3_announcement", "hyperlane_v3_message"] - ) - ) --} - -- | CPM version (see `cpmTestVersion`) with forks and upgrades instantly enabled -- at genesis. We also have an upgrade after genesis that redeploys Coin v5 as -- a Pact 5 module. From 68e6d51674cce8947c96bc34928cd3fd1ed06e99 Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 25 Jun 2025 10:24:29 -0700 Subject: [PATCH 270/378] update ea for pact53 transition test version --- chainweb.cabal | 2 + cwtools/ea/Ea.hs | 2 + cwtools/ea/Ea/Genesis.hs | 19 +++++++++ .../Pact53TransitionTimedCPM0Payload.hs | 39 +++++++++++++++++++ .../Pact53TransitionTimedCPM1to9Payload.hs | 39 +++++++++++++++++++ .../Genesis/Pact5InstantTimedCPM0Payload.hs | 6 +-- .../Pact5InstantTimedCPM1to9Payload.hs | 6 +-- test/lib/Chainweb/Test/TestVersions.hs | 15 +++---- 8 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM0Payload.hs create mode 100644 src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM1to9Payload.hs diff --git a/chainweb.cabal b/chainweb.cabal index 10da248eb6..5400db7abf 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -143,6 +143,8 @@ library , Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload , Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM0Payload , Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM1to9Payload + , Chainweb.BlockHeader.Genesis.Pact53TransitionTimedCPM0Payload + , Chainweb.BlockHeader.Genesis.Pact53TransitionTimedCPM1to9Payload , Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM0Payload , Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM1to9Payload , Chainweb.BlockHeader.Genesis.Testnet040Payload diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index 5104def571..a00b2fe738 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -81,6 +81,7 @@ main = do , fastnet , instantnet , pact5Instantnet + , pact53Transitionnet , quirkedPact5Instantnet , testnet04 , mainnet @@ -104,6 +105,7 @@ main = do fastnet = mkPayloads [fastTimedCPM0, fastTimedCPMN] instantnet = mkPayloads [instantCPM0, instantCPMN] pact5Instantnet = mkPayloads [pact5InstantCPM0, pact5InstantCPMN] + pact53Transitionnet = mkPayloads [pact53TransitionCPM0, pact53TransitionCPMN] quirkedPact5Instantnet = mkPayloads [quirkedPact5InstantCPM0, quirkedPact5InstantCPMN] testnet04 = mkPayloads [testnet040, testnet04N] mainnet = mkPayloads diff --git a/cwtools/ea/Ea/Genesis.hs b/cwtools/ea/Ea/Genesis.hs index f1160ae447..cccc4a9faa 100644 --- a/cwtools/ea/Ea/Genesis.hs +++ b/cwtools/ea/Ea/Genesis.hs @@ -26,6 +26,8 @@ module Ea.Genesis , instantCPMN , pact5InstantCPM0 , pact5InstantCPMN +, pact53TransitionCPM0 +, pact53TransitionCPMN , quirkedPact5InstantCPM0 , quirkedPact5InstantCPMN @@ -269,6 +271,23 @@ pact5InstantCPMN = pact5InstantCPM0 & txChainIds .~ mkChainIdRange 1 9 & coinbase ?~ fastNGrants +pact53TransitionCPM0 :: Genesis +pact53TransitionCPM0 = Genesis + { _version = pact53TransitionCpmTestVersion petersenChainGraph + , _tag = "Pact53TransitionTimedCPM" + , _txChainIds = onlyChainId 0 + , _coinbase = Just fast0Grants + , _keysets = Just fastKeysets + , _allocations = Just fastAllocations + , _namespaces = Just devNs2 + , _coinContract = [fungibleAssetV1, fungibleXChainV1, fungibleAssetV2, installCoinContractV6, gasPayer] + } + +pact53TransitionCPMN :: Genesis +pact53TransitionCPMN = pact53TransitionCPM0 + & txChainIds .~ mkChainIdRange 1 9 + & coinbase ?~ fastNGrants + quirkedPact5InstantCPM0 :: Genesis quirkedPact5InstantCPM0 = Genesis { _version = quirkedGasPact5InstantCpmTestVersion petersenChainGraph diff --git a/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM0Payload.hs new file mode 100644 index 0000000000..ce8c967ea3 --- /dev/null +++ b/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM0Payload.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.BlockHeader.Genesis.Pact53TransitionTimedCPM0Payload ( payloadBlock ) where + +import qualified Data.Text as T +import qualified Data.Vector as V +import GHC.Stack + +import Chainweb.Payload +import Chainweb.Utils (unsafeFromText, toText) + +payloadBlock :: HasCallStack => PayloadWithOutputs +payloadBlock + | actualHash == expectedHash = payload + | otherwise = error + $ "inconsistent genesis payload detected. THIS IS A BUG in chainweb-node" + <> ". Expected: " <> T.unpack (toText expectedHash) + <> ", actual: " <> T.unpack (toText actualHash) + where + actualHash, expectedHash :: BlockPayloadHash + actualHash = _payloadWithOutputsPayloadHash payload + expectedHash = unsafeFromText "S7PsDlRGDjPYLdeM3eeEZdvuYtdAthe3_LIVVwlDOaU" + + payload = newPayloadWithOutputs minerData coinbase txs + minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" + coinbase = unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6Ik5PX0NPSU5CQVNFIn0sInJlcUtleSI6IkRsZFJ3Q2JsUTdMb3F5NndZSm5hb2RIbDMwZDNqM2VILXF0RnpmRXY0NmciLCJsb2dzIjpudWxsLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjpudWxsfQ" + txs = V.fromList + [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") + , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") + , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") + , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") + , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") + ] diff --git a/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM1to9Payload.hs new file mode 100644 index 0000000000..cbe7800a24 --- /dev/null +++ b/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM1to9Payload.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.BlockHeader.Genesis.Pact53TransitionTimedCPM1to9Payload ( payloadBlock ) where + +import qualified Data.Text as T +import qualified Data.Vector as V +import GHC.Stack + +import Chainweb.Payload +import Chainweb.Utils (unsafeFromText, toText) + +payloadBlock :: HasCallStack => PayloadWithOutputs +payloadBlock + | actualHash == expectedHash = payload + | otherwise = error + $ "inconsistent genesis payload detected. THIS IS A BUG in chainweb-node" + <> ". Expected: " <> T.unpack (toText expectedHash) + <> ", actual: " <> T.unpack (toText actualHash) + where + actualHash, expectedHash :: BlockPayloadHash + actualHash = _payloadWithOutputsPayloadHash payload + expectedHash = unsafeFromText "yHOBLd5WogOZQno-TBaG3gFePMVDvY1RPFuAib4SyWY" + + payload = newPayloadWithOutputs minerData coinbase txs + minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" + coinbase = unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6Ik5PX0NPSU5CQVNFIn0sInJlcUtleSI6IkRsZFJ3Q2JsUTdMb3F5NndZSm5hb2RIbDMwZDNqM2VILXF0RnpmRXY0NmciLCJsb2dzIjpudWxsLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjpudWxsfQ" + txs = V.fromList + [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") + , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") + , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") + , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") + , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") + ] diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs index 812911c6e1..73223246a0 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "S7PsDlRGDjPYLdeM3eeEZdvuYtdAthe3_LIVVwlDOaU" + expectedHash = unsafeFromText "GB8YsdVgd50IaJ_shYGN3pnzOohPi-gE3YaP2pvhw0w" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiY2tiMmZnNWVJeXRoS3U5YVN3TkhIWllsWVY2V1R1NGlYaUZDamc5WFFLWSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVxcblxcbjsgZW5vdWdoIHRvIGNvdmVyIHRoZSBnYXMgY29zdHMgZm9yIGFsbG9jYXRpb24gcmVsZWFzZVxcbihjb2luLmNvaW5iYXNlIFxcXCJhbGxvY2F0aW9uMDBcXFwiIChrZXlzZXQtcmVmLWd1YXJkIFxcXCJhbGxvY2F0aW9uMDBcXFwiKSAxMDAwMDAuMClcXG4oY29pbi5jb2luYmFzZSBcXFwiYWxsb2NhdGlvbjAxXFxcIiAoa2V5c2V0LXJlZi1ndWFyZCBcXFwiYWxsb2NhdGlvbjAxXFxcIikgMTAwMDAwLjApXFxuKGNvaW4uY29pbmJhc2UgXFxcImFsbG9jYXRpb24wMlxcXCIgKGtleXNldC1yZWYtZ3VhcmQgXFxcImFsbG9jYXRpb24wMlxcXCIpIDEwMDAwMC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50czBcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJja2IyZmc1ZUl5dGhLdTlhU3dOSEhaWWxZVjZXVHU0aVhpRkNqZzlYUUtZIiwibG9ncyI6ImFCUXM2aDJGVUN1a00tM0pjQmdLZGFvRU1Ta3p2OTJkM0VmT2t5MXNrMmciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJhbGxvY2F0aW9uMDAiLDEwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiYWxsb2NhdGlvbjAxIiwxMDAwMDBdLCJuYW1lIjoiVFJBTlNGRVIiLCJtb2R1bGUiOnsibmFtZXNwYWNlIjpudWxsLCJuYW1lIjoiY29pbiJ9LCJtb2R1bGVIYXNoIjoiM2lJQlFkSm5zdDQ0WjJaZ1hvSFBrQWF1eWJKMGg4NWxfZW5fU0dITmliRSJ9LHsicGFyYW1zIjpbIiIsImFsbG9jYXRpb24wMiIsMTAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs index d2ab4f4ffb..731b94558e 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs @@ -21,7 +21,7 @@ payloadBlock where actualHash, expectedHash :: BlockPayloadHash actualHash = _payloadWithOutputsPayloadHash payload - expectedHash = unsafeFromText "yHOBLd5WogOZQno-TBaG3gFePMVDvY1RPFuAib4SyWY" + expectedHash = unsafeFromText "tL8Um8Zql3uKtYX5yo9-YoW_cKGf_BFBONzVD5JDd5k" payload = newPayloadWithOutputs minerData coinbase txs minerData = unsafeFromText "eyJhY2NvdW50IjoiTm9NaW5lciIsInByZWRpY2F0ZSI6IjwiLCJwdWJsaWMta2V5cyI6W119" @@ -30,9 +30,9 @@ payloadBlock [ (unsafeFromText "eyJoYXNoIjoiNDhUMExqQW5TRnBGV3h2dmFQVi1fNkUtQ2pEQVBoV1lVRldidnlmMmxGcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjFcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcbiAgIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICApXFxuICAgICBAZG9jIFxcXCIgVHJhbnNmZXIgQU1PVU5UIGJldHdlZW4gYWNjb3VudHMgU0VOREVSIGFuZCBSRUNFSVZFUi4gXFxcXFxcbiAgICAgICAgICBcXFxcIEZhaWxzIGlmIFNFTkRFUiBkb2VzIG5vdCBleGlzdC4gSWYgUkVDRUlWRVIgZXhpc3RzLCBndWFyZCBcXFxcXFxuICAgICAgICAgIFxcXFwgbXVzdCBtYXRjaCBleGlzdGluZyB2YWx1ZS4gSWYgUkVDRUlWRVIgZG9lcyBub3QgZXhpc3QsIFxcXFxcXG4gICAgICAgICAgXFxcXCBSRUNFSVZFUiBhY2NvdW50IGlzIGNyZWF0ZWQgdXNpbmcgUkVDRUlWRVItR1VBUkQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBTdWJqZWN0IHRvIG1hbmFnZW1lbnQgYnkgVFJBTlNGRVIgY2FwYWJpbGl0eS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIDItc3RlcCBwYWN0IHRvIHRyYW5zZmVyIEFNT1VOVCBmcm9tIFNFTkRFUiBvbiBjdXJyZW50IGNoYWluIFxcXFxcXG4gICAgICAgICAgXFxcXCB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4gdmlhIFNQViBwcm9vZi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFRBUkdFVC1DSEFJTiBtdXN0IGJlIGRpZmZlcmVudCB0aGFuIGN1cnJlbnQgY2hhaW4gaWQuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGaXJzdCBzdGVwIGRlYml0cyBBTU9VTlQgY29pbnMgaW4gU0VOREVSIGFjY291bnQgYW5kIHlpZWxkcyBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIsIFJFQ0VJVkVSX0dVQVJEIGFuZCBBTU9VTlQgdG8gVEFSR0VULUNIQUlOLiBcXFxcXFxuICAgICAgICAgIFxcXFwgU2Vjb25kIHN0ZXAgY29udGludWF0aW9uIGlzIHNlbnQgaW50byBUQVJHRVQtQ0hBSU4gd2l0aCBwcm9vZiBcXFxcXFxuICAgICAgICAgIFxcXFwgb2J0YWluZWQgZnJvbSB0aGUgc3B2ICdvdXRwdXQnIGVuZHBvaW50IG9mIENoYWlud2ViLiBcXFxcXFxuICAgICAgICAgIFxcXFwgUHJvb2YgaXMgdmFsaWRhdGVkIGFuZCBSRUNFSVZFUiBpcyBjcmVkaXRlZCB3aXRoIEFNT1VOVCBcXFxcXFxuICAgICAgICAgIFxcXFwgY3JlYXRpbmcgYWNjb3VudCB3aXRoIFJFQ0VJVkVSX0dVQVJEIGFzIG5lY2Vzc2FyeS5cXFwiXFxuICAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gdGFyZ2V0LWNoYWluIFxcXCJcXFwiKSlcXG4gICAgICAgICAgICBdXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWxcXG4gICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAgXFxcIiBHZXQgYmFsYW5jZSBmb3IgQUNDT1VOVC4gRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2FjY291bnQtZGV0YWlsc31cXG4gICAgICggYWNjb3VudDogc3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGFuIG9iamVjdCB3aXRoIGRldGFpbHMgb2YgQUNDT1VOVC4gXFxcXFxcbiAgICAgXFxcXCBGYWlscyBpZiBhY2NvdW50IGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgICgpXFxuICAgICBcXFwiUmV0dXJuIHRoZSBtYXhpbXVtIGFsbG93ZWQgZGVjaW1hbCBwcmVjaXNpb24uXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbFxcbiAgICAgKCBhbW91bnQ6ZGVjaW1hbCApXFxuICAgICBcXFwiIEVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgdHJhbnNhY3Rpb25zLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nXFxuICAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgIGd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIENyZWF0ZSBBQ0NPVU5UIHdpdGggMC4wIGJhbGFuY2UsIHdpdGggR1VBUkQgY29udHJvbGxpbmcgYWNjZXNzLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gcm90YXRlOnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBuZXctZ3VhcmQ6Z3VhcmRcXG4gICAgIClcXG4gICAgIFxcXCIgUm90YXRlIGd1YXJkIGZvciBBQ0NPVU5ULiBUcmFuc2FjdGlvbiBpcyB2YWxpZGF0ZWQgYWdhaW5zdCBcXFxcXFxuICAgICBcXFxcIGV4aXN0aW5nIGd1YXJkIGJlZm9yZSBpbnN0YWxsaW5nIG5ldyBndWFyZC4gXFxcIlxcbiAgICAgKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMtMDFcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjEsIGhhc2ggTm1kUnRzekN3YWNqSEd5eUU5Tl9mbFFNUlJCaGNIVkZQNE9qaGlQZWoxRSJ9LCJyZXFLZXkiOiI0OFQwTGpBblNGcEZXeHZ2YVBWLV82RS1DakRBUGhXWVVGV2J2eWYybEZzIiwibG9ncyI6IlB3eDlycG9kS2prSGNPUlh2WFVRSUpjRVZRZDhPSXRNM0FlakNFRExMVXMiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjowfQ") , (unsafeFromText "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxLCBoYXNoIGg4OWljM2doejIzUmprMHBITkt0Rk93cnZTczdtYm11cWxvVlhPaGwyR0kifSwicmVxS2V5IjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsImxvZ3MiOiJSUDhnMUJrZnkyQjN1bHE0WWU4amF1VlRZcGpqRXlaLWtydGJ5SVg2STRnIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6MX0") , (unsafeFromText "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZnVuZ2libGUtdjIsIGhhc2ggWXhFLUtnQzZKbUttQ2NnS1RKbWxFbl9JNThJNEVYbWhmcnZ6WDdiZXJZayJ9LCJyZXFLZXkiOiIwNUJ0ajdlQlpCVzdvLVNhTG9WaEFpY01VUFpVQmJHNlFUOF9MQWtDeEhzIiwibG9ncyI6IjFTQmJBNHVzS0E5OFNlX09WRUdkcVk5WjJoSW1jVmcydWpISDJkM1ZmeEkiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjoyfQ") - , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkZqTER6TktqYWktR080enNlc0tOenZ5WGNFenlkWmN4U3pGdDQ5UFhsS2ciLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") + , (unsafeFromText "eyJoYXNoIjoiR1JJUXU0aEFLazBJRDlGOHpfLTB5WHlBS0ozM01QeVRZTVhPamxQSm1wSSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cXG4oY3JlYXRlLXRhYmxlIGNvaW4tdGFibGUpXFxuKGNyZWF0ZS10YWJsZSBhbGxvY2F0aW9uLXRhYmxlKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12Ni1pbnN0YWxsXCJ9In0", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IlRhYmxlQ3JlYXRlZCJ9LCJyZXFLZXkiOiJHUklRdTRoQUtrMElEOUY4el8tMHlYeUFLSjMzTVB5VFlNWE9qbFBKbXBJIiwibG9ncyI6IkxKWlA1V2ZFN1VfNGZINWltQm56dE94ZndyWWN0enQtdmNoSkRvUXN2T1EiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjozfQ") , (unsafeFromText "eyJoYXNoIjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZ2FzLXBheWVyLXYxXFxuXFxuICAoZGVmY2FwIEdBU19QQVlFUjpib29sXFxuICAgICggdXNlcjpzdHJpbmdcXG4gICAgICBsaW1pdDppbnRlZ2VyXFxuICAgICAgcHJpY2U6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgY2FwYWJpbGl0eSBpbmRpY2F0aW5nIHRoYXQgZGVjbGFyaW5nIG1vZHVsZSBzdXBwb3J0cyBcXFxcXFxuICAgIFxcXFwgZ2FzIHBheW1lbnQgZm9yIFVTRVIgZm9yIGdhcyBMSU1JVCBhbmQgUFJJQ0UuIEZ1bmN0aW9uYWxpdHkgXFxcXFxcbiAgICBcXFxcIHNob3VsZCByZXF1aXJlIGNhcGFiaWxpdHkgKGNvaW4uRlVORF9UWCksIGFuZCBzaG91bGQgdmFsaWRhdGUgXFxcXFxcbiAgICBcXFxcIHRoZSBzcGVuZCBvZiAobGltaXQgKiBwcmljZSksIHBvc3NpYmx5IHVwZGF0aW5nIHNvbWUgZGF0YWJhc2UgXFxcXFxcbiAgICBcXFxcIGVudHJ5LiBcXFxcXFxuICAgIFxcXFwgU2hvdWxkIGNvbXBvc2UgY2FwYWJpbGl0eSByZXF1aXJlZCBmb3IgJ2NyZWF0ZS1nYXMtcGF5ZXItZ3VhcmQnLlxcXCJcXG4gICAgQG1vZGVsXFxuICAgIFsgKHByb3BlcnR5ICh1c2VyICE9IFxcXCJcXFwiKSlcXG4gICAgICAocHJvcGVydHkgKGxpbWl0ID4gMCkpXFxuICAgICAgKHByb3BlcnR5IChwcmljZSA-IDAuMCkpXFxuICAgIF1cXG4gIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtZ2FzLXBheWVyLWd1YXJkOmd1YXJkICgpXFxuICAgIEBkb2NcXG4gICAgXFxcIiBQcm92aWRlIGEgZ3VhcmQgc3VpdGFibGUgZm9yIGNvbnRyb2xsaW5nIGEgY29pbiBhY2NvdW50IHRoYXQgY2FuIFxcXFxcXG4gICAgXFxcXCBwYXkgZ2FzIHZpYSBHQVNfUEFZRVIgbWVjaGFuaWNzLiBHZW5lcmFsbHkgdGhpcyBpcyBhY2NvbXBsaXNoZWQgXFxcXFxcbiAgICBcXFxcIGJ5IGhhdmluZyBHQVNfUEFZRVIgY29tcG9zZSBhbiB1bnBhcmFtZXRlcml6ZWQsIHVubWFuYWdlZCBjYXBhYmlsaXR5IFxcXFxcXG4gICAgXFxcXCB0aGF0IGlzIHJlcXVpcmVkIGluIHRoaXMgZ3VhcmQuIFRodXMsIGlmIGNvaW4gY29udHJhY3QgaXMgYWJsZSB0byBcXFxcXFxuICAgIFxcXFwgc3VjY2Vzc2Z1bGx5IGFjcXVpcmUgR0FTX1BBWUVSLCB0aGUgY29tcG9zZWQgJ2Fub255bW91cycgY2FwIHJlcXVpcmVkIFxcXFxcXG4gICAgXFxcXCBoZXJlIHdpbGwgYmUgaW4gc2NvcGUsIGFuZCBnYXMgYnV5IHdpbGwgc3VjY2VlZC5cXFwiXFxuICApXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZ2VuZXNpcy0wMVwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IkxvYWRlZCBpbnRlcmZhY2UgZ2FzLXBheWVyLXYxLCBoYXNoIGlJV0FQMW9lbl9rcFhqRmJuUU04N0FKUlpJZ3RzZlpjQXdZckV5MjFSV1EifSwicmVxS2V5IjoiU0IzVzVFTGl6azl4elNWWk9MX3dsem5VNjh5aUhPQzlwWUhreHBVXzBnbyIsImxvZ3MiOiJ3TXdXV3hkSFVuU1VOWXJ1RXlISEtTTzUyeGtkVXU0TXBmbE1FcHYyT05FIiwibWV0YURhdGEiOm51bGwsImNvbnRpbnVhdGlvbiI6bnVsbCwidHhJZCI6NH0") - , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoiUUhPVmJXQ09OZTcydDJXZ28xN0w4Z1JpRk55alJndFdnZFZNRWNXX0ZiayIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") + , (unsafeFromText "eyJoYXNoIjoiQ1pteWxXZmllUk1CTXU1dzJFT21wRndLLXQ3YmNPVHM0bUsyYVQzM3VSNCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wibnMtYWRtaW4ta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLW9wZXJhdGUta2V5c2V0XCI6W1wiMzY4ODIwZjgwYzMyNGJiYzdjMmIwNjEwNjg4YTdkYTQzZTM5ZjkxZDExODczMjY3MWNkOWM3NTAwZmY0M2NjYVwiXSxcIm5zLWdlbmVzaXMta2V5c2V0XCI6e1wicHJlZFwiOlwiPVwiLFwia2V5c1wiOltdfX0sXCJjb2RlXCI6XCIoZGVmaW5lLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0IChyZWFkLWtleXNldCAnbnMtYWRtaW4ta2V5c2V0KSlcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1nZW5lc2lzLWtleXNldCkpXFxuXFxuKG1vZHVsZSBucyBHT1ZFUk5BTkNFXFxuICBcXFwiQWRtaW5pc3RlcnMgZGVmaW5pdGlvbiBvZiBuZXcgbmFtZXNwYWNlcyBpbiBDaGFpbndlYi5cXFwiXFxuXFxuICAoZGVmc2NoZW1hIHJlZy1lbnRyeVxcbiAgICBhZG1pbi1ndWFyZDpndWFyZFxcbiAgICBhY3RpdmU6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSByZWdpc3RyeTp7cmVnLWVudHJ5fSlcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZS1rZXlzZXQgJ25zLWFkbWluLWtleXNldCkpXFxuXFxuICAoZGVmY2FwIE9QRVJBVEUgKClcXG4gICAgKGVuZm9yY2Uta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuICAoZGVmY29uc3QgR1VBUkRfU1VDQ0VTUyAoY3JlYXRlLXVzZXItZ3VhcmQgKHN1Y2Nlc3MpKSlcXG4gIChkZWZjb25zdCBHVUFSRF9GQUlMVVJFIChjcmVhdGUtdXNlci1ndWFyZCAoZmFpbHVyZSkpKVxcblxcbiAgKGRlZnVuIHN1Y2Nlc3MgKClcXG4gICAgdHJ1ZSlcXG4gIChkZWZ1biBmYWlsdXJlICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJEaXNhYmxlZFxcXCIpKVxcblxcbiAgKGRlZnVuIHZhbGlkYXRlLW5hbWUgKG5hbWUpXFxuICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiBuYW1lKSBcXFwiRW1wdHkgbmFtZSBub3QgYWxsb3dlZFxcXCIpXFxuICAgIChlbmZvcmNlICg8IChsZW5ndGggbmFtZSkgNjQpIFxcXCJOYW1lIG11c3QgYmUgbGVzcyB0aGFuIDY0IGNoYXJhY3RlcnMgbG9uZ1xcXCIpXFxuICAgIChlbmZvcmNlIChpcy1jaGFyc2V0IENIQVJTRVRfTEFUSU4xIG5hbWUpXFxuICAgICAgICAgICAgIFxcXCJOYW1lIG11c3QgYmUgaW4gbGF0aW4xIGNoYXJzZXRcXFwiKSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtcHJpbmNpcGFsLW5hbWVzcGFjZTpzdHJpbmdcXG4gICAgICAoIGc6Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBGb3JtYXQgcHJpbmNpcGFsIG5hbWVzcGFjZSBhcyBQYWN0IGhhc2ggKEJMQUtFMmIyNTYpIG9mIHByaW5jaXBhbCBcXFxcXFxuICAgIFxcXFwgaW4gaGV4IHRydW5jYXRlZCB0byAxNjAgYml0cyAoNDAgY2hhcmFjdGVycyksIHByZXBlbmRlZCB3aXRoICduXycuXFxcXFxcbiAgICBcXFxcIE9ubHkgdzogYW5kIGs6IGFjY291bnQgcHJvdG9jb2xzIGFyZSBzdXBwb3J0ZWQuIFxcXCJcXG5cXG4gICAgKGxldFxcbiAgICAgICgodHkgKHR5cGVvZi1wcmluY2lwYWwgKGNyZWF0ZS1wcmluY2lwYWwgZykpKSlcXG5cXG4gICAgICA7OyBvbmx5IHc6IGFuZCBrOiBjdXJyZW50bHkgc3VwcG9ydGVkXFxuICAgICAgKGlmIChvciAoPSB0eSBcXFwiazpcXFwiKSAoPSB0eSBcXFwidzpcXFwiKSlcXG4gICAgICAgICgrIFxcXCJuX1xcXCIgKHRha2UgNDAgKGludC10by1zdHIgMTYgKHN0ci10by1pbnQgNjQgKGhhc2ggZykpKSkpXFxuICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJVbnN1cHBvcnRlZCBndWFyZCBwcm90b2NvbDoge31cXFwiIFt0eV0pKVxcbiAgICAgICAgKSlcXG4gIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZTpib29sXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgbnMtYWRtaW46Z3VhcmRcXG4gICAgICAgIClcXG4gICAgXFxcIiBNYW5hZ2VzIG5hbWVzcGFjZSBpbnN0YWxsIGZvciBDaGFpbndlYi4gXFxcXFxcbiAgICBcXFxcIEFsbG93cyBwcmluY2lwYWwgbmFtZXNwYWNlcy4gXFxcXFxcbiAgICBcXFxcIE5vbi1wcmluY2lwYWwgbmFtZXNwYWNlcyByZXF1aXJlIGFjdGl2ZSByb3cgaW4gcmVnaXN0cnkgXFxcXFxcbiAgICBcXFxcIGZvciBOUy1OQU1FIHdpdGggZ3VhcmQgbWF0Y2hpbmcgTlMtQURNSU4uXFxcIlxcblxcbiAgICAoaWYgKD0gKGNyZWF0ZS1wcmluY2lwYWwtbmFtZXNwYWNlIG5zLWFkbWluKSBucy1uYW1lKVxcblxcbiAgICAgIHRydWUgOzsgYWxsb3cgcHJpbmNpcGFsIG5hbWVzcGFjZXNcXG5cXG4gICAgICAod2l0aC1kZWZhdWx0LXJlYWQgcmVnaXN0cnkgbnMtbmFtZSAgICAgICA7OyBvdGhlcndpc2UgZW5mb3JjZSByZWdpc3RyeVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOiBucy1hZG1pblxcbiAgICAgICAgLCAnYWN0aXZlIDogZmFsc2UgfVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQgOj0gYWdcXG4gICAgICAgICwgJ2FjdGl2ZSA6PSBpcy1hY3RpdmUgfVxcblxcbiAgICAgICAgKGVuZm9yY2UgaXMtYWN0aXZlIFxcXCJJbmFjdGl2ZSBvciB1bnJlZ2lzdGVyZWQgbmFtZXNwYWNlXFxcIilcXG4gICAgICAgIChlbmZvcmNlICg9IG5zLWFkbWluIGFnKSBcXFwiQWRtaW4gZ3VhcmQgbXVzdCBtYXRjaCBndWFyZCBpbiByZWdpc3RyeVxcXCIpXFxuXFxuICAgICAgICB0cnVlKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gd3JpdGUtcmVnaXN0cnk6c3RyaW5nXFxuICAgICAgKCBucy1uYW1lOnN0cmluZ1xcbiAgICAgICAgZ3VhcmQ6Z3VhcmRcXG4gICAgICAgIGFjdGl2ZTpib29sXFxuICAgICAgICApXFxuICAgIFxcXCIgV3JpdGUgZW50cnkgd2l0aCBHVUFSRCBhbmQgQUNUSVZFIGludG8gcmVnaXN0cnkgZm9yIE5BTUUuIFxcXFxcXG4gICAgXFxcXCBHdWFyZGVkIGJ5IG9wZXJhdGUga2V5c2V0LiBcXFwiXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKE9QRVJBVEUpXFxuXFxuICAgICAgKHZhbGlkYXRlLW5hbWUgbnMtbmFtZSlcXG5cXG4gICAgICAod3JpdGUgcmVnaXN0cnkgbnMtbmFtZVxcbiAgICAgICAgeyAnYWRtaW4tZ3VhcmQ6IGd1YXJkXFxuICAgICAgICAsICdhY3RpdmU6IGFjdGl2ZSB9KVxcblxcbiAgICAgIFxcXCJSZWdpc3RlciBlbnRyeSB3cml0dGVuXFxcIikpXFxuXFxuICAoZGVmdW4gcXVlcnk6b2JqZWN0e3JlZy1lbnRyeX1cXG4gICAgICAoIG5zLW5hbWU6c3RyaW5nIClcXG4gICAgKHJlYWQgcmVnaXN0cnkgbnMtbmFtZSkpXFxuXFxuKVxcblxcbihjcmVhdGUtdGFibGUgcmVnaXN0cnkpXFxuXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJrYWRlbmFcXFwiXFxuICAoa2V5c2V0LXJlZi1ndWFyZCAnbnMtb3BlcmF0ZS1rZXlzZXQpIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJ1c2VyXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuKHdyaXRlLXJlZ2lzdHJ5IFxcXCJmcmVlXFxcIiBHVUFSRF9GQUlMVVJFIHRydWUpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcImthZGVuYVxcXCJcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldClcXG4gIChrZXlzZXQtcmVmLWd1YXJkICducy1vcGVyYXRlLWtleXNldCkpXFxuXFxuKGRlZmluZS1uYW1lc3BhY2UgXFxcInVzZXJcXFwiIEdVQVJEX1NVQ0NFU1MgR1VBUkRfRkFJTFVSRSlcXG4oZGVmaW5lLW5hbWVzcGFjZSBcXFwiZnJlZVxcXCIgR1VBUkRfU1VDQ0VTUyBHVUFSRF9GQUlMVVJFKVxcbjs7cm90YXRlIHRvIHJlYWwgb3BlcmF0ZSBrZXlzZXRcXG4oZGVmaW5lLWtleXNldCAnbnMtb3BlcmF0ZS1rZXlzZXQgKHJlYWQta2V5c2V0ICducy1vcGVyYXRlLWtleXNldCkpXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJsb2FkLW5zLWRldm5ldC1zZW5kZXIwMFwifSJ9", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IkNabXlsV2ZpZVJNQk11NXcyRU9tcEZ3Sy10N2JjT1RzNG1LMmFUMzN1UjQiLCJsb2dzIjoidDdzQzg1Y1hlWFdSdG90M1lEb2pSejZibWtncEN6enJQQ0Z6QThzTkg3OCIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjV9") , (unsafeFromText "eyJoYXNoIjoiWE5BNmxPSjFXLTdYWXY1WWI0QXFsNzRveFlJN09KcWpPUVQ1b1k0OHEtZyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wiYWxsb2NhdGlvbi10ZXN0MDFcIjpbXCI3MDExYzM3OTE0MGZmODk5ZjdlNjEzYWMwYTk3ODRhN2MwYTUxNWQzMGZlMGFiMmIwYzA3ZTVhYTE3NjM1ZTNlXCJdLFwiYWxsb2NhdGlvbjAyXCI6W1wiZTllNGU3MWJkMDYzZGNmN2UwNmJkNWIxYTE2Njg4ODk3ZDE1Y2E4YmQyZTUwOWM0NTNjNjE2MjE5YzE4NmNjNVwiXSxcImFsbG9jYXRpb24wMFwiOltcImQ4MmQwZGNkZTk4MjU1MDVkODZhZmI2ZGNjMTA0MTFkNmI2N2E0MjlhNzllMjFiZGE0YmIxMTliZjI4YWI4NzFcIl0sXCJhbGxvY2F0aW9uLXRlc3QwMlwiOltcIjA2NTQ0ZTIyYmZlZjIzMGQ2ZDIyZjk0ODZhYzZjYjc2YmYyNThlYmZiZjAxMzdlZTU4ZjY1NzQzZTFhNWI4YzRcIl0sXCJhbGxvY2F0aW9uMDFcIjpbXCJiNGM4YTNlYTkxZDMxNDZiMDU2MDk5NDc0MGYwZTNlZWQ5MWM1OWQyZWVjYTFkYzk5ZjBjMjg3Mjg0NWMyOTRkXCJdfSxcImNvZGVcIjpcIihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDBcXFwiIChyZWFkLWtleXNldCBcXFwiYWxsb2NhdGlvbjAwXFxcIikpXFxuKGRlZmluZS1rZXlzZXQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uMDFcXFwiKSlcXG4oZGVmaW5lLWtleXNldCBcXFwiYWxsb2NhdGlvbjAyXFxcIiAocmVhZC1rZXlzZXQgXFxcImFsbG9jYXRpb24wMlxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMVxcXCIpKVxcbihkZWZpbmUta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIpKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWtleXNldHNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IktleXNldCB3cml0ZSBzdWNjZXNzIn0sInJlcUtleSI6IlhOQTZsT0oxVy03WFl2NVliNEFxbDc0b3hZSTdPSnFqT1FUNW9ZNDhxLWciLCJsb2dzIjoiYXJRSWpQVGRrcXhoQTNjWl9WaVE3OXM3Uk5XRTlGV2Rha0xWdXl4Nlc0YyIsIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjZ9") , (unsafeFromText "eyJoYXNoIjoiNWtxZ0tzWWtwbjZCcS1KUnhNYnA1VG9jUWhRWGRJVVNiM1NGQm1Ba3VWcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMFxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMTVUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMFxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMVxcXCIgKHRpbWUgXFxcIjIxMDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMVxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24wMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24wMlxcXCIgMTAwMDAwMC4wKVxcbihjb2luLmNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnQgXFxcImFsbG9jYXRpb24tdGVzdDAxXFxcIiAodGltZSBcXFwiMTkwMC0xMC0zMVQxODowMDowMFpcXFwiKSBcXFwiYWxsb2NhdGlvbi10ZXN0MDFcXFwiIDEwMDAwMDAuMClcXG4oY29pbi5jcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50IFxcXCJhbGxvY2F0aW9uLXRlc3QwMlxcXCIgKHRpbWUgXFxcIjE5MDAtMTAtMzFUMTg6MDA6MDBaXFxcIikgXFxcImFsbG9jYXRpb24tdGVzdDAyXFxcIiAyMDAwMDAwLjApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtYWxsb2NhdGlvbnNcIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiI1a3FnS3NZa3BuNkJxLUpSeE1icDVUb2NRaFFYZElVU2IzU0ZCbUFrdVZzIiwibG9ncyI6IkhpX3ZfVEhFT2s2aEJFRU0yWmV2Vzh3eVJsQWpUZzNjVUQydXBlVkx0b1UiLCJtZXRhRGF0YSI6bnVsbCwiY29udGludWF0aW9uIjpudWxsLCJ0eElkIjo3fQ") , (unsafeFromText "eyJoYXNoIjoiZjIxVTFJRnlRN0ZfLTRlTnR6QTFuWnZjQ0g5cnY5ZnZsNWM1a0RyNXFsdyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6e1wic2VuZGVyMDdcIjpbXCI0YzMxZGM5ZWU3ZjI0MTc3Zjc4YjZmNTE4MDEyYTIwODMyNmUyYWYxZjM3YmIwYTI0MDViNTA1NmQwY2FkNjI4XCJdLFwic2VuZGVyMDFcIjpbXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDZcIjpbXCI1ZmZjMWY3ZmVmN2E0NDczODYyNTc2MmY3NWE0MjI5NDU0OTUxZTAzZjJhZmM2ZjgxMzA5YzBjMWJkZjllZTZmXCJdLFwic2VuZGVyMDBcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCJdLFwiZTdmN1wiOltcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcIl0sXCJzZW5kZXIwNVwiOltcImYwOWQ4ZjYzOTRhZWE0MjVmZTY3ODNkODhjZDgxMzYzZDgwMTdmMTZhZmQzNzExYzU3NWJlMGY1Y2Q1YzliYjlcIl0sXCJzZW5kZXIwNFwiOltcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl0sXCJtdWx0aS0wMi0wMy0wNC1hbnlcIjp7XCJwcmVkXCI6XCJrZXlzLWFueVwiLFwia2V5c1wiOltcIjNhOWRkNTMyZDczZGFjZTE5NWRiYjY0ZDFkYmE2NTcyZmI3ODNkMGZkZDMyNDY4NWUzMmZiZGEyZjg5Zjk5YTZcIixcIjQzZjJhZGIxZGUxOTIwMDBjYjM3NzdiYWNjN2Y5ODNiNjYxNGZkOWMxNzE1Y2Q0NGNkNDg0YjZkM2EwZDM0YzhcIixcIjJkNzBhYTRmNjk3YzNhM2I4ZGQ2ZDk3NzQ1YWMwNzRlZGNmZDBlYjY1YzM3Nzc0Y2RlMjUxMzU0ODNiZWE3MWVcIl19LFwic2VuZGVyMDlcIjpbXCJjNTlkOTg0MGIwYjY2MDkwODM2NTQ2YjdlYjRhNzM2MDYyNTc1MjdlYzhjMmI0ODIzMDBmZDIyOTI2NGIwN2U2XCJdLFwic2VuZGVyMDNcIjpbXCI0M2YyYWRiMWRlMTkyMDAwY2IzNzc3YmFjYzdmOTgzYjY2MTRmZDljMTcxNWNkNDRjZDQ4NGI2ZDNhMGQzNGM4XCJdLFwibXVsdGktMDAtMDFcIjpbXCIzNjg4MjBmODBjMzI0YmJjN2MyYjA2MTA2ODhhN2RhNDNlMzlmOTFkMTE4NzMyNjcxY2Q5Yzc1MDBmZjQzY2NhXCIsXCI2YmUyZjQ4NWE3YWY3NWZlZGI0YjdmMTUzYTkwM2Y3ZTYwMDBjYTRhYTUwMTE3OWM5MWEyNDUwYjc3N2JkMmE3XCJdLFwic2VuZGVyMDhcIjpbXCI2M2IyZWJhNGVkNzBkNDYxMmQzZTdiYzkwZGIyZmJmNGM3NmY3YjA3NDM2M2U4NmQ3M2YwYmM2MTdmOGU4YjgxXCJdLFwic2VuZGVyMDJcIjpbXCIzYTlkZDUzMmQ3M2RhY2UxOTVkYmI2NGQxZGJhNjU3MmZiNzgzZDBmZGQzMjQ2ODVlMzJmYmRhMmY4OWY5OWE2XCJdfSxcImNvZGVcIjpcIihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMFxcXCIpIDEwMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMVxcXCIpIDExMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwMlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwMlxcXCIpIDEyMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwM1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwM1xcXCIpIDEzMDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNFxcXCIpIDE0MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNVxcXCIpIDE1MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwNlxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwNlxcXCIpIDE2MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwN1xcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwN1xcXCIpIDE3MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOFxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOFxcXCIpIDE4MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJzZW5kZXIwOVxcXCIgKHJlYWQta2V5c2V0IFxcXCJzZW5kZXIwOVxcXCIpIDE5MDAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMC0wMVxcXCIgKHJlYWQta2V5c2V0IFxcXCJtdWx0aS0wMC0wMVxcXCIpIDEwMTAwMDAwMC4wKVxcbihjb2luLmNvaW5iYXNlIFxcXCJtdWx0aS0wMi0wMy0wNC1hbnlcXFwiIChyZWFkLWtleXNldCBcXFwibXVsdGktMDItMDMtMDQtYW55XFxcIikgMTIzNDAwMDAwLjApXFxuXFxuKGNvaW4uY29pbmJhc2UgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIChyZWFkLWtleXNldCBcXFwiZTdmN1xcXCIpIDE1MC4wKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiZGV2bmV0LWdyYW50c05cIn0ifQ", unsafeFromText "eyJnYXMiOjAsInJlc3VsdCI6eyJzdGF0dXMiOiJzdWNjZXNzIiwiZGF0YSI6IldyaXRlIHN1Y2NlZWRlZCJ9LCJyZXFLZXkiOiJmMjFVMUlGeVE3Rl8tNGVOdHpBMW5admNDSDlydjlmdmw1YzVrRHI1cWx3IiwibG9ncyI6IkF3MTVuVFF0YmlQYlVCQm1reGpRV09JZlc3ZHNfUi1sWjhNMGZDMXAxZHciLCJldmVudHMiOlt7InBhcmFtcyI6WyIiLCJzZW5kZXIwMCIsMTAwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMSIsMTEwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMiIsMTIwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwMyIsMTMwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNCIsMTQwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNSIsMTUwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNiIsMTYwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwNyIsMTcwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOCIsMTgwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJzZW5kZXIwOSIsMTkwMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMC0wMSIsMTAxMDAwMDAwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifSx7InBhcmFtcyI6WyIiLCJtdWx0aS0wMi0wMy0wNC1hbnkiLDEyMzQwMDAwMF0sIm5hbWUiOiJUUkFOU0ZFUiIsIm1vZHVsZSI6eyJuYW1lc3BhY2UiOm51bGwsIm5hbWUiOiJjb2luIn0sIm1vZHVsZUhhc2giOiIzaUlCUWRKbnN0NDRaMlpnWG9IUGtBYXV5YkowaDg1bF9lbl9TR0hOaWJFIn0seyJwYXJhbXMiOlsiIiwiZTdmNzYzNGU5MjU1NDFmMzY4YjgyN2FkNWM3MjQyMTkwNTEwMGY2MjA1Mjg1YTc4YzE5ZDdiNGEzODcxMTgwNSIsMTUwXSwibmFtZSI6IlRSQU5TRkVSIiwibW9kdWxlIjp7Im5hbWVzcGFjZSI6bnVsbCwibmFtZSI6ImNvaW4ifSwibW9kdWxlSGFzaCI6IjNpSUJRZEpuc3Q0NFoyWmdYb0hQa0FhdXliSjBoODVsX2VuX1NHSE5pYkUifV0sIm1ldGFEYXRhIjpudWxsLCJjb250aW51YXRpb24iOm51bGwsInR4SWQiOjh9") diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index 8736e8e952..82d6e22ad1 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -35,6 +35,8 @@ import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN import qualified Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM0Payload as PIN0 import qualified Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM1to9Payload as PINN +import qualified Chainweb.BlockHeader.Genesis.Pact53TransitionTimedCPM0Payload as PIT0 +import qualified Chainweb.BlockHeader.Genesis.Pact53TransitionTimedCPM1to9Payload as PITN import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM0Payload as QPIN0 import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM1to9Payload as QPINN @@ -465,8 +467,6 @@ pact5InstantCpmTestVersion g = buildTestVersion $ \v -> v -- SPV Bridge is not in effect for Pact 5 yet. SPVBridge -> AllChains ForkNever - Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 100) - _ -> AllChains ForkAtGenesis ) & versionQuirks .~ noQuirks @@ -490,21 +490,18 @@ pact53TransitionCpmTestVersion g = buildTestVersion $ \v -> v & cpmTestVersion g & versionName .~ ChainwebVersionName ("pact53-transition-CPM-" <> toText g) & versionForks .~ tabulateHashMap (\case - -- pact 5 is off until here - Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 20 - -- SPV Bridge is not in effect for Pact 5 yet. SPVBridge -> AllChains ForkNever - Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 100) + Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 5) _ -> AllChains ForkAtGenesis ) & versionQuirks .~ noQuirks & versionGenesis .~ VersionGenesis { _genesisBlockPayload = onChains $ - (unsafeChainId 0, IN0.payloadBlock) : - [(n, INN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] + (unsafeChainId 0, PIT0.payloadBlock) : + [(n, PITN.payloadBlock) | n <- HS.toList (unsafeChainId 0 `HS.delete` graphChainIds g)] , _genesisBlockTarget = AllChains maxTarget , _genesisTime = AllChains $ BlockCreationTime epoch } @@ -560,8 +557,6 @@ instantCpmTransitionTestVersion g = buildTestVersion $ \v -> v -- SPV Bridge is not in effect for Pact 5 yet. SPVBridge -> AllChains ForkNever - --Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 100) - _ -> AllChains ForkAtGenesis ) & versionQuirks .~ noQuirks From 279cb3ec465d9f71852eb6671eb282dc8be7e344 Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 25 Jun 2025 10:33:31 -0700 Subject: [PATCH 271/378] upgrade to pact53 and add flag parity to applyLocal for pact52 and pact53 --- cabal.project | 4 +- cabal.project.freeze | 406 ------------------ src/Chainweb/Pact5/TransactionExec.hs | 25 +- .../Chainweb/Test/Pact5/TransactionTests.hs | 2 +- 4 files changed, 16 insertions(+), 421 deletions(-) delete mode 100644 cabal.project.freeze diff --git a/cabal.project b/cabal.project index 07535dd4f9..6a7737a26a 100644 --- a/cabal.project +++ b/cabal.project @@ -101,8 +101,8 @@ source-repository-package source-repository-package type: git location: https://github.com/kadena-io/pact-5.git - tag: 7e908efd74482cce7721731f8ba43e37fc025ac5 - --sha256: 016k1si0wvibdz4b3kz02sqj72fah8q9g5kfalykl9s04plr3a8w + tag: 2d7605e8139be57cedacf303cf67f5a364ea5320 + --sha256: 148hx36kjfw5xmqnrrznjqn291rmzclpb8bcmmmd9inj4d0vpc3r source-repository-package type: git diff --git a/cabal.project.freeze b/cabal.project.freeze deleted file mode 100644 index e7f9cad5cf..0000000000 --- a/cabal.project.freeze +++ /dev/null @@ -1,406 +0,0 @@ -active-repositories: hackage.haskell.org:merge -constraints: any.Cabal ==3.12.1.0, - any.Cabal-syntax ==3.12.1.0, - any.Decimal ==0.5.2, - any.Diff ==1.0.2, - any.Glob ==0.10.2, - any.HUnit ==1.6.2.0, - any.JuicyPixels ==3.3.9, - any.OneTuple ==0.4.2, - any.Only ==0.1, - any.QuickCheck ==2.15.0.1, - any.RSA ==2.4.1, - any.SHA ==1.6.4.4, - any.StateVar ==1.2.2, - any.adjunctions ==4.4.3, - any.aeson ==2.2.3.0, - any.aeson-pretty ==0.8.10, - any.alex ==3.5.3.0, - any.ansi-terminal ==1.1.2, - any.ansi-terminal-types ==1.1, - any.ap-normalize ==0.1.0.1, - any.appar ==0.1.8, - any.array ==0.5.6.0, - any.asn1-encoding ==0.9.6, - any.asn1-parse ==0.9.5, - any.asn1-types ==0.3.4, - any.assoc ==1.1.1, - any.async ==2.2.5, - any.atomic-primops ==0.8.8, - any.attoparsec ==0.14.4, - any.authenticate-oauth ==1.7, - any.auto-update ==0.2.6, - any.barbies ==2.1.1.0, - any.base ==4.19.1.0, - any.base-compat ==0.14.1, - any.base-compat-batteries ==0.14.1, - any.base-orphans ==0.9.3, - any.base-unicode-symbols ==0.2.4.2, - any.base16-bytestring ==1.0.2.0, - any.base64-bytestring ==1.2.1.0, - any.base64-bytestring-kadena ==0.1, - any.basement ==0.0.16, - any.bifunctors ==5.6.2, - any.binary ==0.8.9.1, - any.binary-orphans ==1.0.5, - any.bitvec ==1.1.5.0, - any.blaze-builder ==0.4.2.3, - any.blaze-html ==0.9.2.0, - any.blaze-markup ==0.8.3.0, - any.boring ==0.2.2, - any.bound ==2.0.7, - any.bsb-http-chunked ==0.0.0.4, - any.bytebuild ==0.3.16.3, - any.byteorder ==1.0.4, - any.bytes ==0.17.4, - any.byteslice ==0.2.14.0, - any.bytesmith ==0.3.11.1, - any.bytestring ==0.12.1.0, - any.bytestring-builder ==0.10.8.2.0, - any.cache ==0.1.3.0, - any.call-stack ==0.4.0, - any.case-insensitive ==1.2.1.0, - any.cassava ==0.5.3.2, - any.cborg ==0.2.10.0, - any.cereal ==0.5.8.3, - chainweb -debug -ed25519 -ghc-flags, - chainweb-node -debug -ed25519 -ghc-flags, - any.character-ps ==0.1, - any.charset ==0.3.12, - any.chronos ==1.1.6.2, - any.citeproc ==0.8.1.3, - any.clock ==0.8.4, - any.co-log-core ==0.3.2.5, - any.code-page ==0.2.1, - any.colour ==2.3.6, - any.commonmark ==0.2.6.1, - any.commonmark-extensions ==0.2.6, - any.commonmark-pandoc ==0.2.3, - any.comonad ==5.0.9, - any.concurrent-output ==1.10.21, - any.conduit ==1.3.6.1, - any.conduit-extra ==1.3.7, - any.configuration-tools ==0.7.1, - any.constraints ==0.14.2, - any.containers ==0.6.8, - any.contiguous ==0.6.4.2, - any.contravariant ==1.5.5, - any.cookie ==0.5.1, - any.criterion ==1.6.4.0, - any.criterion-measurement ==0.2.3.0, - any.crypto-api ==0.13.3, - any.crypto-pubkey-types ==0.4.3, - any.crypto-token ==0.1.2, - any.cryptohash-md5 ==0.11.101.0, - any.cryptohash-sha1 ==0.11.101.0, - any.crypton ==1.0.4, - any.crypton-connection ==0.4.4, - any.crypton-x509 ==1.7.7, - any.crypton-x509-store ==1.6.10, - any.crypton-x509-system ==1.6.7, - any.crypton-x509-validation ==1.6.14, - any.cryptonite ==0.30, - any.cuckoo ==0.3.1, - cwtools -debug -ed25519 -ghc-flags -remote-db, - any.data-bword ==0.1.0.2, - any.data-default ==0.8.0.1, - any.data-default-class ==0.2.0.0, - any.data-dword ==0.3.2.1, - any.data-fix ==0.3.4, - any.data-ordlist ==0.4.7.0, - any.dec ==0.0.6, - any.deepseq ==1.5.0.0, - any.dense-linear-algebra ==0.1.0.0, - any.deriving-compat ==0.6.7, - any.digest ==0.0.2.1, - any.digraph ==0.3.2, - any.direct-sqlite ==2.3.29, - any.directory ==1.3.8.1, - any.distributive ==0.6.2.1, - any.djot ==0.1.2.2, - any.dlist ==1.0, - any.doclayout ==0.5, - any.doctemplates ==0.11.0.1, - any.easy-file ==0.2.5, - any.ech-config ==0.0.1, - any.emojis ==0.1.4.1, - any.enclosed-exceptions ==1.0.3, - any.entropy ==0.4.1.11, - any.erf ==2.0.0.0, - any.errors ==2.3.0, - any.ethereum ==0.1.0.2, - ethereum -ethhash -openssl-use-pkg-config, - any.exceptions ==0.10.7, - any.extra ==1.8, - any.fast-logger ==3.2.5, - any.file-embed ==0.0.16.0, - any.filepath ==1.4.200.1, - any.fingertree ==0.1.5.0, - any.finite-typelits ==0.2.1.0, - any.free ==5.2, - any.generic-data ==1.1.0.2, - any.generic-lens ==2.2.2.0, - any.generic-lens-core ==2.2.1.0, - any.generically ==0.1.1, - any.ghc-bignum ==1.3, - any.ghc-boot-th ==9.8.2, - any.ghc-compact ==0.1.0.0, - any.ghc-heap ==9.8.2, - any.ghc-prim ==0.11.0, - any.gridtables ==0.1.0.0, - any.groups ==0.5.3, - any.growable-vector ==0.1, - any.haddock-library ==1.11.0, - any.half ==0.3.2, - any.happy ==2.1.5, - any.happy-lib ==2.1.5, - any.hashable ==1.5.0.0, - any.hashes ==0.3.0.1, - any.haskeline ==0.8.2.1, - any.haskell-lexer ==1.2.1, - any.haskell-src-exts ==1.23.1, - any.haskell-src-meta ==0.8.15, - any.heaps ==0.4.1, - any.hedgehog ==1.5, - any.hourglass ==0.2.12, - any.hpke ==0.0.0, - any.http-api-data ==0.6.2, - any.http-client ==0.7.19, - any.http-client-tls ==0.3.6.4, - any.http-date ==0.0.11, - any.http-media ==0.8.1.1, - any.http-semantics ==0.3.0, - any.http-types ==0.12.4, - any.http2 ==5.3.9, - any.indexed-list-literals ==0.2.1.3, - any.indexed-profunctors ==0.1.1.1, - any.indexed-traversable ==0.1.4, - any.indexed-traversable-instances ==0.1.2, - any.integer-conversion ==0.1.1, - any.integer-gmp ==1.1, - any.integer-logarithms ==1.0.4, - any.invariant ==0.6.4, - any.iproute ==1.7.15, - any.ipynb ==0.2, - any.ixset-typed ==0.5.1.0, - any.jira-wiki-markup ==1.5.1, - any.js-chart ==2.9.4.1, - any.kan-extensions ==5.2.6, - any.lens ==5.3.4, - any.lens-aeson ==1.2.3, - any.libyaml ==0.1.4, - any.libyaml-clib ==0.2.5, - any.lifted-async ==0.10.2.7, - any.lifted-base ==0.2.3.12, - any.loglevel ==0.1.0.0, - any.lrucaching ==0.3.4, - any.lsp ==2.7.0.1, - any.lsp-types ==2.3.0.1, - any.managed ==1.0.10, - any.massiv ==1.0.4.1, - any.math-functions ==0.3.4.4, - any.megaparsec ==9.7.0, - any.memory ==0.18.0, - any.merkle-log ==0.2.0, - any.microlens ==0.4.14.0, - any.microstache ==1.0.3, - any.mime-types ==0.1.2.0, - any.mmorph ==1.2.0, - any.mod ==0.2.0.1, - any.monad-control ==1.0.3.1, - any.mono-traversable ==1.0.21.0, - any.mtl ==2.3.1, - any.mtl-compat ==0.2.2, - any.mwc-probability ==2.3.1, - any.mwc-random ==0.15.2.0, - any.natural-arithmetic ==0.2.2.0, - any.neat-interpolation ==0.5.1.4, - any.network ==3.2.7.0, - any.network-byte-order ==0.1.7, - any.network-control ==0.1.6, - any.network-info ==0.2.1, - any.network-uri ==2.6.4.2, - any.nothunks ==0.3.0.0, - any.old-locale ==1.0.0.7, - any.old-time ==1.1.0.4, - any.optparse-applicative ==0.18.1.0, - any.ordered-containers ==0.2.4, - any.os-string ==2.0.7, - any.pact ==4.13.2, - pact -build-tool +cryptonite-ed25519 -tests-in-lib, - any.pact-json ==0.1.0.0, - any.pact-time ==0.3.0.1, - pact-time -with-time, - any.pact-tng ==5.2, - pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, - any.pandoc ==3.6.4, - any.pandoc-types ==1.23.1, - any.parallel ==3.2.2.0, - any.parsec ==3.1.17.0, - any.parser-combinators ==1.3.0, - any.parsers ==0.12.12, - any.patience ==0.3, - any.pem ==0.2.4, - any.poly ==0.5.1.0, - any.pretty ==1.1.3.6, - any.pretty-show ==1.10, - any.pretty-simple ==4.1.3.0, - any.prettyprinter ==1.7.1, - any.prettyprinter-ansi-terminal ==1.1.3, - any.primitive ==0.9.1.0, - any.primitive-addr ==0.1.0.3, - any.primitive-offset ==0.2.0.1, - any.primitive-unlifted ==2.1.0.0, - any.process ==1.6.18.0, - any.profunctors ==5.6.2, - any.property-matchers ==0.4.0.0, - any.psqueues ==0.2.8.1, - any.pvar ==1.0.0.0, - any.quickcheck-instances ==0.3.32, - any.ralist ==0.4.0.0, - any.random ==1.3.1, - any.recover-rtti ==0.5.0, - any.recv ==0.1.0, - any.reducers ==3.12.5, - any.reflection ==2.1.9, - any.regex-base ==0.94.0.3, - any.regex-tdfa ==1.3.2.3, - any.resource-pool ==0.4.0.0, - any.resourcet ==1.3.0, - any.retry ==0.9.3.1, - any.rocksdb-haskell-kadena ==1.1.0, - rocksdb-haskell-kadena -with-tbb, - any.row-types ==1.0.1.2, - any.rts ==1.0.2, - any.run-st ==0.1.3.3, - any.safe ==0.3.21, - any.safe-exceptions ==0.1.7.4, - any.safecopy ==0.10.4.2, - any.scheduler ==2.0.1.0, - any.scientific ==0.3.8.0, - any.semialign ==1.3.1, - any.semigroupoids ==6.0.1, - any.semigroups ==0.20, - any.semirings ==0.7, - any.serialise ==0.2.6.1, - any.servant ==0.20.2, - any.servant-client ==0.20.2, - any.servant-client-core ==0.20.2, - any.servant-server ==0.20.2, - any.sha-validation ==0.1.0.1, - any.show-combinators ==0.2.0.0, - any.simple-sendfile ==0.2.32, - any.singleton-bool ==0.1.8, - any.skylighting ==0.14.6, - any.skylighting-core ==0.14.6, - any.skylighting-format-ansi ==0.1, - any.skylighting-format-blaze-html ==0.1.1.3, - any.skylighting-format-context ==0.1.0.2, - any.skylighting-format-latex ==0.1, - any.skylighting-format-typst ==0.1, - any.socks ==0.6.1, - any.some ==1.0.6, - any.sop-core ==0.5.0.2, - any.sorted-list ==0.2.3.1, - any.split ==0.2.5, - any.splitmix ==0.1.1, - any.statistics ==0.16.3.0, - any.stm ==2.5.2.1, - any.stm-chans ==3.0.0.9, - any.stopwatch ==0.1.0.6, - any.streaming ==0.2.4.0, - any.streaming-commons ==0.2.3.0, - any.strict ==0.5.1, - any.strict-concurrency ==0.2.4.3, - any.syb ==0.7.2.4, - any.tagged ==0.8.9, - any.tagsoup ==0.14.8, - any.tasty ==1.5.3, - any.tasty-golden ==2.3.5, - any.tasty-hedgehog ==1.4.0.2, - any.tasty-hunit ==0.10.2, - any.tasty-json ==0.1.0.0, - any.tasty-quickcheck ==0.11.1, - any.template-haskell ==2.21.0.0, - any.temporary ==1.3, - any.terminal-progress-bar ==0.4.2, - any.terminal-size ==0.3.4, - any.terminfo ==0.4.1.6, - any.texmath ==0.12.9, - any.text ==2.1.1, - any.text-conversions ==0.3.1.1, - any.text-iso8601 ==0.1.1, - any.text-rope ==0.3, - any.text-short ==0.1.6, - any.th-abstraction ==0.7.1.0, - any.th-compat ==0.1.6, - any.th-expand-syns ==0.4.12.0, - any.th-lift ==0.8.6, - any.th-lift-instances ==0.1.20, - any.th-orphans ==0.13.16, - any.th-reify-many ==0.1.10, - any.these ==1.2.1, - any.time ==1.12.2, - any.time-compat ==1.9.8, - any.time-locale-compat ==0.1.1.5, - any.time-manager ==0.2.2, - any.tls ==2.1.9, - any.tls-session-manager ==0.0.8, - any.token-bucket ==0.1.0.1, - any.toml-parser ==2.0.1.0, - any.torsor ==0.1.0.1, - any.transformers ==0.6.1.0, - any.transformers-base ==0.4.6, - any.transformers-compat ==0.7.2, - any.trifecta ==2.1.4, - any.tuples ==0.1.0.0, - any.typed-process ==0.2.13.0, - any.typst ==0.7, - any.typst-symbols ==0.1.7, - any.unicode-collation ==0.1.3.6, - any.unicode-data ==0.6.0, - any.unicode-transforms ==0.4.0.1, - any.uniplate ==1.6.13, - any.unix ==2.8.4.0, - any.unix-compat ==0.7.4, - any.unix-time ==0.4.16, - any.unlifted ==0.2.3.0, - any.unliftio ==0.2.25.1, - any.unliftio-core ==0.2.1.0, - any.unordered-containers ==0.2.20, - any.utf8-string ==1.0.2, - any.uuid ==1.3.16, - any.uuid-types ==1.0.6, - any.validation ==1.1.3, - any.vault ==0.3.1.5, - any.vector ==0.13.2.0, - any.vector-algorithms ==0.9.1.0, - any.vector-binary-instances ==0.2.5.2, - any.vector-sized ==1.6.1, - any.vector-stream ==0.1.0.1, - any.vector-th-unbox ==0.2.2, - any.void ==0.7.3, - any.wai ==3.2.4, - any.wai-app-static ==3.1.9, - any.wai-cors ==0.2.7, - any.wai-extra ==3.1.17, - any.wai-logger ==2.5.0, - any.wai-middleware-throttle ==0.3.0.1, - any.wai-middleware-validation ==0.1.0.2, - any.warp ==3.4.7, - any.warp-tls ==3.4.13, - any.wherefrom-compat ==0.1.1.1, - any.wide-word ==0.1.7.0, - any.witherable ==0.5, - any.wl-pprint-annotated ==0.1.0.1, - any.word8 ==0.1.3, - any.wreq ==0.5.4.3, - any.xml ==1.3.14, - any.xml-conduit ==1.10.0.0, - any.xml-types ==0.3.8, - any.yaml ==0.11.11.2, - any.yet-another-logger ==0.4.2, - any.zigzag ==0.1.0.0, - any.zip-archive ==0.4.3.2, - any.zlib ==0.7.1.0 -index-state: hackage.haskell.org 2025-04-14T16:11:29Z diff --git a/src/Chainweb/Pact5/TransactionExec.hs b/src/Chainweb/Pact5/TransactionExec.hs index 9c5a1fa801..f3da0ba3cd 100644 --- a/src/Chainweb/Pact5/TransactionExec.hs +++ b/src/Chainweb/Pact5/TransactionExec.hs @@ -294,7 +294,7 @@ applyLocal logger maybeGasLogger coreDb txCtx spvSupport cmd = do } where - localFlags = S.fromList + defaultFlags = S.fromList [ FlagDisableRuntimeRTC , FlagEnforceKeyFormats -- Note: this is currently not conditional @@ -302,7 +302,8 @@ applyLocal logger maybeGasLogger coreDb txCtx spvSupport cmd = do -- anyone's workflow , FlagAllowReadInLocal , FlagRequireKeysetNs - ] `Set.union` guardDisablePact51Flags txCtx + ] + localFlags = Set.unions [defaultFlags, guardDisablePact51Flags txCtx, guardDisablePact52And53Flags txCtx] -- | The main entry point to executing transactions. From here, -- 'applyCmd' assembles the command environment for a command, @@ -338,7 +339,7 @@ applyCmd logger maybeGasLogger db txCtx txIdxInBlock spv initialGas cmd = do , FlagEnforceKeyFormats , FlagRequireKeysetNs ] - let flags = Set.unions [defaultFlags, guardDisablePact51Flags txCtx, guardDisablePact52Flags txCtx] + let flags = Set.unions [defaultFlags, guardDisablePact51Flags txCtx, guardDisablePact52And53Flags txCtx] let gasLogsEnabled = maybe GasLogsDisabled (const GasLogsEnabled) maybeGasLogger gasEnv <- mkTableGasEnv (MilliGasLimit $ gasToMilliGas $ gasLimit ^. _GasLimit) gasLogsEnabled @@ -475,7 +476,7 @@ applyCoinbase logger db reward txCtx = do let (coinbaseTerm, coinbaseData) = mkCoinbaseTerm mid mks reward defaultFlags = Set.singleton FlagDisableRuntimeRTC - flags = Set.unions [defaultFlags, guardDisablePact52Flags txCtx] + flags = Set.unions [defaultFlags, guardDisablePact52And53Flags txCtx] eCoinbaseTxResult <- evalExecTerm Transactional db noSPVSupport freeGasEnv flags SimpleNamespacePolicy @@ -565,7 +566,7 @@ runGenesisPayload logger db spv ctx cmd = do -- Todo gas logs freeGasEnv <- mkFreeGasEnv GasLogsDisabled let defaultFlags = Set.fromList [FlagDisableRuntimeRTC] - flags = Set.union defaultFlags (guardDisablePact52Flags ctx) + flags = Set.union defaultFlags (guardDisablePact52And53Flags ctx) runExceptT (runReaderT (runTransactionM @@ -680,7 +681,7 @@ runUpgrade _logger db txContext cmd = case payload ^. pPayload of freeGasEnv <- mkFreeGasEnv GasLogsDisabled let flags = Set.unions [ Set.singleton FlagDisableRuntimeRTC - , guardDisablePact52Flags txContext + , guardDisablePact52And53Flags txContext ] evalExec (RawCode (_pcCode (_pmCode pm))) Transactional db noSPVSupport freeGasEnv flags @@ -761,7 +762,7 @@ buyGas logger origGasEnv db txCtx cmd = do if isChainweb224Pact then mkBuyGasTerm sender supply else mkFundTxTerm mid mks sender supply - flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52Flags txCtx] + flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52And53Flags txCtx] runExceptT $ do gasPayerCap <- case gasPayerCaps of @@ -861,7 +862,7 @@ redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd "redeeming gas (post-2.24) for " <> sshow (_cmdHash cmd) -- if we're past chainweb 2.24, we don't use defpacts for gas; see 'pact/coin-contract/coin.pact#redeem-gas' let (redeemGasTerm, redeemGasData) = mkRedeemGasTerm mid mks sender gasTotal gasFee - flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52Flags txCtx] + flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52And53Flags txCtx] -- todo: gas logs freeGasEnv <- mkFreeGasEnv GasLogsDisabled evalExecTerm @@ -888,7 +889,7 @@ redeemGas logger db txCtx gasUsed maybeFundTxPactId cmd "redeeming gas (pre-2.24) for " <> sshow (_cmdHash cmd) -- before chainweb 2.24, we use defpacts for gas; see: 'pact/coin-contract/coin.pact#fund-tx' let redeemGasData = PObject $ Map.singleton "fee" (PDecimal $ _pact5GasSupply gasFee) - flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52Flags txCtx] + flags = Set.unions [Set.singleton FlagDisableRuntimeRTC, guardDisablePact52And53Flags txCtx] evalContinuation Transactional db noSPVSupport freeGasEnv flags SimpleNamespacePolicy @@ -1004,7 +1005,7 @@ guardDisablePact51Flags txCtx | guardCtx chainweb228Pact txCtx = Set.empty | otherwise = Set.singleton FlagDisablePact51 -guardDisablePact52Flags :: TxContext -> Set ExecutionFlag -guardDisablePact52Flags txCtx +guardDisablePact52And53Flags :: TxContext -> Set ExecutionFlag +guardDisablePact52And53Flags txCtx | guardCtx chainweb230Pact txCtx = Set.empty - | otherwise = Set.fromList [FlagDisablePact52, FlagDisableReentrancyCheck] + | otherwise = Set.fromList [FlagDisablePact52, FlagDisablePact53, FlagDisableReentrancyCheck] diff --git a/test/unit/Chainweb/Test/Pact5/TransactionTests.hs b/test/unit/Chainweb/Test/Pact5/TransactionTests.hs index aee393c044..9de5221b3f 100644 --- a/test/unit/Chainweb/Test/Pact5/TransactionTests.hs +++ b/test/unit/Chainweb/Test/Pact5/TransactionTests.hs @@ -48,7 +48,7 @@ nsReplV2 = "pact/pact5/namespaces/ns.repl" runReplTest :: FilePath -> Assertion runReplTest file = do - (scriptout, rstate) <- execScript False file + (scriptout, rstate) <- execScript False False file case scriptout of Left e -> failWithErr rstate e Right _ -> do From 786331a41f49c0853ef7d3953dd17a5540359fd7 Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 25 Jun 2025 10:40:17 -0700 Subject: [PATCH 272/378] update cabal.project.freeze --- cabal.project.freeze | 406 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 cabal.project.freeze diff --git a/cabal.project.freeze b/cabal.project.freeze new file mode 100644 index 0000000000..07ecc744cc --- /dev/null +++ b/cabal.project.freeze @@ -0,0 +1,406 @@ +active-repositories: hackage.haskell.org:merge +constraints: any.Cabal ==3.12.1.0, + any.Cabal-syntax ==3.12.1.0, + any.Decimal ==0.5.2, + any.Diff ==1.0.2, + any.Glob ==0.10.2, + any.HUnit ==1.6.2.0, + any.JuicyPixels ==3.3.9, + any.OneTuple ==0.4.2, + any.Only ==0.1, + any.QuickCheck ==2.15.0.1, + any.RSA ==2.4.1, + any.SHA ==1.6.4.4, + any.StateVar ==1.2.2, + any.adjunctions ==4.4.3, + any.aeson ==2.2.3.0, + any.aeson-pretty ==0.8.10, + any.alex ==3.5.3.0, + any.ansi-terminal ==1.1.2, + any.ansi-terminal-types ==1.1, + any.ap-normalize ==0.1.0.1, + any.appar ==0.1.8, + any.array ==0.5.6.0, + any.asn1-encoding ==0.9.6, + any.asn1-parse ==0.9.5, + any.asn1-types ==0.3.4, + any.assoc ==1.1.1, + any.async ==2.2.5, + any.atomic-primops ==0.8.8, + any.attoparsec ==0.14.4, + any.authenticate-oauth ==1.7, + any.auto-update ==0.2.6, + any.barbies ==2.1.1.0, + any.base ==4.19.1.0, + any.base-compat ==0.14.1, + any.base-compat-batteries ==0.14.1, + any.base-orphans ==0.9.3, + any.base-unicode-symbols ==0.2.4.2, + any.base16-bytestring ==1.0.2.0, + any.base64-bytestring ==1.2.1.0, + any.base64-bytestring-kadena ==0.1, + any.basement ==0.0.16, + any.bifunctors ==5.6.2, + any.binary ==0.8.9.1, + any.binary-orphans ==1.0.5, + any.bitvec ==1.1.5.0, + any.blaze-builder ==0.4.2.3, + any.blaze-html ==0.9.2.0, + any.blaze-markup ==0.8.3.0, + any.boring ==0.2.2, + any.bound ==2.0.7, + any.bsb-http-chunked ==0.0.0.4, + any.bytebuild ==0.3.16.3, + any.byteorder ==1.0.4, + any.bytes ==0.17.4, + any.byteslice ==0.2.14.0, + any.bytesmith ==0.3.11.1, + any.bytestring ==0.12.1.0, + any.bytestring-builder ==0.10.8.2.0, + any.cache ==0.1.3.0, + any.call-stack ==0.4.0, + any.case-insensitive ==1.2.1.0, + any.cassava ==0.5.3.2, + any.cborg ==0.2.10.0, + any.cereal ==0.5.8.3, + chainweb -debug -ed25519 -ghc-flags, + chainweb-node -debug -ed25519 -ghc-flags, + any.character-ps ==0.1, + any.charset ==0.3.12, + any.chronos ==1.1.6.2, + any.citeproc ==0.8.1.3, + any.clock ==0.8.4, + any.co-log-core ==0.3.2.5, + any.code-page ==0.2.1, + any.colour ==2.3.6, + any.commonmark ==0.2.6.1, + any.commonmark-extensions ==0.2.6, + any.commonmark-pandoc ==0.2.3, + any.comonad ==5.0.9, + any.concurrent-output ==1.10.21, + any.conduit ==1.3.6.1, + any.conduit-extra ==1.3.7, + any.configuration-tools ==0.7.1, + any.constraints ==0.14.2, + any.containers ==0.6.8, + any.contiguous ==0.6.4.2, + any.contravariant ==1.5.5, + any.cookie ==0.5.1, + any.criterion ==1.6.4.0, + any.criterion-measurement ==0.2.3.0, + any.crypto-api ==0.13.3, + any.crypto-pubkey-types ==0.4.3, + any.crypto-token ==0.1.2, + any.cryptohash-md5 ==0.11.101.0, + any.cryptohash-sha1 ==0.11.101.0, + any.crypton ==1.0.4, + any.crypton-connection ==0.4.4, + any.crypton-x509 ==1.7.7, + any.crypton-x509-store ==1.6.10, + any.crypton-x509-system ==1.6.7, + any.crypton-x509-validation ==1.6.14, + any.cryptonite ==0.30, + any.cuckoo ==0.3.1, + cwtools -debug -ed25519 -ghc-flags -remote-db, + any.data-bword ==0.1.0.2, + any.data-default ==0.8.0.1, + any.data-default-class ==0.2.0.0, + any.data-dword ==0.3.2.1, + any.data-fix ==0.3.4, + any.data-ordlist ==0.4.7.0, + any.dec ==0.0.6, + any.deepseq ==1.5.0.0, + any.dense-linear-algebra ==0.1.0.0, + any.deriving-compat ==0.6.7, + any.digest ==0.0.2.1, + any.digraph ==0.3.2, + any.direct-sqlite ==2.3.29, + any.directory ==1.3.8.1, + any.distributive ==0.6.2.1, + any.djot ==0.1.2.2, + any.dlist ==1.0, + any.doclayout ==0.5, + any.doctemplates ==0.11.0.1, + any.easy-file ==0.2.5, + any.ech-config ==0.0.1, + any.emojis ==0.1.4.1, + any.enclosed-exceptions ==1.0.3, + any.entropy ==0.4.1.11, + any.erf ==2.0.0.0, + any.errors ==2.3.0, + any.ethereum ==0.1.0.2, + ethereum -ethhash -openssl-use-pkg-config, + any.exceptions ==0.10.7, + any.extra ==1.8, + any.fast-logger ==3.2.5, + any.file-embed ==0.0.16.0, + any.filepath ==1.4.200.1, + any.fingertree ==0.1.5.0, + any.finite-typelits ==0.2.1.0, + any.free ==5.2, + any.generic-data ==1.1.0.2, + any.generic-lens ==2.2.2.0, + any.generic-lens-core ==2.2.1.0, + any.generically ==0.1.1, + any.ghc-bignum ==1.3, + any.ghc-boot-th ==9.8.2, + any.ghc-compact ==0.1.0.0, + any.ghc-heap ==9.8.2, + any.ghc-prim ==0.11.0, + any.gridtables ==0.1.0.0, + any.groups ==0.5.3, + any.growable-vector ==0.1, + any.haddock-library ==1.11.0, + any.half ==0.3.2, + any.happy ==2.1.5, + any.happy-lib ==2.1.5, + any.hashable ==1.5.0.0, + any.hashes ==0.3.0.1, + any.haskeline ==0.8.2.1, + any.haskell-lexer ==1.2.1, + any.haskell-src-exts ==1.23.1, + any.haskell-src-meta ==0.8.15, + any.heaps ==0.4.1, + any.hedgehog ==1.5, + any.hourglass ==0.2.12, + any.hpke ==0.0.0, + any.http-api-data ==0.6.2, + any.http-client ==0.7.19, + any.http-client-tls ==0.3.6.4, + any.http-date ==0.0.11, + any.http-media ==0.8.1.1, + any.http-semantics ==0.3.0, + any.http-types ==0.12.4, + any.http2 ==5.3.9, + any.indexed-list-literals ==0.2.1.3, + any.indexed-profunctors ==0.1.1.1, + any.indexed-traversable ==0.1.4, + any.indexed-traversable-instances ==0.1.2, + any.integer-conversion ==0.1.1, + any.integer-gmp ==1.1, + any.integer-logarithms ==1.0.4, + any.invariant ==0.6.4, + any.iproute ==1.7.15, + any.ipynb ==0.2, + any.ixset-typed ==0.5.1.0, + any.jira-wiki-markup ==1.5.1, + any.js-chart ==2.9.4.1, + any.kan-extensions ==5.2.6, + any.lens ==5.3.4, + any.lens-aeson ==1.2.3, + any.libyaml ==0.1.4, + any.libyaml-clib ==0.2.5, + any.lifted-async ==0.10.2.7, + any.lifted-base ==0.2.3.12, + any.loglevel ==0.1.0.0, + any.lrucaching ==0.3.4, + any.lsp ==2.7.0.1, + any.lsp-types ==2.3.0.1, + any.managed ==1.0.10, + any.massiv ==1.0.4.1, + any.math-functions ==0.3.4.4, + any.megaparsec ==9.7.0, + any.memory ==0.18.0, + any.merkle-log ==0.2.0, + any.microlens ==0.4.14.0, + any.microstache ==1.0.3, + any.mime-types ==0.1.2.0, + any.mmorph ==1.2.0, + any.mod ==0.2.0.1, + any.monad-control ==1.0.3.1, + any.mono-traversable ==1.0.21.0, + any.mtl ==2.3.1, + any.mtl-compat ==0.2.2, + any.mwc-probability ==2.3.1, + any.mwc-random ==0.15.2.0, + any.natural-arithmetic ==0.2.2.0, + any.neat-interpolation ==0.5.1.4, + any.network ==3.2.7.0, + any.network-byte-order ==0.1.7, + any.network-control ==0.1.6, + any.network-info ==0.2.1, + any.network-uri ==2.6.4.2, + any.nothunks ==0.3.0.0, + any.old-locale ==1.0.0.7, + any.old-time ==1.1.0.4, + any.optparse-applicative ==0.18.1.0, + any.ordered-containers ==0.2.4, + any.os-string ==2.0.7, + any.pact ==4.13.2, + pact -build-tool +cryptonite-ed25519 -tests-in-lib, + any.pact-json ==0.1.0.0, + any.pact-time ==0.3.0.1, + pact-time -with-time, + any.pact-tng ==5.3, + pact-tng +with-crypto +with-funcall-tracing +with-native-tracing, + any.pandoc ==3.6.4, + any.pandoc-types ==1.23.1, + any.parallel ==3.2.2.0, + any.parsec ==3.1.17.0, + any.parser-combinators ==1.3.0, + any.parsers ==0.12.12, + any.patience ==0.3, + any.pem ==0.2.4, + any.poly ==0.5.1.0, + any.pretty ==1.1.3.6, + any.pretty-show ==1.10, + any.pretty-simple ==4.1.3.0, + any.prettyprinter ==1.7.1, + any.prettyprinter-ansi-terminal ==1.1.3, + any.primitive ==0.9.1.0, + any.primitive-addr ==0.1.0.3, + any.primitive-offset ==0.2.0.1, + any.primitive-unlifted ==2.1.0.0, + any.process ==1.6.18.0, + any.profunctors ==5.6.2, + any.property-matchers ==0.4.0.0, + any.psqueues ==0.2.8.1, + any.pvar ==1.0.0.0, + any.quickcheck-instances ==0.3.32, + any.ralist ==0.4.0.0, + any.random ==1.3.1, + any.recover-rtti ==0.5.0, + any.recv ==0.1.0, + any.reducers ==3.12.5, + any.reflection ==2.1.9, + any.regex-base ==0.94.0.3, + any.regex-tdfa ==1.3.2.3, + any.resource-pool ==0.4.0.0, + any.resourcet ==1.3.0, + any.retry ==0.9.3.1, + any.rocksdb-haskell-kadena ==1.1.0, + rocksdb-haskell-kadena -with-tbb, + any.row-types ==1.0.1.2, + any.rts ==1.0.2, + any.run-st ==0.1.3.3, + any.safe ==0.3.21, + any.safe-exceptions ==0.1.7.4, + any.safecopy ==0.10.4.2, + any.scheduler ==2.0.1.0, + any.scientific ==0.3.8.0, + any.semialign ==1.3.1, + any.semigroupoids ==6.0.1, + any.semigroups ==0.20, + any.semirings ==0.7, + any.serialise ==0.2.6.1, + any.servant ==0.20.2, + any.servant-client ==0.20.2, + any.servant-client-core ==0.20.2, + any.servant-server ==0.20.2, + any.sha-validation ==0.1.0.1, + any.show-combinators ==0.2.0.0, + any.simple-sendfile ==0.2.32, + any.singleton-bool ==0.1.8, + any.skylighting ==0.14.6, + any.skylighting-core ==0.14.6, + any.skylighting-format-ansi ==0.1, + any.skylighting-format-blaze-html ==0.1.1.3, + any.skylighting-format-context ==0.1.0.2, + any.skylighting-format-latex ==0.1, + any.skylighting-format-typst ==0.1, + any.socks ==0.6.1, + any.some ==1.0.6, + any.sop-core ==0.5.0.2, + any.sorted-list ==0.2.3.1, + any.split ==0.2.5, + any.splitmix ==0.1.1, + any.statistics ==0.16.3.0, + any.stm ==2.5.2.1, + any.stm-chans ==3.0.0.9, + any.stopwatch ==0.1.0.6, + any.streaming ==0.2.4.0, + any.streaming-commons ==0.2.3.0, + any.strict ==0.5.1, + any.strict-concurrency ==0.2.4.3, + any.syb ==0.7.2.4, + any.tagged ==0.8.9, + any.tagsoup ==0.14.8, + any.tasty ==1.5.3, + any.tasty-golden ==2.3.5, + any.tasty-hedgehog ==1.4.0.2, + any.tasty-hunit ==0.10.2, + any.tasty-json ==0.1.0.0, + any.tasty-quickcheck ==0.11.1, + any.template-haskell ==2.21.0.0, + any.temporary ==1.3, + any.terminal-progress-bar ==0.4.2, + any.terminal-size ==0.3.4, + any.terminfo ==0.4.1.6, + any.texmath ==0.12.9, + any.text ==2.1.1, + any.text-conversions ==0.3.1.1, + any.text-iso8601 ==0.1.1, + any.text-rope ==0.3, + any.text-short ==0.1.6, + any.th-abstraction ==0.7.1.0, + any.th-compat ==0.1.6, + any.th-expand-syns ==0.4.12.0, + any.th-lift ==0.8.6, + any.th-lift-instances ==0.1.20, + any.th-orphans ==0.13.16, + any.th-reify-many ==0.1.10, + any.these ==1.2.1, + any.time ==1.12.2, + any.time-compat ==1.9.8, + any.time-locale-compat ==0.1.1.5, + any.time-manager ==0.2.2, + any.tls ==2.1.9, + any.tls-session-manager ==0.0.8, + any.token-bucket ==0.1.0.1, + any.toml-parser ==2.0.1.0, + any.torsor ==0.1.0.1, + any.transformers ==0.6.1.0, + any.transformers-base ==0.4.6, + any.transformers-compat ==0.7.2, + any.trifecta ==2.1.4, + any.tuples ==0.1.0.0, + any.typed-process ==0.2.13.0, + any.typst ==0.7, + any.typst-symbols ==0.1.7, + any.unicode-collation ==0.1.3.6, + any.unicode-data ==0.6.0, + any.unicode-transforms ==0.4.0.1, + any.uniplate ==1.6.13, + any.unix ==2.8.4.0, + any.unix-compat ==0.7.4, + any.unix-time ==0.4.16, + any.unlifted ==0.2.3.0, + any.unliftio ==0.2.25.1, + any.unliftio-core ==0.2.1.0, + any.unordered-containers ==0.2.20, + any.utf8-string ==1.0.2, + any.uuid ==1.3.16, + any.uuid-types ==1.0.6, + any.validation ==1.1.3, + any.vault ==0.3.1.5, + any.vector ==0.13.2.0, + any.vector-algorithms ==0.9.1.0, + any.vector-binary-instances ==0.2.5.2, + any.vector-sized ==1.6.1, + any.vector-stream ==0.1.0.1, + any.vector-th-unbox ==0.2.2, + any.void ==0.7.3, + any.wai ==3.2.4, + any.wai-app-static ==3.1.9, + any.wai-cors ==0.2.7, + any.wai-extra ==3.1.17, + any.wai-logger ==2.5.0, + any.wai-middleware-throttle ==0.3.0.1, + any.wai-middleware-validation ==0.1.0.2, + any.warp ==3.4.7, + any.warp-tls ==3.4.13, + any.wherefrom-compat ==0.1.1.1, + any.wide-word ==0.1.7.0, + any.witherable ==0.5, + any.wl-pprint-annotated ==0.1.0.1, + any.word8 ==0.1.3, + any.wreq ==0.5.4.3, + any.xml ==1.3.14, + any.xml-conduit ==1.10.0.0, + any.xml-types ==0.3.8, + any.yaml ==0.11.11.2, + any.yet-another-logger ==0.4.2, + any.zigzag ==0.1.0.0, + any.zip-archive ==0.4.3.2, + any.zlib ==0.7.1.0 +index-state: hackage.haskell.org 2025-06-17T19:59:02Z From f008f085eed97cd402a4fe877361331851012c8d Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 25 Jun 2025 10:43:00 -0700 Subject: [PATCH 273/378] remove warning disable flags in Pact5 RemotePactTest --- test/unit/Chainweb/Test/Pact5/RemotePactTest.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs index 060db0d314..1d8bc2744a 100644 --- a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs @@ -1,5 +1,3 @@ -{-# options_ghc -fno-warn-unused-local-binds -fno-warn-unused-matches #-} - {-# language ConstraintKinds , DataKinds From 2ffba75ffdc66c2ee5fb3071a31b0a243d7a1c1b Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 25 Jun 2025 10:55:56 -0700 Subject: [PATCH 274/378] simplify advanceToForkHeight and get another transition test to use it --- test/unit/Chainweb/Test/Pact5/CutFixture.hs | 62 ++++++------------- .../Chainweb/Test/Pact5/RemotePactTest.hs | 6 +- 2 files changed, 20 insertions(+), 48 deletions(-) diff --git a/test/unit/Chainweb/Test/Pact5/CutFixture.hs b/test/unit/Chainweb/Test/Pact5/CutFixture.hs index 4ee40eaafa..00719a97ca 100644 --- a/test/unit/Chainweb/Test/Pact5/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact5/CutFixture.hs @@ -47,7 +47,6 @@ import Chainweb.BlockCreationTime (BlockCreationTime(..)) import Chainweb.BlockHash (BlockHash) import Chainweb.BlockHeader hiding (blockCreationTime, blockNonce) import Chainweb.BlockHeader.Internal (blockCreationTime, blockNonce) -import Chainweb.BlockHeight (BlockHeight) import Chainweb.ChainId import Chainweb.ChainValue (ChainValue(..), ChainValueCasLookup, chainLookupM) import Chainweb.Cut @@ -103,48 +102,6 @@ data Fixture = Fixture } makeLenses ''Fixture --- Advance to the forkheight of a fork. --- Throws an error if the fork is not found in the ChainwebVersion '_versionForks', or if --- the fork height is ever 'ForkNever' on any chain. -advanceToForkHeight :: (HasFixture fx) => fx -> Fork -> IO () -advanceToForkHeight fx fork = do - Fixture{..} <- cutFixture fx - let v = _chainwebVersion _fixtureCutDb - latestCut <- liftIO $ _fixtureCutDb ^. cut - let blockHeights = fmap (view blockHeight) $ latestCut ^. cutMap - let latestBlockHeight = maximum blockHeights - - let targetHeight = 1 + getMaxForkHeight v fork latestBlockHeight - when (targetHeight > latestBlockHeight) $ do - -- advance all chains to the target height - replicateM_ (int (targetHeight - latestBlockHeight)) $ advanceAllChains_ fx - - -- check if we reached the target height - latestCut' <- _fixtureCutDb ^. cut - let newHeights = fmap (view blockHeight) $ latestCut' ^. cutMap - when (maximum newHeights < targetHeight) $ - throwM $ InternalInvariantViolation $ "advanceToForkHeight: did not reach target height " <> sshow targetHeight - - where - -- get the max fork height for a given fork in the ChainwebVersion. - -- this will let us safely advance to where the unlocked feature(s) are available - -- on all chains. - getMaxForkHeight :: ChainwebVersion -> Fork -> BlockHeight -> BlockHeight - getMaxForkHeight v frk latestBlockHeight = - fromMaybe (error "getMaxForkHeight: no forks") $ - maximumOf (versionForks . ix frk . to expand . folded . to (uncurry forkHeightAt)) v - where - expand :: ChainMap a -> [(ChainId, a)] - expand = \case - AllChains fh -> (,fh) <$> HashSet.toList (chainIdsAt v latestBlockHeight) - OnChains m -> m ^@.. ifolded - - forkHeightAt :: ChainId -> ForkHeight -> BlockHeight - forkHeightAt cid = \case - ForkAtBlockHeight bh -> bh - ForkAtGenesis -> genesisHeight v cid - ForkNever -> error "ForkNever encountered" - class HasFixture a where cutFixture :: a -> IO Fixture instance HasFixture Fixture where @@ -216,6 +173,25 @@ advanceAllChains_ -> IO () advanceAllChains_ = void . advanceAllChains +-- Advance to the forkheight of a fork. +-- +-- Throws an 'error' if the fork is not found in the ChainwebVersion '_versionForks', or if +-- the fork height is ever 'ForkNever' or 'ForkGenesis' on all chains. +-- +-- Does nothing if you are already at or past the the forkheight. +advanceToForkHeight :: (HasCallStack, HasFixture fx) => fx -> Fork -> IO () +advanceToForkHeight fx fork = do + Fixture{..} <- cutFixture fx + let v = _chainwebVersion _fixtureCutDb + latestCut <- liftIO $ _fixtureCutDb ^. cut + let latestBlockHeight = latestCut ^. cutMaxHeight + + let targetHeight = fromMaybe (error "advanceToForkHeight: no fork found") $ + maximumOf (versionForks . ix fork . folded . _ForkAtBlockHeight) v + + when (targetHeight > latestBlockHeight) $ do + replicateM_ (int (targetHeight - latestBlockHeight)) $ advanceAllChains_ fx + withTestCutDb :: (Logger logger) => logger -> ChainwebVersion diff --git a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs index 1d8bc2744a..c72b4b25b4 100644 --- a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs @@ -856,11 +856,7 @@ transitionOccurs rdb _step = runResourceT $ do liftIO $ do checkPactVersion fx v cid >>= P.equals Pact4 - forM_ @_ @_ @Word [1..17] $ \i -> do - advanceAllChains_ fx - -- index trick to show which iteration fails, if any - (i,) <$> checkPactVersion fx v cid >>= P.equals (i, Pact4) - advanceAllChains_ fx + advanceToForkHeight fx Pact5Fork checkPactVersion fx v cid >>= P.equals Pact5 -- | Test that xchains work across the Pact4->Pact4 transition boundary. From 7243571bd1458ee9687266657bb13ae39cc728e0 Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 25 Jun 2025 11:19:13 -0700 Subject: [PATCH 275/378] respond to review --- .../Chainweb/Test/Pact5/RemotePactTest.hs | 64 +++++++------------ 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs index c72b4b25b4..84edcdf51a 100644 --- a/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact5/RemotePactTest.hs @@ -579,6 +579,9 @@ pact53TransitionTest baseRdb step = runResourceT $ do ] liftIO $ do + let errorTx = mkExec' "(error \"test error\")" + let pureTx = mkExec' "(pure 1)" + txErr0 <- buildTextCmd v $ set cbRPC errorTx $ defaultCmd cid @@ -586,34 +589,38 @@ pact53TransitionTest baseRdb step = runResourceT $ do $ set cbRPC pureTx $ defaultCmd cid txReadOnlyGuardSetup <- buildTextCmd v - $ set cbRPC readOnlyGuardSetupTx - $ defaultCmd cid - txReadOnly0 <- buildTextCmd v - $ set cbRPC readOnlyGuardTx + $ set cbRPC (mkExec' $ T.concat + [ "(namespace 'free)" + , "(module test-read-only g (defcap g () true)" + , " (defschema my-schema key:integer)" + , " (deftable my-table:{my-schema})" + , " (defun read-only-fn () (read my-table 'key))" + , " (defun mk-ug () (create-user-guard (read-only-fn)))" + , ")" + , "(create-table my-table)" + , "(insert my-table 'key {'key:1})" + ]) $ defaultCmd cid - step "sending" + step "sending first batch of transactions" send fx v cid [txErr0, txPure0, txReadOnlyGuardSetup] - - step "advancing chains" - advanceAllChains_ fx + step "polling for first batch of transactions" + assertTxFailure txErr0 "Cannot find module: error" + assertTxFailure txPure0 "Cannot find module: pure" + assertTxSuccess txReadOnlyGuardSetup (PString "Write succeeded") step "sending read only guard tx" - + let readOnlyGuardTx = mkExec' "(enforce-guard (free.test-read-only.mk-ug))" + txReadOnly0 <- buildTextCmd v + $ set cbRPC readOnlyGuardTx + $ defaultCmd cid send fx v cid [txReadOnly0] - advanceAllChains_ fx - - step "polling" - - assertTxFailure txErr0 "Cannot find module: error" - assertTxFailure txPure0 "Cannot find module: pure" - assertTxSuccess txReadOnlyGuardSetup (PString "Write succeeded") + step "polling for read only guard tx" assertTxFailure txReadOnly0 "Error during database operation: Operation is not allowed in read-only or system-only mode." step "advancing past the fork" - advanceToForkHeight fx Chainweb230Pact txErr1 <- buildTextCmd v @@ -627,36 +634,13 @@ pact53TransitionTest baseRdb step = runResourceT $ do $ defaultCmd cid step "Sending post fork txs" - send fx v cid [txErr1, txPure1, txReadOnly1] - - step "advancing chains again" - advanceAllChains_ fx - step "polling post-fork results" - -- Note: correct output from calling (error "test error") assertTxFailure txErr1 "test error" - assertTxSuccess txPure1 (PInteger 1) - assertTxSuccess txReadOnly1 (PBool True) - where - errorTx = mkExec' "(error \"test error\")" - pureTx = mkExec' "(pure 1)" - readOnlyGuardSetupTx = mkExec' $ T.concat - [ "(namespace 'free)" - , "(module test-read-only g (defcap g () true)" - , " (defschema my-schema key:integer)" - , " (deftable my-table:{my-schema})" - , " (defun read-only-fn () (read my-table 'key))" - , " (defun mk-ug () (create-user-guard (read-only-fn)))" - , ")" - , "(create-table my-table)" - , "(insert my-table 'key {'key:1})" - ] - readOnlyGuardTx = mkExec' "(enforce-guard (free.test-read-only.mk-ug))" allocation01KeyPair :: (Text, Text) allocation01KeyPair = From c6853db95bc176af5f030dc40d42ed6ec48419d8 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 26 Jun 2025 13:05:00 -0700 Subject: [PATCH 276/378] evm-genesis: write payload data to file instead of stdout --- cwtools/evm-genesis/Main.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 97b1b3c5b9..596f8479c6 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -24,7 +24,6 @@ import Control.Monad import Data.Aeson import Data.String import Data.Text qualified as T -import Data.Text.IO qualified as T import Ethereum.Misc qualified as E import Ethereum.RLP qualified as E import Network.HTTP.Client qualified as HTTP @@ -72,15 +71,16 @@ main = do return ("evm-development-pair", cids, evmDevnetSpecFile 1) _ -> error "Invalid argument for the chainweb version provided. The version must be one of: 'mainnet', 'testnet', 'evm-testnet', or 'evm-development'." + let specFileDir = "./chain-specs/" <> n + createDirectoryIfMissing True specFileDir hdrs <- forM cids $ \cid -> do - let specFileDir = "./chain-specs/" <> n - createDirectoryIfMissing True specFileDir let specFileName = specFileDir <> "/chain-spec-" <> show cid <> ".json" encodeFile specFileName $ spec cid hdr <- queryNode cid specFileName return (cid, hdr) - T.putStrLn $ encodeToText + let payloadFileName = specFileDir <> "/payloads.json" + encodeFile payloadFileName $ [ object [ "chainId" .= cid , "blockPayloadHash" .= E._hdrPayloadHash hdr From 47561c6872ef84eb8af34afc71cb0f348ff68ad0 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 26 Jun 2025 13:06:30 -0700 Subject: [PATCH 277/378] update evm-development and evm-testnet genesis info --- cwtools/evm-genesis/Main.hs | 46 ++++++++++++------- src/Chainweb/PayloadProvider/EVM/Genesis.hs | 24 +++++----- src/Chainweb/Version/EvmDevelopment.hs | 10 ++-- .../Version/EvmDevelopmentSingleton.hs | 4 +- src/Chainweb/Version/EvmTestnet.hs | 10 ++-- 5 files changed, 54 insertions(+), 40 deletions(-) diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 596f8479c6..6699c3f3b4 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -161,7 +161,7 @@ mkRpcCtx u = do -- | -- --- System contract addresses: +-- Chainweb System contract addresses: -- -- * Chainweb-chainId system contract: -- 0x9b02c3e2dF42533e0FD166798B5A616f59DBd2cc @@ -174,6 +174,8 @@ mkRpcCtx u = do -- * ERC-20 x-chain SPV Precompile address from KIP-34 (EthDenver 2025 demo): -- 48c3b4d2757447601776837b6a85f31ef88a87bf -- Keccak256("/Chainweb/KIP-34/VERIFY/SVP/") +-- (This is a precompile in kadena-reth and not included in the genesis +-- allocations) -- baseSpecFile :: Natural @@ -230,7 +232,6 @@ baseSpecFile netId offset cid genesisTime allocs = object , "gasLimit" .= t "0x1c9c380" , "alloc" .= object ( chainwebChainIdAlloc - : xChanRedeemAlloc : eip4788Alloc : eip2935Alloc : allocs @@ -245,20 +246,19 @@ baseSpecFile netId offset cid genesisTime allocs = object i :: Natural -> Natural i = id - -- Chainweb System Contracts: Keccak256("/Chainweb/Chain/Id/") - -- + -- Chainweb System Contracts + + -- Chainweb Chain Id + -- Address: Keccak256("/Chainweb/Chain/Id/") chainwebChainIdAlloc = "0x9b02c3e2df42533e0fd166798b5a616f59dbd2cc" .= object [ "balance" .= t "0x0" , "code" .= t "0x5f545f526004601cf3" , "storage" .= object [ zero32 .= (printf "0x%064x" cid :: String) ] ] - -- Native X-Chan redeem system contract: Keccack256("/Chainweb/XChan/Redeem/") - -- TODO: cleanup and optimize the code - xChanRedeemAlloc = "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object - [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - , "code" .= t "0x346101535773ad9923c37370bcbcf00ed194506d8950848956965f9060209160c060e091610140946101609061018091609a9660e3360361014c575f359860203560f01c9360223560ea1c9860263560601c9a603a3599605a359b607a359a5f60db3560c01c9a03610145576004815f80739b02c3e2df42533e0fd166798b5a616f59dbd2cc5afa1561013e5751036101375760408593602060018580896080985f869c372086528181601f8601370191013760015afa1561013057510361012957602083918193720f3df6d732807ef1319fb7b8bb8522d0beac029082525afa1561012257510361011b5781548091038411610114575f84819482948284950190555af11561010d575f80f35b600a610157565b6009610157565b6008610157565b6007610157565b6006610157565b6005610157565b6004610157565b6003610157565b6002610157565b6001610157565b5f80fd5b5f5260205ffd" - ] + -- Native X-Chan Redeem + -- Address: Keccack256("/Chainweb/XChan/Redeem/") + -- See below -- Official Ethreum System Contracts: @@ -352,6 +352,15 @@ evmDevnetSpecFile offset cid = baseSpecFile 1789 offset cid 0x684c5d2a , "0x3492DA004098d728201fD82657f1207a6E5426bd" .= object [ "balance" .= t "0xd3c21bcecceda1000000" ] + -- Native X-Chan Redeem + -- Address: Keccack256("/Chainweb/XChan/Redeem/") + -- Redeem key address: 0xaD9923C37370BCbCF00ed194506D895084895696 + -- Redeem public key: 0x02d33118ef4a40a2bd797daf61e71488d465b1174c11ae582abfd2ce1c77d0a2a4 + , "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object + [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + , "code" .= t "0x346101535773ad9923c37370bcbcf00ed194506d8950848956965f9060209160c060e091610140946101609061018091609a9660e3360361014c575f359860203560f01c9360223560ea1c9860263560601c9a603a3599605a359b607a359a5f60db3560c01c9a03610145576004815f80739b02c3e2df42533e0fd166798b5a616f59dbd2cc5afa1561013e5751036101375760408593602060018580896080985f869c372086528181601f8601370191013760015afa1561013057510361012957602083918193720f3df6d732807ef1319fb7b8bb8522d0beac029082525afa1561012257510361011b5781548091038411610114575f84819482948284950190555af11561010d575f80f35b600a610157565b6009610157565b6008610157565b6007610157565b6006610157565b6005610157565b6004610157565b6003610157565b6002610157565b6001610157565b5f80fd5b5f5260205ffd" + ] + ] -- -------------------------------------------------------------------------- -- @@ -382,8 +391,17 @@ evmTestnetSpecFile cid = baseSpecFile 5920 20 cid 0x684c5d2a , "0xE482e4F590D4155B51F4Fc21d64823f4d7854397" .= object [ "balance" .= t "0x422ca8b0a00a425000000" ] -- additional platform funds that are separate from the faucet accounts - , "0xeC1B36992C3c7d0f7AbB8EDA43EEbC9A418c0A1e" .= object + , "0x8cb33Ecc40C31B79aE414a3D958B1b094B8993ce" .= object [ "balance" .= t "0x422ca8b0a00a425000000" ] + + -- Native X-Chan Redeem + -- Address: Keccack256("/Chainweb/XChan/Redeem/") + -- Redeem key address: 0xcFc539fe84a4Dd6a50070CEFD638B869c745C58D + -- Redeem public key: 0x02afe12e765568cc0ca395e3aad1f42108afa63cdf08562c062afc94aeecc47eb5 + , "0x49eed2ac33f09e931bd660f0168417b9614485b6" .= object + [ "balance" .= t "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + , "code" .= t "0x346101535773cfc539fe84a4dd6a50070cefd638b869c745c58d5f9060209160c060e091610140946101609061018091609a9660e3360361014c575f359860203560f01c9360223560ea1c9860263560601c9a603a3599605a359b607a359a5f60db3560c01c9a03610145576004815f80739b02c3e2df42533e0fd166798b5a616f59dbd2cc5afa1561013e5751036101375760408593602060018580896080985f869c372086528181601f8601370191013760015afa1561013057510361012957602083918193720f3df6d732807ef1319fb7b8bb8522d0beac029082525afa1561012257510361011b5781548091038411610114575f84819482948284950190555af11561010d575f80f35b600a610157565b6009610157565b6008610157565b6007610157565b6006610157565b6005610157565b6004610157565b6003610157565b6002610157565b6001610157565b5f80fd5b5f5260205ffd" + ] ] -- -------------------------------------------------------------------------- -- @@ -408,9 +426,7 @@ testnetSpecFile -> Value testnetSpecFile cid = baseSpecFile 5910 20 cid 0x684c5d2a [ - error "mainnetSpecFile: the EVM genesis allocations for mainnet are TBD" - -- TODO: Native-X-Chain System contract - -- TODO: other allocations. + error "testnetSpecFile: the EVM genesis allocations for mainnet are TBD" ] -- -------------------------------------------------------------------------- -- @@ -432,7 +448,5 @@ mainnetSpecFile mainnetSpecFile cid = baseSpecFile 5900 20 cid 0x684c5d2a [ error "mainnetSpecFile: the EVM genesis allocations for mainnet are TBD" - -- TODO: Native-X-Chain System contract - -- TODO: other allocations. ] diff --git a/src/Chainweb/PayloadProvider/EVM/Genesis.hs b/src/Chainweb/PayloadProvider/EVM/Genesis.hs index 110d65c635..6d4b0133e4 100644 --- a/src/Chainweb/PayloadProvider/EVM/Genesis.hs +++ b/src/Chainweb/PayloadProvider/EVM/Genesis.hs @@ -83,35 +83,35 @@ genesisBlocks c = go (_versionCode implicitVersion) (_chainId c) go v | v == _versionCode EvmDevelopmentSingleton = \case ChainId 0 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAA2crMmbXJmDV0kYzNgUxxb81_98dF7x7DpLpiCCncKoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoJDlc024_e97Dr4Uv0g3dG5zKSQlwAQ3St76Ji4VJwqFoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopmentPair = \case ChainId 1 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGdrdw1X0NGnQo7tjWXxmEH4FaZ2kxNUS4wjFHK32jf0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoK-TXRmht5ve-e6iGP7HB0FLBfmEd29Gn3sxSKzESCrnoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmDevelopment = \case ChainId 20 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGh5aF5SSPtKSwZF-XodozKEGk1zR-cTbejDMP79xBpgoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoH1Tf6KL6gN_qPuh05nl7XVptm1JQk1tK70UYJ0a0hleoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 21 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoHt-qa_7PESYpnnNe3DA1R3dqQyHl--cKixUD_npu2iZoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoI4lEpK-cGTnZpXaf8Ov_V1y9qT2ZXFK6p3NBOdfITowoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 22 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoP31_evK0ZUY9-eRgBIzXNggLwzmGgDotU7lq-qVwAoSoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoNY0e_PfJ4V4sPaTFkMMwUD59kRd5PguTwv2DMkdon1hoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 23 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoFgLbJIr0oBjh6uu27NuCBURIohpdDOQvLkUCz0bL2LzoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoAP4nqTDVeA4cqwOQjErrR6OaXl2c1wbrwgkrAdtJX83oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 24 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoOC5mgvpXJ_mGQ_a7iM8YJNaZKW1zeKOmu7wmhdRdf7eoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoDKfhfuRcyKtKhvzKpl5HwRBhzHSAmdhUpkiqdlhdPE2oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | v == _versionCode EvmTestnet = \case ChainId 20 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoKbDZWZrFFqWPU3bezQI5R_qPD3-3X34MMgXKzdvcXi0oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoCTHeI9_GgUd4bjjjAEald4V0u_EMqenZXs0GCcwRz6xoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 21 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoGLMUuszrb8PwGI6UataLWkRYy1gg1T5GdD259GhltLioFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoF-hwkBj12gWfQn_kYGVNBQ_SBe3ucEywFCdxkzdAlwToFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 22 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoMpjjCvbdyvJ3FfSW-YSPOGwCEEBytSjDZUEJfQLPHDWoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoDKLFnX-TIPlT3jRGPEwgGmEendfdeXw2GLb66g9UTXPoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 23 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoDowccPnGmALtoavlrHGPC7oI5adHM-YlihcTftWgyoKoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoBsnLZieDJJ2Bt0qkEHDN08t1ZB83NV0kRH60GLbTaL8oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" ChainId 24 -> f - "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoPqG_AF2PsNCMT1yzVN2ojlHJWs5JZejHkYmlkQoOKs3oFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" + "-QJfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoB3MTejex116q4W1Z7bM1BrTEkUblIp0E_ChQv1A1JNHlAAAAAAAAAAAAAAAAAAAAAAAAAAAoF3-gmUhbm6xlQHDAHKZZcZrnib-AEFCoLMcuLDI53YpoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhoFboHxcbzFWm_4NF5pLA-G5bSOAbmWytwAFiL7XjY7QhuQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAICAhAHJw4CAhGhMXSqAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiAAAAAAAAAAAhDuaygCgVugfFxvMVab_g0XmksD4bltI4BuZbK3AAWIvteNjtCGAgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKDjsMRCmPwcFJr79MiZb7kkJ65B5GSbk0yklZkbeFK4VQ" _ -> error $ "unsupported chain: " <> T.unpack (toText (_chainId c)) | otherwise = error "requested genesis block for unsupported chain" diff --git a/src/Chainweb/Version/EvmDevelopment.hs b/src/Chainweb/Version/EvmDevelopment.hs index ade1007b19..bccc2c9ebb 100644 --- a/src/Chainweb/Version/EvmDevelopment.hs +++ b/src/Chainweb/Version/EvmDevelopment.hs @@ -90,11 +90,11 @@ evmDevnet = withVersion evmDevnet $ ChainwebVersion , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider - , (unsafeChainId 20, unsafeFromText "QKbnNzpnlw4UJLGEzbqrQyUoQFU-9eztSgUiD-V0X3k") - , (unsafeChainId 21, unsafeFromText "euSvOK8XmhIUlYvxPgtul2fvvooOHHmljZQkgXGy13M") - , (unsafeChainId 22, unsafeFromText "OTUtmgGTX2vfphUw-dv0_xS1Fucd-iVO_-bvmDq0_cM") - , (unsafeChainId 23, unsafeFromText "Wth4RDfphibr5Y5kIb14zlqZlXwWlX1zJ3kmNTy-E_c") - , (unsafeChainId 24, unsafeFromText "DO__J4SPz20hmeq_bEiHmRUnoIpk1vAE40USFZeFpgo") + , (unsafeChainId 20, unsafeFromText "qYQdSfQqyGsD2F0SRHMye3a8bXupFrm8N59M1NF2bjM") + , (unsafeChainId 21, unsafeFromText "2ZeBk00dmZUS51qSw2sNmwRq4s68r0T01HbGAt-QAuI") + , (unsafeChainId 22, unsafeFromText "ymyCEiKTSa3vp8rmPMxKw2GCDQyVZ6UM1VR7ICNYNjQ") + , (unsafeChainId 23, unsafeFromText "PVN3fDMOU5hlfgjwvqjnxbo-4cS4s_Kbbk9b8LruHa8") + , (unsafeChainId 24, unsafeFromText "1rPqbYtUki_AhVTtN-vgmnZbV16ndaXxHoGjGrslY3o") -- Minimal Payload Provider , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") diff --git a/src/Chainweb/Version/EvmDevelopmentSingleton.hs b/src/Chainweb/Version/EvmDevelopmentSingleton.hs index 4e2fd3f0f7..27e4fbd264 100644 --- a/src/Chainweb/Version/EvmDevelopmentSingleton.hs +++ b/src/Chainweb/Version/EvmDevelopmentSingleton.hs @@ -77,7 +77,7 @@ evmDevnetPair = withVersion evmDevnetPair $ ChainwebVersion -- Pact Payload Provider [ (unsafeChainId 0, unsafeFromText "QzxVHFZ5go4PYd3QeAZhxP61hsVnICPw4BB9h-T3PDM") -- EVM Payload Provider - , (unsafeChainId 1, unsafeFromText "MlhkFoanwAk9wn1yl2ol7GlYq6A0pTharRhqEPH4jRg") + , (unsafeChainId 1, unsafeFromText "E8Z96yfRFs9dHYlml71XhWmZHmWzK5TRAmpXLvPj5rQ") ] } @@ -122,7 +122,7 @@ evmDevnetSingleton = withVersion evmDevnetSingleton $ ChainwebVersion [ (unsafeChainId 0, BlockCreationTime (Time (secondsToTimeSpan 0x684c5d2a))) ] , _genesisBlockPayload = onChains $ -- EVM Payload Provider - [ (unsafeChainId 0, unsafeFromText "a4zEmOvuUN4yE8dKUmLRE86TrChu9_lnZya4mEWa-Sc") ] + [ (unsafeChainId 0, unsafeFromText "ogx6zpVYf2jh1WHwYXFxCt211b1TspNymq0B7p2i3KI") ] } -- still the *default* block gas limit is set, see diff --git a/src/Chainweb/Version/EvmTestnet.hs b/src/Chainweb/Version/EvmTestnet.hs index b281192684..f536484bd3 100644 --- a/src/Chainweb/Version/EvmTestnet.hs +++ b/src/Chainweb/Version/EvmTestnet.hs @@ -90,11 +90,11 @@ evmTestnet = withVersion evmTestnet $ ChainwebVersion , (unsafeChainId 18, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") , (unsafeChainId 19, unsafeFromText "66JSEmDIl6AqWTKN29LprukaeUmK0OOd4RufVO8e6-4") -- EVM Payload Provider - , (unsafeChainId 20, unsafeFromText "alGF_--FuFzVcFCR6JbGv0DYwiHTH57maa0K6m3-7-w") - , (unsafeChainId 21, unsafeFromText "Q39zqJqrJhlL16fZR2v8QDEqAmf4dqkqLJX--prSa10") - , (unsafeChainId 22, unsafeFromText "fFvj_sA6jD2FlDbRDb25bLPSMBfwIZQeJCRcwEUFsps") - , (unsafeChainId 23, unsafeFromText "bo4xDNcaPh8iYCtWx8vMLHEQ2Q7OSFQFQpOt_Dfb2LM") - , (unsafeChainId 24, unsafeFromText "RO-CWlqduZRJLc57vj1_l_sHlqUjTmtXSAXMHvuRtwU") + , (unsafeChainId 20, unsafeFromText "WaNoJ17itOufUCumv0lj6OXC9PUFxITPlgHNc9wfPjk") + , (unsafeChainId 21, unsafeFromText "OLCBUaUhSJPIgzrZOUVLWRF2fcHR-GF3mLXrIyp-BL4") + , (unsafeChainId 22, unsafeFromText "uO7Z-HX0miQj9byBZmsGHRl1uzyp_pxcz3RWZrJH-Xw") + , (unsafeChainId 23, unsafeFromText "QwDXwhfUmv-H0ZY_V_GHxqaMc2m3gp0VBquH66SUZxg") + , (unsafeChainId 24, unsafeFromText "HMkFs_GR7Zrrgk56obxtVnR8B7UQwCffTvZ58RLtFgQ") -- Minimal Payload Provider , (unsafeChainId 25, unsafeFromText "Gt116uJVwjUEM0f07u_x8-SUFHgGpoH1xf3sfPoe0ZY") , (unsafeChainId 26, unsafeFromText "NLRP0OiqRldiZclvoKBGhv9m5wO0TrhNKaZZslZuZvw") From f8a6cf987f9207bdbdfc0ed985da3da8e341ad06 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 26 Jun 2025 19:51:31 -0700 Subject: [PATCH 278/378] Fix mining rewards for EVM provider and be more clear about precisions --- src/Chainweb/MinerReward.hs | 177 +++++++++++++++--- src/Chainweb/PayloadProvider/EVM.hs | 16 +- src/Chainweb/PayloadProvider/EVM/EngineAPI.hs | 1 + src/Chainweb/PayloadProvider/EVM/Header.hs | 2 +- 4 files changed, 165 insertions(+), 31 deletions(-) diff --git a/src/Chainweb/MinerReward.hs b/src/Chainweb/MinerReward.hs index a19c5ac0e5..d200da4f52 100644 --- a/src/Chainweb/MinerReward.hs +++ b/src/Chainweb/MinerReward.hs @@ -29,16 +29,29 @@ module Chainweb.MinerReward Stu(..) , divideStu +-- * MStu +, MStu(..) +, divideMStu + +-- * GStu +, GStu(..) +, divideGStu + -- * KDA , Kda , pattern Kda , _kda , stuToKda , kdaToStu +, mstuToKda +, kdaToMStu +, gstuToKda +, kdaToGStu -- * Miner Reward , MinerReward(..) , minerRewardKda +, minerRewardGStu , blockMinerReward , encodeMinerReward , decodeMinerReward @@ -81,16 +94,23 @@ import GHC.Stack import Numeric.Natural -- -------------------------------------------------------------------------- -- --- STU +-- Stu --- | Smallest Unit of KDA: 1 KDA == 1e12 STU. +-- | Stu: 1 KDA == 1e18 Stu. +-- +-- This is the smallest unit of KDA throughout the Chainweb ecosystem. +-- +-- It is used for balances in native X-Channels. +-- +-- It is also used to represent balances and transfered amount of native KDA in +-- the EVM. -- -- Values are non-negative and substraction can result in an arithmetic -- underflow. -- newtype Stu = Stu { _stu :: Natural } - deriving stock (Show, Eq, Ord, Generic) - deriving newtype (Enum, Num, Real, Integral, NFData) + deriving stock (Show, Generic) + deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData) instance HasTextRepresentation Stu where toText = toText . _stu @@ -115,6 +135,81 @@ instance FromJSON Stu where divideStu :: Stu -> Natural -> Stu divideStu s n = round $ s % fromIntegral n +-- -------------------------------------------------------------------------- -- +-- Mega Stu + +-- | Mega Stu: 1 KDA == 1e12 MStu. +-- +-- This is smallest unit of KDA on Pact Chains. +-- +-- Values are non-negative and substraction can result in an arithmetic +-- underflow. +-- +newtype MStu = MStu { _mstu :: Natural } + deriving stock (Show, Generic) + deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData) + +instance HasTextRepresentation MStu where + toText = toText . _mstu + fromText = fmap MStu . fromText + {-# INLINEABLE toText #-} + {-# INLINEABLE fromText #-} + +instance ToJSON MStu where + toJSON = toJSON . toText + toEncoding = toEncoding . toText + {-# INLINEABLE toJSON #-} + {-# INLINEABLE toEncoding #-} + +instance FromJSON MStu where + parseJSON = parseJsonFromText "MStu" + {-# INLINABLE parseJSON #-} + +-- | Divide a MStu by a Natural number. +-- +-- The result is rounded using bankers rounding. +-- +divideMStu :: MStu -> Natural -> MStu +divideMStu s n = round $ s % fromIntegral n + +-- -------------------------------------------------------------------------- -- +-- Giga Stu + +-- | Giga Stu: 1 KDA == 1e9 gstu. +-- +-- This is used to represent gas and withdrawals in the EVM. In particular, it +-- is used for mining rewards in the EVM. +-- +-- Values are non-negative and substraction can result in an arithmetic +-- underflow. +-- +newtype GStu = GStu { _gstu :: Natural } + deriving stock (Show, Generic) + deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData) + +instance HasTextRepresentation GStu where + toText = toText . _gstu + fromText = fmap GStu . fromText + {-# INLINEABLE toText #-} + {-# INLINEABLE fromText #-} + +instance ToJSON GStu where + toJSON = toJSON . toText + toEncoding = toEncoding . toText + {-# INLINEABLE toJSON #-} + {-# INLINEABLE toEncoding #-} + +instance FromJSON GStu where + parseJSON = parseJsonFromText "GStu" + {-# INLINABLE parseJSON #-} + +-- | Divide a GStu by a Natural number. +-- +-- The result is rounded using bankers rounding. +-- +divideGStu :: GStu -> Natural -> GStu +divideGStu s n = round $ s % fromIntegral n + -- -------------------------------------------------------------------------- -- -- KDA @@ -122,8 +217,21 @@ divideStu s n = round $ s % fromIntegral n -- -- No arithmetic conversions or operations are provided. -- --- The precision of KDA values is 1e12 decimal digits. The value is stored in --- a normalized format with the smallest possible mantissa. +-- The available precision of KDA values depends on the payload provider +-- context. +-- +-- The maximum precision across the Chainweb ecosystem is 1e18 decimal digits +-- (Stu). The minimum precision that payload providers must suppport is 1e9 +-- (GStu). +-- +-- Native X-channels use KDA with a precision of 1e18 (Stu). Fractional amounts +-- that are smaller than the precision available at the receiver can not be +-- withdrawn and remain in the channel. +-- +-- Mining rewards are calcuated in KDA with a precision of 1e9 (GStu). +-- +-- The value is stored in a normalized format with the smallest possible +-- mantissa. -- newtype Kda = Kda_ Decimal deriving stock (Show, Eq, Ord, Generic) @@ -138,61 +246,82 @@ pattern Kda { _kda } <- Kda_ _kda where | otherwise = Kda_ $ normalizeDecimal k {-# COMPLETE Kda #-} +-- ----------------------------------------------------------------------------- +-- Conversions + stuToKda :: HasCallStack => Stu -> Kda -stuToKda (Stu k) = Kda $ normalizeDecimal $ Decimal 12 (fromIntegral k) +stuToKda (Stu k) = Kda $ normalizeDecimal $ Decimal 18 (fromIntegral k) kdaToStu :: Kda -> Stu -kdaToStu (Kda { _kda = s }) = Stu $ round (s * 1e12) +kdaToStu (Kda { _kda = s }) = Stu $ round (s * 1e18) + +mstuToKda :: MStu -> Kda +mstuToKda (MStu k) = Kda $ normalizeDecimal $ Decimal 12 (fromIntegral k) + +kdaToMStu :: Kda -> MStu +kdaToMStu (Kda { _kda = s }) = MStu $ round (s * 1e12) + +gstuToKda :: GStu -> Kda +gstuToKda (GStu k) = Kda $ normalizeDecimal $ Decimal 9 (fromIntegral k) + +kdaToGStu :: Kda -> GStu +kdaToGStu (Kda { _kda = s }) = GStu $ round (s * 1e9) -- -------------------------------------------------------------------------- -- -- Miner Reward --- | Miner Reward in Stu +-- | Miner Reward in GStu -- --- The maximum miner reward is 23045230000000, which is smaller than 2^51-1. +-- The maximum miner reward is 23045230000, which is smaller than 2^51-1. -- Miner rewards can thus be represented losslessly as JSON numbers. -- -newtype MinerReward = MinerReward { _minerReward :: Stu } +newtype MinerReward = MinerReward { _minerReward :: GStu } deriving (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via JsonTextRepresentation "MinerReward" MinerReward instance HasTextRepresentation MinerReward where - toText (MinerReward (Stu n)) = toText n - fromText t = MinerReward . Stu <$> fromText t + toText (MinerReward (GStu n)) = toText n + fromText t = MinerReward . GStu <$> fromText t {-# INLINE toText #-} {-# INLINE fromText #-} minerRewardKda :: MinerReward -> Kda -minerRewardKda (MinerReward d) = stuToKda d +minerRewardKda (MinerReward d) = gstuToKda d + +minerRewardGStu :: MinerReward -> GStu +minerRewardGStu (MinerReward d) = d -- | Calculate miner reward for a block at the given height. -- -- NOTE: -- This used to compute the value as @roundTo 8 $ (_kda $ stuToKda m) / n@. --- The new caclulcation based on Stu is equivalent for 10 and 20 chains, +-- The new caclulcation based on GStu is equivalent for 10 and 20 chains, -- except for the pre-last entry in the miner rewards table, namely --- @(125538056,0.023999333). However, since this value hasen't yet been used +-- @(125538056,0.023999333). However, since this value has not yet been used -- in any network, we can still change the algorithm. -- +-- For other graphs that do not cleanly divide 10000, this value is does not +-- hold and changes in precision constitue a forking change. +-- blockMinerReward :: HasVersion => BlockHeight -> MinerReward blockMinerReward h = case M.lookupGE h minerRewards of - Nothing -> MinerReward $ Stu 0 - Just (_, s) -> MinerReward $ divideStu s n + Nothing -> MinerReward $ GStu 0 + Just (_, s) -> MinerReward $ divideGStu s n where !n = int . order $ chainGraphAt h -- | Binary encoding of mining rewards as unsigned integral number in little -- endian encoding. -- --- The maximum miner reward is 23045230000000. The miner reward can therefore be +-- The maximum miner reward is 23045230000. The miner reward can therefore be -- encoded in as Word64 value. -- encodeMinerReward :: MinerReward -> Put -encodeMinerReward (MinerReward (Stu n)) = putWord64le (int n) +encodeMinerReward (MinerReward (GStu n)) = putWord64le (int n) {-# INLINE encodeMinerReward #-} decodeMinerReward :: Get MinerReward @@ -206,7 +335,7 @@ decodeMinerReward = MinerReward . int <$> getWord64le -- -------------------------------------------------------------------------- -- -- Miner Rewards Table -type MinerRewardsTable = M.Map BlockHeight Stu +type MinerRewardsTable = M.Map BlockHeight GStu -- | Rewards table mapping 3-month periods to their rewards according to the -- calculated exponential decay over about a 120 year period (125538057 block @@ -243,8 +372,8 @@ mkMinerRewards = then rewards else error $ "hash of miner rewards table does not match expected hash" where - formatRow :: (Word64, CsvDecimal) -> (BlockHeight, Stu) - formatRow (a, b) = (BlockHeight $ int a, kdaToStu (Kda $ _csvDecimal b)) + formatRow :: (Word64, CsvDecimal) -> (BlockHeight, GStu) + formatRow (a, b) = (BlockHeight $ int a, kdaToGStu (Kda $ _csvDecimal b)) -- -------------------------------------------------------------------------- -- -- Miner Rewards File @@ -273,7 +402,7 @@ minerRewardsHash = hash . M.toAscList expectedMinerRewardsHash :: Digest SHA512 -expectedMinerRewardsHash = read "8e4fb006c5045b3baab638d16d62c952e4981a4ba473ec63620dfb54093d5104abd0be1a62ce52113575d598881fb57e84a41ec5c617e4348e270b9eacd300c9" +expectedMinerRewardsHash = read "264189d9d9181d76e2be948e3b336ab09b723cc095c059b6b3c79ff4e58b5c412c5d0b01882464a4f27fbe297b3f0dcbe3bda6ee6eb85556eeba6224ecb6458e" expectedRawMinerRewardsHash :: Digest SHA512 expectedRawMinerRewardsHash = read "903d10b06666c0d619c8a28c74c3bb0af47209002f005b12bbda7b7df1131b2072ce758c1a8148facb1506022215ea201629f38863feb285c7e66f5965498fe0" diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 964041a6e8..21923f39a0 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1088,7 +1088,11 @@ awaitNewPayload p = do ctx = _evmEngineCtx p cid = _chainId p - fees v1 = Stu $ bf * gu + -- GasUsed is demoninated in Gwei, BaseFeePerGas is in Wei. + -- Wei in Ethereum is equivalent to Stu in Chainweb. + gweiToWei x = x * 1_000_000_000 + + fees v1 = Stu $ bf * gweiToWei gu where EVM.BaseFeePerGas bf = _executionPayloadV1BaseFeePerGas v1 GasUsed gu = _executionPayloadV1GasUsed v1 @@ -1154,11 +1158,11 @@ awaitNewPayload p = do -- Check that the fees of the execution paylod match the block -- value of the response. -- FIXME FIXME FIXME - -- unless (EVM._blockValueStu (_getPayloadV4ResponseBlockValue resp) == fees v1) $ - -- throwM InconsistentNewPayloadFees - -- { _inconsistentPayloadBlockValue = _getPayloadV4ResponseBlockValue resp - -- , _inconsistentPayloadFees = fees v1 - -- } + unless (EVM._blockValueStu (_getPayloadV4ResponseBlockValue resp) == fees v1) $ + throwM InconsistentNewPayloadFees + { _inconsistentPayloadBlockValue = _getPayloadV4ResponseBlockValue resp + , _inconsistentPayloadFees = fees v1 + } -- Check that the computed block hash matches the hash from the -- response diff --git a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs index 7c317e8f26..c73c809aa3 100644 --- a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs +++ b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs @@ -532,6 +532,7 @@ data ExecutionPayloadV1 = ExecutionPayloadV1 -- ^ gasLimit: QUANTITY, 64 Bits , _executionPayloadV1GasUsed :: !E.GasUsed -- ^ gasUsed: QUANTITY, 64 Bits + -- Denoted in GWei , _executionPayloadV1Timestamp :: !E.Timestamp -- ^ timestamp: QUANTITY, 64 Bits , _executionPayloadV1ExtraData :: !E.ExtraData diff --git a/src/Chainweb/PayloadProvider/EVM/Header.hs b/src/Chainweb/PayloadProvider/EVM/Header.hs index 0549c958a4..d5c452db03 100644 --- a/src/Chainweb/PayloadProvider/EVM/Header.hs +++ b/src/Chainweb/PayloadProvider/EVM/Header.hs @@ -177,7 +177,7 @@ timestamp (Timestamp e) (Chainweb.BlockCreationTime (Time (TimeSpan (Micros t))) -- The mixHash field was repurposed in the Paris hardfork to store the previous -- RANDAO mix. --- | Base Fee Per Gas of a Block +-- | Base Fee Per Gas of a Block in Wei -- -- A natural number [cf. yellow paper 4.4.3 (44)] -- From 35691412a8af2fa90e210ba078914a499fc657b2 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 26 Jun 2025 22:00:25 -0700 Subject: [PATCH 279/378] fix miner reward tests --- test/unit/Chainweb/Test/MinerReward.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/Chainweb/Test/MinerReward.hs b/test/unit/Chainweb/Test/MinerReward.hs index a69c404489..a40f73b078 100644 --- a/test/unit/Chainweb/Test/MinerReward.hs +++ b/test/unit/Chainweb/Test/MinerReward.hs @@ -104,13 +104,13 @@ test_finalMinerReward = withVersion mainnet $ do test_minerRewardsMax :: Assertion test_minerRewardsMax = assertBool - "maximum miner reward is smaller than 1e12 * 24" - (_stu (maximum minerRewards) < 1e12 * 24) + "maximum miner reward is smaller than 1e9 * 24" + (_gstu (maximum minerRewards) < 1e9 * 24) test_minerRewardsFitWord64 :: Assertion test_minerRewardsFitWord64 = assertBool "maximum miner reward fits into Word64" - (_stu (maximum minerRewards) <= fromIntegral (maxBound @Word64)) + (_gstu (maximum minerRewards) <= fromIntegral (maxBound @Word64)) test_expectedMinerRewardsHash :: Assertion test_expectedMinerRewardsHash = assertEqual From af4579f49f0d881bff67b7e71dad53f5beb30eff Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 26 Jun 2025 23:20:05 -0700 Subject: [PATCH 280/378] disable fee consistency check again --- src/Chainweb/PayloadProvider/EVM.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 21923f39a0..3a474a1bdf 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1158,11 +1158,11 @@ awaitNewPayload p = do -- Check that the fees of the execution paylod match the block -- value of the response. -- FIXME FIXME FIXME - unless (EVM._blockValueStu (_getPayloadV4ResponseBlockValue resp) == fees v1) $ - throwM InconsistentNewPayloadFees - { _inconsistentPayloadBlockValue = _getPayloadV4ResponseBlockValue resp - , _inconsistentPayloadFees = fees v1 - } + -- unless (EVM._blockValueStu (_getPayloadV4ResponseBlockValue resp) == fees v1) $ + -- throwM InconsistentNewPayloadFees + -- { _inconsistentPayloadBlockValue = _getPayloadV4ResponseBlockValue resp + -- , _inconsistentPayloadFees = fees v1 + -- } -- Check that the computed block hash matches the hash from the -- response From 8e84203bf7e9155ea5be5b8efa8a941a6945108e Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 27 Jun 2025 00:00:06 -0700 Subject: [PATCH 281/378] add a comment --- src/Chainweb/PayloadProvider/EVM.hs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 3a474a1bdf..4bbeb379d4 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1157,7 +1157,10 @@ awaitNewPayload p = do -- Check that the fees of the execution paylod match the block -- value of the response. - -- FIXME FIXME FIXME + -- FXIME: the fee computation is wrong. I think, the block value + -- is not the base fee per gas times the gas used. It's probably + -- a lower bound, but not 100% sure. + -- -- unless (EVM._blockValueStu (_getPayloadV4ResponseBlockValue resp) == fees v1) $ -- throwM InconsistentNewPayloadFees -- { _inconsistentPayloadBlockValue = _getPayloadV4ResponseBlockValue resp From 498e3d8d511e301d81989ec97eb9cb5f87c14fe6 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 9 May 2025 11:51:40 -0400 Subject: [PATCH 282/378] Fix duplicate results from keys function --- src/Chainweb/Pact5/Backend/ChainwebPactDb.hs | 14 +++-- .../Chainweb/Test/Pact5/CheckpointerTest.hs | 55 +++++++++++++++---- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs index e56d5ce220..841c4c24d2 100644 --- a/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs @@ -82,7 +82,7 @@ import Chainweb.Pact.Backend.Utils import Chainweb.Utils (sshow, T2) import Chainweb.Utils.Serialization (runPutS) import Chainweb.Version -import Chainweb.Version.Guards (pact5Serialiser) +import Chainweb.Version.Guards import Control.Applicative import Control.Concurrent.MVar import Control.Exception.Safe @@ -105,7 +105,7 @@ import Data.HashMap.Strict qualified as HM import Data.HashMap.Strict qualified as HashMap import Data.HashSet qualified as HashSet import Data.Int -import Data.List(sort) +import Data.List(group, sort) import Data.Map.Strict qualified as M import Data.Maybe import Data.Singletons (Dict(..)) @@ -594,8 +594,14 @@ doKeys d = do Just v -> pure (v, Dict ()) Nothing -> internalDbError $ "doKeys.DModuleSources: unexpected decoding" case ordDict of - Dict () -> - return $ sort (memKeys ++ parsedKeys) + Dict () -> do + v <- view blockHandlerVersion + cid <- view blockHandlerChainId + bh <- view blockHandlerBlockHeight + if chainweb230Pact v cid bh + -- the read-cache contains duplicate keys that we need to remove. + then return $ fmap head $ group $ sort (memKeys ++ parsedKeys) + else return $ sort (memKeys ++ parsedKeys) where diff --git a/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs b/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs index c104cbcafd..ad26746d30 100644 --- a/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs +++ b/test/unit/Chainweb/Test/Pact5/CheckpointerTest.hs @@ -47,7 +47,7 @@ import Pact.Core.Builtin import Pact.Core.Evaluate (Info) import Pact.Core.Literal import Pact.Core.Names -import qualified Pact.Core.PactDbRegression as Pact.Core +import qualified Pact.Core.PactDbRegression as Pact import Pact.Core.PactValue import Pact.Core.Persistence import qualified Streaming.Prelude as Stream @@ -187,19 +187,27 @@ runBlocks -> ParentHeader -> [DbBlock (Const ())] -> IO [(BlockHeader, DbBlock Identity)] -runBlocks cp ph blks = do - ((), finishedBlks) <- Checkpointer.restoreAndSave cp (Just ph) $ traverse_ Stream.yield - [ Pact5RunnableBlock $ \db _ph startHandle -> do +runBlocks cp rewindPt blks = do + ((), finishedBlks) <- Checkpointer.restoreAndSave cp (Just rewindPt) $ traverse_ Stream.yield + [ Pact5RunnableBlock $ \db ph startHandle -> do doPact5DbTransaction db startHandle Nothing $ \txdb -> do - _ <- ignoreGas noInfo $ _pdbBeginTx txdb Transactional - blk' <- traverse (runDbAction txdb) blk - txLogs <- ignoreGas noInfo $ _pdbCommitTx txdb - bh <- blockHeaderFromTxLogs (fromJuste _ph) txLogs - return ([(bh, blk')], bh) + runBlk txdb ph (traverse (runDbAction txdb) blk) | blk <- blks ] return finishedBlks +runBlk + :: PactDb x Info + -> Maybe ParentHeader + -> IO r + -> IO ([(BlockHeader, r)], BlockHeader) +runBlk txdb ph blk = do + _ <- ignoreGas noInfo $ _pdbBeginTx txdb Transactional + blk' <- blk + txLogs <- ignoreGas noInfo $ _pdbCommitTx txdb + bh <- blockHeaderFromTxLogs (fromJuste ph) txLogs + return ([(bh, blk')], bh) + -- Check that a block's result at the time it was added to the checkpointer -- is consistent with us executing that block with `readFrom` assertBlock :: Checkpointer GenericLogger -> ParentHeader -> (BlockHeader, DbBlock Identity) -> IO () @@ -234,7 +242,7 @@ tests = testGroup "Pact5 Checkpointer tests" ((), _handle) <- (throwIfNoHistory =<<) $ Checkpointer.readFrom cp Nothing Pact5T $ \db blockHandle -> do doPact5DbTransaction db blockHandle Nothing $ \txdb -> - Pact.Core.runPactDbRegression txdb + Pact.runPactDbRegression txdb return () , withResourceT (liftIO . initCheckpointer testVer cid =<< withTempSQLiteResource) $ \cpIO -> testProperty "readFrom with linear block history is valid" $ withTests 1000 $ property $ do @@ -253,6 +261,33 @@ tests = testGroup "Pact5 Checkpointer tests" -- gives the same results forM_ finishedBlocksWithParents $ \(ph, block) -> do assertBlock cp ph block + , withResourceT (liftIO . initCheckpointer testVer cid =<< withTempSQLiteResource) $ \cpIO -> + testCase "reading doesn't duplicate keys results" $ do + cp <- cpIO + _ <- Checkpointer.restoreAndSave cp Nothing $ Stream.yield $ Pact5RunnableBlock $ \_ _ hndl -> + return (((), gh), hndl) + _ <- Checkpointer.restoreAndSave cp (Just $ ParentHeader gh) $ do + let coinTable = TableName "coin-table" (ModuleName "coin" Nothing) + let domain = DUserTables coinTable + Stream.yield $ Pact5RunnableBlock $ \db ph hndl -> + doPact5DbTransaction db hndl Nothing $ \txdb -> + runBlk txdb ph $ do + ignoreGas noInfo $ _pdbCreateUserTable txdb + coinTable + ignoreGas noInfo $ _pdbWrite txdb Insert + domain + (RowKey "k") + (RowData $ Map.singleton (Field "f") (PString "value")) + Stream.yield $ Pact5RunnableBlock $ \db ph hndl -> + doPact5DbTransaction db hndl Nothing $ \txdb -> + runBlk txdb ph $ do + _ <- ignoreGas noInfo $ _pdbRead txdb + domain + (RowKey "k") + keys <- ignoreGas noInfo $ _pdbKeys txdb domain + assertEqual "keys after reading" [RowKey "k"] keys + + return () ] testVer :: ChainwebVersion From e730ecf60b66b1b491ec98856558dca9f0413e7d Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 2 Jul 2025 11:09:34 -0400 Subject: [PATCH 283/378] Use unsafeHead to avoid x-partial warning --- src/Chainweb/Pact5/Backend/ChainwebPactDb.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs index 841c4c24d2..e6baa11dd1 100644 --- a/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact5/Backend/ChainwebPactDb.hs @@ -79,7 +79,7 @@ import Chainweb.Logger import Chainweb.Pact.Backend.InMemDb qualified as InMemDb import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils -import Chainweb.Utils (sshow, T2) +import Chainweb.Utils (sshow, unsafeHead, T2) import Chainweb.Utils.Serialization (runPutS) import Chainweb.Version import Chainweb.Version.Guards @@ -600,7 +600,7 @@ doKeys d = do bh <- view blockHandlerBlockHeight if chainweb230Pact v cid bh -- the read-cache contains duplicate keys that we need to remove. - then return $ fmap head $ group $ sort (memKeys ++ parsedKeys) + then return $ fmap (unsafeHead "doKeys") $ group $ sort (memKeys ++ parsedKeys) else return $ sort (memKeys ++ parsedKeys) where From 7ff46302ba845b62b500d8995b20739e469114a2 Mon Sep 17 00:00:00 2001 From: June <38109440+DevopsGoth@users.noreply.github.com> Date: Fri, 18 Apr 2025 18:11:17 -0600 Subject: [PATCH 284/378] Update release.yml ghc regex --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1fdaecc578..dc0009ed24 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,7 +84,7 @@ jobs: - name: Get ghc version id: set-ghc-version run: | - VERSION_GHC=$(grep -Po '(?<="ghc": \[")(\d\.\d\.\d)' .github/workflows/applications.yml | head -1) + VERSION_GHC=$(grep -Po '(?<="ghc": ")(\d\.\d\.\d)' .github/workflows/applications.yml | head -1) if [[ -z $VERSION_GHC ]]; then echo "Unable to get GHC version from chainweb node build" exit 1 From aabf22f0fd64d4ae505a4c6966b7713dac338b07 Mon Sep 17 00:00:00 2001 From: June <38109440+DevopsGoth@users.noreply.github.com> Date: Fri, 18 Apr 2025 18:17:49 -0600 Subject: [PATCH 285/378] Update applications.yml --- .github/workflows/applications.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index 761447cf50..1c1a25d77b 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -134,13 +134,14 @@ jobs: if "${{ github.event_name == 'schedule' }}" == "true"; then MATRIX="$(jq -c '.' < Date: Wed, 2 Jul 2025 12:57:02 -0700 Subject: [PATCH 286/378] add fork variant for release 2.30 --- src/Chainweb/Version.hs | 3 +++ src/Chainweb/Version/Guards.hs | 4 ++++ src/Chainweb/Version/Mainnet.hs | 5 +++-- src/Chainweb/Version/RecapDevelopment.hs | 1 + src/Chainweb/Version/Testnet04.hs | 5 +++-- test/lib/Chainweb/Test/TestVersions.hs | 4 +++- 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 7edc6e6964..0f65fed053 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -233,6 +233,7 @@ data Fork | Chainweb228Pact | Chainweb229Pact | Chainweb230Pact + | Chainweb231Pact -- always add new forks at the end, not in the middle of the constructors. deriving stock (Bounded, Generic, Eq, Enum, Ord, Show) deriving anyclass (NFData, Hashable) @@ -273,6 +274,7 @@ instance HasTextRepresentation Fork where toText Chainweb228Pact = "chainweb228Pact" toText Chainweb229Pact = "chainweb229Pact" toText Chainweb230Pact = "chainweb230Pact" + toText Chainweb231Pact = "chainweb231Pact" fromText "slowEpoch" = return SlowEpoch fromText "vuln797Fix" = return Vuln797Fix @@ -309,6 +311,7 @@ instance HasTextRepresentation Fork where fromText "chainweb228Pact" = return Chainweb228Pact fromText "chainweb229Pact" = return Chainweb229Pact fromText "chainweb230Pact" = return Chainweb230Pact + fromText "chainweb231Pact" = return Chainweb231Pact fromText t = throwM . TextFormatException $ "Unknown Chainweb fork: " <> t instance ToJSON Fork where diff --git a/src/Chainweb/Version/Guards.hs b/src/Chainweb/Version/Guards.hs index 1601f79683..b01bfe4d84 100644 --- a/src/Chainweb/Version/Guards.hs +++ b/src/Chainweb/Version/Guards.hs @@ -51,6 +51,7 @@ module Chainweb.Version.Guards , chainweb228Pact , chainweb229Pact , chainweb230Pact + , chainweb231Pact , pact5 , pact44NewTrans , pact4ParserVersion @@ -280,6 +281,9 @@ chainweb229Pact = checkFork atOrAfter Chainweb229Pact chainweb230Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool chainweb230Pact = checkFork atOrAfter Chainweb230Pact +chainweb231Pact :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +chainweb231Pact = checkFork atOrAfter Chainweb231Pact + pact5Serialiser :: ChainwebVersion -> ChainId -> BlockHeight -> Pact5.PactSerialise Pact5.CoreBuiltin Pact5.LineInfo pact5Serialiser v cid bh | chainweb228Pact v cid bh = Pact5.serialisePact_lineinfo_pact51 diff --git a/src/Chainweb/Version/Mainnet.hs b/src/Chainweb/Version/Mainnet.hs index f76c6ae89c..d7677a37e2 100644 --- a/src/Chainweb/Version/Mainnet.hs +++ b/src/Chainweb/Version/Mainnet.hs @@ -154,7 +154,8 @@ mainnet = ChainwebVersion Pact5Fork -> AllChains (ForkAtBlockHeight $ BlockHeight 5_555_698) -- 2025-02-10 00:00:00+00:00 Chainweb228Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 5_659_280) -- 2025-03-18 00:00:00+00:00 Chainweb229Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 5_785_923) -- 2025-05-01 00:00:00+00:00 - Chainweb230Pact -> AllChains ForkNever + Chainweb230Pact -> AllChains (ForkAtBlockHeight $ BlockHeight 6_027_616) -- 2025-07-24 00:00:00+00:00 + Chainweb231Pact -> AllChains ForkNever , _versionGraphs = (to20ChainsMainnet, twentyChainGraph) `Above` @@ -226,5 +227,5 @@ mainnet = ChainwebVersion , (unsafeChainId 9, HM.fromList [((BlockHeight 4594049, TxBlockIdx 0), Gas 69_092)]) ] } - , _versionServiceDate = Just "2025-07-23T00:00:00Z" + , _versionServiceDate = Just "2025-10-15T00:00:00Z" } diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index 57e9d92425..6b51343051 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -79,6 +79,7 @@ recapDevnet = ChainwebVersion Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 650 Chainweb229Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 660 Chainweb230Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 680 + Chainweb231Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 690 , _versionUpgrades = foldr (chainZip HM.union) (AllChains mempty) [ indexByForkHeights recapDevnet diff --git a/src/Chainweb/Version/Testnet04.hs b/src/Chainweb/Version/Testnet04.hs index e0a923457b..1a506cb829 100644 --- a/src/Chainweb/Version/Testnet04.hs +++ b/src/Chainweb/Version/Testnet04.hs @@ -134,7 +134,8 @@ testnet04 = ChainwebVersion Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_058_738 -- 2025-02-05 12:00:00+00:00 Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_155_146 -- 2025-03-11 00:00:00+00:00 Chainweb229Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_300_466 -- 2025-04-30 12:00:00+00:00 - Chainweb230Pact -> AllChains ForkNever + Chainweb230Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 5_542_190 -- 2025-07-23 12:00:00+00:00 + Chainweb231Pact -> AllChains ForkNever , _versionGraphs = (to20ChainsTestnet, twentyChainGraph) `Above` @@ -194,5 +195,5 @@ testnet04 = ChainwebVersion , (unsafeChainId 2, HM.fromList [((BlockHeight 4108311, TxBlockIdx 0), Gas 65_130)]) ] } - , _versionServiceDate = Just "2025-07-23T00:00:00Z" + , _versionServiceDate = Just "2025-10-15T00:00:00Z" } diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index 82d6e22ad1..7946b5deae 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -320,6 +320,7 @@ slowForks = tabulateHashMap \case Chainweb228Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 145) Chainweb229Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 150) Chainweb230Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 155) + Chainweb231Pact -> AllChains $ ForkAtBlockHeight (BlockHeight 160) -- | A set of fork heights which are relatively fast, but not fast enough to break anything. fastForks :: HashMap Fork (ChainMap ForkHeight) @@ -358,7 +359,8 @@ fastForks = tabulateHashMap $ \case Pact5Fork -> AllChains $ ForkAtBlockHeight $ BlockHeight 46 Chainweb228Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 48 Chainweb229Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 50 - Chainweb230Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 54 + Chainweb230Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 52 + Chainweb231Pact -> AllChains $ ForkAtBlockHeight $ BlockHeight 54 -- | CPM version (see `cpmTestVersion`) with forks and upgrades slowly enabled. slowForkingCpmTestVersion :: ChainGraph -> ChainwebVersion From 3478d5db38c479d9e38cfc358c2ea47c3684f730 Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 2 Jul 2025 13:21:54 -0700 Subject: [PATCH 287/378] update cabal package versions for release 2.30 --- chainweb.cabal | 2 +- cwtools/cwtools.cabal | 2 +- node/chainweb-node.cabal | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index 5400db7abf..1d02d91c88 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -1,7 +1,7 @@ cabal-version: 3.8 name: chainweb -version: 2.29.1 +version: 2.30 synopsis: A Proof-of-Work Parallel-Chain Architecture for Massive Throughput description: A Proof-of-Work Parallel-Chain Architecture for Massive Throughput. homepage: https://github.com/kadena-io/chainweb diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index 12d48d7340..5b88f2b3fe 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -1,7 +1,7 @@ cabal-version: 3.8 name: cwtools -version: 2.29.1 +version: 2.30 synopsis: A collection of various tools for Chainweb users and developers. description: A collection of various tools for Chainweb users and developers. homepage: https://github.com/kadena-io/chainweb diff --git a/node/chainweb-node.cabal b/node/chainweb-node.cabal index fb5f5aa8e5..7dad3df839 100644 --- a/node/chainweb-node.cabal +++ b/node/chainweb-node.cabal @@ -1,7 +1,7 @@ cabal-version: 3.8 name: chainweb-node -version: 2.29.1 +version: 2.30 synopsis: A Proof-of-Work Parallel-Chain Architecture for Massive Throughput description: A Proof-of-Work Parallel-Chain Architecture for Massive Throughput. homepage: https://github.com/kadena-io/chainweb From 91c4a6e5f4e93b30ea951dda4016a8aa60d6cb67 Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 2 Jul 2025 13:22:23 -0700 Subject: [PATCH 288/378] update CHANGELOG for release 2.30 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb59162eb4..aab1ff14ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 2.30 (FIXME) +This is a major version update. This release replaces all previous versions. + +Any prior version will stop working on **2025-07-23T00:00:00Z**. Node administrators must +upgrade to this version before that date. The 2.30 feature upgrade will +occur at block height 6027616 which is estimated to be mined at **2025-07-24T00:00:00Z**. + +### Changes +- Upgrade to Pact 5.3 [`5c0d473`](https://github.com/kadena-io/chainweb-node/commit/5c0d473e347dbc2fc56827117de972ca89aed6f8) +- Fix duplicate results from `keys` function [`498e3d8`](https://github.com/kadena-io/chainweb-node/commit/498e3d8d511e301d81989ec97eb9cb5f87c14fe6) + ## 2.29.1 (2025-06-17) This is a minor point release. Upgrading is **strongly recommended**. From e0acda06fcda6217e2241b728f266199b39bee42 Mon Sep 17 00:00:00 2001 From: chessai Date: Wed, 2 Jul 2025 13:24:54 -0700 Subject: [PATCH 289/378] update cabal freeze file for release 2.30 --- CHANGELOG.md | 2 +- cabal.project.freeze | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aab1ff14ce..8bb775b4d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.30 (FIXME) +## 2.30 (2025-07-07) This is a major version update. This release replaces all previous versions. Any prior version will stop working on **2025-07-23T00:00:00Z**. Node administrators must diff --git a/cabal.project.freeze b/cabal.project.freeze index 07ecc744cc..182ea5d541 100644 --- a/cabal.project.freeze +++ b/cabal.project.freeze @@ -403,4 +403,4 @@ constraints: any.Cabal ==3.12.1.0, any.zigzag ==0.1.0.0, any.zip-archive ==0.4.3.2, any.zlib ==0.7.1.0 -index-state: hackage.haskell.org 2025-06-17T19:59:02Z +index-state: hackage.haskell.org 2025-07-02T20:17:36Z From 1e620baa08a1d85b14600b468878960f399e77ea Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 11 Jul 2025 16:05:41 -0400 Subject: [PATCH 290/378] Copy tests into artifacts Change-Id: Id00000006b3b9c494f29282bc0670a5677d76ec2 --- .github/workflows/applications.yml | 69 +++++++++++++++--------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index 3062d89662..90c84ccabb 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -359,29 +359,28 @@ jobs: - name: Build chainweb library run: cabal build --ghc-options=-j2 lib:chainweb # # Temporarily disabled on lars/pp/* branches - # - name: Build chainweb applications - # run: | - # cabal build -j2 --ghc-options=-j2 \ - # chainweb:bench:bench \ - # exe:b64 \ - # exe:calculate-release \ - # exe:compact \ - # exe:db-checksum \ - # exe:ea \ - # exe:genconf \ - # exe:header-dump \ - # exe:known-graphs \ - # exe:pact-diff \ - # exe:run-nodes \ - # exe:tx-list \ - # test:chainweb-tests \ - # test:compaction-tests \ - # test:multi-node-network-tests \ - # test:remote-tests \ - # test:chainweb-storage-tests - name: Build chainweb applications run: | - cabal build -j2 --ghc-options=-j2 exe:evm-genesis + cabal build -j2 --ghc-options=-j2 \ + exe:b64 \ + exe:calculate-release \ + exe:compact \ + exe:ea \ + exe:genconf \ + exe:known-graphs \ + exe:pact-diff \ + test:chainweb-tests \ + test:multi-node-network-tests \ + test:remote-tests \ + test:chainweb-storage-tests \ + exe:evm-genesis + # TODO: PP + # exe:tx-list \ + # exe:header-dump \ + # exe:run-nodes \ + # exe:db-checksum \ + # chainweb:bench:bench \ + # test:compaction-tests \ - name: Build chainweb-node application run: cabal build -j2 --ghc-options=-j2 chainweb-node:exe:chainweb-node @@ -408,20 +407,15 @@ jobs: mkdir -p artifacts/chainweb cp $(cabal list-bin chainweb-node) artifacts/chainweb cp $(cabal list-bin evm-genesis) artifacts/chainweb - # cp $(cabal list-bin chainweb-storage-tests) artifacts/chainweb - # cp $(cabal list-bin chainweb-tests) artifacts/chainweb - # cp $(cabal list-bin compact) artifacts/chainweb - # cp $(cabal list-bin compaction-tests) artifacts/chainweb - # cp $(cabal list-bin db-checksum) artifacts/chainweb - # cp $(cabal list-bin ea) artifacts/chainweb - # cp $(cabal list-bin genconf) artifacts/chainweb - # cp $(cabal list-bin header-dump) artifacts/chainweb - # cp $(cabal list-bin known-graphs) artifacts/chainweb - # cp $(cabal list-bin multi-node-network-tests) artifacts/chainweb - # cp $(cabal list-bin pact-diff) artifacts/chainweb - # cp $(cabal list-bin remote-tests) artifacts/chainweb - # cp $(cabal list-bin run-nodes) artifacts/chainweb - # cp $(cabal list-bin tx-list) artifacts/chainweb + cp $(cabal list-bin chainweb-storage-tests) artifacts/chainweb + cp $(cabal list-bin chainweb-tests) artifacts/chainweb + cp $(cabal list-bin compact) artifacts/chainweb + cp $(cabal list-bin ea) artifacts/chainweb + cp $(cabal list-bin genconf) artifacts/chainweb + cp $(cabal list-bin known-graphs) artifacts/chainweb + cp $(cabal list-bin multi-node-network-tests) artifacts/chainweb + cp $(cabal list-bin pact-diff) artifacts/chainweb + cp $(cabal list-bin remote-tests) artifacts/chainweb cp README.md artifacts/chainweb cp CHANGELOG.md artifacts/chainweb cp LICENSE artifacts/chainweb @@ -429,6 +423,11 @@ jobs: cp cabal.project artifacts/chainweb cp cabal.project.local artifacts/chainweb cp cabal.project.freeze artifacts/chainweb + # cp $(cabal list-bin tx-list) artifacts/chainweb + # cp $(cabal list-bin header-dump) artifacts/chainweb + # cp $(cabal list-bin run-nodes) artifacts/chainweb + # cp $(cabal list-bin compaction-tests) artifacts/chainweb + # cp $(cabal list-bin db-checksum) artifacts/chainweb - name: Create artifacts archive run: | echo "tar -C ./artifacts/ -czf $ARTIFACTS_ARCHIVE chainweb" From 0c1f4ce78cebe9486c03d01c8523bbd313414253 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 9 Jul 2025 17:16:10 -0400 Subject: [PATCH 291/378] Add cut storage test Change-Id: Id0000000e4c25130b32b06ca2edca4f0908a0c94 --- test/lib/Chainweb/Test/MultiNode.hs | 50 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index da8620e0af..6cfeea2e5f 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -78,7 +78,7 @@ import Chainweb.WebBlockHeaderDB import Control.Concurrent.Async import Control.DeepSeq import Control.Exception -import Control.Lens (set, view, (^?!), ix) +import Control.Lens (set, view, (^?!), ix, (^.)) import Control.Monad import Data.Aeson (ToJSON, object, (.=)) import Data.ByteString (ByteString) @@ -110,6 +110,9 @@ import System.LogLevel import System.Timeout import Test.Tasty.HUnit import Chainweb.Miner.Pact +import Chainweb.Storage.Table (IterableTable(withTableIterator), Casify) +import Chainweb.Cut.CutHashes +import Data.Function -- -------------------------------------------------------------------------- -- -- * Configuration @@ -216,10 +219,11 @@ harvestConsensusState -> MVar ConsensusState -> Int -> StartedChainweb logger + -> RocksDb -> IO () -harvestConsensusState _ _ _ (Replayed _ _) = +harvestConsensusState _ _ _ (Replayed _ _) _ = error "harvestConsensusState: doesn't work when replaying, replays don't do consensus" -harvestConsensusState logger stateVar nid (StartedChainweb cw) = do +harvestConsensusState logger stateVar nid (StartedChainweb cw) rdb = do runChainweb cw (\_ -> return ()) `finally` do logFunctionText logger Info "write sample data" modifyMVar_ stateVar $ @@ -227,6 +231,8 @@ harvestConsensusState logger stateVar nid (StartedChainweb cw) = do nid (view (chainwebCutResources . cutResCutDb . cutDbWebBlockHeaderDb) cw) (view (chainwebCutResources . cutResCutDb) cw) + (cutHashesTable rdb) + logFunctionText logger Info "assert on cut count" logFunctionText logger Info "shutdown node" multiNode @@ -238,7 +244,7 @@ multiNode -> FilePath -> Int -- ^ Unique node id. Node id 0 is used for the bootstrap node - -> (forall logger. Int -> StartedChainweb logger -> IO ()) + -> (forall logger. Int -> StartedChainweb logger -> RocksDb -> IO ()) -> IO () multiNode loglevel write bootstrapPeerInfoVar conf rdb pactDbDir nid inner = do withSystemTempDirectory "multiNode-backup-dir" $ \backupTmpDir -> @@ -248,7 +254,7 @@ multiNode loglevel write bootstrapPeerInfoVar conf rdb pactDbDir nid inner = do when (nid == 0) $ putMVar bootstrapPeerInfoVar $ view (chainwebPeer . peerResPeer . peerInfo) cw' Replayed _ _ -> return () - inner nid cw + inner nid cw namespacedNodeRocksDb where logger :: GenericLogger logger = addLabel ("node", toText nid) $ genericLogger loglevel T.putStrLn @@ -266,7 +272,7 @@ runNodes -- ^ number of nodes -> RocksDb -> FilePath - -> (forall logger. Int -> StartedChainweb logger -> IO ()) + -> (forall logger. Int -> StartedChainweb logger -> RocksDb -> IO ()) -> IO () runNodes loglevel write baseConf n rdb pactDbDir inner = do -- NOTE: pact is enabled until we have a good way to disable it globally in @@ -304,7 +310,7 @@ runNodesForSeconds -- ^ test duration in seconds -> RocksDb -> FilePath - -> (forall logger. Int -> StartedChainweb logger -> IO ()) + -> (forall logger. Int -> StartedChainweb logger -> RocksDb -> IO ()) -> IO () runNodesForSeconds loglevel write baseConf n (Seconds seconds) rdb pactDbDir inner = do void $ timeout (int seconds * 1_000_000) @@ -413,7 +419,7 @@ pactImportTest logLevel n rocksDb pactDir step = do -- on the current cut. This is fine because we ultimately just want -- to make sure that we are making progress (i.e, new blocks). stateVar <- newMVar emptyConsensusState - let ct :: Int -> StartedChainweb logger -> IO () + let ct :: Int -> StartedChainweb logger -> RocksDb -> IO () ct = harvestConsensusState logger stateVar runNodesForSeconds logLevel logFun (multiConfig n) n 10 rocksDb pactDir ct consensusState <- swapMVar stateVar emptyConsensusState @@ -591,14 +597,14 @@ replayTest loglevel n rdb pactDbDir step = do (multiConfig n & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight) & set configOnlySync True) - n (Seconds 20) rdb pactDbDir $ \nid cw -> case cw of + n (Seconds 20) rdb pactDbDir $ \nid cw _ -> case cw of Replayed l (Just u) -> do writeIORef firstReplayCompleteRef True _ <- flip HM.traverseWithKey (_cutMap l) $ \cid bh -> assertEqual ("lower chain " <> sshow cid) replayInitialHeight (view blockHeight bh) -- TODO: this is flaky, presumably because a node's cutdb -- is not being cancelled synchronously enough - assertEqual "upper cut" (_stateCutMap state2 HM.! nid) u + assertEqual "upper cut" (snd $ _stateCutMap state2 HM.! nid) u _ <- flip HM.traverseWithKey (_cutMap u) $ \cid bh -> assertGe ("upper chain " <> sshow cid) (Actual $ view blockHeight bh) (Expected replayInitialHeight) return () @@ -613,7 +619,7 @@ replayTest loglevel n rdb pactDbDir step = do & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight) & set (configCuts . cutFastForwardBlockHeightLimit) (Just fastForwardHeight) & set configOnlySync True) - n (Seconds 20) rdb pactDbDir $ \_ cw -> case cw of + n (Seconds 20) rdb pactDbDir $ \_ cw _ -> case cw of Replayed l (Just u) -> do writeIORef secondReplayCompleteRef True _ <- flip HM.traverseWithKey (_cutMap l) $ \cid bh -> @@ -665,6 +671,10 @@ test loglevel n seconds rdb pactDbDir step = do ] (assertGe "number of blocks") (Actual $ _statBlockCount stats) (Expected $ _statBlockCount l) + (assertLe "max stored cut count") + (Actual $ _statMaxCutCount stats) + (Expected $ ceiling (avgBlockHeightAtCutHeight $ _statMaxHeight stats) + + int (diameterAtCutHeight (_statMaxHeight stats))) (assertGe "maximum cut height") (Actual $ _statMaxHeight stats) (Expected $ _statMaxHeight l) (assertGe "minimum cut height") (Actual $ _statMinHeight stats) (Expected $ _statMinHeight l) (assertGe "median cut height") (Actual $ _statMedHeight stats) (Expected $ _statMedHeight l) @@ -689,8 +699,8 @@ data ConsensusState = ConsensusState -- ^ for short tests this is fine. For larger test runs we should -- use HyperLogLog+ - , _stateCutMap :: !(HM.HashMap Int Cut) - -- ^ Node Id map + , _stateCutMap :: !(HM.HashMap Int (Int, Cut)) + -- ^ The latest cut in each node, as well as the number of cuts stored } deriving (Show, Generic, NFData) @@ -703,16 +713,18 @@ sampleConsensusState -- ^ node Id -> WebBlockHeaderDb -> CutDb + -> Casify RocksDbTable CutHashes -> ConsensusState -> IO ConsensusState -sampleConsensusState nid bhdb cutdb s = do +sampleConsensusState nid bhdb cutdb ct s = do + numStoredCuts <- withTableIterator ct $ \i -> iterToEntryStream i & S.length_ !hashes' <- webEntries bhdb $ S.fold_ (flip HS.insert) (_stateBlockHashes s) id . S.map (view blockHash) !c <- _cut cutdb return $! s { _stateBlockHashes = hashes' - , _stateCutMap = HM.insert nid c (_stateCutMap s) + , _stateCutMap = HM.insert nid (numStoredCuts, c) (_stateCutMap s) } data Stats = Stats @@ -721,6 +733,7 @@ data Stats = Stats , _statMinHeight :: !CutHeight , _statMedHeight :: !CutHeight , _statAvgHeight :: !Double + , _statMaxCutCount :: Int } deriving (Show, Eq, Ord, Generic, ToJSON) @@ -733,11 +746,12 @@ consensusStateSummary s , _statMinHeight = minHeight , _statMedHeight = medHeight , _statAvgHeight = avgHeight + , _statMaxCutCount = fst $ maximumBy (compare `on` fst) $ _stateCutMap s } where - cutHeights = _cutHeight <$> _stateCutMap s + cutHeights = _cutHeight . snd <$> _stateCutMap s graph = chainGraphAt - $ maximum . concatMap chainHeights + $ maximum . concatMap (chainHeights . snd) $ toList $ _stateCutMap s hashCount = HS.size (_stateBlockHashes s) - int (order graph) @@ -766,6 +780,7 @@ lowerStats seconds = Stats , _statMinHeight = round $ ebc * 0.1 -- temporarily, was 0.3 , _statMedHeight = round $ ebc * 0.5 , _statAvgHeight = ebc * 0.5 + , _statMaxCutCount = undefined } where ebc = expectedBlockCount seconds @@ -778,6 +793,7 @@ upperStats seconds = Stats , _statMinHeight = round $ ech * 1.4 , _statMedHeight = round $ ech * 1.4 , _statAvgHeight = ech * 1.4 + , _statMaxCutCount = undefined } where ebc = expectedBlockCount seconds From 5acb69d41888733788fec8e1f20aa0c9adcd7b5d Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 11 Jul 2025 15:58:57 -0400 Subject: [PATCH 292/378] Simpler limited cut storage We're storing way too many cuts still. This change stores one cut per block height, as well as storing the current cut when the node shuts down. Change-Id: Id0000000757c6f8601e01b85f9b0c65de9ebb62e --- src/Chainweb/CutDB.hs | 188 +++++++++++++++++++------------ src/Data/PQueue.hs | 10 ++ test/unit/Chainweb/Test/CutDB.hs | 3 +- 3 files changed, 124 insertions(+), 77 deletions(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index bf80149556..a9576ff0c2 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -39,7 +39,6 @@ module Chainweb.CutDB , cutDbParamsInitialHeightLimit , cutDbParamsFetchTimeout , cutDbParamsAvgBlockHeightPruningDepth -, cutDbParamsPruningFrequency , cutDbParamsReadOnly , defaultCutDbParams , farAheadThreshold @@ -125,7 +124,6 @@ import Prelude hiding (lookup) import Streaming.Prelude qualified as S import System.LogLevel -import System.Random.MWC qualified as Prob import System.Timeout -- internal modules @@ -179,8 +177,6 @@ data CutDbParams = CutDbParams , _cutDbParamsAvgBlockHeightPruningDepth :: !BlockHeight -- ^ How many block heights' worth of cuts should we keep around? -- (how far back do we expect that a fork can happen) - , _cutDbParamsPruningFrequency :: !BlockHeight - -- ^ After how many blocks do we prune cuts (on average)? , _cutDbParamsReadOnly :: !Bool -- ^ Should the cut store be read-only? -- Enabled during replay-only mode. @@ -199,13 +195,23 @@ defaultCutDbParams ft = CutDbParams , _cutDbParamsFetchTimeout = ft , _cutDbParamsInitialHeightLimit = Nothing , _cutDbParamsFastForwardHeightLimit = Nothing + -- really this is 2500 minutes, or ~1.7 days, which gives us some time to + -- recover from long-lived forks. , _cutDbParamsAvgBlockHeightPruningDepth = 5000 - , _cutDbParamsPruningFrequency = 10000 , _cutDbParamsReadOnly = False } where g = chainGraphAt (maxBound @BlockHeight) +newtype CutPruningState = CutPruningState + { cutPruningStateLatestWrittenHeight :: BlockHeight + } + +initialCutPruningState :: Cut -> CutPruningState +initialCutPruningState initialCut = CutPruningState + { cutPruningStateLatestWrittenHeight = view cutMaxHeight initialCut + } + -- | We ignore cuts that are two far ahead of the current best cut that we have. -- There are two reasons for this: -- @@ -384,12 +390,10 @@ pruneCuts logfun conf curAvgBlockHeight cutHashesStore = do let pruneCutHeight = avgCutHeightAt (curAvgBlockHeight - min curAvgBlockHeight avgBlockHeightPruningDepth) logfun @T.Text Info $ "pruning CutDB before cut height " <> T.pack (show pruneCutHeight) + -- deleteRange is constant time in rocksdb, it just inserts a tombstone. + -- either way, we will use constant space for cuts. deleteRangeRocksDb (unCasify cutHashesStore) (Nothing, Just (pruneCutHeight, 0, maxBound :: CutId)) - -- compactRangeRocksDb waits for compaction to complete which takes a while - void $ async $ - compactRangeRocksDb (unCasify cutHashesStore) - (Nothing, Just (pruneCutHeight, 0, maxBound :: CutId)) cutDbQueueSize :: CutDb -> IO Natural cutDbQueueSize = pQueueSize . _cutDbQueue @@ -436,11 +440,13 @@ startCutDb config logger headerStore providers cutHashesStore = mask_ $ do -- intentionally don't delete up to recovery cut, the initial cut could be useful later (Just $ over _1 succ $ casKey $ cutToCutHashes Nothing initialCut, Nothing) cutVar <- newTVarIO recoveryCut + -- use the recovery cut for the pruning state, so that we write cuts more quickly after recovering + cutPruningStateVar <- newTVarIO $ initialCutPruningState recoveryCut c <- readTVarIO cutVar logg Info $ T.unlines $ "got initial cut:" : [" " <> block | block <- cutToTextShort c] queue <- newEmptyPQueue - cutAsync <- asyncWithUnmask $ \u -> u $ processor queue cutVar + cutAsync <- asyncWithUnmask $ \u -> u $ processor queue cutVar cutPruningStateVar return CutDb { _cutDbCut = cutVar , _cutDbQueue = queue @@ -457,9 +463,9 @@ startCutDb config logger headerStore providers cutHashesStore = mask_ $ do logg = logFunctionText logger wbhdb = _webBlockHeaderStoreCas headerStore - processor :: PQueue (Down CutHashes) -> TVar Cut -> IO () - processor queue cutVar = runForever (logFunction logger) "CutDB" $ - processCuts config (logFunction logger) headerStore providers cutHashesStore queue cutVar + processor :: PQueue (Down CutHashes) -> TVar Cut -> TVar CutPruningState -> IO () + processor queue cutVar cutPruningStateVar = runForever (logFunction logger) "CutDB" $ + processCuts config (logFunction logger) headerStore providers cutHashesStore queue cutVar cutPruningStateVar readInitialCut :: IO Cut readInitialCut = do @@ -646,74 +652,106 @@ processCuts -> Casify RocksDbTable CutHashes -> PQueue (Down CutHashes) -> TVar Cut + -> TVar CutPruningState -> IO () -processCuts conf logFun headerStore providers cutHashesStore queue cutVar = do - rng <- Prob.createSystemRandom - queueToStream - & S.chain (\c -> loggCutId logFun Debug c "start processing") - & S.filterM (fmap not . isVeryOld) - & S.filterM (fmap not . farAhead) - & S.filterM (fmap not . isOld) - & S.filterM (fmap not . isCurrent) - - & S.chain (\c -> loggCutId logFun Info c "fetching all prerequisites") - & S.mapM (cutHashesToBlockHeaderMap conf logFun headerStore providers) - & S.catMaybes - -- ignore unsuccessful values for now - - -- using S.scanM would be slightly more efficient (one pointer dereference) - -- by keeping the value of cutVar in memory. We use the S.mapM variant with - -- an redundant 'readTVarIO' because it is easier to read. - & S.mapM_ (\newCut -> do - curCut <- readTVarIO cutVar - !resultCut <- trace logFun "Chainweb.CutDB.processCuts._joinIntoHeavier" () 1 - $ joinIntoHeavier_ hdrStore (_cutMap curCut) newCut - unless (_cutDbParamsReadOnly conf) $ do - maybePrune rng (cutAvgBlockHeight curCut) - loggCutId logFun Debug newCut "writing cut" - casInsert cutHashesStore (cutToCutHashes Nothing resultCut) - -- ensure that payload providers are in sync with the *merged* - -- cut, so that they produce payloads on the correct parents. - iforM_ (_cutMap resultCut) $ \cid bh -> do - -- avoid asking for syncToBlock when we know that we're already - -- in sync, otherwise some payload providers misbehave. - when (Just bh /= curCut ^? ixg cid) $ - case providers ^?! atChain cid of - ConfiguredPayloadProvider provider -> do - finfo <- forkInfoForHeader hdrStore bh Nothing Nothing - r <- syncToBlock provider Nothing finfo - unless (r == _forkInfoTargetState finfo) $ do - error $ "unexpected result state" - <> "; expected: " <> sshow (_forkInfoTargetState finfo) - <> "; actual: " <> sshow r - _ -> return () - let cutDiff = cutDiffToTextShort curCut resultCut - let currentCutIdMsg = T.unwords - [ "current cut is now" - , cutIdToTextShort (_cutId resultCut) <> "," - , "diff:" - ] - let catOverflowing x xs = - if length xs == 1 - then T.unwords (x : xs) - else T.intercalate "\n" (x : (map (" " <>) xs)) - logFun @T.Text Info $ catOverflowing currentCutIdMsg cutDiff - atomically $ writeTVar cutVar resultCut - ) +processCuts conf logFun headerStore providers cutHashesStore queue cutVar cutPruningStateVar = + flip onException writeLatestCutOnExit $ do + pQueueToStream queue + & S.map getDown + & S.chain (\c -> loggCutId logFun Debug c "start processing") + & S.filterM (fmap not . isVeryOld) + & S.filterM (fmap not . farAhead) + & S.filterM (fmap not . isOld) + & S.filterM (fmap not . isCurrent) + + & S.chain (\c -> loggCutId logFun Info c "fetching all prerequisites") + & S.mapM (cutHashesToBlockHeaderMap conf logFun headerStore providers) + & S.catMaybes + -- ignore unsuccessful values for now + + -- using S.scanM would be slightly more efficient (one pointer dereference) + -- by keeping the value of cutVar in memory. We use the S.mapM variant with + -- an redundant 'readTVarIO' because it is easier to read. + & S.mapM_ (\newCut -> do + curCut <- readTVarIO cutVar + !resultCut <- trace logFun "Chainweb.CutDB.processCuts._joinIntoHeavier" () 1 + $ joinIntoHeavier_ hdrStore (_cutMap curCut) newCut + -- write the cut to storage for later use when the node restarts + -- or if the operator has to do manual fork resolution later. + -- + -- we don't write *all* cuts. in the worst case if the node + -- receives each block one at a time, we have one cut for each + -- block. each cut has one block hash per chain, so that would + -- be quadratic storage in the number of chains. + -- we also prune old cuts for this reason. + + -- however, we do want to write them at least *once in a while*, + -- to ensure that the node doesn't have to replay too much + -- history on restart, and to ensure that we can do manual fork + -- recovery without more complicated cut rediscovery mechanisms. + unless (_cutDbParamsReadOnly conf) $ do + let resultCutMaxHeight = view cutMaxHeight resultCut + shouldWriteAndPrune <- atomically $ do + latestPruningState <- readTVar cutPruningStateVar + -- we write one cut each time the max height advances by + -- a block ahead of the previously written cut. + -- note that we are not that smart here; this code isn't + -- aware of reorgs. reorgs, especially rewinds, would + -- probably make some sense to write; but we assume that + -- this is good enough, because reorgs are small, one + -- block is a fast thing to wait for, and we + -- regardless write our current cut on a healthy + -- shutdown. + let lastWriteHeight = cutPruningStateLatestWrittenHeight latestPruningState + let nextWriteHeight = succ lastWriteHeight + if all (\bh -> view blockHeight bh >= nextWriteHeight) (view cutMap resultCut) + then do + writeTVar cutPruningStateVar + latestPruningState { cutPruningStateLatestWrittenHeight = resultCutMaxHeight } + return True + else + return False + when shouldWriteAndPrune $ do + pruneCuts logFun conf (cutAvgBlockHeight curCut) cutHashesStore + loggCutId logFun Info newCut + $ "writing cut at bh " <> sshow resultCutMaxHeight + casInsert cutHashesStore (cutToCutHashes Nothing resultCut) + -- ensure that payload providers are in sync with the *merged* + -- cut, so that they produce payloads on the correct parents. + iforM_ (_cutMap resultCut) $ \cid bh -> do + -- avoid asking for syncToBlock when we know that we're already + -- in sync, otherwise some payload providers misbehave. + when (Just bh /= curCut ^? ixg cid) $ + case providers ^?! atChain cid of + ConfiguredPayloadProvider provider -> do + finfo <- forkInfoForHeader hdrStore bh Nothing Nothing + r <- syncToBlock provider Nothing finfo + unless (r == _forkInfoTargetState finfo) $ do + error $ "unexpected result state" + <> "; expected: " <> sshow (_forkInfoTargetState finfo) + <> "; actual: " <> sshow r + _ -> return () + let cutDiff = cutDiffToTextShort curCut resultCut + let currentCutIdMsg = T.unwords + [ "current cut is now" + , cutIdToTextShort (_cutId resultCut) <> "," + , "diff:" + ] + let catOverflowing x xs = + if length xs == 1 + then T.unwords (x : xs) + else T.intercalate "\n" (x : (map (" " <>) xs)) + logFun @T.Text Info $ catOverflowing currentCutIdMsg cutDiff + atomically $ writeTVar cutVar resultCut + ) where - maybePrune rng curCutAvgBlockHeight = do - r :: Double <- Prob.uniform rng - when (r < 1 / int (int (_cutDbParamsPruningFrequency conf) * chainCountAt maxBound)) $ - pruneCuts logFun conf curCutAvgBlockHeight cutHashesStore + writeLatestCutOnExit = do + latestCut <- readTVarIO cutVar + casInsert cutHashesStore (cutToCutHashes Nothing latestCut) hdrStore = _webBlockHeaderStoreCas headerStore - queueToStream = do - Down a <- liftIO (pQueueRemove queue) - S.yield a - queueToStream - -- FIXME: this is problematic. We should drop these much earlier before they -- are even added to the queue, to prevent the queue from becoming stale. -- Although its probably low risk, since the processing pipeline is probably diff --git a/src/Data/PQueue.hs b/src/Data/PQueue.hs index 43da59d2da..c7d96fcef1 100644 --- a/src/Data/PQueue.hs +++ b/src/Data/PQueue.hs @@ -18,11 +18,13 @@ module Data.PQueue , pQueueRemove , pQueueIsEmpty , pQueueSize +, pQueueToStream ) where import Control.Concurrent.MVar import Control.Exception (evaluate) import Control.Monad +import Control.Monad.IO.Class import qualified Data.Heap as H @@ -30,6 +32,9 @@ import GHC.Generics import Numeric.Natural +import Streaming (Stream, Of) +import qualified Streaming.Prelude as S + -- -------------------------------------------------------------------------- -- -- PQueue @@ -79,3 +84,8 @@ pQueueRemove (PQueue s q) = run case r of Nothing -> takeMVar s >> run (Just !x) -> return x + +pQueueToStream :: PQueue a -> Stream (Of a) IO () +pQueueToStream pq = forever $ do + a <- liftIO (pQueueRemove pq) + S.yield a diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 70e78fb7d4..1b13e73bb5 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -536,8 +536,7 @@ testCutPruning rdb = testCase "cut pruning" $ runResourceT $ withVersion (barebo round (avgBlockHeightAtCutHeight mostCutHeight) >= int minedBlockHeight - fuzz where alterPruningSettings = - set cutDbParamsAvgBlockHeightPruningDepth 50 . - set cutDbParamsPruningFrequency 1 + set cutDbParamsAvgBlockHeightPruningDepth 50 minedBlockHeight = 300 testCutGet :: RocksDb -> TestTree From e6ad62c1e0e89cf2b089ca2d94dd30ac1c415aeb Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 24 Jul 2025 14:10:53 -0400 Subject: [PATCH 293/378] Re-enable most rest tests Not the payload-related ones; those will need to be PP-specific, because payload storage is now PP-specific. That includes the /block endpoint, which will be Pact-specific. Change-Id: Id0000000c29dc660698f5b7bc3155cf10027c9ce --- test/unit/Chainweb/Test/RestAPI.hs | 476 ++++++++++++++--------------- 1 file changed, 237 insertions(+), 239 deletions(-) diff --git a/test/unit/Chainweb/Test/RestAPI.hs b/test/unit/Chainweb/Test/RestAPI.hs index 9afa512dce..db2d057762 100644 --- a/test/unit/Chainweb/Test/RestAPI.hs +++ b/test/unit/Chainweb/Test/RestAPI.hs @@ -83,6 +83,8 @@ import Chainweb.Logger import Chainweb.Chainweb.ChainResources (payloadP2pResources) import P2P.Node.Configuration (defaultP2pConfiguration) import System.LogLevel +import Chainweb.BlockHeaderDB.RestAPI.Client +import Control.Exception (evaluate) -- -------------------------------------------------------------------------- -- -- BlockHeaderDb queries @@ -172,20 +174,22 @@ simpleSessionTests rdb tls = withVersion version $ let neverLogger = genericLogger Error (\_ -> return ()) in withResourceT (do { mgr <- liftIO mgrIO; dbs <- liftIO dbsIO; mkEnv neverLogger mgr rdb tls dbs}) $ \env -> testGroup "client session tests" - $ [httpHeaderTests env (head $ toList chainIds)] - -- : (simpleClientSession env <$> toList chainIds) + $ httpHeaderTests env (head $ toList chainIds) + : (simpleClientSession env <$> toList chainIds) httpHeaderTests :: IO TestClientEnv_ -> ChainId -> TestTree httpHeaderTests envIO cid = testGroup ("http header tests for chain " <> sshow cid) [ testCase "headerClient" $ go $ \h -> headerClient' cid (key h) , testCase "headersClient" $ go $ \_ -> headersClient' cid Nothing Nothing Nothing Nothing + -- TODO: PP -- , testCase "blocksClient" $ go $ \_ -> blocksClient' cid Nothing Nothing Nothing Nothing , testCase "hashesClient" $ go $ \_ -> hashesClient' cid Nothing Nothing Nothing Nothing , testCase "branchHashesClient" $ go $ \_ -> branchHashesClient' cid Nothing Nothing Nothing Nothing (BranchBounds mempty mempty) , testCase "branchHeadersClient" $ go $ \_ -> branchHeadersClient' cid Nothing Nothing Nothing Nothing (BranchBounds mempty mempty) + -- TODO: PP -- , testCase "branchBlocksClient" $ go $ \_ -> branchBlocksClient' cid Nothing Nothing Nothing -- Nothing (BranchBounds mempty mempty) ] @@ -210,243 +214,237 @@ httpHeaderTests envIO cid = (d <= 2) return res --- simpleClientSession :: IO TestClientEnv_ -> ChainId -> TestTree --- simpleClientSession envIO cid = --- testCaseSteps ("simple session for chain " <> sshow cid) $ \step -> do --- env <- _envClientEnv <$> envIO --- bhdbs <- _envBlockHeaderDbs <$> envIO --- pdbs <- _envPayloads <$> envIO --- res <- runClientM (withVersion version $ session bhdbs pdbs step) env --- assertBool ("test failed: " <> sshow res) (isRight res) --- where - --- session :: HasVersion => [(ChainId, BlockHeaderDb)] -> [(ChainId, PayloadDb RocksDbTable)] -> (String -> IO a) -> ClientM () --- session bhdbs pdbs step = do - --- let gbh0 = genesisBlockHeader cid - --- bhdb <- case Prelude.lookup cid bhdbs of --- Just x -> return x --- Nothing -> error "Chainweb.Test.RestAPI.simpleClientSession: missing block header db in test" - --- pdb <- case Prelude.lookup cid pdbs of --- Just x -> return x --- Nothing -> error "Chainweb.Test.RestAPI.simpleClientSession: missing payload db in test" - --- void $ liftIO $ step "headerClient: get genesis block header" --- gen0 <- headerClient cid (key gbh0) --- assertExpectation "header client returned wrong entry" --- (Expected gbh0) --- (Actual gen0) - --- void $ liftIO $ step "headerClient: get genesis block header pretty" --- gen01 <- headerClientJsonPretty cid (key gbh0) --- assertExpectation "header client returned wrong entry" --- (Expected gbh0) --- (Actual gen01) - --- void $ liftIO $ step "headerClient: get genesis block header binary" --- gen02 <- headerClientJsonBinary cid (key gbh0) --- assertExpectation "header client returned wrong entry" --- (Expected gbh0) --- (Actual gen02) - --- void $ liftIO $ step "headersClient: get genesis block header" --- bhs1 <- headersClient cid Nothing Nothing Nothing Nothing --- gen1 <- case _pageItems bhs1 of --- [] -> liftIO $ assertFailure "headersClient did return empty result" --- (h:_) -> return h --- assertExpectation "header client returned wrong entry" --- (Expected gbh0) --- (Actual gen1) - --- void $ liftIO $ step "blocksClient: get genesis block" --- block1 <- blocksClient cid Nothing Nothing Nothing Nothing --- gen1Block <- case _pageItems block1 of --- [] -> liftIO $ assertFailure "blocksClient did return empty result" --- (h:_) -> return h --- assertExpectation "block client returned wrong entry" --- (Expected (Block gbh0 (implicitVersion ^?! versionGenesis . genesisBlockPayload . atChain cid))) --- (Actual gen1Block) - --- void $ liftIO $ step "put 3 new blocks" --- let newHeaders = take 3 $ testBlockHeaders (ParentHeader gbh0) --- liftIO $ traverse_ (unsafeInsertBlockHeaderDb bhdb) newHeaders --- liftIO $ traverse_ (\x -> addNewPayload pdb (view blockHeight x) (testBlockPayload_ x)) newHeaders - --- void $ liftIO $ step "headersClient: get all 4 block headers" --- bhs2 <- headersClient cid Nothing Nothing Nothing Nothing --- assertExpectation "headersClient returned wrong number of entries" --- (Expected 4) --- (Actual $ _pageLimit bhs2) - --- void $ liftIO $ step "blocksClient: get all 4 blocks" --- blocks2 <- blocksClient cid Nothing Nothing Nothing Nothing --- assertExpectation "blocksClient returned wrong number of entries" --- (Expected 4) --- (Actual $ _pageLimit blocks2) - --- void $ liftIO $ step "hashesClient: get all 4 block hashes" --- hs2 <- hashesClient cid Nothing Nothing Nothing Nothing --- assertExpectation "hashesClient returned wrong number of entries" --- (Expected $ _pageLimit bhs2) --- (Actual $ _pageLimit hs2) --- assertExpectation "hashesClient returned wrong hashes" --- (Expected $ key <$> _pageItems bhs2) --- (Actual $ _pageItems hs2) - --- forM_ newHeaders $ \h -> do --- void $ liftIO $ step $ "headerClient: " <> T.unpack (encodeToText (view blockHash h)) --- r <- headerClient cid (key h) --- assertExpectation "header client returned wrong entry" --- (Expected h) --- (Actual r) - --- -- branchHeaders - --- do --- void $ liftIO $ step "branchHeadersClient: BranchBounds limits exceeded" --- clientEnv <- liftIO $ _envClientEnv <$> envIO --- let query bounds = liftIO --- $ flip runClientM clientEnv --- $ branchHeadersClient --- cid Nothing Nothing Nothing Nothing bounds --- let limit = 32 --- let blockHeaders = testBlockHeaders (ParentHeader gbh0) --- let maxBlockHeaders = take limit blockHeaders --- let excessBlockHeaders = take (limit + 1) blockHeaders - --- let mkLower :: [BlockHeader] -> HS.HashSet (LowerBound BlockHash) --- mkLower hs = HS.fromList $ map (LowerBound . key) hs --- let mkUpper :: [BlockHeader] -> HS.HashSet (UpperBound BlockHash) --- mkUpper hs = HS.fromList $ map (UpperBound . key) hs - --- let emptyLower = mkLower [] --- let badLower = mkLower excessBlockHeaders --- let goodLower = mkLower maxBlockHeaders - --- let emptyUpper = mkUpper [] --- let badUpper = mkUpper excessBlockHeaders --- let goodUpper = mkUpper maxBlockHeaders - --- let badRespCheck :: Int -> ClientError -> Bool --- badRespCheck s e = isFailureResponse e && clientErrorStatusCode e == Just s - --- badLowerResponse <- query (BranchBounds badLower emptyUpper) --- assertExpectation "branchHeadersClient returned a 400 error code on excess lower" --- (Expected (Left True)) --- (Actual (first (badRespCheck 400) badLowerResponse)) - --- badUpperResponse <- query (BranchBounds emptyLower badUpper) --- assertExpectation "branchHeadersClient returned a 400 error code on excess upper" --- (Expected (Left True)) --- (Actual (first (badRespCheck 400) badUpperResponse)) - --- -- This will still fail because a bunch of these keys won't be found, --- -- but it won't fail the bounds check, which happens first --- doesntFailBoundsCheck <- query (BranchBounds goodLower goodUpper) --- assertExpectation "branchHeadersClient returned a 404; bounds were within the limits, still fails key exists check" --- (Expected (Left True)) --- (Actual (first (badRespCheck 404) doesntFailBoundsCheck)) - --- doesntFailAtAll <- query (BranchBounds emptyLower emptyUpper) --- assertExpectation "branchHeadersClient returned a 200; bounds were within the limits, and no keys to check at all" --- (Expected (Right ())) --- (Actual (() <$ doesntFailAtAll)) - --- void $ liftIO $ step "branchHeadersClient: get no block headers" --- bhs3 <- branchHeadersClient cid Nothing Nothing Nothing Nothing --- (BranchBounds mempty mempty) --- assertExpectation "branchHeadersClient returned wrong number of entries" --- (Expected 0) --- (Actual $ _pageLimit bhs3) - --- forM_ ([2..] `zip` newHeaders) $ \(i, h) -> do --- void $ liftIO $ step $ "branchHeadersClient: get " <> sshow i <> " block headers with upper bound" --- bhs4 <- branchHeadersClient cid Nothing Nothing Nothing Nothing --- (BranchBounds mempty (HS.singleton (UpperBound $ key h))) --- assertExpectation "branchHeadersClient returned wrong number of entries" --- (Expected i) --- (Actual $ _pageLimit bhs4) - --- void $ liftIO $ step "branchHeadersClient: get no block headers with lower and upper bound" --- bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing --- (BranchBounds (HS.singleton (LowerBound $ key h)) (HS.singleton (UpperBound $ key h))) --- assertExpectation "branchHeadersClient returned wrong number of entries" --- (Expected 0) --- (Actual $ _pageLimit bhs5) - --- forM_ (newHeaders `zip` drop 1 newHeaders) $ \(h0, h1) -> do --- void $ liftIO $ step "branchHeadersClient: get one block headers with lower and upper bound" --- bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing --- (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) --- assertExpectation "branchHeadersClient returned wrong number of entries" --- (Expected 1) --- (Actual $ _pageLimit bhs5) - --- forM_ (newHeaders `zip` drop 2 newHeaders) $ \(h0, h1) -> do --- void $ liftIO $ step "branchHeadersClient: get two block headers with lower and upper bound" --- bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing --- (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) --- assertExpectation "branchHeadersClient returned wrong number of entries" --- (Expected 2) --- (Actual $ _pageLimit bhs5) - --- -- branchHeaders - --- void $ liftIO $ step "branchHashesClient: get no block headers" --- hs3 <- branchHashesClient cid Nothing Nothing Nothing Nothing --- (BranchBounds mempty mempty) --- assertExpectation "branchHashesClient returned wrong number of entries" --- (Expected 0) --- (Actual $ _pageLimit hs3) - --- forM_ ([2..] `zip` newHeaders) $ \(i, h) -> do --- void $ liftIO $ step $ "branchHashesClient: get " <> sshow i <> " block headers with upper bound" --- hs4 <- branchHashesClient cid Nothing Nothing Nothing Nothing --- (BranchBounds mempty (HS.singleton (UpperBound $ key h))) --- assertExpectation "branchHashesClient returned wrong number of entries" --- (Expected i) --- (Actual $ _pageLimit hs4) - --- void $ liftIO $ step "branchHashesClient: get no block headers with lower and upper bound" --- hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing --- (BranchBounds (HS.singleton (LowerBound $ key h)) (HS.singleton (UpperBound $ key h))) --- assertExpectation "branchHashesClient returned wrong number of entries" --- (Expected 0) --- (Actual $ _pageLimit hs5) - --- forM_ (newHeaders `zip` drop 1 newHeaders) $ \(h0, h1) -> do --- void $ liftIO $ step "branchHashesClient: get one block headers with lower and upper bound" --- hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing --- (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) --- assertExpectation "branchHashesClient returned wrong number of entries" --- (Expected 1) --- (Actual $ _pageLimit hs5) - --- forM_ (newHeaders `zip` drop 2 newHeaders) $ \(h0, h1) -> do --- void $ liftIO $ step "branchHashesClient: get two block headers with lower and upper bound" --- hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing --- (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) --- assertExpectation "branchHashesClient returned wrong number of entries" --- (Expected 2) --- (Actual $ _pageLimit hs5) - --- -- branch hashes with fork - --- void $ liftIO $ step "headerPutClient: put 3 new blocks on a new fork" --- let newHeaders2 = take 3 $ testBlockHeadersWithNonce (Nonce 17) (ParentHeader gbh0) --- liftIO $ traverse_ (unsafeInsertBlockHeaderDb bhdb) newHeaders2 --- liftIO $ traverse_ (\x -> addNewPayload pdb (view blockHeight x) (testBlockPayload_ x)) newHeaders2 - --- let lower = last newHeaders --- forM_ ([1..] `zip` newHeaders2) $ \(i, h) -> do --- void $ liftIO $ step "branchHashesClient: get one block headers with lower and upper bound" --- hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing --- (BranchBounds (HS.singleton (LowerBound $ key lower)) (HS.singleton (UpperBound $ key h))) --- assertExpectation "branchHashesClient returned wrong number of entries" --- (Expected i) --- (Actual $ _pageLimit hs5) +simpleClientSession :: IO TestClientEnv_ -> ChainId -> TestTree +simpleClientSession envIO cid = + testCaseSteps ("simple session for chain " <> sshow cid) $ \step -> do + env <- _envClientEnv <$> envIO + bhdbs <- _envBlockHeaderDbs <$> envIO + -- pdbs <- _envPayloads <$> envIO + res <- runClientM (withVersion version $ session bhdbs step) env + assertBool ("test failed: " <> sshow res) (isRight res) + where + + session :: HasVersion => ChainMap BlockHeaderDb -> (String -> IO a) -> ClientM () + session bhdbs step = do + + let gbh0 = genesisBlockHeader cid + + bhdb <- liftIO $ evaluate $ bhdbs ^?! atChain cid + + void $ liftIO $ step "headerClient: get genesis block header" + gen0 <- headerClient cid (key gbh0) + assertExpectation "header client returned wrong entry" + (Expected gbh0) + (Actual gen0) + + void $ liftIO $ step "headerClient: get genesis block header pretty" + gen01 <- headerClientJsonPretty cid (key gbh0) + assertExpectation "header client returned wrong entry" + (Expected gbh0) + (Actual gen01) + + void $ liftIO $ step "headerClient: get genesis block header binary" + gen02 <- headerClientJsonBinary cid (key gbh0) + assertExpectation "header client returned wrong entry" + (Expected gbh0) + (Actual gen02) + + void $ liftIO $ step "headersClient: get genesis block header" + bhs1 <- headersClient cid Nothing Nothing Nothing Nothing + gen1 <- case _pageItems bhs1 of + [] -> liftIO $ assertFailure "headersClient did return empty result" + (h:_) -> return h + assertExpectation "header client returned wrong entry" + (Expected gbh0) + (Actual gen1) + + -- void $ liftIO $ step "blocksClient: get genesis block" + -- block1 <- blocksClient cid Nothing Nothing Nothing Nothing + -- gen1Block <- case _pageItems block1 of + -- [] -> liftIO $ assertFailure "blocksClient did return empty result" + -- (h:_) -> return h + -- assertExpectation "block client returned wrong entry" + -- (Expected (Block gbh0 (implicitVersion ^?! versionGenesis . genesisBlockPayload . atChain cid))) + -- (Actual gen1Block) + + void $ liftIO $ step "put 3 new blocks" + let newHeaders = take 3 $ testBlockHeaders (Parent gbh0) + liftIO $ traverse_ (unsafeInsertBlockHeaderDb bhdb) newHeaders + -- liftIO $ traverse_ (\x -> addNewPayload pdb (view blockHeight x) (testBlockPayload_ x)) newHeaders + + void $ liftIO $ step "headersClient: get all 4 block headers" + bhs2 <- headersClient cid Nothing Nothing Nothing Nothing + assertExpectation "headersClient returned wrong number of entries" + (Expected 4) + (Actual $ _pageLimit bhs2) + + -- void $ liftIO $ step "blocksClient: get all 4 blocks" + -- blocks2 <- blocksClient cid Nothing Nothing Nothing Nothing + -- assertExpectation "blocksClient returned wrong number of entries" + -- (Expected 4) + -- (Actual $ _pageLimit blocks2) + + void $ liftIO $ step "hashesClient: get all 4 block hashes" + hs2 <- hashesClient cid Nothing Nothing Nothing Nothing + assertExpectation "hashesClient returned wrong number of entries" + (Expected $ _pageLimit bhs2) + (Actual $ _pageLimit hs2) + assertExpectation "hashesClient returned wrong hashes" + (Expected $ key <$> _pageItems bhs2) + (Actual $ _pageItems hs2) + + forM_ newHeaders $ \h -> do + void $ liftIO $ step $ "headerClient: " <> T.unpack (encodeToText (view blockHash h)) + r <- headerClient cid (key h) + assertExpectation "header client returned wrong entry" + (Expected h) + (Actual r) + + -- branchHeaders + + do + void $ liftIO $ step "branchHeadersClient: BranchBounds limits exceeded" + clientEnv <- liftIO $ _envClientEnv <$> envIO + let query bounds = liftIO + $ flip runClientM clientEnv + $ branchHeadersClient + cid Nothing Nothing Nothing Nothing bounds + let limit = 32 + let blockHeaders = testBlockHeaders (Parent gbh0) + let maxBlockHeaders = take limit blockHeaders + let excessBlockHeaders = take (limit + 1) blockHeaders + + let mkLower :: [BlockHeader] -> HS.HashSet (LowerBound BlockHash) + mkLower hs = HS.fromList $ map (LowerBound . key) hs + let mkUpper :: [BlockHeader] -> HS.HashSet (UpperBound BlockHash) + mkUpper hs = HS.fromList $ map (UpperBound . key) hs + + let emptyLower = mkLower [] + let badLower = mkLower excessBlockHeaders + let goodLower = mkLower maxBlockHeaders + + let emptyUpper = mkUpper [] + let badUpper = mkUpper excessBlockHeaders + let goodUpper = mkUpper maxBlockHeaders + + let badRespCheck :: Int -> ClientError -> Bool + badRespCheck s e = isFailureResponse e && clientErrorStatusCode e == Just s + + badLowerResponse <- query (BranchBounds badLower emptyUpper) + assertExpectation "branchHeadersClient returned a 400 error code on excess lower" + (Expected (Left True)) + (Actual (first (badRespCheck 400) badLowerResponse)) + + badUpperResponse <- query (BranchBounds emptyLower badUpper) + assertExpectation "branchHeadersClient returned a 400 error code on excess upper" + (Expected (Left True)) + (Actual (first (badRespCheck 400) badUpperResponse)) + + -- This will still fail because a bunch of these keys won't be found, + -- but it won't fail the bounds check, which happens first + doesntFailBoundsCheck <- query (BranchBounds goodLower goodUpper) + assertExpectation "branchHeadersClient returned a 404; bounds were within the limits, still fails key exists check" + (Expected (Left True)) + (Actual (first (badRespCheck 404) doesntFailBoundsCheck)) + + doesntFailAtAll <- query (BranchBounds emptyLower emptyUpper) + assertExpectation "branchHeadersClient returned a 200; bounds were within the limits, and no keys to check at all" + (Expected (Right ())) + (Actual (() <$ doesntFailAtAll)) + + void $ liftIO $ step "branchHeadersClient: get no block headers" + bhs3 <- branchHeadersClient cid Nothing Nothing Nothing Nothing + (BranchBounds mempty mempty) + assertExpectation "branchHeadersClient returned wrong number of entries" + (Expected 0) + (Actual $ _pageLimit bhs3) + + forM_ ([2..] `zip` newHeaders) $ \(i, h) -> do + void $ liftIO $ step $ "branchHeadersClient: get " <> sshow i <> " block headers with upper bound" + bhs4 <- branchHeadersClient cid Nothing Nothing Nothing Nothing + (BranchBounds mempty (HS.singleton (UpperBound $ key h))) + assertExpectation "branchHeadersClient returned wrong number of entries" + (Expected i) + (Actual $ _pageLimit bhs4) + + void $ liftIO $ step "branchHeadersClient: get no block headers with lower and upper bound" + bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing + (BranchBounds (HS.singleton (LowerBound $ key h)) (HS.singleton (UpperBound $ key h))) + assertExpectation "branchHeadersClient returned wrong number of entries" + (Expected 0) + (Actual $ _pageLimit bhs5) + + forM_ (newHeaders `zip` drop 1 newHeaders) $ \(h0, h1) -> do + void $ liftIO $ step "branchHeadersClient: get one block headers with lower and upper bound" + bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing + (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) + assertExpectation "branchHeadersClient returned wrong number of entries" + (Expected 1) + (Actual $ _pageLimit bhs5) + + forM_ (newHeaders `zip` drop 2 newHeaders) $ \(h0, h1) -> do + void $ liftIO $ step "branchHeadersClient: get two block headers with lower and upper bound" + bhs5 <- branchHeadersClient cid Nothing Nothing Nothing Nothing + (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) + assertExpectation "branchHeadersClient returned wrong number of entries" + (Expected 2) + (Actual $ _pageLimit bhs5) + + -- branchHeaders + + void $ liftIO $ step "branchHashesClient: get no block headers" + hs3 <- branchHashesClient cid Nothing Nothing Nothing Nothing + (BranchBounds mempty mempty) + assertExpectation "branchHashesClient returned wrong number of entries" + (Expected 0) + (Actual $ _pageLimit hs3) + + forM_ ([2..] `zip` newHeaders) $ \(i, h) -> do + void $ liftIO $ step $ "branchHashesClient: get " <> sshow i <> " block headers with upper bound" + hs4 <- branchHashesClient cid Nothing Nothing Nothing Nothing + (BranchBounds mempty (HS.singleton (UpperBound $ key h))) + assertExpectation "branchHashesClient returned wrong number of entries" + (Expected i) + (Actual $ _pageLimit hs4) + + void $ liftIO $ step "branchHashesClient: get no block headers with lower and upper bound" + hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing + (BranchBounds (HS.singleton (LowerBound $ key h)) (HS.singleton (UpperBound $ key h))) + assertExpectation "branchHashesClient returned wrong number of entries" + (Expected 0) + (Actual $ _pageLimit hs5) + + forM_ (newHeaders `zip` drop 1 newHeaders) $ \(h0, h1) -> do + void $ liftIO $ step "branchHashesClient: get one block headers with lower and upper bound" + hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing + (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) + assertExpectation "branchHashesClient returned wrong number of entries" + (Expected 1) + (Actual $ _pageLimit hs5) + + forM_ (newHeaders `zip` drop 2 newHeaders) $ \(h0, h1) -> do + void $ liftIO $ step "branchHashesClient: get two block headers with lower and upper bound" + hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing + (BranchBounds (HS.singleton (LowerBound $ key h0)) (HS.singleton (UpperBound $ key h1))) + assertExpectation "branchHashesClient returned wrong number of entries" + (Expected 2) + (Actual $ _pageLimit hs5) + + -- branch hashes with fork + + void $ liftIO $ step "headerPutClient: put 3 new blocks on a new fork" + let newHeaders2 = take 3 $ testBlockHeadersWithNonce (Nonce 17) (Parent gbh0) + liftIO $ traverse_ (unsafeInsertBlockHeaderDb bhdb) newHeaders2 + -- liftIO $ traverse_ (\x -> addNewPayload pdb (view blockHeight x) (testBlockPayload_ x)) newHeaders2 + + let lower = last newHeaders + forM_ ([1..] `zip` newHeaders2) $ \(i, h) -> do + void $ liftIO $ step "branchHashesClient: get one block headers with lower and upper bound" + hs5 <- branchHashesClient cid Nothing Nothing Nothing Nothing + (BranchBounds (HS.singleton (LowerBound $ key lower)) (HS.singleton (UpperBound $ key h))) + assertExpectation "branchHashesClient returned wrong number of entries" + (Expected i) + (Actual $ _pageLimit hs5) From ea2fdb1028f23c46b041b4306018e82748a2ab71 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 24 Jul 2025 15:06:22 -0400 Subject: [PATCH 294/378] No more todo Change-Id: Id0000000666e4e482c8bf32e768ae9672cf59ba2 --- test/lib/Chainweb/Test/Utils.hs | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index 5e591b40bc..f8d42b43c1 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -620,8 +620,6 @@ withTestAppServer tls appIO envIO userFunc = bracket start stop go data ShouldValidateSpec = ValidateSpec | DoNotValidateSpec --- TODO: catch, wrap, and forward exceptions from chainwebApplication --- withChainwebTestServer :: HasVersion => ShouldValidateSpec From 32e11db698121527fb7c15db541a334ce6a5abe5 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 24 Jul 2025 16:03:55 -0400 Subject: [PATCH 295/378] More use of ResourceT in tests Change-Id: Id00000004a898fe526b7628066f580513b3e9747 --- src/Chainweb/Utils.hs | 20 ++++++++ test/lib/Chainweb/Test/Utils.hs | 53 ++++++++++------------ test/unit/Chainweb/Test/Mempool/RestAPI.hs | 50 ++++++++++---------- 3 files changed, 70 insertions(+), 53 deletions(-) diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index 4f8870d6df..b96d64f72e 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -196,6 +196,7 @@ module Chainweb.Utils , concurrentWith , withLink , withAsyncR +, resourceToBracket , concurrentlies , concurrentlies_ @@ -296,6 +297,7 @@ import Data.Functor.Of import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS import Data.Hashable +import Data.IORef import Data.String (IsString(..)) import Data.Text qualified as T import Data.Text.Encoding qualified as T @@ -1370,6 +1372,24 @@ withLink act = do withAsyncR :: IO a -> ResourceT IO (Async a) withAsyncR act = snd <$> allocate (async act) uninterruptibleCancel +-- | Use a ResourceT-allocated resource from @bracket@. +resourceToBracket + :: ResourceT IO a + -> (IO (a, InternalState) + , (a, InternalState) -> IO ()) +resourceToBracket res = do + let prime = do + r <- res + uninterruptibleMask_ $ do + s <- getInternalState + releaseMap <- liftIO $ readIORef s + s' <- createInternalState + liftIO $ writeIORef s =<< readIORef s' + liftIO $ writeIORef s' releaseMap + return (r, s') + + (runResourceT prime, (\(_, s) -> closeInternalState s)) + -- | Like `sequence` for IO but concurrent concurrentlies :: forall a. [IO a] -> IO [a] concurrentlies = runConcurrently . traverse Concurrently diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index 50e322ede6..d0aa4091e0 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -586,37 +586,30 @@ pattern PayloadTestClientEnv { _pEnvClientEnv, _pEnvCutDb } withTestAppServer :: Bool - -> IO W.Application - -> (Int -> IO a) - -> (a -> IO b) - -> IO b -withTestAppServer tls appIO envIO userFunc = bracket start stop go - where + -> W.Application + -> ResourceT IO Int +withTestAppServer tls app = do + (port, sock) <- snd <$> allocate W.openFreePort (close . snd) + readyVar <- liftIO newEmptyMVar + server <- withAsyncR $ do + let settings = + W.setOnException warpOnException $ + W.setBeforeMainLoop (putMVar readyVar ()) W.defaultSettings + if + | tls -> do + let certBytes = testBootstrapCertificate + let keyBytes = testBootstrapKey + let tlsSettings = tlsServerSettings certBytes keyBytes + W.runTLSSocket tlsSettings settings sock app + | otherwise -> + W.runSettingsSocket settings sock app + + liftIO $ link server + _ <- liftIO $ takeMVar readyVar + return port + + where warpOnException _ _ = return () - start = do - app <- appIO - (port, sock) <- W.openFreePort - readyVar <- newEmptyMVar - server <- async $ do - let settings = W.setOnException warpOnException $ - W.setBeforeMainLoop (putMVar readyVar ()) W.defaultSettings - if - | tls -> do - let certBytes = testBootstrapCertificate - let keyBytes = testBootstrapKey - let tlsSettings = tlsServerSettings certBytes keyBytes - W.runTLSSocket tlsSettings settings sock app - | otherwise -> - W.runSettingsSocket settings sock app - - link server - _ <- takeMVar readyVar - env <- envIO port - return (server, sock, env) - stop (server, sock, _) = do - uninterruptibleCancel server - close sock - go (_, _, env) = userFunc env data ShouldValidateSpec = ValidateSpec | DoNotValidateSpec diff --git a/test/unit/Chainweb/Test/Mempool/RestAPI.hs b/test/unit/Chainweb/Test/Mempool/RestAPI.hs index 158c2b3bac..a24d194cd2 100644 --- a/test/unit/Chainweb/Test/Mempool/RestAPI.hs +++ b/test/unit/Chainweb/Test/Mempool/RestAPI.hs @@ -1,8 +1,11 @@ +{-# LANGUAGE NumericUnderscores #-} module Chainweb.Test.Mempool.RestAPI (tests) where import Control.Concurrent import Control.Concurrent.STM import Control.Exception +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource import qualified Data.Pool as Pool import qualified Data.Vector as V @@ -27,13 +30,14 @@ import Chainweb.Test.Mempool (InsertCheck, MempoolWithFunc(..)) import qualified Chainweb.Test.Mempool import Chainweb.Test.Utils (withTestAppServer) import Chainweb.Test.TestVersions -import Chainweb.Utils (Codec(..)) +import Chainweb.Utils (Codec(..), withAsyncR, resourceToBracket) import Chainweb.Version import Chainweb.Version.Utils import Chainweb.Storage.Table.RocksDB import Network.X509.SelfSigned +import Control.Monad ------------------------------------------------------------------------------ tests :: TestTree @@ -47,34 +51,35 @@ data TestServer = TestServer { _tsRemoteMempool :: !(MempoolBackend MockTx) , _tsLocalMempool :: !(MempoolBackend MockTx) , _tsInsertCheck :: InsertCheck - , _tsServerThread :: !ThreadId } -newTestServer :: IO TestServer -newTestServer = withVersion version $ mask_ $ do +newTestServer :: ResourceT IO TestServer +newTestServer = withVersion version $ do let chain = someChainId - checkMv <- newMVar (pure . V.map Right) + checkMv <- liftIO $ newMVar (pure . V.map Right) let inMemCfg = InMemConfig txcfg mockBlockGasLimit (GasPrice 0) 2048 Right (checkMvFunc checkMv) (1024 * 10) - inmemMv <- newEmptyMVar - envMv <- newEmptyMVar - tid <- forkIOWithUnmask $ \u -> server chain inMemCfg inmemMv envMv u - inmem <- takeMVar inmemMv - env <- takeMVar envMv + inmemMv <- liftIO newEmptyMVar + envMv <- liftIO newEmptyMVar + _ <- withAsyncR $ server chain inMemCfg inmemMv envMv + inmem <- liftIO $ takeMVar inmemMv + env <- liftIO $ takeMVar envMv let remoteMp0 = MClient.toMempool chain txcfg env -- allow remoteMp to call the local mempool's getBlock (for testing) let remoteMp = remoteMp0 { mempoolGetBlock = mempoolGetBlock inmem } - return $! TestServer remoteMp inmem checkMv tid + return $! TestServer remoteMp inmem checkMv where checkMvFunc mv xs = do f <- readMVar mv f xs - server chain inMemCfg inmemMv envMv restore = withVersion version $ do + server chain inMemCfg inmemMv envMv = withVersion version $ do inmem <- InMem.startInMemoryMempoolTest inMemCfg putMVar inmemMv inmem - restore $ withTestAppServer True (return $! mkApp chain inmem) mkEnv $ \env -> do - putMVar envMv env - atomically retry + runResourceT $ do + port <- withTestAppServer True (mkApp chain inmem) + env <- liftIO $ mkEnv port + liftIO $ putMVar envMv env + liftIO $ forever $ threadDelay 10_000_000 version :: ChainwebVersion version = barebonesTestVersion singletonChainGraph @@ -93,15 +98,14 @@ newTestServer = withVersion version $ mask_ $ do mgr <- HTTP.newManager mgrSettings return $! mkClientEnv mgr $ BaseUrl Https host port "" -destroyTestServer :: TestServer -> IO () -destroyTestServer = killThread . _tsServerThread - -newPool :: IO (Pool.Pool TestServer) +newPool :: IO (Pool.Pool (TestServer, InternalState)) newPool = Pool.newPool $ Pool.defaultPoolConfig - newTestServer - destroyTestServer + create + destroy 10 {- ttl seconds -} 20 {- max entries -} + where + (create, destroy) = resourceToBracket newTestServer ------------------------------------------------------------------------------ @@ -113,12 +117,12 @@ serverMempools mempools = emptyChainwebServerDbs } withRemoteMempool - :: IO (Pool.Pool TestServer) + :: IO (Pool.Pool (TestServer, InternalState)) -> (InsertCheck -> MempoolBackend MockTx -> IO a) -> IO a withRemoteMempool poolIO userFunc = do pool <- poolIO - Pool.withResource pool $ \ts -> do + Pool.withResource pool $ \(ts, _) -> do mempoolClear $ _tsLocalMempool ts userFunc (_tsInsertCheck ts) (_tsRemoteMempool ts) From f920656e8a2da66a569fbc9120c0aad252adaa48 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 22 Jul 2025 13:15:29 -0400 Subject: [PATCH 296/378] Serve payloads on service APIs (including Pact outputs) Change-Id: Id000000070ee05e7a3d0a8eb4333c855809ed46e --- src/Chainweb/Chainweb.hs | 1 + src/Chainweb/Chainweb/ChainResources.hs | 72 +++++++++++++++++-------- test/unit/Chainweb/Test/CutDB.hs | 4 +- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index cdb30df5c1..3b21ca75bb 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -285,6 +285,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba (_peerResManager peerRes) defaultPactDbDir (_peerResConfig peerRes) + (_configServiceApi conf) myInfo peerDb (_configReorgLimit conf) diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 99fe28ebce..9b353c1c13 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -17,6 +17,8 @@ {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE RecursiveDo #-} {-# LANGUAGE TupleSections #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module: Chainweb.Chainweb.ChainResources @@ -110,6 +112,9 @@ import Chainweb.Version import Chainweb.Version.Guards (maxBlockGasLimit) import Pact.Core.Gas qualified as Pact import Control.Monad (forM) +import Chainweb.Payload.PayloadStore (CanReadablePayloadCas) +import qualified Chainweb.Payload.RestAPI as PactPayload.RestAPI +import qualified Chainweb.Payload.RestAPI.Server as PactPayload.RestAPI.Server -- -------------------------------------------------------------------------- -- -- Payload P2P Network Resources @@ -160,7 +165,7 @@ payloadP2pResources logger p2pConfig myInfo peerDb tbl queue mgr = do { _payloadResPeerDb = peerDb , _payloadResP2pNode = p2pNode , _payloadResP2pApi = SomeApi (payloadApi @v @c @p) - , _payloadResP2pServer = somePayloadServer @_ @v @c @p 20 tbl + , _payloadResP2pServer = somePayloadServer @_ @v @c @p (PayloadBatchLimit 20) tbl } where p2pLogger = addLabel ("sub-component", "p2p") logger @@ -187,10 +192,10 @@ runPayloadP2pNodes r = [ p2pRunNode (_payloadResP2pNode r) ] -- -------------------------------------------------------------------------- -- -- Payload Service API Resources -data PayloadServiceApiResources = PayloadServiceApiResources - { _payloadResServiceApi :: !SomeApi - , _payloadResServiceServer :: !SomeServer +newtype PayloadServiceApiResources = PayloadServiceApiResources + { _payloadResServiceServer :: SomeServer } + deriving newtype (Semigroup, Monoid) payloadServiceApiResources :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) tbl @@ -202,10 +207,22 @@ payloadServiceApiResources -> tbl -> PayloadServiceApiResources payloadServiceApiResources config pdb = PayloadServiceApiResources - { _payloadResServiceApi = SomeApi (payloadApi @v @c @p) - , _payloadResServiceServer = somePayloadServer @_ @v @c @p batchLimit pdb + { _payloadResServiceServer = somePayloadServer @_ @v @c @p batchLimit pdb } - where + where + batchLimit = int $ _serviceApiPayloadBatchLimit config + +pactPayloadServiceApiResources + :: forall tbl + . HasVersion + => CanReadablePayloadCas tbl + => ServiceApiConfig + -> PactPayload.RestAPI.SomePayloadDb tbl + -> PayloadServiceApiResources +pactPayloadServiceApiResources config pdb = PayloadServiceApiResources + { _payloadResServiceServer = PactPayload.RestAPI.Server.somePayloadServer @_ batchLimit pdb + } + where batchLimit = int $ _serviceApiPayloadBatchLimit config -- -------------------------------------------------------------------------- -- @@ -226,6 +243,7 @@ withPayloadProviderResources => HasVersion => logger -> ChainId + -> ServiceApiConfig -> Maybe (P2pConfiguration, PeerInfo, PeerDb, HTTP.Manager) -> RocksDb -> RewindLimit @@ -238,7 +256,7 @@ withPayloadProviderResources -- default db location from the chainweb configuration. -> PayloadProviderConfig -> ResourceT IO ProviderResources -withPayloadProviderResources logger cid peerStuff rdb rewindLimit initialUnlimitedRewind defaultPactDbDir configs = do +withPayloadProviderResources logger cid serviceApiConfig peerStuff rdb rewindLimit initialUnlimitedRewind defaultPactDbDir configs = do SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal SomeChainIdT @c' _ <- return $ someChainIdVal cid withSomeSing provider $ \case @@ -259,9 +277,11 @@ withPayloadProviderResources logger cid peerStuff rdb rewindLimit initialUnlimit p2pRes <- liftIO $ forM peerStuff $ \(p2pConfig, myPeerInfo, peerDb, mgr) -> payloadP2pResources @v' @c' @'MinimalProvider logger p2pConfig myPeerInfo peerDb pdb queue mgr + let serviceRes = + payloadServiceApiResources @v' @c' @'MinimalProvider serviceApiConfig pdb return ProviderResources { _providerResPayloadProvider = ConfiguredPayloadProvider p - , _providerResServiceApi = Nothing + , _providerResServiceApi = Just serviceRes , _providerResP2pApiResources = p2pRes } @@ -328,6 +348,8 @@ withPayloadProviderResources logger cid peerStuff rdb rewindLimit initialUnlimit p2pRes <- liftIO $ forM peerStuff $ \(p2pConfig, myPeerInfo, peerDb, mgr) -> payloadP2pResources @v' @c' @'PactProvider logger p2pConfig myPeerInfo peerDb pdb queue mgr + let serviceApiPayloadServer = pactPayloadServiceApiResources serviceApiConfig + (PactPayload.RestAPI.SomePayloadDb $ PactPayload.RestAPI.PayloadDb' @_ @v' @c' pdb) let pactServerData = Pact.PactServerData { Pact._pactServerDataLogger = pactPayloadProviderLogger pp @@ -336,15 +358,11 @@ withPayloadProviderResources logger cid peerStuff rdb rewindLimit initialUnlimit , Pact._pactServerDataPact = pactPayloadProviderServiceEnv pp } - let pactServer = Pact.somePactServer (Pact.somePactServerData cid pactServerData) + -- this is a bit of a misnomer, as it's not a payload API + let pactServer = PayloadServiceApiResources $ Pact.somePactServer (Pact.somePactServerData cid pactServerData) return ProviderResources { _providerResPayloadProvider = ConfiguredPayloadProvider pp - , _providerResServiceApi = Just $ PayloadServiceApiResources - -- TODO: I think this isn't what was in mind for this... - -- this seems to really just be for the payload API - { _payloadResServiceApi = Pact.somePactServiceApi cid - , _payloadResServiceServer = pactServer - } + , _providerResServiceApi = Just $ pactServer <> serviceApiPayloadServer , _providerResP2pApiResources = p2pRes } @@ -359,13 +377,19 @@ withPayloadProviderResources logger cid peerStuff rdb rewindLimit initialUnlimit p <- withEvmPayloadProvider logger cid rdb (view _4 <$> peerStuff) config let pdb = view evmPayloadDb p let queue = view evmPayloadQueue p - p2pRes <- liftIO $ forM peerStuff $ \(p2pConfig, myPeerInfo, peerDb, mgr) -> - payloadP2pResources @v' @c' @('EvmProvider n) - logger p2pConfig myPeerInfo peerDb pdb queue mgr + apiRes <- liftIO $ forM peerStuff $ \(p2pConfig, myPeerInfo, peerDb, mgr) -> do + p2pRes <- + payloadP2pResources @v' @c' @('EvmProvider n) + logger p2pConfig myPeerInfo peerDb pdb queue mgr + let + serviceRes = + payloadServiceApiResources @v' @c' @('EvmProvider n) serviceApiConfig pdb + return (p2pRes, serviceRes) + return ProviderResources { _providerResPayloadProvider = ConfiguredPayloadProvider p - , _providerResServiceApi = Nothing - , _providerResP2pApiResources = p2pRes + , _providerResServiceApi = snd <$> apiRes + , _providerResP2pApiResources = fst <$> apiRes } _ -> return $ ProviderResources DisabledPayloadProvider Nothing Nothing @@ -412,6 +436,7 @@ withChainResources -- payload providers live within chainweb-consensus they inherit the -- default db location from the chainweb configuration. -> P2pConfiguration + -> ServiceApiConfig -> PeerInfo -> PeerDb -> RewindLimit @@ -420,7 +445,7 @@ withChainResources -- ^ whether to allow unlimited rewind on startup -> PayloadProviderConfig -> ResourceT IO (ChainResources logger) -withChainResources logger cid rdb mgr defaultPactDbDir p2pConf myInfo peerDb rewindLimit initialUnlimitedRewind configs = do +withChainResources logger cid rdb mgr defaultPactDbDir p2pConf serviceConf myInfo peerDb rewindLimit initialUnlimitedRewind configs = do -- This uses the the CutNetwork for fetching block headers. cdb <- withBlockHeaderDb rdb cid @@ -428,7 +453,8 @@ withChainResources logger cid rdb mgr defaultPactDbDir p2pConf myInfo peerDb rew -- Payload Providers are using per chain payload networks for fetching -- block headers. provider <- withPayloadProviderResources - providerLogger cid (Just (p2pConf, myInfo, peerDb, mgr)) rdb rewindLimit initialUnlimitedRewind defaultPactDbDir configs + providerLogger cid serviceConf (Just (p2pConf, myInfo, peerDb, mgr)) + rdb rewindLimit initialUnlimitedRewind defaultPactDbDir configs return ChainResources { _chainResBlockHeaderDb = cdb diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 1b13e73bb5..9e5fff9fe9 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -98,7 +98,7 @@ import Chainweb.Logger import System.LogLevel import Chainweb.Chainweb.ChainResources (withPayloadProviderResources, providerResPayloadProvider) import P2P.Node.Configuration (defaultP2pConfiguration) -import Chainweb.Chainweb.Configuration (PayloadProviderConfig(PayloadProviderConfig), defaultPayloadProviderConfig) +import Chainweb.Chainweb.Configuration (PayloadProviderConfig(PayloadProviderConfig), defaultPayloadProviderConfig, defaultServiceApiConfig) import Chainweb.Test.Pact.Utils (getTestLogger) import qualified Data.HashSet as HS @@ -512,6 +512,7 @@ testCutPruning rdb = testCase "cut pruning" $ runResourceT $ withVersion (barebo pps <- tabulateChainsM $ \cid -> view providerResPayloadProvider <$> withPayloadProviderResources testLogger cid + defaultServiceApiConfig Nothing rdb (RewindLimit 10) @@ -550,6 +551,7 @@ testCutGet rdb = testCase "cut get" $ withVersion (barebonesTestVersion pairChai pps <- tabulateChainsM $ \cid -> view providerResPayloadProvider <$> withPayloadProviderResources (genericLogger Error (\_ -> return ())) cid + defaultServiceApiConfig Nothing rdb (RewindLimit 10) From 5750d4a7e548fdc41cfe515df27916b885580efc Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 3 Jul 2025 21:11:12 +0200 Subject: [PATCH 297/378] be lenient during rewind when reintroducing transactions --- src/Chainweb/Pact/PactService.hs | 143 ++++++++++----------- src/Chainweb/Pact/PactService/ExecBlock.hs | 16 +-- 2 files changed, 76 insertions(+), 83 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 5c7da964c5..11da6d4f36 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -40,90 +40,79 @@ module Chainweb.Pact.PactService ) where import Control.Concurrent.Async -import Control.Concurrent.STM -import Control.Exception.Safe (mask) -import Control.Lens hiding ((:>)) -import Control.Monad -import Control.Monad.Cont (evalContT) -import Control.Monad.Except -import Control.Monad.Reader -import Control.Monad.State.Strict -import Control.Monad.Trans.Resource - -import Data.Either -import Data.Foldable (traverse_) -import qualified Data.HashMap.Strict as HM -import Data.Maybe -import Data.Monoid -import Data.Pool (Pool) -import qualified Data.Text as Text -import Data.Vector (Vector) -import qualified Data.Vector as V - -import System.IO -import System.LogLevel - -import Prelude hiding (lookup) - -import qualified Pact.JSON.Encode as J - -import qualified Pact.Core.Gas as Pact - -import qualified Chainweb.Pact.TransactionExec as Pact -import qualified Chainweb.Pact.Validations as Pact - import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainId +import Chainweb.Core.Brief +import Chainweb.Counter import Chainweb.Logger import Chainweb.Mempool.Mempool as Mempool import Chainweb.Miner.Pact - +import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb +import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils (withSavepoint, SavepointName (..)) +import Chainweb.Pact.NoCoinbase qualified as Pact +import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer import Chainweb.Pact.PactService.ExecBlock -import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact +import Chainweb.Pact.PactService.ExecBlock qualified as Pact +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.TransactionExec qualified as Pact import Chainweb.Pact.Types --- import Chainweb.Pact.SPV qualified as Pact +import Chainweb.Pact.Validations qualified as Pact import Chainweb.Parent import Chainweb.Payload import Chainweb.Payload.PayloadStore +import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.P2P +import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest +import Chainweb.Ranked +import Chainweb.Storage.Table +import Chainweb.Storage.Table.Map qualified as MapTable import Chainweb.Time -import qualified Chainweb.Pact.Transaction as Pact import Chainweb.Utils hiding (check) import Chainweb.Version -import Chainweb.Counter -import Pact.Core.Command.Types qualified as Pact -import Pact.Core.Hash qualified as Pact +import Control.Concurrent.STM +import Control.Exception.Safe (mask) +import Control.Lens hiding ((:>)) +import Control.Monad +import Control.Monad.Cont (evalContT) +import Control.Monad.Except +import Control.Monad.Reader +import Control.Monad.State.Strict +import Control.Monad.Trans.Resource +import Control.Parallel.Strategies qualified as Strategies +import Data.Align import Data.ByteString.Short qualified as SB import Data.Coerce (coerce) +import Data.DList qualified as DList +import Data.Either +import Data.Foldable (traverse_) +import Data.HashMap.Strict qualified as HM +import Data.List.NonEmpty qualified as NEL +import Data.List.NonEmpty qualified as NonEmpty +import Data.Maybe +import Data.Monoid +import Data.Pool (Pool) +import Data.Pool qualified as Pool +import Data.Text qualified as Text +import Data.Vector (Vector) +import Data.Vector qualified as V import Data.Void -import Chainweb.Pact.PactService.ExecBlock qualified as Pact -import qualified Pact.Core.Evaluate as Pact -import qualified Pact.Core.Errors as Pact -import Chainweb.Pact.Backend.Types -import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer -import qualified Pact.Core.StableEncoding as Pact -import qualified Data.List.NonEmpty as NonEmpty -import Chainweb.PayloadProvider -import Chainweb.Storage.Table -import qualified Chainweb.Storage.Table.Map as MapTable -import Chainweb.PayloadProvider.P2P -import P2P.TaskQueue (Priority(..)) -import qualified Network.HTTP.Client as HTTP -import qualified Chainweb.PayloadProvider.P2P.RestAPI.Client as Rest -import Chainweb.Pact.Backend.Utils (withSavepoint, SavepointName (..)) -import qualified Data.DList as DList -import Chainweb.Ranked -import qualified Chainweb.Pact.Backend.ChainwebPactDb as ChainwebPactDb -import qualified Pact.Core.ChainData as Pact import GHC.Stack (HasCallStack) -import qualified Data.Pool as Pool -import qualified Data.List.NonEmpty as NEL -import qualified Control.Parallel.Strategies as Strategies -import qualified Chainweb.Pact.NoCoinbase as Pact -import Chainweb.Core.Brief -import Data.Align -import qualified Data.List.NonEmpty as NE +import Network.HTTP.Client qualified as HTTP +import P2P.TaskQueue (Priority(..)) +import Pact.Core.ChainData qualified as Pact +import Pact.Core.Command.Types qualified as Pact +import Pact.Core.Errors qualified as Pact +import Pact.Core.Evaluate qualified as Pact +import Pact.Core.Gas qualified as Pact +import Pact.Core.Hash qualified as Pact +import Pact.Core.StableEncoding qualified as Pact +import Pact.JSON.Encode qualified as J +import Prelude hiding (lookup) +import System.LogLevel withPactService :: (Logger logger, CanPayloadCas tbl) @@ -562,7 +551,7 @@ syncToFork logger serviceEnv hints forkInfo = do let validatedTxs = msum blockResults Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState return (rewoundTxs, validatedTxs, forkInfo._forkInfoTargetState) - liftIO $ mpaProcessFork memPoolAccess (rewoundTxs, validatedTxs) + liftIO $ mpaProcessFork memPoolAccess (V.concat rewoundTxs, validatedTxs) case forkInfo._forkInfoNewBlockCtx of Just newBlockCtx | Just _ <- _psMiner serviceEnv @@ -630,16 +619,22 @@ syncToFork logger serviceEnv hints forkInfo = do -- remember to call this *before* executing the actual rewind, -- and only alter the mempool *after* the db transaction is done. - getRewoundTxs :: Parent BlockHeight -> IO (Vector Pact.Transaction) + getRewoundTxs :: Parent BlockHeight -> IO [Vector Pact.Transaction] getRewoundTxs rewindTargetHeight = do rewoundPayloadHashes <- Checkpointer.getPayloadsAfter sql rewindTargetHeight - rewoundPayloads <- liftIO $ fmap fromJuste <$> - lookupPayloadDataWithHeightBatch - (_payloadStoreTable pdb) - [(Just (rank rbph), _ranked rbph) | rbph <- rewoundPayloadHashes] - V.concat <$> traverse - (fmap (fromRight (error "invalid payload in database")) . runExceptT . pact5TransactionsFromPayload) - rewoundPayloads + rewoundPayloads <- lookupPayloadDataWithHeightBatch + (_payloadStoreTable pdb) + [(Just (rank rbph), _ranked rbph) | rbph <- rewoundPayloadHashes] + forM (zip rewoundPayloadHashes rewoundPayloads) $ \case + (rbph, Nothing) -> do + logFunctionText logger Error $ "missing payload in database: " <> brief rbph + return V.empty + (rbph, Just payload) -> case pact5TransactionsFromPayload payload of + Right txs -> do + return txs + Left err -> do + logFunctionText logger Error $ "invalid payload in database (" <> brief rbph <> "): " <> sshow err + return V.empty -- | Start a thread that makes fresh payloads periodically startPayloadRefresher :: Logger logger => HasVersion => logger -> ServiceEnv tbl -> BlockInProgress -> IO () diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index 9f9731704b..b9843dbc85 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -488,14 +488,12 @@ validateParsedChainwebTx _logger blockEnv tx signers = Pact._pSigners $ view Pact.payloadObj $ Pact._cmdPayload t pact5TransactionsFromPayload - :: forall m - . MonadIO m - => PayloadData - -> ExceptT BlockInvalidError m (Vector Pact.Transaction) + :: PayloadData + -> Either BlockInvalidError (Vector Pact.Transaction) pact5TransactionsFromPayload plData = do - vtrans <- liftIO $ - mapM toCWTransaction $ - toList (view payloadDataTransactions plData) + let vtrans = + map toCWTransaction $ + toList (view payloadDataTransactions plData) let (theLefts, theRights) = partitionEithers vtrans unless (null theLefts) $ do let ls = map T.pack theLefts @@ -503,7 +501,7 @@ pact5TransactionsFromPayload plData = do return $! V.fromList theRights where toCWTransaction bs = - evaluate (force (codecDecode commandCodec $ _transactionBytes bs)) + codecDecode commandCodec (_transactionBytes bs) execExistingBlock :: (CanReadablePayloadCas tbl, Logger logger) @@ -517,7 +515,7 @@ execExistingBlock logger serviceEnv blockEnv payload = do let blockCtx = _psBlockCtx blockEnv let plData = checkablePayloadToPayloadData payload miner :: Miner <- decodeStrictOrThrow (_minerData $ view payloadDataMiner plData) - txs <- lift $ pact5TransactionsFromPayload plData + txs <- liftEither $ pact5TransactionsFromPayload plData let errors <- liftIO $ flip foldMap txs $ \tx -> do errorOrSuccess <- runExceptT $ From 3776ec17c8697e851f2283b1105f43a24913c804 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 31 Jul 2025 11:49:24 -0400 Subject: [PATCH 298/378] Remove from and reorder knownVersions Change-Id: Id000000011569e25d5033fa63de63fec483b8cc2 --- src/Chainweb/Version/Registry.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index 9d4e99946b..bb286e0df3 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -80,11 +80,11 @@ knownVersions = [ mainnet , testnet04 , evmTestnet - , recapDevnet , devnet , evmDevnet - , evmDevnetSingleton - , evmDevnetPair + , recapDevnet + -- , evmDevnetSingleton + -- , evmDevnetPair ] -- | Look up a known version by name, usually with `m` instantiated to some From 554eb61298cb7ba7c490c7ed0f52faa3952cb26a Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 31 Jul 2025 11:41:03 -0400 Subject: [PATCH 299/378] Move header stream config into its own area Change-Id: Id000000014dc8554cba51f00aec7711cd8856335 --- src/Chainweb/Chainweb.hs | 2 +- src/Chainweb/Chainweb/Configuration.hs | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 3b21ca75bb..e2b892434e 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -801,7 +801,7 @@ runChainweb cw nowServing = do , _chainwebServerPeerDbs = [] } (_chainwebCoordinator cw) - (HeaderStream . _configHeaderStream $ _chainwebConfig cw) + (HeaderStream . _serviceApiConfigHeaderStream . _configServiceApi $ _chainwebConfig cw) (_chainwebBackup cw <$ guard backupApiEnabled) (_serviceApiPayloadBatchLimit . _configServiceApi $ _chainwebConfig cw) mw diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 1209f2795c..a228977f2a 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -48,6 +48,7 @@ module Chainweb.Chainweb.Configuration , ServiceApiConfig(..) , serviceApiConfigPort , serviceApiConfigInterface +, serviceApiConfigHeaderStream , defaultServiceApiConfig , pServiceApiConfig @@ -62,7 +63,6 @@ module Chainweb.Chainweb.Configuration , configChainwebVersion , configCuts , configMining -, configHeaderStream , configP2p , configThrottling , configReorgLimit @@ -399,6 +399,8 @@ data ServiceApiConfig = ServiceApiConfig , _serviceApiPayloadBatchLimit :: PayloadBatchLimit -- ^ maximum size for payload batches on the service API. Default is -- 'Chainweb.Payload.RestAPI.defaultServicePayloadBatchLimit'. + , _serviceApiConfigHeaderStream :: !Bool + -- ^ whether to serve a header update stream endpoint. } deriving (Show, Eq, Generic) @@ -410,6 +412,7 @@ defaultServiceApiConfig = ServiceApiConfig , _serviceApiConfigInterface = "*" , _serviceApiConfigValidateSpec = False , _serviceApiPayloadBatchLimit = defaultServicePayloadBatchLimit + , _serviceApiConfigHeaderStream = False } instance ToJSON ServiceApiConfig where @@ -418,6 +421,7 @@ instance ToJSON ServiceApiConfig where , "interface" .= hostPreferenceToText (_serviceApiConfigInterface o) , "validateSpec" .= _serviceApiConfigValidateSpec o , "payloadBatchLimit" .= _serviceApiPayloadBatchLimit o + , "headerStream" .= _serviceApiConfigHeaderStream o ] instance FromJSON (ServiceApiConfig -> ServiceApiConfig) where @@ -426,6 +430,7 @@ instance FromJSON (ServiceApiConfig -> ServiceApiConfig) where <*< setProperty serviceApiConfigInterface "interface" (parseJsonFromText "interface") o <*< serviceApiConfigValidateSpec ..: "validateSpec" % o <*< serviceApiPayloadBatchLimit ..: "payloadBatchLimit" % o + <*< serviceApiConfigHeaderStream ..: "headerStream" % o pServiceApiConfig :: MParser ServiceApiConfig pServiceApiConfig = id @@ -440,6 +445,10 @@ pServiceApiConfig = id <*< serviceApiConfigValidateSpec .:: enableDisableFlag % prefixLong service "validate-spec" <> internal -- hidden option, for expert use + <*< serviceApiConfigHeaderStream .:: boolOption_ + % prefixLong service "header-stream" + <> help "whether to enable an endpoint for streaming block updates" + where service = Just "service" @@ -505,7 +514,6 @@ data ChainwebConfiguration = ChainwebConfiguration { _configChainwebVersion :: !ChainwebVersion , _configCuts :: !CutConfig , _configMining :: !MiningConfig - , _configHeaderStream :: !Bool , _configP2p :: !P2pConfiguration , _configThrottling :: !ThrottlingConfig , _configReorgLimit :: !RewindLimit @@ -566,7 +574,6 @@ defaultChainwebConfiguration v = ChainwebConfiguration { _configChainwebVersion = v , _configCuts = defaultCutConfig , _configMining = defaultMining - , _configHeaderStream = False , _configP2p = defaultP2pConfiguration , _configThrottling = defaultThrottlingConfig , _configReorgLimit = defaultReorgLimit @@ -583,7 +590,6 @@ instance ToJSON ChainwebConfiguration where [ "chainwebVersion" .= _versionName (_configChainwebVersion o) , "cuts" .= _configCuts o , "mining" .= _configMining o - , "headerStream" .= _configHeaderStream o , "p2p" .= _configP2p o , "throttling" .= _configThrottling o , "reorgLimit" .= _configReorgLimit o @@ -604,7 +610,6 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where (findKnownVersion <=< parseJSON) o <*< configCuts %.: "cuts" % o <*< configMining %.: "mining" % o - <*< configHeaderStream ..: "headerStream" % o <*< configP2p %.: "p2p" % o <*< configThrottling %.: "throttling" % o <*< configReorgLimit ..: "reorgLimit" % o @@ -618,9 +623,6 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where pChainwebConfiguration :: MParser ChainwebConfiguration pChainwebConfiguration = id <$< configChainwebVersion %:: parseVersion - <*< configHeaderStream .:: boolOption_ - % long "header-stream" - <> help "whether to enable an endpoint for streaming block updates" <*< parserOptionGroup "P2P" (configP2p %:: pP2pConfiguration) <*< configReorgLimit .:: jsonOption % long "reorg-limit" From c4b923a675f5a5893be4c3865d8f9d98259298a4 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 31 Jul 2025 11:47:26 -0400 Subject: [PATCH 300/378] Add examples to version option Change-Id: Id00000002aa16bd774416bfd6d9b44ae1526eec6 --- src/Chainweb/Chainweb/Configuration.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index a228977f2a..911406d663 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -654,6 +654,8 @@ parseVersion = constructVersion % long "chainweb-version" <> short 'v' <> help "the chainweb version that this node is using" + <> metavar (T.unpack $ + "[" <> T.intercalate "," (getChainwebVersionName . _versionName <$> knownVersions) <> "]") ) <*> optional (textOption @Fork (long "fork-upper-bound" <> help "(development mode only) the latest fork the node will enable")) <*> optional (BlockDelay <$> textOption (long "block-delay" <> help "(development mode only) the block delay in seconds per block")) From 00c9430ff1bf22c245facd965d07b2ea0a5a7413 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 31 Jul 2025 11:48:40 -0400 Subject: [PATCH 301/378] Improved error messaging around EVM PP init Change-Id: Id0000000a95d821cbb42fa0640dcd4bfbeb83d3a --- src/Chainweb/PayloadProvider/EVM.hs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 4bbeb379d4..16e8f84b0d 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -418,11 +418,20 @@ logg p l t = logFunctionText (_evmLogger p) l t -- Exceptions data EvmExecutionEngineException - = EvmChainIdMissmatch (Expected EVM.ChainId) (Actual EVM.ChainId) - | EvmInvalidGensisHeader (Expected BlockPayloadHash) (Actual BlockPayloadHash) - deriving (Show, Eq, Generic) + = EvmChainIdMismatch (Expected EVM.ChainId) (Actual EVM.ChainId) + | EvmInvalidGenesisHeader (Expected BlockPayloadHash) (Actual BlockPayloadHash) + deriving (Eq, Generic) + +instance Show EvmExecutionEngineException where + show = displayException -instance Exception EvmExecutionEngineException +instance Exception EvmExecutionEngineException where + displayException (EvmChainIdMismatch e a) = + "Mismatched chain IDs: " <> sshow e <> ", " <> sshow a + <> ". The most probable cause is a mismatch between chainweb network-id and chainspec files." + displayException (EvmInvalidGenesisHeader e a) = + "Invalid genesis header: " <> sshow e <> ", " <> sshow a + <> ". The most probable cause is a mismatch between chainweb network-id and chainspec files." -- | Raised when an EVM header ca nnot be found data EvmHeaderNotFoundException @@ -579,12 +588,12 @@ checkExecutionClient logger c ctx expectedEcid = do error "Error connecting to EVM" Right ecid -> return ecid unless (expectedEcid == ecid) $ - throwM $ EvmChainIdMissmatch (Expected expectedEcid) (Actual ecid) + throwM $ EvmChainIdMismatch (Expected expectedEcid) (Actual ecid) callMethodHttp @Eth_GetBlockByNumber ctx (DefaultBlockNumber 0, False) >>= \case Nothing -> throwM EvmGenesisHeaderNotFound Just h -> do unless (EVM._hdrPayloadHash h == expectedGenesisHeader) $ - throwM $ EvmInvalidGensisHeader + throwM $ EvmInvalidGenesisHeader (Expected expectedGenesisHeader) (Actual $ EVM._hdrPayloadHash h) return h From c26e30792e7f73ee320260944b8d546b3ad1b339 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 31 Jul 2025 11:40:39 -0400 Subject: [PATCH 302/378] Put minimal PP options into a group Change-Id: Id00000001d225a8fefe8038c380cd3b06b02f734 --- src/Chainweb/Chainweb/Configuration.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 911406d663..3d0d47ffe7 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -251,7 +251,8 @@ instance FromJSON (PayloadProviderConfig -> PayloadProviderConfig) where -- pPayloadProviderConfig :: MParser PayloadProviderConfig pPayloadProviderConfig = id - <$< payloadProviderConfigMinimal %:: pMinimalProviderConfig + <$< parserOptionGroup "Minimal Payload Provider" + (payloadProviderConfigMinimal %:: pMinimalProviderConfig) <*< pevm where cids = [ unsafeChainId i | i <- [0..100]] From 2942a03ecf5597a30ec590f2cc0a086aec6a6943 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 31 Jul 2025 11:49:47 -0400 Subject: [PATCH 303/378] Typo fix Change-Id: Id00000000c09e5fffcffdd5c3819688c8a4118b5 --- src/Network/X509/SelfSigned.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Network/X509/SelfSigned.hs b/src/Network/X509/SelfSigned.hs index 04c541a9b3..226fa35ad9 100644 --- a/src/Network/X509/SelfSigned.hs +++ b/src/Network/X509/SelfSigned.hs @@ -720,7 +720,7 @@ certificateCache query = ValidationCache queryCallback (\_ _ _ -> return ()) <> " expected fingerprint: " <> T.unpack (fingerprintToText f) <> " but got fingerprint: " <> T.unpack (fingerprintToText fp) --- | Check whether a connection failed due to an certificate missmatch +-- | Check whether a connection failed due to an certificate mismatch -- isCertificateMismatchException :: HttpException -> Bool isCertificateMismatchException (HttpExceptionRequest _ (InternalException e)) = From 98fff0e2393f100bce0b66d6c56bba60bfda625e Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 31 Jul 2025 11:49:08 -0400 Subject: [PATCH 304/378] Delete pact queue size option There is no more pact queue Change-Id: Id0000000cf6e3c746b03f249901af373d9e013dd --- src/Chainweb/PayloadProvider/Pact/Configuration.hs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Chainweb/PayloadProvider/Pact/Configuration.hs b/src/Chainweb/PayloadProvider/Pact/Configuration.hs index 599cfb110a..247f7a221c 100644 --- a/src/Chainweb/PayloadProvider/Pact/Configuration.hs +++ b/src/Chainweb/PayloadProvider/Pact/Configuration.hs @@ -19,7 +19,6 @@ module Chainweb.PayloadProvider.Pact.Configuration , pactConfigBlockGasLimit , pactConfigLogGas , pactConfigMinGasPrice -, pactConfigPactQueueSize , pactConfigPreInsertCheckTimeout , pactConfigAllowReadsInLocal , pactConfigFullHistoricPactState @@ -81,7 +80,6 @@ data PactProviderConfig = PactProviderConfig , _pactConfigBlockGasLimit :: !Mempool.GasLimit , _pactConfigLogGas :: !Bool , _pactConfigMinGasPrice :: !Mempool.GasPrice - , _pactConfigPactQueueSize :: !Natural , _pactConfigPreInsertCheckTimeout :: !Micros , _pactConfigAllowReadsInLocal :: !Bool @@ -104,7 +102,6 @@ instance ToJSON PactProviderConfig where , "gasLimitOfBlock" .= J.toJsonViaEncode (StableEncoding $ _pactConfigBlockGasLimit o) , "logGas" .= _pactConfigLogGas o , "minGasPrice" .= J.toJsonViaEncode (StableEncoding $ _pactConfigMinGasPrice o) - , "pactQueueSize" .= _pactConfigPactQueueSize o , "preInsertCheckTimeout" .= _pactConfigPreInsertCheckTimeout o , "allowReadsInLocal" .= _pactConfigAllowReadsInLocal o , "fullHistoricPactState" .= _pactConfigFullHistoricPactState o @@ -121,7 +118,6 @@ instance FromJSON (PactProviderConfig -> PactProviderConfig) where <*< pactConfigBlockGasLimit . iso StableEncoding _stableEncoding ..: "gasLimitOfBlock" % o <*< pactConfigLogGas ..: "logGas" % o <*< pactConfigMinGasPrice . iso StableEncoding _stableEncoding ..: "minGasPrice" % o - <*< pactConfigPactQueueSize ..: "pactQueueSize" % o <*< pactConfigPreInsertCheckTimeout ..: "preInsertCheckTimeout" % o <*< pactConfigAllowReadsInLocal ..: "allowReadsInLocal" % o <*< pactConfigFullHistoricPactState ..: "fullHistoricPactState" % o @@ -137,7 +133,6 @@ defaultPactProviderConfig = PactProviderConfig , _pactConfigBlockGasLimit = Pact.GasLimit (Pact.Gas 150_000) , _pactConfigLogGas = False , _pactConfigMinGasPrice = Pact.GasPrice 1e-8 - , _pactConfigPactQueueSize = 2000 , _pactConfigPreInsertCheckTimeout = defaultPreInsertCheckTimeout , _pactConfigAllowReadsInLocal = False , _pactConfigFullHistoricPactState = True @@ -162,9 +157,6 @@ pPactProviderConfig cid = id <*< pactConfigMinGasPrice . iso StableEncoding _stableEncoding .:: jsonOption % prefixLongCid cid "pact-min-gas-price" <> helpCid cid "the gas price of an individual transaction in a block must not be beneath this number" - <*< pactConfigPactQueueSize .:: jsonOption - % prefixLongCid cid "pact-queue-size" - <> helpCid cid "max size of pact internal queue" <*< pactConfigPreInsertCheckTimeout .:: jsonOption % prefixLongCid cid "pact-pre-insert-check-timeout" <> helpCid cid "Max allowed time in microseconds for the transactions validation in the PreInsertCheck command." From a82a5e41e1681829c5bcdae1bb814b0f95ea66fc Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 5 Aug 2025 12:27:34 -0400 Subject: [PATCH 305/378] Mark unsafeFromText NOINLINE --- src/Chainweb/Utils.hs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index b96d64f72e..95558674c1 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -659,10 +659,13 @@ eitherFromText = either f return . fromText -- | Unsafely decode a value rom its textual representation. It is an program -- error if decoding fails. +-- Marked NOINLINE heuristically, because usually the runtime performance is not +-- very important and if applied to a lot of literals at once (as in the +-- *Payload files) it can result in severe code blowup, slowing compilation. -- unsafeFromText :: HasCallStack => HasTextRepresentation a => T.Text -> a unsafeFromText = fromJuste . fromText -{-# INLINE unsafeFromText #-} +{-# NOINLINE unsafeFromText #-} -- | Run a 'A.Parser' on a text input. All input must be consume by the parser. -- A 'TextFormatException' is thrown if parsing fails. From 834e09bddfc701543bd5368fbae0d2a440c45237 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 5 Aug 2025 20:24:16 -0400 Subject: [PATCH 306/378] Fix computeMerkleLogRoot This function was trusting its input, rather than actually computing the root. --- src/Chainweb/Crypto/MerkleLog.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Crypto/MerkleLog.hs b/src/Chainweb/Crypto/MerkleLog.hs index a1c0e8978c..f485c62d26 100644 --- a/src/Chainweb/Crypto/MerkleLog.hs +++ b/src/Chainweb/Crypto/MerkleLog.hs @@ -594,7 +594,8 @@ computeMerkleLogRoot . HasMerkleLog a u b => b -> MerkleRoot a -computeMerkleLogRoot = _merkleLogRoot . toLog @a +computeMerkleLogRoot = + merkleRoot . toList . mapLogEntries (toMerkleNodeTagged @a) . _merkleLogEntries . toLog @a {-# INLINE computeMerkleLogRoot #-} -- -------------------------------------------------------------------------- -- From d9b4ba7b840654193314e0651ed791032fbefbc1 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 5 Aug 2025 21:03:23 -0400 Subject: [PATCH 307/378] Use recapDevnet everywhere in prop_da_validate It was using recapDevnet headers with HasVersion set to mainnet --- test/unit/Chainweb/Test/BlockHeader/Validation.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/Chainweb/Test/BlockHeader/Validation.hs b/test/unit/Chainweb/Test/BlockHeader/Validation.hs index 18b82a7c43..add909ac86 100644 --- a/test/unit/Chainweb/Test/BlockHeader/Validation.hs +++ b/test/unit/Chainweb/Test/BlockHeader/Validation.hs @@ -69,7 +69,7 @@ tests = testGroup "Chainweb.Test.Blockheader.Validation" [ prop_validateMainnet , prop_validateTestnet04 , withVersion mainnet prop_fail_validate - , withVersion mainnet prop_da_validate + , prop_da_validate , withVersion mainnet prop_legacy_da_validate , withVersion (barebonesTestVersion petersenChainGraph) prop_featureFlag 10 , testProperty "validate arbitrary test header" $ \v -> withVersion v prop_validateArbitrary @@ -146,8 +146,8 @@ prop_validateHeader msg h = testCase msg $ do prop_fail_validate :: HasVersion => TestTree prop_fail_validate = validate_cases "validate invalid BlockHeaders" validationFailures -prop_da_validate :: HasVersion => TestTree -prop_da_validate = validate_cases "difficulty adjustment validation" daValidation +prop_da_validate :: TestTree +prop_da_validate = withVersion recapDevnet $ validate_cases "difficulty adjustment validation" daValidation prop_legacy_da_validate :: HasVersion => TestTree prop_legacy_da_validate = validate_cases "legacy difficulty adjustment validation" legacyDaValidation From a514284ada896577c773491011ee62c1c3150f30 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 5 Aug 2025 21:58:05 -0400 Subject: [PATCH 308/378] Use real nonce in encodeAsMiningWork Required to correctly compute blockPow --- src/Chainweb/BlockHeader/Internal.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index 365e9e7466..66cfcb1913 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -863,7 +863,7 @@ encodeAsMiningWork b = do encodeBlockHeight (_blockHeight b) encodeChainwebVersionCode (_blockChainwebVersion b) encodeEpochStartTime (_blockEpochStart b) - encodeNonce (Nonce 0) + encodeNonce (_blockNonce b) -- | Decode and check that -- From c3897a426595ec1d2f5f35f8f671e3557b61bf63 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 5 Aug 2025 21:58:14 -0400 Subject: [PATCH 309/378] Fix header validation test Now we don't ever check conditions for versions unless they're the HasVersion version --- test/unit/Chainweb/Test/BlockHeader/Validation.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/unit/Chainweb/Test/BlockHeader/Validation.hs b/test/unit/Chainweb/Test/BlockHeader/Validation.hs index add909ac86..03faebb1da 100644 --- a/test/unit/Chainweb/Test/BlockHeader/Validation.hs +++ b/test/unit/Chainweb/Test/BlockHeader/Validation.hs @@ -240,7 +240,11 @@ validationFailures = , [IncorrectHash, IncorrectPow, ChainMismatch, AdjacentChainMismatch] ) , ( hdr & testHeaderHdr . blockChainwebVersion .~ _versionCode RecapDevelopment - , [IncorrectHash, IncorrectPow, VersionMismatch, InvalidFeatureFlags, CreatedBeforeParent, AdjacentChainMismatch, InvalidAdjacentVersion] + -- , [IncorrectHash, IncorrectPow, VersionMismatch, InvalidFeatureFlags, CreatedBeforeParent, AdjacentChainMismatch, InvalidAdjacentVersion] + -- no longer. we don't check InvalidFeatureFlags according to the version + -- in the header anymore, but in the HasVersion version. Doesn't matter + -- materially because we get a VersionMismatch error anyway. + , [IncorrectHash, IncorrectPow, VersionMismatch, InvalidAdjacentVersion] ) , ( hdr & testHeaderHdr . blockWeight .~ 10 , [IncorrectHash, IncorrectPow, IncorrectWeight] From 6716e22f7f086d5c0fe4849ddbc603db5abc789e Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 5 Aug 2025 21:58:30 -0400 Subject: [PATCH 310/378] Nits --- src/Chainweb/Version/RecapDevelopment.hs | 2 +- test/unit/Chainweb/Test/BlockHeader/Validation.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index eaab20cbdc..5ec679dea1 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -65,7 +65,7 @@ recapDevnet = withVersion recapDevnet ChainwebVersion Chainweb224Pact -> onAllChains ForkAtGenesis Chainweb225Pact -> onAllChains ForkAtGenesis Chainweb226Pact -> onAllChains ForkAtGenesis - Pact5Fork -> onAllChains $ ForkAtGenesis + Pact5Fork -> onAllChains ForkAtGenesis Chainweb228Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 10 Chainweb229Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 20 Chainweb230Pact -> onAllChains $ ForkAtBlockHeight $ BlockHeight 30 diff --git a/test/unit/Chainweb/Test/BlockHeader/Validation.hs b/test/unit/Chainweb/Test/BlockHeader/Validation.hs index 03faebb1da..6cda40cae9 100644 --- a/test/unit/Chainweb/Test/BlockHeader/Validation.hs +++ b/test/unit/Chainweb/Test/BlockHeader/Validation.hs @@ -312,7 +312,7 @@ validationFailures = daValidation :: [(TestHeader, [ValidationFailureType])] daValidation = - -- test corret epoch transition + -- test correct epoch transition [ ( hdr, expected) -- epoch transition with wrong epoch start time From ea0ed66e06e9a3915258e629281b5bac52aaf1e7 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 5 Aug 2025 21:59:44 -0400 Subject: [PATCH 311/378] Fix some more tests --- test/lib/Chainweb/Test/Orphans/Internal.hs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index ae99fc4991..cff522e94f 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -14,6 +14,7 @@ {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE LambdaCase #-} -- | -- Module: Chainweb.Test.Orphans.Internal @@ -69,6 +70,7 @@ import qualified Data.ByteString as B import qualified Data.ByteString.Short as BS import Data.Foldable import Data.Function +import Data.Functor ((<&>)) import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS import Data.Hash.Keccak @@ -308,6 +310,17 @@ instance Arbitrary NodeInfo where , nodeHistoricalChains = ruleElems $ fmap (HM.toList . HM.map HS.toList . toAdjacencySets) $ _versionGraphs v , nodeServiceDate = T.pack <$> _versionServiceDate v , nodeBlockDelay = _versionBlockDelay v + , nodePayloadProviders = _versionPayloadProviderTypes implicitVersion <&> \case + EvmProvider n -> object + [ "type" .= ("eth" :: T.Text) + , "ethChainId" .= n + ] + PactProvider -> object + [ "type" .= ("pact" :: T.Text) + ] + MinimalProvider -> object + [ "type" .= ("parked" :: T.Text) + ] } -- -------------------------------------------------------------------------- -- From 59bf9be64d2017a95470c3d3d3f28da91d60fcfc Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 6 Aug 2025 08:19:59 -0400 Subject: [PATCH 312/378] Make MockTx.gasPrice JSONify as a string, not a number Otherwise it fails to round-trip. Not sure why this is only coming up now. --- src/Chainweb/Mempool/Mempool.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Mempool/Mempool.hs index 79e8b0f8f5..5a99da7a4e 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Mempool/Mempool.hs @@ -721,7 +721,7 @@ data MockTx = MockTx { instance J.Encode MockTx where build o = J.object [ "mockNonce" J..= J.Aeson (mockNonce o) - , "mockGasPrice" J..= J.number (realToFrac @Decimal @_ (view _GasPrice (mockGasPrice o))) + , "mockGasPrice" J..= J.string (show (view _GasPrice (mockGasPrice o))) , "mockGasLimit" J..= J.number (fromIntegral $ view (_GasLimit . to _gas) (mockGasLimit o)) , "mockMeta" J..= mockMeta o ] @@ -735,7 +735,7 @@ instance ToJSON MockTx where instance FromJSON MockTx where parseJSON = withObject "MockTx" $ \o -> MockTx <$> o .: "mockNonce" - <*> fmap (GasPrice . realToFrac @Double @Decimal) (o .: "mockGasPrice") + <*> fmap (GasPrice . read @Decimal) (o .: "mockGasPrice") <*> fmap (GasLimit . Gas . fromIntegral @Int @SatWord) (o .: "mockGasLimit") <*> o .: "mockMeta" {-# INLINE parseJSON #-} From e2d23d7cb650091cec14087d4e33a187fd0e52a2 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 6 Aug 2025 08:24:32 -0400 Subject: [PATCH 313/378] Make Kda pattern require 18 decimal digits, not 12 --- src/Chainweb/MinerReward.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/MinerReward.hs b/src/Chainweb/MinerReward.hs index d200da4f52..536a7ca046 100644 --- a/src/Chainweb/MinerReward.hs +++ b/src/Chainweb/MinerReward.hs @@ -242,7 +242,7 @@ newtype Kda = Kda_ Decimal pattern Kda :: HasCallStack => Decimal -> Kda pattern Kda { _kda } <- Kda_ _kda where Kda k - | roundTo 12 k /= k = error "KDA value with a precision of more than 12 decimal digits" + | roundTo 18 k /= k = error "KDA value with a precision of more than 18 decimal digits" | otherwise = Kda_ $ normalizeDecimal k {-# COMPLETE Kda #-} From dad106aec68d1f71ad0c354828d7900e2f343aad Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 6 Aug 2025 08:53:05 -0400 Subject: [PATCH 314/378] Replace block gas limit TODO with comment --- src/Chainweb/Pact/Validations.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Pact/Validations.hs b/src/Chainweb/Pact/Validations.hs index c98e81e68e..a64daf425a 100644 --- a/src/Chainweb/Pact/Validations.hs +++ b/src/Chainweb/Pact/Validations.hs @@ -74,7 +74,8 @@ assertPreflightMetadata -> Either (NonEmpty Text) () assertPreflightMetadata env cmd@(Pact.Command pay sigs hsh) blockCtx sigVerify = do let cid = view chainId env - -- TODO PP: fix this in master too; master uses the wrong block gas limit. + -- note that we use the maximum legal block gas limit here. if no miner is + -- willing to accept this tx, it still may not make it on the chain. let mbgl = maxBlockGasLimit (_bctxCurrentBlockHeight blockCtx) let Pact.PublicMeta pcid _ gl gp _ _ = Pact._pMeta pay From f69d29ec614115ec11ac33099eb42a4f14fdb973 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 19 Aug 2025 16:21:29 -0400 Subject: [PATCH 315/378] Add initial cut file option back into chainweb Change-Id: Id0000000bbf4389fca5cffef0bc5a4f9ada88b36 --- src/Chainweb/Chainweb.hs | 1 + src/Chainweb/Chainweb/Configuration.hs | 7 +++++++ src/Chainweb/CutDB.hs | 29 ++++++++++++++++---------- src/Chainweb/WebBlockHeaderDB.hs | 13 ++++++++++++ 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index e2b892434e..08cd101920 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -330,6 +330,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba , _cutDbParamsInitialHeightLimit = _cutInitialBlockHeightLimit cutConf , _cutDbParamsFastForwardHeightLimit = _cutFastForwardBlockHeightLimit cutConf , _cutDbParamsReadOnly = _configOnlySync conf || _configReadOnlyReplay conf + , _cutDbParamsInitialCutFile = _cutInitialCutFile cutConf } where cutConf = _configCuts conf diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 3d0d47ffe7..afb38b3db9 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -342,6 +342,7 @@ data CutConfig = CutConfig { _cutFetchTimeout :: !Int , _cutInitialBlockHeightLimit :: !(Maybe BlockHeight) , _cutFastForwardBlockHeightLimit :: !(Maybe BlockHeight) + , _cutInitialCutFile :: !(Maybe FilePath) } deriving (Eq, Show) makeLenses ''CutConfig @@ -351,6 +352,7 @@ instance ToJSON CutConfig where [ "fetchTimeout" .= _cutFetchTimeout o , "initialBlockHeightLimit" .= _cutInitialBlockHeightLimit o , "fastForwardBlockHeightLimit" .= _cutFastForwardBlockHeightLimit o + , "initialCutFile" .= _cutInitialCutFile o ] instance FromJSON (CutConfig -> CutConfig) where @@ -358,12 +360,14 @@ instance FromJSON (CutConfig -> CutConfig) where <$< cutFetchTimeout ..: "fetchTimeout" % o <*< cutInitialBlockHeightLimit ..: "initialBlockHeightLimit" % o <*< cutFastForwardBlockHeightLimit ..: "fastForwardBlockHeightLimit" % o + <*< cutInitialCutFile ..: "initialCutFile" % o defaultCutConfig :: CutConfig defaultCutConfig = CutConfig { _cutFetchTimeout = 3_000_000 , _cutInitialBlockHeightLimit = Nothing , _cutFastForwardBlockHeightLimit = Nothing + , _cutInitialCutFile = Nothing } pCutConfig :: MParser CutConfig @@ -379,6 +383,9 @@ pCutConfig = id % long "fast-forward-block-height-limit" <> help "When --only-sync-pact is given fast forward to this height. Ignored otherwise." <> metavar "INT" + <*< cutInitialCutFile .:: fmap Just . textOption + % long "initial-cut-file" + <> help "When --initial-cut-file is given, use the cut in the given file as the initial cut. Note that this will not contact the P2P network." -- -------------------------------------------------------------------------- -- -- Service API Configuration diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index a9576ff0c2..b0c06daf52 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -101,7 +101,7 @@ import Control.Monad.Morph import Control.Monad.STM import Control.Monad.Trans.Resource hiding (throwM) -import Data.Aeson (ToJSON) +import Data.Aeson (ToJSON, decodeStrict') import Data.Foldable import Data.Function import Data.Functor.Of @@ -162,6 +162,7 @@ import Chainweb.Logger import Chainweb.Core.Brief import Chainweb.Parent import Control.Exception (asyncExceptionFromException, asyncExceptionToException) +import qualified Data.ByteString.Lazy as BS -- -------------------------------------------------------------------------- -- -- Cut DB Configuration @@ -469,16 +470,22 @@ startCutDb config logger headerStore providers cutHashesStore = mask_ $ do readInitialCut :: IO Cut readInitialCut = do - unsafeMkCut <$> do - hm <- readHighestCutHeaders logg wbhdb cutHashesStore - case _cutDbParamsInitialHeightLimit config of - Nothing -> return hm - Just h -> do - limitedCutHeaders <- limitCutHeaders wbhdb h hm - let limitedCut = unsafeMkCut limitedCutHeaders - unless (_cutDbParamsReadOnly config) $ - casInsert cutHashesStore (cutToCutHashes Nothing limitedCut) - return limitedCutHeaders + case _cutDbParamsInitialCutFile config of + Nothing -> do + unsafeMkCut <$> do + hm <- readHighestCutHeaders logg wbhdb cutHashesStore + case _cutDbParamsInitialHeightLimit config of + Nothing -> return hm + Just h -> do + limitedCutHeaders <- limitCutHeaders wbhdb h hm + let limitedCut = unsafeMkCut limitedCutHeaders + unless (_cutDbParamsReadOnly config) $ + casInsert cutHashesStore (cutToCutHashes Nothing limitedCut) + return limitedCutHeaders + Just f -> do + rankedBlockHashes :: HM.HashMap ChainId RankedBlockHash <- decodeOrThrow =<< BS.readFile f + blockHeaders <- iforM rankedBlockHashes (lookupRankedWebBlockHeaderDb wbhdb) + return $ unsafeMkCut blockHeaders synchronizeProviders :: (Logger logger, HasVersion) diff --git a/src/Chainweb/WebBlockHeaderDB.hs b/src/Chainweb/WebBlockHeaderDB.hs index 96df451a23..a5f020e2db 100644 --- a/src/Chainweb/WebBlockHeaderDB.hs +++ b/src/Chainweb/WebBlockHeaderDB.hs @@ -26,6 +26,7 @@ module Chainweb.WebBlockHeaderDB , webBlockHeaderDb , webEntries , lookupWebBlockHeaderDb +, lookupRankedWebBlockHeaderDb , lookupAdjacentParentHeader , lookupParentHeader , insertWebBlockHeaderDb @@ -69,6 +70,7 @@ import Chainweb.Version import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB +import Chainweb.Ranked(Ranked(..)) -- -------------------------------------------------------------------------- -- -- Web Chain Database @@ -152,6 +154,17 @@ lookupWebBlockHeaderDb wdb c h = do db <- getWebBlockHeaderDb wdb c lookupM db h +lookupRankedWebBlockHeaderDb + :: HasVersion + => WebBlockHeaderDb + -> ChainId + -> RankedBlockHash + -> IO BlockHeader +lookupRankedWebBlockHeaderDb wdb c rh = do + checkWebChainId (chainGraphAt $ maxBound @BlockHeight) c + db <- getWebBlockHeaderDb wdb c + lookupRankedM db (int $ _rankedHeight rh) (_ranked rh) + blockAdjacentParentHeaders :: HasVersion => WebBlockHeaderDb From 58a8c87f4edc438e4bb3a6ddd140b45235a6593c Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 19 Aug 2025 16:30:56 -0400 Subject: [PATCH 316/378] Add graph transition test for d4k4 Change-Id: Id000000031699f8fc0d4d59c93d3c8720304394b --- test/multinode/MultiNodeNetworkTests.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/multinode/MultiNodeNetworkTests.hs b/test/multinode/MultiNodeNetworkTests.hs index 52b8de7942..711227b8f5 100644 --- a/test/multinode/MultiNodeNetworkTests.hs +++ b/test/multinode/MultiNodeNetworkTests.hs @@ -37,6 +37,11 @@ suite = independentSequentialTestGroup "MultiNodeNetworkTests" withSystemTempDirectory "multinode-tests-timedconsensus-petersen-twenty-pact" $ \pactDbDir -> withVersion (timedConsensusVersion petersenChainGraph twentyChainGraph) $ Chainweb.Test.MultiNode.test loglevel 10 30 rdb pactDbDir step + , testCaseSteps "ConsensusNetwork - TimedConsensus - 10 nodes - 30 seconds - d4k4 upgrade" $ \step -> + withTempRocksDb "multinode-tests-timedconsensus-twenty-d4k4-rocks" $ \rdb -> + withSystemTempDirectory "multinode-tests-timedconsensus-twenty-d4k4-pact" $ \pactDbDir -> + withVersion (timedConsensusVersion twentyChainGraph d4k4ChainGraph) $ + Chainweb.Test.MultiNode.test loglevel 4 100 rdb pactDbDir step , testCaseSteps "ConsensusNetwork - InstantTimedCPM singleChainGraph - 10 nodes - 30 seconds" $ \step -> withTempRocksDb "multinode-tests-instantcpm-single-rocks" $ \rdb -> withSystemTempDirectory "multinode-tests-instantcpm-single-pact" $ \pactDbDir -> From 23b7b65f2da6a46c4417d2f043ee4dc8be3515b2 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 26 Aug 2025 16:23:45 -0400 Subject: [PATCH 317/378] Fix limitCut performance regression limitCut was replaced earlier with an implementation based on limitCut_ which is *much* less efficient, especially in the case that the input cut is much further ahead. This restores the old version while keeping limitCut_ around. Change-Id: Id0000000fece787f99054a6213fdf54c5075135c --- src/Chainweb/Cut/Create.hs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index ba3a6deda0..f02477aa26 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -948,6 +948,13 @@ joinIntoHeavier_ wdb a b = do -- -------------------------------------------------------------------------- -- -- Limit Cut Hashes By Height +-- | Finds a `Cut` that is a predecessor of the given one, and that has a block +-- height that is smaller or equal to the given height. Also, returns a queue +-- ordered by height of all of the blocks that lie between the resulting cut and +-- the input cut. +-- +-- Always iterates over every block being removed from the input cut. For a +-- faster version, see `limitCut`. limitCut_ :: (HasCallStack, HasVersion) => WebBlockHeaderDb @@ -993,6 +1000,7 @@ limitCut_ wdb h c -- Otherwise, the predecessor of the given cut at the given height on each chain -- is returned. -- +-- See the performance notes for `seekAncestor`. limitCut :: (HasCallStack, HasVersion) => WebBlockHeaderDb @@ -1001,7 +1009,23 @@ limitCut -- bound. -> Cut -> IO Cut -limitCut wdb h c = evalStateT (limitCut_ wdb h c) mempty +limitCut wdb h c + | all (\bh -> h >= view blockHeight bh) (view cutHeaders c) = + return c + | otherwise = do + hdrs <- itraverse go $ view cutHeaders c + return $! unsafeMkCut $ projectChains $ HM.mapMaybe id hdrs + where + + go :: ChainId -> BlockHeader -> IO (Maybe BlockHeader) + go cid bh = do + if h >= view blockHeight bh + then return (Just bh) + else do + !db <- getWebBlockHeaderDb wdb cid + seekAncestor db bh (min (int $ view blockHeight bh) (int h)) + -- this is safe because it's guaranteed that the requested rank is + -- smaller then the block height of the argument -- | Find a `Cut` that is a predecessor of the given one, and that has a block -- height that is as low as possible while not exceeding the given height and From 9fa2baeaedf25fc052c6542519459c9865e841d9 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 21 Aug 2025 11:59:27 -0400 Subject: [PATCH 318/378] Add extra bootstrap node for evm-testnet --- src/Chainweb/Version/EvmTestnet.hs | 3 ++- src/P2P/BootstrapNodes.hs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Version/EvmTestnet.hs b/src/Chainweb/Version/EvmTestnet.hs index f536484bd3..1b139bedc8 100644 --- a/src/Chainweb/Version/EvmTestnet.hs +++ b/src/Chainweb/Version/EvmTestnet.hs @@ -22,6 +22,7 @@ import Chainweb.Utils.Rule import Chainweb.Version import Pact.Core.Names +import P2P.BootstrapNodes (evmTestnetBootstrapHosts) pattern EvmTestnet :: ChainwebVersion pattern EvmTestnet <- ((== evmTestnet) -> True) where @@ -59,7 +60,7 @@ evmTestnet = withVersion evmTestnet $ ChainwebVersion , _versionBlockDelay = BlockDelay 30_000_000 , _versionWindow = WindowWidth 120 , _versionHeaderBaseSizeBytes = 318 - 110 - , _versionBootstraps = [] + , _versionBootstraps = domainAddr2PeerInfo evmTestnetBootstrapHosts , _versionGenesis = VersionGenesis { _genesisBlockTarget = onAllChains $ HashTarget (maxBound `div` 100_000) , _genesisTime = onChains diff --git a/src/P2P/BootstrapNodes.hs b/src/P2P/BootstrapNodes.hs index e0fed17252..e9b8831807 100644 --- a/src/P2P/BootstrapNodes.hs +++ b/src/P2P/BootstrapNodes.hs @@ -13,6 +13,7 @@ module P2P.BootstrapNodes ( mainnetBootstrapHosts , testnet04BootstrapHosts +, evmTestnetBootstrapHosts ) where -- internal modules @@ -62,3 +63,8 @@ testnet04BootstrapHosts = map unsafeHostAddressFromText , "ap1.testnet.chainweb.com:443" , "ap2.testnet.chainweb.com:443" ] + +evmTestnetBootstrapHosts :: [HostAddress] +evmTestnetBootstrapHosts = map unsafeHostAddressFromText + [ "us-e1.evm-testnet.chainweb.com:443" + ] From 7d59da4a09f8be708fa46a9258c1decef177ad40 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 26 Aug 2025 11:24:30 -0400 Subject: [PATCH 319/378] Fix Pact 5 header oracle during rewinds Change-Id: Id0000000423292a30fae2de432886e5f28a5cea3 --- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index ec63098c03..ecbdf3ee6b 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -139,7 +139,7 @@ import Chainweb.Pact.Backend.Utils import Chainweb.Pact.SPV (pactSPV) import Chainweb.Parent import Chainweb.PayloadProvider (ConsensusState (..), SyncState (..)) -import Chainweb.Utils (sshow) +import Chainweb.Utils (sshow, int) import Chainweb.Utils.Serialization (runPutS, runGetEitherS) import Chainweb.Version import Chainweb.Version.Guards (pact5Serialiser) @@ -252,9 +252,13 @@ chainwebPactBlockDb env = ChainwebPactDb , Pact._pdbRollbackTx = runOnBlockGassed env stateVar doRollback } + let currentHeight = _blockHandlerBlockHeight env let headerOracle = HeaderOracle { chain = _chainId env - , consult = throwOnDbError . lookupBlockHash (_blockHandlerDb env) . unwrapParent + , consult = \(Parent hsh) -> do + throwOnDbError (lookupBlockHash (_blockHandlerDb env) hsh) <&> \case + Nothing -> False + Just rootHeight -> rootHeight > currentHeight } let spv = pactSPV headerOracle r <- kont pactDb spv @@ -906,13 +910,14 @@ lookupBlockWithHeight db bheight = do where qtext = "SELECT hash FROM BlockHistory WHERE blockheight = ?;" -lookupBlockHash :: HasCallStack => SQ3.Database -> BlockHash -> ExceptT LocatedSQ3Error IO Bool +lookupBlockHash :: HasCallStack => SQ3.Database -> BlockHash -> ExceptT LocatedSQ3Error IO (Maybe BlockHeight) lookupBlockHash db hash = do qry db qtext [SBlob (runPutS (encodeBlockHash hash))] [RInt] >>= \case - [[SInt n]] -> return $! n == 1 + [[SInt n]] -> return $! Just $! int n + [] -> return $ Nothing res -> error $ "Invalid result, " <> sshow res where - qtext = "SELECT COUNT(*) FROM BlockHistory WHERE hash = ?;" + qtext = "SELECT blockheight FROM BlockHistory WHERE hash = ?;" lookupRankedBlockHash :: HasCallStack => SQ3.Database -> RankedBlockHash -> IO Bool lookupRankedBlockHash db rankedBHash = throwOnDbError $ do From dc574a804b9eaab702aacc73c59f0eb667a7d679 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 26 Aug 2025 10:36:40 -0400 Subject: [PATCH 320/378] Restore Pact 4 Adds Pact 4 files back into the branch so that they can be adapted to work with pp/evm. Change-Id: Id0000000cfaa82298eba0c132f0df4c7ba132062 --- .../Pact/PactService/Pact4/ExecBlock.hs | 886 ++++++++++ .../Pact/Transactions/CoinV3Transactions.hs | 19 + .../Pact/Transactions/CoinV4Transactions.hs | 21 + .../Pact/Transactions/CoinV5Transactions.hs | 19 + .../Pact/Transactions/CoinV6Transactions.hs | 19 + .../Transactions/FungibleV2Transactions.hs | 19 + .../Pact/Transactions/Mainnet0Transactions.hs | 23 + .../Pact/Transactions/Mainnet1Transactions.hs | 23 + .../Pact/Transactions/Mainnet2Transactions.hs | 23 + .../Pact/Transactions/Mainnet3Transactions.hs | 23 + .../Pact/Transactions/Mainnet4Transactions.hs | 23 + .../Pact/Transactions/Mainnet5Transactions.hs | 23 + .../Pact/Transactions/Mainnet6Transactions.hs | 23 + .../Pact/Transactions/Mainnet7Transactions.hs | 23 + .../Pact/Transactions/Mainnet8Transactions.hs | 23 + .../Pact/Transactions/Mainnet9Transactions.hs | 23 + .../Transactions/MainnetKADTransactions.hs | 19 + .../Pact/Transactions/OtherTransactions.hs | 21 + .../RecapDevelopmentTransactions.hs | 23 + src/Chainweb/Pact4/Backend/ChainwebPactDb.hs | 891 ++++++++++ src/Chainweb/Pact4/ModuleCache.hs | 74 + src/Chainweb/Pact4/NoCoinbase.hs | 31 + src/Chainweb/Pact4/SPV.hs | 489 ++++++ src/Chainweb/Pact4/Templates.hs | 203 +++ src/Chainweb/Pact4/Transaction.hs | 190 ++ src/Chainweb/Pact4/TransactionExec.hs | 1553 +++++++++++++++++ src/Chainweb/Pact4/Types.hs | 326 ++++ src/Chainweb/Pact4/Validations.hs | 285 +++ test/lib/Chainweb/Test/Pact4/Utils.hs | 1118 ++++++++++++ .../Test/Pact4/VerifierPluginTest/Unit.hs | 56 + test/unit/Chainweb/Test/Pact4/DbCacheTest.hs | 86 + test/unit/Chainweb/Test/Pact4/GrandHash.hs | 190 ++ test/unit/Chainweb/Test/Pact4/NoCoinbase.hs | 37 + test/unit/Chainweb/Test/Pact4/RewardsTest.hs | 40 + test/unit/Chainweb/Test/Pact4/SQLite.hs | 307 ++++ .../Chainweb/Test/Pact4/TransactionTests.hs | 410 +++++ .../Chainweb/Test/Pact4/VerifierPluginTest.hs | 16 + 37 files changed, 7578 insertions(+) create mode 100644 src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs create mode 100644 src/Chainweb/Pact/Transactions/CoinV3Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/CoinV4Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/CoinV5Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/CoinV6Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/FungibleV2Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs create mode 100644 src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs create mode 100644 src/Chainweb/Pact/Transactions/OtherTransactions.hs create mode 100644 src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs create mode 100644 src/Chainweb/Pact4/Backend/ChainwebPactDb.hs create mode 100644 src/Chainweb/Pact4/ModuleCache.hs create mode 100644 src/Chainweb/Pact4/NoCoinbase.hs create mode 100644 src/Chainweb/Pact4/SPV.hs create mode 100644 src/Chainweb/Pact4/Templates.hs create mode 100644 src/Chainweb/Pact4/Transaction.hs create mode 100644 src/Chainweb/Pact4/TransactionExec.hs create mode 100644 src/Chainweb/Pact4/Types.hs create mode 100644 src/Chainweb/Pact4/Validations.hs create mode 100644 test/lib/Chainweb/Test/Pact4/Utils.hs create mode 100644 test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs create mode 100644 test/unit/Chainweb/Test/Pact4/DbCacheTest.hs create mode 100644 test/unit/Chainweb/Test/Pact4/GrandHash.hs create mode 100644 test/unit/Chainweb/Test/Pact4/NoCoinbase.hs create mode 100644 test/unit/Chainweb/Test/Pact4/RewardsTest.hs create mode 100644 test/unit/Chainweb/Test/Pact4/SQLite.hs create mode 100644 test/unit/Chainweb/Test/Pact4/TransactionTests.hs create mode 100644 test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs diff --git a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs new file mode 100644 index 0000000000..b7e92d4487 --- /dev/null +++ b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs @@ -0,0 +1,886 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE ViewPatterns #-} + +-- | +-- Module: Chainweb.Pact.PactService.Pact4.ExecBlock +-- Copyright: Copyright © 2020 Kadena LLC. +-- License: See LICENSE file +-- Maintainers: Lars Kuhtz, Emily Pillmore, Stuart Popejoy +-- Stability: experimental +-- +-- Functionality for playing block transactions. +-- +module Chainweb.Pact.PactService.Pact4.ExecBlock + ( execBlock + , execTransactions + , continueBlock + , toPayloadWithOutputs + , validateParsedChainwebTx + , validateRawChainwebTx + , validateHashes + , throwCommandInvalidError + , initModuleCacheForBlock + , runCoinbase + , CommandInvalidError(..) + , checkParse + ) where + +import Chronos qualified +import Control.Concurrent.MVar +import Control.DeepSeq +import Control.Exception (evaluate) +import Control.Lens +import Control.Monad +import Control.Monad.Catch +import Control.Monad.Reader +import Control.Monad.State.Strict + +import System.LogLevel (LogLevel(..)) +import qualified Data.ByteString.Short as SB +import Data.List qualified as List +import Data.Either +import Data.Foldable (toList) +import qualified Data.HashMap.Strict as HashMap +import qualified Data.Map as Map +import Data.Maybe +import Data.Text (Text) +import qualified Data.Text as T +import Data.Vector (Vector) +import qualified Data.Vector as V + +import System.IO +import System.Timeout + +import Prelude hiding (lookup) + +import Pact.Compile (compileExps) +import Pact.Interpreter(PactDbEnv(..)) +import qualified Pact.JSON.Encode as J +import qualified Pact.Parse as Pact4 hiding (parsePact) +import qualified Pact.Types.Command as Pact4 +import Pact.Types.ExpParser (mkTextInfo, ParseEnv(..)) +import qualified Pact.Types.Hash as Pact4 +import Pact.Types.RPC +import qualified Pact.Types.Runtime as Pact4 +import qualified Pact.Types.SPV as Pact4 + +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.Logger +import Chainweb.Mempool.Mempool as Mempool +import Chainweb.MinerReward +import Chainweb.Miner.Pact + +import Chainweb.Pact.Types +import Chainweb.Pact4.SPV qualified as Pact4 +import Chainweb.Pact4.NoCoinbase +import qualified Chainweb.Pact4.Transaction as Pact4 +import qualified Chainweb.Pact4.TransactionExec as Pact4 +import qualified Chainweb.Pact4.Validations as Pact4 +import Chainweb.Payload +import Chainweb.Payload.PayloadStore +import Chainweb.Time +import Chainweb.Utils hiding (check) +import Chainweb.Version +import Chainweb.Version.Guards +import Chainweb.Pact4.Backend.ChainwebPactDb +import Data.Coerce +import Data.Word +import GrowableVector.Lifted (Vec) +import Control.Monad.Primitive +import qualified GrowableVector.Lifted as Vec +import qualified Data.Set as S +import Chainweb.Pact4.Types +import Chainweb.Pact4.ModuleCache +import Control.Monad.Except +import qualified Data.List.NonEmpty as NE +import Chainweb.Pact.Backend.Types (BlockHandle(..)) + + +-- | Execute a block -- only called in validate either for replay or for validating current block. +-- +execBlock + :: (CanReadablePayloadCas tbl, Logger logger) + => BlockHeader + -- ^ this is the current header. We may consider changing this to the parent + -- header to avoid confusion with new block and prevent using data from this + -- header when we should use the respective values from the parent header + -- instead. + -> CheckablePayload + -> PactBlockM logger tbl (Pact4.Gas, PayloadWithOutputs) +execBlock currHeader payload = do + let plData = checkablePayloadToPayloadData payload + dbEnv <- view psBlockDbEnv + miner <- decodeStrictOrThrow' (_minerData $ view payloadDataMiner plData) + + trans <- liftIO $ pact4TransactionsFromPayload + (pact4ParserVersion v (_chainId currHeader) (view blockHeight currHeader)) + plData + logger <- view (psServiceEnv . psLogger) + + -- The reference time for tx timings validation. + -- + -- The legacy behavior is to use the creation time of the /current/ header. + -- The new default behavior is to use the creation time of the /parent/ header. + -- + txValidationTime <- if isGenesisBlockHeader currHeader + then return (ParentCreationTime $ view blockCreationTime currHeader) + else ParentCreationTime . view blockCreationTime . _parentHeader <$> view psParentHeader + + -- prop_tx_ttl_validate + errorsIfPresent <- liftIO $ + forM (V.toList trans) $ \tx -> + fmap (Pact4._cmdHash tx,) $ + runExceptT $ + validateParsedChainwebTx logger v cid dbEnv txValidationTime + (view blockHeight currHeader) tx + + case NE.nonEmpty [ (hsh, sshow err) | (hsh, Left err) <- errorsIfPresent ] of + Nothing -> return () + Just errs -> throwM $ Pact4TransactionValidationException errs + + logInitCache + + !results <- go miner trans >>= throwCommandInvalidError + + let !totalGasUsed = sumOf (folded . to Pact4._crGas) results + + pwo <- either throwM return $ + validateHashes currHeader payload miner results + return (totalGasUsed, pwo) + where + blockGasLimit = + fromIntegral <$> maxBlockGasLimit v (view blockHeight currHeader) + + logInitCache = liftPactServiceM $ do + mc <- fmap (fmap instr . _getModuleCache) <$> use psInitCache + logDebugPact $ "execBlock: initCache: " <> sshow mc + + instr (md,_) = preview (Pact4._MDModule . Pact4.mHash) $ Pact4._mdModule md + + v = _chainwebVersion currHeader + cid = _chainId currHeader + + isGenesisBlock = isGenesisBlockHeader currHeader + + go m txs = if isGenesisBlock + then + -- GENESIS VALIDATE COINBASE: Reject bad coinbase, use date rule for precompilation + execTransactions m txs + (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled False) blockGasLimit Nothing + else + -- VALIDATE COINBASE: back-compat allow failures, use date rule for precompilation + execTransactions m txs + (EnforceCoinbaseFailure False) (CoinbaseUsePrecompiled False) blockGasLimit Nothing + +throwCommandInvalidError + :: Transactions Pact4 (Either CommandInvalidError a) + -> PactBlockM logger tbl (Transactions Pact4 a) +throwCommandInvalidError = (transactionPairs . traverse . _2) throwGasFailure + where + throwGasFailure = \case + Left (CommandInvalidGasPurchaseFailure e) -> throwM (Pact4BuyGasFailure e) + + -- this should be impossible because we don't + -- set tx time limits in validateBlock + Left (CommandInvalidTxTimeout t) -> throwM t + + Right r -> pure r + +-- | The validation logic for Pact Transactions that have not had their +-- code parsed yet. This is used by the mempool to estimate tx validity +-- before inclusion into blocks, but it's also used by ExecBlock to check +-- if all of the txs in a block are valid. +-- +-- Skips validation for genesis transactions, since gas accounts, etc. don't +-- exist yet. +-- +validateRawChainwebTx + :: forall logger + . (Logger logger) + => logger + -> ChainwebVersion + -> ChainId + -> PactDbFor logger Pact4 + -> ParentCreationTime + -- ^ reference time for tx validation. + -> BlockHeight + -- ^ Current block height + -> Pact4.UnparsedTransaction + -> ExceptT InsertError IO Pact4.Transaction +validateRawChainwebTx logger v cid dbEnv txValidationTime bh tx = do + parsed <- checkParse logger v cid bh tx + validateParsedChainwebTx logger v cid dbEnv txValidationTime bh parsed + return parsed + +-- | The principal validation logic for groups of Pact Transactions. +-- This is used by the mempool to estimate tx validity +-- before inclusion into blocks, but it's also used by ExecBlock to check +-- if all of the txs in a block are valid. +-- +-- Skips validation for genesis transactions, since gas accounts, etc. don't +-- exist yet. +-- +validateParsedChainwebTx + :: Logger logger + => logger + -> ChainwebVersion + -> ChainId + -> PactDbFor logger Pact4 + -> ParentCreationTime + -- ^ reference time for tx validation. + -> BlockHeight + -- ^ Current block height + -> Pact4.Transaction + -> ExceptT InsertError IO () +validateParsedChainwebTx logger v cid dbEnv txValidationTime bh tx + | bh == genesisHeight v cid = pure () + | otherwise = do + checkUnique logger dbEnv tx + checkTxHash logger v cid bh tx + checkChain cid tx + checkTxSigs logger v cid bh tx + checkTimes logger v cid bh txValidationTime tx + _ <- checkCompile logger v cid bh tx + return () + +checkChain + :: ChainId -> Pact4.Transaction -> ExceptT InsertError IO () +checkChain cid tx = + unless (Pact4.assertChainId cid txCid) $ + throwError $ InsertErrorWrongChain (chainIdToText cid) (Pact4._chainId txCid) + where + txCid = view (Pact4.cmdPayload . to Pact4.payloadObj . Pact4.pMeta . Pact4.pmChainId) tx + +checkUnique + :: (Logger logger) + => logger + -> PactDbFor logger Pact4 + -> Pact4.Command (Pact4.PayloadWithText meta code) + -> ExceptT InsertError IO () +checkUnique logger dbEnv t = do + liftIO $ logFunctionText logger Debug $ "Pact4.checkUnique: " <> sshow (Pact4._cmdHash t) + found <- liftIO $ + HashMap.lookup (coerce $ Pact4.toUntypedHash $ Pact4._cmdHash t) <$> + _cpLookupProcessedTx dbEnv + (V.singleton $ coerce $ Pact4.toUntypedHash $ Pact4._cmdHash t) + case found of + Nothing -> pure () + Just _ -> throwError InsertErrorDuplicate + +checkTimes + :: (Logger logger) + => logger + -> ChainwebVersion + -> ChainId + -> BlockHeight + -> ParentCreationTime + -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta code) + -> ExceptT InsertError IO () +checkTimes logger v cid bh txValidationTime t = do + liftIO $ logFunctionText logger Debug $ "Pact4.checkTimes: " <> sshow (Pact4._cmdHash t) + if | skipTxTimingValidation v cid bh -> + return () + | not (Pact4.assertTxNotInFuture txValidationTime (Pact4.payloadObj <$> t)) -> + throwError InsertErrorTimeInFuture + | not (Pact4.assertTxTimeRelativeToParent txValidationTime (Pact4.payloadObj <$> t)) -> + throwError InsertErrorTTLExpired + | otherwise -> + return () + +checkTxHash + :: (Logger logger) + => logger + -> ChainwebVersion + -> ChainId + -> BlockHeight + -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta code) + -> ExceptT InsertError IO () +checkTxHash logger v cid bh t = do + liftIO $ logFunctionText logger Debug $ "Pact4.checkTxHash: " <> sshow (Pact4._cmdHash t) + case Pact4.verifyHash (Pact4._cmdHash t) (SB.fromShort $ Pact4.payloadBytes $ Pact4._cmdPayload t) of + Left _ + | doCheckTxHash v cid bh -> throwError InsertErrorInvalidHash + | otherwise -> pure () + Right _ -> pure () + +checkTxSigs + :: (MonadIO f, MonadError InsertError f, Logger logger) + => logger + -> ChainwebVersion + -> ChainId + -> BlockHeight + -> Pact4.Command (Pact4.PayloadWithText m c) + -> f () +checkTxSigs logger v cid bh t = do + liftIO $ logFunctionText logger Debug $ "Pact4.checkTxSigs: " <> sshow (Pact4._cmdHash t) + case Pact4.assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs of + Right _ -> do + pure () + Left err -> do + throwError $ InsertErrorInvalidSigs (displayAssertValidateSigsError err) + where + hsh = Pact4._cmdHash t + sigs = Pact4._cmdSigs t + signers = Pact4._pSigners $ Pact4.payloadObj $ Pact4._cmdPayload t + validSchemes = validPPKSchemes v cid bh + webAuthnPrefixLegal = isWebAuthnPrefixLegal v cid bh + +checkCompile + :: (Logger logger) + => logger + -> ChainwebVersion + -> ChainId + -> BlockHeight + -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Pact4.ParsedCode) + -> ExceptT InsertError IO Pact4.Transaction +checkCompile logger v cid bh tx = do + liftIO $ logFunctionText logger Debug $ "Pact4.checkCompile: " <> sshow (Pact4._cmdHash tx) + case payload of + Exec (ExecMsg parsedCode _) -> + case compileCode parsedCode of + Left perr -> throwError $ InsertErrorCompilationFailed (sshow perr) + Right _ -> return tx + _ -> return tx + where + payload = Pact4._pPayload $ Pact4.payloadObj $ Pact4._cmdPayload tx + compileCode p = + let e = ParseEnv (chainweb216Pact v cid bh) + in compileExps e (mkTextInfo (Pact4._pcCode p)) (Pact4._pcExps p) + +checkParse + :: (Logger logger) + => logger + -> ChainwebVersion + -> ChainId + -> BlockHeight + -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text) + -> ExceptT InsertError IO Pact4.Transaction +checkParse logger v cid bh tx = do + liftIO $ logFunctionText logger Debug $ "Pact4.checkParse: " <> sshow (Pact4._cmdHash tx) + forMOf (traversed . traversed) tx + (either (throwError . InsertErrorPactParseError . T.pack) return . Pact4.parsePact (pact4ParserVersion v cid bh)) + +execTransactions + :: (Logger logger) + => Miner + -> Vector Pact4.Transaction + -> EnforceCoinbaseFailure + -> CoinbaseUsePrecompiled + -> Maybe Pact4.Gas + -> Maybe Micros + -> PactBlockM logger tbl (Transactions Pact4 (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))) +execTransactions miner ctxs enfCBFail usePrecomp gasLimit timeLimit = do + mc <- initModuleCacheForBlock + -- for legacy reasons (ask Emily) we don't use the module cache resulting + -- from coinbase to run the pact cmds + coinOut <- runCoinbase miner enfCBFail usePrecomp mc + T2 txOuts _mcOut <- applyPactCmds ctxs miner mc gasLimit timeLimit + return $! Transactions (V.zip ctxs txOuts) coinOut + +initModuleCacheForBlock :: (Logger logger) => PactBlockM logger tbl ModuleCache +initModuleCacheForBlock = do + PactServiceState{..} <- get + isGenesis <- view psIsGenesis + pbh <- views psParentHeader (view blockHeight . _parentHeader) + case Map.lookupLE pbh _psInitCache of + Nothing -> if isGenesis + then return mempty + else do + mc <- Pact4.readInitModules + updateInitCacheM mc + return mc + Just (_,mc) -> pure mc + +runCoinbase + :: (Logger logger) + => Miner + -> EnforceCoinbaseFailure + -> CoinbaseUsePrecompiled + -> ModuleCache + -> PactBlockM logger tbl (Pact4.CommandResult [Pact4.TxLogJson]) +runCoinbase miner enfCBFail usePrecomp mc = do + isGenesis <- view psIsGenesis + if isGenesis + then return noCoinbase + else do + logger <- view (psServiceEnv . psLogger) + v <- view chainwebVersion + txCtx <- getTxContext miner Pact4.noPublicMeta + + let !bh = ctxCurrentBlockHeight txCtx + + let reward = minerReward v bh + dbEnv <- view psBlockDbEnv + let pactDb = _cpPactDbEnv dbEnv + + T2 cr upgradedCacheM <- + liftIO $ Pact4.applyCoinbase v logger pactDb reward txCtx enfCBFail usePrecomp mc + mapM_ upgradeInitCache upgradedCacheM + liftPactServiceM $ debugResult "runPact4Coinbase" (Pact4.crLogs %~ fmap J.Array $ cr) + return $! cr + + where + upgradeInitCache newCache = do + liftPactServiceM $ logInfoPact "Updating init cache for upgrade" + updateInitCacheM newCache + + +data CommandInvalidError + = CommandInvalidGasPurchaseFailure !Pact4GasPurchaseFailure + | CommandInvalidTxTimeout !TxTimeout + +-- | Apply multiple Pact commands, incrementing the transaction Id for each. +-- The output vector is in the same order as the input (i.e. you can zip it +-- with the inputs.) +applyPactCmds + :: forall logger tbl. (Logger logger) + => Vector Pact4.Transaction + -> Miner + -> ModuleCache + -> Maybe Pact4.Gas + -> Maybe Micros + -> PactBlockM logger tbl (T2 (Vector (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))) ModuleCache) +applyPactCmds cmds miner startModuleCache blockGas txTimeLimit = do + let txsGas txs = fromIntegral $ sumOf (traversed . _Right . to Pact4._crGas) txs + (txOuts, T2 mcOut _) <- tracePactBlockM' "applyPactCmds" (\_ -> ()) (txsGas . fst) $ + flip runStateT (T2 startModuleCache blockGas) $ + go [] (zip [0..] $ V.toList cmds) + return $! T2 (V.fromList . List.reverse $ txOuts) mcOut + where + go + :: [Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])] + -> [(Word, Pact4.Transaction)] + -> StateT + (T2 ModuleCache (Maybe Pact4.Gas)) + (PactBlockM logger tbl) + [Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])] + go !acc = \case + [] -> do + pure acc + (txIdxInBlock, tx) : rest -> do + r <- applyPactCmd (TxBlockIdx txIdxInBlock) miner txTimeLimit tx + case r of + Left e@(CommandInvalidTxTimeout _) -> do + pure (Left e : acc) + Left e@(CommandInvalidGasPurchaseFailure _) -> do + go (Left e : acc) rest + Right a -> do + go (Right a : acc) rest + +applyPactCmd + :: (Logger logger) + => TxIdxInBlock + -> Miner + -> Maybe Micros + -> Pact4.Transaction + -> StateT + (T2 ModuleCache (Maybe Pact4.Gas)) + (PactBlockM logger tbl) + (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])) +applyPactCmd txIdxInBlock miner txTimeLimit cmd = StateT $ \(T2 mcache maybeBlockGasRemaining) -> do + dbEnv <- view psBlockDbEnv + let pactDb = _cpPactDbEnv dbEnv + prevBlockState <- liftIO $ fmap _benvBlockState $ + readMVar $ pdPactDbVar pactDb + logger <- view (psServiceEnv . psLogger) + gasLogger <- view (psServiceEnv . psGasLogger) + txFailuresCounter <- view (psServiceEnv . psTxFailuresCounter) + isGenesis <- view psIsGenesis + v <- view chainwebVersion + let + -- for errors so fatal that the tx doesn't make it in the block + onFatalError e + | Just (Pact4BuyGasFailure f) <- fromException e = pure (Left (CommandInvalidGasPurchaseFailure f), T2 mcache maybeBlockGasRemaining) + | Just t@(TxTimeout {}) <- fromException e = do + -- timeouts can occur at any point during the transaction, even after + -- gas has been bought (or even while gas is being redeemed, after the + -- transaction proper is done). therefore we need to revert the block + -- state ourselves if it happens. + liftIO $ Pact4.modifyMVar' + (pdPactDbVar pactDb) + (benvBlockState .~ prevBlockState) + pure (Left (CommandInvalidTxTimeout t), T2 mcache maybeBlockGasRemaining) + | otherwise = throwM e + requestedTxGasLimit = view Pact4.cmdGasLimit (Pact4.payloadObj <$> cmd) + -- notice that we add 1 to the remaining block gas here, to distinguish the + -- cases "tx used exactly as much gas remained in the block" (which is fine) + -- and "tx attempted to use more gas than remains in the block" (which is + -- illegal). for example: tx has a tx gas limit of 10000. the block has 5000 + -- remaining gas. therefore the tx is applied with a tx gas limit of 5001. + -- if it uses 5001, that's illegal; if it uses 5000 or less, that's legal. + newTxGasLimit = case maybeBlockGasRemaining of + Nothing -> requestedTxGasLimit + Just blockGasRemaining -> min (fromIntegral blockGasRemaining) requestedTxGasLimit + gasLimitedCmd = + set Pact4.cmdGasLimit newTxGasLimit (Pact4.payloadObj <$> cmd) + initialGas = Pact4.initialGasOf (Pact4._cmdPayload cmd) + let !hsh = Pact4._cmdHash cmd + + handle onFatalError $ do + T2 result mcache' <- do + txCtx <- getTxContext miner (Pact4.publicMetaOf gasLimitedCmd) + let gasModel = getGasModel txCtx + if isGenesis + then liftIO $! Pact4.applyGenesisCmd logger pactDb Pact4.noSPVSupport txCtx gasLimitedCmd + else do + bhdb <- view (psServiceEnv . psBlockHeaderDb) + parent <- view psParentHeader + let spv = Pact4.pactSPV bhdb (_parentHeader parent) + let + !timeoutError = TxTimeout (pact4RequestKeyToTransactionHash $ Pact4.cmdToRequestKey cmd) + txTimeout io = case txTimeLimit of + Nothing -> do + logFunctionText logger Debug $ "txTimeLimit was not set - defaulting to a function of the block gas limit" + io + Just limit -> do + logFunctionText logger Debug $ "txTimeLimit was " <> sshow limit + maybe (throwM timeoutError) return =<< newTimeout (fromIntegral limit) io + txGas (T3 r _ _) = fromIntegral $ Pact4._crGas r + T3 r c _warns <- do + tracePactBlockM' "applyCmd" (\_ -> J.toJsonViaEncode hsh) txGas $ do + liftIO $ txTimeout $ + Pact4.applyCmd v logger gasLogger txFailuresCounter pactDb miner gasModel txCtx txIdxInBlock spv gasLimitedCmd initialGas mcache ApplySend + pure $ T2 r c + + if isGenesis + then updateInitCacheM mcache' + else liftPactServiceM $ debugResult "applyPactCmd" (Pact4.crLogs %~ fmap J.Array $ result) + + -- mark the tx as processed at the checkpointer. + liftIO $ _cpRegisterProcessedTx dbEnv (coerce $ Pact4.toUntypedHash hsh) + case maybeBlockGasRemaining of + Just blockGasRemaining + | Left _ <- Pact4._pactResult (Pact4._crResult result) + , blockGasRemaining < fromIntegral requestedTxGasLimit + -> throwM $ BlockGasLimitExceeded (fromIntegral requestedTxGasLimit - blockGasRemaining) + -- ^ this tx attempted to consume more gas than remains in the + -- block, so the block is invalid. we know this because failing + -- transactions consume their entire gas limit. + _ -> return () + let maybeBlockGasRemaining' = (\g -> g - Pact4._crGas result) <$> maybeBlockGasRemaining + pure (Right result, T2 mcache' maybeBlockGasRemaining') + +pact4TransactionsFromPayload + :: Pact4.PactParserVersion + -> PayloadData + -> IO (Vector Pact4.Transaction) +pact4TransactionsFromPayload ppv plData = do + vtrans <- fmap V.fromList $ + mapM toCWTransaction $ + toList (view payloadDataTransactions plData) + let (theLefts, theRights) = partitionEithers $ V.toList vtrans + unless (null theLefts) $ do + let ls = map T.pack theLefts + throwM $ TransactionDecodeFailure $ "Failed to decode pact transactions: " + <> T.intercalate ". " ls + return $! V.fromList theRights + where + toCWTransaction bs = evaluate (force (codecDecode (Pact4.payloadCodec ppv) $ + _transactionBytes bs)) + +debugResult :: J.Encode a => Logger logger => Text -> a -> PactServiceM logger tbl () +debugResult msg result = + logDebugPact $ trunc $ msg <> " result: " <> J.encodeText result + where + trunc t | T.length t < limit = t + | otherwise = T.take limit t <> " [truncated]" + limit = 5000 + + +-- | Calculate miner reward. +-- +-- See: 'rewards/miner_rewards.csv' +-- +minerReward + :: ChainwebVersion + -> BlockHeight + -> Pact4.ParsedDecimal +minerReward v = Pact4.ParsedDecimal + . _kda + . minerRewardKda + . blockMinerReward v +{-# INLINE minerReward #-} + +data CRLogPair = CRLogPair Pact4.Hash [Pact4.TxLogJson] + +instance J.Encode CRLogPair where + build (CRLogPair h logs) = J.object + [ "hash" J..= h + , "rawLogs" J..= J.Array logs + ] + {-# INLINE build #-} + +validateHashes + :: BlockHeader + -- ^ Current Header + -> CheckablePayload + -> Miner + -> Transactions Pact4 (Pact4.CommandResult [Pact4.TxLogJson]) + -> Either PactException PayloadWithOutputs +validateHashes bHeader payload miner transactions = + if newHash == prevHash + then Right actualPwo + else Left $ BlockValidationFailure $ BlockValidationFailureMsg $ + J.encodeText $ J.object + [ "header" J..= J.encodeWithAeson (ObjectEncoded bHeader) + , "actual" J..= J.encodeWithAeson actualPwo + , "expected" J..?= case payload of + CheckablePayload _ -> Nothing + CheckablePayloadWithOutputs pwo -> Just $ J.encodeWithAeson pwo + ] + where + + actualPwo = toPayloadWithOutputs Pact4T miner transactions + + newHash = _payloadWithOutputsPayloadHash actualPwo + prevHash = view blockPayloadHash bHeader + +type GrowableVec = Vec (PrimState IO) + +-- | Continue adding transactions to an existing block. +continueBlock + :: forall logger tbl + . (Logger logger, CanReadablePayloadCas tbl) + => MemPoolAccess + -> BlockInProgress Pact4 + -> PactBlockM logger tbl (BlockInProgress Pact4) +continueBlock mpAccess blockInProgress = do + v <- view chainwebVersion + cid <- view chainId + ParentHeader parent <- view psParentHeader + let pHeight = view blockHeight parent + let pHash = view blockHash parent + liftIO $ do + mpaProcessFork mpAccess parent + mpaSetLastHeader mpAccess parent + liftPactServiceM $ + logInfoPact $ "(parent height = " <> sshow pHeight <> ")" + <> " (parent hash = " <> sshow pHash <> ")" + + blockDbEnv <- view psBlockDbEnv + let pactDb = _cpPactDbEnv blockDbEnv + -- restore the block state from the block being continued + liftIO $ + modifyMVar_ (pdPactDbVar pactDb) $ \blockEnv -> + return + $! blockEnv + & benvBlockState . bsPendingBlock .~ _blockHandlePending (_blockInProgressHandle blockInProgress) + & benvBlockState . bsTxId .~ _blockHandleTxId (_blockInProgressHandle blockInProgress) + + blockGasLimit <- view (psServiceEnv . psBlockGasLimit) + mTxTimeLimit <- view (psServiceEnv . psTxTimeLimit) + + let txTimeHeadroomFactor :: Double + txTimeHeadroomFactor = 5 + let txTimeLimit :: Micros + -- 2.5 microseconds per unit gas + txTimeLimit = fromMaybe + (round $ (2.5 * txTimeHeadroomFactor) * fromIntegral blockGasLimit) + mTxTimeLimit + + let Pact4ModuleCache initCache = _blockInProgressModuleCache blockInProgress + let cb = _transactionCoinbase (_blockInProgressTransactions blockInProgress) + let startTxs = _transactionPairs (_blockInProgressTransactions blockInProgress) + + successes <- liftIO $ Vec.fromFoldable startTxs + failures <- liftIO $ Vec.new @_ @_ @TransactionHash + + let initState = BlockFill + (_blockInProgressRemainingGasLimit blockInProgress) + (S.fromList $ pact4RequestKeyToTransactionHash . Pact4._crReqKey . snd <$> V.toList startTxs) + 0 + + -- Heuristic: limit fetches to count of 1000-gas txs in block. + let fetchLimit = fromIntegral $ blockGasLimit `div` 1000 + T2 + finalModuleCache + BlockFill { _bfTxHashes = requestKeys, _bfGasLimit = finalGasLimit } + <- refill fetchLimit txTimeLimit successes failures initCache initState + + liftPactServiceM $ logInfoPact $ "(request keys = " <> sshow requestKeys <> ")" + + liftIO $ do + txHashes <- Vec.toLiftedVector failures + mpaBadlistTx mpAccess txHashes + + txs <- liftIO $ Vec.toLiftedVector successes + -- edmund: we need to be careful about timeouts. + -- If a tx times out, it must not be in the block state, otherwise + -- the "block in progress" will contain pieces of state from that tx. + -- + -- this cannot happen now because applyPactCmd doesn't let it. + finalBlockState <- fmap _benvBlockState + $ liftIO + $ readMVar + $ pdPactDbVar + $ pactDb + let !blockInProgress' = BlockInProgress + { _blockInProgressModuleCache = Pact4ModuleCache finalModuleCache + , _blockInProgressHandle = BlockHandle + { _blockHandleTxId = _bsTxId finalBlockState + , _blockHandlePending = _bsPendingBlock finalBlockState + } + , _blockInProgressParentHeader = newBlockParent + , _blockInProgressRemainingGasLimit = finalGasLimit + , _blockInProgressTransactions = Transactions + { _transactionCoinbase = cb + , _transactionPairs = txs + } + , _blockInProgressMiner = _blockInProgressMiner blockInProgress + , _blockInProgressPactVersion = Pact4T + , _blockInProgressChainwebVersion = v + , _blockInProgressChainId = cid + } + return blockInProgress' + where + newBlockParent = _blockInProgressParentHeader blockInProgress + + + getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector Pact4.Transaction) + getBlockTxs bfState = do + dbEnv <- view psBlockDbEnv + psEnv <- ask + let v = _chainwebVersion psEnv + cid = _chainId psEnv + logger <- view (psServiceEnv . psLogger) + -- parent time needs to know if we're *actually* at genesis + let parentTime = + maybe + (v ^?! versionGenesis . genesisTime . atChain cid) + (view blockCreationTime . _parentHeader) + newBlockParent + ParentHeader parent <- view psParentHeader + let pHeight = view blockHeight parent + let pHash = view blockHash parent + let validate bhi _bha txs = forM txs $ \tx -> runExceptT $ do + validateRawChainwebTx logger v cid dbEnv (ParentCreationTime parentTime) bhi tx + + liftIO $! + mpaGetBlock mpAccess bfState validate (pHeight + 1) pHash parentTime + + refill + :: Word64 + -> Micros + -> GrowableVec (Pact4.Transaction, Pact4.CommandResult [Pact4.TxLogJson]) + -> GrowableVec TransactionHash + -> ModuleCache + -> BlockFill + -> PactBlockM logger tbl (T2 ModuleCache BlockFill) + refill fetchLimit txTimeLimit successes failures = go + where + go :: ModuleCache -> BlockFill -> PactBlockM logger tbl (T2 ModuleCache BlockFill) + go mc unchanged@bfState = do + + case unchanged of + BlockFill g _ c -> do + (goodLength, badLength) <- liftIO $ (,) <$> Vec.length successes <*> Vec.length failures + liftPactServiceM $ logDebugPact $ "Block fill: count=" <> sshow c + <> ", gaslimit=" <> sshow g <> ", good=" + <> sshow goodLength <> ", bad=" <> sshow badLength + + -- LOOP INVARIANT: limit absolute recursion count + if _bfCount bfState > fetchLimit then liftPactServiceM $ do + logInfoPact $ "Refill fetch limit exceeded (" <> sshow fetchLimit <> ")" + pure (T2 mc unchanged) + else do + when (_bfGasLimit bfState < 0) $ + throwM $ MempoolFillFailure $ "Internal error, negative gas limit: " <> sshow bfState + + if _bfGasLimit bfState == 0 then pure (T2 mc unchanged) else do + + newTrans <- getBlockTxs bfState + if V.null newTrans then pure (T2 mc unchanged) else do + + T2 pairs mc' <- do + T2 txOuts mcOut <- applyPactCmds newTrans (_blockInProgressMiner blockInProgress) mc Nothing (Just txTimeLimit) + return $! T2 (V.force (V.zip newTrans txOuts)) mcOut + + oldSuccessesLength <- liftIO $ Vec.length successes + + (newState, timedOut) <- splitResults successes failures unchanged (V.toList pairs) + + -- LOOP INVARIANT: gas must not increase + when (_bfGasLimit newState > _bfGasLimit bfState) $ + throwM $ MempoolFillFailure $ "Gas must not increase: " <> sshow (bfState,newState) + + newSuccessesLength <- liftIO $ Vec.length successes + let addedSuccessCount = newSuccessesLength - oldSuccessesLength + + if timedOut + then + -- a transaction timed out, so give up early and make the block + pure (T2 mc' (incCount newState)) + else if _bfGasLimit newState >= _bfGasLimit bfState && addedSuccessCount > 0 + then + -- INVARIANT: gas must decrease if any transactions succeeded + throwM $ MempoolFillFailure + $ "Invariant failure, gas did not decrease: " + <> sshow (bfState,newState,V.length newTrans,addedSuccessCount) + else + go mc' (incCount newState) + + incCount :: BlockFill -> BlockFill + incCount b = over bfCount succ b + + -- | Split the results of applying each command into successes and failures, + -- and return the final 'BlockFill'. + -- + -- If we encounter a 'TxTimeout', we short-circuit, and only return + -- what we've put into the block before the timeout. We also report + -- that we timed out, so that `refill` can stop early. + -- + -- The failed txs are later badlisted. + splitResults :: () + => GrowableVec (Pact4.Transaction, Pact4.CommandResult [Pact4.TxLogJson]) + -> GrowableVec TransactionHash -- ^ failed txs + -> BlockFill + -> [(Pact4.Transaction, Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))] + -> PactBlockM logger tbl (BlockFill, Bool) + splitResults successes failures = go + where + go acc@(BlockFill g rks i) = \case + [] -> pure (acc, False) + (t, r) : rest -> case r of + Right cr -> do + !rks' <- enforceUnique rks (pact4RequestKeyToTransactionHash $ Pact4._crReqKey cr) + -- Decrement actual gas used from block limit + let !g' = g - fromIntegral (Pact4._crGas cr) + liftIO $ Vec.push successes (t, cr) + go (BlockFill g' rks' i) rest + Left (CommandInvalidGasPurchaseFailure (Pact4GasPurchaseFailure h _)) -> do + !rks' <- enforceUnique rks h + -- Gas buy failure adds failed request key to fail list only + liftIO $ Vec.push failures h + go (BlockFill g rks' i) rest + Left (CommandInvalidTxTimeout (TxTimeout h)) -> do + liftIO $ Vec.push failures h + liftPactServiceM $ logErrorPact $ "timed out on " <> sshow h + return (acc, True) + + enforceUnique rks rk + | S.member rk rks = + throwM $ MempoolFillFailure $ "Duplicate transaction: " <> sshow rk + | otherwise = return $ S.insert rk rks + +-- | This timeout variant returns Nothing if the timeout elapsed, regardless of whether or not it was actually able to interrupt its argument. +-- This is more robust in the face of scheduler behavior than the standard 'System.Timeout.timeout', with small timeouts. +newTimeout :: Int -> IO a -> IO (Maybe a) +newTimeout n f = do + (timeSpan, a) <- Chronos.stopwatch (timeout n f) + if Chronos.getTimespan timeSpan > fromIntegral (n * 1000) + then return Nothing + else return a diff --git a/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs new file mode 100644 index 0000000000..41ddd3695f --- /dev/null +++ b/src/Chainweb/Pact/Transactions/CoinV3Transactions.hs @@ -0,0 +1,19 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.CoinV3Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiRkd0RlNjcW1neklEQzlENkUwSUtQSFN0ZDhPdW9JdVhRanp4TFdyWTBZayIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgOyB2MyBjYXBhYmlsaXRpZXNcXG4gIChkZWZjYXAgUkVMRUFTRV9BTExPQ0FUSU9OXFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGZvciBhbGxvY2F0aW9uIHJlbGVhc2UsIGNhbiBiZSB1c2VkIGZvciBzaWcgc2NvcGluZy5cXFwiXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJndWFyZFxcXCIgOj0gb2xkLWd1YXJkIH1cXG5cXG4gICAgICAgIChlbmZvcmNlLWd1YXJkIG9sZC1ndWFyZClcXG5cXG4gICAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgIHsgXFxcImd1YXJkXFxcIiA6IG5ldy1ndWFyZCB9XFxuICAgICAgICAgICkpKVxcbiAgICApXFxuXFxuXFxuICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgKClcXG4gICAgTUlOSU1VTV9QUkVDSVNJT04pXFxuXFxuICAoZGVmdW4gdHJhbnNmZXI6c3RyaW5nIChzZW5kZXI6c3RyaW5nIHJlY2VpdmVyOnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBzZW5kZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgcmVjZWl2ZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpIF1cXG5cXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcilcXG4gICAgICBcXFwic2VuZGVyIGNhbm5vdCBiZSB0aGUgcmVjZWl2ZXIgb2YgYSB0cmFuc2ZlclxcXCIpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcInRyYW5zZmVyIGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFRSQU5TRkVSIHNlbmRlciByZWNlaXZlciBhbW91bnQpXFxuICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIHJlY2VpdmVyXFxuICAgICAgICB7IFxcXCJndWFyZFxcXCIgOj0gZyB9XFxuXFxuICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIGcgYW1vdW50KSlcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgYW1vdW50OmRlY2ltYWwgKVxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpIF1cXG5cXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcilcXG4gICAgICBcXFwic2VuZGVyIGNhbm5vdCBiZSB0aGUgcmVjZWl2ZXIgb2YgYSB0cmFuc2ZlclxcXCIpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcInRyYW5zZmVyIGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFRSQU5TRkVSIHNlbmRlciByZWNlaXZlciBhbW91bnQpXFxuICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgKGNyZWRpdCByZWNlaXZlciByZWNlaXZlci1ndWFyZCBhbW91bnQpKVxcbiAgICApXFxuXFxuICAoZGVmdW4gY29pbmJhc2U6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhY2NvdW50LWd1YXJkOmd1YXJkIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJJbnRlcm5hbCBmdW5jdGlvbiBmb3IgdGhlIGluaXRpYWwgY3JlYXRpb24gb2YgY29pbnMuICBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgXFxcXGNhbm5vdCBiZSB1c2VkIG91dHNpZGUgb2YgdGhlIGNvaW4gY29udHJhY3QuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENPSU5CQVNFKSlcXG4gICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIGFjY291bnQgYW1vdW50KSkgO3YzXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIGFjY291bnQgYW1vdW50KSkgO3YzXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuXFxuICAgICAgKGVuZm9yY2UgKDw9IGFtb3VudCBiYWxhbmNlKSBcXFwiSW5zdWZmaWNpZW50IGZ1bmRzXFxcIilcXG5cXG4gICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAoLSBiYWxhbmNlIGFtb3VudCkgfVxcbiAgICAgICAgKSlcXG4gICAgKVxcblxcbiAgKGRlZnBhY3QgZnVuZC10eCAoc2VuZGVyOnN0cmluZyBtaW5lcjpzdHJpbmcgbWluZXItZ3VhcmQ6Z3VhcmQgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiJ2Z1bmQtdHgnIGlzIGEgc3BlY2lhbCBwYWN0IHRvIGZ1bmQgYSB0cmFuc2FjdGlvbiBpbiB0d28gc3RlcHMsICAgICBcXFxcXFxuICAgIFxcXFx3aXRoIHRoZSBhY3R1YWwgdHJhbnNhY3Rpb24gdHJhbnNwaXJpbmcgaW4gdGhlIG1pZGRsZTogICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFxcXFxcXG4gICAgXFxcXCAgMSkgQSBidXlpbmcgcGhhc2UsIGRlYml0aW5nIHRoZSBzZW5kZXIgZm9yIHRvdGFsIGdhcyBhbmQgZmVlLCB5aWVsZGluZyBcXFxcXFxuICAgIFxcXFwgICAgIFRYX01BWF9DSEFSR0UuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAyKSBBIHNldHRsZW1lbnQgcGhhc2UsIHJlc3VtaW5nIFRYX01BWF9DSEFSR0UsIGFuZCBhbGxvY2F0aW5nIHRvIHRoZSAgIFxcXFxcXG4gICAgXFxcXCAgICAgY29pbmJhc2UgYWNjb3VudCBmb3IgdXNlZCBnYXMgYW5kIGZlZSwgYW5kIHNlbmRlciBhY2NvdW50IGZvciBiYWwtICBcXFxcXFxuICAgIFxcXFwgICAgIGFuY2UgKHVudXNlZCBnYXMsIGlmIGFueSkuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICAgIDsocHJvcGVydHkgY29uc2VydmVzLW1hc3MpIG5vdCBzdXBwb3J0ZWQgeWV0XFxuICAgICAgICAgICBdXFxuXFxuICAgIChzdGVwIChidXktZ2FzIHNlbmRlciB0b3RhbCkpXFxuICAgIChzdGVwIChyZWRlZW0tZ2FzIG1pbmVyIG1pbmVyLWd1YXJkIHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZWJpdDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJEZWJpdCBBTU9VTlQgZnJvbSBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJkZWJpdCBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChERUJJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICAoZGVmdW4gY3JlZGl0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkNyZWRpdCBBTU9VTlQgdG8gQUNDT1VOVCBiYWxhbmNlXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcImNyZWRpdCBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDUkVESVQgYWNjb3VudCkpXFxuICAgICh3aXRoLWRlZmF1bHQtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IC0xLjAsIFxcXCJndWFyZFxcXCIgOiBndWFyZCB9XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSwgXFxcImd1YXJkXFxcIiA6PSByZXRnIH1cXG4gICAgICA7IHdlIGRvbid0IHdhbnQgdG8gb3ZlcndyaXRlIGFuIGV4aXN0aW5nIGd1YXJkIHdpdGggdGhlIHVzZXItc3VwcGxpZWQgb25lXFxuICAgICAgKGVuZm9yY2UgKD0gcmV0ZyBndWFyZClcXG4gICAgICAgIFxcXCJhY2NvdW50IGd1YXJkcyBkbyBub3QgbWF0Y2hcXFwiKVxcblxcbiAgICAgIChsZXQgKChpcy1uZXdcXG4gICAgICAgICAgICAgKGlmICg9IGJhbGFuY2UgLTEuMClcXG4gICAgICAgICAgICAgICAgIChlbmZvcmNlLXJlc2VydmVkIGFjY291bnQgZ3VhcmQpXFxuICAgICAgICAgICAgICAgZmFsc2UpKSlcXG5cXG4gICAgICAgICh3cml0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAoaWYgaXMtbmV3IGFtb3VudCAoKyBiYWxhbmNlIGFtb3VudCkpXFxuICAgICAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogcmV0Z1xcbiAgICAgICAgICB9KSlcXG4gICAgICApKVxcblxcbiAgKGRlZnVuIGNoZWNrLXJlc2VydmVkOnN0cmluZyAoYWNjb3VudDpzdHJpbmcpXFxuICAgIFxcXCIgQ2hlY2tzIEFDQ09VTlQgZm9yIHJlc2VydmVkIG5hbWUgYW5kIHJldHVybnMgdHlwZSBpZiBcXFxcXFxuICAgIFxcXFwgZm91bmQgb3IgZW1wdHkgc3RyaW5nLiBSZXNlcnZlZCBuYW1lcyBzdGFydCB3aXRoIGEgXFxcXFxcbiAgICBcXFxcIHNpbmdsZSBjaGFyIGFuZCBjb2xvbiwgZS5nLiAnYzpmb28nLCB3aGljaCB3b3VsZCByZXR1cm4gJ2MnIGFzIHR5cGUuXFxcIlxcbiAgICAobGV0ICgocGZ4ICh0YWtlIDIgYWNjb3VudCkpKVxcbiAgICAgIChpZiAoPSBcXFwiOlxcXCIgKHRha2UgLTEgcGZ4KSkgKHRha2UgMSBwZngpIFxcXCJcXFwiKSkpXFxuXFxuICAoZGVmdW4gZW5mb3JjZS1yZXNlcnZlZDpib29sIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQGRvYyBcXFwiRW5mb3JjZSByZXNlcnZlZCBhY2NvdW50IG5hbWUgcHJvdG9jb2xzLlxcXCJcXG4gICAgKGxldCAoKHIgKGNoZWNrLXJlc2VydmVkIGFjY291bnQpKSlcXG4gICAgICAoaWYgKD0gXFxcIlxcXCIgcikgdHJ1ZVxcbiAgICAgICAgKGlmICg9IFxcXCJrXFxcIiByKVxcbiAgICAgICAgICAoZW5mb3JjZVxcbiAgICAgICAgICAgICg9IChmb3JtYXQgXFxcInt9XFxcIiBbZ3VhcmRdKVxcbiAgICAgICAgICAgICAgIChmb3JtYXQgXFxcIktleVNldCB7a2V5czogW3t9XSxwcmVkOiBrZXlzLWFsbH1cXFwiXFxuICAgICAgICAgICAgICAgICAgICAgICBbKGRyb3AgMiBhY2NvdW50KV0pKVxcbiAgICAgICAgICAgIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgKGVuZm9yY2UgZmFsc2VcXG4gICAgICAgICAgICAoZm9ybWF0IFxcXCJVbnJlY29nbml6ZWQgcmVzZXJ2ZWQgcHJvdG9jb2w6IHt9XFxcIiBbcl0pKSkpKSlcXG5cXG5cXG4gIChkZWZzY2hlbWEgY3Jvc3NjaGFpbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiU2NoZW1hIGZvciB5aWVsZGVkIHZhbHVlIGluIGNyb3NzLWNoYWluIHRyYW5zZmVyc1xcXCJcXG4gICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgIGFtb3VudDpkZWNpbWFsKVxcblxcbiAgKGRlZnBhY3QgdHJhbnNmZXItY3Jvc3NjaGFpbjpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICB0YXJnZXQtY2hhaW46c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWwgKVxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBzZW5kZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgcmVjZWl2ZXIpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAoc3RlcFxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKERFQklUIHNlbmRlcilcXG5cXG4gICAgICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKCE9IFxcXCJcXFwiIHRhcmdldC1jaGFpbikgXFxcImVtcHR5IHRhcmdldC1jaGFpblxcXCIpXFxuICAgICAgICAoZW5mb3JjZSAoIT0gKGF0ICdjaGFpbi1pZCAoY2hhaW4tZGF0YSkpIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgXFxcImNhbm5vdCBydW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzIHRvIHRoZSBzYW1lIGNoYWluXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgICAgIFxcXCJ0cmFuc2ZlciBxdWFudGl0eSBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAgICAgOzsgc3RlcCAxIC0gZGViaXQgZGVsZXRlLWFjY291bnQgb24gY3VycmVudCBjaGFpblxcbiAgICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIFxcXCJcXFwiIGFtb3VudCkpXFxuXFxuICAgICAgICAobGV0XFxuICAgICAgICAgICgoY3Jvc3NjaGFpbi1kZXRhaWxzOm9iamVjdHtjcm9zc2NoYWluLXNjaGVtYX1cXG4gICAgICAgICAgICB7IFxcXCJyZWNlaXZlclxcXCIgOiByZWNlaXZlclxcbiAgICAgICAgICAgICwgXFxcInJlY2VpdmVyLWd1YXJkXFxcIiA6IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAgICAgLCBcXFwiYW1vdW50XFxcIiA6IGFtb3VudFxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICB9XFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50KSlcXG4gICAgICAgIDs7IHN0ZXAgMiAtIGNyZWRpdCBjcmVhdGUgYWNjb3VudCBvbiB0YXJnZXQgY2hhaW5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCByZWNlaXZlcilcXG4gICAgICAgICAgKGNyZWRpdCByZWNlaXZlciByZWNlaXZlci1ndWFyZCBhbW91bnQpKVxcbiAgICAgICAgKSlcXG4gICAgKVxcblxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIGFsbG9jYXRpb25zXFxuXFxuICAoZGVmc2NoZW1hIGFsbG9jYXRpb24tc2NoZW1hXFxuICAgIEBkb2MgXFxcIkdlbmVzaXMgYWxsb2NhdGlvbiByZWdpc3RyeVxcXCJcXG4gICAgO0Btb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZGF0ZTp0aW1lXFxuICAgIGd1YXJkOmd1YXJkXFxuICAgIHJlZGVlbWVkOmJvb2wpXFxuXFxuICAoZGVmdGFibGUgYWxsb2NhdGlvbi10YWJsZTp7YWxsb2NhdGlvbi1zY2hlbWF9KVxcblxcbiAgKGRlZnVuIGNyZWF0ZS1hbGxvY2F0aW9uLWFjY291bnRcXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGRhdGU6dGltZVxcbiAgICAgIGtleXNldC1yZWY6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICBAZG9jIFxcXCJBZGQgYW4gZW50cnkgdG8gdGhlIGNvaW4gYWxsb2NhdGlvbiB0YWJsZS4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGFsc28gY3JlYXRlcyBhIGNvcnJlc3BvbmRpbmcgZW1wdHkgY29pbiBjb250cmFjdCBhY2NvdW50IFxcXFxcXG4gICAgICAgICBcXFxcb2YgdGhlIHNhbWUgbmFtZSBhbmQgZ3VhcmQuIFJlcXVpcmVzIEdFTkVTSVMgY2FwYWJpbGl0eS4gXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoR0VORVNJUykpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlICg-PSBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJhbGxvY2F0aW9uIGFtb3VudCBtdXN0IGJlIG5vbi1uZWdhdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAobGV0XFxuICAgICAgKChndWFyZDpndWFyZCAoa2V5c2V0LXJlZi1ndWFyZCBrZXlzZXQtcmVmKSkpXFxuXFxuICAgICAgKGNyZWF0ZS1hY2NvdW50IGFjY291bnQgZ3VhcmQpXFxuXFxuICAgICAgKGluc2VydCBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogYW1vdW50XFxuICAgICAgICAsIFxcXCJkYXRlXFxcIiA6IGRhdGVcXG4gICAgICAgICwgXFxcImd1YXJkXFxcIiA6IGd1YXJkXFxuICAgICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOiBmYWxzZVxcbiAgICAgICAgfSkpKVxcblxcbiAgKGRlZnVuIHJlbGVhc2UtYWxsb2NhdGlvblxcbiAgICAoIGFjY291bnQ6c3RyaW5nIClcXG5cXG4gICAgQGRvYyBcXFwiUmVsZWFzZSBmdW5kcyBhc3NvY2lhdGVkIHdpdGggYWxsb2NhdGlvbiBBQ0NPVU5UIGludG8gbWFpbiBsZWRnZXIuICAgXFxcXFxcbiAgICAgICAgIFxcXFxBQ0NPVU5UIG11c3QgYWxyZWFkeSBleGlzdCBpbiBtYWluIGxlZGdlci4gQWxsb2NhdGlvbiBpcyBkZWFjdGl2YXRlZCBcXFxcXFxuICAgICAgICAgXFxcXGFmdGVyIHJlbGVhc2UuXFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKHdpdGgtcmVhZCBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlXFxuICAgICAgLCBcXFwiZGF0ZVxcXCIgOj0gcmVsZWFzZS10aW1lXFxuICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDo9IHJlZGVlbWVkXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGd1YXJkXFxuICAgICAgfVxcblxcbiAgICAgIChsZXQgKChjdXJyLXRpbWU6dGltZSAoYXQgJ2Jsb2NrLXRpbWUgKGNoYWluLWRhdGEpKSkpXFxuXFxuICAgICAgICAoZW5mb3JjZSAobm90IHJlZGVlbWVkKVxcbiAgICAgICAgICBcXFwiYWxsb2NhdGlvbiBmdW5kcyBoYXZlIGFscmVhZHkgYmVlbiByZWRlZW1lZFxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZVxcbiAgICAgICAgICAoPj0gY3Vyci10aW1lIHJlbGVhc2UtdGltZSlcXG4gICAgICAgICAgKGZvcm1hdCBcXFwiZnVuZHMgbG9ja2VkIHVudGlsIHt9LiBjdXJyZW50IHRpbWU6IHt9XFxcIiBbcmVsZWFzZS10aW1lIGN1cnItdGltZV0pKVxcblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoUkVMRUFTRV9BTExPQ0FUSU9OIGFjY291bnQgYmFsYW5jZSlcXG5cXG4gICAgICAgIChlbmZvcmNlLWd1YXJkIGd1YXJkKVxcblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBcXFwiXFxcIiBhY2NvdW50IGJhbGFuY2UpKVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKSlcXG4gICAgKSkpXFxuXFxuKVxcblwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwiY29pbi1jb250cmFjdC12M1wifSJ9" + ] diff --git a/src/Chainweb/Pact/Transactions/CoinV4Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV4Transactions.hs new file mode 100644 index 0000000000..c12a24b8f4 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/CoinV4Transactions.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.CoinV4Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoieW5ucDFYVVNSTjJrMUYwYTZ2dXM3RFp0SDZjcHN6MVhmX0d3V0xnTFhTTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUteGNoYWluLXYxXFxuXFxuICBcXFwiIFRoaXMgaW50ZXJmYWNlIG9mZmVycyBhIHN0YW5kYXJkIGNhcGFiaWxpdHkgZm9yIGNyb3NzLWNoYWluIFxcXFxcXG4gIFxcXFwgdHJhbnNmZXJzIGFuZCBhc3NvY2lhdGVkIGV2ZW50cy4gXFxcIlxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZG9jIFxcXCIgTWFuYWdlZCBjYXBhYmlsaXR5IHNlYWxpbmcgQU1PVU5UIGZvciB0cmFuc2ZlciBcXFxcXFxuICAgICAgICAgXFxcXCBmcm9tIFNFTkRFUiB0byBSRUNFSVZFUiBvbiBUQVJHRVQtQ0hBSU4uIFBlcm1pdHMgXFxcXFxcbiAgICAgICAgIFxcXFwgYW55IG51bWJlciBvZiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdXAgdG8gQU1PVU5ULlxcXCJcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiIEFsbG93cyBUUkFOU0ZFUi1YQ0hBSU4gQU1PVU5UIHRvIGJlIGxlc3MgdGhhbiBvciBcXFxcXFxuICAgICAgICAgXFxcXCBlcXVhbCBtYW5hZ2VkIHF1YW50aXR5IGFzIGEgb25lLXNob3QsIHJldHVybmluZyAwLjAuXFxcIlxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGVtaXR0ZWQgb24gcmVjZWlwdCBvZiBjcm9zcy1jaGFpbiB0cmFuc2Zlci5cXFwiXFxuICAgIEBldmVudFxcbiAgKVxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImdlbmVzaXMteGNoYWluXCJ9In0" + , + "eyJoYXNoIjoiby1RNlV2RU4tSmNXSFozUnpvM0lET3R5czRkUTFKX2pwN25vUXdoWEMwVSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBTY2hlbWFzIGFuZCBUYWJsZXNcXG5cXG4gIChkZWZzY2hlbWEgY29pbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiVGhlIGNvaW4gY29udHJhY3QgdG9rZW4gc2NoZW1hXFxcIlxcbiAgICBAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcbiAgKGRlZnRhYmxlIGNvaW4tdGFibGU6e2NvaW4tc2NoZW1hfSlcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ2FwYWJpbGl0aWVzXFxuXFxuICAoZGVmY2FwIEdPVkVSTkFOQ0UgKClcXG4gICAgKGVuZm9yY2UgZmFsc2UgXFxcIkVuZm9yY2Ugbm9uLXVwZ3JhZGVhYmlsaXR5XFxcIikpXFxuXFxuICAoZGVmY2FwIEdBUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IGdhcyBidXkgYW5kIHJlZGVlbVxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgQ09JTkJBU0UgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBtaW5lciByZXdhcmRcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIEdFTkVTSVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgY29uc3RyYWluaW5nIGdlbmVzaXMgdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBSRU1FRElBVEUgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgREVCSVQgKHNlbmRlcjpzdHJpbmcpXFxuICAgIFxcXCJDYXBhYmlsaXR5IGZvciBtYW5hZ2luZyBkZWJpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZS1ndWFyZCAoYXQgJ2d1YXJkIChyZWFkIGNvaW4tdGFibGUgc2VuZGVyKSkpXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCBzZW5kZXJcXFwiKSlcXG5cXG4gIChkZWZjYXAgQ1JFRElUIChyZWNlaXZlcjpzdHJpbmcpXFxuICAgIFxcXCJDYXBhYmlsaXR5IGZvciBtYW5hZ2luZyBjcmVkaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UgKCE9IHJlY2VpdmVyIFxcXCJcXFwiKSBcXFwidmFsaWQgcmVjZWl2ZXJcXFwiKSlcXG5cXG4gIChkZWZjYXAgUk9UQVRFIChhY2NvdW50OnN0cmluZylcXG4gICAgQGRvYyBcXFwiQXV0b25vbW91c2x5IG1hbmFnZWQgY2FwYWJpbGl0eSBmb3IgZ3VhcmQgcm90YXRpb25cXFwiXFxuICAgIEBtYW5hZ2VkXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFRSQU5TRkVSOmJvb2xcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgKVxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpIFxcXCJzYW1lIHNlbmRlciBhbmQgcmVjZWl2ZXJcXFwiKVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIlBvc2l0aXZlIGFtb3VudFxcXCIpXFxuICAgIChjb21wb3NlLWNhcGFiaWxpdHkgKERFQklUIHNlbmRlcikpXFxuICAgIChjb21wb3NlLWNhcGFiaWxpdHkgKENSRURJVCByZWNlaXZlcikpXFxuICApXFxuXFxuICAoZGVmdW4gVFJBTlNGRVItbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChsZXQgKChuZXdiYWwgKC0gbWFuYWdlZCByZXF1ZXN0ZWQpKSlcXG4gICAgICAoZW5mb3JjZSAoPj0gbmV3YmFsIDAuMClcXG4gICAgICAgIChmb3JtYXQgXFxcIlRSQU5TRkVSIGV4Y2VlZGVkIGZvciBiYWxhbmNlIHt9XFxcIiBbbWFuYWdlZF0pKVxcbiAgICAgIG5ld2JhbClcXG4gIClcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVJfWENIQUlOOmJvb2xcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgICB0YXJnZXQtY2hhaW46c3RyaW5nXFxuICAgIClcXG5cXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSX1hDSEFJTi1tZ3JcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJDcm9zcy1jaGFpbiB0cmFuc2ZlcnMgcmVxdWlyZSBhIHBvc2l0aXZlIGFtb3VudFxcXCIpXFxuICAgIChjb21wb3NlLWNhcGFiaWxpdHkgKERFQklUIHNlbmRlcikpXFxuICApXFxuXFxuICAoZGVmdW4gVFJBTlNGRVJfWENIQUlOLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAoZW5mb3JjZSAoPj0gbWFuYWdlZCByZXF1ZXN0ZWQpXFxuICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVJfWENIQUlOIGV4Y2VlZGVkIGZvciBiYWxhbmNlIHt9XFxcIiBbbWFuYWdlZF0pKVxcbiAgICAwLjBcXG4gIClcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVJfWENIQUlOX1JFQ0Q6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHNvdXJjZS1jaGFpbjpzdHJpbmdcXG4gICAgKVxcbiAgICBAZXZlbnQgdHJ1ZVxcbiAgKVxcblxcbiAgOyB2MyBjYXBhYmlsaXRpZXNcXG4gIChkZWZjYXAgUkVMRUFTRV9BTExPQ0FUSU9OXFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIkV2ZW50IGZvciBhbGxvY2F0aW9uIHJlbGVhc2UsIGNhbiBiZSB1c2VkIGZvciBzaWcgc2NvcGluZy5cXFwiXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJndWFyZFxcXCIgOj0gb2xkLWd1YXJkIH1cXG5cXG4gICAgICAgIChlbmZvcmNlLWd1YXJkIG9sZC1ndWFyZClcXG5cXG4gICAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgIHsgXFxcImd1YXJkXFxcIiA6IG5ldy1ndWFyZCB9XFxuICAgICAgICAgICkpKVxcbiAgICApXFxuXFxuXFxuICAoZGVmdW4gcHJlY2lzaW9uOmludGVnZXJcXG4gICAgKClcXG4gICAgTUlOSU1VTV9QUkVDSVNJT04pXFxuXFxuICAoZGVmdW4gdHJhbnNmZXI6c3RyaW5nIChzZW5kZXI6c3RyaW5nIHJlY2VpdmVyOnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBzZW5kZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgcmVjZWl2ZXIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpIF1cXG5cXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcilcXG4gICAgICBcXFwic2VuZGVyIGNhbm5vdCBiZSB0aGUgcmVjZWl2ZXIgb2YgYSB0cmFuc2ZlclxcXCIpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcInRyYW5zZmVyIGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFRSQU5TRkVSIHNlbmRlciByZWNlaXZlciBhbW91bnQpXFxuICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIHJlY2VpdmVyXFxuICAgICAgICB7IFxcXCJndWFyZFxcXCIgOj0gZyB9XFxuXFxuICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIGcgYW1vdW50KSlcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biB0cmFuc2Zlci1jcmVhdGU6c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgYW1vdW50OmRlY2ltYWwgKVxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpIF1cXG5cXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcilcXG4gICAgICBcXFwic2VuZGVyIGNhbm5vdCBiZSB0aGUgcmVjZWl2ZXIgb2YgYSB0cmFuc2ZlclxcXCIpXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcInRyYW5zZmVyIGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFRSQU5TRkVSIHNlbmRlciByZWNlaXZlciBhbW91bnQpXFxuICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgKGNyZWRpdCByZWNlaXZlciByZWNlaXZlci1ndWFyZCBhbW91bnQpKVxcbiAgICApXFxuXFxuICAoZGVmdW4gY29pbmJhc2U6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhY2NvdW50LWd1YXJkOmd1YXJkIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJJbnRlcm5hbCBmdW5jdGlvbiBmb3IgdGhlIGluaXRpYWwgY3JlYXRpb24gb2YgY29pbnMuICBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgXFxcXGNhbm5vdCBiZSB1c2VkIG91dHNpZGUgb2YgdGhlIGNvaW4gY29udHJhY3QuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENPSU5CQVNFKSlcXG4gICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIGFjY291bnQgYW1vdW50KSkgO3YzXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIGFjY291bnQgYW1vdW50KSkgO3YzXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuXFxuICAgICAgKGVuZm9yY2UgKDw9IGFtb3VudCBiYWxhbmNlKSBcXFwiSW5zdWZmaWNpZW50IGZ1bmRzXFxcIilcXG5cXG4gICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAoLSBiYWxhbmNlIGFtb3VudCkgfVxcbiAgICAgICAgKSlcXG4gICAgKVxcblxcbiAgKGRlZnBhY3QgZnVuZC10eCAoc2VuZGVyOnN0cmluZyBtaW5lcjpzdHJpbmcgbWluZXItZ3VhcmQ6Z3VhcmQgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiJ2Z1bmQtdHgnIGlzIGEgc3BlY2lhbCBwYWN0IHRvIGZ1bmQgYSB0cmFuc2FjdGlvbiBpbiB0d28gc3RlcHMsICAgICBcXFxcXFxuICAgIFxcXFx3aXRoIHRoZSBhY3R1YWwgdHJhbnNhY3Rpb24gdHJhbnNwaXJpbmcgaW4gdGhlIG1pZGRsZTogICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFxcXFxcXG4gICAgXFxcXCAgMSkgQSBidXlpbmcgcGhhc2UsIGRlYml0aW5nIHRoZSBzZW5kZXIgZm9yIHRvdGFsIGdhcyBhbmQgZmVlLCB5aWVsZGluZyBcXFxcXFxuICAgIFxcXFwgICAgIFRYX01BWF9DSEFSR0UuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAyKSBBIHNldHRsZW1lbnQgcGhhc2UsIHJlc3VtaW5nIFRYX01BWF9DSEFSR0UsIGFuZCBhbGxvY2F0aW5nIHRvIHRoZSAgIFxcXFxcXG4gICAgXFxcXCAgICAgY29pbmJhc2UgYWNjb3VudCBmb3IgdXNlZCBnYXMgYW5kIGZlZSwgYW5kIHNlbmRlciBhY2NvdW50IGZvciBiYWwtICBcXFxcXFxuICAgIFxcXFwgICAgIGFuY2UgKHVudXNlZCBnYXMsIGlmIGFueSkuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICAgIDsocHJvcGVydHkgY29uc2VydmVzLW1hc3MpIG5vdCBzdXBwb3J0ZWQgeWV0XFxuICAgICAgICAgICBdXFxuXFxuICAgIChzdGVwIChidXktZ2FzIHNlbmRlciB0b3RhbCkpXFxuICAgIChzdGVwIChyZWRlZW0tZ2FzIG1pbmVyIG1pbmVyLWd1YXJkIHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZWJpdDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJEZWJpdCBBTU9VTlQgZnJvbSBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJkZWJpdCBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChERUJJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICAoZGVmdW4gY3JlZGl0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkNyZWRpdCBBTU9VTlQgdG8gQUNDT1VOVCBiYWxhbmNlXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcImNyZWRpdCBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDUkVESVQgYWNjb3VudCkpXFxuICAgICh3aXRoLWRlZmF1bHQtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IC0xLjAsIFxcXCJndWFyZFxcXCIgOiBndWFyZCB9XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSwgXFxcImd1YXJkXFxcIiA6PSByZXRnIH1cXG4gICAgICA7IHdlIGRvbid0IHdhbnQgdG8gb3ZlcndyaXRlIGFuIGV4aXN0aW5nIGd1YXJkIHdpdGggdGhlIHVzZXItc3VwcGxpZWQgb25lXFxuICAgICAgKGVuZm9yY2UgKD0gcmV0ZyBndWFyZClcXG4gICAgICAgIFxcXCJhY2NvdW50IGd1YXJkcyBkbyBub3QgbWF0Y2hcXFwiKVxcblxcbiAgICAgIChsZXQgKChpcy1uZXdcXG4gICAgICAgICAgICAgKGlmICg9IGJhbGFuY2UgLTEuMClcXG4gICAgICAgICAgICAgICAgIChlbmZvcmNlLXJlc2VydmVkIGFjY291bnQgZ3VhcmQpXFxuICAgICAgICAgICAgICAgZmFsc2UpKSlcXG5cXG4gICAgICAgICh3cml0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAoaWYgaXMtbmV3IGFtb3VudCAoKyBiYWxhbmNlIGFtb3VudCkpXFxuICAgICAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogcmV0Z1xcbiAgICAgICAgICB9KSlcXG4gICAgICApKVxcblxcbiAgKGRlZnVuIGNoZWNrLXJlc2VydmVkOnN0cmluZyAoYWNjb3VudDpzdHJpbmcpXFxuICAgIFxcXCIgQ2hlY2tzIEFDQ09VTlQgZm9yIHJlc2VydmVkIG5hbWUgYW5kIHJldHVybnMgdHlwZSBpZiBcXFxcXFxuICAgIFxcXFwgZm91bmQgb3IgZW1wdHkgc3RyaW5nLiBSZXNlcnZlZCBuYW1lcyBzdGFydCB3aXRoIGEgXFxcXFxcbiAgICBcXFxcIHNpbmdsZSBjaGFyIGFuZCBjb2xvbiwgZS5nLiAnYzpmb28nLCB3aGljaCB3b3VsZCByZXR1cm4gJ2MnIGFzIHR5cGUuXFxcIlxcbiAgICAobGV0ICgocGZ4ICh0YWtlIDIgYWNjb3VudCkpKVxcbiAgICAgIChpZiAoPSBcXFwiOlxcXCIgKHRha2UgLTEgcGZ4KSkgKHRha2UgMSBwZngpIFxcXCJcXFwiKSkpXFxuXFxuICAoZGVmdW4gZW5mb3JjZS1yZXNlcnZlZDpib29sIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQGRvYyBcXFwiRW5mb3JjZSByZXNlcnZlZCBhY2NvdW50IG5hbWUgcHJvdG9jb2xzLlxcXCJcXG4gICAgKGlmICh2YWxpZGF0ZS1wcmluY2lwYWwgZ3VhcmQgYWNjb3VudClcXG4gICAgICB0cnVlXFxuICAgICAgKGxldCAoKHIgKGNoZWNrLXJlc2VydmVkIGFjY291bnQpKSlcXG4gICAgICAgIChpZiAoPSByIFxcXCJcXFwiKVxcbiAgICAgICAgICB0cnVlXFxuICAgICAgICAgIChpZiAoPSByIFxcXCJrXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZSBcXFwiU2luZ2xlLWtleSBhY2NvdW50IHByb3RvY29sIHZpb2xhdGlvblxcXCIpXFxuICAgICAgICAgICAgKGVuZm9yY2UgZmFsc2VcXG4gICAgICAgICAgICAgIChmb3JtYXQgXFxcIlJlc2VydmVkIHByb3RvY29sIGd1YXJkIHZpb2xhdGlvbjoge31cXFwiIFtyXSkpXFxuICAgICAgICAgICAgKSkpKSlcXG5cXG5cXG4gIChkZWZzY2hlbWEgY3Jvc3NjaGFpbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiU2NoZW1hIGZvciB5aWVsZGVkIHZhbHVlIGluIGNyb3NzLWNoYWluIHRyYW5zZmVyc1xcXCJcXG4gICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIHNvdXJjZS1jaGFpbjpzdHJpbmcpXFxuXFxuICAoZGVmcGFjdCB0cmFuc2Zlci1jcm9zc2NoYWluOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eVxcbiAgICAgICAgKFRSQU5TRkVSX1hDSEFJTiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50IHRhcmdldC1jaGFpbilcXG5cXG4gICAgICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKCE9IFxcXCJcXFwiIHRhcmdldC1jaGFpbikgXFxcImVtcHR5IHRhcmdldC1jaGFpblxcXCIpXFxuICAgICAgICAoZW5mb3JjZSAoIT0gKGF0ICdjaGFpbi1pZCAoY2hhaW4tZGF0YSkpIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgXFxcImNhbm5vdCBydW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzIHRvIHRoZSBzYW1lIGNoYWluXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgICAgIFxcXCJ0cmFuc2ZlciBxdWFudGl0eSBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAgICAgOzsgc3RlcCAxIC0gZGViaXQgZGVsZXRlLWFjY291bnQgb24gY3VycmVudCBjaGFpblxcbiAgICAgICAgKGRlYml0IHNlbmRlciBhbW91bnQpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIFxcXCJcXFwiIGFtb3VudCkpXFxuXFxuICAgICAgICAobGV0XFxuICAgICAgICAgICgoY3Jvc3NjaGFpbi1kZXRhaWxzOm9iamVjdHtjcm9zc2NoYWluLXNjaGVtYX1cXG4gICAgICAgICAgICB7IFxcXCJyZWNlaXZlclxcXCIgOiByZWNlaXZlclxcbiAgICAgICAgICAgICwgXFxcInJlY2VpdmVyLWd1YXJkXFxcIiA6IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAgICAgLCBcXFwiYW1vdW50XFxcIiA6IGFtb3VudFxcbiAgICAgICAgICAgICwgXFxcInNvdXJjZS1jaGFpblxcXCIgOiAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSlcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImNvaW4tY29udHJhY3QtdjRcIn0ifQ" + ] diff --git a/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs new file mode 100644 index 0000000000..19bd9563d6 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/CoinV5Transactions.hs @@ -0,0 +1,19 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.CoinV5Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiOERDei1xb2pVcWUyRTJ6R1V1clhuanBJUHlxSFVlcFlmdFdockhUZVd3SSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICAoZGVmY2FwIFRSQU5TRkVSX1hDSEFJTjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICApXFxuXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUl9YQ0hBSU4tbWdyXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiQ3Jvc3MtY2hhaW4gdHJhbnNmZXJzIHJlcXVpcmUgYSBwb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSX1hDSEFJTi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGVuZm9yY2UgKD49IG1hbmFnZWQgcmVxdWVzdGVkKVxcbiAgICAgIChmb3JtYXQgXFxcIlRSQU5TRkVSX1hDSEFJTiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgMC4wXFxuICApXFxuXFxuICAoZGVmY2FwIFRSQU5TRkVSX1hDSEFJTl9SRUNEOmJvb2xcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgICBzb3VyY2UtY2hhaW46c3RyaW5nXFxuICAgIClcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgdjMgY2FwYWJpbGl0aWVzXFxuICAoZGVmY2FwIFJFTEVBU0VfQUxMT0NBVElPTlxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgKVxcbiAgICBAZG9jIFxcXCJFdmVudCBmb3IgYWxsb2NhdGlvbiByZWxlYXNlLCBjYW4gYmUgdXNlZCBmb3Igc2lnIHNjb3BpbmcuXFxcIlxcbiAgICBAZXZlbnQgdHJ1ZVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb25zdGFudHNcXG5cXG4gIChkZWZjb25zdCBDT0lOX0NIQVJTRVQgQ0hBUlNFVF9MQVRJTjFcXG4gICAgXFxcIlRoZSBkZWZhdWx0IGNvaW4gY29udHJhY3QgY2hhcmFjdGVyIHNldFxcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9QUkVDSVNJT04gMTJcXG4gICAgXFxcIk1pbmltdW0gYWxsb3dlZCBwcmVjaXNpb24gZm9yIGNvaW4gdHJhbnNhY3Rpb25zXFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX0FDQ09VTlRfTEVOR1RIIDNcXG4gICAgXFxcIk1pbmltdW0gYWNjb3VudCBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUFYSU1VTV9BQ0NPVU5UX0xFTkdUSCAyNTZcXG4gICAgXFxcIk1heGltdW0gYWNjb3VudCBuYW1lIGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBWQUxJRF9DSEFJTl9JRFMgKG1hcCAoaW50LXRvLXN0ciAxMCkgKGVudW1lcmF0ZSAwIDE5KSlcXG4gICAgXFxcIkxpc3Qgb2YgYWxsIHZhbGlkIENoYWlud2ViIGNoYWluIGlkc1xcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFV0aWxpdGllc1xcblxcbiAgKGRlZnVuIGVuZm9yY2UtdW5pdDpib29sIChhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiRW5mb3JjZSBtaW5pbXVtIHByZWNpc2lvbiBhbGxvd2VkIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoPSAoZmxvb3IgYW1vdW50IE1JTklNVU1fUFJFQ0lTSU9OKVxcbiAgICAgICAgIGFtb3VudClcXG4gICAgICAoZm9ybWF0IFxcXCJBbW91bnQgdmlvbGF0ZXMgbWluaW11bSBwcmVjaXNpb246IHt9XFxcIiBbYW1vdW50XSkpXFxuICAgIClcXG5cXG4gIChkZWZ1biB2YWxpZGF0ZS1hY2NvdW50IChhY2NvdW50OnN0cmluZylcXG4gICAgQGRvYyBcXFwiRW5mb3JjZSB0aGF0IGFuIGFjY291bnQgbmFtZSBjb25mb3JtcyB0byB0aGUgY29pbiBjb250cmFjdCBcXFxcXFxuICAgICAgICAgXFxcXG1pbmltdW0gYW5kIG1heGltdW0gbGVuZ3RoIHJlcXVpcmVtZW50cywgYXMgd2VsbCBhcyB0aGUgICAgXFxcXFxcbiAgICAgICAgIFxcXFxsYXRpbi0xIGNoYXJhY3RlciBzZXQuXFxcIlxcblxcbiAgICAoZW5mb3JjZVxcbiAgICAgIChpcy1jaGFyc2V0IENPSU5fQ0hBUlNFVCBhY2NvdW50KVxcbiAgICAgIChmb3JtYXRcXG4gICAgICAgIFxcXCJBY2NvdW50IGRvZXMgbm90IGNvbmZvcm0gdG8gdGhlIGNvaW4gY29udHJhY3QgY2hhcnNldDoge31cXFwiXFxuICAgICAgICBbYWNjb3VudF0pKVxcblxcbiAgICAobGV0ICgoYWNjb3VudC1sZW5ndGggKGxlbmd0aCBhY2NvdW50KSkpXFxuXFxuICAgICAgKGVuZm9yY2VcXG4gICAgICAgICg-PSBhY2NvdW50LWxlbmd0aCBNSU5JTVVNX0FDQ09VTlRfTEVOR1RIKVxcbiAgICAgICAgKGZvcm1hdFxcbiAgICAgICAgICBcXFwiQWNjb3VudCBuYW1lIGRvZXMgbm90IGNvbmZvcm0gdG8gdGhlIG1pbiBsZW5ndGggcmVxdWlyZW1lbnQ6IHt9XFxcIlxcbiAgICAgICAgICBbYWNjb3VudF0pKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPD0gYWNjb3VudC1sZW5ndGggTUFYSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtYXggbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG4gICAgICApXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gQ29udHJhY3RcXG5cXG4gIChkZWZ1biBnYXMtb25seSAoKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMtb25seSB1c2VyIGd1YXJkcy5cXFwiXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpKVxcblxcbiAgKGRlZnVuIGdhcy1ndWFyZCAoZ3VhcmQ6Z3VhcmQpXFxuICAgIFxcXCJQcmVkaWNhdGUgZm9yIGdhcyArIHNpbmdsZSBrZXkgdXNlciBndWFyZHNcXFwiXFxuICAgIChlbmZvcmNlLW9uZVxcbiAgICAgIFxcXCJFbmZvcmNlIGVpdGhlciB0aGUgcHJlc2VuY2Ugb2YgYSBHQVMgY2FwIG9yIGtleXNldFxcXCJcXG4gICAgICBbIChnYXMtb25seSlcXG4gICAgICAgIChlbmZvcmNlLWd1YXJkIGd1YXJkKVxcbiAgICAgIF0pKVxcblxcbiAgKGRlZnVuIGJ1eS1nYXM6c3RyaW5nIChzZW5kZXI6c3RyaW5nIHRvdGFsOmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIlRoaXMgZnVuY3Rpb24gZGVzY3JpYmVzIHRoZSBtYWluICdnYXMgYnV5JyBvcGVyYXRpb24uIEF0IHRoaXMgcG9pbnQgXFxcXFxcbiAgICBcXFxcTUlORVIgaGFzIGJlZW4gY2hvc2VuIGZyb20gdGhlIHBvb2wsIGFuZCB3aWxsIGJlIHZhbGlkYXRlZC4gVGhlIFNFTkRFUiAgIFxcXFxcXG4gICAgXFxcXG9mIHRoaXMgdHJhbnNhY3Rpb24gaGFzIHNwZWNpZmllZCBhIGdhcyBsaW1pdCBMSU1JVCAobWF4aW11bSBnYXMpIGZvciAgICBcXFxcXFxuICAgIFxcXFx0aGUgdHJhbnNhY3Rpb24sIGFuZCB0aGUgcHJpY2UgaXMgdGhlIHNwb3QgcHJpY2Ugb2YgZ2FzIGF0IHRoYXQgdGltZS4gICAgXFxcXFxcbiAgICBcXFxcVGhlIGdhcyBidXkgd2lsbCBiZSBleGVjdXRlZCBwcmlvciB0byBleGVjdXRpbmcgU0VOREVSJ3MgY29kZS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcblxcbiAgICAoZW5mb3JjZS11bml0IHRvdGFsKVxcbiAgICAoZW5mb3JjZSAoPiB0b3RhbCAwLjApIFxcXCJnYXMgc3VwcGx5IG11c3QgYmUgYSBwb3NpdGl2ZSBxdWFudGl0eVxcXCIpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKERFQklUIHNlbmRlcilcXG4gICAgICAoZGViaXQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlZGVlbS1nYXM6c3RyaW5nIChtaW5lcjpzdHJpbmcgbWluZXItZ3VhcmQ6Z3VhcmQgc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAncmVkZWVtIGdhcycgb3BlcmF0aW9uLiBBdCB0aGlzICAgIFxcXFxcXG4gICAgXFxcXHBvaW50LCB0aGUgU0VOREVSJ3MgdHJhbnNhY3Rpb24gaGFzIGJlZW4gZXhlY3V0ZWQsIGFuZCB0aGUgZ2FzIHRoYXQgICAgICBcXFxcXFxuICAgIFxcXFx3YXMgY2hhcmdlZCBoYXMgYmVlbiBjYWxjdWxhdGVkLiBNSU5FUiB3aWxsIGJlIGNyZWRpdGVkIHRoZSBnYXMgY29zdCwgICAgXFxcXFxcbiAgICBcXFxcYW5kIFNFTkRFUiB3aWxsIHJlY2VpdmUgdGhlIHJlbWFpbmRlciB1cCB0byB0aGUgbGltaXRcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBtaW5lcilcXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoR0FTKSlcXG4gICAgKGxldCpcXG4gICAgICAoKGZlZSAocmVhZC1kZWNpbWFsIFxcXCJmZWVcXFwiKSlcXG4gICAgICAgKHJlZnVuZCAoLSB0b3RhbCBmZWUpKSlcXG5cXG4gICAgICAoZW5mb3JjZS11bml0IGZlZSlcXG4gICAgICAoZW5mb3JjZSAoPj0gZmVlIDAuMClcXG4gICAgICAgIFxcXCJmZWUgbXVzdCBiZSBhIG5vbi1uZWdhdGl2ZSBxdWFudGl0eVxcXCIpXFxuXFxuICAgICAgKGVuZm9yY2UgKD49IHJlZnVuZCAwLjApXFxuICAgICAgICBcXFwicmVmdW5kIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgbWluZXIgZmVlKSkgO3YzXFxuXFxuICAgICAgICA7IGRpcmVjdGx5IHVwZGF0ZSBpbnN0ZWFkIG9mIGNyZWRpdFxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBzZW5kZXIpXFxuICAgICAgICAoaWYgKD4gcmVmdW5kIDAuMClcXG4gICAgICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgICAgICAgICh1cGRhdGUgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICAgIHsgXFxcImJhbGFuY2VcXFwiOiAoKyBiYWxhbmNlIHJlZnVuZCkgfSkpXFxuXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuXFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIG1pbmVyKVxcbiAgICAgICAgKGlmICg-IGZlZSAwLjApXFxuICAgICAgICAgIChjcmVkaXQgbWluZXIgbWluZXItZ3VhcmQgZmVlKVxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcbiAgICAgIClcXG5cXG4gICAgKVxcblxcbiAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgZ3VhcmQ6Z3VhcmQpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS1yZXNlcnZlZCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAoaW5zZXJ0IGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiBndWFyZFxcbiAgICAgIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsIChhY2NvdW50OnN0cmluZylcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICBiYWxhbmNlXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gZGV0YWlsczpvYmplY3R7ZnVuZ2libGUtdjIuYWNjb3VudC1kZXRhaWxzfVxcbiAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZyB9XFxuICAgICAgeyBcXFwiYWNjb3VudFxcXCIgOiBhY2NvdW50XFxuICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiBiYWxcXG4gICAgICAsIFxcXCJndWFyZFxcXCI6IGcgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJvdGF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIG5ldy1ndWFyZDpndWFyZClcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoUk9UQVRFIGFjY291bnQpXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcblxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcblxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImNvaW4tY29udHJhY3QtdjVcIn0ifQ" + ] diff --git a/src/Chainweb/Pact/Transactions/CoinV6Transactions.hs b/src/Chainweb/Pact/Transactions/CoinV6Transactions.hs new file mode 100644 index 0000000000..2eea8935e4 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/CoinV6Transactions.hs @@ -0,0 +1,19 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.CoinV6Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiOGJXY0xlSzFSYUZYVGVLbnhzQ2tuWW5QcnAza29vX0cxTk05eHl0VmFjVSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG4gIChpbXBsZW1lbnRzIGZ1bmdpYmxlLXhjaGFpbi12MSlcXG5cXG4gIDs7IGNvaW4tdjJcXG4gIChibGVzcyBcXFwidXRfSl9aTmtveWFQVUVKaGl3VmVXbmtTUW45SlQ5c1FDV0tkampWVnJXb1xcXCIpXFxuXFxuICA7OyBjb2luIHYzXFxuICAoYmxlc3MgXFxcIjFvc19zTEFVWXZCenNwbjVqamF3dFJwSldpSDFXUGZoeU5yYWVWdlNJd1VcXFwiKVxcblxcbiAgOzsgY29pbiB2NFxcbiAgKGJsZXNzIFxcXCJCalpXMFQyYWM2cUVfSTVYOEdFNGZhbDZ0VHFqaExUQzdteTB5dFFTeExVXFxcIilcXG5cXG4gIDs7IGNvaW4gdjVcXG4gIChibGVzcyBcXFwickU3RFU4amxRTDl4X01QWXVuaVpKZjVJQ0JUQUVIQUlGUUNCNGJsb2ZQNFxcXCIpXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IFNjaGVtYXMgYW5kIFRhYmxlc1xcblxcbiAgKGRlZnNjaGVtYSBjb2luLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJUaGUgY29pbiBjb250cmFjdCB0b2tlbiBzY2hlbWFcXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKD49IGJhbGFuY2UgMC4wKSkgXVxcblxcbiAgICBiYWxhbmNlOmRlY2ltYWxcXG4gICAgZ3VhcmQ6Z3VhcmQpXFxuXFxuICAoZGVmdGFibGUgY29pbi10YWJsZTp7Y29pbi1zY2hlbWF9KVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDYXBhYmlsaXRpZXNcXG5cXG4gIChkZWZjYXAgR09WRVJOQU5DRSAoKVxcbiAgICAoZW5mb3JjZSBmYWxzZSBcXFwiRW5mb3JjZSBub24tdXBncmFkZWFiaWxpdHlcXFwiKSlcXG5cXG4gIChkZWZjYXAgR0FTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgZ2FzIGJ1eSBhbmQgcmVkZWVtXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBDT0lOQkFTRSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSB0byBwcm90ZWN0IG1pbmVyIHJld2FyZFxcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgR0VORVNJUyAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBjb25zdHJhaW5pbmcgZ2VuZXNpcyB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIFJFTUVESUFURSAoKVxcbiAgICBcXFwiTWFnaWMgY2FwYWJpbGl0eSBmb3IgcmVtZWRpYXRpb24gdHJhbnNhY3Rpb25zXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBERUJJVCAoc2VuZGVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGRlYml0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlLWd1YXJkIChhdCAnZ3VhcmQgKHJlYWQgY29pbi10YWJsZSBzZW5kZXIpKSlcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciBcXFwiXFxcIikgXFxcInZhbGlkIHNlbmRlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBDUkVESVQgKHJlY2VpdmVyOnN0cmluZylcXG4gICAgXFxcIkNhcGFiaWxpdHkgZm9yIG1hbmFnaW5nIGNyZWRpdGluZyBvcGVyYXRpb25zXFxcIlxcbiAgICAoZW5mb3JjZSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpIFxcXCJ2YWxpZCByZWNlaXZlclxcXCIpKVxcblxcbiAgKGRlZmNhcCBST1RBVEUgKGFjY291bnQ6c3RyaW5nKVxcbiAgICBAZG9jIFxcXCJBdXRvbm9tb3VzbHkgbWFuYWdlZCBjYXBhYmlsaXR5IGZvciBndWFyZCByb3RhdGlvblxcXCJcXG4gICAgQG1hbmFnZWRcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBtYW5hZ2VkIGFtb3VudCBUUkFOU0ZFUi1tZ3JcXG4gICAgKGVuZm9yY2UgKCE9IHNlbmRlciByZWNlaXZlcikgXFxcInNhbWUgc2VuZGVyIGFuZCByZWNlaXZlclxcXCIpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiUG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUi1tZ3I6ZGVjaW1hbFxcbiAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgIHJlcXVlc3RlZDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgKGxldCAoKG5ld2JhbCAoLSBtYW5hZ2VkIHJlcXVlc3RlZCkpKVxcbiAgICAgIChlbmZvcmNlICg-PSBuZXdiYWwgMC4wKVxcbiAgICAgICAgKGZvcm1hdCBcXFwiVFJBTlNGRVIgZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgICAgbmV3YmFsKVxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU46Ym9vbFxcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgKVxcblxcbiAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVJfWENIQUlOLW1nclxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMCkgXFxcIkNyb3NzLWNoYWluIHRyYW5zZmVycyByZXF1aXJlIGEgcG9zaXRpdmUgYW1vdW50XFxcIilcXG4gICAgKGNvbXBvc2UtY2FwYWJpbGl0eSAoREVCSVQgc2VuZGVyKSlcXG4gIClcXG5cXG4gIChkZWZ1biBUUkFOU0ZFUl9YQ0hBSU4tbWdyOmRlY2ltYWxcXG4gICAgKCBtYW5hZ2VkOmRlY2ltYWxcXG4gICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIChlbmZvcmNlICg-PSBtYW5hZ2VkIHJlcXVlc3RlZClcXG4gICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUl9YQ0hBSU4gZXhjZWVkZWQgZm9yIGJhbGFuY2Uge31cXFwiIFttYW5hZ2VkXSkpXFxuICAgIDAuMFxcbiAgKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUl9YQ0hBSU5fUkVDRDpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgICAgc291cmNlLWNoYWluOnN0cmluZ1xcbiAgICApXFxuICAgIEBldmVudCB0cnVlXFxuICApXFxuXFxuICA7IHYzIGNhcGFiaWxpdGllc1xcbiAgKGRlZmNhcCBSRUxFQVNFX0FMTE9DQVRJT05cXG4gICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQGRvYyBcXFwiRXZlbnQgZm9yIGFsbG9jYXRpb24gcmVsZWFzZSwgY2FuIGJlIHVzZWQgZm9yIHNpZyBzY29waW5nLlxcXCJcXG4gICAgQGV2ZW50IHRydWVcXG4gIClcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29uc3RhbnRzXFxuXFxuICAoZGVmY29uc3QgQ09JTl9DSEFSU0VUIENIQVJTRVRfTEFUSU4xXFxuICAgIFxcXCJUaGUgZGVmYXVsdCBjb2luIGNvbnRyYWN0IGNoYXJhY3RlciBzZXRcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fUFJFQ0lTSU9OIDEyXFxuICAgIFxcXCJNaW5pbXVtIGFsbG93ZWQgcHJlY2lzaW9uIGZvciBjb2luIHRyYW5zYWN0aW9uc1xcXCIpXFxuXFxuICAoZGVmY29uc3QgTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSCAzXFxuICAgIFxcXCJNaW5pbXVtIGFjY291bnQgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1BWElNVU1fQUNDT1VOVF9MRU5HVEggMjU2XFxuICAgIFxcXCJNYXhpbXVtIGFjY291bnQgbmFtZSBsZW5ndGggYWRtaXNzaWJsZSBmb3IgY29pbiBhY2NvdW50c1xcXCIpXFxuXFxuICAoZGVmY29uc3QgVkFMSURfQ0hBSU5fSURTIChtYXAgKGludC10by1zdHIgMTApIChlbnVtZXJhdGUgMCAxOSkpXFxuICAgIFxcXCJMaXN0IG9mIGFsbCB2YWxpZCBDaGFpbndlYiBjaGFpbiBpZHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgc2VuZGVyIG1pbmVyIGZlZSkpIDt2M1xcblxcbiAgICAgICAgOyBkaXJlY3RseSB1cGRhdGUgaW5zdGVhZCBvZiBjcmVkaXRcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgc2VuZGVyKVxcbiAgICAgICAgKGlmICg-IHJlZnVuZCAwLjApXFxuICAgICAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBzZW5kZXJcXG4gICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG4gICAgICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIjogKCsgYmFsYW5jZSByZWZ1bmQpIH0pKVxcblxcbiAgICAgICAgICBcXFwibm9vcFxcXCIpKVxcblxcbiAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBtaW5lcilcXG4gICAgICAgIChpZiAoPiBmZWUgMC4wKVxcbiAgICAgICAgICAoY3JlZGl0IG1pbmVyIG1pbmVyLWd1YXJkIGZlZSlcXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG4gICAgICApXFxuXFxuICAgIClcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWNjb3VudDpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgKGluc2VydCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IDAuMFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiAgIDogZ3VhcmRcXG4gICAgICB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gZ2V0LWJhbGFuY2U6ZGVjaW1hbCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgYmFsYW5jZVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRldGFpbHM6b2JqZWN0e2Z1bmdpYmxlLXYyLmFjY291bnQtZGV0YWlsc31cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcbiAgICAgIHsgXFxcImFjY291bnRcXFwiIDogYWNjb3VudFxcbiAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogYmFsXFxuICAgICAgLCBcXFwiZ3VhcmRcXFwiOiBnIH0pXFxuICAgIClcXG5cXG4gIChkZWZ1biByb3RhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBuZXctZ3VhcmQ6Z3VhcmQpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKFJPVEFURSBhY2NvdW50KVxcblxcbiAgICAgIDsgQWxsb3cgcm90YXRpb24gb25seSBmb3IgdmFuaXR5IGFjY291bnRzLCBvclxcbiAgICAgIDsgcmUtcm90YXRpbmcgYSBwcmluY2lwYWwgYWNjb3VudCBiYWNrIHRvIGl0cyBwcm9wZXIgZ3VhcmRcXG4gICAgICAoZW5mb3JjZSAob3IgKG5vdCAoaXMtcHJpbmNpcGFsIGFjY291bnQpKVxcbiAgICAgICAgICAgICAgICAgICh2YWxpZGF0ZS1wcmluY2lwYWwgbmV3LWd1YXJkIGFjY291bnQpKVxcbiAgICAgICAgXFxcIkl0IGlzIHVuc2FmZSBmb3IgcHJpbmNpcGFsIGFjY291bnRzIHRvIHJvdGF0ZSB0aGVpciBndWFyZFxcXCIpXFxuXFxuICAgICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBvbGQtZ3VhcmQgfVxcbiAgICAgICAgKGVuZm9yY2UtZ3VhcmQgb2xkLWd1YXJkKVxcbiAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDogbmV3LWd1YXJkIH1cXG4gICAgICAgICAgKSkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBwcmVjaXNpb246aW50ZWdlclxcbiAgICAoKVxcbiAgICBNSU5JTVVNX1BSRUNJU0lPTilcXG5cXG4gIChkZWZ1biB0cmFuc2ZlcjpzdHJpbmcgKHNlbmRlcjpzdHJpbmcgcmVjZWl2ZXI6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgY29uc2VydmVzLW1hc3MpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCByZWNlaXZlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gc2VuZGVyIHJlY2VpdmVyKSkgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgcmVjZWl2ZXJcXG4gICAgICAgIHsgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG5cXG4gICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgZyBhbW91bnQpKVxcbiAgICAgIClcXG4gICAgKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyLWNyZWF0ZTpzdHJpbmdcXG4gICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgICBhbW91bnQ6ZGVjaW1hbCApXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgXVxcblxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKVxcbiAgICAgIFxcXCJzZW5kZXIgY2Fubm90IGJlIHRoZSByZWNlaXZlciBvZiBhIHRyYW5zZmVyXFxcIilcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwidHJhbnNmZXIgYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoVFJBTlNGRVIgc2VuZGVyIHJlY2VpdmVyIGFtb3VudClcXG4gICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biBjb2luYmFzZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFjY291bnQtZ3VhcmQ6Z3VhcmQgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkludGVybmFsIGZ1bmN0aW9uIGZvciB0aGUgaW5pdGlhbCBjcmVhdGlvbiBvZiBjb2lucy4gIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICBcXFxcY2Fubm90IGJlIHVzZWQgb3V0c2lkZSBvZiB0aGUgY29pbiBjb250cmFjdC5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoQ09JTkJBU0UpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIGFjY291bnQpXFxuICAgICAgKGNyZWRpdCBhY2NvdW50IGFjY291bnQtZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIHJlbWVkaWF0ZTpzdHJpbmcgKGFjY291bnQ6c3RyaW5nIGFtb3VudDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJBbGxvd3MgZm9yIHJlbWVkaWF0aW9uIHRyYW5zYWN0aW9ucy4gVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgICAgICAgXFxcXGlzIHByb3RlY3RlZCBieSB0aGUgUkVNRURJQVRFIGNhcGFiaWxpdHlcXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJSZW1lZGlhdGlvbiBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChSRU1FRElBVEUpKVxcbiAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBhbW91bnQpKSA7djNcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogLTEuMCwgXFxcImd1YXJkXFxcIiA6IGd1YXJkIH1cXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlLCBcXFwiZ3VhcmRcXFwiIDo9IHJldGcgfVxcbiAgICAgIDsgd2UgZG9uJ3Qgd2FudCB0byBvdmVyd3JpdGUgYW4gZXhpc3RpbmcgZ3VhcmQgd2l0aCB0aGUgdXNlci1zdXBwbGllZCBvbmVcXG4gICAgICAoZW5mb3JjZSAoPSByZXRnIGd1YXJkKVxcbiAgICAgICAgXFxcImFjY291bnQgZ3VhcmRzIGRvIG5vdCBtYXRjaFxcXCIpXFxuXFxuICAgICAgKGxldCAoKGlzLW5ld1xcbiAgICAgICAgICAgICAoaWYgKD0gYmFsYW5jZSAtMS4wKVxcbiAgICAgICAgICAgICAgICAgKGVuZm9yY2UtcmVzZXJ2ZWQgYWNjb3VudCBndWFyZClcXG4gICAgICAgICAgICAgICBmYWxzZSkpKVxcblxcbiAgICAgICAgKHdyaXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IChpZiBpcy1uZXcgYW1vdW50ICgrIGJhbGFuY2UgYW1vdW50KSlcXG4gICAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICAgIH0pKVxcbiAgICAgICkpXFxuXFxuICAoZGVmdW4gY2hlY2stcmVzZXJ2ZWQ6c3RyaW5nIChhY2NvdW50OnN0cmluZylcXG4gICAgXFxcIiBDaGVja3MgQUNDT1VOVCBmb3IgcmVzZXJ2ZWQgbmFtZSBhbmQgcmV0dXJucyB0eXBlIGlmIFxcXFxcXG4gICAgXFxcXCBmb3VuZCBvciBlbXB0eSBzdHJpbmcuIFJlc2VydmVkIG5hbWVzIHN0YXJ0IHdpdGggYSBcXFxcXFxuICAgIFxcXFwgc2luZ2xlIGNoYXIgYW5kIGNvbG9uLCBlLmcuICdjOmZvbycsIHdoaWNoIHdvdWxkIHJldHVybiAnYycgYXMgdHlwZS5cXFwiXFxuICAgIChsZXQgKChwZnggKHRha2UgMiBhY2NvdW50KSkpXFxuICAgICAgKGlmICg9IFxcXCI6XFxcIiAodGFrZSAtMSBwZngpKSAodGFrZSAxIHBmeCkgXFxcIlxcXCIpKSlcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXJlc2VydmVkOmJvb2wgKGFjY291bnQ6c3RyaW5nIGd1YXJkOmd1YXJkKVxcbiAgICBAZG9jIFxcXCJFbmZvcmNlIHJlc2VydmVkIGFjY291bnQgbmFtZSBwcm90b2NvbHMuXFxcIlxcbiAgICAoaWYgKHZhbGlkYXRlLXByaW5jaXBhbCBndWFyZCBhY2NvdW50KVxcbiAgICAgIHRydWVcXG4gICAgICAobGV0ICgociAoY2hlY2stcmVzZXJ2ZWQgYWNjb3VudCkpKVxcbiAgICAgICAgKGlmICg9IHIgXFxcIlxcXCIpXFxuICAgICAgICAgIHRydWVcXG4gICAgICAgICAgKGlmICg9IHIgXFxcImtcXFwiKVxcbiAgICAgICAgICAgIChlbmZvcmNlIGZhbHNlIFxcXCJTaW5nbGUta2V5IGFjY291bnQgcHJvdG9jb2wgdmlvbGF0aW9uXFxcIilcXG4gICAgICAgICAgICAoZW5mb3JjZSBmYWxzZVxcbiAgICAgICAgICAgICAgKGZvcm1hdCBcXFwiUmVzZXJ2ZWQgcHJvdG9jb2wgZ3VhcmQgdmlvbGF0aW9uOiB7fVxcXCIgW3JdKSlcXG4gICAgICAgICAgICApKSkpKVxcblxcblxcbiAgKGRlZnNjaGVtYSBjcm9zc2NoYWluLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHlpZWxkZWQgdmFsdWUgaW4gY3Jvc3MtY2hhaW4gdHJhbnNmZXJzXFxcIlxcbiAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgcmVjZWl2ZXItZ3VhcmQ6Z3VhcmRcXG4gICAgYW1vdW50OmRlY2ltYWxcXG4gICAgc291cmNlLWNoYWluOnN0cmluZylcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5XFxuICAgICAgICAoVFJBTlNGRVJfWENIQUlOIHNlbmRlciByZWNlaXZlciBhbW91bnQgdGFyZ2V0LWNoYWluKVxcblxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgc2VuZGVyKVxcbiAgICAgICAgKHZhbGlkYXRlLWFjY291bnQgcmVjZWl2ZXIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoIT0gXFxcIlxcXCIgdGFyZ2V0LWNoYWluKSBcXFwiZW1wdHkgdGFyZ2V0LWNoYWluXFxcIilcXG4gICAgICAgIChlbmZvcmNlICghPSAoYXQgJ2NoYWluLWlkIChjaGFpbi1kYXRhKSkgdGFyZ2V0LWNoYWluKVxcbiAgICAgICAgICBcXFwiY2Fubm90IHJ1biBjcm9zcy1jaGFpbiB0cmFuc2ZlcnMgdG8gdGhlIHNhbWUgY2hhaW5cXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICAgICAgXFxcInRyYW5zZmVyIHF1YW50aXR5IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoY29udGFpbnMgdGFyZ2V0LWNoYWluIFZBTElEX0NIQUlOX0lEUylcXG4gICAgICAgICAgXFxcInRhcmdldCBjaGFpbiBpcyBub3QgYSB2YWxpZCBjaGFpbndlYiBjaGFpbiBpZFxcXCIpXFxuXFxuICAgICAgICA7OyBzdGVwIDEgLSBkZWJpdCBkZWxldGUtYWNjb3VudCBvbiBjdXJyZW50IGNoYWluXFxuICAgICAgICAoZGViaXQgc2VuZGVyIGFtb3VudClcXG4gICAgICAgIChlbWl0LWV2ZW50IChUUkFOU0ZFUiBzZW5kZXIgXFxcIlxcXCIgYW1vdW50KSlcXG5cXG4gICAgICAgIChsZXRcXG4gICAgICAgICAgKChjcm9zc2NoYWluLWRldGFpbHM6b2JqZWN0e2Nyb3NzY2hhaW4tc2NoZW1hfVxcbiAgICAgICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6IHJlY2VpdmVyXFxuICAgICAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDogcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDogYW1vdW50XFxuICAgICAgICAgICAgLCBcXFwic291cmNlLWNoYWluXFxcIiA6IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKVxcbiAgICAgICAgICAgIH0pKVxcbiAgICAgICAgICAoeWllbGQgY3Jvc3NjaGFpbi1kZXRhaWxzIHRhcmdldC1jaGFpbilcXG4gICAgICAgICAgKSkpXFxuXFxuICAgIChzdGVwXFxuICAgICAgKHJlc3VtZVxcbiAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDo9IHJlY2VpdmVyXFxuICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOj0gcmVjZWl2ZXItZ3VhcmRcXG4gICAgICAgICwgXFxcImFtb3VudFxcXCIgOj0gYW1vdW50XFxuICAgICAgICAsIFxcXCJzb3VyY2UtY2hhaW5cXFwiIDo9IHNvdXJjZS1jaGFpblxcbiAgICAgICAgfVxcblxcbiAgICAgICAgKGVtaXQtZXZlbnQgKFRSQU5TRkVSIFxcXCJcXFwiIHJlY2VpdmVyIGFtb3VudCkpXFxuICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVJfWENIQUlOX1JFQ0QgXFxcIlxcXCIgcmVjZWl2ZXIgYW1vdW50IHNvdXJjZS1jaGFpbikpXFxuXFxuICAgICAgICA7OyBzdGVwIDIgLSBjcmVkaXQgY3JlYXRlIGFjY291bnQgb24gdGFyZ2V0IGNoYWluXFxuICAgICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpXFxuICAgICAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgQ29pbiBhbGxvY2F0aW9uc1xcblxcbiAgKGRlZnNjaGVtYSBhbGxvY2F0aW9uLXNjaGVtYVxcbiAgICBAZG9jIFxcXCJHZW5lc2lzIGFsbG9jYXRpb24gcmVnaXN0cnlcXFwiXFxuICAgIDtAbW9kZWwgWyAoaW52YXJpYW50ICg-PSBiYWxhbmNlIDAuMCkpIF1cXG5cXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGRhdGU6dGltZVxcbiAgICBndWFyZDpndWFyZFxcbiAgICByZWRlZW1lZDpib29sKVxcblxcbiAgKGRlZnRhYmxlIGFsbG9jYXRpb24tdGFibGU6e2FsbG9jYXRpb24tc2NoZW1hfSlcXG5cXG4gIChkZWZ1biBjcmVhdGUtYWxsb2NhdGlvbi1hY2NvdW50XFxuICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICBkYXRlOnRpbWVcXG4gICAgICBrZXlzZXQtcmVmOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG5cXG4gICAgQGRvYyBcXFwiQWRkIGFuIGVudHJ5IHRvIHRoZSBjb2luIGFsbG9jYXRpb24gdGFibGUuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxhbHNvIGNyZWF0ZXMgYSBjb3JyZXNwb25kaW5nIGVtcHR5IGNvaW4gY29udHJhY3QgYWNjb3VudCBcXFxcXFxuICAgICAgICAgXFxcXG9mIHRoZSBzYW1lIG5hbWUgYW5kIGd1YXJkLiBSZXF1aXJlcyBHRU5FU0lTIGNhcGFiaWxpdHkuIFxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdFTkVTSVMpKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcbiAgICAoZW5mb3JjZSAoPj0gYW1vdW50IDAuMClcXG4gICAgICBcXFwiYWxsb2NhdGlvbiBhbW91bnQgbXVzdCBiZSBub24tbmVnYXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKGxldFxcbiAgICAgICgoZ3VhcmQ6Z3VhcmQgKGtleXNldC1yZWYtZ3VhcmQga2V5c2V0LXJlZikpKVxcblxcbiAgICAgIChjcmVhdGUtYWNjb3VudCBhY2NvdW50IGd1YXJkKVxcblxcbiAgICAgIChpbnNlcnQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6IGFtb3VudFxcbiAgICAgICAgLCBcXFwiZGF0ZVxcXCIgOiBkYXRlXFxuICAgICAgICAsIFxcXCJndWFyZFxcXCIgOiBndWFyZFxcbiAgICAgICAgLCBcXFwicmVkZWVtZWRcXFwiIDogZmFsc2VcXG4gICAgICAgIH0pKSlcXG5cXG4gIChkZWZ1biByZWxlYXNlLWFsbG9jYXRpb25cXG4gICAgKCBhY2NvdW50OnN0cmluZyApXFxuXFxuICAgIEBkb2MgXFxcIlJlbGVhc2UgZnVuZHMgYXNzb2NpYXRlZCB3aXRoIGFsbG9jYXRpb24gQUNDT1VOVCBpbnRvIG1haW4gbGVkZ2VyLiAgIFxcXFxcXG4gICAgICAgICBcXFxcQUNDT1VOVCBtdXN0IGFscmVhZHkgZXhpc3QgaW4gbWFpbiBsZWRnZXIuIEFsbG9jYXRpb24gaXMgZGVhY3RpdmF0ZWQgXFxcXFxcbiAgICAgICAgIFxcXFxhZnRlciByZWxlYXNlLlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgICh3aXRoLXJlYWQgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZVxcbiAgICAgICwgXFxcImRhdGVcXFwiIDo9IHJlbGVhc2UtdGltZVxcbiAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6PSByZWRlZW1lZFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBndWFyZFxcbiAgICAgIH1cXG5cXG4gICAgICAobGV0ICgoY3Vyci10aW1lOnRpbWUgKGF0ICdibG9jay10aW1lIChjaGFpbi1kYXRhKSkpKVxcblxcbiAgICAgICAgKGVuZm9yY2UgKG5vdCByZWRlZW1lZClcXG4gICAgICAgICAgXFxcImFsbG9jYXRpb24gZnVuZHMgaGF2ZSBhbHJlYWR5IGJlZW4gcmVkZWVtZWRcXFwiKVxcblxcbiAgICAgICAgKGVuZm9yY2VcXG4gICAgICAgICAgKD49IGN1cnItdGltZSByZWxlYXNlLXRpbWUpXFxuICAgICAgICAgIChmb3JtYXQgXFxcImZ1bmRzIGxvY2tlZCB1bnRpbCB7fS4gY3VycmVudCB0aW1lOiB7fVxcXCIgW3JlbGVhc2UtdGltZSBjdXJyLXRpbWVdKSlcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKFJFTEVBU0VfQUxMT0NBVElPTiBhY2NvdW50IGJhbGFuY2UpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoZW1pdC1ldmVudCAoVFJBTlNGRVIgXFxcIlxcXCIgYWNjb3VudCBiYWxhbmNlKSlcXG4gICAgICAgICAgKGNyZWRpdCBhY2NvdW50IGd1YXJkIGJhbGFuY2UpXFxuXFxuICAgICAgICAgICh1cGRhdGUgYWxsb2NhdGlvbi10YWJsZSBhY2NvdW50XFxuICAgICAgICAgICAgeyBcXFwicmVkZWVtZWRcXFwiIDogdHJ1ZVxcbiAgICAgICAgICAgICwgXFxcImJhbGFuY2VcXFwiIDogMC4wXFxuICAgICAgICAgICAgfSlcXG5cXG4gICAgICAgICAgXFxcIkFsbG9jYXRpb24gc3VjY2Vzc2Z1bGx5IHJlbGVhc2VkIHRvIG1haW4gbGVkZ2VyXFxcIikpXFxuICAgICkpKVxcblxcbilcXG5cIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcImNvaW4tY29udHJhY3QtdjZcIn0ifQ" + ] diff --git a/src/Chainweb/Pact/Transactions/FungibleV2Transactions.hs b/src/Chainweb/Pact/Transactions/FungibleV2Transactions.hs new file mode 100644 index 0000000000..da6824b057 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/FungibleV2Transactions.hs @@ -0,0 +1,19 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.FungibleV2Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs new file mode 100644 index 0000000000..e4c81f1994 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet0Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet0Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiMEZ0bjlmYXp3amlpbkZTT3pxQVdXX2Vaa0VFVWo0b0lHbjN6UktENm1HcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiNDA2OWU3MmY2MmRlYjlkZGU4ZWUwNjg2ZWUyOTA4NTgwMTMxMTI4YjBhMzUwZTIzODkwZTliNDZlNjFmMTY2ZFxcXCIgNDYwOTAuNDYwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiAxNjEzMTYuNjEwMDApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJtYWlubmV0LXJlbWVkaWF0aW9ucy0wXCJ9In0" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs new file mode 100644 index 0000000000..c89811abb8 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet1Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet1Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiQTlUdFRpTHpqS2RiaHA1cE5IbnB2SlVKSThQOVc3azVsemY5aUo0TE1OOCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiYTMwZmZkM2JhN2ZhMDhkNGFhZmNmZDlmMTU1OTRkMjBlNzMyYjhmZGRhNDNmMDI0NzBkNzZiN2ViZmE0NjdlNlxcXCIgNjkxMzUuNjkwMDApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJtYWlubmV0LXJlbWVkaWF0aW9ucy0xXCJ9In0" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs new file mode 100644 index 0000000000..8504c9af74 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet2Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet2Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiYVBib0FkV2w3SnJ4d2lNUGNRNGRlSFJ0ZzZtaHJWczdfQ0JRXzZNUE1sTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiYTMwZmZkM2JhN2ZhMDhkNGFhZmNmZDlmMTU1OTRkMjBlNzMyYjhmZGRhNDNmMDI0NzBkNzZiN2ViZmE0NjdlNlxcXCIgOTIxODAuOTIwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCI0MDY5ZTcyZjYyZGViOWRkZThlZTA2ODZlZTI5MDg1ODAxMzExMjhiMGEzNTBlMjM4OTBlOWI0NmU2MWYxNjZkXFxcIiA0NjA5MC40NjAwMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcIm1haW5uZXQtcmVtZWRpYXRpb25zLTJcIn0ifQ" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs new file mode 100644 index 0000000000..eb3a4dec14 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet3Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet3Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoicXN0NzdIOFNvUVZkanhOSnBDRy1qWGsyZ0FXNzFJUEFqNVVIeDNGdWllcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiN2UzYzMwMmRmNDFlYzUxNzViNGU5YmY0M2E3ZDk2N2ZkMDg4ZmQyZGU3NzRlYzMxZTFlNmQwYjRhNzViZTVkM1xcXCIgMjMwNDUuMjMwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiA5MjE4MC45MjAwMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcIm1haW5uZXQtcmVtZWRpYXRpb25zLTNcIn0ifQ" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs new file mode 100644 index 0000000000..8259c60c75 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet4Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet4Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiZzNTMi1ieTM3ZDRac3Y0aTF6VVdtLW5jR1hJM2g4OHBzem9lYXk4eDVvTSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiYTMwZmZkM2JhN2ZhMDhkNGFhZmNmZDlmMTU1OTRkMjBlNzMyYjhmZGRhNDNmMDI0NzBkNzZiN2ViZmE0NjdlNlxcXCIgMTE1MjI2LjE1MDAwKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwibWFpbm5ldC1yZW1lZGlhdGlvbnMtNFwifSJ9" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs new file mode 100644 index 0000000000..633f7fe335 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet5Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet5Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiNy13bXVJT2ZrYkp6bzdYS3M0OUFUMjlCRF9BWENGOEcxYVJTc0t0S25GMCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiN2UzYzMwMmRmNDFlYzUxNzViNGU5YmY0M2E3ZDk2N2ZkMDg4ZmQyZGU3NzRlYzMxZTFlNmQwYjRhNzViZTVkM1xcXCIgNjkxMzUuNjkwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiA0NjA5MC40NjAwMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcIm1haW5uZXQtcmVtZWRpYXRpb25zLTVcIn0ifQ" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs new file mode 100644 index 0000000000..2475deda9a --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet6Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet6Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiQVloSFRveFllYktqcEZYa0t0LXl6c2c2Mkl2RlR2emFTNGZYa3JxSmlUYyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiNDA2OWU3MmY2MmRlYjlkZGU4ZWUwNjg2ZWUyOTA4NTgwMTMxMTI4YjBhMzUwZTIzODkwZTliNDZlNjFmMTY2ZFxcXCIgMjMwNDUuMjMwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiAxMTUyMjYuMTUwMDApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJtYWlubmV0LXJlbWVkaWF0aW9ucy02XCJ9In0" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs new file mode 100644 index 0000000000..b12a2eda30 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet7Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet7Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiSld1VXZmNlNWTzYyaFlKVEx6UTYxWTNxUS1BNEdwU1VXc0l4RXFmSjN0SSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiYTMwZmZkM2JhN2ZhMDhkNGFhZmNmZDlmMTU1OTRkMjBlNzMyYjhmZGRhNDNmMDI0NzBkNzZiN2ViZmE0NjdlNlxcXCIgMjMwNDUyLjMwMDAwKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwibWFpbm5ldC1yZW1lZGlhdGlvbnMtN1wifSJ9" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs new file mode 100644 index 0000000000..87e87b4208 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet8Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet8Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoibXkwVHZzYk1QNTRTTEVta3lqdU9YWDdLX2FJZHU4OWZ4cktyeDlIbE5JMCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiNDA2OWU3MmY2MmRlYjlkZGU4ZWUwNjg2ZWUyOTA4NTgwMTMxMTI4YjBhMzUwZTIzODkwZTliNDZlNjFmMTY2ZFxcXCIgMjMwNDUuMjMwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiA5MjE4MC45MjAwMClcIn19LFwic2lnbmVyc1wiOltdLFwibWV0YVwiOntcImNyZWF0aW9uVGltZVwiOjAsXCJ0dGxcIjoxNzI4MDAsXCJnYXNMaW1pdFwiOjAsXCJjaGFpbklkXCI6XCJcIixcImdhc1ByaWNlXCI6MCxcInNlbmRlclwiOlwiXCJ9LFwibm9uY2VcIjpcIm1haW5uZXQtcmVtZWRpYXRpb25zLThcIn0ifQ" + ] diff --git a/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs b/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs new file mode 100644 index 0000000000..bd9910d976 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/Mainnet9Transactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.Mainnet9Transactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiZTE4Z2QwRF9sREZHT2FKUUFLeGVRX0ozU0xYMHExdVZzUlFTSlVBWGJMRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwiN2UzYzMwMmRmNDFlYzUxNzViNGU5YmY0M2E3ZDk2N2ZkMDg4ZmQyZGU3NzRlYzMxZTFlNmQwYjRhNzViZTVkM1xcXCIgMjMwNDUuMjMwMDApXFxuKGNvaW4ucmVtZWRpYXRlIFxcXCJhMzBmZmQzYmE3ZmEwOGQ0YWFmY2ZkOWYxNTU5NGQyMGU3MzJiOGZkZGE0M2YwMjQ3MGQ3NmI3ZWJmYTQ2N2U2XFxcIiAyOTk1ODcuOTkwMDApXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJtYWlubmV0LXJlbWVkaWF0aW9ucy05XCJ9In0" + ] diff --git a/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs b/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs new file mode 100644 index 0000000000..9f161a9c0c --- /dev/null +++ b/src/Chainweb/Pact/Transactions/MainnetKADTransactions.hs @@ -0,0 +1,19 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.MainnetKADTransactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoieThkd0Rkc0RZdmM5alg2WHZxVG1CSndEX2xlRlJUTWlJTXJMcjhKODlVOCIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIlxcbihpZlxcbiAgKDw9IDEwMC4wIChjb2luLmdldC1iYWxhbmNlIFxcXCJlN2Y3NjM0ZTkyNTU0MWYzNjhiODI3YWQ1YzcyNDIxOTA1MTAwZjYyMDUyODVhNzhjMTlkN2I0YTM4NzExODA1XFxcIikpXFxuXFxuICAoY29pbi5yZW1lZGlhdGUgXFxcImU3Zjc2MzRlOTI1NTQxZjM2OGI4MjdhZDVjNzI0MjE5MDUxMDBmNjIwNTI4NWE3OGMxOWQ3YjRhMzg3MTE4MDVcXFwiIDEwMC4wKVxcbiAgXFxcIldhcm5pbmc6IGluc3VmZmljaWVudCBmdW5kcyBmb3IgcmVtZWRpYXRpb24sIHNvbGRpZXJpbmcgb25cXFwiKVwifX0sXCJzaWduZXJzXCI6W10sXCJtZXRhXCI6e1wiY3JlYXRpb25UaW1lXCI6MCxcInR0bFwiOjE3MjgwMCxcImdhc0xpbWl0XCI6MCxcImNoYWluSWRcIjpcIlwiLFwiZ2FzUHJpY2VcIjowLFwic2VuZGVyXCI6XCJcIn0sXCJub25jZVwiOlwibWFpbm5ldC1yZW1lZGlhdGlvbnMta2FkLW9wc1wifSJ9" + ] diff --git a/src/Chainweb/Pact/Transactions/OtherTransactions.hs b/src/Chainweb/Pact/Transactions/OtherTransactions.hs new file mode 100644 index 0000000000..02c9ecfec0 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/OtherTransactions.hs @@ -0,0 +1,21 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.OtherTransactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + ] diff --git a/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs b/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs new file mode 100644 index 0000000000..c87145ed38 --- /dev/null +++ b/src/Chainweb/Pact/Transactions/RecapDevelopmentTransactions.hs @@ -0,0 +1,23 @@ +{-# LANGUAGE OverloadedStrings #-} + +-- This module is auto-generated. DO NOT EDIT IT MANUALLY. + +module Chainweb.Pact.Transactions.RecapDevelopmentTransactions ( transactions ) where + +import Data.Bifunctor (first) +import System.IO.Unsafe + +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils + +transactions :: [Pact4.Transaction] +transactions = + let decodeTx t = + fromEitherM . (first (userError . show)) . codecDecode (Pact4.payloadCodec maxBound) =<< decodeB64UrlNoPaddingText t + in unsafePerformIO $ mapM decodeTx [ + "eyJoYXNoIjoiMDVCdGo3ZUJaQlc3by1TYUxvVmhBaWNNVVBaVUJiRzZRVDhfTEFrQ3hIcyIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihpbnRlcmZhY2UgZnVuZ2libGUtdjJcXG5cXG4gIFxcXCIgU3RhbmRhcmQgZm9yIGZ1bmdpYmxlIGNvaW5zIGFuZCB0b2tlbnMgYXMgc3BlY2lmaWVkIGluIEtJUC0wMDAyLiBcXFwiXFxuXFxuICAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICAgOyBTY2hlbWFcXG5cXG4gICAoZGVmc2NoZW1hIGFjY291bnQtZGV0YWlsc1xcbiAgICBAZG9jIFxcXCJTY2hlbWEgZm9yIHJlc3VsdHMgb2YgJ2FjY291bnQnIG9wZXJhdGlvbi5cXFwiXFxuICAgIEBtb2RlbCBbIChpbnZhcmlhbnQgKCE9IFxcXCJcXFwiIHNlbmRlcikpIF1cXG5cXG4gICAgYWNjb3VudDpzdHJpbmdcXG4gICAgYmFsYW5jZTpkZWNpbWFsXFxuICAgIGd1YXJkOmd1YXJkKVxcblxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgQ2Fwc1xcblxcbiAgIChkZWZjYXAgVFJBTlNGRVI6Ym9vbFxcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZWQgY2FwYWJpbGl0eSBzZWFsaW5nIEFNT1VOVCBmb3IgdHJhbnNmZXIgZnJvbSBTRU5ERVIgdG8gXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLiBQZXJtaXRzIGFueSBudW1iZXIgb2YgdHJhbnNmZXJzIHVwIHRvIEFNT1VOVC5cXFwiXFxuICAgICBAbWFuYWdlZCBhbW91bnQgVFJBTlNGRVItbWdyXFxuICAgICApXFxuXFxuICAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICAoIG1hbmFnZWQ6ZGVjaW1hbFxcbiAgICAgICByZXF1ZXN0ZWQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIE1hbmFnZXMgVFJBTlNGRVIgQU1PVU5UIGxpbmVhcmx5LCBcXFxcXFxuICAgICAgICAgIFxcXFwgc3VjaCB0aGF0IGEgcmVxdWVzdCBmb3IgMS4wIGFtb3VudCBvbiBhIDMuMCBcXFxcXFxuICAgICAgICAgIFxcXFwgbWFuYWdlZCBxdWFudGl0eSBlbWl0cyB1cGRhdGVkIGFtb3VudCAyLjAuXFxcIlxcbiAgICAgKVxcblxcbiAgIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgIDsgRnVuY3Rpb25hbGl0eVxcblxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuICAgIEBkb2MgXFxcIiBUcmFuc2ZlciBBTU9VTlQgYmV0d2VlbiBhY2NvdW50cyBTRU5ERVIgYW5kIFJFQ0VJVkVSLiBcXFxcXFxuICAgICAgICAgXFxcXCBGYWlscyBpZiBlaXRoZXIgU0VOREVSIG9yIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LlxcXCJcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoIT0gcmVjZWl2ZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICBdXFxuICAgIClcXG5cXG4gICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAgKCBzZW5kZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICAgKVxcbiAgICAgQGRvYyBcXFwiIFRyYW5zZmVyIEFNT1VOVCBiZXR3ZWVuIGFjY291bnRzIFNFTkRFUiBhbmQgUkVDRUlWRVIuIFxcXFxcXG4gICAgICAgICAgXFxcXCBGYWlscyBpZiBTRU5ERVIgZG9lcyBub3QgZXhpc3QuIElmIFJFQ0VJVkVSIGV4aXN0cywgZ3VhcmQgXFxcXFxcbiAgICAgICAgICBcXFxcIG11c3QgbWF0Y2ggZXhpc3RpbmcgdmFsdWUuIElmIFJFQ0VJVkVSIGRvZXMgbm90IGV4aXN0LCBcXFxcXFxuICAgICAgICAgIFxcXFwgUkVDRUlWRVIgYWNjb3VudCBpcyBjcmVhdGVkIHVzaW5nIFJFQ0VJVkVSLUdVQVJELiBcXFxcXFxuICAgICAgICAgIFxcXFwgU3ViamVjdCB0byBtYW5hZ2VtZW50IGJ5IFRSQU5TRkVSIGNhcGFiaWxpdHkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICAgcmVjZWl2ZXI6c3RyaW5nXFxuICAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgIHRhcmdldC1jaGFpbjpzdHJpbmdcXG4gICAgICAgYW1vdW50OmRlY2ltYWxcXG4gICAgIClcXG4gICAgIEBkb2MgXFxcIiAyLXN0ZXAgcGFjdCB0byB0cmFuc2ZlciBBTU9VTlQgZnJvbSBTRU5ERVIgb24gY3VycmVudCBjaGFpbiBcXFxcXFxuICAgICAgICAgIFxcXFwgdG8gUkVDRUlWRVIgb24gVEFSR0VULUNIQUlOIHZpYSBTUFYgcHJvb2YuIFxcXFxcXG4gICAgICAgICAgXFxcXCBUQVJHRVQtQ0hBSU4gbXVzdCBiZSBkaWZmZXJlbnQgdGhhbiBjdXJyZW50IGNoYWluIGlkLiBcXFxcXFxuICAgICAgICAgIFxcXFwgRmlyc3Qgc3RlcCBkZWJpdHMgQU1PVU5UIGNvaW5zIGluIFNFTkRFUiBhY2NvdW50IGFuZCB5aWVsZHMgXFxcXFxcbiAgICAgICAgICBcXFxcIFJFQ0VJVkVSLCBSRUNFSVZFUl9HVUFSRCBhbmQgQU1PVU5UIHRvIFRBUkdFVC1DSEFJTi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFNlY29uZCBzdGVwIGNvbnRpbnVhdGlvbiBpcyBzZW50IGludG8gVEFSR0VULUNIQUlOIHdpdGggcHJvb2YgXFxcXFxcbiAgICAgICAgICBcXFxcIG9idGFpbmVkIGZyb20gdGhlIHNwdiAnb3V0cHV0JyBlbmRwb2ludCBvZiBDaGFpbndlYi4gXFxcXFxcbiAgICAgICAgICBcXFxcIFByb29mIGlzIHZhbGlkYXRlZCBhbmQgUkVDRUlWRVIgaXMgY3JlZGl0ZWQgd2l0aCBBTU9VTlQgXFxcXFxcbiAgICAgICAgICBcXFxcIGNyZWF0aW5nIGFjY291bnQgd2l0aCBSRUNFSVZFUl9HVUFSRCBhcyBuZWNlc3NhcnkuXFxcIlxcbiAgICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgXFxcIlxcXCIpKVxcbiAgICAgICAgICAgICAgKHByb3BlcnR5ICghPSByZWNlaXZlciBcXFwiXFxcIikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHNlbmRlciByZWNlaXZlcikpXFxuICAgICAgICAgICAgICAocHJvcGVydHkgKCE9IHRhcmdldC1jaGFpbiBcXFwiXFxcIikpXFxuICAgICAgICAgICAgXVxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBnZXQtYmFsYW5jZTpkZWNpbWFsXFxuICAgICAoIGFjY291bnQ6c3RyaW5nIClcXG4gICAgIFxcXCIgR2V0IGJhbGFuY2UgZm9yIEFDQ09VTlQuIEZhaWxzIGlmIGFjY291bnQgZG9lcyBub3QgZXhpc3QuXFxcIlxcbiAgICAgKVxcblxcbiAgIChkZWZ1biBkZXRhaWxzOm9iamVjdHthY2NvdW50LWRldGFpbHN9XFxuICAgICAoIGFjY291bnQ6IHN0cmluZyApXFxuICAgICBcXFwiIEdldCBhbiBvYmplY3Qgd2l0aCBkZXRhaWxzIG9mIEFDQ09VTlQuIFxcXFxcXG4gICAgIFxcXFwgRmFpbHMgaWYgYWNjb3VudCBkb2VzIG5vdCBleGlzdC5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICAoKVxcbiAgICAgXFxcIlJldHVybiB0aGUgbWF4aW11bSBhbGxvd2VkIGRlY2ltYWwgcHJlY2lzaW9uLlxcXCJcXG4gICAgIClcXG5cXG4gICAoZGVmdW4gZW5mb3JjZS11bml0OmJvb2xcXG4gICAgICggYW1vdW50OmRlY2ltYWwgKVxcbiAgICAgXFxcIiBFbmZvcmNlIG1pbmltdW0gcHJlY2lzaW9uIGFsbG93ZWQgZm9yIHRyYW5zYWN0aW9ucy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIGNyZWF0ZS1hY2NvdW50OnN0cmluZ1xcbiAgICAgKCBhY2NvdW50OnN0cmluZ1xcbiAgICAgICBndWFyZDpndWFyZFxcbiAgICAgKVxcbiAgICAgXFxcIiBDcmVhdGUgQUNDT1VOVCB3aXRoIDAuMCBiYWxhbmNlLCB3aXRoIEdVQVJEIGNvbnRyb2xsaW5nIGFjY2Vzcy5cXFwiXFxuICAgICApXFxuXFxuICAgKGRlZnVuIHJvdGF0ZTpzdHJpbmdcXG4gICAgICggYWNjb3VudDpzdHJpbmdcXG4gICAgICAgbmV3LWd1YXJkOmd1YXJkXFxuICAgICApXFxuICAgICBcXFwiIFJvdGF0ZSBndWFyZCBmb3IgQUNDT1VOVC4gVHJhbnNhY3Rpb24gaXMgdmFsaWRhdGVkIGFnYWluc3QgXFxcXFxcbiAgICAgXFxcXCBleGlzdGluZyBndWFyZCBiZWZvcmUgaW5zdGFsbGluZyBuZXcgZ3VhcmQuIFxcXCJcXG4gICAgIClcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJmdW5naWJsZS1hc3NldC12MlwifSJ9" + , + "eyJoYXNoIjoibVZzMjNxNnJyUjZrWDFGX0ItamNCX05hLXdZdmR3dnRwa1cwQVNaZExjRSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihtb2R1bGUgY29pbiBHT1ZFUk5BTkNFXFxuXFxuICBAZG9jIFxcXCInY29pbicgcmVwcmVzZW50cyB0aGUgS2FkZW5hIENvaW4gQ29udHJhY3QuIFRoaXMgY29udHJhY3QgcHJvdmlkZXMgYm90aCB0aGUgXFxcXFxcbiAgXFxcXGJ1eS9yZWRlZW0gZ2FzIHN1cHBvcnQgaW4gdGhlIGZvcm0gb2YgJ2Z1bmQtdHgnLCBhcyB3ZWxsIGFzIHRyYW5zZmVyLCAgICAgICBcXFxcXFxuICBcXFxcY3JlZGl0LCBkZWJpdCwgY29pbmJhc2UsIGFjY291bnQgY3JlYXRpb24gYW5kIHF1ZXJ5LCBhcyB3ZWxsIGFzIFNQViBidXJuICAgIFxcXFxcXG4gIFxcXFxjcmVhdGUuIFRvIGFjY2VzcyB0aGUgY29pbiBjb250cmFjdCwgeW91IG1heSB1c2UgaXRzIGZ1bGx5LXF1YWxpZmllZCBuYW1lLCAgXFxcXFxcbiAgXFxcXG9yIGlzc3VlIHRoZSAnKHVzZSBjb2luKScgY29tbWFuZCBpbiB0aGUgYm9keSBvZiBhIG1vZHVsZSBkZWNsYXJhdGlvbi5cXFwiXFxuXFxuICBAbW9kZWxcXG4gICAgWyAoZGVmcHJvcGVydHkgY29uc2VydmVzLW1hc3NcXG4gICAgICAgICg9IChjb2x1bW4tZGVsdGEgY29pbi10YWJsZSAnYmFsYW5jZSkgMC4wKSlcXG5cXG4gICAgICAoZGVmcHJvcGVydHkgdmFsaWQtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgICAgICAoYW5kXFxuICAgICAgICAgICg-PSAobGVuZ3RoIGFjY291bnQpIDMpXFxuICAgICAgICAgICg8PSAobGVuZ3RoIGFjY291bnQpIDI1NikpKVxcbiAgICBdXFxuXFxuICAoaW1wbGVtZW50cyBmdW5naWJsZS12MilcXG5cXG4gIDsgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cXG4gIDsgU2NoZW1hcyBhbmQgVGFibGVzXFxuXFxuICAoZGVmc2NoZW1hIGNvaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlRoZSBjb2luIGNvbnRyYWN0IHRva2VuIHNjaGVtYVxcXCJcXG4gICAgQG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBndWFyZDpndWFyZClcXG5cXG4gIChkZWZ0YWJsZSBjb2luLXRhYmxlOntjb2luLXNjaGVtYX0pXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENhcGFiaWxpdGllc1xcblxcbiAgKGRlZmNhcCBHT1ZFUk5BTkNFICgpXFxuICAgIChlbmZvcmNlIGZhbHNlIFxcXCJFbmZvcmNlIG5vbi11cGdyYWRlYWJpbGl0eVxcXCIpKVxcblxcbiAgKGRlZmNhcCBHQVMgKClcXG4gICAgXFxcIk1hZ2ljIGNhcGFiaWxpdHkgdG8gcHJvdGVjdCBnYXMgYnV5IGFuZCByZWRlZW1cXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIENPSU5CQVNFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IHRvIHByb3RlY3QgbWluZXIgcmV3YXJkXFxcIlxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBHRU5FU0lTICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGNvbnN0cmFpbmluZyBnZW5lc2lzIHRyYW5zYWN0aW9uc1xcXCJcXG4gICAgdHJ1ZSlcXG5cXG4gIChkZWZjYXAgUkVNRURJQVRFICgpXFxuICAgIFxcXCJNYWdpYyBjYXBhYmlsaXR5IGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnNcXFwiXFxuICAgIHRydWUpXFxuXFxuICAoZGVmY2FwIERFQklUIChzZW5kZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgZGViaXRpbmcgb3BlcmF0aW9uc1xcXCJcXG4gICAgKGVuZm9yY2UtZ3VhcmQgKGF0ICdndWFyZCAocmVhZCBjb2luLXRhYmxlIHNlbmRlcikpKVxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIFxcXCJcXFwiKSBcXFwidmFsaWQgc2VuZGVyXFxcIikpXFxuXFxuICAoZGVmY2FwIENSRURJVCAocmVjZWl2ZXI6c3RyaW5nKVxcbiAgICBcXFwiQ2FwYWJpbGl0eSBmb3IgbWFuYWdpbmcgY3JlZGl0aW5nIG9wZXJhdGlvbnNcXFwiXFxuICAgIChlbmZvcmNlICghPSByZWNlaXZlciBcXFwiXFxcIikgXFxcInZhbGlkIHJlY2VpdmVyXFxcIikpXFxuXFxuICAoZGVmY2FwIFJPVEFURSAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkF1dG9ub21vdXNseSBtYW5hZ2VkIGNhcGFiaWxpdHkgZm9yIGd1YXJkIHJvdGF0aW9uXFxcIlxcbiAgICBAbWFuYWdlZFxcbiAgICB0cnVlKVxcblxcbiAgKGRlZmNhcCBUUkFOU0ZFUjpib29sXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsXFxuICAgIClcXG4gICAgQG1hbmFnZWQgYW1vdW50IFRSQU5TRkVSLW1nclxcbiAgICAoZW5mb3JjZSAoIT0gc2VuZGVyIHJlY2VpdmVyKSBcXFwic2FtZSBzZW5kZXIgYW5kIHJlY2VpdmVyXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApIFxcXCJQb3NpdGl2ZSBhbW91bnRcXFwiKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpKVxcbiAgICAoY29tcG9zZS1jYXBhYmlsaXR5IChDUkVESVQgcmVjZWl2ZXIpKVxcbiAgKVxcblxcbiAgKGRlZnVuIFRSQU5TRkVSLW1ncjpkZWNpbWFsXFxuICAgICggbWFuYWdlZDpkZWNpbWFsXFxuICAgICAgcmVxdWVzdGVkOmRlY2ltYWxcXG4gICAgKVxcblxcbiAgICAobGV0ICgobmV3YmFsICgtIG1hbmFnZWQgcmVxdWVzdGVkKSkpXFxuICAgICAgKGVuZm9yY2UgKD49IG5ld2JhbCAwLjApXFxuICAgICAgICAoZm9ybWF0IFxcXCJUUkFOU0ZFUiBleGNlZWRlZCBmb3IgYmFsYW5jZSB7fVxcXCIgW21hbmFnZWRdKSlcXG4gICAgICBuZXdiYWwpXFxuICApXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvbnN0YW50c1xcblxcbiAgKGRlZmNvbnN0IENPSU5fQ0hBUlNFVCBDSEFSU0VUX0xBVElOMVxcbiAgICBcXFwiVGhlIGRlZmF1bHQgY29pbiBjb250cmFjdCBjaGFyYWN0ZXIgc2V0XFxcIilcXG5cXG4gIChkZWZjb25zdCBNSU5JTVVNX1BSRUNJU0lPTiAxMlxcbiAgICBcXFwiTWluaW11bSBhbGxvd2VkIHByZWNpc2lvbiBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiKVxcblxcbiAgKGRlZmNvbnN0IE1JTklNVU1fQUNDT1VOVF9MRU5HVEggM1xcbiAgICBcXFwiTWluaW11bSBhY2NvdW50IGxlbmd0aCBhZG1pc3NpYmxlIGZvciBjb2luIGFjY291bnRzXFxcIilcXG5cXG4gIChkZWZjb25zdCBNQVhJTVVNX0FDQ09VTlRfTEVOR1RIIDI1NlxcbiAgICBcXFwiTWF4aW11bSBhY2NvdW50IG5hbWUgbGVuZ3RoIGFkbWlzc2libGUgZm9yIGNvaW4gYWNjb3VudHNcXFwiKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBVdGlsaXRpZXNcXG5cXG4gIChkZWZ1biBlbmZvcmNlLXVuaXQ6Ym9vbCAoYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgbWluaW11bSBwcmVjaXNpb24gYWxsb3dlZCBmb3IgY29pbiB0cmFuc2FjdGlvbnNcXFwiXFxuXFxuICAgIChlbmZvcmNlXFxuICAgICAgKD0gKGZsb29yIGFtb3VudCBNSU5JTVVNX1BSRUNJU0lPTilcXG4gICAgICAgICBhbW91bnQpXFxuICAgICAgKGZvcm1hdCBcXFwiQW1vdW50IHZpb2xhdGVzIG1pbmltdW0gcHJlY2lzaW9uOiB7fVxcXCIgW2Ftb3VudF0pKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdmFsaWRhdGUtYWNjb3VudCAoYWNjb3VudDpzdHJpbmcpXFxuICAgIEBkb2MgXFxcIkVuZm9yY2UgdGhhdCBhbiBhY2NvdW50IG5hbWUgY29uZm9ybXMgdG8gdGhlIGNvaW4gY29udHJhY3QgXFxcXFxcbiAgICAgICAgIFxcXFxtaW5pbXVtIGFuZCBtYXhpbXVtIGxlbmd0aCByZXF1aXJlbWVudHMsIGFzIHdlbGwgYXMgdGhlICAgIFxcXFxcXG4gICAgICAgICBcXFxcbGF0aW4tMSBjaGFyYWN0ZXIgc2V0LlxcXCJcXG5cXG4gICAgKGVuZm9yY2VcXG4gICAgICAoaXMtY2hhcnNldCBDT0lOX0NIQVJTRVQgYWNjb3VudClcXG4gICAgICAoZm9ybWF0XFxuICAgICAgICBcXFwiQWNjb3VudCBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBjb2luIGNvbnRyYWN0IGNoYXJzZXQ6IHt9XFxcIlxcbiAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgKGxldCAoKGFjY291bnQtbGVuZ3RoIChsZW5ndGggYWNjb3VudCkpKVxcblxcbiAgICAgIChlbmZvcmNlXFxuICAgICAgICAoPj0gYWNjb3VudC1sZW5ndGggTUlOSU1VTV9BQ0NPVU5UX0xFTkdUSClcXG4gICAgICAgIChmb3JtYXRcXG4gICAgICAgICAgXFxcIkFjY291bnQgbmFtZSBkb2VzIG5vdCBjb25mb3JtIHRvIHRoZSBtaW4gbGVuZ3RoIHJlcXVpcmVtZW50OiB7fVxcXCJcXG4gICAgICAgICAgW2FjY291bnRdKSlcXG5cXG4gICAgICAoZW5mb3JjZVxcbiAgICAgICAgKDw9IGFjY291bnQtbGVuZ3RoIE1BWElNVU1fQUNDT1VOVF9MRU5HVEgpXFxuICAgICAgICAoZm9ybWF0XFxuICAgICAgICAgIFxcXCJBY2NvdW50IG5hbWUgZG9lcyBub3QgY29uZm9ybSB0byB0aGUgbWF4IGxlbmd0aCByZXF1aXJlbWVudDoge31cXFwiXFxuICAgICAgICAgIFthY2NvdW50XSkpXFxuICAgICAgKVxcbiAgKVxcblxcbiAgOyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxcbiAgOyBDb2luIENvbnRyYWN0XFxuXFxuICAoZGVmdW4gZ2FzLW9ubHkgKClcXG4gICAgXFxcIlByZWRpY2F0ZSBmb3IgZ2FzLW9ubHkgdXNlciBndWFyZHMuXFxcIlxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKSlcXG5cXG4gIChkZWZ1biBnYXMtZ3VhcmQgKGd1YXJkOmd1YXJkKVxcbiAgICBcXFwiUHJlZGljYXRlIGZvciBnYXMgKyBzaW5nbGUga2V5IHVzZXIgZ3VhcmRzXFxcIlxcbiAgICAoZW5mb3JjZS1vbmVcXG4gICAgICBcXFwiRW5mb3JjZSBlaXRoZXIgdGhlIHByZXNlbmNlIG9mIGEgR0FTIGNhcCBvciBrZXlzZXRcXFwiXFxuICAgICAgWyAoZ2FzLW9ubHkpXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG4gICAgICBdKSlcXG5cXG4gIChkZWZ1biBidXktZ2FzOnN0cmluZyAoc2VuZGVyOnN0cmluZyB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCJUaGlzIGZ1bmN0aW9uIGRlc2NyaWJlcyB0aGUgbWFpbiAnZ2FzIGJ1eScgb3BlcmF0aW9uLiBBdCB0aGlzIHBvaW50IFxcXFxcXG4gICAgXFxcXE1JTkVSIGhhcyBiZWVuIGNob3NlbiBmcm9tIHRoZSBwb29sLCBhbmQgd2lsbCBiZSB2YWxpZGF0ZWQuIFRoZSBTRU5ERVIgICBcXFxcXFxuICAgIFxcXFxvZiB0aGlzIHRyYW5zYWN0aW9uIGhhcyBzcGVjaWZpZWQgYSBnYXMgbGltaXQgTElNSVQgKG1heGltdW0gZ2FzKSBmb3IgICAgXFxcXFxcbiAgICBcXFxcdGhlIHRyYW5zYWN0aW9uLCBhbmQgdGhlIHByaWNlIGlzIHRoZSBzcG90IHByaWNlIG9mIGdhcyBhdCB0aGF0IHRpbWUuICAgIFxcXFxcXG4gICAgXFxcXFRoZSBnYXMgYnV5IHdpbGwgYmUgZXhlY3V0ZWQgcHJpb3IgdG8gZXhlY3V0aW5nIFNFTkRFUidzIGNvZGUuXFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCB0b3RhbClcXG4gICAgKGVuZm9yY2UgKD4gdG90YWwgMC4wKSBcXFwiZ2FzIHN1cHBseSBtdXN0IGJlIGEgcG9zaXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHQVMpKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuICAgICAgKGRlYml0IHNlbmRlciB0b3RhbCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZWRlZW0tZ2FzOnN0cmluZyAobWluZXI6c3RyaW5nIG1pbmVyLWd1YXJkOmd1YXJkIHNlbmRlcjpzdHJpbmcgdG90YWw6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiVGhpcyBmdW5jdGlvbiBkZXNjcmliZXMgdGhlIG1haW4gJ3JlZGVlbSBnYXMnIG9wZXJhdGlvbi4gQXQgdGhpcyAgICBcXFxcXFxuICAgIFxcXFxwb2ludCwgdGhlIFNFTkRFUidzIHRyYW5zYWN0aW9uIGhhcyBiZWVuIGV4ZWN1dGVkLCBhbmQgdGhlIGdhcyB0aGF0ICAgICAgXFxcXFxcbiAgICBcXFxcd2FzIGNoYXJnZWQgaGFzIGJlZW4gY2FsY3VsYXRlZC4gTUlORVIgd2lsbCBiZSBjcmVkaXRlZCB0aGUgZ2FzIGNvc3QsICAgIFxcXFxcXG4gICAgXFxcXGFuZCBTRU5ERVIgd2lsbCByZWNlaXZlIHRoZSByZW1haW5kZXIgdXAgdG8gdGhlIGxpbWl0XFxcIlxcblxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKD4gdG90YWwgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHNlbmRlcikpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBtaW5lcikpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHNlbmRlcilcXG4gICAgKHZhbGlkYXRlLWFjY291bnQgbWluZXIpXFxuICAgIChlbmZvcmNlLXVuaXQgdG90YWwpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKEdBUykpXFxuICAgIChsZXQqXFxuICAgICAgKChmZWUgKHJlYWQtZGVjaW1hbCBcXFwiZmVlXFxcIikpXFxuICAgICAgIChyZWZ1bmQgKC0gdG90YWwgZmVlKSkpXFxuXFxuICAgICAgKGVuZm9yY2UtdW5pdCBmZWUpXFxuICAgICAgKGVuZm9yY2UgKD49IGZlZSAwLjApXFxuICAgICAgICBcXFwiZmVlIG11c3QgYmUgYSBub24tbmVnYXRpdmUgcXVhbnRpdHlcXFwiKVxcblxcbiAgICAgIChlbmZvcmNlICg-PSByZWZ1bmQgMC4wKVxcbiAgICAgICAgXFxcInJlZnVuZCBtdXN0IGJlIGEgbm9uLW5lZ2F0aXZlIHF1YW50aXR5XFxcIilcXG5cXG4gICAgICAgIDsgZGlyZWN0bHkgdXBkYXRlIGluc3RlYWQgb2YgY3JlZGl0XFxuICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHNlbmRlcilcXG4gICAgICAgIChpZiAoPiByZWZ1bmQgMC4wKVxcbiAgICAgICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgc2VuZGVyXFxuICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOj0gYmFsYW5jZSB9XFxuICAgICAgICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIHNlbmRlclxcbiAgICAgICAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCI6ICgrIGJhbGFuY2UgcmVmdW5kKSB9KSlcXG5cXG4gICAgICAgICAgXFxcIm5vb3BcXFwiKSlcXG5cXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChDUkVESVQgbWluZXIpXFxuICAgICAgICAoaWYgKD4gZmVlIDAuMClcXG4gICAgICAgICAgKGNyZWRpdCBtaW5lciBtaW5lci1ndWFyZCBmZWUpXFxuICAgICAgICAgIFxcXCJub29wXFxcIikpXFxuICAgICAgKVxcblxcbiAgICApXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFjY291bnQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZClcXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKSBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChpbnNlcnQgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgICA6IGd1YXJkXFxuICAgICAgfSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGdldC1iYWxhbmNlOmRlY2ltYWwgKGFjY291bnQ6c3RyaW5nKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcbiAgICAgIGJhbGFuY2VcXG4gICAgICApXFxuICAgIClcXG5cXG4gIChkZWZ1biBkZXRhaWxzOm9iamVjdHtmdW5naWJsZS12Mi5hY2NvdW50LWRldGFpbHN9XFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIiA6PSBnIH1cXG4gICAgICB7IFxcXCJhY2NvdW50XFxcIiA6IGFjY291bnRcXG4gICAgICAsIFxcXCJiYWxhbmNlXFxcIiA6IGJhbFxcbiAgICAgICwgXFxcImd1YXJkXFxcIjogZyB9KVxcbiAgICApXFxuXFxuICAoZGVmdW4gcm90YXRlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgbmV3LWd1YXJkOmd1YXJkKVxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChST1RBVEUgYWNjb3VudClcXG4gICAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IG9sZC1ndWFyZCB9XFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBvbGQtZ3VhcmQpXFxuXFxuICAgICAgICAodXBkYXRlIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgICAgICB7IFxcXCJndWFyZFxcXCIgOiBuZXctZ3VhcmQgfVxcbiAgICAgICAgICApKSlcXG4gICAgKVxcblxcblxcbiAgKGRlZnVuIHByZWNpc2lvbjppbnRlZ2VyXFxuICAgICgpXFxuICAgIE1JTklNVU1fUFJFQ0lTSU9OKVxcblxcbiAgKGRlZnVuIHRyYW5zZmVyOnN0cmluZyAoc2VuZGVyOnN0cmluZyByZWNlaXZlcjpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcylcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICghPSBzZW5kZXIgcmVjZWl2ZXIpKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgICh3aXRoLXJlYWQgY29pbi10YWJsZSByZWNlaXZlclxcbiAgICAgICAgeyBcXFwiZ3VhcmRcXFwiIDo9IGcgfVxcblxcbiAgICAgICAgKGNyZWRpdCByZWNlaXZlciBnIGFtb3VudCkpXFxuICAgICAgKVxcbiAgICApXFxuXFxuICAoZGVmdW4gdHJhbnNmZXItY3JlYXRlOnN0cmluZ1xcbiAgICAoIHNlbmRlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlcjpzdHJpbmdcXG4gICAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5IGNvbnNlcnZlcy1tYXNzKSBdXFxuXFxuICAgIChlbmZvcmNlICghPSBzZW5kZXIgcmVjZWl2ZXIpXFxuICAgICAgXFxcInNlbmRlciBjYW5ub3QgYmUgdGhlIHJlY2VpdmVyIG9mIGEgdHJhbnNmZXJcXFwiKVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IHJlY2VpdmVyKVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgIFxcXCJ0cmFuc2ZlciBhbW91bnQgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAod2l0aC1jYXBhYmlsaXR5IChUUkFOU0ZFUiBzZW5kZXIgcmVjZWl2ZXIgYW1vdW50KVxcbiAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcbiAgICAgIChjcmVkaXQgcmVjZWl2ZXIgcmVjZWl2ZXItZ3VhcmQgYW1vdW50KSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGNvaW5iYXNlOnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYWNjb3VudC1ndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiSW50ZXJuYWwgZnVuY3Rpb24gZm9yIHRoZSBpbml0aWFsIGNyZWF0aW9uIG9mIGNvaW5zLiAgVGhpcyBmdW5jdGlvbiBcXFxcXFxuICAgIFxcXFxjYW5ub3QgYmUgdXNlZCBvdXRzaWRlIG9mIHRoZSBjb2luIGNvbnRyYWN0LlxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKD4gYW1vdW50IDAuMCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuICAgIChlbmZvcmNlLXVuaXQgYW1vdW50KVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChDT0lOQkFTRSkpXFxuICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgIChjcmVkaXQgYWNjb3VudCBhY2NvdW50LWd1YXJkIGFtb3VudCkpXFxuICAgIClcXG5cXG4gIChkZWZ1biByZW1lZGlhdGU6c3RyaW5nIChhY2NvdW50OnN0cmluZyBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQWxsb3dzIGZvciByZW1lZGlhdGlvbiB0cmFuc2FjdGlvbnMuIFRoaXMgZnVuY3Rpb24gXFxcXFxcbiAgICAgICAgIFxcXFxpcyBwcm90ZWN0ZWQgYnkgdGhlIFJFTUVESUFURSBjYXBhYmlsaXR5XFxcIlxcbiAgICBAbW9kZWwgWyAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICAgIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG5cXG4gICAgKGVuZm9yY2UgKD4gYW1vdW50IDAuMClcXG4gICAgICBcXFwiUmVtZWRpYXRpb24gYW1vdW50IG11c3QgYmUgcG9zaXRpdmVcXFwiKVxcblxcbiAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgKHJlcXVpcmUtY2FwYWJpbGl0eSAoUkVNRURJQVRFKSlcXG4gICAgKHdpdGgtcmVhZCBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6PSBiYWxhbmNlIH1cXG5cXG4gICAgICAoZW5mb3JjZSAoPD0gYW1vdW50IGJhbGFuY2UpIFxcXCJJbnN1ZmZpY2llbnQgZnVuZHNcXFwiKVxcblxcbiAgICAgICh1cGRhdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgtIGJhbGFuY2UgYW1vdW50KSB9XFxuICAgICAgICApKVxcbiAgICApXFxuXFxuICAoZGVmcGFjdCBmdW5kLXR4IChzZW5kZXI6c3RyaW5nIG1pbmVyOnN0cmluZyBtaW5lci1ndWFyZDpndWFyZCB0b3RhbDpkZWNpbWFsKVxcbiAgICBAZG9jIFxcXCInZnVuZC10eCcgaXMgYSBzcGVjaWFsIHBhY3QgdG8gZnVuZCBhIHRyYW5zYWN0aW9uIGluIHR3byBzdGVwcywgICAgIFxcXFxcXG4gICAgXFxcXHdpdGggdGhlIGFjdHVhbCB0cmFuc2FjdGlvbiB0cmFuc3BpcmluZyBpbiB0aGUgbWlkZGxlOiAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXFxcXFxcbiAgICBcXFxcICAxKSBBIGJ1eWluZyBwaGFzZSwgZGViaXRpbmcgdGhlIHNlbmRlciBmb3IgdG90YWwgZ2FzIGFuZCBmZWUsIHlpZWxkaW5nIFxcXFxcXG4gICAgXFxcXCAgICAgVFhfTUFYX0NIQVJHRS4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcXFxcXFxuICAgIFxcXFwgIDIpIEEgc2V0dGxlbWVudCBwaGFzZSwgcmVzdW1pbmcgVFhfTUFYX0NIQVJHRSwgYW5kIGFsbG9jYXRpbmcgdG8gdGhlICAgXFxcXFxcbiAgICBcXFxcICAgICBjb2luYmFzZSBhY2NvdW50IGZvciB1c2VkIGdhcyBhbmQgZmVlLCBhbmQgc2VuZGVyIGFjY291bnQgZm9yIGJhbC0gIFxcXFxcXG4gICAgXFxcXCAgICAgYW5jZSAodW51c2VkIGdhcywgaWYgYW55KS5cXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiB0b3RhbCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IG1pbmVyKSlcXG4gICAgICAgICAgICAgOyhwcm9wZXJ0eSBjb25zZXJ2ZXMtbWFzcykgbm90IHN1cHBvcnRlZCB5ZXRcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXAgKGJ1eS1nYXMgc2VuZGVyIHRvdGFsKSlcXG4gICAgKHN0ZXAgKHJlZGVlbS1nYXMgbWluZXIgbWluZXItZ3VhcmQgc2VuZGVyIHRvdGFsKSlcXG4gICAgKVxcblxcbiAgKGRlZnVuIGRlYml0OnN0cmluZyAoYWNjb3VudDpzdHJpbmcgYW1vdW50OmRlY2ltYWwpXFxuICAgIEBkb2MgXFxcIkRlYml0IEFNT1VOVCBmcm9tIEFDQ09VTlQgYmFsYW5jZVxcXCJcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgYWNjb3VudCkpXFxuICAgICAgICAgICBdXFxuXFxuICAgICh2YWxpZGF0ZS1hY2NvdW50IGFjY291bnQpXFxuXFxuICAgIChlbmZvcmNlICg-IGFtb3VudCAwLjApXFxuICAgICAgXFxcImRlYml0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKERFQklUIGFjY291bnQpKVxcbiAgICAod2l0aC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UgfVxcblxcbiAgICAgIChlbmZvcmNlICg8PSBhbW91bnQgYmFsYW5jZSkgXFxcIkluc3VmZmljaWVudCBmdW5kc1xcXCIpXFxuXFxuICAgICAgKHVwZGF0ZSBjb2luLXRhYmxlIGFjY291bnRcXG4gICAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogKC0gYmFsYW5jZSBhbW91bnQpIH1cXG4gICAgICAgICkpXFxuICAgIClcXG5cXG5cXG4gIChkZWZ1biBjcmVkaXQ6c3RyaW5nIChhY2NvdW50OnN0cmluZyBndWFyZDpndWFyZCBhbW91bnQ6ZGVjaW1hbClcXG4gICAgQGRvYyBcXFwiQ3JlZGl0IEFNT1VOVCB0byBBQ0NPVU5UIGJhbGFuY2VcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAoPiBhbW91bnQgMC4wKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IGFjY291bnQpKVxcbiAgICAgICAgICAgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKSBcXFwiY3JlZGl0IGFtb3VudCBtdXN0IGJlIHBvc2l0aXZlXFxcIilcXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChyZXF1aXJlLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KSlcXG4gICAgKHdpdGgtZGVmYXVsdC1yZWFkIGNvaW4tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDogMC4wLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmQgfVxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2UsIFxcXCJndWFyZFxcXCIgOj0gcmV0ZyB9XFxuICAgICAgOyB3ZSBkb24ndCB3YW50IHRvIG92ZXJ3cml0ZSBhbiBleGlzdGluZyBndWFyZCB3aXRoIHRoZSB1c2VyLXN1cHBsaWVkIG9uZVxcbiAgICAgIChlbmZvcmNlICg9IHJldGcgZ3VhcmQpXFxuICAgICAgICBcXFwiYWNjb3VudCBndWFyZHMgZG8gbm90IG1hdGNoXFxcIilcXG5cXG4gICAgICAod3JpdGUgY29pbi10YWJsZSBhY2NvdW50XFxuICAgICAgICB7IFxcXCJiYWxhbmNlXFxcIiA6ICgrIGJhbGFuY2UgYW1vdW50KVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiICAgOiByZXRnXFxuICAgICAgICB9KVxcbiAgICAgICkpXFxuXFxuXFxuICAoZGVmc2NoZW1hIGNyb3NzY2hhaW4tc2NoZW1hXFxuICAgIEBkb2MgXFxcIlNjaGVtYSBmb3IgeWllbGRlZCB2YWx1ZSBpbiBjcm9zcy1jaGFpbiB0cmFuc2ZlcnNcXFwiXFxuICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICByZWNlaXZlci1ndWFyZDpndWFyZFxcbiAgICBhbW91bnQ6ZGVjaW1hbClcXG5cXG4gIChkZWZwYWN0IHRyYW5zZmVyLWNyb3NzY2hhaW46c3RyaW5nXFxuICAgICggc2VuZGVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyOnN0cmluZ1xcbiAgICAgIHJlY2VpdmVyLWd1YXJkOmd1YXJkXFxuICAgICAgdGFyZ2V0LWNoYWluOnN0cmluZ1xcbiAgICAgIGFtb3VudDpkZWNpbWFsIClcXG5cXG4gICAgQG1vZGVsIFsgKHByb3BlcnR5ICg-IGFtb3VudCAwLjApKVxcbiAgICAgICAgICAgICAocHJvcGVydHkgKHZhbGlkLWFjY291bnQgc2VuZGVyKSlcXG4gICAgICAgICAgICAgKHByb3BlcnR5ICh2YWxpZC1hY2NvdW50IHJlY2VpdmVyKSlcXG4gICAgICAgICAgIF1cXG5cXG4gICAgKHN0ZXBcXG4gICAgICAod2l0aC1jYXBhYmlsaXR5IChERUJJVCBzZW5kZXIpXFxuXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCBzZW5kZXIpXFxuICAgICAgICAodmFsaWRhdGUtYWNjb3VudCByZWNlaXZlcilcXG5cXG4gICAgICAgIChlbmZvcmNlICghPSBcXFwiXFxcIiB0YXJnZXQtY2hhaW4pIFxcXCJlbXB0eSB0YXJnZXQtY2hhaW5cXFwiKVxcbiAgICAgICAgKGVuZm9yY2UgKCE9IChhdCAnY2hhaW4taWQgKGNoYWluLWRhdGEpKSB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgIFxcXCJjYW5ub3QgcnVuIGNyb3NzLWNoYWluIHRyYW5zZmVycyB0byB0aGUgc2FtZSBjaGFpblxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZSAoPiBhbW91bnQgMC4wKVxcbiAgICAgICAgICBcXFwidHJhbnNmZXIgcXVhbnRpdHkgbXVzdCBiZSBwb3NpdGl2ZVxcXCIpXFxuXFxuICAgICAgICAoZW5mb3JjZS11bml0IGFtb3VudClcXG5cXG4gICAgICAgIDs7IHN0ZXAgMSAtIGRlYml0IGRlbGV0ZS1hY2NvdW50IG9uIGN1cnJlbnQgY2hhaW5cXG4gICAgICAgIChkZWJpdCBzZW5kZXIgYW1vdW50KVxcblxcbiAgICAgICAgKGxldFxcbiAgICAgICAgICAoKGNyb3NzY2hhaW4tZGV0YWlsczpvYmplY3R7Y3Jvc3NjaGFpbi1zY2hlbWF9XFxuICAgICAgICAgICAgeyBcXFwicmVjZWl2ZXJcXFwiIDogcmVjZWl2ZXJcXG4gICAgICAgICAgICAsIFxcXCJyZWNlaXZlci1ndWFyZFxcXCIgOiByZWNlaXZlci1ndWFyZFxcbiAgICAgICAgICAgICwgXFxcImFtb3VudFxcXCIgOiBhbW91bnRcXG4gICAgICAgICAgICB9KSlcXG4gICAgICAgICAgKHlpZWxkIGNyb3NzY2hhaW4tZGV0YWlscyB0YXJnZXQtY2hhaW4pXFxuICAgICAgICAgICkpKVxcblxcbiAgICAoc3RlcFxcbiAgICAgIChyZXN1bWVcXG4gICAgICAgIHsgXFxcInJlY2VpdmVyXFxcIiA6PSByZWNlaXZlclxcbiAgICAgICAgLCBcXFwicmVjZWl2ZXItZ3VhcmRcXFwiIDo9IHJlY2VpdmVyLWd1YXJkXFxuICAgICAgICAsIFxcXCJhbW91bnRcXFwiIDo9IGFtb3VudFxcbiAgICAgICAgfVxcblxcbiAgICAgICAgOzsgc3RlcCAyIC0gY3JlZGl0IGNyZWF0ZSBhY2NvdW50IG9uIHRhcmdldCBjaGFpblxcbiAgICAgICAgKHdpdGgtY2FwYWJpbGl0eSAoQ1JFRElUIHJlY2VpdmVyKVxcbiAgICAgICAgICAoY3JlZGl0IHJlY2VpdmVyIHJlY2VpdmVyLWd1YXJkIGFtb3VudCkpXFxuICAgICAgICApKVxcbiAgICApXFxuXFxuXFxuICA7IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXFxuICA7IENvaW4gYWxsb2NhdGlvbnNcXG5cXG4gIChkZWZzY2hlbWEgYWxsb2NhdGlvbi1zY2hlbWFcXG4gICAgQGRvYyBcXFwiR2VuZXNpcyBhbGxvY2F0aW9uIHJlZ2lzdHJ5XFxcIlxcbiAgICA7QG1vZGVsIFsgKGludmFyaWFudCAoPj0gYmFsYW5jZSAwLjApKSBdXFxuXFxuICAgIGJhbGFuY2U6ZGVjaW1hbFxcbiAgICBkYXRlOnRpbWVcXG4gICAgZ3VhcmQ6Z3VhcmRcXG4gICAgcmVkZWVtZWQ6Ym9vbClcXG5cXG4gIChkZWZ0YWJsZSBhbGxvY2F0aW9uLXRhYmxlOnthbGxvY2F0aW9uLXNjaGVtYX0pXFxuXFxuICAoZGVmdW4gY3JlYXRlLWFsbG9jYXRpb24tYWNjb3VudFxcbiAgICAoIGFjY291bnQ6c3RyaW5nXFxuICAgICAgZGF0ZTp0aW1lXFxuICAgICAga2V5c2V0LXJlZjpzdHJpbmdcXG4gICAgICBhbW91bnQ6ZGVjaW1hbFxcbiAgICApXFxuXFxuICAgIEBkb2MgXFxcIkFkZCBhbiBlbnRyeSB0byB0aGUgY29pbiBhbGxvY2F0aW9uIHRhYmxlLiBUaGlzIGZ1bmN0aW9uIFxcXFxcXG4gICAgICAgICBcXFxcYWxzbyBjcmVhdGVzIGEgY29ycmVzcG9uZGluZyBlbXB0eSBjb2luIGNvbnRyYWN0IGFjY291bnQgXFxcXFxcbiAgICAgICAgIFxcXFxvZiB0aGUgc2FtZSBuYW1lIGFuZCBndWFyZC4gUmVxdWlyZXMgR0VORVNJUyBjYXBhYmlsaXR5LiBcXFwiXFxuXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAocmVxdWlyZS1jYXBhYmlsaXR5IChHRU5FU0lTKSlcXG5cXG4gICAgKHZhbGlkYXRlLWFjY291bnQgYWNjb3VudClcXG4gICAgKGVuZm9yY2UgKD49IGFtb3VudCAwLjApXFxuICAgICAgXFxcImFsbG9jYXRpb24gYW1vdW50IG11c3QgYmUgbm9uLW5lZ2F0aXZlXFxcIilcXG5cXG4gICAgKGVuZm9yY2UtdW5pdCBhbW91bnQpXFxuXFxuICAgIChsZXRcXG4gICAgICAoKGd1YXJkOmd1YXJkIChrZXlzZXQtcmVmLWd1YXJkIGtleXNldC1yZWYpKSlcXG5cXG4gICAgICAoY3JlYXRlLWFjY291bnQgYWNjb3VudCBndWFyZClcXG5cXG4gICAgICAoaW5zZXJ0IGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgICAgeyBcXFwiYmFsYW5jZVxcXCIgOiBhbW91bnRcXG4gICAgICAgICwgXFxcImRhdGVcXFwiIDogZGF0ZVxcbiAgICAgICAgLCBcXFwiZ3VhcmRcXFwiIDogZ3VhcmRcXG4gICAgICAgICwgXFxcInJlZGVlbWVkXFxcIiA6IGZhbHNlXFxuICAgICAgICB9KSkpXFxuXFxuICAoZGVmdW4gcmVsZWFzZS1hbGxvY2F0aW9uXFxuICAgICggYWNjb3VudDpzdHJpbmcgKVxcblxcbiAgICBAZG9jIFxcXCJSZWxlYXNlIGZ1bmRzIGFzc29jaWF0ZWQgd2l0aCBhbGxvY2F0aW9uIEFDQ09VTlQgaW50byBtYWluIGxlZGdlci4gICBcXFxcXFxuICAgICAgICAgXFxcXEFDQ09VTlQgbXVzdCBhbHJlYWR5IGV4aXN0IGluIG1haW4gbGVkZ2VyLiBBbGxvY2F0aW9uIGlzIGRlYWN0aXZhdGVkIFxcXFxcXG4gICAgICAgICBcXFxcYWZ0ZXIgcmVsZWFzZS5cXFwiXFxuICAgIEBtb2RlbCBbIChwcm9wZXJ0eSAodmFsaWQtYWNjb3VudCBhY2NvdW50KSkgXVxcblxcbiAgICAodmFsaWRhdGUtYWNjb3VudCBhY2NvdW50KVxcblxcbiAgICAod2l0aC1yZWFkIGFsbG9jYXRpb24tdGFibGUgYWNjb3VudFxcbiAgICAgIHsgXFxcImJhbGFuY2VcXFwiIDo9IGJhbGFuY2VcXG4gICAgICAsIFxcXCJkYXRlXFxcIiA6PSByZWxlYXNlLXRpbWVcXG4gICAgICAsIFxcXCJyZWRlZW1lZFxcXCIgOj0gcmVkZWVtZWRcXG4gICAgICAsIFxcXCJndWFyZFxcXCIgOj0gZ3VhcmRcXG4gICAgICB9XFxuXFxuICAgICAgKGxldCAoKGN1cnItdGltZTp0aW1lIChhdCAnYmxvY2stdGltZSAoY2hhaW4tZGF0YSkpKSlcXG5cXG4gICAgICAgIChlbmZvcmNlIChub3QgcmVkZWVtZWQpXFxuICAgICAgICAgIFxcXCJhbGxvY2F0aW9uIGZ1bmRzIGhhdmUgYWxyZWFkeSBiZWVuIHJlZGVlbWVkXFxcIilcXG5cXG4gICAgICAgIChlbmZvcmNlXFxuICAgICAgICAgICg-PSBjdXJyLXRpbWUgcmVsZWFzZS10aW1lKVxcbiAgICAgICAgICAoZm9ybWF0IFxcXCJmdW5kcyBsb2NrZWQgdW50aWwge30uIGN1cnJlbnQgdGltZToge31cXFwiIFtyZWxlYXNlLXRpbWUgY3Vyci10aW1lXSkpXFxuXFxuICAgICAgICAoZW5mb3JjZS1ndWFyZCBndWFyZClcXG5cXG4gICAgICAgICh3aXRoLWNhcGFiaWxpdHkgKENSRURJVCBhY2NvdW50KVxcbiAgICAgICAgICAoY3JlZGl0IGFjY291bnQgZ3VhcmQgYmFsYW5jZSlcXG5cXG4gICAgICAgICAgKHVwZGF0ZSBhbGxvY2F0aW9uLXRhYmxlIGFjY291bnRcXG4gICAgICAgICAgICB7IFxcXCJyZWRlZW1lZFxcXCIgOiB0cnVlXFxuICAgICAgICAgICAgLCBcXFwiYmFsYW5jZVxcXCIgOiAwLjBcXG4gICAgICAgICAgICB9KVxcblxcbiAgICAgICAgICBcXFwiQWxsb2NhdGlvbiBzdWNjZXNzZnVsbHkgcmVsZWFzZWQgdG8gbWFpbiBsZWRnZXJcXFwiKVxcbiAgICApKSlcXG5cXG4pXFxuXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJjb2luLWNvbnRyYWN0LXYyXCJ9In0" + , + "eyJoYXNoIjoiS1BleXNfYndLeHpGV1M4cEdrWVg0QmZSWkh6MXhiVm43MkVTSk9PRGs0WSIsInNpZ3MiOltdLCJjbWQiOiJ7XCJuZXR3b3JrSWRcIjpudWxsLFwicGF5bG9hZFwiOntcImV4ZWNcIjp7XCJkYXRhXCI6bnVsbCxcImNvZGVcIjpcIihjb2luLnJlbWVkaWF0ZSBcXFwic2VuZGVyMDdcXFwiIDEzMzcuNylcXG4oY29pbi5yZW1lZGlhdGUgXFxcInNlbmRlcjA5XFxcIiAxMzM3LjkpXCJ9fSxcInNpZ25lcnNcIjpbXSxcIm1ldGFcIjp7XCJjcmVhdGlvblRpbWVcIjowLFwidHRsXCI6MTcyODAwLFwiZ2FzTGltaXRcIjowLFwiY2hhaW5JZFwiOlwiXCIsXCJnYXNQcmljZVwiOjAsXCJzZW5kZXJcIjpcIlwifSxcIm5vbmNlXCI6XCJkZXZuZXQtb3RoZXItcmVtZWRpYXRpb25zXCJ9In0" + ] diff --git a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs new file mode 100644 index 0000000000..80226154d6 --- /dev/null +++ b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs @@ -0,0 +1,891 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ImportQualifiedPost #-} +-- TODO pact5: fix the orphan PactDbFor instance +{-# OPTIONS_GHC -Wno-orphans #-} +{-# LANGUAGE ViewPatterns #-} + +-- | +-- Module: Chainweb.Pact4.Backend.ChainwebPactDb +-- Copyright: Copyright © 2018 - 2020 Kadena LLC. +-- License: MIT +-- Maintainer: Emmanuel Denloye-Ito +-- Stability: experimental +-- + +module Chainweb.Pact4.Backend.ChainwebPactDb +( chainwebPactDb +, rewoundPactDb +, indexPactTransaction +, vacuumDb +, toTxLog +, CurrentBlockDbEnv(..) +, cpPactDbEnv +, cpRegisterProcessedTx +, cpLookupProcessedTx +, callDb +, BlockEnv(..) +, blockHandlerEnv +, benvBlockState +, runBlockEnv +, BlockState(..) +, bsPendingBlock +, bsTxId +, initBlockState +, BlockHandler(..) +, BlockHandlerEnv(..) +, blockHandlerDb +, blockHandlerLogger +, blockHandlerBlockHeight +, blockHandlerModuleNameFix +, blockHandlerSortedKeys +, blockHandlerLowerCaseTables +, blockHandlerPersistIntraBlockWrites +, mkBlockHandlerEnv + +, domainTableName +, convKeySetName +, convModuleName +, convNamespaceName +, convRowKey +, convPactId + +, commitBlockStateToDatabase +) where + +import Control.Applicative +import Control.Lens +import Control.Monad +import Control.Monad.Reader +import Control.Monad.State.Strict +import Control.Monad.Trans.Maybe + +import Data.Aeson hiding ((.=)) +import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as B8 +import qualified Data.DList as DL +import Data.List(sort) +import Data.List.NonEmpty (NonEmpty(..)) +import qualified Data.List.NonEmpty as NE +import qualified Data.HashMap.Strict as HashMap +import qualified Data.HashSet as HashSet +import qualified Data.Map.Strict as M +import Data.Maybe +import qualified Data.Set as Set +import Data.String +import qualified Data.Text as T +import qualified Data.Text.Encoding as T + +import Database.SQLite3.Direct as SQ3 + +import Prelude hiding (concat, log) + +-- pact + +import Pact.Persist +import Pact.PersistPactDb hiding (db) +import Pact.Types.Persistence +import Pact.Types.RowData +import Pact.Types.SQLite +import Pact.Types.Term (ModuleName(..), ObjectMap(..), TableName(..), KeySetName(..), NamespaceName(..), PactId(..)) +import Pact.Types.Util (AsString(..)) + +import qualified Pact.JSON.Encode as J +import qualified Pact.JSON.Legacy.HashMap as LHM + +-- chainweb + +import Chainweb.BlockHash +import Chainweb.BlockHeight +import Chainweb.Logger +import Chainweb.Pact.Backend.DbCache +import Chainweb.Pact.Backend.Utils +import Chainweb.Pact.Types +import Chainweb.Utils +import Chainweb.Version +import Pact.Interpreter (PactDbEnv) +import Data.HashMap.Strict (HashMap) +import Data.Vector (Vector) +import Control.Concurrent +import Chainweb.Version.Guards +import Control.Exception.Safe +import Pact.Types.Command (RequestKey) +import Chainweb.Pact.Backend.Types +import Chainweb.Utils.Serialization (runPutS) +import Data.Foldable + +execMulti :: Traversable t => SQ3.Database -> SQ3.Utf8 -> t [SType] -> IO () +execMulti db q rows = bracket (prepStmt db q) destroy $ \stmt -> do + forM_ rows $ \row -> do + SQ3.reset stmt >>= checkError + SQ3.clearBindings stmt + bindParams stmt row + SQ3.step stmt >>= checkError + where + checkError (Left e) = void $ fail $ "error during batch insert: " ++ show e + checkError (Right _) = return () + + destroy x = void (SQ3.finalize x >>= checkError) + +domainTableName :: Domain k v -> Text +domainTableName = asString + +convKeySetName :: KeySetName -> SQ3.Utf8 +convKeySetName = toUtf8 . asString + +convModuleName + :: Bool + -- ^ whether to apply module name fix + -> ModuleName + -> SQ3.Utf8 +convModuleName False (ModuleName name _) = toUtf8 name +convModuleName True mn = asStringUtf8 mn + +convNamespaceName :: NamespaceName -> SQ3.Utf8 +convNamespaceName (NamespaceName name) = toUtf8 name + +convRowKey :: RowKey -> SQ3.Utf8 +convRowKey (RowKey name) = toUtf8 name + +convPactId :: PactId -> SQ3.Utf8 +convPactId = toUtf8 . sshow + +callDb + :: (MonadCatch m, MonadReader (BlockHandlerEnv logger) m, MonadIO m) + => T.Text + -> (SQ3.Database -> IO b) + -> m b +callDb callerName action = do + c <- asks _blockHandlerDb + res <- tryAny $ liftIO $ action c + case res of + Left err -> internalError $ "callDb (" <> callerName <> "): " <> sshow err + Right r -> return r + +data BlockHandlerEnv logger = BlockHandlerEnv + { _blockHandlerDb :: !SQLiteEnv + , _blockHandlerLogger :: !logger + , _blockHandlerBlockHeight :: !BlockHeight + , _blockHandlerModuleNameFix :: !Bool + , _blockHandlerSortedKeys :: !Bool + , _blockHandlerLowerCaseTables :: !Bool + , _blockHandlerPersistIntraBlockWrites :: !IntraBlockPersistence + } + +mkBlockHandlerEnv + :: ChainwebVersion -> ChainId -> BlockHeight + -> SQLiteEnv -> IntraBlockPersistence -> logger -> BlockHandlerEnv logger +mkBlockHandlerEnv v cid bh sql p logger = BlockHandlerEnv + { _blockHandlerDb = sql + , _blockHandlerLogger = logger + , _blockHandlerBlockHeight = bh + , _blockHandlerModuleNameFix = enableModuleNameFix v cid bh + , _blockHandlerSortedKeys = pact42 v cid bh + , _blockHandlerLowerCaseTables = chainweb217Pact v cid bh + , _blockHandlerPersistIntraBlockWrites = p + } + +makeLenses ''BlockHandlerEnv + +data BlockEnv logger = + BlockEnv + { _blockHandlerEnv :: !(BlockHandlerEnv logger) + , _benvBlockState :: !BlockState -- ^ The current block state. + } + +runBlockEnv :: MVar (BlockEnv logger) -> BlockHandler logger a -> IO a +runBlockEnv e m = modifyMVar e $ + \(BlockEnv dbenv bs) -> do + (!a,!s) <- runStateT (runReaderT (runBlockHandler m) dbenv) bs + return (BlockEnv dbenv s, a) + +-- this monad allows access to the database environment "at" a particular block. +-- unfortunately, this is tied to a useless MVar via runBlockEnv, which will +-- be deleted with pact 5. +newtype BlockHandler logger a = BlockHandler + { runBlockHandler :: ReaderT (BlockHandlerEnv logger) (StateT BlockState IO) a + } deriving newtype + ( Functor + , Applicative + , Monad + , MonadState BlockState + , MonadThrow + , MonadCatch + , MonadMask + , MonadIO + , MonadReader (BlockHandlerEnv logger) + ) + +-- | Monad state for 'BlockHandler. +-- This notably contains all of the information that's being mutated during +-- blocks, notably _bsPendingBlock, the pending writes in the block, and +-- _bsPendingTx, the pending writes in the transaction which will be discarded +-- on tx failure. +data BlockState = BlockState + { _bsTxId :: !TxId + , _bsPendingBlock :: !(SQLitePendingData (PendingWrites Pact4)) + , _bsPendingTx :: !(Maybe (SQLitePendingData (PendingWrites Pact4))) + , _bsMode :: !(Maybe ExecutionMode) + , _bsModuleCache :: !(DbCache PersistModuleData) + } +initBlockState + :: DbCacheLimitBytes + -- ^ Module Cache Limit (in bytes of corresponding rowdata) + -> TxId + -- ^ next tx id (end txid of previous block) + -> BlockState +initBlockState cl txid = BlockState + { _bsTxId = txid + , _bsMode = Nothing + , _bsPendingBlock = emptySQLitePendingData mempty + , _bsPendingTx = Nothing + , _bsModuleCache = emptyDbCache cl + } + +makeLenses ''BlockEnv +makeLenses ''BlockState + + +-- this is effectively a read-write snapshot of the Pact state at a block. +data CurrentBlockDbEnv logger = CurrentBlockDbEnv + { _cpPactDbEnv :: !(PactDbEnv (BlockEnv logger)) + , _cpRegisterProcessedTx :: !(RequestKey -> IO ()) + , _cpLookupProcessedTx :: + !(Vector RequestKey -> IO (HashMap RequestKey (T2 BlockHeight BlockHash))) + } +makeLenses ''CurrentBlockDbEnv + +type instance PactDbFor logger Pact4 = CurrentBlockDbEnv logger + +-- | Pact DB which reads from the tip of the checkpointer +chainwebPactDb :: (Logger logger) => PactDb (BlockEnv logger) +chainwebPactDb = PactDb + { _readRow = \d k e -> runBlockEnv e $ doReadRow Nothing d k + , _writeRow = \wt d k v e -> runBlockEnv e $ doWriteRow Nothing wt d k v + , _keys = \d e -> runBlockEnv e $ doKeys Nothing d + , _txids = \t txid e -> runBlockEnv e $ doTxIds t txid + , _createUserTable = \tn mn e -> runBlockEnv e $ doCreateUserTable Nothing tn mn + , _getUserTableInfo = \_ -> error "WILL BE DEPRECATED!" + , _beginTx = \m e -> runBlockEnv e $ doBegin m + , _commitTx = \e -> runBlockEnv e doCommit + , _rollbackTx = \e -> runBlockEnv e doRollback + , _getTxLog = \d tid e -> runBlockEnv e $ doGetTxLog d tid + } + +-- | Pact DB which reads from some past block height, instead of the tip of the checkpointer +rewoundPactDb :: (Logger logger) => BlockHeight -> TxId -> PactDb (BlockEnv logger) +rewoundPactDb bh endTxId = chainwebPactDb + { _readRow = \d k e -> runBlockEnv e $ doReadRow (Just (bh, endTxId)) d k + , _writeRow = \wt d k v e -> runBlockEnv e $ doWriteRow (Just (bh, endTxId)) wt d k v + , _keys = \d e -> runBlockEnv e $ doKeys (Just (bh, endTxId)) d + , _createUserTable = \tn mn e -> runBlockEnv e $ doCreateUserTable (Just bh) tn mn + } + +-- returns pending writes in the reverse order they were made +getPendingData :: BlockHandler logger [SQLitePendingData (PendingWrites Pact4)] +getPendingData = do + pb <- use bsPendingBlock + ptx <- maybeToList <$> use bsPendingTx + -- lookup in pending transactions first + return $ ptx ++ [pb] + +forModuleNameFix :: (Bool -> BlockHandler logger a) -> BlockHandler logger a +forModuleNameFix f = view blockHandlerModuleNameFix >>= f + +-- TODO: speed this up, cache it? +tableExistsInDbAtHeight :: Text -> BlockHeight -> BlockHandler logger Bool +tableExistsInDbAtHeight tableName bh = do + let knownTbls = + ["SYS:Pacts", "SYS:Modules", "SYS:KeySets", "SYS:Namespaces", "SYS:ModuleSources"] + if tableName `elem` knownTbls + then return True + else callDb "tableExists" $ \db -> do + let tableExistsStmt = + -- table names are case-sensitive + "SELECT tablename FROM VersionedTableCreation WHERE createBlockheight < ? AND lower(tablename) = lower(?)" + qry db tableExistsStmt [SInt $ max 0 (fromIntegral bh), SText (toUtf8 tableName)] [RText] >>= \case + [] -> return False + _ -> return True + +doReadRow + :: (IsString k, FromJSON v) + => Maybe (BlockHeight, TxId) + -- ^ the highest block we should be reading writes from + -> Domain k v + -> k + -> BlockHandler logger (Maybe v) +doReadRow mlim d k = forModuleNameFix $ \mnFix -> + case d of + KeySets -> lookupWithKey (convKeySetName k) noCache + -- TODO: This is incomplete (the modules case), due to namespace + -- resolution concerns + Modules -> lookupWithKey (convModuleName mnFix k) checkModuleCache + Namespaces -> lookupWithKey (convNamespaceName k) noCache + (UserTables _) -> lookupWithKey (convRowKey k) noCache + Pacts -> lookupWithKey (convPactId k) noCache + where + tableName = domainTableName d + + lookupWithKey + :: forall logger v . FromJSON v + => Utf8 + -> (Utf8 -> BS.ByteString -> MaybeT (BlockHandler logger ) v) + -> BlockHandler logger (Maybe v) + lookupWithKey key checkCache = do + pds <- getPendingData + let lookPD = foldr1 (<|>) $ map (lookupInPendingData key) pds + let lookDB = lookupInDb key checkCache + runMaybeT (lookPD <|> lookDB) + + lookupInPendingData + :: forall logger v . FromJSON v + => Utf8 + -> SQLitePendingData (PendingWrites Pact4) + -> MaybeT (BlockHandler logger) v + lookupInPendingData (Utf8 rowkey) p = do + -- we get the latest-written value at this rowkey + allKeys <- hoistMaybe $ HashMap.lookup tableName (_pendingWrites p) + ddata <- _deltaData . NE.head <$> hoistMaybe (HashMap.lookup rowkey allKeys) + MaybeT $ return $! decodeStrict' ddata + + lookupInDb + :: forall logger v . FromJSON v + => Utf8 + -> (Utf8 -> BS.ByteString -> MaybeT (BlockHandler logger) v) + -> MaybeT (BlockHandler logger) v + lookupInDb rowkey checkCache = do + -- First, check: did we create this table during this block? If so, + -- there's no point in looking up the key. + checkDbTablePendingCreation tableName + lift $ forM_ mlim $ \(bh, _) -> + failIfTableDoesNotExistInDbAtHeight "doReadRow" tableName bh + -- we inject the endingtx limitation to reduce the scope up to the provided block height + let blockLimitStmt = maybe "" (const " AND txid < ?") mlim + let blockLimitParam = maybe [] (\(TxId txid) -> [SInt $ fromIntegral txid]) (snd <$> mlim) + let queryStmt = + "SELECT rowdata FROM " <> tbl (toUtf8 tableName) <> " WHERE rowkey = ?" <> blockLimitStmt + <> " ORDER BY txid DESC LIMIT 1;" + result <- lift $ callDb "doReadRow" + $ \db -> qry db queryStmt ([SText rowkey] ++ blockLimitParam) [RBlob] + case result of + [] -> mzero + [[SBlob a]] -> checkCache rowkey a + err -> internalError $ + "doReadRow: Expected (at most) a single result, but got: " <> + T.pack (show err) + + checkModuleCache u b = MaybeT $ do + !txid <- use bsTxId -- cache priority + mc <- use bsModuleCache + (r, mc') <- liftIO $ checkDbCache u decodeStrict b txid mc + modify' (bsModuleCache .~ mc') + return r + + noCache + :: FromJSON v + => Utf8 + -> BS.ByteString + -> MaybeT (BlockHandler logger) v + noCache _key rowdata = MaybeT $ return $! decodeStrict' rowdata + + +checkDbTablePendingCreation :: Text -> MaybeT (BlockHandler logger) () +checkDbTablePendingCreation tableName = do + pds <- lift getPendingData + forM_ pds $ \p -> + when (HashSet.member tableName (_pendingTableCreation p)) mzero + +writeSys + :: (AsString k, J.Encode v) + => Domain k v + -> k + -> v + -> BlockHandler logger () +writeSys d k v = gets _bsTxId >>= go + where + go txid = do + forModuleNameFix $ \mnFix -> + recordPendingUpdate (getKeyString mnFix k) tableName txid v + recordTxLog (TableName tableName) d k v + + tableName = domainTableName d + + getKeyString mnFix = case d of + KeySets -> convKeySetName + Modules -> convModuleName mnFix + Namespaces -> convNamespaceName + Pacts -> convPactId + UserTables _ -> error "impossible" + +recordPendingUpdate + :: J.Encode v + => Utf8 + -> Text + -> TxId + -> v + -> BlockHandler logger () +recordPendingUpdate (Utf8 key) tn txid v = modifyPendingData modf + where + !vs = J.encodeStrict v + delta = SQLiteRowDelta tn txid key vs + + modf = over pendingWrites upd + upd = HashMap.unionWith + HashMap.union + (HashMap.singleton tn + (HashMap.singleton key (NE.singleton delta))) + + +checkInsertIsOK + :: Maybe (BlockHeight, TxId) + -- ^ the highest block we should be reading writes from + -> WriteType + -> Domain RowKey RowData + -> RowKey + -> BlockHandler logger (Maybe RowData) +checkInsertIsOK mlim wt d k = do + olds <- doReadRow mlim d k + case (olds, wt) of + (Nothing, Insert) -> return Nothing + (Just _, Insert) -> err "Insert: row found for key " + (Nothing, Write) -> return Nothing + (Just old, Write) -> return $ Just old + (Just old, Update) -> return $ Just old + (Nothing, Update) -> err "Update: no row found for key " + where + err msg = internalError $ "checkInsertIsOK: " <> msg <> asString k + +writeUser + :: Maybe (BlockHeight, TxId) + -- ^ the highest block we should be reading writes from + -> WriteType + -> Domain RowKey RowData + -> RowKey + -> RowData + -> BlockHandler logger () +writeUser mlim wt d k rowdata@(RowData _ row) = gets _bsTxId >>= go + where + tn = domainTableName d + ttn = TableName tn + + go txid = do + m <- checkInsertIsOK mlim wt d k + row' <- case m of + Nothing -> ins + (Just old) -> upd old + recordTxLog ttn d k row' + + where + upd (RowData oldV oldrow) = do + let row' = RowData oldV $ ObjectMap (M.union (_objectMap row) (_objectMap oldrow)) + recordPendingUpdate (convRowKey k) tn txid row' + return row' + + ins = do + recordPendingUpdate (convRowKey k) tn txid rowdata + return rowdata + +doWriteRow + :: (AsString k, J.Encode v) + => Maybe (BlockHeight, TxId) + -- ^ the highest block we should be reading writes from + -> WriteType + -> Domain k v + -> k + -> v + -> BlockHandler logger () +doWriteRow mlim wt d k v = case d of + (UserTables _) -> writeUser mlim wt d k v + _ -> writeSys d k v + +doKeys + :: (IsString k) + => Maybe (BlockHeight, TxId) + -- ^ the highest block we should be reading writes from + -> Domain k v + -> BlockHandler logger [k] +doKeys mlim d = do + msort <- views blockHandlerSortedKeys (\c -> if c then sort else id) + dbKeys <- getDbKeys + pb <- use bsPendingBlock + mptx <- use bsPendingTx + + let memKeys = fmap (B8.unpack . _deltaRowKey) + $ collect pb ++ maybe [] collect mptx + + let !allKeys = fmap fromString + $ msort -- becomes available with Pact42Upgrade + $ LHM.sort + $ dbKeys ++ memKeys + return allKeys + + where + blockLimitStmt = maybe "" (const " WHERE txid < ?;") mlim + blockLimitParam = maybe [] (\(TxId txid) -> [SInt (fromIntegral txid)]) (snd <$> mlim) + getDbKeys = do + m <- runMaybeT $ checkDbTablePendingCreation $ tn + case m of + Nothing -> return mempty + Just () -> do + forM_ mlim (failIfTableDoesNotExistInDbAtHeight "doKeys" tn . fst) + ks <- callDb "doKeys" $ \db -> + qry db ("SELECT DISTINCT rowkey FROM " <> tbl (toUtf8 tn) <> blockLimitStmt) blockLimitParam [RText] + forM ks $ \row -> do + case row of + [SText k] -> return $! T.unpack $ fromUtf8 k + _ -> internalError "doKeys: The impossible happened." + + tn = domainTableName d + collect p = + concatMap NE.toList $ HashMap.elems $ fromMaybe mempty $ HashMap.lookup tn (_pendingWrites p) +{-# INLINE doKeys #-} + +failIfTableDoesNotExistInDbAtHeight + :: Text -> Text -> BlockHeight -> BlockHandler logger () +failIfTableDoesNotExistInDbAtHeight caller tn bh = do + exists <- tableExistsInDbAtHeight tn bh + -- we must reproduce errors that were thrown in earlier blocks from tables + -- not existing, if this table does not yet exist. + unless exists $ + internalError $ "callDb (" <> caller <> "): user error (Database error: ErrorError)" + +-- tid is non-inclusive lower bound for the search +doTxIds :: TableName -> TxId -> BlockHandler logger [TxId] +doTxIds (TableName tn) _tid@(TxId tid) = do + dbOut <- getFromDb + + -- also collect from pending non-committed data + pb <- use bsPendingBlock + mptx <- use bsPendingTx + + -- uniquify txids before returning + return $ Set.toList + $! Set.fromList + $ dbOut ++ collect pb ++ maybe [] collect mptx + + where + getFromDb = do + m <- runMaybeT $ checkDbTablePendingCreation tn + case m of + Nothing -> return mempty + Just () -> do + rows <- callDb "doTxIds" $ \db -> + qry db stmt + [SInt (fromIntegral tid)] + [RInt] + forM rows $ \case + [SInt tid'] -> return $ TxId (fromIntegral tid') + _ -> internalError "doTxIds: the impossible happened" + + stmt = "SELECT DISTINCT txid FROM " <> tbl (toUtf8 tn) <> " WHERE txid > ?" + + collect p = + let txids = fmap _deltaTxId $ + concatMap NE.toList $ + HashMap.elems $ + fromMaybe mempty $ + HashMap.lookup tn (_pendingWrites p) + in filter (> _tid) txids +{-# INLINE doTxIds #-} + +recordTxLog + :: (AsString k, J.Encode v) + => TableName + -> Domain k v + -> k + -> v + -> BlockHandler logger () +recordTxLog tt d k v = do + -- are we in a tx? + mptx <- use bsPendingTx + modify' $ case mptx of + Nothing -> over (bsPendingBlock . pendingTxLogMap) upd + (Just _) -> over (bsPendingTx . _Just . pendingTxLogMap) upd + + where + !upd = M.insertWith DL.append tt txlogs + !txlogs = DL.singleton $! encodeTxLog $ TxLog (asString d) (asString k) v + +modifyPendingData + :: (SQLitePendingData (PendingWrites Pact4) -> SQLitePendingData (PendingWrites Pact4)) + -> BlockHandler logger () +modifyPendingData f = do + m <- use bsPendingTx + modify' $ case m of + Just d -> set bsPendingTx (Just $! f d) + Nothing -> over bsPendingBlock f + +doCreateUserTable + :: Maybe BlockHeight + -- ^ the highest block we should be seeing tables from + -> TableName + -> ModuleName + -> BlockHandler logger () +doCreateUserTable mbh tn@(TableName ttxt) mn = do + -- first check if tablename already exists in pending queues + m <- runMaybeT $ checkDbTablePendingCreation ttxt + case m of + Nothing -> throwM $ PactDuplicateTableError ttxt + Just () -> do + -- then check if it is in the db + lcTables <- view blockHandlerLowerCaseTables + cond <- inDb lcTables ttxt + when cond $ throwM $ PactDuplicateTableError ttxt + modifyPendingData + $ over pendingTableCreation (HashSet.insert ttxt) + . over pendingTxLogMap (M.insertWith DL.append (TableName txlogKey) txlogs) + where + inDb lcTables t = do + r <- callDb "doCreateUserTable" $ \db -> + qry db (tableLookupStmt lcTables) [SText (toUtf8 t)] [RText] + case r of + [[SText (Utf8 (T.decodeUtf8 -> rname))]] -> + case mbh of + -- if lowercase matching, no need to check equality + -- (wasn't needed before either but leaving alone for replay) + Nothing -> return (lcTables || rname == t) + Just bh -> do + existsInDb <- tableExistsInDbAtHeight t bh + return $ existsInDb && (lcTables || rname == t) + _ -> return False + + tableLookupStmt False = + "SELECT name FROM sqlite_master WHERE type='table' and name=?;" + tableLookupStmt True = + "SELECT name FROM sqlite_master WHERE type='table' and lower(name)=lower(?);" + txlogKey = "SYS:usertables" + stn = asString tn + uti = UserTableInfo mn + txlogs = DL.singleton $ encodeTxLog $ TxLog txlogKey stn uti +{-# INLINE doCreateUserTable #-} + +doRollback :: BlockHandler logger () +doRollback = modify' + $ set bsMode Nothing + . set bsPendingTx Nothing + +-- | Commit a Pact transaction +doCommit :: BlockHandler logger [TxLogJson] +doCommit = use bsMode >>= \case + Nothing -> doRollback >> internalError "doCommit: Not in transaction" + Just m -> do + txrs <- if m == Transactional + then do + modify' $ over bsTxId succ + -- merge pending tx into block data + pending <- use bsPendingTx + persistIntraBlockWrites <- view blockHandlerPersistIntraBlockWrites + modify' $ over bsPendingBlock (merge persistIntraBlockWrites pending) + -- this is mostly a lie; the previous `merge` call has already replaced the tx + -- logs from bsPendingBlock with those of the transaction. + -- from what I can tell, it's impossible for `pending` to be `Nothing` here, + -- but we don't throw an error for it. + blockLogs <- use $ bsPendingBlock . pendingTxLogMap + modify' $ set bsPendingTx Nothing + resetTemp + return blockLogs + else doRollback >> return mempty + return $! concatMap (reverse . DL.toList) txrs + where + merge _ Nothing a = a + merge persistIntraBlockWrites (Just txPending) blockPending = SQLitePendingData + { _pendingTableCreation = HashSet.union (_pendingTableCreation txPending) (_pendingTableCreation blockPending) + , _pendingWrites = HashMap.unionWith (HashMap.unionWith mergeAtRowKey) (_pendingWrites txPending) (_pendingWrites blockPending) + , _pendingTxLogMap = _pendingTxLogMap txPending + , _pendingSuccessfulTxs = _pendingSuccessfulTxs blockPending + } + where + mergeAtRowKey txWrites blockWrites = + let lastTxWrite = NE.head txWrites + in case persistIntraBlockWrites of + PersistIntraBlockWrites -> lastTxWrite `NE.cons` blockWrites + DoNotPersistIntraBlockWrites -> lastTxWrite :| [] +{-# INLINE doCommit #-} + +-- | Begin a Pact transaction +doBegin :: (Logger logger) => ExecutionMode -> BlockHandler logger (Maybe TxId) +doBegin m = do + logger <- view blockHandlerLogger + use bsMode >>= \case + Just {} -> do + logError_ logger "PactDb.beginTx: In transaction, rolling back" + doRollback + Nothing -> return () + resetTemp + modify' + $ set bsMode (Just m) + . set bsPendingTx (Just $ emptySQLitePendingData mempty) + case m of + Transactional -> Just <$> use bsTxId + Local -> pure Nothing +{-# INLINE doBegin #-} + +resetTemp :: BlockHandler logger () +resetTemp = modify' + $ set bsMode Nothing + -- clear out txlog entries + . set (bsPendingBlock . pendingTxLogMap) mempty + +doGetTxLog :: Domain k RowData -> TxId -> BlockHandler logger [TxLog RowData] +doGetTxLog d txid = do + -- try to look up this tx from pending log -- if we find it there it can't + -- possibly be in the db. + p <- readFromPending + if null p then readFromDb else return p + + where + tableName = domainTableName d + + readFromPending = do + allPendingData <- getPendingData + let deltas = do + -- grab all pending writes in this transaction and elsewhere in + -- this block + pending <- allPendingData + -- all writes to the table + let writesAtTableByKey = + fromMaybe mempty $ HashMap.lookup tableName $ _pendingWrites pending + -- a list of all writes to the table for some particular key + allWritesForSomeKey <- HashMap.elems writesAtTableByKey + -- the single latest write to the table for that key which is + -- from this txid; the most recent writes are inserted at the + -- front of the pending data + latestWriteForSomeKey <- take 1 + [ writeForSomeKey + | writeForSomeKey <- NE.toList allWritesForSomeKey + , _deltaTxId writeForSomeKey == txid + ] + return latestWriteForSomeKey + mapM (\x -> toTxLog (asString d) (Utf8 $ _deltaRowKey x) (_deltaData x)) deltas + + readFromDb = do + rows <- callDb "doGetTxLog" $ \db -> qry db stmt + [SInt (fromIntegral txid)] + [RText, RBlob] + forM rows $ \case + [SText key, SBlob value] -> toTxLog (asString d) key value + err -> internalError $ + "readHistoryResult: Expected single row with two columns as the \ + \result, got: " <> T.pack (show err) + stmt = "SELECT rowkey, rowdata FROM " <> tbl (toUtf8 tableName) <> " WHERE txid = ?" + + +toTxLog :: MonadThrow m => + T.Text -> Utf8 -> BS.ByteString -> m (TxLog RowData) +toTxLog d key value = + case Data.Aeson.decodeStrict' value of + Nothing -> internalError $ "toTxLog: Unexpected value, unable to deserialize log: " <> T.decodeUtf8 value + Just v -> + return $! TxLog d (fromUtf8 key) v + +-- | Register a successful transaction in the pending data for the block +indexPactTransaction :: BS.ByteString -> BlockHandler logger () +indexPactTransaction h = modify' $ + over (bsPendingBlock . pendingSuccessfulTxs) $ HashSet.insert h + + +-- | Careful doing this! It's expensive and for our use case, probably pointless. +-- We should reserve vacuuming for an offline process +vacuumDb :: BlockHandler logger () +vacuumDb = callDb "vacuumDb" (`exec_` "VACUUM;") + +commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockHeight -> BlockHandle Pact4 -> IO () +commitBlockStateToDatabase db hsh bh blockHandle = do + let newTables = _pendingTableCreation $ _blockHandlePending blockHandle + mapM_ (\tn -> createUserTable tn) newTables + let writeV = toChunks $ _pendingWrites (_blockHandlePending blockHandle) + backendWriteUpdateBatch writeV + indexPendingPactTransactions + let nextTxId = _blockHandleTxId blockHandle + blockHistoryInsert nextTxId + where + toChunks writes = + over _2 (concatMap toList . HashMap.elems) . + over _1 toUtf8 <$> HashMap.toList writes + + backendWriteUpdateBatch + :: [(Utf8, [SQLiteRowDelta])] + -> IO () + backendWriteUpdateBatch writesByTable = mapM_ writeTable writesByTable + where + prepRow (SQLiteRowDelta _ txid rowkey rowdata) = + [ SText (Utf8 rowkey) + , SInt (fromIntegral txid) + , SBlob rowdata + ] + + writeTable (tableName, writes) = do + execMulti db q (map prepRow writes) + markTableMutation tableName bh + where + q = "INSERT OR REPLACE INTO " <> tbl tableName <> "(rowkey,txid,rowdata) VALUES(?,?,?)" + + -- Mark the table as being mutated during this block, so that we know + -- to delete from it if we rewind past this block. + markTableMutation tablename blockheight = do + exec' db mutq [SText tablename, SInt (fromIntegral blockheight)] + where + mutq = "INSERT OR IGNORE INTO VersionedTableMutation VALUES (?,?);" + + -- | Record a block as being in the history of the checkpointer. + blockHistoryInsert :: TxId -> IO () + blockHistoryInsert t = + exec' db stmt + [ SInt (fromIntegral bh) + , SBlob (runPutS (encodeBlockHash hsh)) + , SInt (fromIntegral t) + ] + where + stmt = + "INSERT INTO BlockHistory ('blockheight','hash','endingtxid') VALUES (?,?,?);" + + createUserTable :: Text -> IO () + createUserTable (toUtf8 -> tablename) = do + createVersionedTable tablename db + markTableCreation tablename + + -- Mark the table as being created during this block, so that we know + -- to drop it if we rewind past this block. + markTableCreation tablename = + exec' db insertstmt insertargs + where + insertstmt = "INSERT OR IGNORE INTO VersionedTableCreation VALUES (?,?)" + insertargs = [SText tablename, SInt (fromIntegral bh)] + + -- | Commit the index of pending successful transactions to the database + indexPendingPactTransactions :: IO () + indexPendingPactTransactions = do + let txs = _pendingSuccessfulTxs $ _blockHandlePending blockHandle + dbIndexTransactions txs + + where + toRow b = [SBlob b, SInt (fromIntegral bh)] + dbIndexTransactions txs = do + let rows = map toRow $ toList txs + execMulti db "INSERT INTO TransactionIndex (txhash, blockheight) \ + \ VALUES (?, ?)" rows + + +createVersionedTable :: Utf8 -> Database -> IO () +createVersionedTable tablename db = do + exec_ db createtablestmt + exec_ db indexcreationstmt + where + ixName = tablename <> "_ix" + createtablestmt = + "CREATE TABLE IF NOT EXISTS " <> tbl tablename <> " \ + \ (rowkey TEXT\ + \, txid UNSIGNED BIGINT NOT NULL\ + \, rowdata BLOB NOT NULL\ + \, UNIQUE (rowkey, txid));" + indexcreationstmt = + "CREATE INDEX IF NOT EXISTS " <> tbl ixName <> " ON " <> tbl tablename <> "(txid DESC);" diff --git a/src/Chainweb/Pact4/ModuleCache.hs b/src/Chainweb/Pact4/ModuleCache.hs new file mode 100644 index 0000000000..ea961e8b80 --- /dev/null +++ b/src/Chainweb/Pact4/ModuleCache.hs @@ -0,0 +1,74 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE LambdaCase #-} + +module Chainweb.Pact4.ModuleCache + ( ModuleCache(..) + , filterModuleCacheByKey + , moduleCacheToHashMap + , moduleCacheFromHashMap + , moduleCacheKeys + , cleanModuleCache + ) where + +import Control.DeepSeq +import Control.Lens + +-- internal pact modules + +import Pact.Types.Runtime (ModuleData) +import Pact.Types.Term +import qualified Pact.Utils.StableHashMap as SHM + +-- internal chainweb modules + +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.ChainId +import Chainweb.Version + +import qualified Pact.JSON.Legacy.HashMap as LHM + +-- | Block scoped Module Cache +-- +newtype ModuleCache = ModuleCache { _getModuleCache :: LHM.HashMap ModuleName (ModuleData Ref, Bool) } + deriving newtype (Show, Eq, Semigroup, Monoid, NFData) + +filterModuleCacheByKey + :: (ModuleName -> Bool) + -> ModuleCache + -> ModuleCache +filterModuleCacheByKey f (ModuleCache c) = ModuleCache $ + LHM.fromList $ filter (f . fst) $ LHM.toList c +{-# INLINE filterModuleCacheByKey #-} + +moduleCacheToHashMap + :: ModuleCache + -> SHM.StableHashMap ModuleName (ModuleData Ref, Bool) +moduleCacheToHashMap (ModuleCache c) = SHM.fromList $ LHM.toList c +{-# INLINE moduleCacheToHashMap #-} + +moduleCacheFromHashMap + :: SHM.StableHashMap ModuleName (ModuleData Ref, Bool) + -> ModuleCache +moduleCacheFromHashMap = ModuleCache . LHM.fromList . SHM.toList +{-# INLINE moduleCacheFromHashMap #-} + +moduleCacheKeys :: ModuleCache -> [ModuleName] +moduleCacheKeys (ModuleCache a) = fst <$> LHM.toList a +{-# INLINE moduleCacheKeys #-} + +-- this can't go in Chainweb.Version.Guards because it causes an import cycle +-- it uses genesisHeight which is from BlockHeader which imports Guards +cleanModuleCache :: ChainwebVersion -> ChainId -> BlockHeight -> Bool +cleanModuleCache v cid bh = + case v ^?! versionForks . at Chainweb217Pact . _Just . atChain cid of + ForkAtBlockHeight bh' -> bh == bh' + ForkAtGenesis -> bh == genesisHeight v cid + ForkNever -> False diff --git a/src/Chainweb/Pact4/NoCoinbase.hs b/src/Chainweb/Pact4/NoCoinbase.hs new file mode 100644 index 0000000000..5994f6335a --- /dev/null +++ b/src/Chainweb/Pact4/NoCoinbase.hs @@ -0,0 +1,31 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | +-- Module: Chainweb.Pact4.NoCoinbase +-- Copyright: Copyright © 2020 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- A noop coin base for genesis transactions and testing purposes. +-- +module Chainweb.Pact4.NoCoinbase +( noCoinbase +) where + +-- internal modules + +import Pact.Types.Command +import Pact.Types.Exp +import Pact.Types.Hash +import Pact.Types.PactValue + +-- | No-op coinbase payload +-- +noCoinbase :: CommandResult a +noCoinbase = CommandResult + (RequestKey pactInitialHash) Nothing + (PactResult (Right (PLiteral (LString "NO_COINBASE")))) + 0 Nothing Nothing Nothing [] +{-# NOINLINE noCoinbase #-} diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs new file mode 100644 index 0000000000..c0a2ddad3b --- /dev/null +++ b/src/Chainweb/Pact4/SPV.hs @@ -0,0 +1,489 @@ +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ViewPatterns #-} + +-- | +-- Module: Chainweb.Pact4.SPV +-- Copyright: Copyright © 2018 - 2020 Kadena LLC. +-- License: See LICENSE file +-- Maintainers: Emily Pillmore +-- Stability: experimental +-- +-- Pact Service SPV support +-- +module Chainweb.Pact4.SPV +( -- * spv support + pactSPV +, verifySPV +, verifyCont + -- * spv api utilities +, getTxIdx +) where + + +import GHC.Stack + +import Control.Error +import Control.Lens hiding (index) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.Except +import Control.Monad.Trans.Except + +import Data.Aeson hiding (Object, (.=)) +import Data.Bifunctor +import qualified Data.ByteString as B +import qualified Data.ByteString.Base64.URL as B64U +import qualified Data.Map.Strict as M +import Data.Text (Text, pack) +import qualified Data.Text as Text +import qualified Data.Text.Encoding as Text +import Text.Read (readMaybe) + +import Crypto.Hash.Algorithms + +import qualified Ethereum.Header as EthHeader +import Ethereum.Misc +import Ethereum.Receipt +import Ethereum.Receipt.ReceiptProof +import Ethereum.RLP + +import Numeric.Natural + +import qualified Streaming.Prelude as S + +-- internal chainweb modules + +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB +import Chainweb.BlockHeight +import Chainweb.Pact.Types(internalError) +import Chainweb.Pact.Utils (aeson) +import Chainweb.Payload +import Chainweb.Payload.PayloadStore +import Chainweb.SPV +import Chainweb.SPV.VerifyProof +import Chainweb.TreeDB +import Chainweb.Utils +import qualified Chainweb.Version as CW +import qualified Chainweb.Version.Guards as CW + +-- internal pact modules + +import qualified Pact.JSON.Encode as J +import qualified Pact.Types.Command as Pact4 +import qualified Pact.Types.Hash as Pact4 +import qualified Pact.Types.Info as Pact4 +import qualified Pact.Types.PactValue as Pact4 +import qualified Pact.Types.Runtime as Pact4 +import qualified Pact.Types.SPV as Pact4 + +catchAndDisplaySPVError :: BlockHeader -> ExceptT Text IO a -> ExceptT Text IO a +catchAndDisplaySPVError bh = + if CW.chainweb219Pact (CW._chainwebVersion bh) (CW._chainId bh) (view blockHeight bh) + then flip catch $ \case + SpvExceptionVerificationFailed m -> throwError ("spv verification failed: " <> m) + spvErr -> throwM spvErr + else id + +forkedThrower :: BlockHeader -> Text -> ExceptT Text IO a +forkedThrower bh = + if CW.chainweb219Pact (CW._chainwebVersion bh) (CW._chainId bh) (view blockHeight bh) + then throwError + else internalError + +-- | Spv support for pact +-- +pactSPV + :: BlockHeaderDb + -- ^ handle into the cutdb + -> BlockHeader + -- ^ the context for verifying the proof + -> Pact4.SPVSupport +pactSPV bdb bh = Pact4.SPVSupport (verifySPV bdb bh) (verifyCont bdb bh) + +-- | SPV transaction verification support. Calls to 'verify-spv' in Pact +-- will thread through this function and verify an SPV receipt, making the +-- requisite calls to the SPV api and verifying the output proof. +-- +verifySPV + :: BlockHeaderDb + -- ^ handle into the cut db + -> BlockHeader + -- ^ the context for verifying the proof + -> Text + -- ^ TXOUT or TXIN - defines the type of proof + -- used in validation + -> Pact4.Object Pact4.Name + -- ^ the proof object to validate + -> IO (Either Text (Pact4.Object Pact4.Name)) +verifySPV bdb bh typ proof = runExceptT $ go typ proof + where + cid = CW._chainId bdb + enableBridge = CW.enableSPVBridge (CW._chainwebVersion bh) cid (view blockHeight bh) + + mkSPVResult' cr j + | enableBridge = + return $ mkSPVResult cr j + | otherwise = case Pact4.fromPactValue j of + Pact4.TObject o _ -> return o + _ -> throwError "spv-verified tx output has invalid type" + + go s o = case s of + + -- Ethereum Receipt Proof + "ETH" | enableBridge -> except (extractEthProof o) >>= + \parsedProof -> case validateReceiptProof parsedProof of + Left e -> throwError $ "Validation of Eth proof failed: " <> sshow e + Right result -> return $ ethResultToPactValue result + + -- Chainweb tx output proof + "TXOUT" -> do + u <- except $ extractProof enableBridge o + unless (view outputProofChainId u == cid) $ + forkedThrower bh "cannot redeem spv proof on wrong target chain" + + -- SPV proof verification is a 3 step process: + -- + -- 1. verify spv tx output proof via chainweb spv api + -- + -- 2. Decode tx outputs to 'HashCommandResult' + -- + -- 3. Extract tx outputs as a pact object and return the + -- object. + + TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) + + q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of + Nothing -> forkedThrower bh "unable to decode spv transaction output" + Just cr -> return cr + + case Pact4._crResult q of + Pact4.PactResult Left{} -> + throwError "Failed command result in tx output proof" + Pact4.PactResult (Right v) -> + mkSPVResult' q v + + t -> throwError $! "unsupported SPV types: " <> t + +-- | A tag for specifying the format of base64 error messages on chain. +-- +-- `Legacy` errors match those produced by our legacy version of +-- base64-bytestring, and are produced by parsing the error messages +-- we receive from our current version of base64-bytestring (1.2), +-- and formatting them in the older style. Legacy behavior also implies +-- that non-canonical encodings be allowed, because that was the behavior +-- of the legacy bytestring parser; and improperly padded messages +-- will get extra padding, because that is the legacy chainweb behavior. +-- +-- `Simplified` errors are a static string, which may not describe +-- the issue with the base64-encoded string as well, but will be +-- more stable as we upgrade base64 decoding libraries in the future. +-- In the `Simplified` errors setting, messages rejected for using +-- non-canonical encodings will remain as errors, because we want to +-- begin enforcing the expected constraints on base64-encoded messages. +-- Similarly, Simplified parsing will not add extra padding to improperly +-- padded messages. +data GenerateBase64ErrorMessage + = Legacy + | Simplified + deriving (Eq, Show) + + +-- | A modified version of `decodeBase64UrlNoPaddingText` that emits +-- base64 decoding errors in a configurable way. +decodeB64UrlNoPaddingTextWithFixedErrorMessage :: MonadThrow m => GenerateBase64ErrorMessage -> Text.Text -> m B.ByteString +decodeB64UrlNoPaddingTextWithFixedErrorMessage errorMessageType msg = + fromEitherM + . first (\e -> Base64DecodeException $ base64ErrorMessage e) + . (if errorMessageType == Legacy then patchNonCanonical else id) + . B64U.decode + . Text.encodeUtf8 + . (if errorMessageType == Legacy then pad else id) + $ msg + where + pad t = let s = Text.length t `mod` 4 in t <> Text.replicate ((4 - s) `mod` 4) "=" + base64ErrorMessage m = case errorMessageType of + Legacy -> base64DowngradeErrorMessage (Text.pack m) + Simplified -> "could not base64-decode message" + patchNonCanonical decodeResult = case decodeResult of + Right bs -> Right bs + Left e | "non-canonical" `Text.isInfixOf` Text.pack e -> + (B64U.decodeNonCanonical (Text.encodeUtf8 msg)) + Left e -> Left e +{-# INLINE decodeB64UrlNoPaddingTextWithFixedErrorMessage #-} + + + +-- | Converts the error message format of base64-bytestring-1.2 +-- into that of base64-bytestring-0.1, for the error messages +-- that have made it onto the chain. +-- This allows us to upgrade to base64-bytestring-1.2 without +-- breaking compatibility. +base64DowngradeErrorMessage :: Text -> Text +base64DowngradeErrorMessage msg = case msg of + "Base64-encoded bytestring has invalid size" -> + "invalid base64 encoding near offset 0" + (Text.stripPrefix "invalid character at offset: " -> Just suffix) -> + Text.pack $ "invalid base64 encoding near offset " ++ show (adjustedOffset suffix) + (Text.stripPrefix "invalid padding at offset: " -> Just suffix) -> + Text.pack $ "invalid padding near offset " ++ show (adjustedOffset suffix) + e -> e + where + adjustedOffset :: Text -> Int + adjustedOffset suffix = case readMaybe (Text.unpack suffix) of + Nothing -> 0 + Just offset -> let + endsWithThreeEquals = Text.drop (Text.length msg - 3) msg == "===" + adjustment = if endsWithThreeEquals then -1 else 0 + in + offset - (offset `rem` 4) + adjustment + +-- | SPV defpact transaction verification support. This call validates a pact 'endorsement' +-- in Pact, providing a validation that the yield data of a cross-chain pact is valid. +-- +verifyCont + :: BlockHeaderDb + -- ^ handle into the cut db + -> BlockHeader + -- ^ the context for verifying the proof + -> Pact4.ContProof + -- ^ bytestring of 'TransactionOutputP roof' object to validate + -> IO (Either Text Pact4.PactExec) +verifyCont bdb bh (Pact4.ContProof cp) = runExceptT $ do + let errorMessageType = + if CW.chainweb221Pact + (CW._chainwebVersion bh) + (CW._chainId bh) + (view blockHeight bh) + then Simplified + else Legacy + t <- decodeB64UrlNoPaddingTextWithFixedErrorMessage errorMessageType $ Text.decodeUtf8 cp + case decodeStrict' t of + Nothing -> forkedThrower bh "unable to decode continuation proof" + Just u + | view outputProofChainId u /= cid -> + forkedThrower bh "cannot redeem continuation proof on wrong target chain" + | otherwise -> do + + -- Cont proof verification is a 3 step process: + -- + -- 1. verify spv tx output proof via chainweb spv api + -- + -- 2. Decode tx outputs to 'Pact4.CommandResult' 'Pact4.Hash' + -- + -- 3. Extract continuation 'PactExec' from decoded result + -- and return the cont exec object + + TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) + + q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of + Nothing -> forkedThrower bh "unable to decode spv transaction output" + Just cr -> return cr + + case Pact4._crContinuation q of + Nothing -> throwError "no pact exec found in command result" + Just pe -> return pe + where + cid = CW._chainId bdb + +-- | Extract a 'TransactionOutputProof' from a generic pact object +-- +extractProof :: Bool -> Pact4.Object Pact4.Name -> Either Text (TransactionOutputProof SHA512t_256) +extractProof False o = Pact4.toPactValue (Pact4.TObject o Pact4.noInfo) >>= k + where + k = aeson (Left . pack) Right + . fromJSON + . J.toJsonViaEncode +extractProof True (Pact4.Object (Pact4.ObjectMap o) _ _ _) = case M.lookup "proof" o of + Just (Pact4.TLitString proof) -> do + j <- first (const "Base64 decode failed") (decodeB64UrlNoPaddingText proof) + first (const "Decode of TransactionOutputProof failed") (decodeStrictOrThrow j) + _ -> Left "Invalid input, expected 'proof' field with base64url unpadded text" + +-- | Extract an Eth 'ReceiptProof' from a generic pact object +-- +-- The proof object has a sinle property "proof". The value is the +-- base64UrlWithoutPadding encoded proof blob. +-- +-- NOTE: If this fails the failure message is included on the chain. We +-- therefore replace failure and exception messages from external libraries with +-- stable internal messages. +-- +-- For details of the returned value see 'Ethereum.Receipt' +-- +extractEthProof :: Pact4.Object Pact4.Name -> Either Text ReceiptProof +extractEthProof o = case M.lookup "proof" $ Pact4._objectMap $ Pact4._oObject o of + Nothing -> Left "Decoding of Eth proof object failed: missing 'proof' property" + Just (Pact4.TLitString p) -> do + bytes' <- errMsg "Decoding of Eth proof object failed: invalid base64URLWithoutPadding encoding" + $ decodeB64UrlNoPaddingText p + errMsg "Decoding of Eth proof object failed: invalid binary proof data" + $ get getRlp bytes' + Just _ -> Left "Decoding of Eth proof object failed: invalid 'proof' property" + where + errMsg t = first (const t) + +ethResultToPactValue :: ReceiptProofValidation -> Pact4.Object Pact4.Name +ethResultToPactValue ReceiptProofValidation{..} = mkObject + [ ("depth", tInt _receiptProofValidationDepth) + , ("header", header _receiptProofValidationHeader) + , ("index", tix _receiptProofValidationIndex) + , ("root",jsonStr _receiptProofValidationRoot) + , ("weight",tInt _receiptProofValidationWeight) + , ("receipt",receipt _receiptProofValidationReceipt) + ] + where + receipt Receipt{..} = obj + [ ("cumulative-gas-used", tInt _receiptGasUsed) + , ("status",Pact4.toTerm $ _receiptStatus == TxStatus 1) + , ("logs",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map rlog _receiptLogs)] + rlog LogEntry{..} = obj + [ ("address",jsonStr _logEntryAddress) + , ("topics",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map topic _logEntryTopics) + , ("data",jsonStr _logEntryData)] + topic t = jsonStr t + header ch@EthHeader.ConsensusHeader{..} = obj + [ ("difficulty", jsonStr _hdrDifficulty) + , ("extra-data", jsonStr _hdrExtraData) + , ("gas-limit", tInt _hdrGasLimit) + , ("gas-used", tInt _hdrGasUsed) + , ("hash", jsonStr $ EthHeader.blockHash ch) + , ("miner", jsonStr _hdrBeneficiary) + , ("mix-hash", jsonStr _hdrMixHash) + , ("nonce", jsonStr _hdrNonce) + , ("number", tInt _hdrNumber) + , ("parent-hash", jsonStr _hdrParentHash) + , ("receipts-root", jsonStr _hdrReceiptsRoot) + , ("sha3-uncles", jsonStr _hdrOmmersHash) + , ("state-root", jsonStr _hdrStateRoot) + , ("timestamp", ts _hdrTimestamp) + , ("transactions-root", jsonStr _hdrTransactionsRoot) + ] + jsonStr v = case toJSON v of + String s -> Pact4.tStr s + _ -> Pact4.tStr $ sshow v + ts (Timestamp t) = tInt t + tix (TransactionIndex i) = tInt i +{-# INLINE ethResultToPactValue #-} + +-- | Look up pact tx hash at some block height in the +-- payload db, and return the tx index for proof creation. +-- +-- Note: runs in O(n) - this should be revisited if possible +-- +getTxIdx + :: HasCallStack + => CanReadablePayloadCas tbl + => BlockHeaderDb + -> PayloadDb tbl + -> BlockHeight + -> Pact4.PactHash + -> IO (Either Text Int) +getTxIdx bdb pdb bh th = do + -- get BlockPayloadHash + m <- maxEntry bdb + ph <- seekAncestor bdb m (int bh) >>= \case + Just x -> return $ Right $! view blockPayloadHash x + Nothing -> return $ Left "unable to find payload associated with transaction hash" + + case ph of + (Left !s) -> return $ Left s + (Right !a) -> do + -- get payload + Just payload <- lookupPayloadWithHeight pdb (Just bh) a + + -- Find transaction index + r <- S.each (_payloadWithOutputsTransactions payload) + & S.map fst + & S.mapM toTxHash + & sindex (== th) + + r & note "unable to find transaction at the given block height" + & fmap int + & return + where + toPactTx :: MonadThrow m => Transaction -> m (Pact4.Command Text) + toPactTx (Transaction b) = decodeStrictOrThrow' b + + toTxHash :: MonadThrow m => Transaction -> m Pact4.PactHash + toTxHash = fmap Pact4._cmdHash . toPactTx + + sfind :: Monad m => (a -> Bool) -> S.Stream (S.Of a) m () -> m (Maybe a) + sfind p = S.head_ . S.dropWhile (not . p) + + sindex :: Monad m => (a -> Bool) -> S.Stream (S.Of a) m () -> m (Maybe Natural) + sindex p s = S.zip (S.each [0..]) s & sfind (p . snd) & fmap (fmap fst) + +mkObject :: [(Pact4.FieldKey, Pact4.Term n)] -> Pact4.Object n +mkObject ps = Pact4.Object (Pact4.ObjectMap (M.fromList ps)) Pact4.TyAny Nothing Pact4.noInfo + +obj :: [(Pact4.FieldKey, Pact4.Term n)] -> Pact4.Term n +obj = Pact4.toTObject Pact4.TyAny Pact4.noInfo + +tInt :: Integral i => i -> Pact4.Term Pact4.Name +tInt = Pact4.toTerm . fromIntegral @_ @Integer + +-- | Encode a "successful" CommandResult into a Pact object. +mkSPVResult + :: Pact4.CommandResult Pact4.Hash + -- ^ Full CR + -> Pact4.PactValue + -- ^ Success result + -> Pact4.Object Pact4.Name +mkSPVResult Pact4.CommandResult{..} j = + mkObject + [ ("result", Pact4.fromPactValue j) + , ("req-key", Pact4.tStr $ Pact4.asString $ Pact4.unRequestKey _crReqKey) + , ("txid", Pact4.tStr $ maybe "" Pact4.asString _crTxId) + , ("gas", Pact4.toTerm $ (fromIntegral _crGas :: Integer)) + , ("meta", maybe empty metaField _crMetaData) + , ("logs", Pact4.tStr $ Pact4.asString _crLogs) + , ("continuation", maybe empty contField _crContinuation) + , ("events", Pact4.toTList Pact4.TyAny Pact4.noInfo $ map eventField _crEvents) + ] + where + metaField v = case fromJSON v of + Error _ -> obj [] + Success p -> Pact4.fromPactValue p + + contField (Pact4.PactExec stepCount yield executed step pactId pactCont rollback _nested) = obj + [ ("step", Pact4.toTerm step) + , ("step-count", Pact4.toTerm stepCount) + , ("yield", maybe empty yieldField yield) + , ("pact-id", Pact4.toTerm pactId) + , ("cont",contField1 pactCont) + , ("step-has-rollback",Pact4.toTerm rollback) + , ("executed",Pact4.tStr $ maybe "" sshow executed) + ] + + contField1 Pact4.PactContinuation {..} = obj + [ ("name",Pact4.tStr $ Pact4.asString _pcDef) + , ("args",Pact4.toTList Pact4.TyAny Pact4.noInfo $ map Pact4.fromPactValue _pcArgs) + ] + + yieldField Pact4.Yield {..} = obj + [ ("data",Pact4.fromPactValue (Pact4.PObject _yData)) + , ("provenance", maybe empty provField _yProvenance) + ] + + provField Pact4.Provenance {..} = obj + [ ("target-chain", Pact4.toTerm $ Pact4._chainId _pTargetChainId) + , ("module-hash", Pact4.tStr $ Pact4.asString $ Pact4._mhHash $ _pModuleHash) + ] + + eventField Pact4.PactEvent {..} = obj + [ ("name", Pact4.toTerm _eventName) + , ("params", Pact4.toTList Pact4.TyAny Pact4.noInfo (map Pact4.fromPactValue _eventParams)) + , ("module", Pact4.tStr $ Pact4.asString _eventModule) + , ("module-hash", Pact4.tStr $ Pact4.asString _eventModuleHash) + ] + + empty = obj [] diff --git a/src/Chainweb/Pact4/Templates.hs b/src/Chainweb/Pact4/Templates.hs new file mode 100644 index 0000000000..a8f62002bb --- /dev/null +++ b/src/Chainweb/Pact4/Templates.hs @@ -0,0 +1,203 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | +-- Module : Chainweb.Pact.Templates +-- Copyright : Copyright © 2010 Kadena LLC. +-- License : (see the file LICENSE) +-- Maintainer : Stuart Popejoy +-- Stability : experimental +-- +-- Prebuilt Term templates for automated operations (coinbase, gas buy) +-- +module Chainweb.Pact4.Templates +( mkFundTxTerm +, mkBuyGasTerm +, mkRedeemGasTerm +, mkCoinbaseTerm + +, mkCoinbaseCmd +) where + + +import Control.Lens +import Data.Text (Text, pack) + +import Text.Trifecta.Delta (Delta(..)) + +-- internal modules + +import qualified Pact.JSON.Encode as J +import Pact.JSON.Legacy.Value +import Pact.Parse +import Pact.Types.Command +import Pact.Types.RPC +import Pact.Types.Runtime + +import Chainweb.Miner.Pact +import Chainweb.Pact.Types +import Chainweb.Pact4.Types (GasSupply) + +inf :: Info +inf = Info $ Just (Code "",Parsed (Columns 0 0) 0) +{-# NOINLINE inf #-} + +app :: Name -> [Term Name] -> Term Name +app f as = TApp (App (TVar f inf) as inf) inf +{-# INLINE app #-} + +qn :: ModuleName -> Text -> Name +qn mn d = QName $ QualifiedName mn d inf +{-# INLINE qn #-} + +bn :: Text -> Name +bn n = Name $ BareName n inf +{-# INLINE bn #-} + +strLit :: Text -> Term Name +strLit s = TLiteral (LString s) inf +{-# INLINE strLit #-} + +strArgSetter :: Int -> ASetter' (Term Name) Text +strArgSetter idx = tApp . appArgs . ix idx . tLiteral . _LString +{-# INLINE strArgSetter #-} + +fundTxTemplate :: (Term Name, ASetter' (Term Name) Text, ASetter' (Term Name) Text) +fundTxTemplate = + ( app (qn "coin" "fund-tx") + [ strLit "sender" + , strLit "mid" + , app (bn "read-keyset") [strLit "miner-keyset"] + , app (bn "read-decimal") [strLit "total"] + ] + , strArgSetter 0 + , strArgSetter 1 + ) +{-# NOINLINE fundTxTemplate #-} + +buyGasTemplate :: (Term Name, ASetter' (Term Name) Text) +buyGasTemplate = + ( app (qn "coin" "buy-gas") + [ strLit "sender" + , app (bn "read-decimal") [strLit "total"] + ] + , strArgSetter 0 + ) + +redeemGasTemplate :: (Term Name, ASetter' (Term Name) Text, ASetter' (Term Name) Text) +redeemGasTemplate = + ( app (qn "coin" "redeem-gas") + [ strLit "mid" + , app (bn "read-keyset") [strLit "miner-keyset"] + , strLit "sender" + , app (bn "read-decimal") [strLit "total"] + ] + , strArgSetter 2 + , strArgSetter 0 + ) + +dummyParsedCode :: ParsedCode +dummyParsedCode = ParsedCode "1" [ELiteral $ LiteralExp (LInteger 1) (Parsed mempty 0)] +{-# NOINLINE dummyParsedCode #-} + + +mkFundTxTerm + :: MinerId -- ^ Id of the miner to fund + -> MinerKeys -- ^ Miner keyset + -> Text -- ^ Address of the sender from the command + -> GasSupply -- ^ The gas limit total * price + -> (Term Name,ExecMsg ParsedCode) +mkFundTxTerm (MinerId mid) (MinerKeys ks) sender total = (populatedTerm, execMsg) + where (term, senderS, minerS) = fundTxTemplate + populatedTerm = set senderS sender $ set minerS mid term + execMsg = ExecMsg dummyParsedCode (toLegacyJsonViaEncode buyGasData) + buyGasData = J.object + [ "miner-keyset" J..= ks + , "total" J..= total + ] +{-# INLINABLE mkFundTxTerm #-} + +mkBuyGasTerm + :: Text -- ^ Address of the sender from the command + -> GasSupply -- ^ The gas limit total * price + -> (Term Name,ExecMsg ParsedCode) +mkBuyGasTerm sender total = (populatedTerm, execMsg) + where (term, senderS) = buyGasTemplate + populatedTerm = set senderS sender term + execMsg = ExecMsg dummyParsedCode (toLegacyJsonViaEncode buyGasData) + buyGasData = J.object + [ "total" J..= total ] +{-# INLINABLE mkBuyGasTerm #-} + +mkRedeemGasTerm + :: MinerId -- ^ Id of the miner to fund + -> MinerKeys -- ^ Miner keyset + -> Text -- ^ Address of the sender from the command + -> GasSupply -- ^ The gas limit total * price + -> GasSupply -- ^ The gas used * price + -> (Term Name,ExecMsg ParsedCode) +mkRedeemGasTerm (MinerId mid) (MinerKeys ks) sender total fee = (populatedTerm, execMsg) + where (term, senderS, minerS) = redeemGasTemplate + populatedTerm = set senderS sender $ set minerS mid term + execMsg = ExecMsg dummyParsedCode (toLegacyJsonViaEncode redeemGasData) + redeemGasData = J.object + [ "total" J..= total + , "fee" J..= J.toJsonViaEncode fee + , "miner-keyset" J..= ks + ] +{-# INLINABLE mkRedeemGasTerm #-} + +coinbaseTemplate :: (Term Name,ASetter' (Term Name) Text) +coinbaseTemplate = + ( app (qn "coin" "coinbase") + [ strLit "mid" + , app (bn "read-keyset") [strLit "miner-keyset"] + , app (bn "read-decimal") [strLit "reward"] + ] + , strArgSetter 0 + ) +{-# NOINLINE coinbaseTemplate #-} + +mkCoinbaseTerm :: MinerId -> MinerKeys -> ParsedDecimal -> (Term Name,ExecMsg ParsedCode) +mkCoinbaseTerm (MinerId mid) (MinerKeys ks) reward = (populatedTerm, execMsg) + where + (term, minerS) = coinbaseTemplate + populatedTerm = set minerS mid term + execMsg = ExecMsg dummyParsedCode (toLegacyJsonViaEncode coinbaseData) + coinbaseData = J.object + [ "miner-keyset" J..= ks + , "reward" J..= reward + ] +{-# INLINABLE mkCoinbaseTerm #-} + +-- | "Old method" to build a coinbase 'ExecMsg' for back-compat. +-- +mkCoinbaseCmd :: MinerId -> MinerKeys -> ParsedDecimal -> IO (ExecMsg ParsedCode) +mkCoinbaseCmd (MinerId mid) (MinerKeys ks) reward = + buildExecParsedCode $ mconcat + [ "(coin.coinbase" + , " \"" <> mid <> "\"" + , " (read-keyset \"miner-keyset\")" + , " (read-decimal \"reward\"))" + ] + where + + coinbaseData = J.object + [ "miner-keyset" J..= ks + , "reward" J..= reward + ] + + -- | Build the 'ExecMsg' for some pact code fed to the function. + -- + buildExecParsedCode :: Text -> IO (ExecMsg ParsedCode) + buildExecParsedCode code = case parsePact code of + Right !t -> pure $! ExecMsg t (toLegacyJsonViaEncode coinbaseData) + -- if we can't construct coin contract calls, this should + -- fail fast + Left err -> internalError $ "buildExecParsedCode: parse failed: " <> pack err + +{-# INLINABLE mkCoinbaseCmd #-} diff --git a/src/Chainweb/Pact4/Transaction.hs b/src/Chainweb/Pact4/Transaction.hs new file mode 100644 index 0000000000..eb74a6f31b --- /dev/null +++ b/src/Chainweb/Pact4/Transaction.hs @@ -0,0 +1,190 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE DeriveTraversable #-} + +module Chainweb.Pact4.Transaction + ( Transaction + , UnparsedTransaction + , unparseTransaction + , HashableTrans(..) + , PayloadWithText + , PactParserVersion(..) + , IsWebAuthnPrefixLegal(..) + , payloadCodec + , rawCommandCodec + , encodePayload + , decodePayload + , cmdGasLimit + , cmdGasPrice + , cmdTimeToLive + , cmdCreationTime + , mkPayloadWithText + , mkPayloadWithTextOld + , mkPayloadWithTextOldUnparsed + , payloadBytes + , payloadObj + , parsePact + ) where + +import Control.DeepSeq +import Control.Lens + +import qualified Data.Aeson as Aeson +import Data.ByteString.Char8 (ByteString) +import qualified Data.ByteString.Char8 as B +import qualified Data.ByteString.Short as SB +import Data.Hashable +import Data.Text (Text) +import Data.Text.Encoding (decodeUtf8, encodeUtf8) + +import GHC.Generics (Generic) + +import qualified Pact.Parse as P (parsePact, legacyParsePact) +import Pact.Types.ChainMeta +import Pact.Types.Command +import Pact.Types.Gas (GasLimit(..), GasPrice(..)) +import Pact.Types.Hash +import qualified Pact.JSON.Encode as J +import Pact.JSON.Legacy.Value + +import Chainweb.Utils +import Chainweb.Utils.Serialization + +-- | A product type representing a `Payload PublicMeta ParsedCode` coupled with +-- the text that it was parsed from, to make gossiping easier. +-- +data PayloadWithText meta code = PayloadWithText + { _payloadBytes :: !SB.ShortByteString + , _payloadObj :: !(Payload meta code) + } + deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic) + deriving anyclass (NFData) + +payloadBytes :: PayloadWithText meta code -> SB.ShortByteString +payloadBytes = _payloadBytes + +payloadObj :: PayloadWithText meta code -> Payload meta code +payloadObj = _payloadObj + +mkPayloadWithText :: Command (ByteString, Payload meta code) -> Command (PayloadWithText meta code) +mkPayloadWithText = over cmdPayload $ \(bs, p) -> PayloadWithText + { _payloadBytes = SB.toShort bs + , _payloadObj = p + } + +mkPayloadWithTextOld :: Payload PublicMeta ParsedCode -> PayloadWithText PublicMeta ParsedCode +mkPayloadWithTextOld p = PayloadWithText + { _payloadBytes = SB.toShort $ J.encodeStrict $ toLegacyJsonViaEncode $ fmap _pcCode p + , _payloadObj = p + } + +mkPayloadWithTextOldUnparsed :: Payload PublicMeta Text -> PayloadWithText PublicMeta Text +mkPayloadWithTextOldUnparsed p = PayloadWithText + { _payloadBytes = SB.toShort $ J.encodeStrict $ toLegacyJsonViaEncode p + , _payloadObj = p + } + +-- | Pact 4 transactions. +type Transaction = Command (PayloadWithText PublicMeta ParsedCode) + +-- | Pact 4 commands with code left not parsed are used in the mempool. +type UnparsedTransaction = Command (PayloadWithText PublicMeta Text) + +-- | Throw away the parsed representation of the Pact code. +unparseTransaction :: Transaction -> UnparsedTransaction +unparseTransaction cmd = cmd <&> fmap _pcCode + +data PactParserVersion + = PactParserGenesis + | PactParserChainweb213 + deriving (Eq, Ord, Bounded, Show, Enum) + +-- | Denotes whether the `WEBAUTHN-` key prefix is valid at this point in the block history. +data IsWebAuthnPrefixLegal + = WebAuthnPrefixIllegal + | WebAuthnPrefixLegal + deriving (Eq, Ord, Bounded, Show, Enum) + +-- | Hashable newtype of Transaction +newtype HashableTrans a = HashableTrans { unHashable :: Command a } + deriving (Eq, Functor, Ord) + +instance (Eq code, Eq meta) => Hashable (HashableTrans (PayloadWithText meta code)) where + hashWithSalt s (HashableTrans t) = hashWithSalt s hashCode + where + (TypedHash hc) = _cmdHash t + decHC = runGetEitherS getWord64le + !hashCode = either error id $ decHC (B.take 8 $ SB.fromShort hc) + {-# INLINE hashWithSalt #-} + +-- | A codec for the transaction type used in the mempool. +-- +rawCommandCodec :: Codec UnparsedTransaction +rawCommandCodec = Codec enc dec + where + enc cmd = J.encodeStrict $ J.text . decodeUtf8 . SB.fromShort . _payloadBytes <$> cmd + dec bs = do + cmd <- Aeson.eitherDecodeStrict' bs + let p = encodeUtf8 $ _cmdPayload cmd + payloadObject <- Aeson.eitherDecodeStrict' p + let payloadWithText = PayloadWithText { _payloadBytes = (SB.toShort p), _payloadObj = payloadObject } + return $ payloadWithText <$ cmd + +-- | A codec for Pact4's (Command PayloadWithText) transactions. +-- +payloadCodec + :: PactParserVersion + -> Codec Transaction +payloadCodec ppv = Codec enc dec + where + enc c = J.encodeStrict $ fmap (decodeUtf8 . encodePayload) c + dec bs = case Aeson.decodeStrict' bs of + Just cmd -> traverse (decodePayload ppv . encodeUtf8) cmd + Nothing -> Left "decode PayloadWithText failed" + +encodePayload :: PayloadWithText meta code -> ByteString +encodePayload = SB.fromShort . _payloadBytes + +decodePayload + :: Aeson.FromJSON meta + => PactParserVersion + -> ByteString + -> Either String (PayloadWithText meta ParsedCode) +decodePayload ppv bs = case Aeson.decodeStrict' bs of + Just payload -> do + p <- traverse (parsePact ppv) payload + return $! PayloadWithText (SB.toShort bs) p + Nothing -> Left "decoding Payload failed" + +parsePact + :: PactParserVersion + -- ^ If the chain context is @Nothing@, latest parser version is used. + -> Text + -> Either String ParsedCode +parsePact PactParserChainweb213 = P.parsePact +parsePact PactParserGenesis = P.legacyParsePact + +-- | Access the gas limit/supply of a public chain command payload +cmdGasLimit :: Lens' (Command (Payload PublicMeta c)) GasLimit +cmdGasLimit = cmdPayload . pMeta . pmGasLimit +{-# INLINE cmdGasLimit #-} + +-- | Get the gas price of a public chain command payload +cmdGasPrice :: Lens' (Command (Payload PublicMeta c)) GasPrice +cmdGasPrice = cmdPayload . pMeta . pmGasPrice +{-# INLINE cmdGasPrice #-} + +cmdTimeToLive :: Lens' (Command (Payload PublicMeta c)) TTLSeconds +cmdTimeToLive = cmdPayload . pMeta . pmTTL +{-# INLINE cmdTimeToLive #-} + +cmdCreationTime :: Lens' (Command (Payload PublicMeta c)) TxCreationTime +cmdCreationTime = cmdPayload . pMeta . pmCreationTime +{-# INLINE cmdCreationTime #-} diff --git a/src/Chainweb/Pact4/TransactionExec.hs b/src/Chainweb/Pact4/TransactionExec.hs new file mode 100644 index 0000000000..b877b1201b --- /dev/null +++ b/src/Chainweb/Pact4/TransactionExec.hs @@ -0,0 +1,1553 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE MonoLocalBinds #-} +-- | +-- Module : Chainweb.Pact4.TransactionExec +-- Copyright : Copyright © 2018 Kadena LLC. +-- License : (see the file LICENSE) +-- Maintainer : Mark Nichols , Emily Pillmore +-- Stability : experimental +-- +-- Pact command execution and coin-contract transaction logic for Chainweb +-- +module Chainweb.Pact4.TransactionExec + -- * Transaction State + ( TransactionState(..) + , txGasModel + , txGasLimit + , txGasUsed + , txGasId + , txLogs + , txCache + , txWarnings + + -- * Transaction Env + , TransactionEnv(..) + , txMode + , txDbEnv + , txLogger + , txGasLogger + , txPublicData + , txSpvSupport + , txNetworkId + , txGasPrice + , txRequestKey + , txExecutionConfig + , txQuirkGasFee + , txTxFailuresCounter + + -- * Transaction Execution Monad + , TransactionM(..) + , runTransactionM + , evalTransactionM + , execTransactionM + -- * Transaction Execution + + , applyCmd + , applyGenesisCmd + , applyLocal + , applyExec + , applyExec' + , applyContinuation + , applyContinuation' + , runPayload + , readInitModules + , enablePactEvents' + , enforceKeysetFormats' + , disableReturnRTC + + -- * Gas Execution + , buyGas + + -- * Coinbase Execution + , applyCoinbase + , EnforceCoinbaseFailure(..) + + -- * Command Helpers + , publicMetaOf + , networkIdOf + , gasSupplyOf + + -- * Utilities + , buildExecParsedCode + , mkMagicCapSlot + , listErrMsg + , initialGasOf + +) where + +import Control.DeepSeq +import Control.Lens +import Control.Monad +import Control.Monad.Catch +import Control.Monad.Reader +import Control.Monad.State.Strict +import Control.Monad.Trans.Maybe +import Control.Parallel.Strategies(using, rseq) + +import Data.Aeson hiding ((.=)) +import qualified Data.Aeson as A +import Data.Bifunctor +import qualified Data.ByteString as B +import qualified Data.ByteString.Short as SB +import Data.Decimal (Decimal, roundTo) +import Data.Foldable (fold, for_, traverse_) +import Data.IORef +import qualified Data.List as List +import qualified Data.Map.Strict as M +import Data.Maybe +import qualified Data.Set as S +import Data.Text (Text) +import qualified Data.Text as T +import qualified System.LogLevel as L + +-- internal Pact modules + +import Chainweb.Counter +import Pact.Eval (eval, liftTerm) +import Pact.Gas (freeGasEnv) +import Pact.Interpreter +import qualified Pact.JSON.Encode as J +import Pact.JSON.Legacy.Value +import Pact.Native.Capabilities (evalCap) +import Pact.Native.Internal (appToCap) +import Pact.Parse (ParsedDecimal(..)) +import Pact.Runtime.Capabilities (popCapStack) +import Pact.Runtime.Utils (lookupModule) +import Pact.Types.Capability +import Pact.Types.Command +import Pact.Types.Hash as Pact +import Pact.Types.KeySet +import Pact.Types.PactValue +import Pact.Types.Pretty +import Pact.Types.RPC +import Pact.Types.Runtime hiding (catchesPactError) +import Pact.Types.Server +import Pact.Types.SPV +import Pact.Types.Verifier + +import Pact.Types.Util as PU +import qualified Pact.Utils.StableHashMap as SHM + +-- internal Chainweb modules + +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.Logger +import qualified Chainweb.ChainId as Chainweb +import Chainweb.Mempool.Mempool (pact4RequestKeyToTransactionHash) +import Chainweb.Miner.Pact +import Chainweb.Pact4.Templates +import Chainweb.Pact.Types +import Chainweb.Pact4.Types +import Chainweb.Pact4.Transaction +import Chainweb.Utils +import Chainweb.VerifierPlugin +import Chainweb.Version as V +import Chainweb.Version.Guards as V +import Chainweb.Version.Utils as V +import Pact.JSON.Encode (toJsonViaEncode) +import Data.Set (Set) +import Chainweb.Pact4.ModuleCache +import Chainweb.Pact4.Backend.ChainwebPactDb + +import Pact.Core.Errors (VerifierError(..)) + +-- Note [Throw out verifier proofs eagerly] +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-- We try to discard verifier proofs eagerly so that we don't hang onto them in +-- the liveset. This implies that we also try to discard `Command`s for the same +-- reason, because they contain the verifier proofs and other data we probably +-- don't need. + +-- -------------------------------------------------------------------------- -- + +-- -------------------------------------------------------------------- -- +-- Tx Execution Service Monad + +-- | Transaction execution state +-- +data TransactionState = TransactionState + { _txCache :: !ModuleCache + , _txLogs :: ![TxLogJson] + , _txGasUsed :: !Gas + , _txGasId :: !(Maybe GasId) + , _txGasModel :: !GasModel + , _txWarnings :: !(Set PactWarning) + } +makeLenses ''TransactionState + +-- | Transaction execution env +-- +data TransactionEnv logger db = TransactionEnv + { _txMode :: !ExecutionMode + , _txDbEnv :: !(PactDbEnv db) + , _txLogger :: !logger + , _txGasLogger :: !(Maybe logger) + , _txPublicData :: !PublicData + , _txSpvSupport :: !SPVSupport + , _txNetworkId :: !(Maybe NetworkId) + , _txGasPrice :: !GasPrice + , _txRequestKey :: !RequestKey + , _txGasLimit :: !Gas + , _txExecutionConfig :: !ExecutionConfig + , _txQuirkGasFee :: !(Maybe Gas) + , _txTxFailuresCounter :: !(Maybe (Counter "txFailures")) + } +makeLenses ''TransactionEnv + +-- | The transaction monad used in transaction execute. The reader +-- environment is the a Pact command env, writer is a list of json-ified +-- tx logs, and transaction state consists of a module cache, gas env, +-- and log values. +-- +newtype TransactionM logger db a = TransactionM + { _unTransactionM + :: ReaderT (TransactionEnv logger db) (StateT TransactionState IO) a + } deriving newtype + ( Functor, Applicative, Monad + , MonadReader (TransactionEnv logger db) + , MonadState TransactionState + , MonadThrow, MonadCatch, MonadMask + , MonadIO + ) + +-- | Run a 'TransactionM' computation given some initial +-- reader and state values, returning the full range of +-- results in a strict tuple +-- +runTransactionM + :: forall logger db a + . TransactionEnv logger db + -- ^ initial reader env + -> TransactionState + -- ^ initial state + -> TransactionM logger db a + -- ^ computation to execute + -> IO (T2 a TransactionState) +runTransactionM tenv txst act + = view (from _T2) + <$> runStateT (runReaderT (_unTransactionM act) tenv) txst + +-- | Run a 'TransactionM' computation given some initial +-- reader and state values, discarding the final state. +-- +evalTransactionM + :: forall logger db a + . TransactionEnv logger db + -- ^ initial reader env + -> TransactionState + -- ^ initial state + -> TransactionM logger db a + -> IO a +evalTransactionM tenv txst act + = evalStateT (runReaderT (_unTransactionM act) tenv) txst + +-- | Run a 'TransactionM' computation given some initial +-- reader and state values, returning just the final state. +-- +execTransactionM + :: forall logger db a + . TransactionEnv logger db + -- ^ initial reader env + -> TransactionState + -- ^ initial state + -> TransactionM logger db a + -> IO TransactionState +execTransactionM tenv txst act + = execStateT (runReaderT (_unTransactionM act) tenv) txst + + + + +-- | "Magic" capability 'COINBASE' used in the coin contract to +-- constrain coinbase calls. +-- +magic_COINBASE :: CapSlot SigCapability +magic_COINBASE = mkMagicCapSlot "COINBASE" + +-- | "Magic" capability 'GAS' used in the coin contract to +-- constrain gas buy/redeem calls. +-- +magic_GAS :: CapSlot SigCapability +magic_GAS = mkMagicCapSlot "GAS" + +-- | "Magic" capability 'GENESIS' used in the coin contract to +-- constrain genesis-only allocations +-- +magic_GENESIS :: CapSlot SigCapability +magic_GENESIS = mkMagicCapSlot "GENESIS" + +debitCap :: Text -> SigCapability +debitCap s = mkCoinCap "DEBIT" [PLiteral (LString s)] + +onChainErrorPrintingFor :: TxContext -> UnexpectedErrorPrinting +onChainErrorPrintingFor txCtx = + if guardCtx chainweb219Pact txCtx + then CensorsUnexpectedError + else PrintsUnexpectedError + +-- | The main entry point to executing transactions. From here, +-- 'applyCmd' assembles the command environment for a command and +-- orchestrates gas buys/redemption, and executing payloads. +-- Note that crMetaData is intentionally left unset in this path; +-- it's populated by `/poll`, when using `applyLocal`, or by the preflight +-- codepath later. +-- +applyCmd + :: (Logger logger) + => ChainwebVersion + -> logger + -- ^ Pact logger + -> Maybe logger + -- ^ Pact gas logger + -> Maybe (Counter "txFailures") + -> PactDbEnv p + -- ^ Pact db environment + -> Miner + -- ^ The miner chosen to mine the block + -> GasModel + -- ^ Gas model (pact Service config) + -> TxContext + -- ^ tx metadata and parent header + -> TxIdxInBlock + -> SPVSupport + -- ^ SPV support (validates cont proofs) + -> Command (Payload PublicMeta ParsedCode) + -- ^ command with payload to execute + -> Gas + -- ^ initial gas used + -> ModuleCache + -- ^ cached module state + -> ApplyCmdExecutionContext + -- ^ is this a local or send execution context? + -> IO (T3 (CommandResult [TxLogJson]) ModuleCache (S.Set PactWarning)) +applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxInBlock spv cmd initialGas mcache0 callCtx = do + T2 cr st <- runTransactionM cenv txst applyBuyGas + + let cache = _txCache st + warns = _txWarnings st + + pure $ T3 cr cache warns + where + stGasModel + | chainweb217Pact' = gasModel + | otherwise = _geGasModel freeGasEnv + txst = TransactionState mcache0 mempty 0 Nothing stGasModel mempty + quirkGasFee = v ^? versionQuirks . quirkGasFees . ixg cid . ix (ctxCurrentBlockHeight txCtx, txIdxInBlock) + + executionConfigNoHistory = ExecutionConfig + $ S.singleton FlagDisableHistoryInTransactionalMode + <> S.fromList + ([ FlagOldReadOnlyBehavior | isPactBackCompatV16 ] + ++ [ FlagPreserveModuleNameBug | not isModuleNameFix ] + ++ [ FlagPreserveNsModuleInstallBug | not isModuleNameFix2 ]) + <> flagsFor v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) + + cenv = TransactionEnv Transactional pdbenv logger gasLogger (ctxToPublicData txCtx) spv nid gasPrice + requestKey (fromIntegral gasLimit) executionConfigNoHistory quirkGasFee txFailuresCounter + + !requestKey = cmdToRequestKey cmd + !gasPrice = view cmdGasPrice cmd + !gasLimit = view cmdGasLimit cmd + !nid = networkIdOf cmd + currHeight = ctxCurrentBlockHeight txCtx + cid = ctxChainId txCtx + isModuleNameFix = enableModuleNameFix v cid currHeight + isModuleNameFix2 = enableModuleNameFix2 v cid currHeight + isPactBackCompatV16 = pactBackCompat_v16 v cid currHeight + chainweb213Pact' = guardCtx chainweb213Pact txCtx + chainweb217Pact' = guardCtx chainweb217Pact txCtx + chainweb219Pact' = guardCtx chainweb219Pact txCtx + chainweb223Pact' = guardCtx chainweb223Pact txCtx + allVerifiers = verifiersAt v cid currHeight + toEmptyPactError (PactError errty _ _ _) = PactError errty noInfo [] mempty + + toOldListErr pe = pe { peDoc = listErrMsg } + isOldListErr = \case + PactError EvalError _ _ doc -> "Unknown primitive" `T.isInfixOf` renderCompactText' doc + _ -> False + + redeemAllGas r = do + txGasUsed .= fromIntegral gasLimit + applyRedeem r + + applyBuyGas = + catchesPactError logger (onChainErrorPrintingFor txCtx) (buyGas txCtx cmd miner) >>= \case + Left e -> view txRequestKey >>= \rk -> + throwM $ Pact4BuyGasFailure $ Pact4GasPurchaseFailure (pact4RequestKeyToTransactionHash rk) e + Right _ -> checkTooBigTx initialGas gasLimit applyVerifiers redeemAllGas + + displayPactError e = do + r <- failTxWith e "tx failure for request key when running cmd" + redeemAllGas r + + stripPactError e = do + let e' = case callCtx of + ApplyLocal -> e + ApplySend -> toEmptyPactError e + r <- failTxWith e' "tx failure for request key when running cmd" + redeemAllGas r + + applyVerifiers = do + if chainweb223Pact' + then do + gasUsed <- use txGasUsed + let initGasRemaining = fromIntegral gasLimit - gasUsed + verifierResult <- + liftIO $ runVerifierPlugins + (ctxVersion txCtx, cid, currHeight) + logger allVerifiers initGasRemaining + (fromMaybe [] (cmd ^. cmdPayload . pVerifiers)) + case verifierResult of + Left err -> do + let errMsg = "Tx verifier error: " <> _verifierError err + cmdResult <- failTxWith + (PactError TxFailure noInfo [] (pretty errMsg)) + errMsg + redeemAllGas cmdResult + Right verifierGasRemaining -> do + txGasUsed += initGasRemaining - verifierGasRemaining + applyPayload + else applyPayload + + applyPayload = do + txGasModel .= gasModel + if chainweb217Pact' then txGasUsed += initialGas + else txGasUsed .= initialGas + + cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! runPayload cmd managedNamespacePolicy + case cr of + Left e + -- 2.19 onwards errors return on chain + | chainweb219Pact' -> displayPactError e + -- 2.17 errors were removed + | chainweb217Pact' -> stripPactError e + | chainweb213Pact' || not (isOldListErr e) -> displayPactError e + | otherwise -> do + r <- failTxWith (toOldListErr e) "tx failure for request key when running cmd" + redeemAllGas r + Right r -> applyRedeem r + + applyRedeem cr = do + txGasModel .= _geGasModel freeGasEnv + + r <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! redeemGas txCtx cmd miner + case r of + Left e -> + -- redeem gas failure is fatal (block-failing) so miner doesn't lose coins + fatal $ "tx failure for request key while redeeming gas: " <> sshow e + Right es -> do + logs <- use txLogs + + -- /local requires enriched results with metadata, while /send strips them. + -- when ?preflight=true is set, make sure that metadata occurs in result. + + let !cr' = case callCtx of + ApplySend -> set crLogs (Just logs) $ over crEvents (es ++) cr + ApplyLocal -> set crMetaData (Just $ J.toJsonViaEncode $ ctxToPublicData' txCtx) + $ set crLogs (Just logs) + $ over crEvents (es ++) cr + + return cr' + +listErrMsg :: Doc +listErrMsg = + "Unknown primitive \"list\" in determining cost of GUnreduced\nCallStack (from HasCallStack):\n error, called at src/Pact/Gas/Table.hs:209:22 in pact-4.2.0-fe223ad86f1795ba381192792f450820557e59c2926c747bf2aa6e398394bee6:Pact.Gas.Table" + +applyGenesisCmd + :: (Logger logger) + => logger + -- ^ Pact logger + -> PactDbEnv p + -- ^ Pact db environment + -> SPVSupport + -- ^ SPV support (validates cont proofs) + -> TxContext + -- ^ tx metadata + -> Command (Payload PublicMeta ParsedCode) + -- ^ command with payload to execute + -> IO (T2 (CommandResult [TxLogJson]) ModuleCache) +applyGenesisCmd logger dbEnv spv txCtx cmd = + second _txCache <$!> runTransactionM tenv txst go + where + nid = networkIdOf cmd + rk = cmdToRequestKey cmd + tenv = TransactionEnv + { _txMode = Transactional + , _txDbEnv = dbEnv + , _txLogger = logger + , _txGasLogger = Nothing + , _txPublicData = noPublicData + , _txSpvSupport = spv + , _txNetworkId = nid + , _txGasPrice = 0.0 + , _txRequestKey = rk + , _txGasLimit = 0 + , _txExecutionConfig = ExecutionConfig + $ flagsFor (ctxVersion txCtx) (ctxChainId txCtx) (view blockHeight $ ctxBlockHeader txCtx) + -- TODO this is very ugly. Genesis blocks need to install keysets + -- outside of namespaces so we need to disable Pact 4.4. It would be + -- preferable to have a flag specifically for the namespaced keyset + -- stuff so that we retain this power in genesis and upgrade txs even + -- after the block height where pact4.4 is on. + <> S.fromList [ FlagDisableInlineMemCheck, FlagDisablePact44 ] + , _txQuirkGasFee = Nothing + , _txTxFailuresCounter = Nothing + } + txst = TransactionState + { _txCache = mempty + , _txLogs = mempty + , _txGasUsed = 0 + , _txGasId = Nothing + , _txGasModel = _geGasModel freeGasEnv + , _txWarnings = mempty + } + + interp = initStateInterpreter + $ initCapabilities [magic_GENESIS, magic_COINBASE] + + go = do + -- TODO: fix with version recordification so that this matches the flags at genesis heights. + cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! runGenesis cmd permissiveNamespacePolicy interp + case cr of + Left e -> fatal $ "Genesis command failed: " <> sshow e + Right r -> r <$ debug "successful genesis tx for request key" + +flagsFor :: ChainwebVersion -> V.ChainId -> BlockHeight -> S.Set ExecutionFlag +flagsFor v cid bh = S.fromList $ concat + [ enablePactEvents' v cid bh + , enablePact40 v cid bh + , enablePact42 v cid bh + , enforceKeysetFormats' v cid bh + , enablePactModuleMemcheck v cid bh + , enablePact43 v cid bh + , enablePact431 v cid bh + , enablePact44 v cid bh + , enablePact45 v cid bh + , enableNewTrans v cid bh + , enablePact46 v cid bh + , enablePact47 v cid bh + , enablePact48 v cid bh + , disableReturnRTC v cid bh + , enablePact49 v cid bh + , enablePact410 v cid bh + , enablePact411 v cid bh + , enablePact412 v cid bh + ] + +applyCoinbase + :: (Logger logger) + => ChainwebVersion + -> logger + -- ^ Pact logger + -> PactDbEnv p + -- ^ Pact db environment + -> ParsedDecimal + -- ^ Miner reward + -> TxContext + -- ^ tx metadata and parent header + -> EnforceCoinbaseFailure + -- ^ enforce coinbase failure or not + -> CoinbaseUsePrecompiled + -- ^ always enable precompilation + -> ModuleCache + -> IO (T2 (CommandResult [TxLogJson]) (Maybe ModuleCache)) +applyCoinbase v logger dbEnv reward@(ParsedDecimal d) txCtx + (EnforceCoinbaseFailure enfCBFailure) (CoinbaseUsePrecompiled enablePC) mc + | fork1_3InEffect || enablePC = do + when chainweb213Pact' $ enforceKeyFormats + (\k -> throwM $ CoinbaseFailure $ Pact4CoinbaseFailure $ "Invalid miner key: " <> sshow k) + (validKeyFormats v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx)) + mk + let (cterm, cexec) = mkCoinbaseTerm mid mks reward + interp = Interpreter $ \_ -> do put initState; fmap pure (eval cterm) + + go interp cexec + | otherwise = do + cexec <- mkCoinbaseCmd mid mks reward + let interp = initStateInterpreter initState + go interp cexec + where + (Miner mid mks@(MinerKeys mk)) = _tcMiner txCtx + chainweb213Pact' = chainweb213Pact v cid bh + fork1_3InEffect = vuln797Fix v cid bh + throwCritical = fork1_3InEffect || enfCBFailure + ec = ExecutionConfig $ S.delete FlagEnforceKeyFormats $ fold + [ S.singleton FlagDisableModuleInstall + , S.singleton FlagDisableHistoryInTransactionalMode + , flagsFor v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) + ] + tenv = TransactionEnv Transactional dbEnv logger Nothing (ctxToPublicData txCtx) noSPVSupport + Nothing 0.0 rk 0 ec Nothing Nothing + txst = TransactionState mc mempty 0 Nothing (_geGasModel freeGasEnv) mempty + initState = setModuleCache mc $ initCapabilities [magic_COINBASE] + rk = RequestKey chash + parent = _tcParentHeader txCtx + + bh = ctxCurrentBlockHeight txCtx + cid = Chainweb._chainId parent + chash = Pact.Hash $ SB.toShort $ encodeToByteString $ view blockHash $ _parentHeader parent + -- NOTE: it holds that @ _pdPrevBlockHash pd == encode _blockHash@ + -- NOTE: chash includes the /quoted/ text of the parent header. + + go interp cexec = evalTransactionM tenv txst $! do + cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $ + applyExec' 0 interp cexec [] [] chash managedNamespacePolicy + + case cr of + Left e + | throwCritical -> throwM $ CoinbaseFailure $ Pact4CoinbaseFailure $ sshow e + | otherwise -> (`T2` Nothing) <$> failTxWith e "coinbase tx failure" + Right er -> do + debug + $! "successful coinbase of " + <> T.take 18 (sshow d) + <> " to " + <> sshow mid + + upgradedModuleCache <- applyUpgrades v cid bh + + logs <- use txLogs + + return $! T2 + CommandResult + { _crReqKey = rk + , _crTxId = _erTxId er + , _crResult = PactResult (Right (last (_erOutput er))) + , _crGas = _erGas er + , _crLogs = Just logs + , _crContinuation = _erExec er + , _crMetaData = Nothing + , _crEvents = _erEvents er + } + upgradedModuleCache + +applyLocal + :: (Logger logger) + => logger + -- ^ Pact logger + -> Maybe logger + -- ^ Pact gas logger + -> PactDbEnv p + -- ^ Pact db environment + -> GasModel + -- ^ Gas model (pact Service config) + -> TxContext + -- ^ tx metadata and parent header + -> SPVSupport + -- ^ SPV support (validates cont proofs) + -> Transaction + -- ^ command with payload to execute + -> ModuleCache + -> ExecutionConfig + -> IO (CommandResult [TxLogJson]) +applyLocal logger gasLogger dbEnv gasModel txCtx spv cmdIn mc execConfig = + evalTransactionM tenv txst go + where + !cmd = payloadObj <$> cmdIn `using` traverse rseq + !rk = cmdToRequestKey cmd + !nid = networkIdOf cmd + !chash = toUntypedHash $ _cmdHash cmd + !signers = _pSigners $ _cmdPayload cmd + !verifiers = fromMaybe [] $ _pVerifiers $ _cmdPayload cmd + !gasPrice = view cmdGasPrice cmd + !gasLimit = view cmdGasLimit cmd + tenv = TransactionEnv Local dbEnv logger gasLogger (ctxToPublicData txCtx) spv nid gasPrice + rk (fromIntegral gasLimit) execConfig Nothing Nothing + txst = TransactionState mc mempty 0 Nothing gasModel mempty + gas0 = initialGasOf (_cmdPayload cmdIn) + currHeight = ctxCurrentBlockHeight txCtx + cid = V._chainId txCtx + v = _chainwebVersion txCtx + allVerifiers = verifiersAt v cid currHeight + -- Note [Throw out verifier proofs eagerly] + !verifiersWithNoProof = + (fmap . fmap) (\_ -> ()) verifiers + `using` (traverse . traverse) rseq + + applyVerifiers m = do + let initGasRemaining = fromIntegral gasLimit - gas0 + verifierResult <- + liftIO $ runVerifierPlugins + (v, cid, currHeight) logger allVerifiers initGasRemaining + (fromMaybe [] $ cmd ^. cmdPayload . pVerifiers) + case verifierResult of + Left err -> do + let errMsg = "Tx verifier error: " <> _verifierError err + failTxWith + (PactError TxFailure noInfo [] (pretty errMsg)) + errMsg + Right verifierGasRemaining -> do + let gas1 = (initGasRemaining - verifierGasRemaining) + gas0 + applyPayload gas1 m + + applyPayload gas1 m = do + interp <- gasInterpreter gas1 + cr <- catchesPactError logger PrintsUnexpectedError $! case m of + Exec em -> + applyExec gas1 interp em signers verifiersWithNoProof chash managedNamespacePolicy + Continuation cm -> + applyContinuation gas1 interp cm signers chash managedNamespacePolicy + + case cr of + Left e -> failTxWith e "applyLocal" + Right r -> return $! r { _crMetaData = Just (J.toJsonViaEncode $ ctxToPublicData' txCtx) } + + go = checkTooBigTx gas0 gasLimit (applyVerifiers $ _pPayload $ _cmdPayload cmd) return + +readInitModules + :: forall logger tbl. (Logger logger) + => PactBlockM logger tbl ModuleCache +readInitModules = do + logger <- view (psServiceEnv . psLogger) + dbEnv <- view (psBlockDbEnv . to _cpPactDbEnv) + txCtx <- getTxContext noMiner noPublicMeta + + -- guarding chainweb 2.17 here to allow for + -- cache purging everything but coin and its + -- dependencies. + let + chainweb217Pact' = guardCtx chainweb217Pact txCtx + chainweb224Pact' = guardCtx chainweb224Pact txCtx + + parent = _tcParentHeader txCtx + v = ctxVersion txCtx + cid = ctxChainId txCtx + h = view blockHeight (_parentHeader parent) + 1 + rk = RequestKey chash + nid = Nothing + chash = pactInitialHash + tenv = TransactionEnv Local dbEnv logger Nothing (ctxToPublicData txCtx) noSPVSupport nid 0.0 + rk 0 emptyExecutionConfig Nothing Nothing + txst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty + interp = defaultInterpreter + die msg = internalError $ "readInitModules: " <> msg + mkCmd = buildExecParsedCode (pact4ParserVersion v cid h) Nothing + run :: Text -> ExecMsg ParsedCode -> TransactionM logger p PactValue + run msg cmd = do + er <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! + applyExec' 0 interp cmd [] [] chash permissiveNamespacePolicy + case er of + Left e -> die $ msg <> ": failed: " <> sshow e + Right r -> case _erOutput r of + [] -> die $ msg <> ": empty result" + (o:_) -> return o + + + go :: TransactionM logger p ModuleCache + go = do + + -- see if fungible-v2 is there + checkCmd <- liftIO $ mkCmd "(contains \"fungible-v2\" (list-modules))" + checkFv2 <- run "check fungible-v2" checkCmd + hasFv2 <- case checkFv2 of + (PLiteral (LBool b)) -> return b + t -> die $ "got non-bool result from module read: " <> T.pack (showPretty t) + + -- see if fungible-xchain-v1 is there + checkCmdx <- liftIO $ mkCmd "(contains \"fungible-xchain-v1\" (list-modules))" + checkFx <- run "check fungible-xchain-v1" checkCmdx + hasFx <- case checkFx of + (PLiteral (LBool b)) -> return b + t -> die $ "got non-bool result from module read: " <> T.pack (showPretty t) + + -- load modules by referencing members + refModsCmd <- liftIO $ mkCmd $ T.intercalate " " $ + [ "coin.MINIMUM_PRECISION" + , "ns.GUARD_SUCCESS" + , "(use gas-payer-v1)" + , "fungible-v1.account-details"] ++ + [ "fungible-v2.account-details" | hasFv2 ] ++ + [ "(let ((m:module{fungible-xchain-v1} coin)) 1)" | hasFx ] + void $ run "load modules" refModsCmd + + -- return loaded cache + use txCache + + -- Only load coin and its dependencies for chainweb >=2.17 + -- Note: no need to check if things are there, because this + -- requires a block height that witnesses the invariant. + -- + -- if this changes, we must change the filter in 'updateInitCache' + goCw217 :: TransactionM logger p ModuleCache + goCw217 = do + coinDepCmd <- liftIO $ mkCmd "coin.MINIMUM_PRECISION" + void $ run "load modules" coinDepCmd + use txCache + + if + | chainweb224Pact' -> pure mempty + | chainweb217Pact' -> liftIO $ evalTransactionM tenv txst goCw217 + | otherwise -> liftIO $ evalTransactionM tenv txst go + +-- | Apply (forking) upgrade transactions and module cache updates +-- at a particular blockheight. +-- +-- This is the place where we consistently /introduce/ new transactions +-- into the blockchain along with module cache updates. The only other +-- places are Pact Service startup and the +-- empty-module-cache-after-initial-rewind case caught in 'execTransactions' +-- which both hit the database. +-- +applyUpgrades + :: forall logger p + . (Logger logger) + => ChainwebVersion + -> Chainweb.ChainId + -> BlockHeight + -> TransactionM logger p (Maybe ModuleCache) +applyUpgrades v cid height + | Just Pact4Upgrade{_pact4UpgradeTransactions = txs, _legacyUpgradeIsPrecocious = isPrecocious} <- + v ^? versionUpgrades . atChain cid . ix height = applyUpgrade txs isPrecocious + | Just Pact5Upgrade{} <- + v ^? versionUpgrades . atChain cid . ix height = error "Expected Pact 4 upgrade, got Pact 5" + | cleanModuleCache v cid height = filterModuleCache + | otherwise = return Nothing + where + installCoinModuleAdmin = set (evalCapabilities . capModuleAdmin) $ S.singleton (ModuleName "coin" Nothing) + + filterModuleCache = do + mc <- use txCache + pure $ Just $ filterModuleCacheByKey (== "coin") mc + + applyUpgrade :: [Transaction] -> Bool -> TransactionM logger p (Maybe ModuleCache) + applyUpgrade upg isPrecocious = do + infoLog "Applying upgrade!" + let payloads = map (fmap payloadObj) upg + + -- + -- In order to prime the module cache with all new modules for subsequent + -- blocks, the caches from each tx are collected and the union of all + -- those caches is returned. The calling code adds this new cache to the + -- init cache in the pact service state (_psInitCache). + -- + + let flags = flagsFor v cid (if isPrecocious then height + 1 else height) + caches <- local + (txExecutionConfig .~ ExecutionConfig flags) + (mapM applyTx payloads) + return $ Just $ mconcat $ reverse caches + + interp = initStateInterpreter + $ installCoinModuleAdmin + $ initCapabilities [mkMagicCapSlot "REMEDIATE"] + + applyTx tx = do + infoLog $ "Running upgrade tx " <> sshow (_cmdHash tx) + + tryAllSynchronous (runGenesis tx permissiveNamespacePolicy interp) >>= \case + Right _ -> use txCache + Left e -> do + logError $ "Upgrade transaction failed! " <> sshow e + throwM e + +failTxWith + :: (Logger logger) + => PactError + -> Text + -> TransactionM logger p (CommandResult [TxLogJson]) +failTxWith err msg = do + logs <- use txLogs + gas <- view txGasLimit -- error means all gas was charged + rk <- view txRequestKey + l <- view txLogger + + liftIO $ logFunction l L.Debug + (Pact4TxFailureLog rk err msg) + liftIO . traverse_ inc + =<< view txTxFailuresCounter + + return $! CommandResult rk Nothing (PactResult (Left err)) + gas (Just logs) Nothing Nothing [] + +runPayload + :: (Logger logger) + => Command (Payload PublicMeta ParsedCode) + -> NamespacePolicy + -> TransactionM logger p (CommandResult [TxLogJson]) +runPayload cmd nsp = do + g0 <- use txGasUsed + interp <- gasInterpreter g0 + + -- Note [Throw out verifier proofs eagerly] + let !verifiersWithNoProof = + (fmap . fmap) (\_ -> ()) verifiers + `using` (traverse . traverse) rseq + + case payload of + Exec pm -> + applyExec g0 interp pm signers verifiersWithNoProof chash nsp + Continuation ym -> + applyContinuation g0 interp ym signers chash nsp + + + where + verifiers = fromMaybe [] $ _pVerifiers $ _cmdPayload cmd + signers = _pSigners $ _cmdPayload cmd + chash = toUntypedHash $ _cmdHash cmd + payload = _pPayload $ _cmdPayload cmd + +-- | Run genesis transaction payloads with custom interpreter +-- +runGenesis + :: (Logger logger) + => Command (Payload PublicMeta ParsedCode) + -> NamespacePolicy + -> Interpreter p + -> TransactionM logger p (CommandResult [TxLogJson]) +runGenesis cmd nsp interp = case payload of + Exec pm -> + applyExec 0 interp pm signers verifiersWithNoProof chash nsp + Continuation ym -> + applyContinuation 0 interp ym signers chash nsp + where + signers = _pSigners $ _cmdPayload cmd + verifiers = fromMaybe [] $ _pVerifiers $ _cmdPayload cmd + -- Note [Throw out verifier proofs eagerly] + !verifiersWithNoProof = + (fmap . fmap) (\_ -> ()) verifiers + `using` (traverse . traverse) rseq + chash = toUntypedHash $ _cmdHash cmd + payload = _pPayload $ _cmdPayload cmd + +-- | Execute an 'ExecMsg' and Return the result with module cache +-- +applyExec + :: (Logger logger) + => Gas + -> Interpreter p + -> ExecMsg ParsedCode + -> [Signer] + -> [Verifier ()] + -> Hash + -> NamespacePolicy + -> TransactionM logger p (CommandResult [TxLogJson]) +applyExec initialGas interp em senderSigs verifiers hsh nsp = do + EvalResult{..} <- applyExec' initialGas interp em senderSigs verifiers hsh nsp + for_ _erLogGas $ \gl -> gasLog $ "gas logs: " <> sshow gl + !logs <- use txLogs + !rk <- view txRequestKey + + -- concat tx warnings with eval warnings + modify' $ txWarnings <>~ _erWarnings + + -- applyExec enforces non-empty expression set so `last` ok + -- forcing it here for lazy errors. TODO NFData the Pacts + let !lastResult = force $ last _erOutput + return $ CommandResult rk _erTxId (PactResult (Right lastResult)) + _erGas (Just logs) _erExec Nothing _erEvents + +-- | Variation on 'applyExec' that returns 'EvalResult' as opposed to +-- wrapping it up in a JSON result. +-- +applyExec' + :: (Logger logger) + => Gas + -> Interpreter p + -> ExecMsg ParsedCode + -> [Signer] + -> [Verifier ()] + -> Hash + -> NamespacePolicy + -> TransactionM logger p EvalResult +applyExec' initialGas interp (ExecMsg parsedCode execData) senderSigs verifiersWithNoProof hsh nsp + | null (_pcExps parsedCode) = throwCmdEx "No expressions found" + | otherwise = do + + eenv <- mkEvalEnv nsp (MsgData execData Nothing hsh senderSigs verifiersWithNoProof) + + setEnvGas initialGas eenv + + evalResult <- liftIO $! evalExec interp eenv parsedCode + -- if we specified this transaction's gas fee manually as a "quirk", + -- here we set the result's gas fee to agree with that + quirkGasFee <- view txQuirkGasFee + let quirkedEvalResult = case quirkGasFee of + Nothing -> evalResult + Just fee -> evalResult { _erGas = fee } + + for_ (_erExec quirkedEvalResult) $ \pe -> debug + $ "applyExec: new pact added: " + <> sshow (_pePactId pe, _peStep pe, _peYield pe, _peExecuted pe) + + -- set log + cache updates + used gas + setTxResultState quirkedEvalResult + + return quirkedEvalResult + +enablePactEvents' :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePactEvents' v cid bh = [FlagDisablePactEvents | not (enablePactEvents v cid bh)] + +enforceKeysetFormats' :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enforceKeysetFormats' v cid bh = [FlagEnforceKeyFormats | enforceKeysetFormats v cid bh] + +enablePact40 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact40 v cid bh = [FlagDisablePact40 | not (pact4Coin3 v cid bh)] + +enablePact42 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact42 v cid bh = [FlagDisablePact42 | not (pact42 v cid bh)] + +enablePactModuleMemcheck :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePactModuleMemcheck v cid bh = [FlagDisableInlineMemCheck | not (chainweb213Pact v cid bh)] + +enablePact43 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact43 v cid bh = [FlagDisablePact43 | not (chainweb214Pact v cid bh)] + +enablePact431 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact431 v cid bh = [FlagDisablePact431 | not (chainweb215Pact v cid bh)] + +enablePact44 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact44 v cid bh = [FlagDisablePact44 | not (chainweb216Pact v cid bh)] + +enablePact45 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact45 v cid bh = [FlagDisablePact45 | not (chainweb217Pact v cid bh)] + +enableNewTrans :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enableNewTrans v cid bh = [FlagDisableNewTrans | not (pact44NewTrans v cid bh)] + +enablePact46 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact46 v cid bh = [FlagDisablePact46 | not (chainweb218Pact v cid bh)] + +enablePact47 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact47 v cid bh = [FlagDisablePact47 | not (chainweb219Pact v cid bh)] + +enablePact48 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact48 v cid bh = [FlagDisablePact48 | not (chainweb220Pact v cid bh)] + +enablePact49 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact49 v cid bh = [FlagDisablePact49 | not (chainweb221Pact v cid bh)] + +enablePact410 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact410 v cid bh = [FlagDisablePact410 | not (chainweb222Pact v cid bh)] + +enablePact411 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact411 v cid bh = [FlagDisablePact411 | not (chainweb223Pact v cid bh)] + +enablePact412 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact412 v cid bh = [FlagDisablePact412 | not (chainweb224Pact v cid bh)] + +-- | Even though this is not forking, abstracting for future shutoffs +disableReturnRTC :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] +disableReturnRTC _v _cid _bh = [FlagDisableRuntimeReturnTypeChecking] + +-- | Execute a 'ContMsg' and return the command result and module cache +-- +applyContinuation + :: (Logger logger) + => Gas + -> Interpreter p + -> ContMsg + -> [Signer] + -> Hash + -> NamespacePolicy + -> TransactionM logger p (CommandResult [TxLogJson]) +applyContinuation initialGas interp cm senderSigs hsh nsp = do + EvalResult{..} <- applyContinuation' initialGas interp cm senderSigs hsh nsp + for_ _erLogGas $ \gl -> gasLog $ "gas logs: " <> sshow gl + logs <- use txLogs + rk <- view txRequestKey + + -- set tx warnings to eval warnings + txWarnings <>= _erWarnings + + -- last safe here because cont msg is guaranteed one exp + return $! CommandResult rk _erTxId (PactResult (Right (last _erOutput))) + _erGas (Just logs) _erExec Nothing _erEvents + + +setEnvGas :: Gas -> EvalEnv e -> TransactionM logger p () +setEnvGas initialGas = liftIO . views eeGas (`writeIORef` gasToMilliGas initialGas) + +-- | Execute a 'ContMsg' and return just eval result, not wrapped in a +-- 'CommandResult' wrapper +-- +applyContinuation' + :: Gas + -> Interpreter p + -> ContMsg + -> [Signer] + -> Hash + -> NamespacePolicy + -> TransactionM logger p EvalResult +applyContinuation' initialGas interp cm@(ContMsg pid s rb d _) senderSigs hsh nsp = do + + eenv <- mkEvalEnv nsp (MsgData d pactStep hsh senderSigs []) + + setEnvGas initialGas eenv + + evalResult <- liftIO $! evalContinuation interp eenv cm + -- if we specified this transaction's gas fee manually as a "quirk", + -- here we set the result's gas fee to agree with that + quirkGasFee <- view txQuirkGasFee + let quirkedEvalResult = case quirkGasFee of + Nothing -> evalResult + Just fee -> evalResult { _erGas = fee } + + setTxResultState quirkedEvalResult + + return quirkedEvalResult + where + pactStep = Just $ PactStep s rb pid Nothing + +-- | Build and execute 'coin.buygas' command from miner info and user command +-- info (see 'TransactionExec.applyCmd') +-- +-- see: 'pact/coin-contract/coin.pact#fund-tx' +-- +buyGas :: (Logger logger) => TxContext -> Command (Payload PublicMeta ParsedCode) -> Miner -> TransactionM logger p () +buyGas txCtx cmd (Miner mid mks) = go + where + isChainweb224Pact = guardCtx chainweb224Pact txCtx + sender = view (cmdPayload . pMeta . pmSender) cmd + + initState mc logGas = + set evalLogGas (guard logGas >> Just [("GBuyGas",0)]) $ setModuleCache mc $ initCapabilities [magic_GAS] + + run input = do + (findPayer txCtx cmd) >>= \r -> case r of + Nothing -> input + Just withPayerCap -> withPayerCap input + + (Hash chash) = toUntypedHash (_cmdHash cmd) + bgHash = Hash (chash <> "-buygas") + + go = do + mcache <- use txCache + supply <- gasSupplyOf <$> view txGasLimit <*> view txGasPrice + logGas <- isJust <$> view txGasLogger + + let (buyGasTerm, buyGasCmd) = + -- post-chainweb 2.24, we call buy-gas directly rather than + -- going through fund-tx which is a defpact. + if isChainweb224Pact + then mkBuyGasTerm sender supply + else mkFundTxTerm mid mks sender supply + -- I don't recall why exactly, but we set up an interpreter + -- that ignores its argument and instead executes a term + -- of our choice. we do the same to redeem gas. + interp mc = Interpreter $ \_input -> + put (initState mc logGas) >> run (pure <$> eval buyGasTerm) + + let + gasCapName = QualifiedName (ModuleName "coin" Nothing) "GAS" noInfo + signedForGas signer = + any (\sc -> _scName sc == gasCapName) (_siCapList signer) + addDebit signer + | signedForGas signer = + signer & siCapList %~ (debitCap sender:) + | otherwise = signer + addDebitToSigners = + fmap addDebit + + -- no verifiers are allowed in buy gas + -- quirked gas is not used either + result <- locally txQuirkGasFee (const Nothing) $ + applyExec' 0 (interp mcache) buyGasCmd + (addDebitToSigners $ _pSigners $ _cmdPayload cmd) [] bgHash managedNamespacePolicy + + case _erExec result of + Nothing + | isChainweb224Pact -> + return () + | otherwise -> + -- should never occur pre-chainweb 2.24: + -- would mean coin.fund-tx is not a pact + fatal "buyGas: Internal error - empty continuation before 2.24 fork" + Just pe + | isChainweb224Pact -> + fatal "buyGas: Internal error - continuation found after 2.24 fork" + | otherwise -> + void $! txGasId .= (Just $! GasId (_pePactId pe)) + +findPayer + :: TxContext + -> Command (Payload PublicMeta ParsedCode) + -> Eval e (Maybe (Eval e [Term Name] -> Eval e [Term Name])) +findPayer txCtx cmd = runMaybeT $ do + (!m,!qn,!as) <- MaybeT findPayerCap + pMod <- MaybeT $ lookupModule qn m + capRef <- MaybeT $ return $ lookupIfaceModRef qn pMod + return $ runCap (getInfo qn) capRef as + where + setEnvMsgBody v e = set eeMsgBody v e + + findPayerCap :: Eval e (Maybe (ModuleName,QualifiedName,[PactValue])) + findPayerCap = preview $ eeMsgSigs . folded . folded . to sigPayerCap . _Just + + sigPayerCap (SigCapability q@(QualifiedName m n _) as) + | n == "GAS_PAYER" = Just (m,q,as) + sigPayerCap _ = Nothing + + gasPayerIface = ModuleName "gas-payer-v1" Nothing + + lookupIfaceModRef (QualifiedName _ n _) (ModuleData (MDModule Module{..}) refs _) + | gasPayerIface `elem` _mInterfaces = SHM.lookup n refs + lookupIfaceModRef _ _ = Nothing + + mkApp i r as = App (TVar r i) (map (liftTerm . fromPactValue) as) i + + runCap i capRef as input = do + let msgBody = enrichedMsgBody cmd + enrichMsgBody | guardCtx pactBackCompat_v16 txCtx = id + | otherwise = setEnvMsgBody (toLegacyJson msgBody) + ar <- local enrichMsgBody $ do + (cap, capDef, args) <- appToCap $ mkApp i capRef as + evalCap i CapCallStack False (cap, capDef, args, i) + + case ar of + NewlyAcquired -> do + r <- input + popCapStack (const (return ())) + return r + _ -> evalError' i "Internal error, GAS_PAYER already acquired" + +enrichedMsgBody :: Command (Payload PublicMeta ParsedCode) -> Value +enrichedMsgBody cmd = case (_pPayload $ _cmdPayload cmd) of + Exec (ExecMsg (ParsedCode _ exps) userData) -> + object [ "tx-type" A..= ( "exec" :: Text) + , "exec-code" A..= map renderCompactText exps + , "exec-user-data" A..= pactFriendlyUserData (_getLegacyValue userData) ] + Continuation (ContMsg pid step isRollback userData proof) -> + object [ "tx-type" A..= ("cont" :: Text) + , "cont-pact-id" A..= toJsonViaEncode pid + , "cont-step" A..= toJsonViaEncode (LInteger $ toInteger step) + , "cont-is-rollback" A..= toJsonViaEncode (LBool isRollback) + , "cont-user-data" A..= pactFriendlyUserData (_getLegacyValue userData) + , "cont-has-proof" A..= toJsonViaEncode (isJust proof) + ] + where + pactFriendlyUserData Null = object [] + pactFriendlyUserData v = v + +-- | Build and execute 'coin.redeem-gas' command from miner info and previous +-- command results (see 'TransactionExec.applyCmd') +-- +-- see: 'pact/coin-contract/coin.pact#fund-tx' +-- +redeemGas :: (Logger logger) => TxContext -> Command (Payload PublicMeta ParsedCode) -> Miner -> TransactionM logger p [PactEvent] +redeemGas txCtx cmd (Miner mid mks) = do + mcache <- use txCache + let sender = view (cmdPayload . pMeta . pmSender) cmd + fee <- gasSupplyOf <$> use txGasUsed <*> view txGasPrice + -- if we're past chainweb 2.24, we don't use defpacts for gas + if guardCtx chainweb224Pact txCtx + then do + total <- gasSupplyOf <$> view txGasLimit <*> view txGasPrice + let (redeemGasTerm, redeemGasCmd) = + mkRedeemGasTerm mid mks sender total fee + -- I don't recall why exactly, but we set up an interpreter + -- that ignores its argument and instead executes a term + -- of our choice. we do the same to buy gas. + interp = Interpreter $ \_input -> do + -- we don't log gas when redeeming, because nobody can pay for it + put (initCapabilities [magic_GAS] & setModuleCache mcache) + fmap List.singleton (eval redeemGasTerm) + (Hash chash) = toUntypedHash (_cmdHash cmd) + rgHash = Hash (chash <> "-redeemgas") + + locally txQuirkGasFee (const Nothing) $ _erEvents <$> + applyExec' 0 interp redeemGasCmd + (_pSigners $ _cmdPayload cmd) + [] + rgHash + managedNamespacePolicy + else do + GasId gid <- use txGasId >>= \case + Nothing -> fatal $! "redeemGas: no gas id in scope for gas refunds" + Just g -> return g + let redeemGasCmd = + ContMsg gid 1 False (toLegacyJson $ object [ "fee" A..= toJsonViaEncode fee ]) Nothing + + fmap _crEvents $ locally txQuirkGasFee (const Nothing) $ + applyContinuation 0 (initState mcache) redeemGasCmd + (_pSigners $ _cmdPayload cmd) (toUntypedHash $ _cmdHash cmd) + managedNamespacePolicy + + where + initState mc = initStateInterpreter + $ setModuleCache mc + $ initCapabilities [magic_GAS] + + +-- ---------------------------------------------------------------------------- -- +-- Utilities + +-- | Initialize a fresh eval state with magic capabilities. +-- This is the way we inject the correct guards into the environment +-- during Pact code execution +-- +initCapabilities :: [CapSlot SigCapability] -> EvalState +initCapabilities cs = set (evalCapabilities . capStack) cs emptyEvalState +{-# INLINABLE initCapabilities #-} + +initStateInterpreter :: EvalState -> Interpreter e +initStateInterpreter s = Interpreter (put s >>) + +-- | Check whether the cost of running a tx is more than the allowed +-- gas limit and do some action depending on the outcome +-- +checkTooBigTx + :: (Logger logger) + => Gas + -> GasLimit + -> TransactionM logger p (CommandResult [TxLogJson]) + -> (CommandResult [TxLogJson] -> TransactionM logger p (CommandResult [TxLogJson])) + -> TransactionM logger p (CommandResult [TxLogJson]) +checkTooBigTx initialGas gasLimit next onFail + | initialGas >= fromIntegral gasLimit = do + + let !pe = PactError GasError noInfo [] + $ "Tx too big (" <> pretty initialGas <> "), limit " + <> pretty gasLimit + + r <- failTxWith pe "Tx too big" + onFail r + | otherwise = next + +gasInterpreter :: Gas -> TransactionM logger db (Interpreter p) +gasInterpreter g = do + mc <- use txCache + logGas <- isJust <$> view txGasLogger + return $ initStateInterpreter + $ set evalLogGas (guard logGas >> Just [("GTxSize",g)]) -- enables gas logging + $ setModuleCache mc emptyEvalState + + +-- | Initial gas charged for transaction size +-- ignoring the size of a continuation proof, if present +-- +initialGasOf :: PayloadWithText PublicMeta ParsedCode -> Gas +initialGasOf payload = gasFee + where + feePerByte :: Rational = 0.01 + + contProofSize = + case _pPayload (payloadObj payload) of + Continuation (ContMsg _ _ _ _ (Just (ContProof p))) -> B.length p + _ -> 0 + txSize = SB.length (payloadBytes payload) - contProofSize + + costPerByte = fromIntegral txSize * feePerByte + sizePenalty = txSizeAccelerationFee costPerByte + gasFee = ceiling (costPerByte + sizePenalty) +{-# INLINE initialGasOf #-} + +txSizeAccelerationFee :: Rational -> Rational +txSizeAccelerationFee costPerByte = total + where + total = (costPerByte / bytePenalty) ^ power + bytePenalty = 512 + power :: Integer = 7 +{-# INLINE txSizeAccelerationFee #-} + +-- | Set the module cache of a pact 'EvalState' +-- +setModuleCache + :: ModuleCache + -> EvalState + -> EvalState +setModuleCache mcache es = + let allDeps = foldMap (allModuleExports . fst) $ _getModuleCache mcache + in set (evalRefs . rsQualifiedDeps) allDeps $ set (evalRefs . rsLoadedModules) c es + where + c = moduleCacheToHashMap mcache +{-# INLINE setModuleCache #-} + +-- | Set tx result state +-- +setTxResultState :: EvalResult -> TransactionM logger db () +setTxResultState er = do + txLogs <>= _erLogs er + txCache .= moduleCacheFromHashMap (_erLoadedModules er) + txGasUsed .= _erGas er +{-# INLINE setTxResultState #-} + +-- | Make an 'EvalEnv' given a tx env + state +-- +mkEvalEnv + :: NamespacePolicy + -> MsgData + -> TransactionM logger db (EvalEnv db) +mkEvalEnv nsp msg = do + tenv <- ask + genv <- GasEnv + <$> view (txGasLimit . to (MilliGasLimit . gasToMilliGas)) + <*> view txGasPrice + <*> use txGasModel + fmap (set eeSigCapBypass txCapBypass) + $ liftIO $ setupEnv tenv genv + where + setupEnv tenv genv = setupEvalEnv (_txDbEnv tenv) Nothing (_txMode tenv) + msg (versionedNativesRefStore (_txExecutionConfig tenv)) genv + nsp (_txSpvSupport tenv) (_txPublicData tenv) (_txExecutionConfig tenv) + txCapBypass = + M.fromList + [ (wizaDebit, (wizaBypass, wizaMH)) + , (skdxDebit, (kdxBypass, skdxMH)) + , (collectGallinasMarket, (collectGallinasBypass, collectGallinasMH)) + , (marmaladeGuardPolicyMint, (marmaladeBypass, marmaladeGuardPolicyMH)) + ] + where + -- wiza code + wizaDebit = QualifiedName "free.wiza" "DEBIT" noInfo + wizaMH = unsafeModuleHashFromB64Text "8b4USA1ZNVoLYRT1LBear4YKt3GB2_bl0AghZU8QxjI" + wizEquipmentOwner = QualifiedName "free.wiz-equipment" "OWNER" noInfo + wizEquipmentAcctGuard = QualifiedName "free.wiz-equipment" "ACCOUNT_GUARD" noInfo + wizArenaAcctGuard = QualifiedName "free.wiz-arena" "ACCOUNT_GUARD" noInfo + wizArenaOwner = QualifiedName "free.wiz-arena" "OWNER" noInfo + wizaTransfer = QualifiedName "free.wiza" "TRANSFER" noInfo + + wizaBypass granted sigCaps = + let debits = filter ((== wizaDebit) . _scName) $ S.toList granted + in all (\c -> any (match c) sigCaps) debits + where + match prov sigCap = fromMaybe False $ do + guard $ _scName sigCap `elem` wizaBypassList + sender <- preview _head (_scArgs prov) + (== sender) <$> preview _head (_scArgs sigCap) + wizaBypassList = + [ wizArenaOwner + , wizEquipmentOwner + , wizaTransfer + , wizEquipmentAcctGuard + , wizArenaAcctGuard] + -- kaddex code + skdxDebit = QualifiedName "kaddex.skdx" "DEBIT" noInfo + skdxMH = unsafeModuleHashFromB64Text "g90VWmbKj87GkMkGs8uW947kh_Wg8JdQowa8rO_vZ1M" + kdxUnstake = QualifiedName "kaddex.staking" "UNSTAKE" noInfo + + kdxBypass granted sigCaps = + let debits = filter ((== skdxDebit) . _scName) $ S.toList granted + in all (\c -> S.member (SigCapability kdxUnstake (_scArgs c)) sigCaps) debits + -- Collect-gallinas code + collectGallinasMH = unsafeModuleHashFromB64Text "x3BLGdidqSjUQy5q3MorGco9mBDpoVTh_Yoagzu0hls" + collectGallinasMarket = QualifiedName "free.collect-gallinas" "MARKET" noInfo + collectGallinasAcctGuard = QualifiedName "free.collect-gallinas" "ACCOUNT_GUARD" noInfo + + collectGallinasBypass granted sigCaps = fromMaybe False $ do + let mkt = filter ((== collectGallinasMarket) . _scName) $ S.toList granted + let matchingGuard provided toMatch = _scName toMatch == collectGallinasAcctGuard && (_scArgs provided == _scArgs toMatch) + pure $ all (\c -> any (matchingGuard c) sigCaps) mkt + -- marmalade code + marmaladeGuardPolicyMH = unsafeModuleHashFromB64Text "LB5sRKx8jN3FP9ZK-rxDK7Bqh0gyznprzS8L4jYlT5o" + marmaladeGuardPolicyMint = QualifiedName "marmalade-v2.guard-policy-v1" "MINT" noInfo + marmaladeLedgerMint = QualifiedName "marmalade-v2.ledger" "MINT-CALL" noInfo + + marmaladeBypass granted sigCaps = fromMaybe False $ do + let mkt = filter ((== marmaladeGuardPolicyMint) . _scName) $ S.toList granted + let matchingGuard provided toMatch = _scName toMatch == marmaladeLedgerMint && (_scArgs provided == _scArgs toMatch) + pure $ all (\c -> any (matchingGuard c) sigCaps) mkt + +unsafeModuleHashFromB64Text :: Text -> ModuleHash +unsafeModuleHashFromB64Text = + either error ModuleHash . PU.fromText' + +-- | Managed namespace policy CAF +-- +managedNamespacePolicy :: NamespacePolicy +managedNamespacePolicy = SmartNamespacePolicy False + (QualifiedName (ModuleName "ns" Nothing) "validate" noInfo) +{-# NOINLINE managedNamespacePolicy #-} + +-- | Builder for "magic" capabilities given a magic cap name +-- +mkMagicCapSlot :: Text -> CapSlot SigCapability +mkMagicCapSlot c = CapSlot CapCallStack (mkCoinCap c []) [] +{-# INLINE mkMagicCapSlot #-} + +mkCoinCap :: Text -> [PactValue] -> SigCapability +mkCoinCap c as = SigCapability fqn as + where + mn = ModuleName "coin" Nothing + fqn = QualifiedName mn c noInfo +{-# INLINE mkCoinCap #-} + +-- | Build the 'ExecMsg' for some pact code fed to the function. The 'value' +-- parameter is for any possible environmental data that needs to go into +-- the 'ExecMsg'. +-- +buildExecParsedCode + :: PactParserVersion + -> Maybe Value + -> Text + -> IO (ExecMsg ParsedCode) +buildExecParsedCode ppv value code = maybe (go Null) go value + where + go val = case parsePact ppv code of + Right !t -> pure $! ExecMsg t (toLegacyJson val) + -- if we can't construct coin contract calls, this should + -- fail fast + Left err -> internalError $ "buildExecParsedCode: parse failed: " <> T.pack err + +-- | Retrieve public metadata from a command +-- +publicMetaOf :: Command (Payload PublicMeta code) -> PublicMeta +publicMetaOf = _pMeta . _cmdPayload +{-# INLINE publicMetaOf #-} + +-- | Retrieve the optional Network identifier from a command +-- +networkIdOf :: Command (Payload PublicMeta code) -> Maybe NetworkId +networkIdOf = _pNetworkId . _cmdPayload +{-# INLINE networkIdOf #-} + +-- | Calculate the gas fee (pact-generate gas cost * user-specified gas price), +-- rounding to the nearest stu. +-- +gasSupplyOf :: Gas -> GasPrice -> GasSupply +gasSupplyOf gas (GasPrice (ParsedDecimal gp)) = GasSupply (ParsedDecimal gs) + where + gs = toCoinUnit ((fromIntegral gas) * gp) +{-# INLINE gasSupplyOf #-} + +-- | Round to the nearest Stu +-- +toCoinUnit :: Decimal -> Decimal +toCoinUnit = roundTo 12 +{-# INLINE toCoinUnit #-} + +gasLog :: (Logger logger) => Text -> TransactionM logger db () +gasLog m = do + l <- view txGasLogger + rk <- view txRequestKey + for_ l $ \logger -> + logInfo_ logger $ m <> ": " <> sshow rk + +-- | Log request keys at DEBUG when successful +-- +debug :: (Logger logger) => Text -> TransactionM logger db () +debug s = do + l <- view txLogger + rk <- view txRequestKey + logDebug_ l $ s <> ": " <> sshow rk + +-- | Denotes fatal failure points in the tx exec process +-- +fatal :: (Logger logger) => Text -> TransactionM logger db a +fatal e = do + l <- view txLogger + rk <- view txRequestKey + + logError_ l + $ "critical transaction failure: " + <> sshow rk <> ": " <> e + + throwM $ PactTransactionExecError (fromUntypedHash $ unRequestKey rk) e + +logError :: (Logger logger) => Text -> TransactionM logger db () +logError msg = view txLogger >>= \l -> logError_ l msg + +infoLog :: (Logger logger) => Text -> TransactionM logger db () +infoLog msg = view txLogger >>= \l -> logInfo_ l msg diff --git a/src/Chainweb/Pact4/Types.hs b/src/Chainweb/Pact4/Types.hs new file mode 100644 index 0000000000..92ad20eb0a --- /dev/null +++ b/src/Chainweb/Pact4/Types.hs @@ -0,0 +1,326 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE LambdaCase #-} + +module Chainweb.Pact4.Types + ( getInitCache + , updateInitCache + , updateInitCacheM + + , GasSupply(..) + + -- * TxContext + , TxContext(..) + , ctxToPublicData + , ctxToPublicData' + , ctxBlockHeader + , ctxCurrentBlockHeight + , ctxChainId + , ctxVersion + , guardCtx + , getTxContext + , localLabelBlock + + , catchesPactError + , UnexpectedErrorPrinting(..) + , GasId(..) + , EnforceCoinbaseFailure(..) + , CoinbaseUsePrecompiled(..) + , PactBlockM(..) + , liftPactServiceM + , runPactBlockM + , tracePactBlockM + , tracePactBlockM' + + , getGasModel + ) where + +import Control.Exception.Safe +import Control.Lens +import Control.Monad.Reader +import Control.Monad.State.Strict + +import Data.Aeson hiding (Error,(.=)) +import qualified Data.Map.Strict as M +import Data.Text (Text) + +-- internal pact modules + +import qualified Pact.JSON.Encode as J +import Pact.Parse (ParsedDecimal) +import Pact.Types.ChainMeta +import Pact.Types.Gas +import Pact.Types.Info +import Pact.Types.Pretty (viaShow) +import Pact.Types.Runtime (PactError(..), PactErrorType(..)) +import Pact.Types.Term + +-- internal chainweb modules + +import Chainweb.BlockCreationTime +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.ChainId +import Chainweb.Miner.Pact +import Chainweb.Logger +import Chainweb.Time +import Chainweb.Utils +import Chainweb.Version +import Utils.Logging.Trace + +import Pact.Gas.Table +import Chainweb.Pact.Types +import Chainweb.Pact4.ModuleCache +import Chainweb.Version.Guards + + +-- | Indicates a computed gas charge (gas amount * gas price) +newtype GasSupply = GasSupply { _gasSupply :: ParsedDecimal } + deriving (Eq,Ord) + deriving newtype (Num,Real,Fractional,FromJSON) +instance Show GasSupply where show (GasSupply g) = show g + +instance J.Encode GasSupply where + build = J.build . _gasSupply + +-- | Update init cache at adjusted parent block height (APBH). +-- Contents are merged with cache found at or before APBH. +-- APBH is 0 for genesis and (parent block height + 1) thereafter. +updateInitCache :: ModuleCache -> ParentHeader -> PactServiceM logger tbl () +updateInitCache mc ph = get >>= \PactServiceState{..} -> do + let bf 0 = 0 + bf h = succ h + let pbh = bf (view blockHeight $ _parentHeader ph) + + v <- view psVersion + cid <- view chainId + + psInitCache .= case M.lookupLE pbh _psInitCache of + Nothing -> M.singleton pbh mc + Just (_,before) + | cleanModuleCache v cid pbh -> + M.insert pbh mc _psInitCache + | otherwise -> M.insert pbh (before <> mc) _psInitCache + +-- | Pair parent header with transaction metadata. +-- In cases where there is no transaction/Command, 'PublicMeta' +-- default value is used. +data TxContext = TxContext + { _tcParentHeader :: !ParentHeader + , _tcPublicMeta :: !PublicMeta + , _tcMiner :: !Miner + } deriving Show + +instance HasChainId TxContext where + _chainId = _chainId . _tcParentHeader +instance HasChainwebVersion TxContext where + _chainwebVersion = _chainwebVersion . _tcParentHeader + +-- | Convert context to datatype for Pact environment. +-- +-- TODO: this should be deprecated, since the `ctxBlockHeader` +-- call fetches a grandparent, not the parent. +-- +ctxToPublicData :: TxContext -> PublicData +ctxToPublicData ctx = PublicData + { _pdPublicMeta = _tcPublicMeta ctx + , _pdBlockHeight = bh + , _pdBlockTime = bt + , _pdPrevBlockHash = toText hsh + } + where + h = ctxBlockHeader ctx + BlockHeight bh = ctxCurrentBlockHeight ctx + BlockCreationTime (Time (TimeSpan (Micros !bt))) = view blockCreationTime h + BlockHash hsh = view blockParent h + +-- | Convert context to datatype for Pact environment using the +-- current blockheight, referencing the parent header (not grandparent!) +-- hash and blocktime data +-- +ctxToPublicData' :: TxContext -> PublicData +ctxToPublicData' ctx = PublicData + { _pdPublicMeta = _tcPublicMeta ctx + , _pdBlockHeight = bh + , _pdBlockTime = bt + , _pdPrevBlockHash = toText h + } + where + bheader = _parentHeader (_tcParentHeader ctx) + BlockHeight !bh = succ $ view blockHeight bheader + BlockCreationTime (Time (TimeSpan (Micros !bt))) = + view blockCreationTime bheader + BlockHash h = view blockHash bheader + +-- | Retrieve parent header as 'BlockHeader' +ctxBlockHeader :: TxContext -> BlockHeader +ctxBlockHeader = _parentHeader . _tcParentHeader + +-- | Get "current" block height, which means parent height + 1. +-- This reflects Pact environment focus on current block height, +-- which influenced legacy switch checks as well. +ctxCurrentBlockHeight :: TxContext -> BlockHeight +ctxCurrentBlockHeight = succ . view blockHeight . ctxBlockHeader + +ctxChainId :: TxContext -> ChainId +ctxChainId = view blockChainId . ctxBlockHeader + +ctxVersion :: TxContext -> ChainwebVersion +ctxVersion = view chainwebVersion . ctxBlockHeader + +guardCtx :: (ChainwebVersion -> ChainId -> BlockHeight -> a) -> TxContext -> a +guardCtx g txCtx = g (ctxVersion txCtx) (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) + +-- | Assemble tx context from transaction metadata and parent header. +getTxContext :: Miner -> PublicMeta -> PactBlockM logger tbl TxContext +getTxContext miner pm = view psParentHeader >>= \ph -> return (TxContext ph pm miner) + +-- | A sub-monad of PactServiceM, for actions taking place at a particular block. +newtype PactBlockM logger tbl a = PactBlockM + { _unPactBlockM :: + ReaderT (PactBlockEnv logger Pact4 tbl) (StateT PactServiceState IO) a + } deriving newtype + ( Functor, Applicative, Monad + , MonadReader (PactBlockEnv logger Pact4 tbl) + , MonadState PactServiceState + , MonadThrow, MonadCatch, MonadMask + , MonadIO + ) + +-- | Lifts PactServiceM to PactBlockM by forgetting about the current block. +-- It is unsafe to use `runPactBlockM` inside the argument to this function. +liftPactServiceM :: PactServiceM logger tbl a -> PactBlockM logger tbl a +liftPactServiceM (PactServiceM a) = PactBlockM $ ReaderT $ \e -> + StateT $ \s -> do + runStateT (runReaderT a (_psServiceEnv e)) s + +-- | Look up an init cache that is stored at or before the height of the current parent header. +getInitCache :: PactBlockM logger tbl ModuleCache +getInitCache = do + ph <- views psParentHeader (view blockHeight . _parentHeader) + get >>= \PactServiceState{..} -> + case M.lookupLE ph _psInitCache of + Just (_,mc) -> return mc + Nothing -> return mempty + +-- | A wrapper for 'updateInitCache' that uses the current block. +updateInitCacheM :: ModuleCache -> PactBlockM logger tbl () +updateInitCacheM mc = do + pc <- view psParentHeader + liftPactServiceM $ + updateInitCache mc pc + +-- | Run 'PactBlockM' by providing the block context, in the form of +-- a database snapshot at that block and information about the parent header. +-- It is unsafe to use this function in an argument to `liftPactServiceM`. +runPactBlockM + :: ParentHeader -> Bool -> PactDbFor logger Pact4 + -> PactBlockM logger tbl a -> PactServiceM logger tbl a +runPactBlockM pctx isGenesis dbEnv (PactBlockM act) = PactServiceM $ ReaderT $ \e -> StateT $ \s -> do + let blockEnv = PactBlockEnv + { _psServiceEnv = e + , _psIsGenesis = isGenesis + , _psParentHeader = pctx + , _psBlockDbEnv = dbEnv + } + (a, s') <- runStateT + (runReaderT act blockEnv) + s + return (a, s') + +tracePactBlockM :: (Logger logger, ToJSON param) => Text -> param -> Int -> PactBlockM logger tbl a -> PactBlockM logger tbl a +tracePactBlockM label param weight a = tracePactBlockM' label (const param) (const weight) a + +tracePactBlockM' :: (Logger logger, ToJSON param) => Text -> (a -> param) -> (a -> Int) -> PactBlockM logger tbl a -> PactBlockM logger tbl a +tracePactBlockM' label calcParam calcWeight a = do + e <- ask + s <- get + (r, s') <- liftIO $ trace' (logJsonTrace_ (_psLogger $ _psServiceEnv e)) label (calcParam . fst) (calcWeight . fst) + $ runStateT (runReaderT (_unPactBlockM a) e) s + put s' + return r + +localLabelBlock :: (Logger logger) => (Text, Text) -> PactBlockM logger tbl x -> PactBlockM logger tbl x +localLabelBlock lbl x = do + locally (psServiceEnv . psLogger) (addLabel lbl) x + +data UnexpectedErrorPrinting = PrintsUnexpectedError | CensorsUnexpectedError + +catchesPactError :: (MonadCatch m, MonadIO m, Logger logger) => logger -> UnexpectedErrorPrinting -> m a -> m (Either PactError a) +catchesPactError logger exnPrinting action = catches (Right <$> action) + [ Handler $ \(e :: PactError) -> return $ Left e + , Handler $ \(e :: SomeException) -> do + !err <- case exnPrinting of + PrintsUnexpectedError -> + return (viaShow e) + CensorsUnexpectedError -> do + liftIO $ logWarn_ logger ("catchesPactError: unknown error: " <> sshow e) + return "unknown error" + return $ Left $ PactError EvalError noInfo [] err + ] + + +newtype GasId = GasId PactId deriving (Eq, Show) + +-- | Whether to enforce coinbase failures, failing the block, +-- or be backward-compatible and allow. +-- Backward-compat fix is to enforce in new block, but ignore in validate. +-- +newtype EnforceCoinbaseFailure = EnforceCoinbaseFailure Bool + +-- | Always use precompiled templates in coinbase or use date rule. +newtype CoinbaseUsePrecompiled = CoinbaseUsePrecompiled Bool + +-- | Modified table gas module with free module loads +-- +freeModuleLoadGasModel :: GasModel +freeModuleLoadGasModel = modifiedGasModel + where + defGasModel = tableGasModel defaultGasConfig + fullRunFunction = runGasModel defGasModel + modifiedRunFunction name ga = case ga of + GPostRead ReadModule {} -> MilliGas 0 + _ -> fullRunFunction name ga + modifiedGasModel = defGasModel { runGasModel = modifiedRunFunction } + +chainweb213GasModel :: GasModel +chainweb213GasModel = modifiedGasModel + where + defGasModel = tableGasModel gasConfig + unknownOperationPenalty = 1000000 + multiRowOperation = 40000 + gasConfig = defaultGasConfig { _gasCostConfig_primTable = updTable } + updTable = M.union upd defaultGasTable + upd = M.fromList + [("keys", multiRowOperation) + ,("select", multiRowOperation) + ,("fold-db", multiRowOperation) + ] + fullRunFunction = runGasModel defGasModel + modifiedRunFunction name ga = case ga of + GPostRead ReadModule {} -> 0 + GUnreduced _ts -> case M.lookup name updTable of + Just g -> g + Nothing -> unknownOperationPenalty + _ -> milliGasToGas $ fullRunFunction name ga + modifiedGasModel = defGasModel { runGasModel = \t g -> gasToMilliGas (modifiedRunFunction t g) } + +chainweb224GasModel :: GasModel +chainweb224GasModel = chainweb213GasModel + { runGasModel = \name -> \case + GPostRead ReadInterface {} -> MilliGas 0 + ga -> runGasModel chainweb213GasModel name ga + } + +getGasModel :: TxContext -> GasModel +getGasModel ctx + | guardCtx chainweb213Pact ctx = chainweb213GasModel + | guardCtx chainweb224Pact ctx = chainweb224GasModel + | otherwise = freeModuleLoadGasModel diff --git a/src/Chainweb/Pact4/Validations.hs b/src/Chainweb/Pact4/Validations.hs new file mode 100644 index 0000000000..d279ec439e --- /dev/null +++ b/src/Chainweb/Pact4/Validations.hs @@ -0,0 +1,285 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +-- | +-- Module: Chainweb.Pact4.Validations +-- Copyright: Copyright © 2018,2019,2020,2021,2022 Kadena LLC. +-- License: See LICENSE file +-- Maintainers: Lars Kuhtz, Emily Pillmore, Stuart Popejoy, Greg Hale +-- Stability: experimental +-- +-- Validation checks for transaction requests. +-- These functions are meant to be shared between: +-- - The codepath for adding transactions to the mempool +-- - The codepath for letting users test their transaction via /local +-- +module Chainweb.Pact4.Validations +( -- * Local metadata _validation + assertPreflightMetadata + -- * Validation checks +, assertParseChainId +, assertChainId +, assertGasPrice +, assertNetworkId +, assertSigSize +, assertTxSize +, IsWebAuthnPrefixLegal(..) +, assertValidateSigs +, AssertValidateSigsError(..) +, displayAssertValidateSigsError +, assertTxTimeRelativeToParent +, assertTxNotInFuture +, assertCommand +, AssertCommandError(..) +, displayAssertCommandError + -- * Defaults +, defaultMaxCommandUserSigListSize +, defaultMaxCoinDecimalPlaces +, defaultMaxTTL +, defaultLenientTimeSlop +) where + +import Control.Lens + +import Data.Decimal (decimalPlaces) +import Data.Bifunctor (first) +import Data.Maybe (isJust, catMaybes, fromMaybe) +import Data.Either (isRight) +import Data.List.NonEmpty (NonEmpty, nonEmpty) +import Data.Text (Text) +import qualified Data.Text as Text +import qualified Data.ByteString.Short as SBS +import Data.Word (Word8) + +-- internal modules + +import Chainweb.BlockHeader +import Chainweb.BlockCreationTime (BlockCreationTime(..)) +import Chainweb.Pact.Types +import Chainweb.Pact.Utils (fromPactChainId) +import Chainweb.Time (Seconds(..), Time(..), secondsToTimeSpan, scaleTimeSpan, second, add) +import Chainweb.Pact4.Transaction +import Chainweb.Version +import Chainweb.Version.Guards (isWebAuthnPrefixLegal, validPPKSchemes) + +import qualified Pact.Types.Gas as P +import qualified Pact.Types.Hash as P +import qualified Pact.Types.ChainId as P +import qualified Pact.Types.Command as P +import qualified Pact.Types.ChainMeta as P +import qualified Pact.Types.KeySet as P +import qualified Pact.Parse as P +import Chainweb.Pact4.Types +import Chainweb.Utils (ebool_) + + +-- | Check whether a local Api request has valid metadata +-- +assertPreflightMetadata + :: P.Command (P.Payload P.PublicMeta c) + -> TxContext + -> Maybe LocalSignatureVerification + -> PactServiceM logger tbl (Either (NonEmpty Text) ()) +assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do + v <- view psVersion + cid <- view chainId + bgl <- view psBlockGasLimit + + let bh = ctxCurrentBlockHeight txCtx + let validSchemes = validPPKSchemes v cid bh + let webAuthnPrefixLegal = isWebAuthnPrefixLegal v cid bh + + let P.PublicMeta pcid _ gl gp _ _ = P._pMeta pay + nid = P._pNetworkId pay + signers = P._pSigners pay + + let errs = catMaybes + [ eUnless "Unparseable transaction chain id" $ assertParseChainId pcid + , eUnless "Chain id mismatch" $ assertChainId cid pcid + -- TODO + , eUnless "Transaction Gas limit exceeds block gas limit" $ assertBlockGasLimit bgl gl + , eUnless "Gas price decimal precision too high" $ assertGasPrice gp + , eUnless "Network id mismatch" $ assertNetworkId v nid + , eUnless "Signature list size too big" $ assertSigSize sigs + , eUnless "Invalid transaction signatures" $ sigValidate validSchemes webAuthnPrefixLegal signers + , eUnless "Tx time outside of valid range" $ assertTxTimeRelativeToParent pct cmd + ] + + pure $ case nonEmpty errs of + Nothing -> Right () + Just vs -> Left vs + where + sigValidate validSchemes webAuthnPrefixLegal signers + | Just NoVerify <- sigVerify = True + | otherwise = isRight $ assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs + + pct = ParentCreationTime + . view blockCreationTime + . _parentHeader + . _tcParentHeader + $ txCtx + + eUnless t assertion + | assertion = Nothing + | otherwise = Just t + +-- | Check whether a particular Pact chain id is parseable +-- +assertParseChainId :: P.ChainId -> Bool +assertParseChainId = isJust . fromPactChainId + +-- | Check whether the chain id defined in the metadata of a Pact/Chainweb +-- command payload matches a given chain id. +-- +-- The supplied chain id should be derived from the current +-- chainweb node structure +-- +assertChainId :: ChainId -> P.ChainId -> Bool +assertChainId cid0 cid1 = chainIdToText cid0 == P._chainId cid1 + +-- | Check and assert that 'GasPrice' is rounded to at most 12 decimal +-- places. +-- +assertGasPrice :: P.GasPrice -> Bool +assertGasPrice (P.GasPrice (P.ParsedDecimal gp)) = decimalPlaces gp <= defaultMaxCoinDecimalPlaces + +-- | Check and assert that the 'GasLimit' of a transaction is less than or eqaul to +-- the block gas limit +-- +assertBlockGasLimit :: P.GasLimit -> P.GasLimit -> Bool +assertBlockGasLimit bgl tgl = bgl >= tgl + +-- | Check and assert that 'ChainwebVersion' is equal to some pact 'NetworkId'. +-- +assertNetworkId :: ChainwebVersion -> Maybe P.NetworkId -> Bool +assertNetworkId _ Nothing = False +assertNetworkId v (Just (P.NetworkId nid)) = ChainwebVersionName nid == _versionName v + +-- | Check and assert that the number of signatures in a 'Command' is +-- at most 100. +-- +assertSigSize :: [P.UserSig] -> Bool +assertSigSize sigs = length sigs <= defaultMaxCommandUserSigListSize + +-- | Check and assert that the initial 'Gas' cost of a transaction +-- is less than the specified 'GasLimit'. +-- +assertTxSize :: P.Gas -> P.GasLimit -> Bool +assertTxSize initialGas gasLimit = initialGas < fromIntegral gasLimit + +-- | Check and assert that signers and user signatures are valid for a given +-- transaction hash. +-- +assertValidateSigs :: () + => [P.PPKScheme] + -> IsWebAuthnPrefixLegal + -> P.PactHash + -> [P.Signer] + -> [P.UserSig] + -> Either AssertValidateSigsError () +assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs = do + let signersLength = length signers + let sigsLength = length sigs + ebool_ + SignersAndSignaturesLengthMismatch + { _signersLength = signersLength + , _signaturesLength = sigsLength + } + (signersLength == sigsLength) + + iforM_ (zip sigs signers) $ \pos (sig, signer) -> do + ebool_ + (InvalidSignerScheme pos) + (fromMaybe P.ED25519 (P._siScheme signer) `elem` validSchemes) + ebool_ + (InvalidSignerWebAuthnPrefix pos) + (webAuthnPrefixLegal == WebAuthnPrefixLegal || not (P.webAuthnPrefix `Text.isPrefixOf` P._siPubKey signer)) + case P.verifyUserSig hsh sig signer of + Left errMsg -> Left (InvalidUserSig pos (Text.pack errMsg)) + Right () -> Right () + +-- prop_tx_ttl_newBlock/validateBlock +-- +-- Timing checks used to be based on the creation time of the validated +-- block. That changed on mainnet at block height 449940. Tx creation time +-- and TTL don't affect the tx outputs and pact state and can thus be +-- skipped when replaying old blocks. +-- +assertTxTimeRelativeToParent + :: ParentCreationTime + -> P.Command (P.Payload P.PublicMeta c) + -> Bool +assertTxTimeRelativeToParent (ParentCreationTime (BlockCreationTime txValidationTime)) tx = + ttl > 0 + && txValidationTime >= timeFromSeconds 0 + && txOriginationTime >= 0 + && timeFromSeconds (txOriginationTime + ttl) > txValidationTime + && P.TTLSeconds ttl <= defaultMaxTTL + where + P.TTLSeconds ttl = view cmdTimeToLive tx + timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral + P.TxCreationTime txOriginationTime = view cmdCreationTime tx + +-- | Check that the tx's creation time is not too far in the future relative +-- to the block creation time +assertTxNotInFuture + :: ParentCreationTime + -> P.Command (P.Payload P.PublicMeta c) + -> Bool +assertTxNotInFuture (ParentCreationTime (BlockCreationTime txValidationTime)) tx = + timeFromSeconds txOriginationTime <= lenientTxValidationTime + where + timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral + P.TxCreationTime txOriginationTime = view cmdCreationTime tx + lenientTxValidationTime = add (scaleTimeSpan defaultLenientTimeSlop second) txValidationTime + + +-- | Assert that the command hash matches its payload and +-- its signatures are valid, without parsing the payload. +assertCommand :: P.Command (PayloadWithText m c) -> [P.PPKScheme] -> IsWebAuthnPrefixLegal -> Either AssertCommandError () +assertCommand (P.Command pwt sigs hsh) ppkSchemePassList webAuthnPrefixLegal = do + if isRight assertHash + then first AssertValidateSigsError $ assertValidateSigs ppkSchemePassList webAuthnPrefixLegal hsh signers sigs + else Left InvalidPayloadHash + where + cmdBS = SBS.fromShort $ payloadBytes pwt + signers = P._pSigners (payloadObj pwt) + assertHash = P.verifyHash @'P.Blake2b_256 hsh cmdBS + +-- -------------------------------------------------------------------- -- +-- defaults + +-- | The maximum admissible signature list size allowed for +-- Pact/Chainweb transactions +-- +defaultMaxCommandUserSigListSize :: Int +defaultMaxCommandUserSigListSize = 100 + +-- | The maximum admissible number of decimal places allowed +-- by the coin contract. +-- +defaultMaxCoinDecimalPlaces :: Word8 +defaultMaxCoinDecimalPlaces = 12 + + +-- | The maximum time-to-live (expressed in seconds) +-- +-- This is probably going to be changed. Let us make it 2 days for now. +-- +defaultMaxTTL :: P.TTLSeconds +defaultMaxTTL = P.TTLSeconds $ P.ParsedInteger $ 2 * 24 * 60 * 60 + +-- | Validation "slop" to allow for a more lenient creation time check after +-- @useLegacyCreationTimeForTxValidation@ is no longer true. +-- +-- Without this, transactions showing up in the interim between +-- parent block issuance and new block creation can get rejected; the tradeoff reduces +-- the accuracy of the tx creation time vs "blockchain time", but is better than e.g. +-- incurring artificial latency to wait for a parent block that is acceptable for a tx. +-- 95 seconds represents the 99th percentile of block arrival times. +-- +defaultLenientTimeSlop :: Seconds +defaultLenientTimeSlop = 95 diff --git a/test/lib/Chainweb/Test/Pact4/Utils.hs b/test/lib/Chainweb/Test/Pact4/Utils.hs new file mode 100644 index 0000000000..4e7ca039e1 --- /dev/null +++ b/test/lib/Chainweb/Test/Pact4/Utils.hs @@ -0,0 +1,1118 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE DataKinds #-} + +{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} +-- | +-- Module: Chainweb.Test.Pact4.Utils +-- Copyright: Copyright © 2018 - 2020 Kadena LLC. +-- License: See LICENSE file +-- Maintainer: Emily Pillmore +-- Stability: experimental +-- +-- Unit test for Pact execution via (inprocess) API in Chainweb +module Chainweb.Test.Pact4.Utils +( -- * Test key data + SimpleKeyPair +, sender00 +, sender01 +, sender00Ks +, sender02WebAuthn +, sender02WebAuthnPrefixed +, sender03WebAuthn +, allocation00KeyPair +, testKeyPairs +, mkKeySetData +-- * 'PactValue' helpers +, pInteger +, pString +, pDecimal +, pBool +, pList +, pKeySet +, pObject +-- * event helpers +, mkEvent +, mkTransferEvent +, mkTransferXChainEvent +, mkTransferXChainRecdEvent +, mkXYieldEvent +, mkXResumeEvent +-- * Capability helpers +, mkCapability +, mkTransferCap +, mkGasCap +, mkCoinCap +, mkXChainTransferCap +-- * Command builder +, defaultCmd +, buildCwCmd +, buildTextCmd +, mkExec' +, mkExec +, mkCont +, mkContMsg +, ContMsg (..) +, mkEd25519Signer +, mkEd25519Signer' +, mkWebAuthnSigner +, mkWebAuthnSigner' +, CmdBuilder(..) +, cbSigners +, cbVerifiers +, cbRPC +, cbNonce +, cbChainId +, cbSender +, cbGasLimit +, cbGasPrice +, cbTTL +, cbCreationTime +, CmdSigner +, csSigner +, csPrivKey +-- * Pact Service creation +, withPactTestBlockDb +, withPactTestBlockDb' +, withWebPactExecutionService +, withWebPactExecutionServiceCompaction +, withPactCtxSQLite +, WithPactCtxSQLite +-- * Other service creation +, initializeSQLite +, freeSQLiteResource +, freeGasModel +, testPactServiceConfig +, withBlockHeaderDb +, withSqliteDb +-- * Mempool utils +, delegateMemPoolAccess +, withDelegateMempool +, setMempool +, setOneShotMempool +-- * Block formation +, runCut +, Noncer +, zeroNoncer +-- * Pact State +, sigmaCompact +, PactRow(..) +, getLatestPactState +, getPactUserTables +-- * miscellaneous +, toTxCreationTime +, dummyLogger +, stdoutDummyLogger +, hunitDummyLogger +, pactTestLogger +, someTestVersionHeader +, someBlockHeader +, testPactFilesDir +, getPWOByHeader +, throwIfNotPact4 + +) where + +import Control.Arrow ((&&&)) +import Control.Concurrent.Async +import Control.Concurrent.MVar +import Control.Lens hiding ((.=)) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.IO.Class + +import Data.Aeson (Value(..), object, (.=), Key) +import Data.ByteString (ByteString) +import qualified Data.ByteString.Short as BS +import Data.Decimal +import Data.Foldable +import qualified Data.HashMap.Strict as HM +import Data.IORef +import Data.List qualified as List +import Data.Map (Map) +import qualified Data.Map.Strict as M +import Data.Maybe +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import Data.String +import qualified Data.Vector as V + +import Database.SQLite3.Direct (Database) + +import GHC.Generics + +import Streaming.Prelude qualified as S +import System.LogLevel + +import Test.Tasty +import Test.Tasty.HUnit(assertFailure) + +-- internal pact modules + +import Pact.ApiReq (ApiKeyPair(..), mkKeyPairs) +import Pact.Gas +import Pact.JSON.Legacy.Value +import Pact.Types.Capability +import qualified Pact.Types.ChainId as P +import Pact.Types.ChainMeta +import Pact.Types.Command +import Pact.Types.Crypto +import Pact.Types.Exp +import Pact.Types.Gas +import Pact.Types.Hash +import Pact.Types.Info (noInfo) +import Pact.Types.KeySet +import qualified Pact.Types.Logger as P +import Pact.Types.Names +import Pact.Types.PactValue +import Pact.Types.RPC +import Pact.Types.Runtime (PactEvent(..)) +import Pact.Types.Term +import Pact.Types.Util (parseB16TextOnly) +import Pact.Types.Verifier + +-- internal modules + +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB hiding (withBlockHeaderDb) +import Chainweb.BlockHeight +import Chainweb.ChainId +import Chainweb.Graph +import Chainweb.Logger +import Chainweb.Miner.Pact +import Chainweb.Pact.Backend.Compaction qualified as Sigma +import Chainweb.Pact.Backend.PactState qualified as PactState +import Chainweb.Pact.Backend.PactState (TableDiffable(..), Table(..), PactRow(..)) +import Chainweb.Pact.PactService.Checkpointer.Internal (initCheckpointerResources) +import Chainweb.Pact.Backend.SQLite.DirectV2 + +import Chainweb.Pact.Backend.Utils hiding (tbl, withSqliteDb) +import Chainweb.Pact.PactService +import Chainweb.Pact.RestAPI.Server (validateCommand) +import Chainweb.Pact.Service.PactQueue +import Chainweb.Pact.Types +import Chainweb.Pact4.Types +import Chainweb.Payload +import Chainweb.Payload.PayloadStore +import Chainweb.Test.Cut +import Chainweb.Test.Cut.TestBlockDb +import Chainweb.Test.Utils +import Chainweb.Test.Utils.BlockHeader +import Chainweb.Test.TestVersions +import Chainweb.Time +import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Utils +import Chainweb.Version (ChainwebVersion(..), chainIds) +import qualified Chainweb.Version as Version +import Chainweb.Version.Utils (someChainId) +import Chainweb.WebBlockHeaderDB +import Chainweb.WebPactExecutionService + +import Chainweb.Storage.Table.RocksDB +import Chainweb.Pact.Backend.Types + +-- ----------------------------------------------------------------------- -- +-- Keys + +testPactFilesDir :: FilePath +testPactFilesDir = "test/pact/" + +type SimpleKeyPair = (Text,Text) + +sender00 :: SimpleKeyPair +sender00 = ("368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca" + ,"251a920c403ae8c8f65f59142316af3c82b631fba46ddea92ee8c95035bd2898") + +sender01 :: SimpleKeyPair +sender01 = ("6be2f485a7af75fedb4b7f153a903f7e6000ca4aa501179c91a2450b777bd2a7" + ,"2beae45b29e850e6b1882ae245b0bab7d0689ebdd0cd777d4314d24d7024b4f7") + +sender02WebAuthnPrefixed :: SimpleKeyPair +sender02WebAuthnPrefixed = + ("WEBAUTHN-a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf" + ,"fecd4feb1243d715d095e24713875ca76c476f8672ec487be8e3bc110dd329ab") + +sender02WebAuthn :: SimpleKeyPair +sender02WebAuthn = + ("a4010103272006215820c18831c6f15306d6271e154842906b68f26c1af79b132dde6f6add79710303bf" + ,"fecd4feb1243d715d095e24713875ca76c476f8672ec487be8e3bc110dd329ab") + +sender03WebAuthn :: SimpleKeyPair +sender03WebAuthn = + ("a4010103272006215820ad72392508272b4c45536976474cdd434e772bfd630738ee9aac7343e7222eb6" + ,"ebe7d1119a53863fa64be7347d82d9fcc9ebeb8cbbe480f5e8642c5c36831434") + +allocation00KeyPair :: SimpleKeyPair +allocation00KeyPair = + ( "d82d0dcde9825505d86afb6dcc10411d6b67a429a79e21bda4bb119bf28ab871" + , "c63cd081b64ae9a7f8296f11c34ae08ba8e1f8c84df6209e5dee44fa04bcb9f5" + ) + + +-- | Make trivial keyset data +mkKeySetData :: Key -> [SimpleKeyPair] -> Value +mkKeySetData name keys = object [ name .= map fst keys ] + +sender00Ks :: KeySet +sender00Ks = mkKeySet + ["368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca"] + "keys-all" + +-- ----------------------------------------------------------------------- -- +-- PactValue helpers + +-- | Make PactValue from 'Integral' +pInteger :: Integer -> PactValue +pInteger = PLiteral . LInteger + +-- | Make PactValue from text +pString :: Text -> PactValue +pString = PLiteral . LString + +-- | Make PactValue from decimal +pDecimal :: Decimal -> PactValue +pDecimal = PLiteral . LDecimal + +-- | Make PactValue from boolean +pBool :: Bool -> PactValue +pBool = PLiteral . LBool + +pList :: [PactValue] -> PactValue +pList = PList . V.fromList + +pKeySet :: KeySet -> PactValue +pKeySet = PGuard . GKeySet + +pObject :: [(FieldKey,PactValue)] -> PactValue +pObject = PObject . ObjectMap . M.fromList + +mkEvent + :: MonadThrow m + => Text + -- ^ name + -> [PactValue] + -- ^ params + -> ModuleName + -> Text + -- ^ module hash + -> m PactEvent +mkEvent n params m mh = do + mh' <- decodeB64UrlNoPaddingText mh + return $ PactEvent n params m (ModuleHash (Hash $ BS.toShort mh')) + +mkTransferEvent + :: MonadThrow m + => Text + -- ^ sender + -> Text + -- ^ receiver + -> Decimal + -- ^ amount + -> ModuleName + -> Text + -- ^ module hash + -> m PactEvent +mkTransferEvent sender receiver amount m mh = + mkEvent "TRANSFER" [pString sender,pString receiver,pDecimal amount] m mh + +mkTransferXChainEvent + :: MonadThrow m + => Text + -- ^ sender + -> Text + -- ^ receiver + -> Decimal + -- ^ amount + -> ModuleName + -> Text + -- ^ module hash + -> Text + -- ^ target chain id + -> m PactEvent +mkTransferXChainEvent sender receiver amount m mh tid + = mkEvent "TRANSFER_XCHAIN" args m mh + where + args = + [ pString sender + , pString receiver + , pDecimal amount + , pString tid + ] + +mkTransferXChainRecdEvent + :: MonadThrow m + => Text + -- ^ sender + -> Text + -- ^ receiver + -> Decimal + -- ^ amount + -> ModuleName + -> Text + -- ^ module hash + -> Text + -- ^ source chain id + -> m PactEvent +mkTransferXChainRecdEvent sender receiver amount m mh sid + = mkEvent "TRANSFER_XCHAIN_RECD" args m mh + where + args = + [ pString sender + , pString receiver + , pDecimal amount + , pString sid + ] + +mkXYieldEvent + :: MonadThrow m + => Text + -- ^ sender + -> Text + -- ^ receiver + -> Decimal + -- ^ amount + -> KeySet + -- ^ receiver guard + -> ModuleName + -> Text + -- ^ module hash + -> Text + -- ^ target chain id + -> Text + -- ^ source chain id + -> m PactEvent +mkXYieldEvent sender receiver amount ks mn mh tid sid + = mkEvent "X_YIELD" args mn mh + where + args = + [ pString tid + , pString "coin.transfer-crosschain" + , pList + [ pString sender + , pString receiver + , pKeySet ks + , pString sid + , pDecimal amount + ] + ] + +mkXResumeEvent + :: MonadThrow m + => Text + -- ^ sender + -> Text + -- ^ receiver + -> Decimal + -- ^ amount + -> KeySet + -- ^ receiver guard + -> ModuleName + -> Text + -- ^ module hash + -> Text + -- ^ target chain id + -> Text + -- ^ source chain id + -> m PactEvent +mkXResumeEvent sender receiver amount ks mn mh tid sid + = mkEvent "X_RESUME" args mn mh + where + args = + [ pString tid + , pString "coin.transfer-crosschain" + , pList + [ pString sender + , pString receiver + , pKeySet ks + , pString sid + , pDecimal amount + ] + ] + +-- ----------------------------------------------------------------------- -- +-- Capability helpers + +-- | Cap smart constructor. +mkCapability :: ModuleName -> Text -> [PactValue] -> SigCapability +mkCapability mn cap args = SigCapability (QualifiedName mn cap noInfo) args + +-- | Convenience to make caps like TRANSFER, GAS etc. +mkCoinCap :: Text -> [PactValue] -> SigCapability +mkCoinCap n = mkCapability "coin" n + +mkTransferCap :: Text -> Text -> Decimal -> SigCapability +mkTransferCap sender receiver amount = mkCoinCap "TRANSFER" + [ pString sender, pString receiver, pDecimal amount ] + +mkXChainTransferCap :: Text -> Text -> Decimal -> Text -> SigCapability +mkXChainTransferCap sender receiver amount cid = mkCoinCap "TRANSFER_XCHAIN" + [ pString sender + , pString receiver + , pDecimal amount + , pString cid + ] + +mkGasCap :: SigCapability +mkGasCap = mkCoinCap "GAS" [] + + + +-- ----------------------------------------------------------------------- -- +-- CmdBuilder and friends + + +-- | Pair a 'Signer' with private key. +data CmdSigner = CmdSigner + { _csSigner :: !Signer + , _csPrivKey :: !Text + } deriving (Eq,Show,Ord,Generic) +makeLenses ''CmdSigner + +-- | Make ED25519 signer. +mkEd25519Signer :: Text -> Text -> [SigCapability] -> CmdSigner +mkEd25519Signer pubKey privKey caps = CmdSigner + { _csSigner = signer + , _csPrivKey = privKey + } + where + signer = Signer + { _siScheme = Nothing + , _siPubKey = pubKey + , _siAddress = Nothing + , _siCapList = caps + } + +mkEd25519Signer' :: SimpleKeyPair -> [SigCapability] -> CmdSigner +mkEd25519Signer' (pub,priv) = mkEd25519Signer pub priv + +mkWebAuthnSigner :: Text -> Text -> [SigCapability] -> CmdSigner +mkWebAuthnSigner pubKey privKey caps = CmdSigner + { _csSigner = signer + , _csPrivKey = privKey + } + where + signer = Signer + { _siScheme = Just WebAuthn + , _siPubKey = pubKey + , _siAddress = Nothing + , _siCapList = caps } + +mkWebAuthnSigner' :: SimpleKeyPair -> [SigCapability] -> CmdSigner +mkWebAuthnSigner' (pub, priv) caps = mkWebAuthnSigner pub priv caps + +-- | Chainweb-oriented command builder. +data CmdBuilder = CmdBuilder + { _cbSigners :: ![CmdSigner] + , _cbVerifiers :: ![Verifier ParsedVerifierProof] + , _cbRPC :: !(PactRPC Text) + , _cbNonce :: !Text + , _cbChainId :: !ChainId + , _cbSender :: !Text + , _cbGasLimit :: !GasLimit + , _cbGasPrice :: !GasPrice + , _cbTTL :: !TTLSeconds + , _cbCreationTime :: !TxCreationTime + } deriving (Eq,Show,Generic) +makeLenses ''CmdBuilder + +-- | Make code-only Exec PactRPC +mkExec' :: Text -> PactRPC Text +mkExec' ecode = mkExec ecode Null + +-- | Make Exec PactRPC +mkExec :: Text -> Value -> PactRPC Text +mkExec ecode edata = Exec $ ExecMsg ecode (toLegacyJson edata) + +mkCont :: ContMsg -> PactRPC Text +mkCont = Continuation + +mkContMsg :: PactId -> Int -> ContMsg +mkContMsg pid step = ContMsg + { _cmPactId = pid + , _cmStep = step + , _cmRollback = False + , _cmData = toLegacyJson Null + , _cmProof = Nothing } + +-- | Default builder. +defaultCmd :: CmdBuilder +defaultCmd = CmdBuilder + { _cbSigners = [] + , _cbVerifiers = [] + , _cbRPC = mkExec' "1" + , _cbNonce = "nonce" + , _cbChainId = unsafeChainId 0 + , _cbSender = "sender00" + , _cbGasLimit = 10_000 + , _cbGasPrice = 0.000_1 + , _cbTTL = 300 -- 5 minutes + , _cbCreationTime = 0 -- epoch + } + +-- | Build parsed + verified Pact command +-- +-- TODO: Use the new `assertPact4Command` function. +buildCwCmd :: (MonadThrow m, MonadIO m) => Text -> ChainwebVersion -> CmdBuilder -> m Pact4.Transaction +buildCwCmd nonce v cmd = buildRawCmd nonce v cmd >>= \(c :: Command ByteString) -> + case validateCommand v (_cbChainId cmd) (T.decodeUtf8 <$> c) of + Left err -> throwM $ userError $ "buildCmd failed: " ++ T.unpack err + Right cmd' -> return cmd' + +-- | Build unparsed, unverified command +-- +buildTextCmd :: Text -> ChainwebVersion -> CmdBuilder -> IO (Command Text) +buildTextCmd nonce v = fmap (fmap T.decodeUtf8) . buildRawCmd nonce v + +-- | Build a raw bytestring command +-- +buildRawCmd :: (MonadThrow m, MonadIO m) => Text -> ChainwebVersion -> CmdBuilder -> m (Command ByteString) +buildRawCmd nonce v (set cbNonce nonce -> CmdBuilder{..}) = do + kps <- liftIO $ traverse mkDynKeyPairs _cbSigners + cmd <- liftIO $ mkCommandWithDynKeys kps _cbVerifiers pm _cbNonce (Just nid) _cbRPC + pure cmd + where + nid = P.NetworkId (sshow v) + cid = fromString $ show (chainIdInt _cbChainId :: Int) + pm = PublicMeta cid _cbSender _cbGasLimit _cbGasPrice _cbTTL _cbCreationTime + +dieL :: MonadThrow m => [Char] -> Either [Char] a -> m a +dieL msg = either (\s -> throwM $ userError $ msg ++ ": " ++ s) return + +mkDynKeyPairs :: MonadThrow m => CmdSigner -> m (DynKeyPair, [SigCapability]) +mkDynKeyPairs (CmdSigner Signer{..} privKey) = + case (fromMaybe ED25519 _siScheme, _siPubKey, privKey) of + (ED25519, pub, priv) -> do + pub' <- either diePubKey return $ parseEd25519PubKey =<< parseB16TextOnly pub + priv' <- either diePrivKey return $ parseEd25519SecretKey =<< parseB16TextOnly priv + return (DynEd25519KeyPair (pub', priv'), _siCapList) + + (WebAuthn, pub, priv) -> do + let (pubKeyStripped, wasPrefixed) = fromMaybe + (pub, WebAuthnPubKeyBare) + ((,WebAuthnPubKeyPrefixed) <$> T.stripPrefix webAuthnPrefix pub) + pubWebAuthn <- + either diePubKey return (parseWebAuthnPublicKey =<< parseB16TextOnly pubKeyStripped) + privWebAuthn <- + either diePrivKey return (parseWebAuthnPrivateKey =<< parseB16TextOnly priv) + return (DynWebAuthnKeyPair wasPrefixed pubWebAuthn privWebAuthn, _siCapList) + where + diePubKey str = error $ "pubkey: " <> str + diePrivKey str = error $ "privkey: " <> str + +toApiKp :: MonadThrow m => CmdSigner -> m ApiKeyPair +toApiKp (CmdSigner Signer{..} privKey) = do + sk <- dieL "private key" $ parseB16TextOnly privKey + pk <- dieL "public key" $ parseB16TextOnly _siPubKey + let keyPair = ApiKeyPair (PrivBS sk) (Just (PubBS pk)) _siAddress _siScheme (Just _siCapList) + return $! keyPair + +-- | Legacy; better to use 'CmdSigner'/'CmdBuilder'. +-- if caps are empty, gas cap is implicit. otherwise it must be included +testKeyPairs :: SimpleKeyPair -> Maybe [SigCapability] -> IO [(DynKeyPair, [SigCapability])] +testKeyPairs skp capsm = do + kp <- toApiKp $ mkEd25519Signer' skp (fromMaybe [] capsm) + mkKeyPairs [kp] + +-- ----------------------------------------------------------------------- -- +-- Service creation utilities + +pactTestLogger :: (String -> IO ()) -> Bool -> P.Loggers +pactTestLogger backend showAll = P.initLoggers backend f (P.LogRules mempty) + where + f :: (String -> IO ()) -> P.LogName -> String -> String -> IO () + f _ b "ERROR" d = P.doLog (\_ -> return ()) b "ERROR" d + f _ b "DEBUG" d | not showAll = P.doLog (\_ -> return ()) b "DEBUG" d + f _ b "INFO" d | not showAll = P.doLog (\_ -> return ()) b "INFO" d + f _ b "DDL" d | not showAll = P.doLog (\_ -> return ()) b "DDL" d + f a b c d = P.doLog a b c d + +-- | Test Pact Execution Context for running inside 'PactServiceM'. +-- Only used internally. +data TestPactCtx logger tbl = TestPactCtx + { _testPactCtxState :: !(MVar PactServiceState) + , _testPactCtxEnv :: !(PactServiceEnv logger tbl) + } + +evalPactServiceM_ :: TestPactCtx logger tbl -> PactServiceM logger tbl a -> IO a +evalPactServiceM_ ctx pact = modifyMVar (_testPactCtxState ctx) $ \s -> do + T2 a s' <- runPactServiceM s (_testPactCtxEnv ctx) pact + return (s',a) + +destroyTestPactCtx :: TestPactCtx logger tbl -> IO () +destroyTestPactCtx = void . takeMVar . _testPactCtxState + +-- | setup TestPactCtx, internal function. +-- Use 'withPactCtxSQLite' in tests. +testPactCtxSQLite + :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) + => logger + -> ChainwebVersion + -> Version.ChainId + -> BlockHeaderDb + -> PayloadDb tbl + -> SQLiteEnv + -> PactServiceConfig + -> IO (TestPactCtx logger tbl) +testPactCtxSQLite logger v cid bhdb pdb sqlenv conf = do + cp <- initCheckpointerResources defaultModuleCacheLimit sqlenv DoNotPersistIntraBlockWrites cpLogger v cid + let rs = readRewards + !ctx <- TestPactCtx + <$!> newMVar (PactServiceState mempty) + <*> pure (mkPactServiceEnv cp rs) + evalPactServiceM_ ctx (initialPayloadState v cid) + return ctx + where + cpLogger = addLabel ("chain-id", chainIdToText cid) $ addLabel ("sub-component", "checkpointer") $ logger + mkPactServiceEnv :: Checkpointer logger -> MinerRewards -> PactServiceEnv logger tbl + mkPactServiceEnv cp rs = PactServiceEnv + { _psMempoolAccess = Nothing + , _psCheckpointer = cp + , _psPdb = pdb + , _psBlockHeaderDb = bhdb + , _psMinerRewards = rs + , _psReorgLimit = _pactReorgLimit conf + , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout conf + , _psOnFatalError = defaultOnFatalError mempty + , _psVersion = v + , _psAllowReadsInLocal = _pactAllowReadsInLocal conf + , _psLogger = addLabel ("chain-id", chainIdToText cid) $ addLabel ("component", "pact") $ logger + , _psGasLogger = do + guard (_pactLogGas conf) + return + $ addLabel ("chain-id", chainIdToText cid) + $ addLabel ("component", "pact") + $ addLabel ("sub-component", "gas") + $ logger + + , _psBlockGasLimit = _pactNewBlockGasLimit conf + , _psEnableLocalTimeout = False + , _psTxFailuresCounter = Nothing + , _psTxTimeLimit = _pactTxTimeLimit conf + } + +freeGasModel :: TxContext -> GasModel +freeGasModel = const $ constGasModel 0 + +--- | A queue-less WebPactExecutionService (for all chains) +--- with direct chain access map for local. +withWebPactExecutionServiceCompaction + :: (Logger logger) + => logger + -> ChainwebVersion + -> PactServiceConfig + -> TestBlockDb + -> ChainMap MemPoolAccess + -> ((WebPactExecutionService, HM.HashMap ChainId (SQLiteEnv, PactExecutionService), WebPactExecutionService, HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) -> IO a) -- TODO: second 'WebPactExecutionService' seems unnecessary? + -> IO a +withWebPactExecutionServiceCompaction logger v pactConfig bdb mempools act = + withDbs $ \srcSqlEnvs -> + withDbs $ \targetSqlEnvs -> do + srcPacts <- mkPacts srcSqlEnvs + let srcWeb = mkWebPactExecutionService (snd <$> srcPacts) + + targetPacts <- mkPacts targetSqlEnvs + let targetWeb = mkWebPactExecutionService (snd <$> targetPacts) + + act (srcWeb, srcPacts, targetWeb, targetPacts) + where + mkPacts :: [SQLiteEnv] -> IO (HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) + mkPacts sqlEnvs = fmap HM.fromList + $ traverse (\(dbEnv, cid) -> (cid,) . (dbEnv,) <$> mkTestPactExecutionService dbEnv cid) + $ zip sqlEnvs + $ toList + $ chainIds v + + withDbs :: ([SQLiteEnv] -> IO x) -> IO x + withDbs f = foldl' (\soFar _ -> withDb soFar) f (chainIds v) [] + + withDb :: ([SQLiteEnv] -> IO x) -> [SQLiteEnv] -> IO x + withDb g envs = withTempSQLiteConnection chainwebPragmas $ \s -> g (s : envs) + + mkTestPactExecutionService :: () + => SQLiteEnv + -> ChainId + -> IO PactExecutionService + mkTestPactExecutionService sqlenv c = do + bhdb <- getBlockHeaderDb c bdb + ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig + return $ PactExecutionService + { _pactNewBlock = \_ m fill ph -> + evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph + , _pactContinueBlock = \_ bip -> + evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip + , _pactValidateBlock = \h d -> + evalPactServiceM_ ctx $ fst <$> execValidateBlock (mempools ^?! atChain c) h d + , _pactLocal = \pf sv rd cmd -> + evalPactServiceM_ ctx $ execLocal cmd pf sv rd + , _pactLookup = \_cid cd hashes -> + evalPactServiceM_ ctx $ execLookupPactTxs cd hashes + , _pactPreInsertCheck = \_ txs -> + evalPactServiceM_ ctx $ V.map (\_ -> Nothing) <$> execPreInsertCheckReq txs + , _pactBlockTxHistory = \h d -> + evalPactServiceM_ ctx $ execBlockTxHistory h d + , _pactHistoricalLookup = \h d k -> + evalPactServiceM_ ctx $ execHistoricalLookup h d k + , _pactSyncToBlock = \h -> + evalPactServiceM_ ctx $ execSyncToBlock h + , _pactReadOnlyReplay = \l u -> + evalPactServiceM_ ctx $ execReadOnlyReplay l u + } + +-- | A queue-less WebPactExecutionService (for all chains) +-- with direct chain access map for local. +withWebPactExecutionService + :: (Logger logger) + => logger + -> ChainwebVersion + -> PactServiceConfig + -> TestBlockDb + -> ChainMap MemPoolAccess + -> ((WebPactExecutionService,HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) -> IO a) + -> IO a +withWebPactExecutionService logger v pactConfig bdb mempools act = + withDbs $ \sqlenvs -> do + pacts <- fmap HM.fromList + $ traverse (\(dbEnv, cid) -> (cid,) . (dbEnv,) <$> mkPact dbEnv cid) + $ zip sqlenvs + $ toList + $ chainIds v + act (mkWebPactExecutionService (snd <$> pacts), pacts) + where + withDbs f = foldl' (\soFar _ -> withDb soFar) f (chainIds v) [] + withDb g envs = withTempSQLiteConnection chainwebPragmas $ \s -> g (s : envs) + + mkPact :: SQLiteEnv -> ChainId -> IO PactExecutionService + mkPact sqlenv c = do + bhdb <- getBlockHeaderDb c bdb + ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig + return $ PactExecutionService + { _pactNewBlock = \_ m fill ph -> + evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph + , _pactContinueBlock = \_ bip -> + evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip + , _pactValidateBlock = \h d -> + evalPactServiceM_ ctx $ fst <$> execValidateBlock (mempools ^?! atChain c) h d + , _pactLocal = \pf sv rd cmd -> + evalPactServiceM_ ctx $ execLocal cmd pf sv rd + , _pactLookup = \_cid cd hashes -> + evalPactServiceM_ ctx $ execLookupPactTxs cd hashes + , _pactPreInsertCheck = \_ txs -> + evalPactServiceM_ ctx $ V.map (\_ -> Nothing) <$> execPreInsertCheckReq txs + , _pactBlockTxHistory = \h d -> + evalPactServiceM_ ctx $ execBlockTxHistory h d + , _pactHistoricalLookup = \h d k -> + evalPactServiceM_ ctx $ execHistoricalLookup h d k + , _pactSyncToBlock = \h -> + evalPactServiceM_ ctx $ execSyncToBlock h + , _pactReadOnlyReplay = \l u -> + evalPactServiceM_ ctx $ execReadOnlyReplay l u + } + +-- | Noncer for 'runCut' +type Noncer = ChainId -> IO Nonce + +zeroNoncer :: Noncer +zeroNoncer = const (return $ Nonce 0) + +-- | Populate blocks for every chain of the current cut. Uses provided pact +-- service to produce a new block, add it to dbs, etc. +runCut + :: ChainwebVersion + -> TestBlockDb + -> WebPactExecutionService + -> GenBlockTime + -> Noncer + -> Miner + -> IO () +runCut v bdb pact genTime noncer miner = + forM_ (chainIds v) $ \cid -> do + ph <- ParentHeader <$> getParentTestBlockDb bdb cid + !newBlock <- throwIfNoHistory =<< _webPactNewBlock pact cid miner NewBlockFill ph + let pout = newBlockToPayloadWithOutputs newBlock + n <- noncer cid + + -- skip this chain if mining fails and retry with the next chain. + whenM (addTestBlockDb bdb (succ $ view blockHeight $ _parentHeader ph) n genTime cid pout) $ do + h <- getParentTestBlockDb bdb cid + void $ _webPactValidateBlock pact h (CheckablePayloadWithOutputs pout) + +initializeSQLite :: IO SQLiteEnv +initializeSQLite = open2 file >>= \case + Left (_err, _msg) -> + internalError "initializeSQLite: A connection could not be opened." + Right r -> return r + where + file = "" {- temporary sqlitedb -} + +freeSQLiteResource :: SQLiteEnv -> IO () +freeSQLiteResource sqlenv = void $ close_v2 sqlenv + +type WithPactCtxSQLite logger tbl = forall a . PactServiceM logger tbl a -> IO a + +-- | Used to run 'PactServiceM' functions directly on a database (ie not use checkpointer). +withPactCtxSQLite + :: (Logger logger, CanReadablePayloadCas tbl) + => logger + -> ChainwebVersion + -> IO BlockHeaderDb + -> IO (PayloadDb tbl) + -> PactServiceConfig + -> (WithPactCtxSQLite logger tbl -> TestTree) + -> TestTree +withPactCtxSQLite logger v bhdbIO pdbIO conf f = + withResource + initializeSQLite + freeSQLiteResource $ \io -> + withResource (start io) destroy $ \ctxIO -> f $ \toPact -> do + ctx <- ctxIO + evalPactServiceM_ ctx toPact + where + destroy = destroyTestPactCtx + cid = someChainId v + start ios = do + bhdb <- bhdbIO + pdb <- pdbIO + s <- ios + testPactCtxSQLite logger v cid bhdb pdb s conf + +toTxCreationTime :: Integral a => Time a -> TxCreationTime +toTxCreationTime (Time timespan) = TxCreationTime $ fromIntegral $ timeSpanToSeconds timespan + +-- | 'MemPoolAccess' that delegates all calls to the contents of provided `IORef`. +delegateMemPoolAccess :: IORef MemPoolAccess -> MemPoolAccess +delegateMemPoolAccess r = MemPoolAccess + { mpaGetBlock = \a b c d e -> call mpaGetBlock $ \f -> f a b c d e + , mpaSetLastHeader = \a -> call mpaSetLastHeader ($ a) + , mpaProcessFork = \a -> call mpaProcessFork ($ a) + , mpaBadlistTx = \a -> call mpaBadlistTx ($ a) + } + where + call :: (MemPoolAccess -> f) -> (f -> IO a) -> IO a + call f g = readIORef r >>= g . f + +-- | use a "delegate" which you can dynamically reset/modify. +-- Returns the updateable 'IORef MemPoolAccess` for use +-- in tests, and the plain 'MemPoolAccess' for initializing +-- PactService etc. +withDelegateMempool + :: (IO (IORef MemPoolAccess, MemPoolAccess) -> TestTree) + -> TestTree +withDelegateMempool = withResource' start + where + start = (id &&& delegateMemPoolAccess) <$> newIORef mempty + +-- | Set test mempool using IORef. +setMempool :: MonadIO m => IO (IORef MemPoolAccess) -> MemPoolAccess -> m () +setMempool refIO mp = liftIO (refIO >>= flip writeIORef mp) + +-- | Set test mempool wrapped with a "one shot" 'mpaGetBlock' adapter. +setOneShotMempool :: MonadIO m => IO (IORef MemPoolAccess) -> MemPoolAccess -> m () +setOneShotMempool mpRefIO mp = do + oneShot <- liftIO $ newIORef False + setMempool mpRefIO $ mp + { mpaGetBlock = \g v i a e -> readIORef oneShot >>= \case + False -> writeIORef oneShot True >> mpaGetBlock mp g v i a e + True -> mempty + } + +withBlockHeaderDb + :: IO RocksDb + -> BlockHeader + -> (IO BlockHeaderDb -> TestTree) + -> TestTree +withBlockHeaderDb iordb b = withResource start stop + where + start = do + rdb <- testRocksDb "withBlockHeaderDb" =<< iordb + testBlockHeaderDb rdb b + stop = closeBlockHeaderDb + +-- | Single-chain Pact via service queue. +-- +-- The difference between this and 'withPactTestBlockDb' is that, +-- this function takes a `SQLiteEnv` resource which it then exposes +-- to the test function. +-- +-- TODO: Consolidate these two functions. +withPactTestBlockDb' + :: ChainwebVersion + -> ChainId + -> RocksDb + -> IO SQLiteEnv + -> IO MemPoolAccess + -> PactServiceConfig + -> (IO (SQLiteEnv,PactQueue,TestBlockDb) -> TestTree) + -> TestTree +withPactTestBlockDb' version cid rdb sqlEnvIO mempoolIO pactConfig f = + withResourceT (mkTestBlockDb version rdb) $ \bdbio -> + withResource (startPact bdbio) stopPact $ f . fmap (view _2) + where + startPact bdbio = do + reqQ <- newPactQueue 2000 + bdb <- bdbio + sqlEnv <- sqlEnvIO + mempool <- mempoolIO + bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) cid + let pdb = _bdbPayloadDb bdb + a <- async $ runForever (\_ _ -> return ()) "Chainweb.Test.Pact4.Utils.withPactTestBlockDb" $ + runPactService version cid logger Nothing reqQ mempool bhdb pdb sqlEnv pactConfig + return (a, (sqlEnv,reqQ,bdb)) + + stopPact (a, _) = cancel a + + -- Ideally, we should throw 'error' when the logger is invoked, because + -- error logs should not happen in production and should always be resolved. + -- Unfortunately, that's not yet always the case. So we just drop the + -- message. + -- + logger = genericLogger Error (\_ -> return ()) + +withSqliteDb :: () + => ChainId + -> IO FilePath + -> (IO SQLiteEnv -> TestTree) + -> TestTree +withSqliteDb cid iodir s = withResource start stop s + where + start = do + dir <- iodir + startSqliteDb cid logger dir False + + stop env = do + stopSqliteDb env + + -- Ideally, we should throw 'error' when the logger is invoked, because + -- error logs should not happen in production and should always be resolved. + -- Unfortunately, that's not yet always the case. So we just drop the + -- message. + -- + logger = genericLogger Error (\_ -> return ()) + +-- | Single-chain Pact via service queue. +withPactTestBlockDb + :: ChainwebVersion + -> ChainId + -> RocksDb + -> IO MemPoolAccess + -> PactServiceConfig + -> (IO (SQLiteEnv,PactQueue,TestBlockDb) -> TestTree) + -> TestTree +withPactTestBlockDb version cid rdb mempoolIO pactConfig f = + withResourceT (withTempDir "pact-dir") $ \iodir -> + withResourceT (mkTestBlockDb version rdb) $ \bdbio -> + withResource (startPact bdbio iodir) stopPact $ f . fmap (view _2) + where + startPact bdbio iodir = do + reqQ <- newPactQueue 2000 + dir <- iodir + bdb <- bdbio + mempool <- mempoolIO + bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) cid + let pdb = _bdbPayloadDb bdb + sqlEnv <- startSqliteDb cid logger dir False + a <- async $ runForever (\_ _ -> return ()) "Chainweb.Test.Pact4.Utils.withPactTestBlockDb" $ + runPactService version cid logger Nothing reqQ mempool bhdb pdb sqlEnv pactConfig + return (a, (sqlEnv,reqQ,bdb)) + + stopPact (a, (sqlEnv, _, _)) = cancel a >> stopSqliteDb sqlEnv + + -- Ideally, we should throw 'error' when the logger is invoked, because + -- error logs should not happen in production and should always be resolved. + -- Unfortunately, that's not yet always the case. So we just drop the + -- message. + -- + logger = genericLogger Error (\_ -> return ()) + +dummyLogger :: GenericLogger +dummyLogger = genericLogger Error (error . T.unpack) + +stdoutDummyLogger :: GenericLogger +stdoutDummyLogger = genericLogger Error (putStrLn . T.unpack) + +hunitDummyLogger :: (String -> IO ()) -> GenericLogger +hunitDummyLogger f = genericLogger Error (f . T.unpack) + +someTestVersion :: ChainwebVersion +someTestVersion = instantCpmTestVersion petersenChainGraph + +someTestVersionHeader :: BlockHeader +someTestVersionHeader = someBlockHeader someTestVersion 10 + +-- | The runtime is linear in the requested height. This can be slow if a large +-- block height is requested for a chainweb version that simulates realtime +-- mining. It is fast enough for testing purposes with "fast" mining chainweb +-- versions like 'someTestVersion' for block heights up to, say, 1000. +-- +someBlockHeader :: ChainwebVersion -> BlockHeight -> BlockHeader +someBlockHeader v 0 = genesisBlockHeader v (unsafeChainId 0) +someBlockHeader v h = (!! (int h - 1)) + $ testBlockHeaders + $ ParentHeader + $ genesisBlockHeader v (unsafeChainId 0) + +-- | Get all pact user tables. +-- +-- Note: This consumes a stream. If you are writing a test +-- with very large pact states (think: Gigabytes), use +-- the streaming version of this function from +-- 'Chainweb.Pact.Backend.PactState'. +getPactUserTables :: Database -> IO (Map Text [PactRow]) +getPactUserTables db = fmap (M.map (List.sortOn (\pr -> (pr.rowKey, pr.txId)))) $ do + S.foldM_ + (\m tbl -> pure (M.insert tbl.name tbl.rows m)) + (pure M.empty) + pure + (PactState.getPactTables db) + +-- | Get active/latest pact state. +-- +-- Note: This consumes a stream. If you are writing a test +-- with very large pact states (think: Gigabytes), use +-- the streaming version of this function from +-- 'Chainweb.Pact.Backend.PactState'. +getLatestPactState :: Database -> IO (Map Text (Map ByteString ByteString)) +getLatestPactState db = do + S.foldM_ + (\m td -> pure (M.insert td.name td.rows m)) + (pure M.empty) + pure + (PactState.getLatestPactStateDiffable db) + +sigmaCompact :: () + => SQLiteEnv + -> SQLiteEnv + -> BlockHeight + -> IO () +sigmaCompact srcDb targetDb targetBlockHeight = do + Sigma.withDefaultLogger Warn $ \logger -> do + Sigma.compactPactState logger Sigma.defaultRetainment targetBlockHeight srcDb targetDb + +getPWOByHeader :: BlockHeader -> TestBlockDb -> IO PayloadWithOutputs +getPWOByHeader h (TestBlockDb _ pdb _) = + lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) >>= \case + Nothing -> throwM $ userError "getPWOByHeader: payload not found" + Just pwo -> return pwo + +throwIfNotPact4 :: Version.ForSomePactVersion f -> IO (f Version.Pact4) +throwIfNotPact4 h = case h of + Version.ForSomePactVersion Version.Pact4T a -> do + pure a + Version.ForSomePactVersion Version.Pact5T _ -> do + assertFailure "throwIfNotPact4: should be pact4" diff --git a/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs new file mode 100644 index 0000000000..29ed9eeea2 --- /dev/null +++ b/test/lib/Chainweb/Test/Pact4/VerifierPluginTest/Unit.hs @@ -0,0 +1,56 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Chainweb.Test.Pact4.VerifierPluginTest.Unit +( -- * test suite + tests +) where + +import Data.DoubleWord (Word256) + +import Test.Tasty +import Test.Tasty.HUnit +import Test.Tasty.QuickCheck + +import Test.QuickCheck.Instances () + +import Chainweb.Test.Utils (prop_iso) + +-- internal chainweb modules + +import Chainweb.VerifierPlugin.Hyperlane.Utils + +instance Arbitrary Word256 where + arbitrary = fromInteger . getNonNegative <$> arbitrary + +tests :: TestTree +tests = testGroup "Chainweb.Test.Pact4.VerifierPluginTest.Unit" + [ testCase "decimalToWord" hyperlaneDecimalToWord + , testCase "decimalToWord2" hyperlaneDecimalToWord2 + , testCase "wordToDecimal" hyperlaneWordToDecimal + , testCase "wordToDecimal2" hyperlaneWordToDecimal2 + , testProperty "wordDecimalRoundTrip" $ prop_iso @Word256 @_ decimalToWord wordToDecimal + ] + +hyperlaneDecimalToWord :: Assertion +hyperlaneDecimalToWord = assertEqual "" 10000000000000000000000000000000000000 (decimalToWord 10000000000000000000) + +hyperlaneDecimalToWord2 :: Assertion +hyperlaneDecimalToWord2 = assertEqual "" 10333333333333333000 (decimalToWord 10.333333333333333) + +hyperlaneWordToDecimal :: Assertion +hyperlaneWordToDecimal = assertEqual "" 3333333333333333333.333333333333333333 (wordToDecimal 3333333333333333333333333333333333333) + +hyperlaneWordToDecimal2 :: Assertion +hyperlaneWordToDecimal2 = assertEqual "" 10.333333333333333 (wordToDecimal 10333333333333333000) diff --git a/test/unit/Chainweb/Test/Pact4/DbCacheTest.hs b/test/unit/Chainweb/Test/Pact4/DbCacheTest.hs new file mode 100644 index 0000000000..52cc6f07af --- /dev/null +++ b/test/unit/Chainweb/Test/Pact4/DbCacheTest.hs @@ -0,0 +1,86 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE FlexibleContexts #-} + +module Chainweb.Test.Pact4.DbCacheTest (tests) where + +import Chainweb.Pact.Backend.DbCache + +import Control.Monad (void) +import Control.Monad.State.Strict + +import Data.Aeson +import Data.ByteString.Lazy (toStrict) + +import Database.SQLite3.Direct + +import GHC.Compact + +import Test.Tasty +import Test.Tasty.HUnit + +tests :: TestTree +tests = testGroup "Chainweb.Test.Pact4.DbCacheTest" + [ testCache ] + +entry :: MonadIO m => String -> m ([String], Int) +entry c = do + s <- liftIO $ compactSize =<< compact [c] + return ([c], fromIntegral s) + +testCache :: TestTree +testCache = testCase "testCache" $ do + + -- Create Items + (a, sa) <- entry "a" + (b0, sb0) <- entry "b0" + (b1, sb1) <- entry "b1" + (c, sc) <- entry "c" + (d, sd) <- entry "d" + + -- cache size (enough to hold a + b0 + b1 + c) + let cs = DbCacheLimitBytes . fromIntegral $ sa + sb0 + sb1 + sc + 1 + + void $ (`runStateT` (emptyDbCache cs :: DbCache [String])) $ do + + -- a: simple insert + doCheck "a insert @1 -> [a@1]" "a" a 1 + assertEqual' "size a" sa cacheSize + assertEqual' "count 1" 1 cacheCount + + -- b0: simple insert + doCheck "b->v0 insert @2 -> [a@1,b0@2]" "b" b0 2 + assertEqual' "size a + b0" (sa + sb0) cacheSize + assertEqual' "count 2" 2 cacheCount + + -- b1: insert with different values, same key+txid + doCheck "b->v1 insert @2 -> [a@1,b0@2,b1@2]" "b" b1 2 + assertEqual' "size a + b0 + b1" (sa + sb0 + sb1) cacheSize + assertEqual' "count 3" 3 cacheCount + + -- c: big insert + doCheck "c insert @3 -> [a@1,b0@2,b1@2,c@3]" "c" c 3 + assertEqual' "size a + b0 + b1 + c" (sa + sb0 + sb1 + sc) cacheSize + assertEqual' "count 4" 4 cacheCount + + -- d: small insert to trip limit, evict + doCheck "d insert @4, evict a@1 -> [b0@2,b1@2,c@3,d@4]" "d" d 4 + assertEqual' "size b0 + b1 + c + d" (sb0 + sb1 + sc + sd) cacheSize + assertEqual' "count 4" 4 cacheCount + + -- hit b->v0 to avoid eviction, cache stats unchanged + doCheck "b->v0 hit @5 -> [b1@2,c@3,d@4,b0@5]" "b" b0 5 + assertEqual' "size unchanged" (sb1 + sc + sd + sb0) cacheSize + assertEqual' "count unchanged" 4 cacheCount + + -- reinsert a to evict b->v1, c + doCheck "a reinsert, evict b->v1@2 -> [c@3,d@4,b0@5,a@6]" "a" a 6 + assertEqual' "size c + d + b0 + a) - a - b" (sc + sd + sb0 + sa) cacheSize + assertEqual' "count 4" 4 cacheCount + + where + + doCheck msg k v txid = do + mc <- StateT (checkDbCache (Utf8 k) decodeStrict (toStrict (encode v)) txid) + liftIO $ assertEqual msg (Just v) mc + + assertEqual' msg ex act = get >>= liftIO . assertEqual msg ex . act diff --git a/test/unit/Chainweb/Test/Pact4/GrandHash.hs b/test/unit/Chainweb/Test/Pact4/GrandHash.hs new file mode 100644 index 0000000000..f2eb40553c --- /dev/null +++ b/test/unit/Chainweb/Test/Pact4/GrandHash.hs @@ -0,0 +1,190 @@ +{-# LANGUAGE BinaryLiterals #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} + +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Chainweb.Test.Pact4.GrandHash + ( tests + ) + where + +import Data.ByteArray qualified as Memory +import Data.Functor.Identity (Identity(..)) +import Data.ByteString.Base16 qualified as Base16 +import Data.Maybe (mapMaybe) +import Crypto.Hash (hashWith) +import Data.List qualified as List +import Chainweb.Pact.Backend.PactState (PactRow(..), Table(..)) +import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (TableHash(..), rowToHashInput, hashTable, tableNameToHashInput, hashStream, hashAlgorithm) +import Control.Monad (replicateM) +import Data.ByteString (ByteString) +import Data.ByteString.Builder qualified as BB +import Data.ByteString.Lazy qualified as BL +import Data.ByteString qualified as BS +import Data.Bytes qualified as Bytes +import Data.Bytes.Parser (Parser) +import Data.Bytes.Parser qualified as Smith +import Data.Bytes.Parser.Ascii qualified as Smith +import Data.Bytes.Parser.LittleEndian qualified as SmithLE +import Data.Int (Int64) +import Data.Text (Text) +import Data.Text qualified as Text +import Data.Text.Encoding qualified as Text +import Data.Vector qualified as Vector +import Data.Word (Word8, Word32, Word64) +import Streaming.Prelude qualified as S +import Test.QuickCheck (Property, Arbitrary, Gen, Positive(..), (===), arbitrary, elements) +import Test.Tasty (TestTree) +import Test.Tasty.HUnit (Assertion, testCase, (@?=)) +import Test.Tasty.QuickCheck (testProperty) + +import Chainweb.Test.Utils + +tests :: TestTree +tests = + independentSequentialTestGroup "Chainweb.Test.Pact4.GrandHash" + [ testCase "PactRow hash input roundtrip - habibti ascii" (testPactRow habibtiAscii) + , testCase "PactRow hash input roundtrip - habibti utf8" (testPactRow habibtiUtf8) + , testProperty "PactRow hash input roundtrip - arbitrary utf8" propPactRowHashInputRoundtrip + , testProperty "Table hash input roundtrip - arbitrary utf8" propTableHashInputRoundtrip + , testProperty "Table hash: incremental equals non-incremental" propHashWholeTableEqualsIncremental + , testProperty "Grand Hash of tables: incremental equals non-incremental" propHashWholeChainEqualsIncremental + ] + +habibtiAscii :: PactRow +habibtiAscii = PactRow + { rowKey = Text.encodeUtf8 "Habibti" + , rowData = Text.encodeUtf8 "Asophiel" + , txId = 2100 + } + +habibtiUtf8 :: PactRow +habibtiUtf8 = PactRow + { rowKey = Text.encodeUtf8 "حبيبتي" + , rowData = Text.encodeUtf8 "Kaspitell" + , txId = 2300 + } + +propPactRowHashInputRoundtrip :: PactRow -> Property +propPactRowHashInputRoundtrip row = + Right row === parseRowHashInput (rowToHashInput row) + +propTableHashInputRoundtrip :: Text -> Property +propTableHashInputRoundtrip tablename = + Right (len, tblName) === parseTableHashInput (tableNameToHashInput tablename) + where + tblName = Text.encodeUtf8 (Text.toLower tablename) + len = fromIntegral @Int @Word64 (BS.length tblName) + +propHashWholeTableEqualsIncremental :: Table -> Property +propHashWholeTableEqualsIncremental tbl = + let + incrementalHash :: Maybe TableHash + incrementalHash = hashTable tbl.name + $ Vector.fromList + $ List.sortOn (\pr -> pr.rowKey) tbl.rows + + wholeHash :: Maybe ByteString + wholeHash = testHashTableNotIncremental tbl + in + fmap (hex . getTableHash) incrementalHash === fmap hex wholeHash + +testHashTableNotIncremental :: Table -> Maybe ByteString +testHashTableNotIncremental tbl = if null tbl.rows + then Nothing + else Just + $ Memory.convert + $ hashWith hashAlgorithm + $ BL.toStrict + $ BB.toLazyByteString + $ (BB.byteString (tableNameToHashInput tbl.name) <>) + $ foldMap (BB.byteString . rowToHashInput) + $ List.sortOn (\pr -> pr.rowKey) tbl.rows + +propHashWholeChainEqualsIncremental :: [Table] -> Property +propHashWholeChainEqualsIncremental tbls = + let + sortedTables = List.sortOn (\tbl -> tbl.name) tbls + tableHashes = mapMaybe testHashTableNotIncremental sortedTables + + incrementalHash = fst $ runIdentity $ hashStream @Identity (S.each tableHashes) + + wholeHash = Memory.convert $ hashWith hashAlgorithm $ BS.concat tableHashes + in + incrementalHash === wholeHash + +instance Arbitrary PactRow where + arbitrary = genPactRow + +instance Arbitrary Table where + arbitrary = genTable + +genTable :: Gen Table +genTable = do + tblNameLen <- elements @Int [3 .. 20] + tblName <- Text.pack <$> replicateM tblNameLen (arbitrary @Char) + + numRows <- elements @Int [1 .. 10] + tblRows <- replicateM numRows genPactRow + + pure $ Table + { name = tblName + , rows = tblRows + } + +genPactRow :: Gen PactRow +genPactRow = do + txid <- arbitrary @Word32 + rkLen <- fmap (fromIntegral @_ @Int . getPositive) $ arbitrary @(Positive Word8) + rdLen <- fmap (fromIntegral @_ @Int . getPositive) $ arbitrary @(Positive Word8) + + rk <- Text.pack <$> replicateM rkLen (arbitrary @Char) + rd <- Text.pack <$> replicateM rdLen (arbitrary @Char) + + pure $ PactRow + { rowKey = Text.encodeUtf8 rk + , txId = fromIntegral @Word32 @Int64 txid + , rowData = Text.encodeUtf8 rd + } + +testPactRow :: PactRow -> Assertion +testPactRow row = do + Right row @?= parseRowHashInput (rowToHashInput row) + +parseRowHashInput :: ByteString -> Either Text PactRow +parseRowHashInput b = Smith.parseBytesEither parser (Bytes.fromByteString b) + where + parser :: Parser Text s PactRow + parser = do + _ <- Smith.char "rowkey tag" 'K' + rkLen <- SmithLE.word64 "rowkey len" + rk <- Smith.take "rowkey" (fromIntegral @Word64 @Int rkLen) + + _ <- Smith.char "txid tag" 'I' + txid <- SmithLE.word64 "txid" + + _ <- Smith.char "rowdata tag" 'D' + rdLen <- SmithLE.word64 "rowdata len" + rd <- Smith.take "rowdata" (fromIntegral @Word64 @Int rdLen) + + pure $ PactRow + { rowKey = Bytes.toByteString rk + , txId = fromIntegral @Word64 @Int64 txid + , rowData = Bytes.toByteString rd + } + +parseTableHashInput :: ByteString -> Either Text (Word64, ByteString) +parseTableHashInput b = Smith.parseBytesEither parser (Bytes.fromByteString b) + where + parser :: Parser Text s (Word64, ByteString) + parser = do + _ <- Smith.char "tablename tag" 'T' + len <- SmithLE.word64 "tablename len" + tablename <- Smith.take "tablename" (fromIntegral @Word64 @Int len) + pure (len, Bytes.toByteString tablename) + +hex :: ByteString -> Text +hex = Text.decodeUtf8 . Base16.encode diff --git a/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs b/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs new file mode 100644 index 0000000000..f1d13dc076 --- /dev/null +++ b/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs @@ -0,0 +1,37 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- | +-- Module: Chainweb.Test.Pact4.NoCoinbase +-- Copyright: Copyright © 2020 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- TODO +-- +module Chainweb.Test.Pact4.NoCoinbase +( tests +) where + +import qualified Pact.JSON.Encode as J +import Pact.Types.Command +import Pact.Types.Hash + +import Test.Tasty +import Test.Tasty.HUnit + +-- internal modules + +import Chainweb.Pact4.NoCoinbase +import Chainweb.Payload + +tests :: TestTree +tests = testGroup "Chainweb.Test.Pact4.NoCoinbase" + [testCase "noCoinbaseOutput is consistent" test_noCoinbase] + +test_noCoinbase :: Assertion +test_noCoinbase = + noCoinbaseOutput + @=? + CoinbaseOutput (J.encodeStrict (noCoinbase :: CommandResult Hash)) diff --git a/test/unit/Chainweb/Test/Pact4/RewardsTest.hs b/test/unit/Chainweb/Test/Pact4/RewardsTest.hs new file mode 100644 index 0000000000..236bf39381 --- /dev/null +++ b/test/unit/Chainweb/Test/Pact4/RewardsTest.hs @@ -0,0 +1,40 @@ + +module Chainweb.Test.Pact4.RewardsTest +( tests +) where + + +import Test.Tasty +import Test.Tasty.HUnit + +import Chainweb.Graph +import Chainweb.MinerReward +import Chainweb.Test.TestVersions +import Chainweb.Version + +v :: ChainwebVersion +v = instantCpmTestVersion petersenChainGraph + +tests :: TestTree +tests = testGroup "Chainweb.Test.Pact4.RewardsTest" + [ testGroup "Miner Rewards Unit Tests" + [ rewardsTest + ] + ] + +rewardsTest :: HasCallStack => TestTree +rewardsTest = testCaseSteps "rewards" $ \step -> do + + let k = _kda . minerRewardKda . blockMinerReward v + + step "block heights below initial threshold" + let a = k 0 + assertEqual "initial miner reward is 2.304523" 2.304523 a + + step "block heights at threshold" + let b = k 87600 + assertEqual "max threshold miner reward is 2.304523" 2.304523 b + + step "block heights exceeding thresholds change" + let c = k 87601 + assertEqual "max threshold miner reward is 2.297878" 2.297878 c diff --git a/test/unit/Chainweb/Test/Pact4/SQLite.hs b/test/unit/Chainweb/Test/Pact4/SQLite.hs new file mode 100644 index 0000000000..d96f353dfb --- /dev/null +++ b/test/unit/Chainweb/Test/Pact4/SQLite.hs @@ -0,0 +1,307 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} + +-- | +-- Module: Chainweb.Test.Pact4.SQLite +-- Copyright: Copyright © 2022 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- TODO +-- +module Chainweb.Test.Pact4.SQLite +( tests +) where + +import Control.Concurrent.MVar +import Control.Monad +import Control.Monad.Trans.State + +import Data.Bifunctor +import qualified Data.ByteString as B +import qualified Data.ByteString.Short as BS +import Data.Coerce +import qualified Data.Hash.SHA3 as SHA3 +import Data.Hash.SHA3 (Sha3_224(..), Sha3_256(..), Sha3_384(..), Sha3_512(..)) +import qualified Data.List as L +import Data.String + +import Pact.Types.SQLite + +import System.IO.Unsafe +import System.Random (uniformByteString, getStdRandom) + +import Test.Hash.SHA3 +import Test.Tasty +import Test.Tasty.HUnit + +-- internal modules + + +import Chainweb.Test.Utils +import Chainweb.Pact.Backend.Types (SQLiteEnv) + +-- -------------------------------------------------------------------------- -- +-- Tests + +tests :: TestTree +tests = withResourceT withInMemSQLiteResource $ \dbIO -> + withResource' (dbIO >>= newMVar) $ \dbVarIO -> + let run = runMsgTest dbVarIO [] + runMonte = runMonteTest dbVarIO [] + + -- Split input + runVar = runMsgTest dbVarIO [1,2,17] + runMonteVar = runMonteTest dbVarIO [1,2,17] + + in testGroup "SQL Tests" + [ testGroup "sha3 single argument" + [ testGroup "ShortMsg" + [ testCase "-" $ run 0 sha3_256ShortMsg + , testCase "224" $ run 224 sha3_224ShortMsg + , testCase "256" $ run 256 sha3_256ShortMsg + , testCase "384" $ run 384 sha3_384ShortMsg + , testCase "512" $ run 512 sha3_512ShortMsg + ] + , testGroup "LongMsg" + [ testCase "-" $ run 0 sha3_256LongMsg + , testCase "224" $ run 224 sha3_224LongMsg + , testCase "256" $ run 256 sha3_256LongMsg + , testCase "384" $ run 384 sha3_384LongMsg + , testCase "512" $ run 512 sha3_512LongMsg + ] + , testGroup "Monte" + [ testCase "-" $ runMonte 0 sha3_256Monte + , testCase "224" $ runMonte 224 sha3_224Monte + , testCase "256" $ runMonte 256 sha3_256Monte + , testCase "384" $ runMonte 384 sha3_384Monte + , testCase "512" $ runMonte 512 sha3_512Monte + ] + ] + , testGroup "sha3 multiple arguments" + [ testGroup "ShortMsg" + [ testCase "-" $ runVar 0 sha3_256ShortMsg + , testCase "224" $ runVar 224 sha3_224ShortMsg + , testCase "256" $ runVar 256 sha3_256ShortMsg + , testCase "384" $ runVar 384 sha3_384ShortMsg + , testCase "512" $ runVar 512 sha3_512ShortMsg + ] + , testGroup "LongMsg" + [ testCase "-" $ runVar 0 sha3_256LongMsg + , testCase "224" $ runVar 224 sha3_224LongMsg + , testCase "256" $ runVar 256 sha3_256LongMsg + , testCase "384" $ runVar 384 sha3_384LongMsg + , testCase "512" $ runVar 512 sha3_512LongMsg + ] + , testGroup "Monte" + [ testCase "-" $ runMonteVar 0 sha3_256Monte + , testCase "224" $ runMonteVar 224 sha3_224Monte + , testCase "256" $ runMonteVar 256 sha3_256Monte + , testCase "384" $ runMonteVar 384 sha3_384Monte + , testCase "512" $ runMonteVar 512 sha3_512Monte + ] + ] + , withAggTable dbVarIO 512 128 $ \tbl -> testGroup "sha3 aggregation" + [ testCase "-" $ testAgg 0 dbVarIO tbl + , testCase "224" $ testAgg 224 dbVarIO tbl + , testCase "256" $ testAgg 256 dbVarIO tbl + , testCase "384" $ testAgg 384 dbVarIO tbl + , testCase "512" $ testAgg 512 dbVarIO tbl + ] + , testGroup "sha3 msgTable" + [ testCase "-" $ msgTableTest dbVarIO 0 sha3_256ShortMsg + , testCase "224" $ msgTableTest dbVarIO 224 sha3_224ShortMsg + , testCase "256" $ msgTableTest dbVarIO 256 sha3_256ShortMsg + , testCase "384" $ msgTableTest dbVarIO 384 sha3_384ShortMsg + , testCase "512" $ msgTableTest dbVarIO 512 sha3_512ShortMsg + ] + , testGroup "sha3 monteTable" + [ testCase "-" $ monteTableTest dbVarIO 0 sha3_256Monte + , testCase "sha224" $ monteTableTest dbVarIO 224 sha3_224Monte + , testCase "sha256" $ monteTableTest dbVarIO 256 sha3_256Monte + , testCase "sha384" $ monteTableTest dbVarIO 384 sha3_384Monte + , testCase "sha512" $ monteTableTest dbVarIO 512 sha3_512Monte + ] + ] + +-- -------------------------------------------------------------------------- -- +-- + +sha :: IsString a => Monoid a => Int -> a +sha 0 = "sha3" +sha i = "sha3_" <> fromString (show i) + +shaa :: IsString a => Monoid a => Int -> a +shaa 0 = "sha3a" +shaa i = "sha3a_" <> fromString (show i) + +-- -------------------------------------------------------------------------- -- +-- + +runMsgTest :: IO (MVar SQLiteEnv) -> [Int] -> Int -> MsgFile -> IO () +runMsgTest dbVarIO splitArg n f = do + dbVar <- dbVarIO + withMVar dbVar $ \db -> do + msgAssert (\_ a b -> a @?= b) (sqliteSha3 db n splitArg) f + +runMonteTest :: IO (MVar SQLiteEnv) -> [Int] -> Int -> MonteFile -> IO () +runMonteTest dbVarIO splitArg n f = do + dbVar <- dbVarIO + withMVar dbVar $ \db -> do + monteAssert (\_ a b -> a @?= b) (sqliteSha3 db n splitArg) f + +-- -------------------------------------------------------------------------- -- +-- Repeated use in a query + +msgTableTest :: IO (MVar SQLiteEnv) -> Int -> MsgFile -> IO () +msgTableTest dbVarIO n msgFile = do + dbVar <- dbVarIO + withMVar dbVar $ \db -> do + msgTable db name msgFile + rows <- qry_ db query [RInt] + h <- case rows of + [[SInt r]] -> return r + [[x]] -> error $ "unexpected return value: " <> show x + [a] -> error $ "unexpected number of result fields: " <> show (length a) + a -> error $ "unexpected number of result rows: " <> show (length a) + h @?= 0 + exec_ db ("DROP TABLE " <> fromString name) + where + query = "SELECT sum(" <> sha n <> "(substr(msg,1,len)) != md) FROM " <> fromString name + name = "msgTable_" <> show n + +msgTable :: SQLiteEnv -> String -> MsgFile -> IO () +msgTable db name msgFile = do + exec_ db ("CREATE TABLE " <> tbl <> " (len INT, msg BLOB, md BLOB)") + forM_ (_msgVectors msgFile) $ \i -> do + let l = fromIntegral $ _msgLen i + exec' + db + ("INSERT INTO " <> tbl <> " VALUES (?, ?, ?)") + [SInt l, SBlob (_msgMsg i), SBlob (_msgMd i)] + where + tbl = fromString name + +-- -------------------------------------------------------------------------- -- +-- Repeated use in query for MonteFile + +monteTableTest :: IO (MVar SQLiteEnv) -> Int -> MonteFile -> IO () +monteTableTest dbVarIO n monteFile = do + dbVar <- dbVarIO + withMVar dbVar $ \db -> + monteTableTest_ db n monteFile + +monteTableTest_ :: SQLiteEnv -> Int -> MonteFile -> IO () +monteTableTest_ db n monteFile = do + monteTable db monteTableName monteFile + let query = fromString $ unwords + [ "WITH RECURSIVE" + , " tmp(c, m) AS (" + , " SELECT 0, ? UNION ALL SELECT c + 1, " <> sha n <> "(m) FROM tmp" + , " WHERE c <= 100000" + , " )," + , " tmp2(count, md) AS (" + , " SELECT c / 1000 - 1 AS count, m AS md FROM tmp" + , " WHERE c % 1000 == 0 AND count >= 0" + , " )" + , "SELECT" + , " sum(tmp2.md != " <> monteTableName <> ".md)" + , "FROM tmp2" + , "LEFT JOIN " <> monteTableName + , "ON tmp2.count = " <> monteTableName <> ".count" + ] + rows <- qry db query [SBlob $ _monteSeed monteFile] [RInt] + case rows of + [[SInt r]] -> r @?= 0 + [[x]] -> error $ "unexpected return value: " <> show x + [a] -> error $ "unexpected number of result fields: " <> show (length a) + a -> error $ "unexpected number of result rows: " <> show (length a) + where + monteTableName = "monteTable_" <> show n + +monteTable :: SQLiteEnv -> String -> MonteFile -> IO () +monteTable db name monteFile = do + exec_ db ("CREATE TABLE " <> tbl <> " (count INT, md BLOB)") + forM_ (_monteVectors monteFile) $ \i -> do + exec' + db + ("INSERT INTO " <> tbl <> " VALUES (?, ?)") + [SInt (fromIntegral $ _monteCount i), SBlob (_monteMd i)] + where + tbl = fromString name + +-- -------------------------------------------------------------------------- -- +-- Aggregate functions +-- +-- split a large input accross table rows + +withAggTable + :: IO (MVar SQLiteEnv) + -> Int + -> Int + -> (IO (String, [B.ByteString]) -> TestTree) + -> TestTree +withAggTable dbVarIO rowCount chunkSize = + withResource' createAggTable + where + tbl = "bytesTbl" + createAggTable = do + dbVar <- dbVarIO + withMVar dbVar $ \db -> do + input <- getStdRandom $ runState $ + replicateM rowCount $ state (uniformByteString chunkSize) + exec_ db ("CREATE TABLE " <> fromString tbl <> " (bytes BLOB)") + forM_ input $ \i -> + exec' db ("INSERT INTO " <> fromString tbl <> " VALUES(?)") [SBlob i] + return (tbl, input) + +testAgg :: Int -> IO (MVar SQLiteEnv) -> IO (String, [B.ByteString]) -> IO () +testAgg n dbVarIO tblIO = do + dbVar <- dbVarIO + (tbl, input) <- first fromString <$> tblIO + withMVar dbVar $ \db -> do + rows <- qry_ db ("SELECT " <> shaa n <> "(bytes) FROM " <> tbl) [RBlob] + h <- case rows of + [[SBlob r]] -> return r + [[x]] -> error $ "unexpected return value: " <> show x + [a] -> error $ "unexpected number of result fields: " <> show (length a) + a -> error $ "unexpected number of result rows: " <> show (length a) + + hBytes <- hash n (mconcat input) + h @?= hBytes + where + hash :: Int -> B.ByteString -> IO B.ByteString + hash d b = case d of + 0 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_256 b + 224 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_224 b + 256 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_256 b + 384 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_384 b + 512 -> hashToByteString <$> SHA3.hashByteString @SHA3.Sha3_512 b + _ -> error $ "unsupported SHA3 digest size: " <> show d + +hashToByteString :: (SHA3.Hash a, Coercible a BS.ShortByteString) => a -> B.ByteString +hashToByteString = BS.fromShort . coerce + +-- -------------------------------------------------------------------------- -- +-- SHA3 Implementation + +sqliteSha3 :: SQLiteEnv -> Int -> [Int] -> B.ByteString -> B.ByteString +sqliteSha3 db n argSplit arg = unsafePerformIO $ do + rows <- qry db queryStr params [RBlob] + case rows of + [[SBlob r]] -> return r + [[x]] -> error $ "unexpected return value: " <> show x + [a] -> error $ "unexpected number of result fields: " <> show (length a) + a -> error $ "unexpected number of result rows: " <> show (length a) + where + argN = length argSplit + argStr = fromString $ L.intercalate "," $ replicate (argN + 1) "?" + + queryStr = "select " <> sha n <> "(" <> argStr <> ")" + + params = go argSplit arg + + go [] l = [SBlob l] + go (h:t) bs = let (a,b) = B.splitAt h bs in SBlob a : go t b diff --git a/test/unit/Chainweb/Test/Pact4/TransactionTests.hs b/test/unit/Chainweb/Test/Pact4/TransactionTests.hs new file mode 100644 index 0000000000..88334342bb --- /dev/null +++ b/test/unit/Chainweb/Test/Pact4/TransactionTests.hs @@ -0,0 +1,410 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE ScopedTypeVariables #-} + +-- I have no idea why this warning is being triggered +-- for things that are clearly used +{-# options_ghc -fno-warn-unused-top-binds #-} + +-- | +-- Module: Chainweb.Test.BlockHeaderDB +-- Copyright: Copyright © 2018 Kadena LLC. +-- License: MIT +-- Maintainer: Emily Pillmore +-- Stability: experimental +-- +-- Test func in TransactionExec +-- +module Chainweb.Test.Pact4.TransactionTests ( tests ) where + +import Test.Tasty +import Test.Tasty.HUnit + +import Control.Concurrent (readMVar) +import Control.Lens hiding ((.=)) +import Control.Monad + +import Data.Aeson +import Data.Aeson.Lens +import Data.Foldable (for_, traverse_) +import Data.Function (on) +import Data.List (intercalate) +import Data.Text (Text,isInfixOf,unpack) +import qualified System.LogLevel as L + +-- internal pact modules + +import Pact.Gas +import Pact.Interpreter +import Pact.Parse +import Pact.Repl +import Pact.Repl.Types +import Pact.Types.Command +import qualified Pact.Types.Hash as H +import Pact.Types.PactValue +import Pact.Types.RPC +import Pact.Types.Runtime +import Pact.Types.SPV + +-- internal chainweb modules + +import Chainweb.BlockCreationTime +import Chainweb.BlockHeader.Internal +import Chainweb.BlockHeight +import Chainweb.Logger +import Chainweb.Miner.Pact +import Chainweb.Pact4.Templates +import Chainweb.Pact4.TransactionExec +import qualified Chainweb.Pact4.Types as Pact4 + +import Chainweb.Test.Utils +import Chainweb.Test.TestVersions +import Chainweb.Time +import Chainweb.Utils +import Chainweb.Version as V +import Chainweb.Version.RecapDevelopment +import Chainweb.Version.Mainnet +import Chainweb.Test.Pact4.Utils +import qualified Chainweb.Pact4.ModuleCache as Pact4 + + +-- ---------------------------------------------------------------------- -- +-- Global settings + +v :: ChainwebVersion +v = RecapDevelopment + +coinReplV1 :: FilePath +coinReplV1 = "pact/coin-contract/v1/coin.repl" + +coinReplV4 :: FilePath +coinReplV4 = "pact/coin-contract/v4/coin-v4.repl" + +coinReplV5 :: FilePath +coinReplV5 = "pact/coin-contract/v5/coin-v5.repl" + +coinReplV6 :: FilePath +coinReplV6 = "pact/coin-contract/coin.repl" + +nsReplV1 :: FilePath +nsReplV1 = "pact/namespaces/v1/ns.repl" + +nsReplV2 :: FilePath +nsReplV2 = "pact/namespaces/ns.repl" + +logger :: GenericLogger +#if DEBUG_TEST +logger = genericLogger L.Info (step . T.unpack) +#else +logger = genericLogger L.Error (\_ -> return ()) +#endif + +-- ---------------------------------------------------------------------- -- +-- Tests + +tests :: TestTree +tests = testGroup "Chainweb.Test.Pact4.TransactionTests" + [ testGroup "Pact Command Parsing" + [ testCase "Build Exec with Data" buildExecWithData + , testCase "Build Exec without Data" buildExecWithoutData + ] + , testGroup "Pact Code Unit Tests" + [ testGroup "Coin Contract repl tests" + [ testCase "v1" (ccReplTests coinReplV1) + -- v2 and v3 repl tests were consolidated in v4 + , testCase "v4" (ccReplTests coinReplV4) + , testCase "v5" (ccReplTests coinReplV5) + , testCase "v6" (ccReplTests coinReplV6) + ] + , testGroup "Namespace repl unit tests" + [ testCase "Ns-v1 repl tests" $ ccReplTests nsReplV1 + , testCase "Ns-v2 repl tests" $ ccReplTests nsReplV2 + ] + , testCase "Payer Repl Tests" (ccReplTests "pact/gas-payer/gas-payer-v1.repl") + ] + , testGroup "Precompiled Statements Tests" + [ testCase "Basic Injection Test" baseInjTest + , testCase "Fixed Injection Test" fixedInjTest + ] + -- , testGroup "Coinbase Vuln Fix Tests" + -- [ testCoinbase797DateFix + -- , testCase "testCoinbaseEnforceFailure" testCoinbaseEnforceFailure + -- , testCase "testCoinbaseUpgradeDevnet0" (testCoinbaseUpgradeDevnet (unsafeChainId 0) 3) + -- , testCase "testCoinbaseUpgradeDevnet1" (testCoinbaseUpgradeDevnet (unsafeChainId 1) 4) + -- ] + -- , testGroup "20-Chain Fork Upgrade Tests" + -- [ testTwentyChainDevnetUpgrades + -- ] + ] + +-- ---------------------------------------------------------------------- -- +-- Coin Contract repl tests + +ccReplTests :: FilePath -> Assertion +ccReplTests ccFile = do + (r, rst) <- execScript' Quiet ccFile + either fail (\_ -> execRepl rst) r + where + execRepl rst = do + lst <- readMVar $! _eePactDbVar . _rEnv $ rst + for_ (_rlsTests lst) $ \tr -> + traverse_ (uncurry failCC) $ trFailure tr + + failCC i e = assertFailure $ renderInfo (_faInfo i) <> ": " <> unpack e + +loadCC :: FilePath -> IO (PactDbEnv LibState, Pact4.ModuleCache) +loadCC = loadScript + +loadScript :: FilePath -> IO (PactDbEnv LibState, Pact4.ModuleCache) +loadScript fp = do + (r, rst) <- execScript' Quiet fp + either fail (const $ return ()) r + let pdb = PactDbEnv + (view (rEnv . eePactDb) rst) + (view (rEnv . eePactDbVar) rst) + mc = view (rEvalState . evalRefs . rsLoadedModules) rst + -- TODO: setup eval env & run the code & and pass + return (pdb, Pact4.moduleCacheFromHashMap mc) + +-- ---------------------------------------------------------------------- -- +-- Template vuln tests + +baseInjTest :: Assertion +baseInjTest = mkCoinbaseCmd badMinerId minerKeys0 (ParsedDecimal 1.0) >>= \case + ExecMsg (ParsedCode pccode _pcexps) _pmdata -> + assertEqual "Precompiled exploit yields correct code" (unpack pccode) exploit + where + exploit = "(coin.coinbase \"alpha\" (read-keyset \"miner-keyset\") 9999999.99)" + <> "(coin.coinbase \"alpha\" (read-keyset \"miner-keyset\") (read-decimal \"reward\"))" + +fixedInjTest :: Assertion +fixedInjTest = case exec of + ExecMsg (ParsedCode pccode _pcexps) _pmdata + | isInfixOf "coinbase" pccode -> assertFailure + $ "Precompiled statement contains exploitable code: " + <> unpack pccode + | isInfixOf "read-keyset" pccode -> assertFailure + $ "Precompiled statement contains exploitable code: " + <> unpack pccode + | otherwise -> return () + where + (_, exec) = mkCoinbaseTerm badMinerId minerKeys0 (ParsedDecimal 1.0) + + +buildExecWithData :: Assertion +buildExecWithData = void $ buildExecParsedCode maxBound + (Just $ object [ "data" .= (1 :: Int) ]) "(+ 1 1)" + +buildExecWithoutData :: Assertion +buildExecWithoutData = void $ buildExecParsedCode maxBound Nothing "(+ 1 1)" + +badMinerId :: MinerId +badMinerId = MinerId "alpha\" (read-keyset \"miner-keyset\") 9999999.99)(coin.coinbase \"alpha" + +minerKeys0 :: MinerKeys +minerKeys0 = MinerKeys $ mkKeySet + ["f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27"] + "default" + +-- ---------------------------------------------------------------------- -- +-- Vuln 792 fork tests + +testCoinbase797DateFix :: TestTree +testCoinbase797DateFix = testCaseSteps "testCoinbase791Fix" $ \step -> do + (pdb,mc) <- loadCC coinReplV1 + + step "pre-fork code injection succeeds, no enforced precompile" + + cmd <- buildExecParsedCode maxBound Nothing "(coin.get-balance \"tester01\")" + + doCoinbaseExploit pdb mc preForkHeight cmd False $ \case + Left _ -> assertFailure "local call to get-balance failed" + Right (PLiteral (LDecimal d)) + | d == 1000.1 -> return () + | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d + Right l -> assertFailure $ "wrong return type: " <> show l + + step "post-fork code injection fails, no enforced precompile" + + cmd' <- buildExecParsedCode maxBound Nothing + "(coin.get-balance \"tester01\\\" (read-keyset \\\"miner-keyset\\\") 1000.0)(coin.coinbase \\\"tester01\")" + + doCoinbaseExploit pdb mc postForkHeight cmd' False $ \case + Left _ -> assertFailure "local call to get-balance failed" + Right (PLiteral (LDecimal d)) + | d == 0.1 -> return () + | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d + Right l -> assertFailure $ "wrong return type: " <> show l + + step "pre-fork code injection fails, enforced precompile" + + doCoinbaseExploit pdb mc preForkHeight cmd' True $ \case + Left _ -> assertFailure "local call to get-balance failed" + Right (PLiteral (LDecimal d)) + | d == 0.2 -> return () + | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d + Right l -> assertFailure $ "wrong return type: " <> show l + + step "post-fork code injection fails, enforced precompile" + + doCoinbaseExploit pdb mc postForkHeight cmd' True $ \case + Left _ -> assertFailure "local call to get-balance failed" + Right (PLiteral (LDecimal d)) + | d == 0.3 -> return () + | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d + Right l -> assertFailure $ "wrong return type: " <> show l + + where + doCoinbaseExploit pdb mc height localCmd precompile testResult = do + let ctx = Pact4.TxContext (mkTestParentHeader $ height - 1) noPublicMeta miner + + void $ applyCoinbase Mainnet01 logger pdb 0.1 ctx + (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled precompile) mc + + let h = H.toUntypedHash (H.hash "" :: H.PactHash) + tenv = TransactionEnv Transactional pdb logger Nothing noPublicData + noSPVSupport Nothing 0.0 (RequestKey h) 0 emptyExecutionConfig Nothing Nothing + txst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty + + CommandResult _ _ (PactResult pr) _ _ _ _ _ <- evalTransactionM tenv txst $! + applyExec 0 defaultInterpreter localCmd [] [] h permissiveNamespacePolicy + + testResult pr + + miner = Miner + (MinerId "tester01\" (read-keyset \"miner-keyset\") 1000.0)(coin.coinbase \"tester01") + (MinerKeys $ mkKeySet ["b67e109352e8e33c8fe427715daad57d35d25d025914dd705b97db35b1bfbaa5"] "keys-all") + + preForkHeight = 121451 + postForkHeight = 121452 + + -- | someBlockHeader is a bit slow for the vuln797Fix to trigger. So, instead + -- of mining a full chain we fake the height. + -- + mkTestParentHeader :: BlockHeight -> ParentHeader + mkTestParentHeader h = ParentHeader $ someBlockHeader (slowForkingCpmTestVersion singleton) 10 + & blockHeight .~ h + +testCoinbaseEnforceFailure :: Assertion +testCoinbaseEnforceFailure = do + (pdb,mc) <- loadCC coinReplV4 + r <- tryAllSynchronous $ + applyCoinbase toyVersion logger pdb 0.1 + (Pact4.TxContext someParentHeader noPublicMeta badMiner) + (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled False) mc + case r of + Left e -> + if isInfixOf "CoinbaseFailure" (sshow e) then + return () + else assertFailure $ "Coinbase failed for unknown reason: " <> show e + Right _ -> assertFailure "Coinbase did not fail for bad miner id" + where + badMiner = Miner (MinerId "") (MinerKeys $ mkKeySet [] "<") + blockHeight' = 123 + someParentHeader = ParentHeader $ someTestVersionHeader + & blockHeight .~ blockHeight' + & blockCreationTime .~ BlockCreationTime [timeMicrosQQ| 2019-12-10T01:00:00.0 |] + +testCoinbaseUpgradeDevnet :: V.ChainId -> BlockHeight -> Assertion +testCoinbaseUpgradeDevnet cid upgradeHeight = + testUpgradeScript "test/pact/coin-and-devaccts.repl" cid upgradeHeight test + where + test (T2 cr mcm) = case (_crLogs cr,mcm) of + (_,Nothing) -> assertFailure "Expected module cache from successful upgrade" + (Nothing,_) -> assertFailure "Expected logs from successful upgrade" + (Just logs,_) -> + matchLogs (logResults logs) expectedResults + + expectedResults = + [ ("USER_coin_coin-table", "NoMiner", Just (Number 0.1)) + , ("SYS_modules","fungible-v2",Nothing) + , ("SYS_modules","coin",Nothing) + , ("USER_coin_coin-table","sender07",Just (Number 998662.3)) + , ("USER_coin_coin-table","sender09",Just (Number 998662.1)) + ] + +testTwentyChainDevnetUpgrades :: TestTree +testTwentyChainDevnetUpgrades = testCaseSteps "Test 20-chain Devnet upgrades" $ \step -> do + step "Check that 20-chain upgrades fire at block height 60" + testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) 60 test0 + + step "Check that 20-chain upgrades do not fire at block heights < 60 and > 60" + testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) (60 - 1) test1 + testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) (60 + 1) test1 + + step "Check that 20-chain upgrades do not fire at on other chains" + testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 1) 60 test1 + + step "Check that 20-chain upgrades succeed even if e7f7 balance is insufficient" + testUpgradeScript "test/pact/twenty-chain-insufficient-bal.repl" (unsafeChainId 0) 60 test1 + where + test0 (T2 cr _) = case _crLogs cr of + Just logs -> matchLogs (logResults logs) + [ ("USER_coin_coin-table","NoMiner",Just (Number 0.1)) + , ( "USER_coin_coin-table" + , "e7f7634e925541f368b827ad5c72421905100f6205285a78c19d7b4a38711805" + , Just (Number 50.0) -- 100.0 tokens remediated + ) + ] + Nothing -> assertFailure "Expected logs from upgrade" + + test1 (T2 cr _) = case _crLogs cr of + Just logs -> matchLogs (logResults logs) + [ ("USER_coin_coin-table", "NoMiner", Just (Number 0.1)) + ] + Nothing -> assertFailure "Expected logs from upgrade" + +-- ---------------------------------------------------------------------- -- +-- Utils + +testUpgradeScript + :: FilePath + -> V.ChainId + -> BlockHeight + -> (T2 (CommandResult [TxLogJson]) (Maybe Pact4.ModuleCache) -> IO ()) + -> IO () +testUpgradeScript script cid bh test = do + (pdb, mc) <- loadScript script + r <- tryAllSynchronous $ applyCoinbase v logger pdb 0.1 (Pact4.TxContext parent noPublicMeta noMiner) + (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled False) mc + case r of + Left e -> assertFailure $ "tx execution failed: " ++ show e + Right cr -> test cr + where + parent = ParentHeader $ someBlockHeader v bh + & blockChainwebVersion .~ _versionCode v + & blockChainId .~ cid + & blockHeight .~ pred bh + +matchLogs :: [(Text, Text, Maybe Value)] -> [(Text, Text, Maybe Value)] -> IO () +matchLogs expectedResults actualResults + | length actualResults /= length expectedResults = void $ + assertFailure $ intercalate "\n" + [ "matchLogs: length mismatch " + <> show (length actualResults) <> " /= " <> show (length expectedResults) + , "actual: " ++ show actualResults + , "expected: " ++ show expectedResults + ] + | otherwise = void $ zipWithM matchLog actualResults expectedResults + where + matchLog actual expected = do + (assertEqual "domain matches" `on` view _1) actual expected + (assertEqual "key matches" `on` view _2) actual expected + (assertEqual "balance matches" `on` view _3) actual expected + +logResults :: [TxLogJson] -> [(Text, Text, Maybe Value)] +logResults = fmap go + where + go x = case decodeTxLogJson x of + Left e -> error $ "unable to parse TxLog: " <> show e + Right (r :: TxLog Value) -> f r + f l = + ( _txDomain l + , _txKey l + -- This lens is because some of the transacctions happen post 420 fork + -- So the object representation changes due to the RowData type. + , l ^? txValue . _Object . (ix "balance" `failing` ix "$d" . _Object . ix "balance") + ) diff --git a/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs b/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs new file mode 100644 index 0000000000..16c0f69d1c --- /dev/null +++ b/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs @@ -0,0 +1,16 @@ +module Chainweb.Test.Pact4.VerifierPluginTest +( tests +) where + +import Test.Tasty + +import Chainweb.Storage.Table.RocksDB + +import qualified Chainweb.Test.Pact4.VerifierPluginTest.Transaction +import qualified Chainweb.Test.Pact4.VerifierPluginTest.Unit + +tests :: RocksDb -> TestTree +tests rdb = testGroup "Chainweb.Test.Pact4.VerifierPluginTest" + [ Chainweb.Test.Pact4.VerifierPluginTest.Unit.tests + , Chainweb.Test.Pact4.VerifierPluginTest.Transaction.tests rdb + ] From 4433ed010dd2115aad0dbb9c8b2de7a22bd496b9 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 21 Aug 2025 13:47:36 -0400 Subject: [PATCH 321/378] Migrate Pact 4 Adapt Pact 4's modules and tests to work with pp/evm. Change-Id: Id0000000fde5db9706213850c954d1f7a5926af4 --- chainweb.cabal | 21 + src/Chainweb/Pact/Backend/DbCache.hs | 6 +- src/Chainweb/Pact/PactService.hs | 317 ++++---- src/Chainweb/Pact/PactService/Checkpointer.hs | 155 +++- .../Pact/PactService/Pact4/ExecBlock.hs | 707 ++++++------------ src/Chainweb/Pact/SPV.hs | 9 +- src/Chainweb/Pact/TransactionExec.hs | 4 +- src/Chainweb/Pact/Types.hs | 11 + src/Chainweb/Pact4/Backend/ChainwebPactDb.hs | 190 +++-- src/Chainweb/Pact4/ModuleCache.hs | 14 +- src/Chainweb/Pact4/SPV.hs | 66 +- src/Chainweb/Pact4/Templates.hs | 41 +- src/Chainweb/Pact4/Transaction.hs | 44 +- src/Chainweb/Pact4/TransactionExec.hs | 574 +++++++------- src/Chainweb/Pact4/Types.hs | 391 +++++----- src/Chainweb/Pact4/Validations.hs | 53 +- src/Chainweb/SPV/VerifyProof.hs | 15 + src/Chainweb/Version.hs | 35 +- src/Chainweb/Version/Guards.hs | 13 + src/Chainweb/Version/Registry.hs | 4 +- test/lib/Chainweb/Test/Pact4/Utils.hs | 410 +--------- .../Chainweb/Test/Pact/CheckpointerTest.hs | 49 +- .../Chainweb/Test/Pact/PactServiceTest.hs | 6 +- .../Chainweb/Test/Pact/TransactionExecTest.hs | 3 +- test/unit/Chainweb/Test/Pact4/RewardsTest.hs | 2 +- test/unit/Chainweb/Test/Pact4/SQLite.hs | 3 +- .../Chainweb/Test/Pact4/TransactionTests.hs | 194 +---- .../Chainweb/Test/Pact4/VerifierPluginTest.hs | 8 +- test/unit/ChainwebTests.hs | 18 +- 29 files changed, 1408 insertions(+), 1955 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index 1767823ddb..3d1c0dfe9c 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -352,6 +352,17 @@ library , Chainweb.Pact.Types , Chainweb.Pact.Validations , Chainweb.Pact.Utils + -- pact 4 + , Chainweb.Pact4.Backend.ChainwebPactDb + , Chainweb.Pact4.ModuleCache + , Chainweb.Pact4.NoCoinbase + , Chainweb.Pact.PactService.Pact4.ExecBlock + , Chainweb.Pact4.SPV + , Chainweb.Pact4.Templates + , Chainweb.Pact4.Transaction + , Chainweb.Pact4.TransactionExec + , Chainweb.Pact4.Types + , Chainweb.Pact4.Validations -- utils , Utils.Logging @@ -449,6 +460,7 @@ library , tls-session-manager >= 0.0 , token-bucket >= 0.1 , transformers >= 0.5 + , trifecta >= 2.1 , unliftio >= 0.2 , unordered-containers >= 0.2.20 , validation @@ -491,6 +503,8 @@ library chainweb-test-utils Chainweb.Test.P2P.Peer.BootstrapConfig Chainweb.Test.Pact.CmdBuilder Chainweb.Test.Pact.Utils + Chainweb.Test.Pact4.Utils + Chainweb.Test.Pact4.VerifierPluginTest.Unit Chainweb.Test.RestAPI.Client_ Chainweb.Test.RestAPI.Utils Chainweb.Test.TestVersions @@ -631,6 +645,13 @@ test-suite chainweb-tests -- Chainweb.Test.Pact.SPVTest Chainweb.Test.Pact.TransactionExecTest Chainweb.Test.Pact.TransactionTests + Chainweb.Test.Pact4.DbCacheTest + Chainweb.Test.Pact4.GrandHash + Chainweb.Test.Pact4.NoCoinbase + Chainweb.Test.Pact4.RewardsTest + Chainweb.Test.Pact4.SQLite + Chainweb.Test.Pact4.TransactionTests + Chainweb.Test.Pact4.VerifierPluginTest Chainweb.Test.RestAPI Chainweb.Test.Roundtrips -- Chainweb.Test.SPV diff --git a/src/Chainweb/Pact/Backend/DbCache.hs b/src/Chainweb/Pact/Backend/DbCache.hs index 0a6461ccbb..a466473074 100644 --- a/src/Chainweb/Pact/Backend/DbCache.hs +++ b/src/Chainweb/Pact/Backend/DbCache.hs @@ -17,6 +17,7 @@ -- module Chainweb.Pact.Backend.DbCache ( DbCacheLimitBytes(..) +, defaultModuleCacheLimit , DbCache , checkDbCache , emptyDbCache @@ -58,7 +59,7 @@ import Pact.Types.Persistence -- internal modules -import Chainweb.Utils (ix') +import Chainweb.Utils (ix', mebi) -- -------------------------------------------------------------------------- -- @@ -67,6 +68,9 @@ import Chainweb.Utils (ix') newtype DbCacheLimitBytes = DbCacheLimitBytes Natural deriving (Show, Read, Eq, Ord, ToJSON, FromJSON) +defaultModuleCacheLimit :: DbCacheLimitBytes +defaultModuleCacheLimit = DbCacheLimitBytes (60 * mebi) + -- -------------------------------------------------------------------------- -- -- CacheAddress diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 11da6d4f36..df9bed47be 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -57,10 +57,12 @@ import Chainweb.Pact.NoCoinbase qualified as Pact import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer import Chainweb.Pact.PactService.ExecBlock import Chainweb.Pact.PactService.ExecBlock qualified as Pact +import Chainweb.Pact.PactService.Pact4.ExecBlock qualified as Pact4 import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.TransactionExec qualified as Pact import Chainweb.Pact.Types import Chainweb.Pact.Validations qualified as Pact +import Chainweb.Pact4.Backend.ChainwebPactDb qualified as Pact4 import Chainweb.Parent import Chainweb.Payload import Chainweb.Payload.PayloadStore @@ -113,6 +115,8 @@ import Pact.Core.StableEncoding qualified as Pact import Pact.JSON.Encode qualified as J import Prelude hiding (lookup) import System.LogLevel +import Chainweb.Version.Guards (pact5) +import Control.Concurrent.MVar (newMVar) withPactService :: (Logger logger, CanPayloadCas tbl) @@ -141,6 +145,7 @@ withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb read liftIO $ ChainwebPactDb.initSchema readWriteSqlenv candidatePdb <- liftIO MapTable.emptyTable + moduleInitCacheVar <- liftIO $ newMVar mempty let !pse = ServiceEnv { _psChainId = cid @@ -165,6 +170,7 @@ withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb read GenesisPayload p -> Just p GenesisNotNeeded -> Nothing , _psBlockRefreshInterval = _pactBlockRefreshInterval config + , _psModuleInitCacheVar = moduleInitCacheVar } case pactGenesis of @@ -208,10 +214,16 @@ runGenesisIfNeeded logger serviceEnv = do maybeErr <- runExceptT $ Checkpointer.restoreAndSave logger cid (_psReadWriteSql serviceEnv) $ NEL.singleton - $ (blockCtx, \blockEnv -> do + $ (blockCtx, + if pact5 cid (genesisHeight cid) + then Checkpointer.Pact5RunnableBlock $ \blockEnv -> do _ <- Pact.execExistingBlock logger serviceEnv blockEnv (CheckablePayloadWithOutputs genesisPayload) return ((), (genesisBlockHash, genesisPayloadHash)) + else Checkpointer.Pact4RunnableBlock $ \blockDbEnv -> do + _ <- Pact4.execBlock logger serviceEnv (Pact4.BlockEnv blockCtx blockDbEnv) + (CheckablePayloadWithOutputs genesisPayload) + return $ ((), (genesisBlockHash, genesisPayloadHash)) ) case maybeErr of Left err -> error $ "genesis block invalid: " <> sshow err @@ -221,15 +233,22 @@ runGenesisIfNeeded logger serviceEnv = do (genesisHeight cid) genesisPayload Checkpointer.setConsensusState (_psReadWriteSql serviceEnv) targetSyncState - forM_ (_psMiner serviceEnv) $ \_ -> do - emptyBlock <- (throwIfNoHistory =<<) $ - Checkpointer.readFrom logger cid - (_psReadWriteSql serviceEnv) - (Parent gTime) - (Parent genesisRankedBlockHash) $ - \blockEnv blockHandle -> makeEmptyBlock logger serviceEnv blockEnv blockHandle - -- we have to kick off payload refreshing here first - startPayloadRefresher logger serviceEnv emptyBlock + -- we can't produce pact 4 blocks anymore, so don't make + -- payloads if pact 4 is on + when (pact5 cid (succ $ genesisHeight cid)) $ + forM_ (_psMiner serviceEnv) $ \_ -> do + emptyBlock <- (throwIfNoHistory =<<) $ + Checkpointer.readFrom logger cid + (_psReadWriteSql serviceEnv) + (Parent gTime) + (Parent genesisRankedBlockHash) + Checkpointer.PactRead + { pact5Read = \blockEnv blockHandle -> + makeEmptyBlock logger serviceEnv blockEnv blockHandle + , pact4Read = error "Pact 4 cannot make new blocks" + } + -- we have to kick off payload refreshing here first + startPayloadRefresher logger serviceEnv emptyBlock where cid = _chainId serviceEnv @@ -247,40 +266,43 @@ execNewGenesisBlock logger serviceEnv newTrans = do let cid = _chainId serviceEnv let parentCreationTime = Parent (implicitVersion ^?! versionGenesis . genesisTime . atChain cid) let genesisParent = Parent $ RankedBlockHash (genesisHeight cid) (unwrapParent $ genesisParentBlockHash cid) - historicalBlock <- Checkpointer.readFrom logger cid (_psReadWriteSql serviceEnv) parentCreationTime genesisParent $ \blockEnv startHandle -> do - - let bipStart = BlockInProgress - { _blockInProgressHandle = startHandle - , _blockInProgressNumber = 0 - -- fake gas limit, gas is free for genesis - , _blockInProgressRemainingGasLimit = Pact.GasLimit (Pact.Gas 999_999_999) - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = absurd <$> Pact.noCoinbase - , _transactionPairs = mempty + historicalBlock <- Checkpointer.readFrom logger cid (_psReadWriteSql serviceEnv) parentCreationTime genesisParent Checkpointer.PactRead + { pact5Read = \blockEnv startHandle -> do + + let bipStart = BlockInProgress + { _blockInProgressHandle = startHandle + , _blockInProgressNumber = 0 + -- fake gas limit, gas is free for genesis + , _blockInProgressRemainingGasLimit = Pact.GasLimit (Pact.Gas 999_999_999) + , _blockInProgressTransactions = Transactions + { _transactionCoinbase = absurd <$> Pact.noCoinbase + , _transactionPairs = mempty + } + , _blockInProgressBlockCtx = _psBlockCtx blockEnv } - , _blockInProgressBlockCtx = _psBlockCtx blockEnv - } - let fakeMempoolServiceEnv = serviceEnv - & psMempoolAccess .~ mempty - { mpaGetBlock = \bf pbc evalCtx -> do - if _bfCount bf == 0 - then do - maybeInvalidTxs <- pbc evalCtx newTrans - validTxs <- case partitionEithers (V.toList maybeInvalidTxs) of - ([], validTxs) -> return validTxs - (errs, _) -> error $ "Pact 5 genesis commands invalid: " <> sshow errs - V.fromList validTxs `Strategies.usingIO` traverse Strategies.rseq - else do - return V.empty - } - & psMiner .~ Just noMiner - - results <- Pact.continueBlock logger fakeMempoolServiceEnv (_psBlockDbEnv blockEnv) bipStart - let !pwo = toPayloadWithOutputs - noMiner - (_blockInProgressTransactions results) - return $! pwo + let fakeMempoolServiceEnv = serviceEnv + & psMempoolAccess .~ mempty + { mpaGetBlock = \bf pbc evalCtx -> do + if _bfCount bf == 0 + then do + maybeInvalidTxs <- pbc evalCtx newTrans + validTxs <- case partitionEithers (V.toList maybeInvalidTxs) of + ([], validTxs) -> return validTxs + (errs, _) -> error $ "Pact 5 genesis commands invalid: " <> sshow errs + V.fromList validTxs `Strategies.usingIO` traverse Strategies.rseq + else do + return V.empty + } + & psMiner .~ Just noMiner + + results <- Pact.continueBlock logger fakeMempoolServiceEnv (_psBlockDbEnv blockEnv) bipStart + let !pwo = toPayloadWithOutputs + noMiner + (_blockInProgressTransactions results) + return $! pwo + , pact4Read = error "cannot make new Pact 4 genesis blocks" + } case historicalBlock of NoHistory -> error "PactService.execNewGenesisBlock: Impossible error, unable to rewind before genesis" Historical block -> return block @@ -311,9 +333,14 @@ execReadOnlyReplay logger serviceEnv blocks = do sql (_evaluationCtxParentCreationTime evalCtx) (_evaluationCtxRankedParentHash evalCtx) - (\blockEnv blockHandle -> - runExceptT $ flip evalStateT blockHandle $ - void $ Pact.execExistingBlock logger serviceEnv blockEnv (CheckablePayloadWithOutputs payload)) + Checkpointer.PactRead + { pact5Read = \blockEnv blockHandle -> + runExceptT $ flip evalStateT blockHandle $ + void $ Pact.execExistingBlock logger serviceEnv blockEnv (CheckablePayloadWithOutputs payload) + , pact4Read = \blockEnv -> + runExceptT $ + void $ Pact4.execBlock logger serviceEnv blockEnv (CheckablePayloadWithOutputs payload) + } either Just (\_ -> Nothing) <$> throwIfNoHistory hist else return Nothing @@ -345,69 +372,70 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do doLocal = Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> do fakeNewBlockCtx <- liftIO Checkpointer.mkFakeParentCreationTime - Checkpointer.readFromNthParent logger cid sql fakeNewBlockCtx (fromIntegral rewindDepth) $ \blockEnv blockHandle -> do - let blockCtx = view psBlockCtx blockEnv - let requestKey = Pact.cmdToRequestKey cwtx - evalContT $ withEarlyReturn $ \earlyReturn -> do - -- this is just one of our metadata validation passes. - -- in preflight, we do another one, which replicates some of this work; - -- TODO: unify preflight, newblock, and validateblock tx metadata validation - case (preflight, sigVerify) of - (_, Just NoVerify) -> do - let payloadBS = SB.fromShort (Pact._cmdPayload $ view Pact.payloadBytes <$> cwtx) - case Pact.verifyHash (Pact._cmdHash cwtx) payloadBS of - Left err -> earlyReturn $ - MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err - Right _ -> return () - _ -> do - case Pact.assertCommand cwtx of - Left err -> earlyReturn $ - MetadataValidationFailure (pure $ displayAssertCommandError err) - Right () -> return () - - case preflight of - Just PreflightSimulation -> do - -- preflight needs to do additional checks on the metadata - -- to match on-chain tx validation - case (Pact.assertPreflightMetadata serviceEnv (view Pact.payloadObj <$> cwtx) blockCtx sigVerify) of - Left err -> earlyReturn $ MetadataValidationFailure err - Right () -> return () - let initialGas = Pact.initialGasOf $ Pact._cmdPayload cwtx - applyCmdResult <- flip evalStateT blockHandle $ pactTransaction blockEnv Nothing (\dbEnv spvSupport -> - Pact.applyCmd - logger gasLogger dbEnv noMiner - blockCtx (TxBlockIdx 0) spvSupport initialGas (view Pact.payloadObj <$> cwtx) - ) - commandResult <- case applyCmdResult of - Left err -> - earlyReturn $ LocalResultWithWarns (Pact.CommandResult - { _crReqKey = requestKey - , _crTxId = Nothing - , _crResult = Pact.PactResultErr $ - txInvalidErrorToOnChainPactError err - , _crGas = - cwtx ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmGasLimit . Pact._GasLimit - , _crLogs = Nothing - , _crContinuation = Nothing - , _crMetaData = Nothing - , _crEvents = [] - }) - [] - Right commandResult -> return commandResult - let pact5Pm = cwtx ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta - let metadata = J.toJsonViaEncode $ Pact.StableEncoding $ Pact.ctxToPublicData pact5Pm blockCtx - let commandResult' = hashPactTxLogs $ set Pact.crMetaData (Just metadata) commandResult - -- TODO: once Pact 5 has warnings, include them here. - pure $ LocalResultWithWarns - (Pact.pactErrorToOnChainError <$> commandResult') - [] - _ -> lift $ do - -- default is legacy mode: use applyLocal, don't buy gas, don't do any - -- metadata checks beyond signature and hash checking - cr <- flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \dbEnv spvSupport -> do - -- TODO: PPgaslog - fmap Pact.pactErrorToOnChainError <$> Pact.applyLocal logger Nothing dbEnv blockCtx spvSupport (view Pact.payloadObj <$> cwtx) - pure $ LocalResultLegacy $ hashPactTxLogs cr + Checkpointer.readFromNthParent logger cid sql fakeNewBlockCtx (fromIntegral rewindDepth) + $ Checkpointer.readPact5 "Pact 4 cannot execute local calls" $ \blockEnv blockHandle -> do + let blockCtx = view psBlockCtx blockEnv + let requestKey = Pact.cmdToRequestKey cwtx + evalContT $ withEarlyReturn $ \earlyReturn -> do + -- this is just one of our metadata validation passes. + -- in preflight, we do another one, which replicates some of this work; + -- TODO: unify preflight, newblock, and validateblock tx metadata validation + case (preflight, sigVerify) of + (_, Just NoVerify) -> do + let payloadBS = SB.fromShort (Pact._cmdPayload $ view Pact.payloadBytes <$> cwtx) + case Pact.verifyHash (Pact._cmdHash cwtx) payloadBS of + Left err -> earlyReturn $ + MetadataValidationFailure $ NonEmpty.singleton $ Text.pack err + Right _ -> return () + _ -> do + case Pact.assertCommand cwtx of + Left err -> earlyReturn $ + MetadataValidationFailure (pure $ displayAssertCommandError err) + Right () -> return () + + case preflight of + Just PreflightSimulation -> do + -- preflight needs to do additional checks on the metadata + -- to match on-chain tx validation + case (Pact.assertPreflightMetadata serviceEnv (view Pact.payloadObj <$> cwtx) blockCtx sigVerify) of + Left err -> earlyReturn $ MetadataValidationFailure err + Right () -> return () + let initialGas = Pact.initialGasOf $ Pact._cmdPayload cwtx + applyCmdResult <- flip evalStateT blockHandle $ pactTransaction blockEnv Nothing (\dbEnv spvSupport -> + Pact.applyCmd + logger gasLogger dbEnv noMiner + blockCtx (TxBlockIdx 0) spvSupport initialGas (view Pact.payloadObj <$> cwtx) + ) + commandResult <- case applyCmdResult of + Left err -> + earlyReturn $ LocalResultWithWarns (Pact.CommandResult + { _crReqKey = requestKey + , _crTxId = Nothing + , _crResult = Pact.PactResultErr $ + txInvalidErrorToOnChainPactError err + , _crGas = + cwtx ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmGasLimit . Pact._GasLimit + , _crLogs = Nothing + , _crContinuation = Nothing + , _crMetaData = Nothing + , _crEvents = [] + }) + [] + Right commandResult -> return commandResult + let pact5Pm = cwtx ^. Pact.cmdPayload . Pact.payloadObj . Pact.pMeta + let metadata = J.toJsonViaEncode $ Pact.StableEncoding $ Pact.ctxToPublicData pact5Pm blockCtx + let commandResult' = hashPactTxLogs $ set Pact.crMetaData (Just metadata) commandResult + -- TODO: once Pact 5 has warnings, include them here. + pure $ LocalResultWithWarns + (Pact.pactErrorToOnChainError <$> commandResult') + [] + _ -> lift $ do + -- default is legacy mode: use applyLocal, don't buy gas, don't do any + -- metadata checks beyond signature and hash checking + cr <- flip evalStateT blockHandle $ pactTransaction blockEnv Nothing $ \dbEnv spvSupport -> do + -- TODO: PPgaslog + fmap Pact.pactErrorToOnChainError <$> Pact.applyLocal logger Nothing dbEnv blockCtx spvSupport (view Pact.payloadObj <$> cwtx) + pure $ LocalResultLegacy $ hashPactTxLogs cr gasLogger = view psGasLogger serviceEnv enableLocalTimeout = view psEnableLocalTimeout serviceEnv @@ -483,7 +511,7 @@ syncToFork logger serviceEnv hints forkInfo = do -- check if some past block had the target as its parent; if so, that -- means we can rewind to it latestBlockRewindable <- - Checkpointer.lookupBlockHash sql (_latestBlockHash forkInfo._forkInfoTargetState) + isJust <$> Checkpointer.lookupBlockHash sql (_latestBlockHash forkInfo._forkInfoTargetState) if atTarget then do -- no work to do at all except set consensus state @@ -535,13 +563,24 @@ syncToFork logger serviceEnv hints forkInfo = do Nothing -> getPayloadForContext logger serviceEnv hints evalCtx Just payload -> return payload let expectedPayloadHash = _consensusPayloadHash $ _evaluationCtxPayload evalCtx - return $ - (blockCtxOfEvaluationCtx cid evalCtx, \blockEnv -> do - (_, pwo, validatedTxs) <- Pact.execExistingBlock logger serviceEnv blockEnv (CheckablePayload expectedPayloadHash payload) - -- add payload immediately after executing the block, because this is when we learn it's valid - liftIO $ addNewPayload (_payloadStoreTable $ _psPdb serviceEnv) (_evaluationCtxCurrentHeight evalCtx) pwo - return $ (DList.singleton validatedTxs, (_ranked rankedBHash, expectedPayloadHash)) - ) + let blockCtx = blockCtxOfEvaluationCtx cid evalCtx + if guardCtx pact5 blockCtx + then + return $ + (blockCtx, Checkpointer.Pact5RunnableBlock $ \blockEnv -> do + (_, pwo, validatedTxs) <- Pact.execExistingBlock logger serviceEnv blockEnv (CheckablePayload expectedPayloadHash payload) + -- add payload immediately after executing the block, because this is when we learn it's valid + liftIO $ addNewPayload (_payloadStoreTable $ _psPdb serviceEnv) (_evaluationCtxCurrentHeight evalCtx) pwo + return $ (DList.singleton validatedTxs, (_ranked rankedBHash, expectedPayloadHash)) + ) + else + return $ + (blockCtx, Checkpointer.Pact4RunnableBlock $ \blockDbEnv -> do + (_, pwo) <- Pact4.execBlock logger serviceEnv (Pact4.BlockEnv blockCtx blockDbEnv) (CheckablePayload expectedPayloadHash payload) + -- add payload immediately after executing the block, because this is when we learn it's valid + liftIO $ addNewPayload (_payloadStoreTable $ _psPdb serviceEnv) (_evaluationCtxCurrentHeight evalCtx) pwo + return $ (mempty, (_ranked rankedBHash, expectedPayloadHash)) + ) runExceptT (Checkpointer.restoreAndSave logger cid sql runnableBlocks) >>= \case Left err -> do @@ -555,6 +594,7 @@ syncToFork logger serviceEnv hints forkInfo = do case forkInfo._forkInfoNewBlockCtx of Just newBlockCtx | Just _ <- _psMiner serviceEnv + , pact5 cid (_rankedHeight (_latestRankedBlockHash newConsensusState)) , _syncStateBlockHash (_consensusStateLatest newConsensusState) == _latestBlockHash (forkInfo._forkInfoTargetState) -> do -- if we're at the target block we were sent, and we were @@ -562,8 +602,10 @@ syncToFork logger serviceEnv hints forkInfo = do -- immediately. then we set up a separate thread -- to add new transactions to the block. logFunctionText logger Debug "producing new block" - emptyBlock <- Checkpointer.readFromLatest logger cid sql (_newBlockCtxParentCreationTime newBlockCtx) $ \blockEnv blockHandle -> - makeEmptyBlock logger serviceEnv blockEnv blockHandle + emptyBlock <- + Checkpointer.readFromLatest logger cid sql (_newBlockCtxParentCreationTime newBlockCtx) + $ Checkpointer.readPact5 "Pact 4 cannot make new blocks" $ \blockEnv blockHandle -> + makeEmptyBlock logger serviceEnv blockEnv blockHandle let payloadVar = view psMiningPayloadVar serviceEnv -- cancel payload refresher thread @@ -660,9 +702,12 @@ refreshPayloads logger serviceEnv = do "refreshing payloads for " <> brief (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) maybeRefreshedBlockInProgress <- Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> - Checkpointer.readFrom logger cid sql (_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress) (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) $ \blockEnv _bh -> do - let dbEnv = view psBlockDbEnv blockEnv - continueBlock logger serviceEnv dbEnv blockInProgress + Checkpointer.readFrom logger cid sql + (_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress) + (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) + $ Checkpointer.readPact5 "Pact 4 cannot make new blocks" $ \blockEnv _bh -> do + let dbEnv = view psBlockDbEnv blockEnv + continueBlock logger serviceEnv dbEnv blockInProgress case maybeRefreshedBlockInProgress of -- the block's parent was removed NoHistory -> logOutraced @@ -725,14 +770,19 @@ execPreInsertCheckReq logger serviceEnv txs = do let requestKeys = V.map Pact.cmdToRequestKey txs logFunctionText logger Info $ "(pre-insert check " <> sshow requestKeys <> ")" fakeParentCreationTime <- Checkpointer.mkFakeParentCreationTime - let act sql = Checkpointer.readFromLatest logger cid sql fakeParentCreationTime $ \blockEnv bh -> do - forM txs $ \tx -> - fmap (either Just (\_ -> Nothing)) $ runExceptT $ do - -- it's safe to use initialBlockHandle here because it's - -- only used to check for duplicate pending txs in a block - () <- mapExceptT liftIO - $ Pact.validateParsedChainwebTx logger blockEnv tx - evalStateT (attemptBuyGas blockEnv tx) bh + let act sql = Checkpointer.readFromLatest logger cid sql fakeParentCreationTime $ Checkpointer.PactRead + { pact5Read = \blockEnv bh -> do + forM txs $ \tx -> + fmap (either Just (\_ -> Nothing)) $ runExceptT $ do + -- it's safe to use initialBlockHandle here because it's + -- only used to check for duplicate pending txs in a block + () <- mapExceptT liftIO + $ Pact.validateParsedChainwebTx logger blockEnv tx + evalStateT (attemptBuyGas blockEnv tx) bh + -- pessimistically, if we're catching up and not even past the Pact + -- 5 activation, just badlist everything as in-the-future. + , pact4Read = \_ -> return $ Just InsertErrorTimeInFuture <$ txs + } Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> timeoutYield timeoutLimit (act sql) >>= \case Just r -> do @@ -785,6 +835,9 @@ execLookupPactTxs logger serviceEnv confDepth txs = do depth = maybe 0 (fromIntegral . _confirmationDepth) confDepth cid = _chainId serviceEnv go ctx = Pool.withResource (_psReadSqlPool serviceEnv) $ \sql -> - Checkpointer.readFromNthParent logger cid sql ctx depth $ \blockEnv _ -> do - let dbenv = view psBlockDbEnv blockEnv - fmap (HM.mapKeys coerce) $ liftIO $ Pact.lookupPactTransactions dbenv (coerce txs) + Checkpointer.readFromNthParent logger cid sql ctx depth + -- not sure about this, disallows looking up pact txs if we haven't + -- caught up to pact 5 + $ Checkpointer.readPact5 "Pact 4 cannot look up transactions" $ \blockEnv _ -> do + let dbenv = view psBlockDbEnv blockEnv + fmap (HM.mapKeys coerce) $ liftIO $ Pact.lookupPactTransactions dbenv (coerce txs) diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 4626b2fbcc..c4b9adb7de 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -50,6 +50,9 @@ module Chainweb.Pact.PactService.Checkpointer , getPayloadsAfter , getConsensusState , setConsensusState + , RunnableBlock(..) + , PactRead(..) + , readPact5 ) where import Control.Lens hiding ((:>), (:<)) @@ -86,6 +89,17 @@ import qualified Data.List.NonEmpty as NE import Chainweb.Pact.Backend.ChainwebPactDb (lookupRankedBlockHash) import Control.Monad.State.Strict import System.LogLevel +import qualified Chainweb.Pact4.Types as Pact4 +import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact4 +import Control.Concurrent.MVar +import qualified Pact.Types.Persistence as Pact4 +import Chainweb.Pact.Backend.DbCache (defaultModuleCacheLimit) +import qualified Pact.Interpreter as Pact4 +import qualified Data.ByteString.Short as BS +import Data.Coerce +import qualified Data.HashMap.Strict as HashMap +import qualified Pact.Types.Command as Pact4 +import qualified Pact.Types.Hash as Pact4 -- readFrom demands to know this context in case it's mining a block. -- But it's not really used by /local, or polling, so we fabricate it @@ -106,7 +120,7 @@ readFromLatest -> ChainId -> SQLiteEnv -> Parent BlockCreationTime - -> (BlockEnv -> BlockHandle -> IO a) + -> PactRead a -> IO a readFromLatest logger cid sql parentCreationTime doRead = readFromNthParent logger cid sql parentCreationTime 0 doRead >>= \case @@ -122,7 +136,7 @@ readFromNthParent -> SQLiteEnv -> Parent BlockCreationTime -> Word - -> (BlockEnv -> BlockHandle -> IO a) + -> PactRead a -> IO (Historical a) readFromNthParent logger cid sql parentCreationTime n doRead = do withSavepoint sql ReadFromNSavepoint $ do @@ -154,9 +168,9 @@ readFrom -> Parent BlockCreationTime -- ^ you can fake this if you're not making a new block -> Parent RankedBlockHash - -> (BlockEnv -> BlockHandle -> IO a) + -> PactRead a -> IO (Historical a) -readFrom logger cid sql parentCreationTime parent doRead = do +readFrom logger cid sql parentCreationTime parent pactRead = do let blockCtx = BlockCtx { _bctxParentCreationTime = parentCreationTime , _bctxParentHash = _rankedBlockHashHash <$> parent @@ -170,22 +184,44 @@ readFrom logger cid sql parentCreationTime parent doRead = do -- is the parent the latest header, i.e., can we get away without rewinding? let parentIsLatestHeader = latestHeader == parent let currentHeight = _bctxCurrentBlockHeight blockCtx - if pact5 cid currentHeight - then PactDb.getEndTxId cid sql parent >>= traverse \startTxId -> do - let - blockHandlerEnv = ChainwebPactDb.BlockHandlerEnv - { ChainwebPactDb._blockHandlerDb = sql - , ChainwebPactDb._blockHandlerLogger = logger - , ChainwebPactDb._blockHandlerChainId = cid - , ChainwebPactDb._blockHandlerBlockHeight = currentHeight - , ChainwebPactDb._blockHandlerMode = Pact.Transactional - , ChainwebPactDb._blockHandlerUpperBoundTxId = startTxId - , ChainwebPactDb._blockHandlerAtTip = parentIsLatestHeader - } - let pactDb = ChainwebPactDb.chainwebPactBlockDb blockHandlerEnv - let blockEnv = BlockEnv blockCtx pactDb - doRead blockEnv (emptyBlockHandle startTxId) - else error "Pact 4 blocks are not playable anymore" + PactDb.getEndTxId cid sql parent >>= traverse \startTxId -> + if pact5 cid currentHeight then do + let + blockHandlerEnv = ChainwebPactDb.BlockHandlerEnv + { ChainwebPactDb._blockHandlerDb = sql + , ChainwebPactDb._blockHandlerLogger = logger + , ChainwebPactDb._blockHandlerChainId = cid + , ChainwebPactDb._blockHandlerBlockHeight = currentHeight + , ChainwebPactDb._blockHandlerMode = Pact.Transactional + , ChainwebPactDb._blockHandlerUpperBoundTxId = startTxId + , ChainwebPactDb._blockHandlerAtTip = parentIsLatestHeader + } + let pactDb = ChainwebPactDb.chainwebPactBlockDb blockHandlerEnv + let blockEnv = BlockEnv blockCtx pactDb + pact5Read pactRead blockEnv (emptyBlockHandle startTxId) + else do + let pact4TxId = Pact4.TxId (coerce startTxId) + let blockHandlerEnv = Pact4.mkBlockHandlerEnv cid currentHeight sql logger + newBlockDbEnv <- liftIO $ newMVar $ Pact4.BlockDbEnv + blockHandlerEnv + -- FIXME not sharing the cache + (Pact4.initBlockState defaultModuleCacheLimit pact4TxId) + let pactDb = Pact4.rewoundPactDb currentHeight pact4TxId + + let pact4DbEnv = Pact4.CurrentBlockDbEnv + { _cpPactDbEnv = Pact4.PactDbEnv pactDb newBlockDbEnv + , _cpRegisterProcessedTx = \hash -> + Pact4.runBlockDbEnv newBlockDbEnv (Pact4.indexPactTransaction $ BS.fromShort $ Pact4.unHash $ Pact4.unRequestKey hash) + , _cpLookupProcessedTx = \hs -> do + res <- doLookupSuccessful sql currentHeight (coerce hs) + return + $ HashMap.mapKeys coerce + $ HashMap.map + (\(T3 height _payloadhash bhash) -> T2 height bhash) + res + , _cpHeaderOracle = Pact4.headerOracleForBlock blockHandlerEnv + } + pact4Read pactRead (Pact4.BlockEnv blockCtx pact4DbEnv) -- the special case where one doesn't want to extend the chain, just rewind it. rewindTo @@ -198,6 +234,23 @@ rewindTo cid sql ancestor = do withSavepoint sql RewindSavePoint $ void $ PactDb.rewindDbTo cid sql ancestor +data PactRead a + = PactRead + { pact4Read :: (forall logger. Logger logger => Pact4.BlockEnv logger -> IO a) + , pact5Read :: BlockEnv -> BlockHandle -> IO a + } + +readPact5 :: String -> (BlockEnv -> BlockHandle -> IO a) -> PactRead a +readPact5 msg act = PactRead + { pact4Read = \_ -> error msg + , pact5Read = act + } + +data RunnableBlock m q + = Pact4RunnableBlock + (forall logger. Logger logger => Pact4.CurrentBlockDbEnv logger -> m (q, (BlockHash, BlockPayloadHash))) + | Pact5RunnableBlock (BlockEnv -> StateT BlockHandle m (q, (BlockHash, BlockPayloadHash))) + -- TODO: log more? -- | Given a list of blocks in ascending order, rewind to the first -- of them and play all of the subsequent blocks, saving the result persistently. @@ -215,7 +268,7 @@ restoreAndSave => logger -> ChainId -> SQLiteEnv - -> NonEmpty (BlockCtx, BlockEnv -> StateT BlockHandle m (q, (BlockHash, BlockPayloadHash))) + -> NonEmpty (BlockCtx, RunnableBlock m q) -> m q restoreAndSave logger cid sql blocks = do withSavepoint sql RestoreAndSaveSavePoint $ do @@ -240,21 +293,51 @@ restoreAndSave logger cid sql blocks = do executeBlock startTxId (blockCtx, blockAction) = do let !bh = _bctxCurrentBlockHeight blockCtx - blockEnv = ChainwebPactDb.BlockHandlerEnv - { ChainwebPactDb._blockHandlerDb = sql - , ChainwebPactDb._blockHandlerLogger = logger - , ChainwebPactDb._blockHandlerBlockHeight = bh - , ChainwebPactDb._blockHandlerChainId = cid - , ChainwebPactDb._blockHandlerMode = Pact.Transactional - , ChainwebPactDb._blockHandlerUpperBoundTxId = startTxId - , ChainwebPactDb._blockHandlerAtTip = True - } - pactDb = ChainwebPactDb.chainwebPactBlockDb blockEnv - in if pact5 cid bh then do + case blockAction of + Pact4RunnableBlock block | not (pact5 cid bh) -> do + let pact4TxId = Pact4.TxId (coerce startTxId) + let blockHandlerEnv = Pact4.mkBlockHandlerEnv cid bh sql logger + newBlockDbEnv <- liftIO $ newMVar $ Pact4.BlockDbEnv + blockHandlerEnv + -- FIXME not sharing the cache + (Pact4.initBlockState defaultModuleCacheLimit pact4TxId) + let pactDb = Pact4.chainwebPactDb + let pact4DbEnv = Pact4.CurrentBlockDbEnv + { _cpPactDbEnv = Pact4.PactDbEnv pactDb newBlockDbEnv + , _cpRegisterProcessedTx = \hash -> + Pact4.runBlockDbEnv newBlockDbEnv (Pact4.indexPactTransaction $ BS.fromShort $ Pact4.unHash $ Pact4.unRequestKey hash) + , _cpLookupProcessedTx = \hs -> do + res <- doLookupSuccessful sql bh (coerce hs) + return + $ HashMap.mapKeys coerce + $ HashMap.map + (\(T3 height _payloadhash bhash) -> T2 height bhash) + res + , _cpHeaderOracle = Pact4.headerOracleForBlock blockHandlerEnv + } + ((m, blockInfo)) <- block pact4DbEnv + -- let outputBlockHandle = + finalBlockState <- liftIO $ + Pact4._benvBlockState <$> readMVar newBlockDbEnv + -- TODO Robert: use payload hash here too + liftIO $ Pact4.commitBlockStateToDatabase sql (fst blockInfo) (snd blockInfo) bh finalBlockState + return (m, Pact.TxId $ coerce $ Pact4._bsTxId finalBlockState) + Pact5RunnableBlock block | pact5 cid bh -> do + let + blockEnv = ChainwebPactDb.BlockHandlerEnv + { ChainwebPactDb._blockHandlerDb = sql + , ChainwebPactDb._blockHandlerLogger = logger + , ChainwebPactDb._blockHandlerBlockHeight = bh + , ChainwebPactDb._blockHandlerChainId = cid + , ChainwebPactDb._blockHandlerMode = Pact.Transactional + , ChainwebPactDb._blockHandlerUpperBoundTxId = startTxId + , ChainwebPactDb._blockHandlerAtTip = True + } + pactDb = ChainwebPactDb.chainwebPactBlockDb blockEnv -- run the block ((m, blockInfo), blockHandle) <- - runStateT (blockAction (BlockEnv blockCtx pactDb)) (emptyBlockHandle startTxId) + runStateT (block (BlockEnv blockCtx pactDb)) (emptyBlockHandle startTxId) -- TODO PP: also check the child matches the parent height -- case maybeParent of -- Nothing @@ -272,8 +355,8 @@ restoreAndSave logger cid sql blocks = do return (m, _blockHandleTxId blockHandle) - else error $ - "Pact 5 block executed on block height before Pact 5 fork, height: " <> sshow bh + _ -> do + error $ "pact 4/5 mismatch: " <> sshow (cid, bh) getEarliestBlock :: SQLiteEnv -> IO (Maybe RankedBlockHash) getEarliestBlock sql = do @@ -291,7 +374,7 @@ lookupBlockWithHeight :: HasCallStack => SQLiteEnv -> BlockHeight -> IO (Maybe ( lookupBlockWithHeight sql bh = do ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockWithHeight sql bh -lookupBlockHash :: HasCallStack => SQLiteEnv -> BlockHash -> IO Bool +lookupBlockHash :: HasCallStack => SQLiteEnv -> BlockHash -> IO (Maybe BlockHeight) lookupBlockHash sql pbh = do ChainwebPactDb.throwOnDbError $ ChainwebPactDb.lookupBlockHash sql pbh diff --git a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs index b7e92d4487..0e9af5cadc 100644 --- a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs @@ -13,6 +13,7 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE PartialTypeSignatures #-} -- | -- Module: Chainweb.Pact.PactService.Pact4.ExecBlock @@ -26,15 +27,12 @@ module Chainweb.Pact.PactService.Pact4.ExecBlock ( execBlock , execTransactions - , continueBlock , toPayloadWithOutputs , validateParsedChainwebTx , validateRawChainwebTx , validateHashes - , throwCommandInvalidError , initModuleCacheForBlock , runCoinbase - , CommandInvalidError(..) , checkParse ) where @@ -84,7 +82,7 @@ import Chainweb.Mempool.Mempool as Mempool import Chainweb.MinerReward import Chainweb.Miner.Pact -import Chainweb.Pact.Types +-- import Chainweb.Pact.Types import Chainweb.Pact4.SPV qualified as Pact4 import Chainweb.Pact4.NoCoinbase import qualified Chainweb.Pact4.Transaction as Pact4 @@ -99,106 +97,118 @@ import Chainweb.Version.Guards import Chainweb.Pact4.Backend.ChainwebPactDb import Data.Coerce import Data.Word -import GrowableVector.Lifted (Vec) import Control.Monad.Primitive -import qualified GrowableVector.Lifted as Vec import qualified Data.Set as S import Chainweb.Pact4.Types import Chainweb.Pact4.ModuleCache import Control.Monad.Except import qualified Data.List.NonEmpty as NE import Chainweb.Pact.Backend.Types (BlockHandle(..)) - +import Chainweb.Parent +import Chainweb.BlockCreationTime +import qualified Data.Map as M +import Chainweb.Pact.Types (ServiceEnv(..), Transactions (..), transactionPairs, bctxParentCreationTime, bctxParentHeight, _bctxIsGenesis, _bctxCurrentBlockHeight, BlockCtx, BlockInvalidError (..), TxInvalidError (..)) +import qualified Pact.Core.Command.Types as Pact5 +import qualified Pact.Core.Hash as Pact5 + +-- | Update init cache at adjusted parent block height (APBH). +-- Contents are merged with cache found at or before APBH. +-- APBH is 0 for genesis and (parent block height + 1) thereafter. +updateInitCache :: HasVersion => BlockCtx -> ModuleCache -> StateT ModuleInitCache IO () +updateInitCache bctx mc = get >>= \initCache -> do + let bf 0 = 0 + bf h = succ h + let pbh = bf (view (bctxParentHeight . _Parent) bctx) + + let cid = view chainId bctx + + put $ case M.lookupLE pbh initCache of + Nothing -> M.singleton pbh mc + Just (_,before) + | cleanModuleCache cid pbh -> + M.insert pbh mc initCache + | otherwise -> M.insert pbh (before <> mc) initCache -- | Execute a block -- only called in validate either for replay or for validating current block. -- execBlock - :: (CanReadablePayloadCas tbl, Logger logger) - => BlockHeader - -- ^ this is the current header. We may consider changing this to the parent - -- header to avoid confusion with new block and prevent using data from this - -- header when we should use the respective values from the parent header - -- instead. + :: (CanReadablePayloadCas tbl, Logger logger1, Logger logger2) + => HasVersion + => logger1 + -> ServiceEnv tbl + -> BlockEnv logger2 -> CheckablePayload - -> PactBlockM logger tbl (Pact4.Gas, PayloadWithOutputs) -execBlock currHeader payload = do + -> ExceptT BlockInvalidError IO (Pact4.Gas, PayloadWithOutputs) +execBlock logger serviceEnv blockEnv payload = do let plData = checkablePayloadToPayloadData payload - dbEnv <- view psBlockDbEnv miner <- decodeStrictOrThrow' (_minerData $ view payloadDataMiner plData) + let currentHeight = blockEnv ^. benvBlockCtx . to _bctxCurrentBlockHeight - trans <- liftIO $ pact4TransactionsFromPayload - (pact4ParserVersion v (_chainId currHeader) (view blockHeight currHeader)) + trans <- pact4TransactionsFromPayload + (pact4ParserVersion (blockEnv ^. benvBlockCtx . chainId) currentHeight) plData - logger <- view (psServiceEnv . psLogger) -- The reference time for tx timings validation. -- -- The legacy behavior is to use the creation time of the /current/ header. -- The new default behavior is to use the creation time of the /parent/ header. -- - txValidationTime <- if isGenesisBlockHeader currHeader - then return (ParentCreationTime $ view blockCreationTime currHeader) - else ParentCreationTime . view blockCreationTime . _parentHeader <$> view psParentHeader + let txValidationTime = + if blockEnv ^. benvBlockCtx . to _bctxIsGenesis + then Parent $ implicitVersion ^?! versionGenesis . genesisTime . atChain (view chainId serviceEnv) + else blockEnv ^. benvBlockCtx . bctxParentCreationTime -- prop_tx_ttl_validate errorsIfPresent <- liftIO $ forM (V.toList trans) $ \tx -> fmap (Pact4._cmdHash tx,) $ runExceptT $ - validateParsedChainwebTx logger v cid dbEnv txValidationTime - (view blockHeight currHeader) tx + validateParsedChainwebTx logger cid (blockEnv ^. benvDbEnv) txValidationTime + currentHeight tx - case NE.nonEmpty [ (hsh, sshow err) | (hsh, Left err) <- errorsIfPresent ] of + case NE.nonEmpty + [ (Pact5.RequestKey $ Pact5.Hash hsh, err) + | (Pact4.toUntypedHash -> Pact4.Hash hsh, Left err) <- errorsIfPresent ] + of Nothing -> return () - Just errs -> throwM $ Pact4TransactionValidationException errs + Just errs -> throwError $ BlockInvalidDueToInvalidTxs (coerce errs) + + liftIO logInitCache + + startModuleInitCache <- liftIO $ readMVar (_psModuleInitCacheVar serviceEnv) - logInitCache + !(results, finalModuleInitCache) <- runStateT (go miner trans) startModuleInitCache - !results <- go miner trans >>= throwCommandInvalidError + _ <- liftIO $ swapMVar (_psModuleInitCacheVar serviceEnv) finalModuleInitCache let !totalGasUsed = sumOf (folded . to Pact4._crGas) results pwo <- either throwM return $ - validateHashes currHeader payload miner results + validateHashes (blockEnv ^. benvBlockCtx) payload miner results return (totalGasUsed, pwo) where blockGasLimit = - fromIntegral <$> maxBlockGasLimit v (view blockHeight currHeader) + fromIntegral <$> maxBlockGasLimit (blockEnv ^. benvBlockCtx . to _bctxCurrentBlockHeight) - logInitCache = liftPactServiceM $ do - mc <- fmap (fmap instr . _getModuleCache) <$> use psInitCache - logDebugPact $ "execBlock: initCache: " <> sshow mc + logInitCache = do + mc <- fmap (fmap instr . _getModuleCache) <$> readMVar (_psModuleInitCacheVar serviceEnv) + liftIO $ logFunctionText logger Debug $ "execBlock: initCache: " <> sshow mc instr (md,_) = preview (Pact4._MDModule . Pact4.mHash) $ Pact4._mdModule md - v = _chainwebVersion currHeader - cid = _chainId currHeader + cid = blockEnv ^. benvBlockCtx . chainId - isGenesisBlock = isGenesisBlockHeader currHeader + isGenesisBlock = blockEnv ^. benvBlockCtx . to _bctxIsGenesis go m txs = if isGenesisBlock then -- GENESIS VALIDATE COINBASE: Reject bad coinbase, use date rule for precompilation - execTransactions m txs - (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled False) blockGasLimit Nothing + execTransactions logger serviceEnv blockEnv m txs + (EnforceCoinbaseFailure True) (CoinbaseUsePrecompiled False) blockGasLimit else -- VALIDATE COINBASE: back-compat allow failures, use date rule for precompilation - execTransactions m txs - (EnforceCoinbaseFailure False) (CoinbaseUsePrecompiled False) blockGasLimit Nothing - -throwCommandInvalidError - :: Transactions Pact4 (Either CommandInvalidError a) - -> PactBlockM logger tbl (Transactions Pact4 a) -throwCommandInvalidError = (transactionPairs . traverse . _2) throwGasFailure - where - throwGasFailure = \case - Left (CommandInvalidGasPurchaseFailure e) -> throwM (Pact4BuyGasFailure e) - - -- this should be impossible because we don't - -- set tx time limits in validateBlock - Left (CommandInvalidTxTimeout t) -> throwM t - - Right r -> pure r + execTransactions logger serviceEnv blockEnv m txs + (EnforceCoinbaseFailure False) (CoinbaseUsePrecompiled False) blockGasLimit -- | The validation logic for Pact Transactions that have not had their -- code parsed yet. This is used by the mempool to estimate tx validity @@ -211,19 +221,19 @@ throwCommandInvalidError = (transactionPairs . traverse . _2) throwGasFailure validateRawChainwebTx :: forall logger . (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> ChainId - -> PactDbFor logger Pact4 - -> ParentCreationTime + -> CurrentBlockDbEnv logger + -> Parent BlockCreationTime -- ^ reference time for tx validation. -> BlockHeight -- ^ Current block height -> Pact4.UnparsedTransaction -> ExceptT InsertError IO Pact4.Transaction -validateRawChainwebTx logger v cid dbEnv txValidationTime bh tx = do - parsed <- checkParse logger v cid bh tx - validateParsedChainwebTx logger v cid dbEnv txValidationTime bh parsed +validateRawChainwebTx logger cid dbEnv txValidationTime bh tx = do + parsed <- checkParse logger cid bh tx + validateParsedChainwebTx logger cid dbEnv txValidationTime bh parsed return parsed -- | The principal validation logic for groups of Pact Transactions. @@ -235,26 +245,26 @@ validateRawChainwebTx logger v cid dbEnv txValidationTime bh tx = do -- exist yet. -- validateParsedChainwebTx - :: Logger logger - => logger - -> ChainwebVersion + :: (Logger logger1, Logger logger2) + => HasVersion + => logger1 -> ChainId - -> PactDbFor logger Pact4 - -> ParentCreationTime + -> CurrentBlockDbEnv logger2 + -> Parent BlockCreationTime -- ^ reference time for tx validation. -> BlockHeight -- ^ Current block height -> Pact4.Transaction -> ExceptT InsertError IO () -validateParsedChainwebTx logger v cid dbEnv txValidationTime bh tx - | bh == genesisHeight v cid = pure () +validateParsedChainwebTx logger cid dbEnv txValidationTime bh tx + | bh == genesisHeight cid = pure () | otherwise = do checkUnique logger dbEnv tx - checkTxHash logger v cid bh tx + checkTxHash logger cid bh tx checkChain cid tx - checkTxSigs logger v cid bh tx - checkTimes logger v cid bh txValidationTime tx - _ <- checkCompile logger v cid bh tx + checkTxSigs logger cid bh tx + checkTimes logger cid bh txValidationTime tx + _ <- checkCompile logger cid bh tx return () checkChain @@ -266,9 +276,9 @@ checkChain cid tx = txCid = view (Pact4.cmdPayload . to Pact4.payloadObj . Pact4.pMeta . Pact4.pmChainId) tx checkUnique - :: (Logger logger) - => logger - -> PactDbFor logger Pact4 + :: (Logger logger1, Logger logger2) + => logger1 + -> CurrentBlockDbEnv logger2 -> Pact4.Command (Pact4.PayloadWithText meta code) -> ExceptT InsertError IO () checkUnique logger dbEnv t = do @@ -283,16 +293,16 @@ checkUnique logger dbEnv t = do checkTimes :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> ChainId -> BlockHeight - -> ParentCreationTime + -> Parent BlockCreationTime -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta code) -> ExceptT InsertError IO () -checkTimes logger v cid bh txValidationTime t = do +checkTimes logger cid bh txValidationTime t = do liftIO $ logFunctionText logger Debug $ "Pact4.checkTimes: " <> sshow (Pact4._cmdHash t) - if | skipTxTimingValidation v cid bh -> + if | skipTxTimingValidation cid bh -> return () | not (Pact4.assertTxNotInFuture txValidationTime (Pact4.payloadObj <$> t)) -> throwError InsertErrorTimeInFuture @@ -303,51 +313,51 @@ checkTimes logger v cid bh txValidationTime t = do checkTxHash :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> ChainId -> BlockHeight -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta code) -> ExceptT InsertError IO () -checkTxHash logger v cid bh t = do +checkTxHash logger cid bh t = do liftIO $ logFunctionText logger Debug $ "Pact4.checkTxHash: " <> sshow (Pact4._cmdHash t) case Pact4.verifyHash (Pact4._cmdHash t) (SB.fromShort $ Pact4.payloadBytes $ Pact4._cmdPayload t) of Left _ - | doCheckTxHash v cid bh -> throwError InsertErrorInvalidHash + | doCheckTxHash cid bh -> throwError InsertErrorInvalidHash | otherwise -> pure () Right _ -> pure () checkTxSigs :: (MonadIO f, MonadError InsertError f, Logger logger) + => HasVersion => logger - -> ChainwebVersion -> ChainId -> BlockHeight -> Pact4.Command (Pact4.PayloadWithText m c) -> f () -checkTxSigs logger v cid bh t = do +checkTxSigs logger cid bh t = do liftIO $ logFunctionText logger Debug $ "Pact4.checkTxSigs: " <> sshow (Pact4._cmdHash t) case Pact4.assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs of Right _ -> do pure () Left err -> do - throwError $ InsertErrorInvalidSigs (displayAssertValidateSigsError err) + throwError $ InsertErrorInvalidSigs (Pact4.displayAssertValidateSigsError err) where hsh = Pact4._cmdHash t sigs = Pact4._cmdSigs t signers = Pact4._pSigners $ Pact4.payloadObj $ Pact4._cmdPayload t - validSchemes = validPPKSchemes v cid bh - webAuthnPrefixLegal = isWebAuthnPrefixLegal v cid bh + validSchemes = validPPKSchemes cid bh + webAuthnPrefixLegal = isWebAuthnPrefixLegal cid bh checkCompile :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> ChainId -> BlockHeight -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Pact4.ParsedCode) -> ExceptT InsertError IO Pact4.Transaction -checkCompile logger v cid bh tx = do +checkCompile logger cid bh tx = do liftIO $ logFunctionText logger Debug $ "Pact4.checkCompile: " <> sshow (Pact4._cmdHash tx) case payload of Exec (ExecMsg parsedCode _) -> @@ -358,163 +368,140 @@ checkCompile logger v cid bh tx = do where payload = Pact4._pPayload $ Pact4.payloadObj $ Pact4._cmdPayload tx compileCode p = - let e = ParseEnv (chainweb216Pact v cid bh) + let e = ParseEnv (chainweb216Pact cid bh) in compileExps e (mkTextInfo (Pact4._pcCode p)) (Pact4._pcExps p) checkParse :: (Logger logger) + => HasVersion => logger - -> ChainwebVersion -> ChainId -> BlockHeight -> Pact4.Command (Pact4.PayloadWithText Pact4.PublicMeta Text) -> ExceptT InsertError IO Pact4.Transaction -checkParse logger v cid bh tx = do +checkParse logger cid bh tx = do liftIO $ logFunctionText logger Debug $ "Pact4.checkParse: " <> sshow (Pact4._cmdHash tx) forMOf (traversed . traversed) tx - (either (throwError . InsertErrorPactParseError . T.pack) return . Pact4.parsePact (pact4ParserVersion v cid bh)) + (either (throwError . InsertErrorPactParseError . T.pack) return . Pact4.parsePact (pact4ParserVersion cid bh)) execTransactions - :: (Logger logger) - => Miner + :: (Logger logger1, Logger logger2) + => HasVersion + => logger1 + -> ServiceEnv tbl + -> BlockEnv logger2 + -> Miner -> Vector Pact4.Transaction -> EnforceCoinbaseFailure -> CoinbaseUsePrecompiled -> Maybe Pact4.Gas - -> Maybe Micros - -> PactBlockM logger tbl (Transactions Pact4 (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))) -execTransactions miner ctxs enfCBFail usePrecomp gasLimit timeLimit = do - mc <- initModuleCacheForBlock + -> StateT ModuleInitCache (ExceptT BlockInvalidError IO) (Transactions Pact4.Transaction ((Pact4.CommandResult [Pact4.TxLogJson]))) +execTransactions logger serviceEnv blockEnv miner ctxs enfCBFail usePrecomp gasLimit = do + mc <- mapStateT lift $ initModuleCacheForBlock logger blockEnv -- for legacy reasons (ask Emily) we don't use the module cache resulting -- from coinbase to run the pact cmds - coinOut <- runCoinbase miner enfCBFail usePrecomp mc - T2 txOuts _mcOut <- applyPactCmds ctxs miner mc gasLimit timeLimit - return $! Transactions (V.zip ctxs txOuts) coinOut - -initModuleCacheForBlock :: (Logger logger) => PactBlockM logger tbl ModuleCache -initModuleCacheForBlock = do - PactServiceState{..} <- get - isGenesis <- view psIsGenesis - pbh <- views psParentHeader (view blockHeight . _parentHeader) - case Map.lookupLE pbh _psInitCache of + coinOut <- mapStateT lift $ runCoinbase logger enfCBFail usePrecomp blockEnv miner mc + T2 txOuts _mcOut <- lift $ applyPactCmds logger serviceEnv blockEnv ctxs miner mc gasLimit + return $! Transactions (V.zipWith T2 ctxs txOuts) coinOut + +initModuleCacheForBlock + :: (Logger logger1, Logger logger2) + => HasVersion + => logger1 + -> BlockEnv logger2 + -> StateT ModuleInitCache IO ModuleCache +initModuleCacheForBlock logger blockEnv = do + moduleInitCache <- get + let pbh = blockEnv ^. benvBlockCtx . bctxParentHeight . _Parent + let isGenesis = blockEnv ^. benvBlockCtx . to _bctxIsGenesis + case Map.lookupLE pbh moduleInitCache of Nothing -> if isGenesis then return mempty else do - mc <- Pact4.readInitModules - updateInitCacheM mc + mc <- liftIO $ Pact4.readInitModules logger blockEnv + updateInitCache (blockEnv ^. benvBlockCtx) mc return mc Just (_,mc) -> pure mc runCoinbase - :: (Logger logger) - => Miner + :: (Logger logger1, Logger logger2) + => HasVersion + => logger1 -> EnforceCoinbaseFailure -> CoinbaseUsePrecompiled + -> BlockEnv logger2 + -> Miner -> ModuleCache - -> PactBlockM logger tbl (Pact4.CommandResult [Pact4.TxLogJson]) -runCoinbase miner enfCBFail usePrecomp mc = do - isGenesis <- view psIsGenesis - if isGenesis + -> StateT ModuleInitCache IO (Pact4.CommandResult [Pact4.TxLogJson]) +runCoinbase logger enfCBFail usePrecomp blockEnv miner mc = do + if blockEnv ^. benvBlockCtx . to _bctxIsGenesis then return noCoinbase else do - logger <- view (psServiceEnv . psLogger) - v <- view chainwebVersion - txCtx <- getTxContext miner Pact4.noPublicMeta + let !bh = _bctxCurrentBlockHeight (blockEnv ^. benvBlockCtx) - let !bh = ctxCurrentBlockHeight txCtx - - let reward = minerReward v bh - dbEnv <- view psBlockDbEnv - let pactDb = _cpPactDbEnv dbEnv + let reward = minerReward bh T2 cr upgradedCacheM <- - liftIO $ Pact4.applyCoinbase v logger pactDb reward txCtx enfCBFail usePrecomp mc + liftIO $ Pact4.applyCoinbase logger blockEnv miner reward enfCBFail usePrecomp mc mapM_ upgradeInitCache upgradedCacheM - liftPactServiceM $ debugResult "runPact4Coinbase" (Pact4.crLogs %~ fmap J.Array $ cr) return $! cr where upgradeInitCache newCache = do - liftPactServiceM $ logInfoPact "Updating init cache for upgrade" - updateInitCacheM newCache - + liftIO $ logFunctionText logger Info "Updating init cache for upgrade" + updateInitCache (blockEnv ^. benvBlockCtx) newCache -data CommandInvalidError - = CommandInvalidGasPurchaseFailure !Pact4GasPurchaseFailure - | CommandInvalidTxTimeout !TxTimeout -- | Apply multiple Pact commands, incrementing the transaction Id for each. -- The output vector is in the same order as the input (i.e. you can zip it -- with the inputs.) applyPactCmds - :: forall logger tbl. (Logger logger) - => Vector Pact4.Transaction + :: forall logger1 logger2 tbl. (Logger logger1, Logger logger2) + => HasVersion + => logger1 + -> ServiceEnv tbl + -> BlockEnv logger2 + -> Vector Pact4.Transaction -> Miner -> ModuleCache -> Maybe Pact4.Gas - -> Maybe Micros - -> PactBlockM logger tbl (T2 (Vector (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))) ModuleCache) -applyPactCmds cmds miner startModuleCache blockGas txTimeLimit = do - let txsGas txs = fromIntegral $ sumOf (traversed . _Right . to Pact4._crGas) txs - (txOuts, T2 mcOut _) <- tracePactBlockM' "applyPactCmds" (\_ -> ()) (txsGas . fst) $ + -> ExceptT BlockInvalidError IO (T2 (Vector ((Pact4.CommandResult [Pact4.TxLogJson]))) ModuleCache) +applyPactCmds logger serviceEnv blockEnv cmds miner startModuleCache blockGas = do + (txOuts, T2 mcOut _) <- flip runStateT (T2 startModuleCache blockGas) $ go [] (zip [0..] $ V.toList cmds) return $! T2 (V.fromList . List.reverse $ txOuts) mcOut where go - :: [Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])] + :: [Pact4.CommandResult [Pact4.TxLogJson]] -> [(Word, Pact4.Transaction)] -> StateT (T2 ModuleCache (Maybe Pact4.Gas)) - (PactBlockM logger tbl) - [Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])] + (ExceptT BlockInvalidError IO) + [Pact4.CommandResult [Pact4.TxLogJson]] go !acc = \case [] -> do pure acc (txIdxInBlock, tx) : rest -> do - r <- applyPactCmd (TxBlockIdx txIdxInBlock) miner txTimeLimit tx - case r of - Left e@(CommandInvalidTxTimeout _) -> do - pure (Left e : acc) - Left e@(CommandInvalidGasPurchaseFailure _) -> do - go (Left e : acc) rest - Right a -> do - go (Right a : acc) rest + r <- applyPactCmd logger serviceEnv blockEnv (TxBlockIdx txIdxInBlock) miner tx + go (r : acc) rest applyPactCmd - :: (Logger logger) - => TxIdxInBlock + :: (Logger logger1, Logger logger2) + => HasVersion + => logger1 + -> ServiceEnv tbl + -> BlockEnv logger2 + -> TxIdxInBlock -> Miner - -> Maybe Micros -> Pact4.Transaction -> StateT (T2 ModuleCache (Maybe Pact4.Gas)) - (PactBlockM logger tbl) - (Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson])) -applyPactCmd txIdxInBlock miner txTimeLimit cmd = StateT $ \(T2 mcache maybeBlockGasRemaining) -> do - dbEnv <- view psBlockDbEnv - let pactDb = _cpPactDbEnv dbEnv - prevBlockState <- liftIO $ fmap _benvBlockState $ - readMVar $ pdPactDbVar pactDb - logger <- view (psServiceEnv . psLogger) - gasLogger <- view (psServiceEnv . psGasLogger) - txFailuresCounter <- view (psServiceEnv . psTxFailuresCounter) - isGenesis <- view psIsGenesis - v <- view chainwebVersion + (ExceptT BlockInvalidError IO) + (Pact4.CommandResult [Pact4.TxLogJson]) +applyPactCmd logger serviceEnv blockEnv txIdxInBlock miner cmd = StateT $ \(T2 mcache maybeBlockGasRemaining) -> do + let pactDb = blockEnv ^. benvDbEnv . cpPactDbEnv let - -- for errors so fatal that the tx doesn't make it in the block - onFatalError e - | Just (Pact4BuyGasFailure f) <- fromException e = pure (Left (CommandInvalidGasPurchaseFailure f), T2 mcache maybeBlockGasRemaining) - | Just t@(TxTimeout {}) <- fromException e = do - -- timeouts can occur at any point during the transaction, even after - -- gas has been bought (or even while gas is being redeemed, after the - -- transaction proper is done). therefore we need to revert the block - -- state ourselves if it happens. - liftIO $ Pact4.modifyMVar' - (pdPactDbVar pactDb) - (benvBlockState .~ prevBlockState) - pure (Left (CommandInvalidTxTimeout t), T2 mcache maybeBlockGasRemaining) - | otherwise = throwM e requestedTxGasLimit = view Pact4.cmdGasLimit (Pact4.payloadObj <$> cmd) -- notice that we add 1 to the remaining block gas here, to distinguish the -- cases "tx used exactly as much gas remained in the block" (which is fine) @@ -530,54 +517,51 @@ applyPactCmd txIdxInBlock miner txTimeLimit cmd = StateT $ \(T2 mcache maybeBloc initialGas = Pact4.initialGasOf (Pact4._cmdPayload cmd) let !hsh = Pact4._cmdHash cmd - handle onFatalError $ do - T2 result mcache' <- do - txCtx <- getTxContext miner (Pact4.publicMetaOf gasLimitedCmd) - let gasModel = getGasModel txCtx - if isGenesis - then liftIO $! Pact4.applyGenesisCmd logger pactDb Pact4.noSPVSupport txCtx gasLimitedCmd - else do - bhdb <- view (psServiceEnv . psBlockHeaderDb) - parent <- view psParentHeader - let spv = Pact4.pactSPV bhdb (_parentHeader parent) - let - !timeoutError = TxTimeout (pact4RequestKeyToTransactionHash $ Pact4.cmdToRequestKey cmd) - txTimeout io = case txTimeLimit of - Nothing -> do - logFunctionText logger Debug $ "txTimeLimit was not set - defaulting to a function of the block gas limit" - io - Just limit -> do - logFunctionText logger Debug $ "txTimeLimit was " <> sshow limit - maybe (throwM timeoutError) return =<< newTimeout (fromIntegral limit) io - txGas (T3 r _ _) = fromIntegral $ Pact4._crGas r - T3 r c _warns <- do - tracePactBlockM' "applyCmd" (\_ -> J.toJsonViaEncode hsh) txGas $ do - liftIO $ txTimeout $ - Pact4.applyCmd v logger gasLogger txFailuresCounter pactDb miner gasModel txCtx txIdxInBlock spv gasLimitedCmd initialGas mcache ApplySend - pure $ T2 r c - - if isGenesis - then updateInitCacheM mcache' - else liftPactServiceM $ debugResult "applyPactCmd" (Pact4.crLogs %~ fmap J.Array $ result) - - -- mark the tx as processed at the checkpointer. - liftIO $ _cpRegisterProcessedTx dbEnv (coerce $ Pact4.toUntypedHash hsh) - case maybeBlockGasRemaining of - Just blockGasRemaining - | Left _ <- Pact4._pactResult (Pact4._crResult result) - , blockGasRemaining < fromIntegral requestedTxGasLimit - -> throwM $ BlockGasLimitExceeded (fromIntegral requestedTxGasLimit - blockGasRemaining) - -- ^ this tx attempted to consume more gas than remains in the - -- block, so the block is invalid. we know this because failing - -- transactions consume their entire gas limit. - _ -> return () - let maybeBlockGasRemaining' = (\g -> g - Pact4._crGas result) <$> maybeBlockGasRemaining - pure (Right result, T2 mcache' maybeBlockGasRemaining') + T2 result mcache' <- do + let gasModel = getGasModel bCtx + if _bctxIsGenesis bCtx + then liftIO $! Pact4.applyGenesisCmd logger pactDb Pact4.noSPVSupport bCtx gasLimitedCmd + else do + -- FIXME + -- let bhdb = view psBlockHeaderDb serviceEnv + -- let spv = Pact4.pactSPV bhdb (_parentHeader parent) + let + txTimeout io = do + logFunctionText logger Debug $ "txTimeLimit was not set - defaulting to a function of the block gas limit" + io + T3 r c _warns <- do + liftIO $ txTimeout $ + Pact4.applyCmd logger + -- FIXME spv + blockEnv miner gasModel txIdxInBlock undefined gasLimitedCmd initialGas mcache + pure $ T2 r c + + if _bctxIsGenesis bCtx + then return () + -- FIXME: what? why are we doing this here? is this a bug? + -- _k $ updateInitCache (bCtx ^. tcParentHeader) mcache' + else liftIO $ debugResult logger "applyPactCmd" (Pact4.crLogs %~ fmap J.Array $ result) + + -- mark the tx as processed at the checkpointer. + liftIO $ _cpRegisterProcessedTx (blockEnv ^. benvDbEnv) (coerce $ Pact4.toUntypedHash hsh) + case maybeBlockGasRemaining of + Just blockGasRemaining + | Left _ <- Pact4._pactResult (Pact4._crResult result) + , blockGasRemaining < fromIntegral requestedTxGasLimit + -> throwError $ BlockInvalidDueToInvalidTxAtRuntime TxExceedsBlockGasLimit + -- ^ this tx attempted to consume more gas than remains in the + -- block, so the block is invalid. we know this because failing + -- transactions consume their entire gas limit. + _ -> return () + let maybeBlockGasRemaining' = (\g -> g - Pact4._crGas result) <$> maybeBlockGasRemaining + pure (result, T2 mcache' maybeBlockGasRemaining') + where + bCtx = blockEnv ^. benvBlockCtx pact4TransactionsFromPayload :: Pact4.PactParserVersion -> PayloadData - -> IO (Vector Pact4.Transaction) + -> ExceptT BlockInvalidError IO (Vector Pact4.Transaction) pact4TransactionsFromPayload ppv plData = do vtrans <- fmap V.fromList $ mapM toCWTransaction $ @@ -585,16 +569,16 @@ pact4TransactionsFromPayload ppv plData = do let (theLefts, theRights) = partitionEithers $ V.toList vtrans unless (null theLefts) $ do let ls = map T.pack theLefts - throwM $ TransactionDecodeFailure $ "Failed to decode pact transactions: " - <> T.intercalate ". " ls + throwError $ BlockInvalidDueToTxDecodeFailure ls return $! V.fromList theRights where - toCWTransaction bs = evaluate (force (codecDecode (Pact4.payloadCodec ppv) $ - _transactionBytes bs)) + toCWTransaction bs = + liftIO $ evaluate $ + codecDecode (Pact4.payloadCodec ppv) (_transactionBytes bs) -debugResult :: J.Encode a => Logger logger => Text -> a -> PactServiceM logger tbl () -debugResult msg result = - logDebugPact $ trunc $ msg <> " result: " <> J.encodeText result +debugResult :: J.Encode a => Logger logger => logger -> Text -> a -> IO () +debugResult logger msg result = + logFunctionText logger Debug $ trunc $ msg <> " result: " <> J.encodeText result where trunc t | T.length t < limit = t | otherwise = T.take limit t <> " [truncated]" @@ -606,13 +590,13 @@ debugResult msg result = -- See: 'rewards/miner_rewards.csv' -- minerReward - :: ChainwebVersion - -> BlockHeight + :: HasVersion + => BlockHeight -> Pact4.ParsedDecimal -minerReward v = Pact4.ParsedDecimal +minerReward = Pact4.ParsedDecimal . _kda . minerRewardKda - . blockMinerReward v + . blockMinerReward {-# INLINE minerReward #-} data CRLogPair = CRLogPair Pact4.Hash [Pact4.TxLogJson] @@ -625,262 +609,27 @@ instance J.Encode CRLogPair where {-# INLINE build #-} validateHashes - :: BlockHeader + :: HasVersion + => BlockCtx -- ^ Current Header -> CheckablePayload -> Miner - -> Transactions Pact4 (Pact4.CommandResult [Pact4.TxLogJson]) - -> Either PactException PayloadWithOutputs -validateHashes bHeader payload miner transactions = - if newHash == prevHash + -> Transactions Pact4.Transaction (Pact4.CommandResult [Pact4.TxLogJson]) + -> Either PactInternalError PayloadWithOutputs +validateHashes bctx payload miner transactions = + if newHash == expectedPayloadHash then Right actualPwo - else Left $ BlockValidationFailure $ BlockValidationFailureMsg $ + else Left $ BlockValidationFailure $ J.encodeText $ J.object - [ "header" J..= J.encodeWithAeson (ObjectEncoded bHeader) + [ "header" J..= sshow @_ @Text bctx , "actual" J..= J.encodeWithAeson actualPwo , "expected" J..?= case payload of - CheckablePayload _ -> Nothing + CheckablePayload _ _ -> Nothing CheckablePayloadWithOutputs pwo -> Just $ J.encodeWithAeson pwo ] where - actualPwo = toPayloadWithOutputs Pact4T miner transactions + actualPwo = toPayloadWithOutputs miner transactions newHash = _payloadWithOutputsPayloadHash actualPwo - prevHash = view blockPayloadHash bHeader - -type GrowableVec = Vec (PrimState IO) - --- | Continue adding transactions to an existing block. -continueBlock - :: forall logger tbl - . (Logger logger, CanReadablePayloadCas tbl) - => MemPoolAccess - -> BlockInProgress Pact4 - -> PactBlockM logger tbl (BlockInProgress Pact4) -continueBlock mpAccess blockInProgress = do - v <- view chainwebVersion - cid <- view chainId - ParentHeader parent <- view psParentHeader - let pHeight = view blockHeight parent - let pHash = view blockHash parent - liftIO $ do - mpaProcessFork mpAccess parent - mpaSetLastHeader mpAccess parent - liftPactServiceM $ - logInfoPact $ "(parent height = " <> sshow pHeight <> ")" - <> " (parent hash = " <> sshow pHash <> ")" - - blockDbEnv <- view psBlockDbEnv - let pactDb = _cpPactDbEnv blockDbEnv - -- restore the block state from the block being continued - liftIO $ - modifyMVar_ (pdPactDbVar pactDb) $ \blockEnv -> - return - $! blockEnv - & benvBlockState . bsPendingBlock .~ _blockHandlePending (_blockInProgressHandle blockInProgress) - & benvBlockState . bsTxId .~ _blockHandleTxId (_blockInProgressHandle blockInProgress) - - blockGasLimit <- view (psServiceEnv . psBlockGasLimit) - mTxTimeLimit <- view (psServiceEnv . psTxTimeLimit) - - let txTimeHeadroomFactor :: Double - txTimeHeadroomFactor = 5 - let txTimeLimit :: Micros - -- 2.5 microseconds per unit gas - txTimeLimit = fromMaybe - (round $ (2.5 * txTimeHeadroomFactor) * fromIntegral blockGasLimit) - mTxTimeLimit - - let Pact4ModuleCache initCache = _blockInProgressModuleCache blockInProgress - let cb = _transactionCoinbase (_blockInProgressTransactions blockInProgress) - let startTxs = _transactionPairs (_blockInProgressTransactions blockInProgress) - - successes <- liftIO $ Vec.fromFoldable startTxs - failures <- liftIO $ Vec.new @_ @_ @TransactionHash - - let initState = BlockFill - (_blockInProgressRemainingGasLimit blockInProgress) - (S.fromList $ pact4RequestKeyToTransactionHash . Pact4._crReqKey . snd <$> V.toList startTxs) - 0 - - -- Heuristic: limit fetches to count of 1000-gas txs in block. - let fetchLimit = fromIntegral $ blockGasLimit `div` 1000 - T2 - finalModuleCache - BlockFill { _bfTxHashes = requestKeys, _bfGasLimit = finalGasLimit } - <- refill fetchLimit txTimeLimit successes failures initCache initState - - liftPactServiceM $ logInfoPact $ "(request keys = " <> sshow requestKeys <> ")" - - liftIO $ do - txHashes <- Vec.toLiftedVector failures - mpaBadlistTx mpAccess txHashes - - txs <- liftIO $ Vec.toLiftedVector successes - -- edmund: we need to be careful about timeouts. - -- If a tx times out, it must not be in the block state, otherwise - -- the "block in progress" will contain pieces of state from that tx. - -- - -- this cannot happen now because applyPactCmd doesn't let it. - finalBlockState <- fmap _benvBlockState - $ liftIO - $ readMVar - $ pdPactDbVar - $ pactDb - let !blockInProgress' = BlockInProgress - { _blockInProgressModuleCache = Pact4ModuleCache finalModuleCache - , _blockInProgressHandle = BlockHandle - { _blockHandleTxId = _bsTxId finalBlockState - , _blockHandlePending = _bsPendingBlock finalBlockState - } - , _blockInProgressParentHeader = newBlockParent - , _blockInProgressRemainingGasLimit = finalGasLimit - , _blockInProgressTransactions = Transactions - { _transactionCoinbase = cb - , _transactionPairs = txs - } - , _blockInProgressMiner = _blockInProgressMiner blockInProgress - , _blockInProgressPactVersion = Pact4T - , _blockInProgressChainwebVersion = v - , _blockInProgressChainId = cid - } - return blockInProgress' - where - newBlockParent = _blockInProgressParentHeader blockInProgress - - - getBlockTxs :: BlockFill -> PactBlockM logger tbl (Vector Pact4.Transaction) - getBlockTxs bfState = do - dbEnv <- view psBlockDbEnv - psEnv <- ask - let v = _chainwebVersion psEnv - cid = _chainId psEnv - logger <- view (psServiceEnv . psLogger) - -- parent time needs to know if we're *actually* at genesis - let parentTime = - maybe - (v ^?! versionGenesis . genesisTime . atChain cid) - (view blockCreationTime . _parentHeader) - newBlockParent - ParentHeader parent <- view psParentHeader - let pHeight = view blockHeight parent - let pHash = view blockHash parent - let validate bhi _bha txs = forM txs $ \tx -> runExceptT $ do - validateRawChainwebTx logger v cid dbEnv (ParentCreationTime parentTime) bhi tx - - liftIO $! - mpaGetBlock mpAccess bfState validate (pHeight + 1) pHash parentTime - - refill - :: Word64 - -> Micros - -> GrowableVec (Pact4.Transaction, Pact4.CommandResult [Pact4.TxLogJson]) - -> GrowableVec TransactionHash - -> ModuleCache - -> BlockFill - -> PactBlockM logger tbl (T2 ModuleCache BlockFill) - refill fetchLimit txTimeLimit successes failures = go - where - go :: ModuleCache -> BlockFill -> PactBlockM logger tbl (T2 ModuleCache BlockFill) - go mc unchanged@bfState = do - - case unchanged of - BlockFill g _ c -> do - (goodLength, badLength) <- liftIO $ (,) <$> Vec.length successes <*> Vec.length failures - liftPactServiceM $ logDebugPact $ "Block fill: count=" <> sshow c - <> ", gaslimit=" <> sshow g <> ", good=" - <> sshow goodLength <> ", bad=" <> sshow badLength - - -- LOOP INVARIANT: limit absolute recursion count - if _bfCount bfState > fetchLimit then liftPactServiceM $ do - logInfoPact $ "Refill fetch limit exceeded (" <> sshow fetchLimit <> ")" - pure (T2 mc unchanged) - else do - when (_bfGasLimit bfState < 0) $ - throwM $ MempoolFillFailure $ "Internal error, negative gas limit: " <> sshow bfState - - if _bfGasLimit bfState == 0 then pure (T2 mc unchanged) else do - - newTrans <- getBlockTxs bfState - if V.null newTrans then pure (T2 mc unchanged) else do - - T2 pairs mc' <- do - T2 txOuts mcOut <- applyPactCmds newTrans (_blockInProgressMiner blockInProgress) mc Nothing (Just txTimeLimit) - return $! T2 (V.force (V.zip newTrans txOuts)) mcOut - - oldSuccessesLength <- liftIO $ Vec.length successes - - (newState, timedOut) <- splitResults successes failures unchanged (V.toList pairs) - - -- LOOP INVARIANT: gas must not increase - when (_bfGasLimit newState > _bfGasLimit bfState) $ - throwM $ MempoolFillFailure $ "Gas must not increase: " <> sshow (bfState,newState) - - newSuccessesLength <- liftIO $ Vec.length successes - let addedSuccessCount = newSuccessesLength - oldSuccessesLength - - if timedOut - then - -- a transaction timed out, so give up early and make the block - pure (T2 mc' (incCount newState)) - else if _bfGasLimit newState >= _bfGasLimit bfState && addedSuccessCount > 0 - then - -- INVARIANT: gas must decrease if any transactions succeeded - throwM $ MempoolFillFailure - $ "Invariant failure, gas did not decrease: " - <> sshow (bfState,newState,V.length newTrans,addedSuccessCount) - else - go mc' (incCount newState) - - incCount :: BlockFill -> BlockFill - incCount b = over bfCount succ b - - -- | Split the results of applying each command into successes and failures, - -- and return the final 'BlockFill'. - -- - -- If we encounter a 'TxTimeout', we short-circuit, and only return - -- what we've put into the block before the timeout. We also report - -- that we timed out, so that `refill` can stop early. - -- - -- The failed txs are later badlisted. - splitResults :: () - => GrowableVec (Pact4.Transaction, Pact4.CommandResult [Pact4.TxLogJson]) - -> GrowableVec TransactionHash -- ^ failed txs - -> BlockFill - -> [(Pact4.Transaction, Either CommandInvalidError (Pact4.CommandResult [Pact4.TxLogJson]))] - -> PactBlockM logger tbl (BlockFill, Bool) - splitResults successes failures = go - where - go acc@(BlockFill g rks i) = \case - [] -> pure (acc, False) - (t, r) : rest -> case r of - Right cr -> do - !rks' <- enforceUnique rks (pact4RequestKeyToTransactionHash $ Pact4._crReqKey cr) - -- Decrement actual gas used from block limit - let !g' = g - fromIntegral (Pact4._crGas cr) - liftIO $ Vec.push successes (t, cr) - go (BlockFill g' rks' i) rest - Left (CommandInvalidGasPurchaseFailure (Pact4GasPurchaseFailure h _)) -> do - !rks' <- enforceUnique rks h - -- Gas buy failure adds failed request key to fail list only - liftIO $ Vec.push failures h - go (BlockFill g rks' i) rest - Left (CommandInvalidTxTimeout (TxTimeout h)) -> do - liftIO $ Vec.push failures h - liftPactServiceM $ logErrorPact $ "timed out on " <> sshow h - return (acc, True) - - enforceUnique rks rk - | S.member rk rks = - throwM $ MempoolFillFailure $ "Duplicate transaction: " <> sshow rk - | otherwise = return $ S.insert rk rks - --- | This timeout variant returns Nothing if the timeout elapsed, regardless of whether or not it was actually able to interrupt its argument. --- This is more robust in the face of scheduler behavior than the standard 'System.Timeout.timeout', with small timeouts. -newTimeout :: Int -> IO a -> IO (Maybe a) -newTimeout n f = do - (timeSpan, a) <- Chronos.stopwatch (timeout n f) - if Chronos.getTimespan timeSpan > fromIntegral (n * 1000) - then return Nothing - else return a + expectedPayloadHash = checkablePayloadExpectedHash payload diff --git a/src/Chainweb/Pact/SPV.hs b/src/Chainweb/Pact/SPV.hs index 06ebef4bb4..c8ab2c7fe6 100644 --- a/src/Chainweb/Pact/SPV.hs +++ b/src/Chainweb/Pact/SPV.hs @@ -30,7 +30,7 @@ import Chainweb.Pact.Backend.Types import Chainweb.Parent import Chainweb.Payload (TransactionOutput(..)) import Chainweb.SPV (TransactionOutputProof(..), outputProofChainId) -import Chainweb.SPV.VerifyProof (runTransactionOutputProof) +import Chainweb.SPV.VerifyProof (runTransactionOutputProof, checkProofAndExtractOutput) import Chainweb.Utils (decodeB64UrlNoPaddingText, unlessM) import Chainweb.Version qualified as CW @@ -40,13 +40,6 @@ pactSPV oracle = SPVSupport , _spvVerifyContinuation = \contProof -> verifyCont oracle contProof } -checkProofAndExtractOutput :: HeaderOracle -> TransactionOutputProof ChainwebMerkleHashAlgorithm -> ExceptT Text IO TransactionOutput -checkProofAndExtractOutput oracle proof@(TransactionOutputProof _cid p) = do - h <- runTransactionOutputProof proof - unlessM (liftIO $ oracle.consult (Parent h)) $ throwError - "spv verification failed: target header is not in the chain" - proofSubject p - -- | Attempt to verify an SPV proof of a continuation given -- a continuation payload object bytestring. On success, returns -- the 'DefPactExec' associated with the proof. diff --git a/src/Chainweb/Pact/TransactionExec.hs b/src/Chainweb/Pact/TransactionExec.hs index 339f50cb1c..2af40e505a 100644 --- a/src/Chainweb/Pact/TransactionExec.hs +++ b/src/Chainweb/Pact/TransactionExec.hs @@ -493,9 +493,9 @@ applyUpgrades -> BlockCtx -> IO () applyUpgrades logger db txCtx - | Just PactUpgrade{_pactUpgradeTransactions = upgradeTxs} <- + | Just Pact5Upgrade{_pact5UpgradeTransactions = upgradeTxs} <- implicitVersion ^? versionUpgrades . atChain cid . ix currentHeight = applyUpgrade upgradeTxs - | otherwise = return () + | otherwise = return () where currentHeight = _bctxCurrentBlockHeight txCtx cid = _chainId txCtx diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index 8bb50d4673..23bbaf2d19 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -39,6 +39,7 @@ module Chainweb.Pact.Types , psNewBlockGasLimit , psGenesisPayload , psBlockRefreshInterval + , psModuleInitCacheVar , BlockCtx(..) , blockCtxOfEvaluationCtx @@ -48,6 +49,11 @@ module Chainweb.Pact.Types , _bctxIsGenesis , _bctxCurrentBlockHeight , genesisEvaluationCtx + , bctxParentCreationTime + , bctxParentHash + , bctxParentHeight + , bctxChainId + , bctxMinerReward , PactServiceConfig(..) , defaultPactServiceConfig @@ -171,6 +177,7 @@ import Chainweb.Miner.Pact (Miner, toMinerData, noMiner) import Chainweb.Pact.Backend.ChainwebPactDb import Chainweb.Pact.Backend.Types import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact4.ModuleCache import Chainweb.Payload qualified as Chainweb import Chainweb.Payload.PayloadStore import Chainweb.PayloadProvider.P2P @@ -193,6 +200,7 @@ import Chainweb.MinerReward import Chainweb.BlockCreationTime import Control.Concurrent.Async import qualified Data.Aeson as A +import Control.Concurrent.MVar (MVar) data Transactions t r = Transactions { _transactionPairs :: !(Vector (T2 t r)) @@ -357,6 +365,8 @@ data BlockCtx = BlockCtx , _bctxMinerReward :: !MinerReward } deriving stock (Eq, Generic, Show) +makeLenses ''BlockCtx + instance ToJSON BlockCtx where toJSON BlockCtx{..} = object [ "parentCreationTime" A..= _bctxParentCreationTime @@ -534,6 +544,7 @@ data ServiceEnv tbl = ServiceEnv , _psGenesisPayload :: Maybe Chainweb.PayloadWithOutputs -- ^ The genesis payload for this chain. , _psBlockRefreshInterval :: Micros + , _psModuleInitCacheVar :: MVar ModuleInitCache } instance HasChainId (ServiceEnv tbl) where diff --git a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs index 80226154d6..026b866ef0 100644 --- a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs @@ -34,9 +34,12 @@ module Chainweb.Pact4.Backend.ChainwebPactDb , cpLookupProcessedTx , callDb , BlockEnv(..) +, benvDbEnv +, benvBlockCtx +, BlockDbEnv(..) , blockHandlerEnv , benvBlockState -, runBlockEnv +, runBlockDbEnv , BlockState(..) , bsPendingBlock , bsTxId @@ -49,7 +52,6 @@ module Chainweb.Pact4.Backend.ChainwebPactDb , blockHandlerModuleNameFix , blockHandlerSortedKeys , blockHandlerLowerCaseTables -, blockHandlerPersistIntraBlockWrites , mkBlockHandlerEnv , domainTableName @@ -60,6 +62,8 @@ module Chainweb.Pact4.Backend.ChainwebPactDb , convPactId , commitBlockStateToDatabase + +, headerOracleForBlock ) where import Control.Applicative @@ -69,7 +73,7 @@ import Control.Monad.Reader import Control.Monad.State.Strict import Control.Monad.Trans.Maybe -import Data.Aeson hiding ((.=)) +import Data.Aeson hiding ((.=), Error) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as B8 import qualified Data.DList as DL @@ -84,6 +88,7 @@ import qualified Data.Set as Set import Data.String import qualified Data.Text as T import qualified Data.Text.Encoding as T +import System.LogLevel import Database.SQLite3.Direct as SQ3 @@ -108,8 +113,8 @@ import Chainweb.BlockHash import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Pact.Backend.DbCache -import Chainweb.Pact.Backend.Utils -import Chainweb.Pact.Types +-- import Chainweb.Pact.Backend.Utils hiding (SType(..), RType(..), bindParams, qry) +import Chainweb.Pact4.Types import Chainweb.Utils import Chainweb.Version import Pact.Interpreter (PactDbEnv) @@ -119,9 +124,13 @@ import Control.Concurrent import Chainweb.Version.Guards import Control.Exception.Safe import Pact.Types.Command (RequestKey) -import Chainweb.Pact.Backend.Types import Chainweb.Utils.Serialization (runPutS) import Data.Foldable +import Chainweb.Pact.Backend.Types (SQLiteEnv, HeaderOracle (..)) +import Chainweb.Pact.Backend.Utils (toUtf8, asStringUtf8, tbl, fromUtf8) +import Chainweb.BlockPayloadHash (BlockPayloadHash, encodeBlockPayloadHash) +import Chainweb.Pact.Types (BlockCtx) +import Chainweb.Parent execMulti :: Traversable t => SQ3.Database -> SQ3.Utf8 -> t [SType] -> IO () execMulti db q rows = bracket (prepStmt db q) destroy $ \stmt -> do @@ -175,41 +184,41 @@ data BlockHandlerEnv logger = BlockHandlerEnv { _blockHandlerDb :: !SQLiteEnv , _blockHandlerLogger :: !logger , _blockHandlerBlockHeight :: !BlockHeight + , _blockHandlerChainId :: !ChainId , _blockHandlerModuleNameFix :: !Bool , _blockHandlerSortedKeys :: !Bool , _blockHandlerLowerCaseTables :: !Bool - , _blockHandlerPersistIntraBlockWrites :: !IntraBlockPersistence } mkBlockHandlerEnv - :: ChainwebVersion -> ChainId -> BlockHeight - -> SQLiteEnv -> IntraBlockPersistence -> logger -> BlockHandlerEnv logger -mkBlockHandlerEnv v cid bh sql p logger = BlockHandlerEnv + :: HasVersion => ChainId -> BlockHeight + -> SQLiteEnv -> logger -> BlockHandlerEnv logger +mkBlockHandlerEnv cid bh sql logger = BlockHandlerEnv { _blockHandlerDb = sql , _blockHandlerLogger = logger , _blockHandlerBlockHeight = bh - , _blockHandlerModuleNameFix = enableModuleNameFix v cid bh - , _blockHandlerSortedKeys = pact42 v cid bh - , _blockHandlerLowerCaseTables = chainweb217Pact v cid bh - , _blockHandlerPersistIntraBlockWrites = p + , _blockHandlerChainId = cid + , _blockHandlerModuleNameFix = enableModuleNameFix cid bh + , _blockHandlerSortedKeys = pact42 cid bh + , _blockHandlerLowerCaseTables = chainweb217Pact cid bh } makeLenses ''BlockHandlerEnv -data BlockEnv logger = - BlockEnv +data BlockDbEnv logger = + BlockDbEnv { _blockHandlerEnv :: !(BlockHandlerEnv logger) , _benvBlockState :: !BlockState -- ^ The current block state. } -runBlockEnv :: MVar (BlockEnv logger) -> BlockHandler logger a -> IO a -runBlockEnv e m = modifyMVar e $ - \(BlockEnv dbenv bs) -> do +runBlockDbEnv :: MVar (BlockDbEnv logger) -> BlockHandler logger a -> IO a +runBlockDbEnv e m = modifyMVar e $ + \(BlockDbEnv dbenv bs) -> do (!a,!s) <- runStateT (runReaderT (runBlockHandler m) dbenv) bs - return (BlockEnv dbenv s, a) + return (BlockDbEnv dbenv s, a) -- this monad allows access to the database environment "at" a particular block. --- unfortunately, this is tied to a useless MVar via runBlockEnv, which will +-- unfortunately, this is tied to a useless MVar via runBlockDbEnv, which will -- be deleted with pact 5. newtype BlockHandler logger a = BlockHandler { runBlockHandler :: ReaderT (BlockHandlerEnv logger) (StateT BlockState IO) a @@ -232,8 +241,8 @@ newtype BlockHandler logger a = BlockHandler -- on tx failure. data BlockState = BlockState { _bsTxId :: !TxId - , _bsPendingBlock :: !(SQLitePendingData (PendingWrites Pact4)) - , _bsPendingTx :: !(Maybe (SQLitePendingData (PendingWrites Pact4))) + , _bsPendingBlock :: !SQLitePendingData + , _bsPendingTx :: !(Maybe SQLitePendingData) , _bsMode :: !(Maybe ExecutionMode) , _bsModuleCache :: !(DbCache PersistModuleData) } @@ -246,52 +255,51 @@ initBlockState initBlockState cl txid = BlockState { _bsTxId = txid , _bsMode = Nothing - , _bsPendingBlock = emptySQLitePendingData mempty + , _bsPendingBlock = emptySQLitePendingData , _bsPendingTx = Nothing , _bsModuleCache = emptyDbCache cl } -makeLenses ''BlockEnv +makeLenses ''BlockDbEnv makeLenses ''BlockState -- this is effectively a read-write snapshot of the Pact state at a block. data CurrentBlockDbEnv logger = CurrentBlockDbEnv - { _cpPactDbEnv :: !(PactDbEnv (BlockEnv logger)) + { _cpPactDbEnv :: !(PactDbEnv (BlockDbEnv logger)) , _cpRegisterProcessedTx :: !(RequestKey -> IO ()) , _cpLookupProcessedTx :: !(Vector RequestKey -> IO (HashMap RequestKey (T2 BlockHeight BlockHash))) + , _cpHeaderOracle :: !HeaderOracle } makeLenses ''CurrentBlockDbEnv -type instance PactDbFor logger Pact4 = CurrentBlockDbEnv logger - -- | Pact DB which reads from the tip of the checkpointer -chainwebPactDb :: (Logger logger) => PactDb (BlockEnv logger) +chainwebPactDb :: (Logger logger) => PactDb (BlockDbEnv logger) chainwebPactDb = PactDb - { _readRow = \d k e -> runBlockEnv e $ doReadRow Nothing d k - , _writeRow = \wt d k v e -> runBlockEnv e $ doWriteRow Nothing wt d k v - , _keys = \d e -> runBlockEnv e $ doKeys Nothing d - , _txids = \t txid e -> runBlockEnv e $ doTxIds t txid - , _createUserTable = \tn mn e -> runBlockEnv e $ doCreateUserTable Nothing tn mn + { _readRow = \d k e -> runBlockDbEnv e $ doReadRow Nothing d k + , _writeRow = \wt d k v e -> runBlockDbEnv e $ doWriteRow Nothing wt d k v + , _keys = \d e -> runBlockDbEnv e $ doKeys Nothing d + , _txids = \t txid e -> runBlockDbEnv e $ doTxIds t txid + , _createUserTable = \tn mn e -> runBlockDbEnv e $ doCreateUserTable Nothing tn mn , _getUserTableInfo = \_ -> error "WILL BE DEPRECATED!" - , _beginTx = \m e -> runBlockEnv e $ doBegin m - , _commitTx = \e -> runBlockEnv e doCommit - , _rollbackTx = \e -> runBlockEnv e doRollback - , _getTxLog = \d tid e -> runBlockEnv e $ doGetTxLog d tid + , _beginTx = \m e -> runBlockDbEnv e $ doBegin m + , _commitTx = \e -> runBlockDbEnv e doCommit + , _rollbackTx = \e -> runBlockDbEnv e doRollback + , _getTxLog = \d tid e -> runBlockDbEnv e $ doGetTxLog d tid } -- | Pact DB which reads from some past block height, instead of the tip of the checkpointer -rewoundPactDb :: (Logger logger) => BlockHeight -> TxId -> PactDb (BlockEnv logger) +rewoundPactDb :: (Logger logger) => BlockHeight -> TxId -> PactDb (BlockDbEnv logger) rewoundPactDb bh endTxId = chainwebPactDb - { _readRow = \d k e -> runBlockEnv e $ doReadRow (Just (bh, endTxId)) d k - , _writeRow = \wt d k v e -> runBlockEnv e $ doWriteRow (Just (bh, endTxId)) wt d k v - , _keys = \d e -> runBlockEnv e $ doKeys (Just (bh, endTxId)) d - , _createUserTable = \tn mn e -> runBlockEnv e $ doCreateUserTable (Just bh) tn mn + { _readRow = \d k e -> runBlockDbEnv e $ doReadRow (Just (bh, endTxId)) d k + , _writeRow = \wt d k v e -> runBlockDbEnv e $ doWriteRow (Just (bh, endTxId)) wt d k v + , _keys = \d e -> runBlockDbEnv e $ doKeys (Just (bh, endTxId)) d + , _createUserTable = \tn mn e -> runBlockDbEnv e $ doCreateUserTable (Just bh) tn mn } -- returns pending writes in the reverse order they were made -getPendingData :: BlockHandler logger [SQLitePendingData (PendingWrites Pact4)] +getPendingData :: BlockHandler logger [SQLitePendingData] getPendingData = do pb <- use bsPendingBlock ptx <- maybeToList <$> use bsPendingTx @@ -349,7 +357,7 @@ doReadRow mlim d k = forModuleNameFix $ \mnFix -> lookupInPendingData :: forall logger v . FromJSON v => Utf8 - -> SQLitePendingData (PendingWrites Pact4) + -> SQLitePendingData -> MaybeT (BlockHandler logger) v lookupInPendingData (Utf8 rowkey) p = do -- we get the latest-written value at this rowkey @@ -616,7 +624,7 @@ recordTxLog tt d k v = do !txlogs = DL.singleton $! encodeTxLog $ TxLog (asString d) (asString k) v modifyPendingData - :: (SQLitePendingData (PendingWrites Pact4) -> SQLitePendingData (PendingWrites Pact4)) + :: (SQLitePendingData -> SQLitePendingData) -> BlockHandler logger () modifyPendingData f = do m <- use bsPendingTx @@ -678,37 +686,35 @@ doCommit :: BlockHandler logger [TxLogJson] doCommit = use bsMode >>= \case Nothing -> doRollback >> internalError "doCommit: Not in transaction" Just m -> do - txrs <- if m == Transactional - then do - modify' $ over bsTxId succ - -- merge pending tx into block data - pending <- use bsPendingTx - persistIntraBlockWrites <- view blockHandlerPersistIntraBlockWrites - modify' $ over bsPendingBlock (merge persistIntraBlockWrites pending) - -- this is mostly a lie; the previous `merge` call has already replaced the tx - -- logs from bsPendingBlock with those of the transaction. - -- from what I can tell, it's impossible for `pending` to be `Nothing` here, - -- but we don't throw an error for it. - blockLogs <- use $ bsPendingBlock . pendingTxLogMap - modify' $ set bsPendingTx Nothing - resetTemp - return blockLogs - else doRollback >> return mempty + txrs <- + if m == Transactional + then do + modify' $ over bsTxId succ + -- merge pending tx into block data + pending <- use bsPendingTx + modify' $ over bsPendingBlock (merge pending) + -- this is mostly a lie; the previous `merge` call has already replaced the tx + -- logs from bsPendingBlock with those of the transaction. + -- from what I can tell, it's impossible for `pending` to be `Nothing` here, + -- but we don't throw an error for it. + blockLogs <- use $ bsPendingBlock . pendingTxLogMap + modify' $ set bsPendingTx Nothing + resetTemp + return blockLogs + else doRollback >> return mempty return $! concatMap (reverse . DL.toList) txrs - where - merge _ Nothing a = a - merge persistIntraBlockWrites (Just txPending) blockPending = SQLitePendingData + where + merge Nothing a = a + merge (Just txPending) blockPending = SQLitePendingData { _pendingTableCreation = HashSet.union (_pendingTableCreation txPending) (_pendingTableCreation blockPending) , _pendingWrites = HashMap.unionWith (HashMap.unionWith mergeAtRowKey) (_pendingWrites txPending) (_pendingWrites blockPending) , _pendingTxLogMap = _pendingTxLogMap txPending , _pendingSuccessfulTxs = _pendingSuccessfulTxs blockPending } where - mergeAtRowKey txWrites blockWrites = + mergeAtRowKey txWrites _ = let lastTxWrite = NE.head txWrites - in case persistIntraBlockWrites of - PersistIntraBlockWrites -> lastTxWrite `NE.cons` blockWrites - DoNotPersistIntraBlockWrites -> lastTxWrite :| [] + in lastTxWrite :| [] {-# INLINE doCommit #-} -- | Begin a Pact transaction @@ -717,13 +723,13 @@ doBegin m = do logger <- view blockHandlerLogger use bsMode >>= \case Just {} -> do - logError_ logger "PactDb.beginTx: In transaction, rolling back" + liftIO $ logFunctionText logger Error "PactDb.beginTx: In transaction, rolling back" doRollback Nothing -> return () resetTemp modify' $ set bsMode (Just m) - . set bsPendingTx (Just $ emptySQLitePendingData mempty) + . set bsPendingTx (Just emptySQLitePendingData) case m of Transactional -> Just <$> use bsTxId Local -> pure Nothing @@ -798,16 +804,17 @@ indexPactTransaction h = modify' $ vacuumDb :: BlockHandler logger () vacuumDb = callDb "vacuumDb" (`exec_` "VACUUM;") -commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockHeight -> BlockHandle Pact4 -> IO () -commitBlockStateToDatabase db hsh bh blockHandle = do - let newTables = _pendingTableCreation $ _blockHandlePending blockHandle +commitBlockStateToDatabase :: SQLiteEnv -> BlockHash -> BlockPayloadHash -> BlockHeight -> BlockState -> IO () +commitBlockStateToDatabase db blockHash payloadHash bh blockState = do + let newTables = _pendingTableCreation $ pendingBlock mapM_ (\tn -> createUserTable tn) newTables - let writeV = toChunks $ _pendingWrites (_blockHandlePending blockHandle) + let writeV = toChunks $ _pendingWrites pendingBlock backendWriteUpdateBatch writeV indexPendingPactTransactions - let nextTxId = _blockHandleTxId blockHandle + let nextTxId = _bsTxId blockState blockHistoryInsert nextTxId where + pendingBlock = _bsPendingBlock blockState toChunks writes = over _2 (concatMap toList . HashMap.elems) . over _1 toUtf8 <$> HashMap.toList writes @@ -841,12 +848,13 @@ commitBlockStateToDatabase db hsh bh blockHandle = do blockHistoryInsert t = exec' db stmt [ SInt (fromIntegral bh) - , SBlob (runPutS (encodeBlockHash hsh)) + , SBlob (runPutS (encodeBlockHash blockHash)) + , SBlob (runPutS (encodeBlockPayloadHash payloadHash)) , SInt (fromIntegral t) ] where stmt = - "INSERT INTO BlockHistory ('blockheight','hash','endingtxid') VALUES (?,?,?);" + "INSERT INTO BlockHistory ('blockheight','hash','payloadhash','endingtxid') VALUES (?,?,?,?);" createUserTable :: Text -> IO () createUserTable (toUtf8 -> tablename) = do @@ -864,7 +872,7 @@ commitBlockStateToDatabase db hsh bh blockHandle = do -- | Commit the index of pending successful transactions to the database indexPendingPactTransactions :: IO () indexPendingPactTransactions = do - let txs = _pendingSuccessfulTxs $ _blockHandlePending blockHandle + let txs = _pendingSuccessfulTxs pendingBlock dbIndexTransactions txs where @@ -889,3 +897,27 @@ createVersionedTable tablename db = do \, UNIQUE (rowkey, txid));" indexcreationstmt = "CREATE INDEX IF NOT EXISTS " <> tbl ixName <> " ON " <> tbl tablename <> "(txid DESC);" + +data BlockEnv logger = BlockEnv + { _benvBlockCtx :: !BlockCtx + , _benvDbEnv :: !(CurrentBlockDbEnv logger) + } +makeLenses ''BlockEnv + +lookupBlockHash :: SQ3.Database -> BlockHash -> IO (Maybe BlockHeight) +lookupBlockHash db hash = do + qry db qtext [SBlob (runPutS (encodeBlockHash hash))] [RInt] >>= \case + [[SInt n]] -> return $! Just $! int n + [] -> return $ Nothing + res -> error $ "Invalid result, " <> sshow res + where + qtext = "SELECT blockheight FROM BlockHistory WHERE hash = ?;" + +headerOracleForBlock :: BlockHandlerEnv logger -> HeaderOracle +headerOracleForBlock env = HeaderOracle + { consult = \(Parent blkHash) -> do + lookupBlockHash (_blockHandlerDb env) blkHash <&> \case + Nothing -> False + Just rootHeight -> rootHeight > _blockHandlerBlockHeight env + , chain = _blockHandlerChainId env + } diff --git a/src/Chainweb/Pact4/ModuleCache.hs b/src/Chainweb/Pact4/ModuleCache.hs index ea961e8b80..49fce6fcdd 100644 --- a/src/Chainweb/Pact4/ModuleCache.hs +++ b/src/Chainweb/Pact4/ModuleCache.hs @@ -9,7 +9,8 @@ {-# LANGUAGE LambdaCase #-} module Chainweb.Pact4.ModuleCache - ( ModuleCache(..) + ( ModuleInitCache + , ModuleCache(..) , filterModuleCacheByKey , moduleCacheToHashMap , moduleCacheFromHashMap @@ -34,6 +35,9 @@ import Chainweb.ChainId import Chainweb.Version import qualified Pact.JSON.Legacy.HashMap as LHM +import qualified Data.Map as M + +type ModuleInitCache = M.Map BlockHeight ModuleCache -- | Block scoped Module Cache -- @@ -66,9 +70,9 @@ moduleCacheKeys (ModuleCache a) = fst <$> LHM.toList a -- this can't go in Chainweb.Version.Guards because it causes an import cycle -- it uses genesisHeight which is from BlockHeader which imports Guards -cleanModuleCache :: ChainwebVersion -> ChainId -> BlockHeight -> Bool -cleanModuleCache v cid bh = - case v ^?! versionForks . at Chainweb217Pact . _Just . atChain cid of +cleanModuleCache :: HasVersion => ChainId -> BlockHeight -> Bool +cleanModuleCache cid bh = + case implicitVersion ^?! versionForks . at Chainweb217Pact . _Just . atChain cid of ForkAtBlockHeight bh' -> bh == bh' - ForkAtGenesis -> bh == genesisHeight v cid + ForkAtGenesis -> bh == genesisHeight cid ForkNever -> False diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs index c0a2ddad3b..1b3643990a 100644 --- a/src/Chainweb/Pact4/SPV.hs +++ b/src/Chainweb/Pact4/SPV.hs @@ -64,8 +64,9 @@ import qualified Streaming.Prelude as S import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.BlockHeight -import Chainweb.Pact.Types(internalError) +import Chainweb.Pact.Backend.Types (HeaderOracle) import Chainweb.Pact.Utils (aeson) +import Chainweb.Pact4.Types(internalError) import Chainweb.Payload import Chainweb.Payload.PayloadStore import Chainweb.SPV @@ -84,37 +85,43 @@ import qualified Pact.Types.Info as Pact4 import qualified Pact.Types.PactValue as Pact4 import qualified Pact.Types.Runtime as Pact4 import qualified Pact.Types.SPV as Pact4 +import Chainweb.MerkleUniverse (ChainwebMerkleHashAlgorithm) -catchAndDisplaySPVError :: BlockHeader -> ExceptT Text IO a -> ExceptT Text IO a +forkedThrower :: CW.HasVersion => BlockHeader -> Text -> ExceptT Text IO a +forkedThrower bh = + if CW.chainweb219Pact (CW._chainId bh) (view blockHeight bh) + then throwError + else internalError + +catchAndDisplaySPVError :: CW.HasVersion => BlockHeader -> ExceptT Text IO a -> ExceptT Text IO a catchAndDisplaySPVError bh = - if CW.chainweb219Pact (CW._chainwebVersion bh) (CW._chainId bh) (view blockHeight bh) + if CW.chainweb219Pact (CW._chainId bh) (view blockHeight bh) then flip catch $ \case - SpvExceptionVerificationFailed m -> throwError ("spv verification failed: " <> m) + -- only error we expect + SpvExceptionVerificationFailed _ -> throwError ("spv verification failed: target header is not in the chain") spvErr -> throwM spvErr else id -forkedThrower :: BlockHeader -> Text -> ExceptT Text IO a -forkedThrower bh = - if CW.chainweb219Pact (CW._chainwebVersion bh) (CW._chainId bh) (view blockHeight bh) - then throwError - else internalError - -- | Spv support for pact -- pactSPV - :: BlockHeaderDb + :: CW.HasVersion + => HeaderOracle -- ^ handle into the cutdb -> BlockHeader -- ^ the context for verifying the proof -> Pact4.SPVSupport -pactSPV bdb bh = Pact4.SPVSupport (verifySPV bdb bh) (verifyCont bdb bh) +pactSPV headerOracle bh = Pact4.SPVSupport + (verifySPV headerOracle bh) + (verifyCont headerOracle bh) -- | SPV transaction verification support. Calls to 'verify-spv' in Pact -- will thread through this function and verify an SPV receipt, making the -- requisite calls to the SPV api and verifying the output proof. -- verifySPV - :: BlockHeaderDb + :: CW.HasVersion + => HeaderOracle -- ^ handle into the cut db -> BlockHeader -- ^ the context for verifying the proof @@ -124,10 +131,10 @@ verifySPV -> Pact4.Object Pact4.Name -- ^ the proof object to validate -> IO (Either Text (Pact4.Object Pact4.Name)) -verifySPV bdb bh typ proof = runExceptT $ go typ proof +verifySPV headerOracle bh typ proof = runExceptT $ go typ proof where - cid = CW._chainId bdb - enableBridge = CW.enableSPVBridge (CW._chainwebVersion bh) cid (view blockHeight bh) + cid = CW._chainId headerOracle + enableBridge = CW.enableSPVBridge cid (view blockHeight bh) mkSPVResult' cr j | enableBridge = @@ -159,7 +166,8 @@ verifySPV bdb bh typ proof = runExceptT $ go typ proof -- 3. Extract tx outputs as a pact object and return the -- object. - TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) + TransactionOutput p <- catchAndDisplaySPVError bh $ + checkProofAndExtractOutput headerOracle u q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of Nothing -> forkedThrower bh "unable to decode spv transaction output" @@ -250,19 +258,17 @@ base64DowngradeErrorMessage msg = case msg of -- in Pact, providing a validation that the yield data of a cross-chain pact is valid. -- verifyCont - :: BlockHeaderDb + :: CW.HasVersion + => HeaderOracle -- ^ handle into the cut db -> BlockHeader -- ^ the context for verifying the proof -> Pact4.ContProof -- ^ bytestring of 'TransactionOutputP roof' object to validate -> IO (Either Text Pact4.PactExec) -verifyCont bdb bh (Pact4.ContProof cp) = runExceptT $ do +verifyCont headerOracle bh (Pact4.ContProof cp) = runExceptT $ do let errorMessageType = - if CW.chainweb221Pact - (CW._chainwebVersion bh) - (CW._chainId bh) - (view blockHeight bh) + if CW.chainweb221Pact (CW._chainId bh) (view blockHeight bh) then Simplified else Legacy t <- decodeB64UrlNoPaddingTextWithFixedErrorMessage errorMessageType $ Text.decodeUtf8 cp @@ -282,7 +288,8 @@ verifyCont bdb bh (Pact4.ContProof cp) = runExceptT $ do -- 3. Extract continuation 'PactExec' from decoded result -- and return the cont exec object - TransactionOutput p <- catchAndDisplaySPVError bh $ Pact4.liftIO $ verifyTransactionOutputProofAt_ bdb u (view blockHash bh) + TransactionOutput p <- catchAndDisplaySPVError bh $ + checkProofAndExtractOutput headerOracle u q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of Nothing -> forkedThrower bh "unable to decode spv transaction output" @@ -292,11 +299,11 @@ verifyCont bdb bh (Pact4.ContProof cp) = runExceptT $ do Nothing -> throwError "no pact exec found in command result" Just pe -> return pe where - cid = CW._chainId bdb + cid = CW._chainId headerOracle -- | Extract a 'TransactionOutputProof' from a generic pact object -- -extractProof :: Bool -> Pact4.Object Pact4.Name -> Either Text (TransactionOutputProof SHA512t_256) +extractProof :: Bool -> Pact4.Object Pact4.Name -> Either Text (TransactionOutputProof ChainwebMerkleHashAlgorithm) extractProof False o = Pact4.toPactValue (Pact4.TObject o Pact4.noInfo) >>= k where k = aeson (Left . pack) Right @@ -381,16 +388,17 @@ ethResultToPactValue ReceiptProofValidation{..} = mkObject -- getTxIdx :: HasCallStack + => CW.HasVersion => CanReadablePayloadCas tbl => BlockHeaderDb -> PayloadDb tbl -> BlockHeight -> Pact4.PactHash -> IO (Either Text Int) -getTxIdx bdb pdb bh th = do +getTxIdx headerOracle pdb bh th = do -- get BlockPayloadHash - m <- maxEntry bdb - ph <- seekAncestor bdb m (int bh) >>= \case + m <- maxEntry headerOracle + ph <- seekAncestor headerOracle m (int bh) >>= \case Just x -> return $ Right $! view blockPayloadHash x Nothing -> return $ Left "unable to find payload associated with transaction hash" diff --git a/src/Chainweb/Pact4/Templates.hs b/src/Chainweb/Pact4/Templates.hs index a8f62002bb..4859f2867c 100644 --- a/src/Chainweb/Pact4/Templates.hs +++ b/src/Chainweb/Pact4/Templates.hs @@ -4,6 +4,8 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE ViewPatterns #-} -- | -- Module : Chainweb.Pact.Templates @@ -19,8 +21,8 @@ module Chainweb.Pact4.Templates , mkBuyGasTerm , mkRedeemGasTerm , mkCoinbaseTerm - , mkCoinbaseCmd +, pattern MinerKeys ) where @@ -39,8 +41,11 @@ import Pact.Types.RPC import Pact.Types.Runtime import Chainweb.Miner.Pact -import Chainweb.Pact.Types -import Chainweb.Pact4.Types (GasSupply) +import Chainweb.Pact4.Types +import qualified Pact.Core.Guards as Pact5 +import qualified Pact.Types.Term as Pact +import Data.Aeson (decodeStrict) +import qualified Pact.Core.StableEncoding as Pact5 inf :: Info inf = Info $ Just (Code "",Parsed (Columns 0 0) 0) @@ -106,10 +111,10 @@ dummyParsedCode = ParsedCode "1" [ELiteral $ LiteralExp (LInteger 1) (Parsed mem mkFundTxTerm - :: MinerId -- ^ Id of the miner to fund - -> MinerKeys -- ^ Miner keyset - -> Text -- ^ Address of the sender from the command - -> GasSupply -- ^ The gas limit total * price + :: MinerId -- ^ Id of the miner to fund + -> MinerGuard -- ^ Miner keyset + -> Text -- ^ Address of the sender from the command + -> GasSupply -- ^ The gas limit total * price -> (Term Name,ExecMsg ParsedCode) mkFundTxTerm (MinerId mid) (MinerKeys ks) sender total = (populatedTerm, execMsg) where (term, senderS, minerS) = fundTxTemplate @@ -119,6 +124,7 @@ mkFundTxTerm (MinerId mid) (MinerKeys ks) sender total = (populatedTerm, execMsg [ "miner-keyset" J..= ks , "total" J..= total ] +mkFundTxTerm _ _ _ _ = error "invalid miner guard" {-# INLINABLE mkFundTxTerm #-} mkBuyGasTerm @@ -134,11 +140,11 @@ mkBuyGasTerm sender total = (populatedTerm, execMsg) {-# INLINABLE mkBuyGasTerm #-} mkRedeemGasTerm - :: MinerId -- ^ Id of the miner to fund - -> MinerKeys -- ^ Miner keyset - -> Text -- ^ Address of the sender from the command - -> GasSupply -- ^ The gas limit total * price - -> GasSupply -- ^ The gas used * price + :: MinerId -- ^ Id of the miner to fund + -> MinerGuard -- ^ Miner keyset + -> Text -- ^ Address of the sender from the command + -> GasSupply -- ^ The gas limit total * price + -> GasSupply -- ^ The gas used * price -> (Term Name,ExecMsg ParsedCode) mkRedeemGasTerm (MinerId mid) (MinerKeys ks) sender total fee = (populatedTerm, execMsg) where (term, senderS, minerS) = redeemGasTemplate @@ -149,6 +155,7 @@ mkRedeemGasTerm (MinerId mid) (MinerKeys ks) sender total fee = (populatedTerm, , "fee" J..= J.toJsonViaEncode fee , "miner-keyset" J..= ks ] +mkRedeemGasTerm _ _ _ _ _ = error "invalid miner guard" {-# INLINABLE mkRedeemGasTerm #-} coinbaseTemplate :: (Term Name,ASetter' (Term Name) Text) @@ -162,7 +169,7 @@ coinbaseTemplate = ) {-# NOINLINE coinbaseTemplate #-} -mkCoinbaseTerm :: MinerId -> MinerKeys -> ParsedDecimal -> (Term Name,ExecMsg ParsedCode) +mkCoinbaseTerm :: MinerId -> MinerGuard -> ParsedDecimal -> (Term Name,ExecMsg ParsedCode) mkCoinbaseTerm (MinerId mid) (MinerKeys ks) reward = (populatedTerm, execMsg) where (term, minerS) = coinbaseTemplate @@ -172,11 +179,12 @@ mkCoinbaseTerm (MinerId mid) (MinerKeys ks) reward = (populatedTerm, execMsg) [ "miner-keyset" J..= ks , "reward" J..= reward ] +mkCoinbaseTerm _ _ _ = error "invalid miner guard" {-# INLINABLE mkCoinbaseTerm #-} -- | "Old method" to build a coinbase 'ExecMsg' for back-compat. -- -mkCoinbaseCmd :: MinerId -> MinerKeys -> ParsedDecimal -> IO (ExecMsg ParsedCode) +mkCoinbaseCmd :: MinerId -> MinerGuard -> ParsedDecimal -> IO (ExecMsg ParsedCode) mkCoinbaseCmd (MinerId mid) (MinerKeys ks) reward = buildExecParsedCode $ mconcat [ "(coin.coinbase" @@ -199,5 +207,10 @@ mkCoinbaseCmd (MinerId mid) (MinerKeys ks) reward = -- if we can't construct coin contract calls, this should -- fail fast Left err -> internalError $ "buildExecParsedCode: parse failed: " <> pack err +mkCoinbaseCmd _ _ _ = error "invalid miner guard" {-# INLINABLE mkCoinbaseCmd #-} + +pattern MinerKeys :: Pact.KeySet -> MinerGuard +pattern MinerKeys ks <- + MinerGuard (Pact5.GKeyset (decodeStrict . J.encodeStrict . Pact5.StableEncoding -> Just ks)) diff --git a/src/Chainweb/Pact4/Transaction.hs b/src/Chainweb/Pact4/Transaction.hs index eb74a6f31b..88c31b5ed3 100644 --- a/src/Chainweb/Pact4/Transaction.hs +++ b/src/Chainweb/Pact4/Transaction.hs @@ -10,28 +10,28 @@ {-# LANGUAGE DeriveTraversable #-} module Chainweb.Pact4.Transaction - ( Transaction - , UnparsedTransaction - , unparseTransaction - , HashableTrans(..) - , PayloadWithText - , PactParserVersion(..) - , IsWebAuthnPrefixLegal(..) - , payloadCodec - , rawCommandCodec - , encodePayload - , decodePayload - , cmdGasLimit - , cmdGasPrice - , cmdTimeToLive - , cmdCreationTime - , mkPayloadWithText - , mkPayloadWithTextOld - , mkPayloadWithTextOldUnparsed - , payloadBytes - , payloadObj - , parsePact - ) where + ( Transaction + , UnparsedTransaction + , unparseTransaction + , HashableTrans(..) + , PayloadWithText + , PactParserVersion(..) + , IsWebAuthnPrefixLegal(..) + , payloadCodec + , rawCommandCodec + , encodePayload + , decodePayload + , cmdGasLimit + , cmdGasPrice + , cmdTimeToLive + , cmdCreationTime + , mkPayloadWithText + , mkPayloadWithTextOld + , mkPayloadWithTextOldUnparsed + , payloadBytes + , payloadObj + , parsePact + ) where import Control.DeepSeq import Control.Lens diff --git a/src/Chainweb/Pact4/TransactionExec.hs b/src/Chainweb/Pact4/TransactionExec.hs index b877b1201b..77bb46b68e 100644 --- a/src/Chainweb/Pact4/TransactionExec.hs +++ b/src/Chainweb/Pact4/TransactionExec.hs @@ -12,6 +12,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MonoLocalBinds #-} +{-# LANGUAGE TypeApplications #-} -- | -- Module : Chainweb.Pact4.TransactionExec -- Copyright : Copyright © 2018 Kadena LLC. @@ -37,7 +38,6 @@ module Chainweb.Pact4.TransactionExec , txMode , txDbEnv , txLogger - , txGasLogger , txPublicData , txSpvSupport , txNetworkId @@ -45,7 +45,6 @@ module Chainweb.Pact4.TransactionExec , txRequestKey , txExecutionConfig , txQuirkGasFee - , txTxFailuresCounter -- * Transaction Execution Monad , TransactionM(..) @@ -56,7 +55,6 @@ module Chainweb.Pact4.TransactionExec , applyCmd , applyGenesisCmd - , applyLocal , applyExec , applyExec' , applyContinuation @@ -96,7 +94,7 @@ import Control.Monad.State.Strict import Control.Monad.Trans.Maybe import Control.Parallel.Strategies(using, rseq) -import Data.Aeson hiding ((.=)) +import Data.Aeson hiding ((.=), Error) import qualified Data.Aeson as A import Data.Bifunctor import qualified Data.ByteString as B @@ -110,7 +108,7 @@ import Data.Maybe import qualified Data.Set as S import Data.Text (Text) import qualified Data.Text as T -import qualified System.LogLevel as L +import System.LogLevel -- internal Pact modules @@ -132,7 +130,7 @@ import Pact.Types.KeySet import Pact.Types.PactValue import Pact.Types.Pretty import Pact.Types.RPC -import Pact.Types.Runtime hiding (catchesPactError) +import Pact.Types.Runtime hiding (Info, catchesPactError) import Pact.Types.Server import Pact.Types.SPV import Pact.Types.Verifier @@ -146,10 +144,9 @@ import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger import qualified Chainweb.ChainId as Chainweb -import Chainweb.Mempool.Mempool (pact4RequestKeyToTransactionHash) +import Chainweb.Mempool.Mempool (pactRequestKeyToTransactionHash) import Chainweb.Miner.Pact import Chainweb.Pact4.Templates -import Chainweb.Pact.Types import Chainweb.Pact4.Types import Chainweb.Pact4.Transaction import Chainweb.Utils @@ -163,6 +160,44 @@ import Chainweb.Pact4.ModuleCache import Chainweb.Pact4.Backend.ChainwebPactDb import Pact.Core.Errors (VerifierError(..)) +import Chainweb.Parent +import Chainweb.Pact.Types (ServiceEnv, BlockCtx (_bctxParentHeight), guardCtx, _bctxCurrentBlockHeight, bctxParentCreationTime, bctxParentHash, bctxParentHeight) +import System.LogLevel +import qualified Pact.Core.Gas as Pact5 +import qualified Pact.Types.Gas as Pact +import Data.Int (Int64) +import Chainweb.BlockCreationTime +import Chainweb.Time hiding (second) +import Chainweb.BlockHash +import Chainweb.Ranked +import Control.Concurrent.MVar +import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact5 + +-- | Convert context to datatype for Pact environment. +-- +-- Note that we use the grandparent block hash here. This was an accident which +-- we must preserve for backcompat. +-- +ctxToPublicData :: HasVersion => BlockEnv logger -> PublicMeta -> IO PublicData +ctxToPublicData blockenv pm = do + blockDbEnv <- readMVar $ blockenv ^. benvDbEnv . cpPactDbEnv . to pdPactDbVar + let sql = _blockHandlerDb $ _blockHandlerEnv blockDbEnv + let parentHeight = ctx ^. bctxParentHeight . _Parent + BlockHash bhsh <- + if parentHeight > succ (genesisHeight (view V.chainId ctx)) + then _ranked . fromJuste <$> Pact5.throwOnDbError (Pact5.lookupBlockWithHeight sql (pred parentHeight)) + else return $ view blockHash $ genesisBlockHeader (view V.chainId ctx) + return PublicData + { _pdPublicMeta = pm + , _pdBlockHeight = bh + , _pdBlockTime = bt + , _pdPrevBlockHash = toText bhsh + } + where + ctx = blockenv ^. benvBlockCtx + BlockHeight bh = _bctxCurrentBlockHeight ctx + BlockCreationTime (Time (TimeSpan (Micros !bt))) = ctx ^. bctxParentCreationTime . _Parent + -- Note [Throw out verifier proofs eagerly] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -194,7 +229,6 @@ data TransactionEnv logger db = TransactionEnv { _txMode :: !ExecutionMode , _txDbEnv :: !(PactDbEnv db) , _txLogger :: !logger - , _txGasLogger :: !(Maybe logger) , _txPublicData :: !PublicData , _txSpvSupport :: !SPVSupport , _txNetworkId :: !(Maybe NetworkId) @@ -203,7 +237,6 @@ data TransactionEnv logger db = TransactionEnv , _txGasLimit :: !Gas , _txExecutionConfig :: !ExecutionConfig , _txQuirkGasFee :: !(Maybe Gas) - , _txTxFailuresCounter :: !(Maybe (Counter "txFailures")) } makeLenses ''TransactionEnv @@ -292,12 +325,18 @@ magic_GENESIS = mkMagicCapSlot "GENESIS" debitCap :: Text -> SigCapability debitCap s = mkCoinCap "DEBIT" [PLiteral (LString s)] -onChainErrorPrintingFor :: TxContext -> UnexpectedErrorPrinting -onChainErrorPrintingFor txCtx = - if guardCtx chainweb219Pact txCtx +onChainErrorPrintingFor :: HasVersion => BlockCtx -> UnexpectedErrorPrinting +onChainErrorPrintingFor bctx = + if guardCtx chainweb219Pact bctx then CensorsUnexpectedError else PrintsUnexpectedError +convertGasFeeFromPact5 :: Pact5.Gas -> Gas +convertGasFeeFromPact5 (Pact5.Gas g) = Pact.Gas (int @Pact5.SatWord @Int64 g) + +convertGasFeeToPact5 :: Gas -> Pact5.Gas +convertGasFeeToPact5 (Pact.Gas g) = Pact5.Gas (int @Int64 @Pact5.SatWord g) + -- | The main entry point to executing transactions. From here, -- 'applyCmd' assembles the command environment for a command and -- orchestrates gas buys/redemption, and executing payloads. @@ -306,21 +345,15 @@ onChainErrorPrintingFor txCtx = -- codepath later. -- applyCmd - :: (Logger logger) - => ChainwebVersion - -> logger + :: (Logger logger1, Logger logger2) + => HasVersion + => logger1 -- ^ Pact logger - -> Maybe logger - -- ^ Pact gas logger - -> Maybe (Counter "txFailures") - -> PactDbEnv p - -- ^ Pact db environment + -> BlockEnv logger2 -> Miner -- ^ The miner chosen to mine the block -> GasModel -- ^ Gas model (pact Service config) - -> TxContext - -- ^ tx metadata and parent header -> TxIdxInBlock -> SPVSupport -- ^ SPV support (validates cont proofs) @@ -330,22 +363,23 @@ applyCmd -- ^ initial gas used -> ModuleCache -- ^ cached module state - -> ApplyCmdExecutionContext - -- ^ is this a local or send execution context? -> IO (T3 (CommandResult [TxLogJson]) ModuleCache (S.Set PactWarning)) -applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxInBlock spv cmd initialGas mcache0 callCtx = do - T2 cr st <- runTransactionM cenv txst applyBuyGas +applyCmd logger blockEnv miner gasModel txIdxInBlock spv cmd initialGas mcache0 = do + publicData <- ctxToPublicData blockEnv (publicMetaOf cmd) + T2 cr st <- runTransactionM (cenv publicData) txst applyBuyGas let cache = _txCache st warns = _txWarnings st pure $ T3 cr cache warns where + bctx = blockEnv ^. benvBlockCtx stGasModel | chainweb217Pact' = gasModel | otherwise = _geGasModel freeGasEnv txst = TransactionState mcache0 mempty 0 Nothing stGasModel mempty - quirkGasFee = v ^? versionQuirks . quirkGasFees . ixg cid . ix (ctxCurrentBlockHeight txCtx, txIdxInBlock) + quirkGasFee = fmap convertGasFeeFromPact5 $ + implicitVersion ^? versionQuirks . quirkGasFees . ixg cid . ix (_bctxCurrentBlockHeight bctx, txIdxInBlock) executionConfigNoHistory = ExecutionConfig $ S.singleton FlagDisableHistoryInTransactionalMode @@ -353,25 +387,25 @@ applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxI ([ FlagOldReadOnlyBehavior | isPactBackCompatV16 ] ++ [ FlagPreserveModuleNameBug | not isModuleNameFix ] ++ [ FlagPreserveNsModuleInstallBug | not isModuleNameFix2 ]) - <> flagsFor v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) + <> flagsFor (view V.chainId bctx) (_bctxCurrentBlockHeight bctx) - cenv = TransactionEnv Transactional pdbenv logger gasLogger (ctxToPublicData txCtx) spv nid gasPrice - requestKey (fromIntegral gasLimit) executionConfigNoHistory quirkGasFee txFailuresCounter + cenv publicData = + TransactionEnv Transactional (blockEnv ^. benvDbEnv . cpPactDbEnv) logger publicData spv nid gasPrice requestKey (fromIntegral gasLimit) executionConfigNoHistory quirkGasFee !requestKey = cmdToRequestKey cmd !gasPrice = view cmdGasPrice cmd !gasLimit = view cmdGasLimit cmd !nid = networkIdOf cmd - currHeight = ctxCurrentBlockHeight txCtx - cid = ctxChainId txCtx - isModuleNameFix = enableModuleNameFix v cid currHeight - isModuleNameFix2 = enableModuleNameFix2 v cid currHeight - isPactBackCompatV16 = pactBackCompat_v16 v cid currHeight - chainweb213Pact' = guardCtx chainweb213Pact txCtx - chainweb217Pact' = guardCtx chainweb217Pact txCtx - chainweb219Pact' = guardCtx chainweb219Pact txCtx - chainweb223Pact' = guardCtx chainweb223Pact txCtx - allVerifiers = verifiersAt v cid currHeight + currHeight = _bctxCurrentBlockHeight bctx + cid = view V.chainId bctx + isModuleNameFix = enableModuleNameFix cid currHeight + isModuleNameFix2 = enableModuleNameFix2 cid currHeight + isPactBackCompatV16 = pactBackCompat_v16 cid currHeight + chainweb213Pact' = guardCtx chainweb213Pact bctx + chainweb217Pact' = guardCtx chainweb217Pact bctx + chainweb219Pact' = guardCtx chainweb219Pact bctx + chainweb223Pact' = guardCtx chainweb223Pact bctx + allVerifiers = verifiersAt cid currHeight toEmptyPactError (PactError errty _ _ _) = PactError errty noInfo [] mempty toOldListErr pe = pe { peDoc = listErrMsg } @@ -384,20 +418,18 @@ applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxI applyRedeem r applyBuyGas = - catchesPactError logger (onChainErrorPrintingFor txCtx) (buyGas txCtx cmd miner) >>= \case + catchesPactError logger (onChainErrorPrintingFor bctx) (buyGas bctx cmd miner) >>= \case Left e -> view txRequestKey >>= \rk -> - throwM $ Pact4BuyGasFailure $ Pact4GasPurchaseFailure (pact4RequestKeyToTransactionHash rk) e + throwM $ PactBuyGasFailure $ sshow (rk, e) Right _ -> checkTooBigTx initialGas gasLimit applyVerifiers redeemAllGas displayPactError e = do - r <- failTxWith e "tx failure for request key when running cmd" + r <- failTxWith e redeemAllGas r stripPactError e = do - let e' = case callCtx of - ApplyLocal -> e - ApplySend -> toEmptyPactError e - r <- failTxWith e' "tx failure for request key when running cmd" + let e' = toEmptyPactError e + r <- failTxWith e' redeemAllGas r applyVerifiers = do @@ -407,18 +439,19 @@ applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxI let initGasRemaining = fromIntegral gasLimit - gasUsed verifierResult <- liftIO $ runVerifierPlugins - (ctxVersion txCtx, cid, currHeight) - logger allVerifiers initGasRemaining - (fromMaybe [] (cmd ^. cmdPayload . pVerifiers)) + (cid, currHeight) + logger allVerifiers (convertGasFeeToPact5 initGasRemaining) + -- FIXME + [] + -- (fromMaybe [] (cmd ^. cmdPayload . pVerifiers)) case verifierResult of Left err -> do let errMsg = "Tx verifier error: " <> _verifierError err cmdResult <- failTxWith (PactError TxFailure noInfo [] (pretty errMsg)) - errMsg redeemAllGas cmdResult Right verifierGasRemaining -> do - txGasUsed += initGasRemaining - verifierGasRemaining + txGasUsed += initGasRemaining - convertGasFeeFromPact5 verifierGasRemaining applyPayload else applyPayload @@ -427,7 +460,7 @@ applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxI if chainweb217Pact' then txGasUsed += initialGas else txGasUsed .= initialGas - cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! runPayload cmd managedNamespacePolicy + cr <- catchesPactError logger (onChainErrorPrintingFor bctx) $! runPayload cmd managedNamespacePolicy case cr of Left e -- 2.19 onwards errors return on chain @@ -436,14 +469,14 @@ applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxI | chainweb217Pact' -> stripPactError e | chainweb213Pact' || not (isOldListErr e) -> displayPactError e | otherwise -> do - r <- failTxWith (toOldListErr e) "tx failure for request key when running cmd" + r <- failTxWith (toOldListErr e) redeemAllGas r Right r -> applyRedeem r applyRedeem cr = do txGasModel .= _geGasModel freeGasEnv - r <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! redeemGas txCtx cmd miner + r <- catchesPactError logger (onChainErrorPrintingFor bctx) $! redeemGas bctx cmd miner case r of Left e -> -- redeem gas failure is fatal (block-failing) so miner doesn't lose coins @@ -454,11 +487,7 @@ applyCmd v logger gasLogger txFailuresCounter pdbenv miner gasModel txCtx txIdxI -- /local requires enriched results with metadata, while /send strips them. -- when ?preflight=true is set, make sure that metadata occurs in result. - let !cr' = case callCtx of - ApplySend -> set crLogs (Just logs) $ over crEvents (es ++) cr - ApplyLocal -> set crMetaData (Just $ J.toJsonViaEncode $ ctxToPublicData' txCtx) - $ set crLogs (Just logs) - $ over crEvents (es ++) cr + let !cr' = set crLogs (Just logs) $ over crEvents (es ++) cr return cr' @@ -468,18 +497,19 @@ listErrMsg = applyGenesisCmd :: (Logger logger) + => HasVersion => logger -- ^ Pact logger -> PactDbEnv p -- ^ Pact db environment -> SPVSupport -- ^ SPV support (validates cont proofs) - -> TxContext + -> BlockCtx -- ^ tx metadata -> Command (Payload PublicMeta ParsedCode) -- ^ command with payload to execute -> IO (T2 (CommandResult [TxLogJson]) ModuleCache) -applyGenesisCmd logger dbEnv spv txCtx cmd = +applyGenesisCmd logger dbEnv spv bctx cmd = second _txCache <$!> runTransactionM tenv txst go where nid = networkIdOf cmd @@ -488,7 +518,6 @@ applyGenesisCmd logger dbEnv spv txCtx cmd = { _txMode = Transactional , _txDbEnv = dbEnv , _txLogger = logger - , _txGasLogger = Nothing , _txPublicData = noPublicData , _txSpvSupport = spv , _txNetworkId = nid @@ -496,7 +525,7 @@ applyGenesisCmd logger dbEnv spv txCtx cmd = , _txRequestKey = rk , _txGasLimit = 0 , _txExecutionConfig = ExecutionConfig - $ flagsFor (ctxVersion txCtx) (ctxChainId txCtx) (view blockHeight $ ctxBlockHeader txCtx) + $ flagsFor (view V.chainId bctx) (_bctxCurrentBlockHeight bctx) -- TODO this is very ugly. Genesis blocks need to install keysets -- outside of namespaces so we need to disable Pact 4.4. It would be -- preferable to have a flag specifically for the namespaced keyset @@ -504,7 +533,6 @@ applyGenesisCmd logger dbEnv spv txCtx cmd = -- after the block height where pact4.4 is on. <> S.fromList [ FlagDisableInlineMemCheck, FlagDisablePact44 ] , _txQuirkGasFee = Nothing - , _txTxFailuresCounter = Nothing } txst = TransactionState { _txCache = mempty @@ -520,56 +548,55 @@ applyGenesisCmd logger dbEnv spv txCtx cmd = go = do -- TODO: fix with version recordification so that this matches the flags at genesis heights. - cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! runGenesis cmd permissiveNamespacePolicy interp + cr <- catchesPactError logger (onChainErrorPrintingFor bctx) $! runGenesis cmd permissiveNamespacePolicy interp case cr of Left e -> fatal $ "Genesis command failed: " <> sshow e Right r -> r <$ debug "successful genesis tx for request key" -flagsFor :: ChainwebVersion -> V.ChainId -> BlockHeight -> S.Set ExecutionFlag -flagsFor v cid bh = S.fromList $ concat - [ enablePactEvents' v cid bh - , enablePact40 v cid bh - , enablePact42 v cid bh - , enforceKeysetFormats' v cid bh - , enablePactModuleMemcheck v cid bh - , enablePact43 v cid bh - , enablePact431 v cid bh - , enablePact44 v cid bh - , enablePact45 v cid bh - , enableNewTrans v cid bh - , enablePact46 v cid bh - , enablePact47 v cid bh - , enablePact48 v cid bh - , disableReturnRTC v cid bh - , enablePact49 v cid bh - , enablePact410 v cid bh - , enablePact411 v cid bh - , enablePact412 v cid bh +flagsFor :: HasVersion => V.ChainId -> BlockHeight -> S.Set ExecutionFlag +flagsFor cid bh = S.fromList $ concat + [ enablePactEvents' cid bh + , enablePact40 cid bh + , enablePact42 cid bh + , enforceKeysetFormats' cid bh + , enablePactModuleMemcheck cid bh + , enablePact43 cid bh + , enablePact431 cid bh + , enablePact44 cid bh + , enablePact45 cid bh + , enableNewTrans cid bh + , enablePact46 cid bh + , enablePact47 cid bh + , enablePact48 cid bh + , disableReturnRTC cid bh + , enablePact49 cid bh + , enablePact410 cid bh + , enablePact411 cid bh + , enablePact412 cid bh ] applyCoinbase - :: (Logger logger) - => ChainwebVersion - -> logger + :: (Logger logger1, Logger logger2) + => HasVersion + => logger1 -- ^ Pact logger - -> PactDbEnv p + -> BlockEnv logger2 -- ^ Pact db environment + -> Miner -> ParsedDecimal -- ^ Miner reward - -> TxContext - -- ^ tx metadata and parent header -> EnforceCoinbaseFailure -- ^ enforce coinbase failure or not -> CoinbaseUsePrecompiled -- ^ always enable precompilation -> ModuleCache -> IO (T2 (CommandResult [TxLogJson]) (Maybe ModuleCache)) -applyCoinbase v logger dbEnv reward@(ParsedDecimal d) txCtx +applyCoinbase logger blockEnv miner reward@(ParsedDecimal d) (EnforceCoinbaseFailure enfCBFailure) (CoinbaseUsePrecompiled enablePC) mc | fork1_3InEffect || enablePC = do when chainweb213Pact' $ enforceKeyFormats - (\k -> throwM $ CoinbaseFailure $ Pact4CoinbaseFailure $ "Invalid miner key: " <> sshow k) - (validKeyFormats v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx)) + (\k -> throwM $ CoinbaseFailure $ "Invalid miner key: " <> sshow k) + (validKeyFormats (view V.chainId bctx) (_bctxCurrentBlockHeight bctx)) mk let (cterm, cexec) = mkCoinbaseTerm mid mks reward interp = Interpreter $ \_ -> do put initState; fmap pure (eval cterm) @@ -580,164 +607,92 @@ applyCoinbase v logger dbEnv reward@(ParsedDecimal d) txCtx let interp = initStateInterpreter initState go interp cexec where - (Miner mid mks@(MinerKeys mk)) = _tcMiner txCtx - chainweb213Pact' = chainweb213Pact v cid bh - fork1_3InEffect = vuln797Fix v cid bh + bctx = blockEnv ^. benvBlockCtx + (Miner mid mks@(MinerKeys mk)) = miner + chainweb213Pact' = chainweb213Pact cid bh + fork1_3InEffect = vuln797Fix cid bh throwCritical = fork1_3InEffect || enfCBFailure ec = ExecutionConfig $ S.delete FlagEnforceKeyFormats $ fold [ S.singleton FlagDisableModuleInstall , S.singleton FlagDisableHistoryInTransactionalMode - , flagsFor v (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) + , flagsFor (view V.chainId bctx) (_bctxCurrentBlockHeight bctx) ] - tenv = TransactionEnv Transactional dbEnv logger Nothing (ctxToPublicData txCtx) noSPVSupport - Nothing 0.0 rk 0 ec Nothing Nothing + tenv publicData = TransactionEnv Transactional (blockEnv ^. benvDbEnv . cpPactDbEnv) logger publicData noSPVSupport Nothing 0.0 rk 0 ec Nothing txst = TransactionState mc mempty 0 Nothing (_geGasModel freeGasEnv) mempty initState = setModuleCache mc $ initCapabilities [magic_COINBASE] rk = RequestKey chash - parent = _tcParentHeader txCtx - bh = ctxCurrentBlockHeight txCtx - cid = Chainweb._chainId parent - chash = Pact.Hash $ SB.toShort $ encodeToByteString $ view blockHash $ _parentHeader parent + bh = _bctxCurrentBlockHeight bctx + cid = Chainweb._chainId bctx + chash = Pact.Hash $ SB.toShort $ encodeToByteString $ bctx ^. (bctxParentHash . _Parent) -- NOTE: it holds that @ _pdPrevBlockHash pd == encode _blockHash@ -- NOTE: chash includes the /quoted/ text of the parent header. - go interp cexec = evalTransactionM tenv txst $! do - cr <- catchesPactError logger (onChainErrorPrintingFor txCtx) $ - applyExec' 0 interp cexec [] [] chash managedNamespacePolicy - - case cr of - Left e - | throwCritical -> throwM $ CoinbaseFailure $ Pact4CoinbaseFailure $ sshow e - | otherwise -> (`T2` Nothing) <$> failTxWith e "coinbase tx failure" - Right er -> do - debug - $! "successful coinbase of " - <> T.take 18 (sshow d) - <> " to " - <> sshow mid - - upgradedModuleCache <- applyUpgrades v cid bh - - logs <- use txLogs - - return $! T2 - CommandResult - { _crReqKey = rk - , _crTxId = _erTxId er - , _crResult = PactResult (Right (last (_erOutput er))) - , _crGas = _erGas er - , _crLogs = Just logs - , _crContinuation = _erExec er - , _crMetaData = Nothing - , _crEvents = _erEvents er - } - upgradedModuleCache - -applyLocal - :: (Logger logger) - => logger - -- ^ Pact logger - -> Maybe logger - -- ^ Pact gas logger - -> PactDbEnv p - -- ^ Pact db environment - -> GasModel - -- ^ Gas model (pact Service config) - -> TxContext - -- ^ tx metadata and parent header - -> SPVSupport - -- ^ SPV support (validates cont proofs) - -> Transaction - -- ^ command with payload to execute - -> ModuleCache - -> ExecutionConfig - -> IO (CommandResult [TxLogJson]) -applyLocal logger gasLogger dbEnv gasModel txCtx spv cmdIn mc execConfig = - evalTransactionM tenv txst go - where - !cmd = payloadObj <$> cmdIn `using` traverse rseq - !rk = cmdToRequestKey cmd - !nid = networkIdOf cmd - !chash = toUntypedHash $ _cmdHash cmd - !signers = _pSigners $ _cmdPayload cmd - !verifiers = fromMaybe [] $ _pVerifiers $ _cmdPayload cmd - !gasPrice = view cmdGasPrice cmd - !gasLimit = view cmdGasLimit cmd - tenv = TransactionEnv Local dbEnv logger gasLogger (ctxToPublicData txCtx) spv nid gasPrice - rk (fromIntegral gasLimit) execConfig Nothing Nothing - txst = TransactionState mc mempty 0 Nothing gasModel mempty - gas0 = initialGasOf (_cmdPayload cmdIn) - currHeight = ctxCurrentBlockHeight txCtx - cid = V._chainId txCtx - v = _chainwebVersion txCtx - allVerifiers = verifiersAt v cid currHeight - -- Note [Throw out verifier proofs eagerly] - !verifiersWithNoProof = - (fmap . fmap) (\_ -> ()) verifiers - `using` (traverse . traverse) rseq - - applyVerifiers m = do - let initGasRemaining = fromIntegral gasLimit - gas0 - verifierResult <- - liftIO $ runVerifierPlugins - (v, cid, currHeight) logger allVerifiers initGasRemaining - (fromMaybe [] $ cmd ^. cmdPayload . pVerifiers) - case verifierResult of - Left err -> do - let errMsg = "Tx verifier error: " <> _verifierError err - failTxWith - (PactError TxFailure noInfo [] (pretty errMsg)) - errMsg - Right verifierGasRemaining -> do - let gas1 = (initGasRemaining - verifierGasRemaining) + gas0 - applyPayload gas1 m - - applyPayload gas1 m = do - interp <- gasInterpreter gas1 - cr <- catchesPactError logger PrintsUnexpectedError $! case m of - Exec em -> - applyExec gas1 interp em signers verifiersWithNoProof chash managedNamespacePolicy - Continuation cm -> - applyContinuation gas1 interp cm signers chash managedNamespacePolicy - - case cr of - Left e -> failTxWith e "applyLocal" - Right r -> return $! r { _crMetaData = Just (J.toJsonViaEncode $ ctxToPublicData' txCtx) } - - go = checkTooBigTx gas0 gasLimit (applyVerifiers $ _pPayload $ _cmdPayload cmd) return + go interp cexec = do + publicData <- ctxToPublicData blockEnv noPublicMeta + evalTransactionM (tenv publicData) txst $! do + cr <- catchesPactError logger (onChainErrorPrintingFor bctx) $ + applyExec' 0 interp cexec [] [] chash managedNamespacePolicy + + case cr of + Left e + | throwCritical -> throwM $ CoinbaseFailure $ sshow e + | otherwise -> (`T2` Nothing) <$> failTxWith e + Right er -> do + debug + $! "successful coinbase of " + <> T.take 18 (sshow d) + <> " to " + <> sshow mid + + upgradedModuleCache <- applyUpgrades cid bh + + logs <- use txLogs + + return $! T2 + CommandResult + { _crReqKey = rk + , _crTxId = _erTxId er + , _crResult = PactResult (Right (last (_erOutput er))) + , _crGas = _erGas er + , _crLogs = Just logs + , _crContinuation = _erExec er + , _crMetaData = Nothing + , _crEvents = _erEvents er + } + upgradedModuleCache readInitModules - :: forall logger tbl. (Logger logger) - => PactBlockM logger tbl ModuleCache -readInitModules = do - logger <- view (psServiceEnv . psLogger) - dbEnv <- view (psBlockDbEnv . to _cpPactDbEnv) - txCtx <- getTxContext noMiner noPublicMeta + :: forall logger1 logger2 + . (Logger logger1, Logger logger2) + => HasVersion + => logger1 + -> BlockEnv logger2 + -> IO ModuleCache +readInitModules logger blockEnv = do + let bctx = view benvBlockCtx blockEnv -- guarding chainweb 2.17 here to allow for -- cache purging everything but coin and its -- dependencies. let - chainweb217Pact' = guardCtx chainweb217Pact txCtx - chainweb224Pact' = guardCtx chainweb224Pact txCtx + chainweb217Pact' = guardCtx chainweb217Pact bctx + chainweb224Pact' = guardCtx chainweb224Pact bctx - parent = _tcParentHeader txCtx - v = ctxVersion txCtx - cid = ctxChainId txCtx - h = view blockHeight (_parentHeader parent) + 1 + cid = view V.chainId bctx + h = _bctxCurrentBlockHeight bctx rk = RequestKey chash nid = Nothing chash = pactInitialHash - tenv = TransactionEnv Local dbEnv logger Nothing (ctxToPublicData txCtx) noSPVSupport nid 0.0 - rk 0 emptyExecutionConfig Nothing Nothing + tenv publicData = TransactionEnv Local (blockEnv ^. benvDbEnv . cpPactDbEnv) logger publicData noSPVSupport nid 0.0 + rk 0 emptyExecutionConfig Nothing txst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty interp = defaultInterpreter die msg = internalError $ "readInitModules: " <> msg - mkCmd = buildExecParsedCode (pact4ParserVersion v cid h) Nothing - run :: Text -> ExecMsg ParsedCode -> TransactionM logger p PactValue + mkCmd = buildExecParsedCode (pact4ParserVersion cid h) Nothing + run :: Text -> ExecMsg ParsedCode -> TransactionM logger1 p PactValue run msg cmd = do - er <- catchesPactError logger (onChainErrorPrintingFor txCtx) $! + er <- catchesPactError logger (onChainErrorPrintingFor bctx) $! applyExec' 0 interp cmd [] [] chash permissiveNamespacePolicy case er of Left e -> die $ msg <> ": failed: " <> sshow e @@ -746,7 +701,7 @@ readInitModules = do (o:_) -> return o - go :: TransactionM logger p ModuleCache + go :: TransactionM logger1 p ModuleCache go = do -- see if fungible-v2 is there @@ -781,16 +736,16 @@ readInitModules = do -- requires a block height that witnesses the invariant. -- -- if this changes, we must change the filter in 'updateInitCache' - goCw217 :: TransactionM logger p ModuleCache + goCw217 :: TransactionM logger1 p ModuleCache goCw217 = do coinDepCmd <- liftIO $ mkCmd "coin.MINIMUM_PRECISION" void $ run "load modules" coinDepCmd use txCache - + publicData <- ctxToPublicData blockEnv noPublicMeta if | chainweb224Pact' -> pure mempty - | chainweb217Pact' -> liftIO $ evalTransactionM tenv txst goCw217 - | otherwise -> liftIO $ evalTransactionM tenv txst go + | chainweb217Pact' -> liftIO $ evalTransactionM (tenv publicData) txst goCw217 + | otherwise -> liftIO $ evalTransactionM (tenv publicData) txst go -- | Apply (forking) upgrade transactions and module cache updates -- at a particular blockheight. @@ -804,16 +759,15 @@ readInitModules = do applyUpgrades :: forall logger p . (Logger logger) - => ChainwebVersion - -> Chainweb.ChainId + => HasVersion => Chainweb.ChainId -> BlockHeight -> TransactionM logger p (Maybe ModuleCache) -applyUpgrades v cid height +applyUpgrades cid height | Just Pact4Upgrade{_pact4UpgradeTransactions = txs, _legacyUpgradeIsPrecocious = isPrecocious} <- - v ^? versionUpgrades . atChain cid . ix height = applyUpgrade txs isPrecocious + implicitVersion ^? versionUpgrades . atChain cid . ix height = applyUpgrade txs isPrecocious | Just Pact5Upgrade{} <- - v ^? versionUpgrades . atChain cid . ix height = error "Expected Pact 4 upgrade, got Pact 5" - | cleanModuleCache v cid height = filterModuleCache + implicitVersion ^? versionUpgrades . atChain cid . ix height = error "Expected Pact 4 upgrade, got Pact 5" + | cleanModuleCache cid height = filterModuleCache | otherwise = return Nothing where installCoinModuleAdmin = set (evalCapabilities . capModuleAdmin) $ S.singleton (ModuleName "coin" Nothing) @@ -834,7 +788,7 @@ applyUpgrades v cid height -- init cache in the pact service state (_psInitCache). -- - let flags = flagsFor v cid (if isPrecocious then height + 1 else height) + let flags = flagsFor cid (if isPrecocious then height + 1 else height) caches <- local (txExecutionConfig .~ ExecutionConfig flags) (mapM applyTx payloads) @@ -856,18 +810,11 @@ applyUpgrades v cid height failTxWith :: (Logger logger) => PactError - -> Text -> TransactionM logger p (CommandResult [TxLogJson]) -failTxWith err msg = do +failTxWith err = do logs <- use txLogs gas <- view txGasLimit -- error means all gas was charged rk <- view txRequestKey - l <- view txLogger - - liftIO $ logFunction l L.Debug - (Pact4TxFailureLog rk err msg) - liftIO . traverse_ inc - =<< view txTxFailuresCounter return $! CommandResult rk Nothing (PactResult (Left err)) gas (Just logs) Nothing Nothing [] @@ -879,7 +826,7 @@ runPayload -> TransactionM logger p (CommandResult [TxLogJson]) runPayload cmd nsp = do g0 <- use txGasUsed - interp <- gasInterpreter g0 + interp <- gasInterpreter -- Note [Throw out verifier proofs eagerly] let !verifiersWithNoProof = @@ -936,7 +883,6 @@ applyExec -> TransactionM logger p (CommandResult [TxLogJson]) applyExec initialGas interp em senderSigs verifiers hsh nsp = do EvalResult{..} <- applyExec' initialGas interp em senderSigs verifiers hsh nsp - for_ _erLogGas $ \gl -> gasLog $ "gas logs: " <> sshow gl !logs <- use txLogs !rk <- view txRequestKey @@ -987,60 +933,60 @@ applyExec' initialGas interp (ExecMsg parsedCode execData) senderSigs verifiersW return quirkedEvalResult -enablePactEvents' :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePactEvents' v cid bh = [FlagDisablePactEvents | not (enablePactEvents v cid bh)] +enablePactEvents' :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePactEvents' cid bh = [FlagDisablePactEvents | not (enablePactEvents cid bh)] -enforceKeysetFormats' :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enforceKeysetFormats' v cid bh = [FlagEnforceKeyFormats | enforceKeysetFormats v cid bh] +enforceKeysetFormats' :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enforceKeysetFormats' cid bh = [FlagEnforceKeyFormats | enforceKeysetFormats cid bh] -enablePact40 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact40 v cid bh = [FlagDisablePact40 | not (pact4Coin3 v cid bh)] +enablePact40 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact40 cid bh = [FlagDisablePact40 | not (pact4Coin3 cid bh)] -enablePact42 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact42 v cid bh = [FlagDisablePact42 | not (pact42 v cid bh)] +enablePact42 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact42 cid bh = [FlagDisablePact42 | not (pact42 cid bh)] -enablePactModuleMemcheck :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePactModuleMemcheck v cid bh = [FlagDisableInlineMemCheck | not (chainweb213Pact v cid bh)] +enablePactModuleMemcheck :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePactModuleMemcheck cid bh = [FlagDisableInlineMemCheck | not (chainweb213Pact cid bh)] -enablePact43 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact43 v cid bh = [FlagDisablePact43 | not (chainweb214Pact v cid bh)] +enablePact43 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact43 cid bh = [FlagDisablePact43 | not (chainweb214Pact cid bh)] -enablePact431 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact431 v cid bh = [FlagDisablePact431 | not (chainweb215Pact v cid bh)] +enablePact431 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact431 cid bh = [FlagDisablePact431 | not (chainweb215Pact cid bh)] -enablePact44 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact44 v cid bh = [FlagDisablePact44 | not (chainweb216Pact v cid bh)] +enablePact44 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact44 cid bh = [FlagDisablePact44 | not (chainweb216Pact cid bh)] -enablePact45 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact45 v cid bh = [FlagDisablePact45 | not (chainweb217Pact v cid bh)] +enablePact45 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact45 cid bh = [FlagDisablePact45 | not (chainweb217Pact cid bh)] -enableNewTrans :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enableNewTrans v cid bh = [FlagDisableNewTrans | not (pact44NewTrans v cid bh)] +enableNewTrans :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enableNewTrans cid bh = [FlagDisableNewTrans | not (pact44NewTrans cid bh)] -enablePact46 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact46 v cid bh = [FlagDisablePact46 | not (chainweb218Pact v cid bh)] +enablePact46 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact46 cid bh = [FlagDisablePact46 | not (chainweb218Pact cid bh)] -enablePact47 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact47 v cid bh = [FlagDisablePact47 | not (chainweb219Pact v cid bh)] +enablePact47 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact47 cid bh = [FlagDisablePact47 | not (chainweb219Pact cid bh)] -enablePact48 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact48 v cid bh = [FlagDisablePact48 | not (chainweb220Pact v cid bh)] +enablePact48 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact48 cid bh = [FlagDisablePact48 | not (chainweb220Pact cid bh)] -enablePact49 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact49 v cid bh = [FlagDisablePact49 | not (chainweb221Pact v cid bh)] +enablePact49 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact49 cid bh = [FlagDisablePact49 | not (chainweb221Pact cid bh)] -enablePact410 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact410 v cid bh = [FlagDisablePact410 | not (chainweb222Pact v cid bh)] +enablePact410 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact410 cid bh = [FlagDisablePact410 | not (chainweb222Pact cid bh)] -enablePact411 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact411 v cid bh = [FlagDisablePact411 | not (chainweb223Pact v cid bh)] +enablePact411 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact411 cid bh = [FlagDisablePact411 | not (chainweb223Pact cid bh)] -enablePact412 :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -enablePact412 v cid bh = [FlagDisablePact412 | not (chainweb224Pact v cid bh)] +enablePact412 :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +enablePact412 cid bh = [FlagDisablePact412 | not (chainweb224Pact cid bh)] -- | Even though this is not forking, abstracting for future shutoffs -disableReturnRTC :: ChainwebVersion -> V.ChainId -> BlockHeight -> [ExecutionFlag] -disableReturnRTC _v _cid _bh = [FlagDisableRuntimeReturnTypeChecking] +disableReturnRTC :: HasVersion => V.ChainId -> BlockHeight -> [ExecutionFlag] +disableReturnRTC _cid _bh = [FlagDisableRuntimeReturnTypeChecking] -- | Execute a 'ContMsg' and return the command result and module cache -- @@ -1055,7 +1001,6 @@ applyContinuation -> TransactionM logger p (CommandResult [TxLogJson]) applyContinuation initialGas interp cm senderSigs hsh nsp = do EvalResult{..} <- applyContinuation' initialGas interp cm senderSigs hsh nsp - for_ _erLogGas $ \gl -> gasLog $ "gas logs: " <> sshow gl logs <- use txLogs rk <- view txRequestKey @@ -1106,17 +1051,20 @@ applyContinuation' initialGas interp cm@(ContMsg pid s rb d _) senderSigs hsh ns -- -- see: 'pact/coin-contract/coin.pact#fund-tx' -- -buyGas :: (Logger logger) => TxContext -> Command (Payload PublicMeta ParsedCode) -> Miner -> TransactionM logger p () -buyGas txCtx cmd (Miner mid mks) = go +buyGas + :: (Logger logger) + => HasVersion + => BlockCtx -> Command (Payload PublicMeta ParsedCode) -> Miner -> TransactionM logger p () +buyGas bctx cmd (Miner mid mks) = go where - isChainweb224Pact = guardCtx chainweb224Pact txCtx + isChainweb224Pact = guardCtx chainweb224Pact bctx sender = view (cmdPayload . pMeta . pmSender) cmd initState mc logGas = set evalLogGas (guard logGas >> Just [("GBuyGas",0)]) $ setModuleCache mc $ initCapabilities [magic_GAS] run input = do - (findPayer txCtx cmd) >>= \r -> case r of + (findPayer bctx cmd) >>= \r -> case r of Nothing -> input Just withPayerCap -> withPayerCap input @@ -1126,7 +1074,6 @@ buyGas txCtx cmd (Miner mid mks) = go go = do mcache <- use txCache supply <- gasSupplyOf <$> view txGasLimit <*> view txGasPrice - logGas <- isJust <$> view txGasLogger let (buyGasTerm, buyGasCmd) = -- post-chainweb 2.24, we call buy-gas directly rather than @@ -1138,7 +1085,7 @@ buyGas txCtx cmd (Miner mid mks) = go -- that ignores its argument and instead executes a term -- of our choice. we do the same to redeem gas. interp mc = Interpreter $ \_input -> - put (initState mc logGas) >> run (pure <$> eval buyGasTerm) + put (initState mc False) >> run (pure <$> eval buyGasTerm) let gasCapName = QualifiedName (ModuleName "coin" Nothing) "GAS" noInfo @@ -1172,10 +1119,11 @@ buyGas txCtx cmd (Miner mid mks) = go void $! txGasId .= (Just $! GasId (_pePactId pe)) findPayer - :: TxContext + :: HasVersion + => BlockCtx -> Command (Payload PublicMeta ParsedCode) -> Eval e (Maybe (Eval e [Term Name] -> Eval e [Term Name])) -findPayer txCtx cmd = runMaybeT $ do +findPayer bctx cmd = runMaybeT $ do (!m,!qn,!as) <- MaybeT findPayerCap pMod <- MaybeT $ lookupModule qn m capRef <- MaybeT $ return $ lookupIfaceModRef qn pMod @@ -1200,7 +1148,7 @@ findPayer txCtx cmd = runMaybeT $ do runCap i capRef as input = do let msgBody = enrichedMsgBody cmd - enrichMsgBody | guardCtx pactBackCompat_v16 txCtx = id + enrichMsgBody | guardCtx pactBackCompat_v16 bctx = id | otherwise = setEnvMsgBody (toLegacyJson msgBody) ar <- local enrichMsgBody $ do (cap, capDef, args) <- appToCap $ mkApp i capRef as @@ -1236,13 +1184,19 @@ enrichedMsgBody cmd = case (_pPayload $ _cmdPayload cmd) of -- -- see: 'pact/coin-contract/coin.pact#fund-tx' -- -redeemGas :: (Logger logger) => TxContext -> Command (Payload PublicMeta ParsedCode) -> Miner -> TransactionM logger p [PactEvent] -redeemGas txCtx cmd (Miner mid mks) = do +redeemGas + :: (Logger logger) + => HasVersion + => BlockCtx + -> Command (Payload PublicMeta ParsedCode) + -> Miner + -> TransactionM logger p [PactEvent] +redeemGas bctx cmd (Miner mid mks) = do mcache <- use txCache let sender = view (cmdPayload . pMeta . pmSender) cmd fee <- gasSupplyOf <$> use txGasUsed <*> view txGasPrice -- if we're past chainweb 2.24, we don't use defpacts for gas - if guardCtx chainweb224Pact txCtx + if guardCtx chainweb224Pact bctx then do total <- gasSupplyOf <$> view txGasLimit <*> view txGasPrice let (redeemGasTerm, redeemGasCmd) = @@ -1312,16 +1266,15 @@ checkTooBigTx initialGas gasLimit next onFail $ "Tx too big (" <> pretty initialGas <> "), limit " <> pretty gasLimit - r <- failTxWith pe "Tx too big" + r <- failTxWith pe onFail r | otherwise = next -gasInterpreter :: Gas -> TransactionM logger db (Interpreter p) -gasInterpreter g = do +gasInterpreter :: TransactionM logger db (Interpreter p) +gasInterpreter = do mc <- use txCache - logGas <- isJust <$> view txGasLogger return $ initStateInterpreter - $ set evalLogGas (guard logGas >> Just [("GTxSize",g)]) -- enables gas logging + $ set evalLogGas Nothing -- enables gas logging $ setModuleCache mc emptyEvalState @@ -1518,20 +1471,13 @@ toCoinUnit :: Decimal -> Decimal toCoinUnit = roundTo 12 {-# INLINE toCoinUnit #-} -gasLog :: (Logger logger) => Text -> TransactionM logger db () -gasLog m = do - l <- view txGasLogger - rk <- view txRequestKey - for_ l $ \logger -> - logInfo_ logger $ m <> ": " <> sshow rk - -- | Log request keys at DEBUG when successful -- debug :: (Logger logger) => Text -> TransactionM logger db () debug s = do l <- view txLogger rk <- view txRequestKey - logDebug_ l $ s <> ": " <> sshow rk + liftIO $ logFunctionText l Debug $ s <> ": " <> sshow rk -- | Denotes fatal failure points in the tx exec process -- @@ -1540,14 +1486,14 @@ fatal e = do l <- view txLogger rk <- view txRequestKey - logError_ l + liftIO $ logFunctionText l Error $ "critical transaction failure: " <> sshow rk <> ": " <> e throwM $ PactTransactionExecError (fromUntypedHash $ unRequestKey rk) e logError :: (Logger logger) => Text -> TransactionM logger db () -logError msg = view txLogger >>= \l -> logError_ l msg +logError msg = view txLogger >>= \l -> liftIO $ logFunctionText l Error msg infoLog :: (Logger logger) => Text -> TransactionM logger db () -infoLog msg = view txLogger >>= \l -> logInfo_ l msg +infoLog msg = view txLogger >>= \l -> liftIO $ logFunctionText l Info msg diff --git a/src/Chainweb/Pact4/Types.hs b/src/Chainweb/Pact4/Types.hs index 92ad20eb0a..f86495d3a9 100644 --- a/src/Chainweb/Pact4/Types.hs +++ b/src/Chainweb/Pact4/Types.hs @@ -7,36 +7,44 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE TemplateHaskell #-} module Chainweb.Pact4.Types - ( getInitCache - , updateInitCache - , updateInitCacheM - - , GasSupply(..) + ( GasSupply(..) -- * TxContext - , TxContext(..) - , ctxToPublicData - , ctxToPublicData' - , ctxBlockHeader - , ctxCurrentBlockHeight - , ctxChainId - , ctxVersion - , guardCtx - , getTxContext - , localLabelBlock + -- , TxContext(..) + -- , tcParentHeader + -- , tcPublicMeta + -- , tcMiner + -- , tcIsGenesis + -- , ctxToPublicData + -- , ctxToPublicData' + -- , ctxBlockHeader + -- , ctxCurrentBlockHeight + -- , ctxParentBlockHeight + -- , ctxChainId + -- , guardCtx + -- , getTxContext , catchesPactError , UnexpectedErrorPrinting(..) , GasId(..) , EnforceCoinbaseFailure(..) , CoinbaseUsePrecompiled(..) - , PactBlockM(..) - , liftPactServiceM - , runPactBlockM - , tracePactBlockM - , tracePactBlockM' + , internalError + , PactInternalError(..) + , SQLiteRowDelta(..) + , SQLitePendingData(..) + , emptySQLitePendingData + , pendingTableCreation + , pendingWrites + , pendingTxLogMap + , pendingSuccessfulTxs + , toPayloadWithOutputs + , TxTimeout(..) , getGasModel ) where @@ -44,7 +52,6 @@ module Chainweb.Pact4.Types import Control.Exception.Safe import Control.Lens import Control.Monad.Reader -import Control.Monad.State.Strict import Data.Aeson hiding (Error,(.=)) import qualified Data.Map.Strict as M @@ -54,6 +61,7 @@ import Data.Text (Text) import qualified Pact.JSON.Encode as J import Pact.Parse (ParsedDecimal) +import Pact.Types.Command import Pact.Types.ChainMeta import Pact.Types.Gas import Pact.Types.Info @@ -73,12 +81,28 @@ import Chainweb.Logger import Chainweb.Time import Chainweb.Utils import Chainweb.Version -import Utils.Logging.Trace import Pact.Gas.Table -import Chainweb.Pact.Types +-- import Chainweb.Pact.Types import Chainweb.Pact4.ModuleCache +import qualified Chainweb.Pact4.Transaction as Pact import Chainweb.Version.Guards +import qualified Pact.Types.Persistence as Pact +import GHC.Stack (CallStack, HasCallStack, callStack) +import GHC.Generics +import Data.HashMap.Strict (HashMap) +import Data.HashSet (HashSet) +import Data.ByteString (ByteString) +import qualified Pact.Types.Runtime as Pact +import Data.DList (DList) +import Data.List.NonEmpty (NonEmpty) +import Chainweb.Parent +import System.LogLevel +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import Chainweb.Payload (PayloadWithOutputs, newBlockOutputs, blockPayload, payloadData, payloadWithOutputs, newBlockTransactions, Transaction (..), TransactionOutput (..), CoinbaseOutput (..)) +import Chainweb.Pact.Types (Transactions(..), BlockCtx, guardCtx) +import qualified Data.ByteString.Short as SB -- | Indicates a computed gas charge (gas amount * gas price) @@ -90,166 +114,46 @@ instance Show GasSupply where show (GasSupply g) = show g instance J.Encode GasSupply where build = J.build . _gasSupply --- | Update init cache at adjusted parent block height (APBH). --- Contents are merged with cache found at or before APBH. --- APBH is 0 for genesis and (parent block height + 1) thereafter. -updateInitCache :: ModuleCache -> ParentHeader -> PactServiceM logger tbl () -updateInitCache mc ph = get >>= \PactServiceState{..} -> do - let bf 0 = 0 - bf h = succ h - let pbh = bf (view blockHeight $ _parentHeader ph) - - v <- view psVersion - cid <- view chainId - - psInitCache .= case M.lookupLE pbh _psInitCache of - Nothing -> M.singleton pbh mc - Just (_,before) - | cleanModuleCache v cid pbh -> - M.insert pbh mc _psInitCache - | otherwise -> M.insert pbh (before <> mc) _psInitCache - --- | Pair parent header with transaction metadata. --- In cases where there is no transaction/Command, 'PublicMeta' --- default value is used. -data TxContext = TxContext - { _tcParentHeader :: !ParentHeader - , _tcPublicMeta :: !PublicMeta - , _tcMiner :: !Miner - } deriving Show - -instance HasChainId TxContext where - _chainId = _chainId . _tcParentHeader -instance HasChainwebVersion TxContext where - _chainwebVersion = _chainwebVersion . _tcParentHeader - --- | Convert context to datatype for Pact environment. --- --- TODO: this should be deprecated, since the `ctxBlockHeader` --- call fetches a grandparent, not the parent. --- -ctxToPublicData :: TxContext -> PublicData -ctxToPublicData ctx = PublicData - { _pdPublicMeta = _tcPublicMeta ctx - , _pdBlockHeight = bh - , _pdBlockTime = bt - , _pdPrevBlockHash = toText hsh - } - where - h = ctxBlockHeader ctx - BlockHeight bh = ctxCurrentBlockHeight ctx - BlockCreationTime (Time (TimeSpan (Micros !bt))) = view blockCreationTime h - BlockHash hsh = view blockParent h - --- | Convert context to datatype for Pact environment using the --- current blockheight, referencing the parent header (not grandparent!) --- hash and blocktime data --- -ctxToPublicData' :: TxContext -> PublicData -ctxToPublicData' ctx = PublicData - { _pdPublicMeta = _tcPublicMeta ctx - , _pdBlockHeight = bh - , _pdBlockTime = bt - , _pdPrevBlockHash = toText h - } - where - bheader = _parentHeader (_tcParentHeader ctx) - BlockHeight !bh = succ $ view blockHeight bheader - BlockCreationTime (Time (TimeSpan (Micros !bt))) = - view blockCreationTime bheader - BlockHash h = view blockHash bheader +-- -- | Pair parent header with transaction metadata. +-- -- In cases where there is no transaction/Command, 'PublicMeta' +-- -- default value is used. +-- data TxContext = TxContext +-- { _tcParentHeader :: !(Parent BlockHeader) +-- , _tcPublicMeta :: !PublicMeta +-- , _tcMiner :: !Miner +-- , _tcIsGenesis :: !Bool +-- } deriving Show + +-- makeLenses ''TxContext + +-- instance HasChainId TxContext where +-- _chainId = _chainId . _tcParentHeader --- | Retrieve parent header as 'BlockHeader' -ctxBlockHeader :: TxContext -> BlockHeader -ctxBlockHeader = _parentHeader . _tcParentHeader +-- -- | Retrieve parent header as 'BlockHeader' +-- ctxBlockHeader :: TxContext -> BlockHeader +-- ctxBlockHeader = view _Parent . _tcParentHeader --- | Get "current" block height, which means parent height + 1. --- This reflects Pact environment focus on current block height, --- which influenced legacy switch checks as well. -ctxCurrentBlockHeight :: TxContext -> BlockHeight -ctxCurrentBlockHeight = succ . view blockHeight . ctxBlockHeader +-- -- | Get "current" block height, which means parent height + 1. +-- -- This reflects Pact environment focus on current block height, +-- -- which influenced legacy switch checks as well. +-- ctxCurrentBlockHeight :: TxContext -> BlockHeight +-- ctxCurrentBlockHeight = succ . view blockHeight . ctxBlockHeader -ctxChainId :: TxContext -> ChainId -ctxChainId = view blockChainId . ctxBlockHeader +-- -- | Get "current" block height, which means parent height + 1. +-- -- This reflects Pact environment focus on current block height, +-- -- which influenced legacy switch checks as well. +-- ctxParentBlockHeight :: TxContext -> Parent BlockHeight +-- ctxParentBlockHeight = Parent . view blockHeight . ctxBlockHeader -ctxVersion :: TxContext -> ChainwebVersion -ctxVersion = view chainwebVersion . ctxBlockHeader +-- ctxChainId :: TxContext -> ChainId +-- ctxChainId = view blockChainId . ctxBlockHeader -guardCtx :: (ChainwebVersion -> ChainId -> BlockHeight -> a) -> TxContext -> a -guardCtx g txCtx = g (ctxVersion txCtx) (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) +-- guardCtx :: (ChainId -> BlockHeight -> a) -> TxContext -> a +-- guardCtx g txCtx = g (ctxChainId txCtx) (ctxCurrentBlockHeight txCtx) -- | Assemble tx context from transaction metadata and parent header. -getTxContext :: Miner -> PublicMeta -> PactBlockM logger tbl TxContext -getTxContext miner pm = view psParentHeader >>= \ph -> return (TxContext ph pm miner) - --- | A sub-monad of PactServiceM, for actions taking place at a particular block. -newtype PactBlockM logger tbl a = PactBlockM - { _unPactBlockM :: - ReaderT (PactBlockEnv logger Pact4 tbl) (StateT PactServiceState IO) a - } deriving newtype - ( Functor, Applicative, Monad - , MonadReader (PactBlockEnv logger Pact4 tbl) - , MonadState PactServiceState - , MonadThrow, MonadCatch, MonadMask - , MonadIO - ) - --- | Lifts PactServiceM to PactBlockM by forgetting about the current block. --- It is unsafe to use `runPactBlockM` inside the argument to this function. -liftPactServiceM :: PactServiceM logger tbl a -> PactBlockM logger tbl a -liftPactServiceM (PactServiceM a) = PactBlockM $ ReaderT $ \e -> - StateT $ \s -> do - runStateT (runReaderT a (_psServiceEnv e)) s - --- | Look up an init cache that is stored at or before the height of the current parent header. -getInitCache :: PactBlockM logger tbl ModuleCache -getInitCache = do - ph <- views psParentHeader (view blockHeight . _parentHeader) - get >>= \PactServiceState{..} -> - case M.lookupLE ph _psInitCache of - Just (_,mc) -> return mc - Nothing -> return mempty - --- | A wrapper for 'updateInitCache' that uses the current block. -updateInitCacheM :: ModuleCache -> PactBlockM logger tbl () -updateInitCacheM mc = do - pc <- view psParentHeader - liftPactServiceM $ - updateInitCache mc pc - --- | Run 'PactBlockM' by providing the block context, in the form of --- a database snapshot at that block and information about the parent header. --- It is unsafe to use this function in an argument to `liftPactServiceM`. -runPactBlockM - :: ParentHeader -> Bool -> PactDbFor logger Pact4 - -> PactBlockM logger tbl a -> PactServiceM logger tbl a -runPactBlockM pctx isGenesis dbEnv (PactBlockM act) = PactServiceM $ ReaderT $ \e -> StateT $ \s -> do - let blockEnv = PactBlockEnv - { _psServiceEnv = e - , _psIsGenesis = isGenesis - , _psParentHeader = pctx - , _psBlockDbEnv = dbEnv - } - (a, s') <- runStateT - (runReaderT act blockEnv) - s - return (a, s') - -tracePactBlockM :: (Logger logger, ToJSON param) => Text -> param -> Int -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM label param weight a = tracePactBlockM' label (const param) (const weight) a - -tracePactBlockM' :: (Logger logger, ToJSON param) => Text -> (a -> param) -> (a -> Int) -> PactBlockM logger tbl a -> PactBlockM logger tbl a -tracePactBlockM' label calcParam calcWeight a = do - e <- ask - s <- get - (r, s') <- liftIO $ trace' (logJsonTrace_ (_psLogger $ _psServiceEnv e)) label (calcParam . fst) (calcWeight . fst) - $ runStateT (runReaderT (_unPactBlockM a) e) s - put s' - return r - -localLabelBlock :: (Logger logger) => (Text, Text) -> PactBlockM logger tbl x -> PactBlockM logger tbl x -localLabelBlock lbl x = do - locally (psServiceEnv . psLogger) (addLabel lbl) x +-- getTxContext :: Miner -> PublicMeta -> Parent BlockHeader -> TxContext +-- getTxContext miner pm ph = TxContext ph pm miner data UnexpectedErrorPrinting = PrintsUnexpectedError | CensorsUnexpectedError @@ -261,7 +165,7 @@ catchesPactError logger exnPrinting action = catches (Right <$> action) PrintsUnexpectedError -> return (viaShow e) CensorsUnexpectedError -> do - liftIO $ logWarn_ logger ("catchesPactError: unknown error: " <> sshow e) + liftIO $ logFunctionText logger Warn ("catchesPactError: unknown error: " <> sshow e) return "unknown error" return $ Left $ PactError EvalError noInfo [] err ] @@ -319,8 +223,139 @@ chainweb224GasModel = chainweb213GasModel ga -> runGasModel chainweb213GasModel name ga } -getGasModel :: TxContext -> GasModel +getGasModel :: HasVersion => BlockCtx -> GasModel getGasModel ctx | guardCtx chainweb213Pact ctx = chainweb213GasModel | guardCtx chainweb224Pact ctx = chainweb224GasModel | otherwise = freeModuleLoadGasModel + +-- | While a block is being run, mutations to the pact database are held +-- in RAM to be written to the DB in batches at @save@ time. For any given db +-- write, we need to record the table name, the current tx id, the row key, and +-- the row value. +-- +data SQLiteRowDelta = SQLiteRowDelta + { _deltaTableName :: !Text + , _deltaTxId :: {-# UNPACK #-} !Pact.TxId + , _deltaRowKey :: !ByteString + , _deltaData :: !ByteString + } deriving (Show, Generic, Eq) + +instance Ord SQLiteRowDelta where + compare a b = compare aa bb + where + aa = (_deltaTableName a, _deltaRowKey a, _deltaTxId a) + bb = (_deltaTableName b, _deltaRowKey b, _deltaTxId b) + {-# INLINE compare #-} + +-- | A map from table name to a list of 'TxLog' entries. This is maintained in +-- 'BlockState' and is cleared upon pact transaction commit. +type TxLogMap = M.Map Pact.TableName (DList Pact.TxLogJson) + +-- | Between a @restore..save@ bracket, we also need to record which tables +-- were created during this block (so the necessary @CREATE TABLE@ statements +-- can be performed upon block save). +type SQLitePendingTableCreations = HashSet Text + +-- | Pact transaction hashes resolved during this block. +type SQLitePendingSuccessfulTxs = HashSet ByteString + +-- | Pending writes to the pact db during a block, to be recorded in 'BlockState'. +-- Structured as a map from table name to a map from rowkey to inserted row delta. +type SQLitePendingWrites = HashMap Text (HashMap ByteString (NonEmpty SQLiteRowDelta)) + +-- Note [TxLogs in SQLitePendingData] +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +-- We should really not store TxLogs in SQLitePendingData, +-- because this data structure is specifically for things that +-- can exist both for the whole block and for specific transactions, +-- and txlogs only exist on the transaction level. +-- We don't do this in Pact 5 at all. + +-- | A collection of pending mutations to the pact db. We maintain two of +-- these; one for the block as a whole, and one for any pending pact +-- transaction. Upon pact transaction commit, the two 'SQLitePendingData' +-- values are merged together. +data SQLitePendingData = SQLitePendingData + { _pendingTableCreation :: !SQLitePendingTableCreations + , _pendingWrites :: !SQLitePendingWrites + -- See Note [TxLogs in SQLitePendingData] + , _pendingTxLogMap :: !TxLogMap + , _pendingSuccessfulTxs :: !SQLitePendingSuccessfulTxs + } + deriving (Eq, Show) + +emptySQLitePendingData :: SQLitePendingData +emptySQLitePendingData = SQLitePendingData mempty mempty mempty mempty + +data PactInternalError + = PactInternalError !CallStack !Text + | BlockValidationFailure !Text + | PactDuplicateTableError !Text + | PactTransactionExecError !Pact.PactHash !Text + | PactTransactionValidationException !Text + | CoinbaseFailure !Text + | PactBuyGasFailure !Text + | BlockGasLimitExceeded !Pact.Gas + | TransactionDecodeFailure !Text + deriving stock (Generic) + deriving anyclass (Exception) + +instance Show PactInternalError where + show = T.unpack . J.encodeText + +internalError :: (HasCallStack, MonadThrow m) => Text -> m a +internalError = throwM . PactInternalError callStack + +instance J.Encode PactInternalError where + build (PactInternalError _stack msg) = tagged "PactInternalError" msg + build (PactDuplicateTableError msg) = tagged "PactDuplicateTableError" msg + build (PactTransactionExecError h msg) = tagged "PactTransactionExecError" (J.Array (h, msg)) + build (PactTransactionValidationException errs) = tagged "PactTransactionValidationException" errs + build (CoinbaseFailure msg) = tagged "CoinbaseFailure" msg + build (PactBuyGasFailure msg) = tagged "PactBuyGasFailure" msg + build (BlockValidationFailure msg) = tagged "BlockValidationFailure" msg + build (BlockGasLimitExceeded g) = tagged "BlockGasLimitExceeded" g + build (TransactionDecodeFailure g) = tagged "TransactionDecodeFailure" g + +tagged :: J.Encode v => Text -> v -> J.Builder +tagged t v = J.object + [ "tag" J..= t + , "contents" J..= v + ] + +makeLenses ''SQLitePendingData + +pactCommandToBytes :: Command Text -> Transaction +pactCommandToBytes cwTrans = + let plBytes = J.encodeStrict cwTrans + in Transaction { _transactionBytes = plBytes } + +pactCommandResultToBytes :: CommandResult Pact.Hash -> TransactionOutput +pactCommandResultToBytes cr = + let outBytes = J.encodeStrict cr + in TransactionOutput { _transactionOutputBytes = outBytes } + +hashPactTxLogs :: CommandResult [Pact.TxLogJson] -> CommandResult Pact.Hash +hashPactTxLogs = over (crLogs . _Just) $ Pact.pactHash . Pact.encodeTxLogJsonArray + +toPayloadWithOutputs :: Miner -> Transactions Pact.Transaction (CommandResult [Pact.TxLogJson]) -> PayloadWithOutputs +toPayloadWithOutputs mi ts = + let oldSeq = _transactionPairs ts + trans = cmdBSToTx . sfst <$> oldSeq + transOuts = pactCommandResultToBytes . hashPactTxLogs . ssnd <$> oldSeq + + miner = toMinerData mi + cb = CoinbaseOutput $ J.encodeStrict $ hashPactTxLogs $ _transactionCoinbase ts + blockTrans = snd $ newBlockTransactions miner trans + cmdBSToTx = pactCommandToBytes + . fmap (T.decodeUtf8 . SB.fromShort . Pact.payloadBytes) + blockOuts = snd $ newBlockOutputs cb transOuts + + blockPL = blockPayload blockTrans blockOuts + plData = payloadData blockTrans blockPL + in payloadWithOutputs plData cb transOuts + +newtype TxTimeout = TxTimeout Text + deriving Show +instance Exception TxTimeout diff --git a/src/Chainweb/Pact4/Validations.hs b/src/Chainweb/Pact4/Validations.hs index d279ec439e..6b62c1e5f7 100644 --- a/src/Chainweb/Pact4/Validations.hs +++ b/src/Chainweb/Pact4/Validations.hs @@ -73,24 +73,27 @@ import qualified Pact.Types.ChainMeta as P import qualified Pact.Types.KeySet as P import qualified Pact.Parse as P import Chainweb.Pact4.Types -import Chainweb.Utils (ebool_) +import Chainweb.Utils (ebool_, int) +import Chainweb.Parent +import qualified Pact.Core.Gas.Types as Pact5 -- | Check whether a local Api request has valid metadata -- assertPreflightMetadata - :: P.Command (P.Payload P.PublicMeta c) - -> TxContext + :: HasVersion + => P.Command (P.Payload P.PublicMeta c) + -> BlockCtx -> Maybe LocalSignatureVerification - -> PactServiceM logger tbl (Either (NonEmpty Text) ()) -assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do - v <- view psVersion - cid <- view chainId - bgl <- view psBlockGasLimit + -> ServiceEnv tbl + -> IO (Either (NonEmpty Text) ()) +assertPreflightMetadata cmd@(P.Command pay sigs hsh) bctx sigVerify serviceEnv = do + let cid = view chainId serviceEnv + let bgl = view psNewBlockGasLimit serviceEnv - let bh = ctxCurrentBlockHeight txCtx - let validSchemes = validPPKSchemes v cid bh - let webAuthnPrefixLegal = isWebAuthnPrefixLegal v cid bh + let bh = _bctxCurrentBlockHeight bctx + let validSchemes = validPPKSchemes cid bh + let webAuthnPrefixLegal = isWebAuthnPrefixLegal cid bh let P.PublicMeta pcid _ gl gp _ _ = P._pMeta pay nid = P._pNetworkId pay @@ -102,7 +105,7 @@ assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do -- TODO , eUnless "Transaction Gas limit exceeds block gas limit" $ assertBlockGasLimit bgl gl , eUnless "Gas price decimal precision too high" $ assertGasPrice gp - , eUnless "Network id mismatch" $ assertNetworkId v nid + , eUnless "Network id mismatch" $ assertNetworkId nid , eUnless "Signature list size too big" $ assertSigSize sigs , eUnless "Invalid transaction signatures" $ sigValidate validSchemes webAuthnPrefixLegal signers , eUnless "Tx time outside of valid range" $ assertTxTimeRelativeToParent pct cmd @@ -116,11 +119,7 @@ assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do | Just NoVerify <- sigVerify = True | otherwise = isRight $ assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs - pct = ParentCreationTime - . view blockCreationTime - . _parentHeader - . _tcParentHeader - $ txCtx + pct = view bctxParentCreationTime bctx eUnless t assertion | assertion = Nothing @@ -129,7 +128,7 @@ assertPreflightMetadata cmd@(P.Command pay sigs hsh) txCtx sigVerify = do -- | Check whether a particular Pact chain id is parseable -- assertParseChainId :: P.ChainId -> Bool -assertParseChainId = isJust . fromPactChainId +assertParseChainId (P.ChainId cid) = isJust $ chainIdFromText cid -- | Check whether the chain id defined in the metadata of a Pact/Chainweb -- command payload matches a given chain id. @@ -149,14 +148,14 @@ assertGasPrice (P.GasPrice (P.ParsedDecimal gp)) = decimalPlaces gp <= defaultMa -- | Check and assert that the 'GasLimit' of a transaction is less than or eqaul to -- the block gas limit -- -assertBlockGasLimit :: P.GasLimit -> P.GasLimit -> Bool -assertBlockGasLimit bgl tgl = bgl >= tgl +assertBlockGasLimit :: Pact5.GasLimit -> P.GasLimit -> Bool +assertBlockGasLimit (Pact5.GasLimit (Pact5.Gas bgl)) tgl = P.GasLimit (int bgl) >= tgl -- | Check and assert that 'ChainwebVersion' is equal to some pact 'NetworkId'. -- -assertNetworkId :: ChainwebVersion -> Maybe P.NetworkId -> Bool -assertNetworkId _ Nothing = False -assertNetworkId v (Just (P.NetworkId nid)) = ChainwebVersionName nid == _versionName v +assertNetworkId :: HasVersion => Maybe P.NetworkId -> Bool +assertNetworkId Nothing = False +assertNetworkId (Just (P.NetworkId nid)) = ChainwebVersionName nid == _versionName implicitVersion -- | Check and assert that the number of signatures in a 'Command' is -- at most 100. @@ -209,10 +208,10 @@ assertValidateSigs validSchemes webAuthnPrefixLegal hsh signers sigs = do -- skipped when replaying old blocks. -- assertTxTimeRelativeToParent - :: ParentCreationTime + :: Parent BlockCreationTime -> P.Command (P.Payload P.PublicMeta c) -> Bool -assertTxTimeRelativeToParent (ParentCreationTime (BlockCreationTime txValidationTime)) tx = +assertTxTimeRelativeToParent (Parent (BlockCreationTime txValidationTime)) tx = ttl > 0 && txValidationTime >= timeFromSeconds 0 && txOriginationTime >= 0 @@ -226,10 +225,10 @@ assertTxTimeRelativeToParent (ParentCreationTime (BlockCreationTime txValidation -- | Check that the tx's creation time is not too far in the future relative -- to the block creation time assertTxNotInFuture - :: ParentCreationTime + :: Parent BlockCreationTime -> P.Command (P.Payload P.PublicMeta c) -> Bool -assertTxNotInFuture (ParentCreationTime (BlockCreationTime txValidationTime)) tx = +assertTxNotInFuture (Parent (BlockCreationTime txValidationTime)) tx = timeFromSeconds txOriginationTime <= lenientTxValidationTime where timeFromSeconds = Time . secondsToTimeSpan . Seconds . fromIntegral diff --git a/src/Chainweb/SPV/VerifyProof.hs b/src/Chainweb/SPV/VerifyProof.hs index f42572719e..13c89c07af 100644 --- a/src/Chainweb/SPV/VerifyProof.hs +++ b/src/Chainweb/SPV/VerifyProof.hs @@ -14,9 +14,13 @@ -- module Chainweb.SPV.VerifyProof ( runTransactionOutputProof +, checkProofAndExtractOutput ) where import Control.Monad.Catch +import Control.Monad.Except +import Control.Monad.IO.Class +import Data.Text (Text) import Data.MerkleLog.V1 qualified as V1 @@ -24,12 +28,16 @@ import Data.MerkleLog.V1 qualified as V1 import Chainweb.BlockHash import Chainweb.BlockHeaderDB +import Chainweb.Crypto.MerkleLog (proofSubject) import Chainweb.CutDB import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse import Chainweb.Payload import Chainweb.SPV import Chainweb.Version +import Chainweb.Pact.Backend.Types (HeaderOracle (..)) +import Chainweb.Parent +import Chainweb.Utils (unlessM) -- -------------------------------------------------------------------------- -- @@ -42,3 +50,10 @@ runTransactionOutputProof -> m BlockHash runTransactionOutputProof (TransactionOutputProof _ p) = BlockHash . MerkleLogHash <$> V1.runMerkleProof p + +checkProofAndExtractOutput :: HeaderOracle -> TransactionOutputProof ChainwebMerkleHashAlgorithm -> ExceptT Text IO TransactionOutput +checkProofAndExtractOutput oracle proof@(TransactionOutputProof _cid p) = do + h <- runTransactionOutputProof proof + unlessM (liftIO $ oracle.consult (Parent h)) $ throwError + "spv verification failed: target header is not in the chain" + proofSubject p diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 93795c97f8..4072e11ad1 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -54,6 +54,7 @@ module Chainweb.Version , decodeChainwebVersionCode , ChainwebVersionName(..) , ChainwebVersion(..) + , pact4Upgrade , TxIdxInBlock(..) , _TxBlockIdx , VersionQuirks(..) @@ -192,6 +193,7 @@ import Data.Singletons import P2P.Peer import GHC.Exts (WithDict(..)) +import qualified Chainweb.Pact4.Transaction as Pact4 -- -------------------------------------------------------------------------- -- -- Forks @@ -377,19 +379,36 @@ instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag ChainwebVer -- The type of upgrades, which are sets of transactions to run at certain block -- heights during coinbase. -data PactUpgrade = - PactUpgrade - { _pactUpgradeTransactions :: [Pact.Transaction] - } deriving Eq +data PactUpgrade where + Pact4Upgrade :: + { _pact4UpgradeTransactions :: [Pact4.Transaction] + , _legacyUpgradeIsPrecocious :: Bool + -- ^ when set to `True`, the upgrade transactions are executed using the + -- forks of the next block, rather than the block the upgrade + -- transactions are included in. do not use this for new upgrades + -- unless you are sure you need it, this mostly exists for old upgrades. + } -> PactUpgrade + Pact5Upgrade :: + { _pact5UpgradeTransactions :: [Pact.Transaction] + } -> PactUpgrade + +instance Eq PactUpgrade where + Pact4Upgrade txs precocious == Pact4Upgrade txs' precocious' = + txs == txs' && precocious == precocious' + Pact5Upgrade txs == Pact5Upgrade txs' = + txs == txs' + _ == _ = False instance Show PactUpgrade where - show PactUpgrade {} = "" + show Pact4Upgrade {} = "" + show Pact5Upgrade {} = "" instance NFData PactUpgrade where - rnf (PactUpgrade txs) = rnf txs + rnf (Pact4Upgrade txs precocious) = rnf txs `seq` rnf precocious + rnf (Pact5Upgrade txs) = rnf txs --- -------------------------------------------------------------------------- -- --- Version Quirks +pact4Upgrade :: [Pact4.Transaction] -> PactUpgrade +pact4Upgrade txs = Pact4Upgrade txs False data TxIdxInBlock = TxBlockIdx Word deriving stock (Eq, Ord, Show, Generic) diff --git a/src/Chainweb/Version/Guards.hs b/src/Chainweb/Version/Guards.hs index 1b948ceeaf..2f5b91a92d 100644 --- a/src/Chainweb/Version/Guards.hs +++ b/src/Chainweb/Version/Guards.hs @@ -56,6 +56,8 @@ module Chainweb.Version.Guards , maxBlockGasLimit , validPPKSchemes , validKeyFormats + , isWebAuthnPrefixLegal + , pact4ParserVersion , pact5Serialiser -- ** BlockHeader Validation Guards @@ -68,6 +70,7 @@ module Chainweb.Version.Guards import Chainweb.BlockHeight import Chainweb.ChainId +import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils.Rule import Chainweb.Version import Control.Lens @@ -304,3 +307,13 @@ validKeyFormats cid bh = if chainweb222Pact cid bh then [ed25519HexFormat, webAuthnFormat] else [ed25519HexFormat] + +isWebAuthnPrefixLegal :: HasVersion => ChainId -> BlockHeight -> Pact4.IsWebAuthnPrefixLegal +isWebAuthnPrefixLegal cid bh + | chainweb222Pact cid bh = Pact4.WebAuthnPrefixLegal + | otherwise = Pact4.WebAuthnPrefixIllegal + +pact4ParserVersion :: HasVersion => ChainId -> BlockHeight -> Pact4.PactParserVersion +pact4ParserVersion cid bh + | chainweb213Pact cid bh = Pact4.PactParserChainweb213 + | otherwise = Pact4.PactParserGenesis diff --git a/src/Chainweb/Version/Registry.hs b/src/Chainweb/Version/Registry.hs index bb286e0df3..0708bde58a 100644 --- a/src/Chainweb/Version/Registry.hs +++ b/src/Chainweb/Version/Registry.hs @@ -36,7 +36,6 @@ import GHC.Stack import Chainweb.Version import Chainweb.Version.Development import Chainweb.Version.EvmDevelopment -import Chainweb.Version.EvmDevelopmentSingleton import Chainweb.Version.EvmTestnet import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet @@ -72,7 +71,8 @@ validateVersion v = do unless (null errors) $ error $ unlines $ ["errors encountered validating version", show v] <> errors where - isUpgradeEmpty PactUpgrade{_pactUpgradeTransactions = upg} = null upg + isUpgradeEmpty Pact4Upgrade{_pact4UpgradeTransactions = upg} = null upg + isUpgradeEmpty Pact5Upgrade{_pact5UpgradeTransactions = upg} = null upg -- | Versions known to us by name. knownVersions :: [ChainwebVersion] diff --git a/test/lib/Chainweb/Test/Pact4/Utils.hs b/test/lib/Chainweb/Test/Pact4/Utils.hs index 4e7ca039e1..4e61e677ae 100644 --- a/test/lib/Chainweb/Test/Pact4/Utils.hs +++ b/test/lib/Chainweb/Test/Pact4/Utils.hs @@ -62,7 +62,6 @@ module Chainweb.Test.Pact4.Utils , mkXChainTransferCap -- * Command builder , defaultCmd -, buildCwCmd , buildTextCmd , mkExec' , mkExec @@ -87,27 +86,12 @@ module Chainweb.Test.Pact4.Utils , CmdSigner , csSigner , csPrivKey --- * Pact Service creation -, withPactTestBlockDb -, withPactTestBlockDb' -, withWebPactExecutionService -, withWebPactExecutionServiceCompaction -, withPactCtxSQLite -, WithPactCtxSQLite -- * Other service creation , initializeSQLite , freeSQLiteResource -, freeGasModel -, testPactServiceConfig , withBlockHeaderDb , withSqliteDb --- * Mempool utils -, delegateMemPoolAccess -, withDelegateMempool -, setMempool -, setOneShotMempool -- * Block formation -, runCut , Noncer , zeroNoncer -- * Pact State @@ -125,7 +109,6 @@ module Chainweb.Test.Pact4.Utils , someBlockHeader , testPactFilesDir , getPWOByHeader -, throwIfNotPact4 ) where @@ -200,13 +183,11 @@ import Chainweb.Miner.Pact import Chainweb.Pact.Backend.Compaction qualified as Sigma import Chainweb.Pact.Backend.PactState qualified as PactState import Chainweb.Pact.Backend.PactState (TableDiffable(..), Table(..), PactRow(..)) -import Chainweb.Pact.PactService.Checkpointer.Internal (initCheckpointerResources) import Chainweb.Pact.Backend.SQLite.DirectV2 import Chainweb.Pact.Backend.Utils hiding (tbl, withSqliteDb) import Chainweb.Pact.PactService import Chainweb.Pact.RestAPI.Server (validateCommand) -import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Pact4.Types import Chainweb.Payload @@ -223,10 +204,10 @@ import Chainweb.Version (ChainwebVersion(..), chainIds) import qualified Chainweb.Version as Version import Chainweb.Version.Utils (someChainId) import Chainweb.WebBlockHeaderDB -import Chainweb.WebPactExecutionService import Chainweb.Storage.Table.RocksDB import Chainweb.Pact.Backend.Types +import Chainweb.Parent -- ----------------------------------------------------------------------- -- -- Keys @@ -451,7 +432,7 @@ mkXResumeEvent sender receiver amount ks mn mh tid sid -- | Cap smart constructor. mkCapability :: ModuleName -> Text -> [PactValue] -> SigCapability -mkCapability mn cap args = SigCapability (QualifiedName mn cap noInfo) args +mkCapability mn cap args = SigCapability (QualifiedName mn cap Pact.Types.Info.noInfo) args -- | Convenience to make caps like TRANSFER, GAS etc. mkCoinCap :: Text -> [PactValue] -> SigCapability @@ -566,29 +547,20 @@ defaultCmd = CmdBuilder , _cbCreationTime = 0 -- epoch } --- | Build parsed + verified Pact command --- --- TODO: Use the new `assertPact4Command` function. -buildCwCmd :: (MonadThrow m, MonadIO m) => Text -> ChainwebVersion -> CmdBuilder -> m Pact4.Transaction -buildCwCmd nonce v cmd = buildRawCmd nonce v cmd >>= \(c :: Command ByteString) -> - case validateCommand v (_cbChainId cmd) (T.decodeUtf8 <$> c) of - Left err -> throwM $ userError $ "buildCmd failed: " ++ T.unpack err - Right cmd' -> return cmd' - -- | Build unparsed, unverified command -- -buildTextCmd :: Text -> ChainwebVersion -> CmdBuilder -> IO (Command Text) -buildTextCmd nonce v = fmap (fmap T.decodeUtf8) . buildRawCmd nonce v +buildTextCmd :: Version.HasVersion => Text -> CmdBuilder -> IO (Command Text) +buildTextCmd nonce = fmap (fmap T.decodeUtf8) . buildRawCmd nonce -- | Build a raw bytestring command -- -buildRawCmd :: (MonadThrow m, MonadIO m) => Text -> ChainwebVersion -> CmdBuilder -> m (Command ByteString) -buildRawCmd nonce v (set cbNonce nonce -> CmdBuilder{..}) = do +buildRawCmd :: (MonadThrow m, MonadIO m) => Version.HasVersion => Text -> CmdBuilder -> m (Command ByteString) +buildRawCmd nonce (set cbNonce nonce -> CmdBuilder{..}) = do kps <- liftIO $ traverse mkDynKeyPairs _cbSigners cmd <- liftIO $ mkCommandWithDynKeys kps _cbVerifiers pm _cbNonce (Just nid) _cbRPC pure cmd where - nid = P.NetworkId (sshow v) + nid = P.NetworkId (sshow Version.implicitVersion) cid = fromString $ show (chainIdInt _cbChainId :: Int) pm = PublicMeta cid _cbSender _cbGasLimit _cbGasPrice _cbTTL _cbCreationTime @@ -643,218 +615,14 @@ pactTestLogger backend showAll = P.initLoggers backend f (P.LogRules mempty) f _ b "DDL" d | not showAll = P.doLog (\_ -> return ()) b "DDL" d f a b c d = P.doLog a b c d --- | Test Pact Execution Context for running inside 'PactServiceM'. --- Only used internally. -data TestPactCtx logger tbl = TestPactCtx - { _testPactCtxState :: !(MVar PactServiceState) - , _testPactCtxEnv :: !(PactServiceEnv logger tbl) - } - -evalPactServiceM_ :: TestPactCtx logger tbl -> PactServiceM logger tbl a -> IO a -evalPactServiceM_ ctx pact = modifyMVar (_testPactCtxState ctx) $ \s -> do - T2 a s' <- runPactServiceM s (_testPactCtxEnv ctx) pact - return (s',a) - -destroyTestPactCtx :: TestPactCtx logger tbl -> IO () -destroyTestPactCtx = void . takeMVar . _testPactCtxState - --- | setup TestPactCtx, internal function. --- Use 'withPactCtxSQLite' in tests. -testPactCtxSQLite - :: forall logger tbl. (Logger logger, CanReadablePayloadCas tbl) - => logger - -> ChainwebVersion - -> Version.ChainId - -> BlockHeaderDb - -> PayloadDb tbl - -> SQLiteEnv - -> PactServiceConfig - -> IO (TestPactCtx logger tbl) -testPactCtxSQLite logger v cid bhdb pdb sqlenv conf = do - cp <- initCheckpointerResources defaultModuleCacheLimit sqlenv DoNotPersistIntraBlockWrites cpLogger v cid - let rs = readRewards - !ctx <- TestPactCtx - <$!> newMVar (PactServiceState mempty) - <*> pure (mkPactServiceEnv cp rs) - evalPactServiceM_ ctx (initialPayloadState v cid) - return ctx - where - cpLogger = addLabel ("chain-id", chainIdToText cid) $ addLabel ("sub-component", "checkpointer") $ logger - mkPactServiceEnv :: Checkpointer logger -> MinerRewards -> PactServiceEnv logger tbl - mkPactServiceEnv cp rs = PactServiceEnv - { _psMempoolAccess = Nothing - , _psCheckpointer = cp - , _psPdb = pdb - , _psBlockHeaderDb = bhdb - , _psMinerRewards = rs - , _psReorgLimit = _pactReorgLimit conf - , _psPreInsertCheckTimeout = _pactPreInsertCheckTimeout conf - , _psOnFatalError = defaultOnFatalError mempty - , _psVersion = v - , _psAllowReadsInLocal = _pactAllowReadsInLocal conf - , _psLogger = addLabel ("chain-id", chainIdToText cid) $ addLabel ("component", "pact") $ logger - , _psGasLogger = do - guard (_pactLogGas conf) - return - $ addLabel ("chain-id", chainIdToText cid) - $ addLabel ("component", "pact") - $ addLabel ("sub-component", "gas") - $ logger - - , _psBlockGasLimit = _pactNewBlockGasLimit conf - , _psEnableLocalTimeout = False - , _psTxFailuresCounter = Nothing - , _psTxTimeLimit = _pactTxTimeLimit conf - } - -freeGasModel :: TxContext -> GasModel -freeGasModel = const $ constGasModel 0 - ---- | A queue-less WebPactExecutionService (for all chains) ---- with direct chain access map for local. -withWebPactExecutionServiceCompaction - :: (Logger logger) - => logger - -> ChainwebVersion - -> PactServiceConfig - -> TestBlockDb - -> ChainMap MemPoolAccess - -> ((WebPactExecutionService, HM.HashMap ChainId (SQLiteEnv, PactExecutionService), WebPactExecutionService, HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) -> IO a) -- TODO: second 'WebPactExecutionService' seems unnecessary? - -> IO a -withWebPactExecutionServiceCompaction logger v pactConfig bdb mempools act = - withDbs $ \srcSqlEnvs -> - withDbs $ \targetSqlEnvs -> do - srcPacts <- mkPacts srcSqlEnvs - let srcWeb = mkWebPactExecutionService (snd <$> srcPacts) - - targetPacts <- mkPacts targetSqlEnvs - let targetWeb = mkWebPactExecutionService (snd <$> targetPacts) - - act (srcWeb, srcPacts, targetWeb, targetPacts) - where - mkPacts :: [SQLiteEnv] -> IO (HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) - mkPacts sqlEnvs = fmap HM.fromList - $ traverse (\(dbEnv, cid) -> (cid,) . (dbEnv,) <$> mkTestPactExecutionService dbEnv cid) - $ zip sqlEnvs - $ toList - $ chainIds v - - withDbs :: ([SQLiteEnv] -> IO x) -> IO x - withDbs f = foldl' (\soFar _ -> withDb soFar) f (chainIds v) [] - - withDb :: ([SQLiteEnv] -> IO x) -> [SQLiteEnv] -> IO x - withDb g envs = withTempSQLiteConnection chainwebPragmas $ \s -> g (s : envs) - - mkTestPactExecutionService :: () - => SQLiteEnv - -> ChainId - -> IO PactExecutionService - mkTestPactExecutionService sqlenv c = do - bhdb <- getBlockHeaderDb c bdb - ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig - return $ PactExecutionService - { _pactNewBlock = \_ m fill ph -> - evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph - , _pactContinueBlock = \_ bip -> - evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip - , _pactValidateBlock = \h d -> - evalPactServiceM_ ctx $ fst <$> execValidateBlock (mempools ^?! atChain c) h d - , _pactLocal = \pf sv rd cmd -> - evalPactServiceM_ ctx $ execLocal cmd pf sv rd - , _pactLookup = \_cid cd hashes -> - evalPactServiceM_ ctx $ execLookupPactTxs cd hashes - , _pactPreInsertCheck = \_ txs -> - evalPactServiceM_ ctx $ V.map (\_ -> Nothing) <$> execPreInsertCheckReq txs - , _pactBlockTxHistory = \h d -> - evalPactServiceM_ ctx $ execBlockTxHistory h d - , _pactHistoricalLookup = \h d k -> - evalPactServiceM_ ctx $ execHistoricalLookup h d k - , _pactSyncToBlock = \h -> - evalPactServiceM_ ctx $ execSyncToBlock h - , _pactReadOnlyReplay = \l u -> - evalPactServiceM_ ctx $ execReadOnlyReplay l u - } - --- | A queue-less WebPactExecutionService (for all chains) --- with direct chain access map for local. -withWebPactExecutionService - :: (Logger logger) - => logger - -> ChainwebVersion - -> PactServiceConfig - -> TestBlockDb - -> ChainMap MemPoolAccess - -> ((WebPactExecutionService,HM.HashMap ChainId (SQLiteEnv, PactExecutionService)) -> IO a) - -> IO a -withWebPactExecutionService logger v pactConfig bdb mempools act = - withDbs $ \sqlenvs -> do - pacts <- fmap HM.fromList - $ traverse (\(dbEnv, cid) -> (cid,) . (dbEnv,) <$> mkPact dbEnv cid) - $ zip sqlenvs - $ toList - $ chainIds v - act (mkWebPactExecutionService (snd <$> pacts), pacts) - where - withDbs f = foldl' (\soFar _ -> withDb soFar) f (chainIds v) [] - withDb g envs = withTempSQLiteConnection chainwebPragmas $ \s -> g (s : envs) - - mkPact :: SQLiteEnv -> ChainId -> IO PactExecutionService - mkPact sqlenv c = do - bhdb <- getBlockHeaderDb c bdb - ctx <- testPactCtxSQLite logger v c bhdb (_bdbPayloadDb bdb) sqlenv pactConfig - return $ PactExecutionService - { _pactNewBlock = \_ m fill ph -> - evalPactServiceM_ ctx $ fmap NewBlockInProgress <$> execNewBlock (mempools ^?! atChain c) m fill ph - , _pactContinueBlock = \_ bip -> - evalPactServiceM_ ctx $ execContinueBlock (mempools ^?! atChain c) bip - , _pactValidateBlock = \h d -> - evalPactServiceM_ ctx $ fst <$> execValidateBlock (mempools ^?! atChain c) h d - , _pactLocal = \pf sv rd cmd -> - evalPactServiceM_ ctx $ execLocal cmd pf sv rd - , _pactLookup = \_cid cd hashes -> - evalPactServiceM_ ctx $ execLookupPactTxs cd hashes - , _pactPreInsertCheck = \_ txs -> - evalPactServiceM_ ctx $ V.map (\_ -> Nothing) <$> execPreInsertCheckReq txs - , _pactBlockTxHistory = \h d -> - evalPactServiceM_ ctx $ execBlockTxHistory h d - , _pactHistoricalLookup = \h d k -> - evalPactServiceM_ ctx $ execHistoricalLookup h d k - , _pactSyncToBlock = \h -> - evalPactServiceM_ ctx $ execSyncToBlock h - , _pactReadOnlyReplay = \l u -> - evalPactServiceM_ ctx $ execReadOnlyReplay l u - } - -- | Noncer for 'runCut' type Noncer = ChainId -> IO Nonce zeroNoncer :: Noncer zeroNoncer = const (return $ Nonce 0) --- | Populate blocks for every chain of the current cut. Uses provided pact --- service to produce a new block, add it to dbs, etc. -runCut - :: ChainwebVersion - -> TestBlockDb - -> WebPactExecutionService - -> GenBlockTime - -> Noncer - -> Miner - -> IO () -runCut v bdb pact genTime noncer miner = - forM_ (chainIds v) $ \cid -> do - ph <- ParentHeader <$> getParentTestBlockDb bdb cid - !newBlock <- throwIfNoHistory =<< _webPactNewBlock pact cid miner NewBlockFill ph - let pout = newBlockToPayloadWithOutputs newBlock - n <- noncer cid - - -- skip this chain if mining fails and retry with the next chain. - whenM (addTestBlockDb bdb (succ $ view blockHeight $ _parentHeader ph) n genTime cid pout) $ do - h <- getParentTestBlockDb bdb cid - void $ _webPactValidateBlock pact h (CheckablePayloadWithOutputs pout) - initializeSQLite :: IO SQLiteEnv -initializeSQLite = open2 file >>= \case +initializeSQLite = open2 file [] >>= \case Left (_err, _msg) -> internalError "initializeSQLite: A connection could not be opened." Right r -> return r @@ -864,76 +632,12 @@ initializeSQLite = open2 file >>= \case freeSQLiteResource :: SQLiteEnv -> IO () freeSQLiteResource sqlenv = void $ close_v2 sqlenv -type WithPactCtxSQLite logger tbl = forall a . PactServiceM logger tbl a -> IO a - --- | Used to run 'PactServiceM' functions directly on a database (ie not use checkpointer). -withPactCtxSQLite - :: (Logger logger, CanReadablePayloadCas tbl) - => logger - -> ChainwebVersion - -> IO BlockHeaderDb - -> IO (PayloadDb tbl) - -> PactServiceConfig - -> (WithPactCtxSQLite logger tbl -> TestTree) - -> TestTree -withPactCtxSQLite logger v bhdbIO pdbIO conf f = - withResource - initializeSQLite - freeSQLiteResource $ \io -> - withResource (start io) destroy $ \ctxIO -> f $ \toPact -> do - ctx <- ctxIO - evalPactServiceM_ ctx toPact - where - destroy = destroyTestPactCtx - cid = someChainId v - start ios = do - bhdb <- bhdbIO - pdb <- pdbIO - s <- ios - testPactCtxSQLite logger v cid bhdb pdb s conf - toTxCreationTime :: Integral a => Time a -> TxCreationTime toTxCreationTime (Time timespan) = TxCreationTime $ fromIntegral $ timeSpanToSeconds timespan --- | 'MemPoolAccess' that delegates all calls to the contents of provided `IORef`. -delegateMemPoolAccess :: IORef MemPoolAccess -> MemPoolAccess -delegateMemPoolAccess r = MemPoolAccess - { mpaGetBlock = \a b c d e -> call mpaGetBlock $ \f -> f a b c d e - , mpaSetLastHeader = \a -> call mpaSetLastHeader ($ a) - , mpaProcessFork = \a -> call mpaProcessFork ($ a) - , mpaBadlistTx = \a -> call mpaBadlistTx ($ a) - } - where - call :: (MemPoolAccess -> f) -> (f -> IO a) -> IO a - call f g = readIORef r >>= g . f - --- | use a "delegate" which you can dynamically reset/modify. --- Returns the updateable 'IORef MemPoolAccess` for use --- in tests, and the plain 'MemPoolAccess' for initializing --- PactService etc. -withDelegateMempool - :: (IO (IORef MemPoolAccess, MemPoolAccess) -> TestTree) - -> TestTree -withDelegateMempool = withResource' start - where - start = (id &&& delegateMemPoolAccess) <$> newIORef mempty - --- | Set test mempool using IORef. -setMempool :: MonadIO m => IO (IORef MemPoolAccess) -> MemPoolAccess -> m () -setMempool refIO mp = liftIO (refIO >>= flip writeIORef mp) - --- | Set test mempool wrapped with a "one shot" 'mpaGetBlock' adapter. -setOneShotMempool :: MonadIO m => IO (IORef MemPoolAccess) -> MemPoolAccess -> m () -setOneShotMempool mpRefIO mp = do - oneShot <- liftIO $ newIORef False - setMempool mpRefIO $ mp - { mpaGetBlock = \g v i a e -> readIORef oneShot >>= \case - False -> writeIORef oneShot True >> mpaGetBlock mp g v i a e - True -> mempty - } - withBlockHeaderDb - :: IO RocksDb + :: Version.HasVersion + => IO RocksDb -> BlockHeader -> (IO BlockHeaderDb -> TestTree) -> TestTree @@ -944,46 +648,6 @@ withBlockHeaderDb iordb b = withResource start stop testBlockHeaderDb rdb b stop = closeBlockHeaderDb --- | Single-chain Pact via service queue. --- --- The difference between this and 'withPactTestBlockDb' is that, --- this function takes a `SQLiteEnv` resource which it then exposes --- to the test function. --- --- TODO: Consolidate these two functions. -withPactTestBlockDb' - :: ChainwebVersion - -> ChainId - -> RocksDb - -> IO SQLiteEnv - -> IO MemPoolAccess - -> PactServiceConfig - -> (IO (SQLiteEnv,PactQueue,TestBlockDb) -> TestTree) - -> TestTree -withPactTestBlockDb' version cid rdb sqlEnvIO mempoolIO pactConfig f = - withResourceT (mkTestBlockDb version rdb) $ \bdbio -> - withResource (startPact bdbio) stopPact $ f . fmap (view _2) - where - startPact bdbio = do - reqQ <- newPactQueue 2000 - bdb <- bdbio - sqlEnv <- sqlEnvIO - mempool <- mempoolIO - bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) cid - let pdb = _bdbPayloadDb bdb - a <- async $ runForever (\_ _ -> return ()) "Chainweb.Test.Pact4.Utils.withPactTestBlockDb" $ - runPactService version cid logger Nothing reqQ mempool bhdb pdb sqlEnv pactConfig - return (a, (sqlEnv,reqQ,bdb)) - - stopPact (a, _) = cancel a - - -- Ideally, we should throw 'error' when the logger is invoked, because - -- error logs should not happen in production and should always be resolved. - -- Unfortunately, that's not yet always the case. So we just drop the - -- message. - -- - logger = genericLogger Error (\_ -> return ()) - withSqliteDb :: () => ChainId -> IO FilePath @@ -1005,41 +669,6 @@ withSqliteDb cid iodir s = withResource start stop s -- logger = genericLogger Error (\_ -> return ()) --- | Single-chain Pact via service queue. -withPactTestBlockDb - :: ChainwebVersion - -> ChainId - -> RocksDb - -> IO MemPoolAccess - -> PactServiceConfig - -> (IO (SQLiteEnv,PactQueue,TestBlockDb) -> TestTree) - -> TestTree -withPactTestBlockDb version cid rdb mempoolIO pactConfig f = - withResourceT (withTempDir "pact-dir") $ \iodir -> - withResourceT (mkTestBlockDb version rdb) $ \bdbio -> - withResource (startPact bdbio iodir) stopPact $ f . fmap (view _2) - where - startPact bdbio iodir = do - reqQ <- newPactQueue 2000 - dir <- iodir - bdb <- bdbio - mempool <- mempoolIO - bhdb <- getWebBlockHeaderDb (_bdbWebBlockHeaderDb bdb) cid - let pdb = _bdbPayloadDb bdb - sqlEnv <- startSqliteDb cid logger dir False - a <- async $ runForever (\_ _ -> return ()) "Chainweb.Test.Pact4.Utils.withPactTestBlockDb" $ - runPactService version cid logger Nothing reqQ mempool bhdb pdb sqlEnv pactConfig - return (a, (sqlEnv,reqQ,bdb)) - - stopPact (a, (sqlEnv, _, _)) = cancel a >> stopSqliteDb sqlEnv - - -- Ideally, we should throw 'error' when the logger is invoked, because - -- error logs should not happen in production and should always be resolved. - -- Unfortunately, that's not yet always the case. So we just drop the - -- message. - -- - logger = genericLogger Error (\_ -> return ()) - dummyLogger :: GenericLogger dummyLogger = genericLogger Error (error . T.unpack) @@ -1053,19 +682,19 @@ someTestVersion :: ChainwebVersion someTestVersion = instantCpmTestVersion petersenChainGraph someTestVersionHeader :: BlockHeader -someTestVersionHeader = someBlockHeader someTestVersion 10 +someTestVersionHeader = Version.withVersion someTestVersion $ someBlockHeader 10 -- | The runtime is linear in the requested height. This can be slow if a large -- block height is requested for a chainweb version that simulates realtime -- mining. It is fast enough for testing purposes with "fast" mining chainweb -- versions like 'someTestVersion' for block heights up to, say, 1000. -- -someBlockHeader :: ChainwebVersion -> BlockHeight -> BlockHeader -someBlockHeader v 0 = genesisBlockHeader v (unsafeChainId 0) -someBlockHeader v h = (!! (int h - 1)) +someBlockHeader :: Version.HasVersion => BlockHeight -> BlockHeader +someBlockHeader 0 = genesisBlockHeader (unsafeChainId 0) +someBlockHeader h = (!! (int h - 1)) $ testBlockHeaders - $ ParentHeader - $ genesisBlockHeader v (unsafeChainId 0) + $ Parent + $ genesisBlockHeader (unsafeChainId 0) -- | Get all pact user tables. -- @@ -1109,10 +738,3 @@ getPWOByHeader h (TestBlockDb _ pdb _) = lookupPayloadWithHeight pdb (Just $ view blockHeight h) (view blockPayloadHash h) >>= \case Nothing -> throwM $ userError "getPWOByHeader: payload not found" Just pwo -> return pwo - -throwIfNotPact4 :: Version.ForSomePactVersion f -> IO (f Version.Pact4) -throwIfNotPact4 h = case h of - Version.ForSomePactVersion Version.Pact4T a -> do - pure a - Version.ForSomePactVersion Version.Pact5T _ -> do - assertFailure "throwIfNotPact4: should be pact4" diff --git a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs index dc3609032a..e8fa9d137f 100644 --- a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs +++ b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs @@ -205,7 +205,7 @@ runBlocks sql rootBlockCtx blks = (fakeBlockInfo, block', _finalBlockHandle) <- (throwIfNoHistory =<<) $ Checkpointer.readFrom logger cid sql fakeParentCreationTime parent $ - executeBlockTransaction parent block + Checkpointer.readPact5 "unexpected pact 5" $ executeBlockTransaction parent block let childBlockCtx = BlockCtx { _bctxParentCreationTime = fakeParentCreationTime , _bctxParentHash = Parent $ fst fakeBlockInfo @@ -221,7 +221,7 @@ runBlocks sql rootBlockCtx blks = , _bctxMinerReward = blockMinerReward (unwrapParent $ _rankedBlockHashHeight <$> parent) } _ <- Checkpointer.restoreAndSave logger cid sql - (NE.singleton (parentBlockCtx, \blockEnv -> do + (NE.singleton (parentBlockCtx, Checkpointer.Pact5RunnableBlock $ \blockEnv -> do blockHandle <- get (fakeBlockInfo', _blk, finalBlockHandle) <- liftIO $ executeBlockTransaction parent block blockEnv blockHandle @@ -246,27 +246,28 @@ assertBlock :: SQLiteEnv -> BlockCtx -> (BlockHash, BlockPayloadHash) -> DbBlock assertBlock sql blockCtx expectedBlockInfo blk = withVersion testVer $ do fakeNewBlockCtx <- Checkpointer.mkFakeParentCreationTime logger <- getTestLogger - hist <- Checkpointer.readFrom logger cid sql fakeNewBlockCtx (_bctxParentRankedBlockHash blockCtx) $ \blockEnv startHandle -> do - ((), _endHandle) <- doChainwebPactDbTransaction (_psBlockDbEnv blockEnv) startHandle Nothing $ \txdb _spv -> do - _ <- ignoreGas noInfo $ _pdbBeginTx txdb Transactional - blk' <- forM blk (runDbAction' txdb) - txLogs <- ignoreGas noInfo $ _pdbCommitTx txdb - forM_ blk' $ \case - DbRead _d _k (Pair expected actual) -> - assertEqual "read result" expected actual - DbWrite _wt _d _k _v (Pair expected actual) -> - assertEqual "write result" expected actual - DbKeys _d (Pair expected actual) -> - assertEqual "keys result" expected actual - DbSelect _d (Pair expected actual) -> - assertEqual "select result" expected actual - DbCreateTable _tn (Pair expected actual) -> - assertEqual "create table result" expected actual + hist <- Checkpointer.readFrom logger cid sql fakeNewBlockCtx (_bctxParentRankedBlockHash blockCtx) $ + Checkpointer.readPact5 "unexpected Pact 4" $ \blockEnv startHandle -> do + ((), _endHandle) <- doChainwebPactDbTransaction (_psBlockDbEnv blockEnv) startHandle Nothing $ \txdb _spv -> do + _ <- ignoreGas noInfo $ _pdbBeginTx txdb Transactional + blk' <- forM blk (runDbAction' txdb) + txLogs <- ignoreGas noInfo $ _pdbCommitTx txdb + forM_ blk' $ \case + DbRead _d _k (Pair expected actual) -> + assertEqual "read result" expected actual + DbWrite _wt _d _k _v (Pair expected actual) -> + assertEqual "write result" expected actual + DbKeys _d (Pair expected actual) -> + assertEqual "keys result" expected actual + DbSelect _d (Pair expected actual) -> + assertEqual "select result" expected actual + DbCreateTable _tn (Pair expected actual) -> + assertEqual "create table result" expected actual - actualBlockInfo <- - blockHeaderFromTxLogs (_bctxParentRankedBlockHash blockCtx) txLogs - assertEqual "block header" expectedBlockInfo actualBlockInfo - return () + actualBlockInfo <- + blockHeaderFromTxLogs (_bctxParentRankedBlockHash blockCtx) txLogs + assertEqual "block header" expectedBlockInfo actualBlockInfo + return () throwIfNoHistory hist tests :: TestTree @@ -280,7 +281,7 @@ tests = testGroup "Pact5 Checkpointer tests" fakeNewBlockCtx <- Checkpointer.mkFakeParentCreationTime ((), _handle) <- (throwIfNoHistory =<<) $ Checkpointer.readFrom logger cid sql fakeNewBlockCtx genesisParentRanked - $ \db blockHandle -> do + $ Checkpointer.readPact5 "unexpected Pact 4" $ \db blockHandle -> do doChainwebPactDbTransaction (_psBlockDbEnv db) blockHandle Nothing $ \txdb _spv -> Pact.Core.runPactDbRegression txdb return () @@ -296,7 +297,7 @@ tests = testGroup "Pact5 Checkpointer tests" _ <- Checkpointer.restoreAndSave logger cid sql $ ( NE.singleton (blockCtxOfEvaluationCtx cid (genesisEvalCtx cid), - \_ -> return ((), (view blockHash (genesisBlockHeader cid), genesisBlockPayloadHash cid))) + Checkpointer.Pact5RunnableBlock $ \_ -> return ((), (view blockHash (genesisBlockHeader cid), genesisBlockPayloadHash cid))) ) handle @_ @SomeException (\ex -> putStrLn (displayException ex) >> throw ex) diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index b7084194da..d4294d1c05 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -610,7 +610,8 @@ makeEmptyBlock Fixture{..} ph = do Pool.withResource (_psReadSqlPool serviceEnv) $ \roSql -> do (throwIfNoHistory =<<) $ Checkpointer.readFrom _fixtureLogger cid roSql (view blockCreationTime <$> ph) (view rankedBlockHash <$> ph) $ - \blockEnv initialBlockHandle -> PactService.makeEmptyBlock _fixtureLogger serviceEnv blockEnv initialBlockHandle + Checkpointer.readPact5 "unexpected Pact 4" $ \blockEnv initialBlockHandle -> + PactService.makeEmptyBlock _fixtureLogger serviceEnv blockEnv initialBlockHandle where cid = _chainId ph serviceEnv = _fixturePacts ^?! atChain cid @@ -620,7 +621,8 @@ continueBlock Fixture{..} bip = do Pool.withResource (_psReadSqlPool serviceEnv) $ \roSql -> do (throwIfNoHistory =<<) $ Checkpointer.readFrom _fixtureLogger cid roSql parentCreationTime parentRankedHash $ - \blockEnv _initialBlockHandle -> PactService.continueBlock _fixtureLogger serviceEnv (_psBlockDbEnv blockEnv) bip + Checkpointer.readPact5 "unexpected Pact 4" $ \blockEnv _initialBlockHandle -> + PactService.continueBlock _fixtureLogger serviceEnv (_psBlockDbEnv blockEnv) bip where parentCreationTime = (_bctxParentCreationTime $ _blockInProgressBlockCtx bip) parentRankedHash = (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx bip) diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index 08017cb3e3..87ac541d67 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -73,6 +73,7 @@ import Chainweb.Pact.Backend.Types import Control.Monad.State.Strict import Chainweb.Parent import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as PIN0 +import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer tests :: RocksDb -> TestTree tests baseRdb = testGroup "Pact5 TransactionExecTest" @@ -116,7 +117,7 @@ readFromAfterGenesis rdb act = runResourceT $ do throwIfNoHistory =<< readFrom logger cid writeSql fakeParentCreationTime (Parent (gh cid ^. rankedBlockHash)) - act + (Checkpointer.readPact5 "unexpected Pact 4" $ act) buyGasShouldTakeGasTokensFromTheTransactionSender :: RocksDb -> IO () buyGasShouldTakeGasTokensFromTheTransactionSender rdb = withVersion v $ readFromAfterGenesis rdb $ \blockEnv blockHandle -> diff --git a/test/unit/Chainweb/Test/Pact4/RewardsTest.hs b/test/unit/Chainweb/Test/Pact4/RewardsTest.hs index 236bf39381..d7c6a35cbd 100644 --- a/test/unit/Chainweb/Test/Pact4/RewardsTest.hs +++ b/test/unit/Chainweb/Test/Pact4/RewardsTest.hs @@ -25,7 +25,7 @@ tests = testGroup "Chainweb.Test.Pact4.RewardsTest" rewardsTest :: HasCallStack => TestTree rewardsTest = testCaseSteps "rewards" $ \step -> do - let k = _kda . minerRewardKda . blockMinerReward v + let k = _kda . minerRewardKda . withVersion v blockMinerReward step "block heights below initial threshold" let a = k 0 diff --git a/test/unit/Chainweb/Test/Pact4/SQLite.hs b/test/unit/Chainweb/Test/Pact4/SQLite.hs index d96f353dfb..3c1d84a5dc 100644 --- a/test/unit/Chainweb/Test/Pact4/SQLite.hs +++ b/test/unit/Chainweb/Test/Pact4/SQLite.hs @@ -42,12 +42,13 @@ import Test.Tasty.HUnit import Chainweb.Test.Utils import Chainweb.Pact.Backend.Types (SQLiteEnv) +import Chainweb.Pact.Backend.Utils (withSQLiteConnection) -- -------------------------------------------------------------------------- -- -- Tests tests :: TestTree -tests = withResourceT withInMemSQLiteResource $ \dbIO -> +tests = withResourceT (withSQLiteConnection ":memory:" []) $ \dbIO -> withResource' (dbIO >>= newMVar) $ \dbVarIO -> let run = runMsgTest dbVarIO [] runMonte = runMonteTest dbVarIO [] diff --git a/test/unit/Chainweb/Test/Pact4/TransactionTests.hs b/test/unit/Chainweb/Test/Pact4/TransactionTests.hs index 88334342bb..68cf9584c4 100644 --- a/test/unit/Chainweb/Test/Pact4/TransactionTests.hs +++ b/test/unit/Chainweb/Test/Pact4/TransactionTests.hs @@ -37,38 +37,28 @@ import qualified System.LogLevel as L -- internal pact modules -import Pact.Gas import Pact.Interpreter import Pact.Parse import Pact.Repl import Pact.Repl.Types import Pact.Types.Command -import qualified Pact.Types.Hash as H -import Pact.Types.PactValue import Pact.Types.RPC import Pact.Types.Runtime -import Pact.Types.SPV -- internal chainweb modules -import Chainweb.BlockCreationTime -import Chainweb.BlockHeader.Internal -import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Miner.Pact import Chainweb.Pact4.Templates import Chainweb.Pact4.TransactionExec -import qualified Chainweb.Pact4.Types as Pact4 -import Chainweb.Test.Utils -import Chainweb.Test.TestVersions -import Chainweb.Time import Chainweb.Utils import Chainweb.Version as V import Chainweb.Version.RecapDevelopment -import Chainweb.Version.Mainnet -import Chainweb.Test.Pact4.Utils import qualified Chainweb.Pact4.ModuleCache as Pact4 +import qualified Pact.Core.Guards as Pact5 +import qualified Data.Set as Set +import qualified Pact.Core.Names as Pact5 -- ---------------------------------------------------------------------- -- @@ -204,180 +194,10 @@ buildExecWithoutData = void $ buildExecParsedCode maxBound Nothing "(+ 1 1)" badMinerId :: MinerId badMinerId = MinerId "alpha\" (read-keyset \"miner-keyset\") 9999999.99)(coin.coinbase \"alpha" -minerKeys0 :: MinerKeys -minerKeys0 = MinerKeys $ mkKeySet - ["f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27"] - "default" - --- ---------------------------------------------------------------------- -- --- Vuln 792 fork tests - -testCoinbase797DateFix :: TestTree -testCoinbase797DateFix = testCaseSteps "testCoinbase791Fix" $ \step -> do - (pdb,mc) <- loadCC coinReplV1 - - step "pre-fork code injection succeeds, no enforced precompile" - - cmd <- buildExecParsedCode maxBound Nothing "(coin.get-balance \"tester01\")" - - doCoinbaseExploit pdb mc preForkHeight cmd False $ \case - Left _ -> assertFailure "local call to get-balance failed" - Right (PLiteral (LDecimal d)) - | d == 1000.1 -> return () - | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d - Right l -> assertFailure $ "wrong return type: " <> show l - - step "post-fork code injection fails, no enforced precompile" - - cmd' <- buildExecParsedCode maxBound Nothing - "(coin.get-balance \"tester01\\\" (read-keyset \\\"miner-keyset\\\") 1000.0)(coin.coinbase \\\"tester01\")" - - doCoinbaseExploit pdb mc postForkHeight cmd' False $ \case - Left _ -> assertFailure "local call to get-balance failed" - Right (PLiteral (LDecimal d)) - | d == 0.1 -> return () - | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d - Right l -> assertFailure $ "wrong return type: " <> show l - - step "pre-fork code injection fails, enforced precompile" - - doCoinbaseExploit pdb mc preForkHeight cmd' True $ \case - Left _ -> assertFailure "local call to get-balance failed" - Right (PLiteral (LDecimal d)) - | d == 0.2 -> return () - | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d - Right l -> assertFailure $ "wrong return type: " <> show l - - step "post-fork code injection fails, enforced precompile" - - doCoinbaseExploit pdb mc postForkHeight cmd' True $ \case - Left _ -> assertFailure "local call to get-balance failed" - Right (PLiteral (LDecimal d)) - | d == 0.3 -> return () - | otherwise -> assertFailure $ "miner balance is incorrect: " <> show d - Right l -> assertFailure $ "wrong return type: " <> show l - - where - doCoinbaseExploit pdb mc height localCmd precompile testResult = do - let ctx = Pact4.TxContext (mkTestParentHeader $ height - 1) noPublicMeta miner - - void $ applyCoinbase Mainnet01 logger pdb 0.1 ctx - (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled precompile) mc - - let h = H.toUntypedHash (H.hash "" :: H.PactHash) - tenv = TransactionEnv Transactional pdb logger Nothing noPublicData - noSPVSupport Nothing 0.0 (RequestKey h) 0 emptyExecutionConfig Nothing Nothing - txst = TransactionState mempty mempty 0 Nothing (_geGasModel freeGasEnv) mempty - - CommandResult _ _ (PactResult pr) _ _ _ _ _ <- evalTransactionM tenv txst $! - applyExec 0 defaultInterpreter localCmd [] [] h permissiveNamespacePolicy - - testResult pr - - miner = Miner - (MinerId "tester01\" (read-keyset \"miner-keyset\") 1000.0)(coin.coinbase \"tester01") - (MinerKeys $ mkKeySet ["b67e109352e8e33c8fe427715daad57d35d25d025914dd705b97db35b1bfbaa5"] "keys-all") - - preForkHeight = 121451 - postForkHeight = 121452 - - -- | someBlockHeader is a bit slow for the vuln797Fix to trigger. So, instead - -- of mining a full chain we fake the height. - -- - mkTestParentHeader :: BlockHeight -> ParentHeader - mkTestParentHeader h = ParentHeader $ someBlockHeader (slowForkingCpmTestVersion singleton) 10 - & blockHeight .~ h - -testCoinbaseEnforceFailure :: Assertion -testCoinbaseEnforceFailure = do - (pdb,mc) <- loadCC coinReplV4 - r <- tryAllSynchronous $ - applyCoinbase toyVersion logger pdb 0.1 - (Pact4.TxContext someParentHeader noPublicMeta badMiner) - (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled False) mc - case r of - Left e -> - if isInfixOf "CoinbaseFailure" (sshow e) then - return () - else assertFailure $ "Coinbase failed for unknown reason: " <> show e - Right _ -> assertFailure "Coinbase did not fail for bad miner id" - where - badMiner = Miner (MinerId "") (MinerKeys $ mkKeySet [] "<") - blockHeight' = 123 - someParentHeader = ParentHeader $ someTestVersionHeader - & blockHeight .~ blockHeight' - & blockCreationTime .~ BlockCreationTime [timeMicrosQQ| 2019-12-10T01:00:00.0 |] - -testCoinbaseUpgradeDevnet :: V.ChainId -> BlockHeight -> Assertion -testCoinbaseUpgradeDevnet cid upgradeHeight = - testUpgradeScript "test/pact/coin-and-devaccts.repl" cid upgradeHeight test - where - test (T2 cr mcm) = case (_crLogs cr,mcm) of - (_,Nothing) -> assertFailure "Expected module cache from successful upgrade" - (Nothing,_) -> assertFailure "Expected logs from successful upgrade" - (Just logs,_) -> - matchLogs (logResults logs) expectedResults - - expectedResults = - [ ("USER_coin_coin-table", "NoMiner", Just (Number 0.1)) - , ("SYS_modules","fungible-v2",Nothing) - , ("SYS_modules","coin",Nothing) - , ("USER_coin_coin-table","sender07",Just (Number 998662.3)) - , ("USER_coin_coin-table","sender09",Just (Number 998662.1)) - ] - -testTwentyChainDevnetUpgrades :: TestTree -testTwentyChainDevnetUpgrades = testCaseSteps "Test 20-chain Devnet upgrades" $ \step -> do - step "Check that 20-chain upgrades fire at block height 60" - testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) 60 test0 - - step "Check that 20-chain upgrades do not fire at block heights < 60 and > 60" - testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) (60 - 1) test1 - testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 0) (60 + 1) test1 - - step "Check that 20-chain upgrades do not fire at on other chains" - testUpgradeScript "test/pact/twenty-chain-upgrades.repl" (unsafeChainId 1) 60 test1 - - step "Check that 20-chain upgrades succeed even if e7f7 balance is insufficient" - testUpgradeScript "test/pact/twenty-chain-insufficient-bal.repl" (unsafeChainId 0) 60 test1 - where - test0 (T2 cr _) = case _crLogs cr of - Just logs -> matchLogs (logResults logs) - [ ("USER_coin_coin-table","NoMiner",Just (Number 0.1)) - , ( "USER_coin_coin-table" - , "e7f7634e925541f368b827ad5c72421905100f6205285a78c19d7b4a38711805" - , Just (Number 50.0) -- 100.0 tokens remediated - ) - ] - Nothing -> assertFailure "Expected logs from upgrade" - - test1 (T2 cr _) = case _crLogs cr of - Just logs -> matchLogs (logResults logs) - [ ("USER_coin_coin-table", "NoMiner", Just (Number 0.1)) - ] - Nothing -> assertFailure "Expected logs from upgrade" - --- ---------------------------------------------------------------------- -- --- Utils - -testUpgradeScript - :: FilePath - -> V.ChainId - -> BlockHeight - -> (T2 (CommandResult [TxLogJson]) (Maybe Pact4.ModuleCache) -> IO ()) - -> IO () -testUpgradeScript script cid bh test = do - (pdb, mc) <- loadScript script - r <- tryAllSynchronous $ applyCoinbase v logger pdb 0.1 (Pact4.TxContext parent noPublicMeta noMiner) - (EnforceCoinbaseFailure True) (Pact4.CoinbaseUsePrecompiled False) mc - case r of - Left e -> assertFailure $ "tx execution failed: " ++ show e - Right cr -> test cr - where - parent = ParentHeader $ someBlockHeader v bh - & blockChainwebVersion .~ _versionCode v - & blockChainId .~ cid - & blockHeight .~ pred bh +minerKeys0 :: MinerGuard +minerKeys0 = MinerGuard $ Pact5.GKeyset $ Pact5.KeySet + (Set.fromList [Pact5.PublicKeyText "f880a433d6e2a13a32b6169030f56245efdd8c1b8a5027e9ce98a88e886bef27"]) + (Pact5.CustomPredicate (fromJuste $ Pact5.parseParsedTyName "default")) matchLogs :: [(Text, Text, Maybe Value)] -> [(Text, Text, Maybe Value)] -> IO () matchLogs expectedResults actualResults diff --git a/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs b/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs index 16c0f69d1c..048569f834 100644 --- a/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs +++ b/test/unit/Chainweb/Test/Pact4/VerifierPluginTest.hs @@ -4,13 +4,9 @@ module Chainweb.Test.Pact4.VerifierPluginTest import Test.Tasty -import Chainweb.Storage.Table.RocksDB - -import qualified Chainweb.Test.Pact4.VerifierPluginTest.Transaction import qualified Chainweb.Test.Pact4.VerifierPluginTest.Unit -tests :: RocksDb -> TestTree -tests rdb = testGroup "Chainweb.Test.Pact4.VerifierPluginTest" +tests :: TestTree +tests = testGroup "Chainweb.Test.Pact4.VerifierPluginTest" [ Chainweb.Test.Pact4.VerifierPluginTest.Unit.tests - , Chainweb.Test.Pact4.VerifierPluginTest.Transaction.tests rdb ] diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index cc2312bc22..4cc8567394 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -63,6 +63,11 @@ import qualified Chainweb.Test.Pact.RemotePactTest -- import qualified Chainweb.Test.Pact.SPVTest import qualified Chainweb.Test.Pact.TransactionExecTest import qualified Chainweb.Test.Pact.TransactionTests +import qualified Chainweb.Test.Pact4.NoCoinbase +import qualified Chainweb.Test.Pact4.RewardsTest +import qualified Chainweb.Test.Pact4.SQLite +import qualified Chainweb.Test.Pact4.VerifierPluginTest +import qualified Chainweb.Test.Pact4.TransactionTests import qualified Chainweb.Test.RestAPI (tests) import qualified Chainweb.Test.Roundtrips (tests) -- import qualified Chainweb.Test.SPV (tests) @@ -124,10 +129,17 @@ suite rdb = , Chainweb.Test.RestAPI.tests rdb -- TODO: PP -- , testGroup "SPV" - -- [ Chainweb.Test.SPV.tests rdb - -- , Chainweb.Test.Pact4.SPV.tests rdb + -- [ Chainweb.Test.SPV.tests rdb + -- [ Chainweb.Test.Pact4.SPV.tests rdb -- , Chainweb.Test.SPV.EventProof.properties - -- ] + -- ] + , testGroup "Pact 4" + [ Chainweb.Test.Pact4.NoCoinbase.tests + , Chainweb.Test.Pact4.RewardsTest.tests + , Chainweb.Test.Pact4.SQLite.tests + , Chainweb.Test.Pact4.VerifierPluginTest.tests + , Chainweb.Test.Pact4.TransactionTests.tests + ] , Chainweb.Test.Mempool.InMem.tests , Chainweb.Test.Mempool.Sync.tests , Chainweb.Test.Mempool.RestAPI.tests From ac2574825e46342cb5900d5d50f9378bf4a11569 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 2 Sep 2025 18:35:54 -0400 Subject: [PATCH 322/378] Use the right payload client Change-Id: Id0000000b28cbb44848258b02c9fd9225387b787 --- src/Chainweb/Pact/PactService.hs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 92a6abe19b..829a3e38c2 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -117,6 +117,7 @@ import Prelude hiding (lookup) import System.LogLevel import Chainweb.Version.Guards (pact5) import Control.Concurrent.MVar (newMVar) +import Chainweb.Payload.RestAPI.Client (payloadClient) withPactService :: (Logger logger, CanPayloadCas tbl) @@ -133,10 +134,7 @@ withPactService -> GenesisConfig -> ResourceT IO (ServiceEnv tbl) withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config pactGenesis = do - SomeChainwebVersionT @v _ <- pure someChainwebVersionVal - SomeChainIdT @c _ <- pure $ someChainIdVal cid - let payloadClient = Rest.payloadClient @v @c @'PactProvider - payloadStore <- liftIO $ newPayloadStore http (logFunction chainwebLogger) pdb payloadClient + payloadStore <- liftIO $ newPayloadStore http (logFunction chainwebLogger) pdb (\rph -> payloadClient cid (_ranked rph) (Just $ rank rph)) (_, miningPayloadVar) <- allocate newEmptyTMVarIO (\v -> do refresherThread <- fmap (view _1) <$> atomically (tryReadTMVar v) From b6ae1041c063c3fc3f11fe8c13fbb96c0bff6bb6 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 3 Sep 2025 11:02:20 -0400 Subject: [PATCH 323/378] Move Chainweb.{Payload,Mempool} underneath Chainweb.Pact --- bench/Chainweb/MempoolBench.hs | 6 ++-- bench/Chainweb/Pact/Backend/ForkingBench.hs | 8 ++--- bench/Chainweb/Pact/Backend/PactService.hs | 8 ++--- bench/Chainweb/Utils/Bench.hs | 2 +- bench/JSONEncoding.hs | 2 +- chainweb.cabal | 34 +++++++++---------- cwtools/ea/Ea.hs | 4 +-- cwtools/header-dump/Main.hs | 6 ++-- cwtools/txstream/Main.hs | 4 +-- node/src/ChainwebNode.hs | 2 +- src/Chainweb/BlockHeader.hs | 2 +- .../Genesis/Development0Payload.hs | 2 +- .../Genesis/Development1to19Payload.hs | 2 +- .../Genesis/FastTimedCPM0Payload.hs | 2 +- .../Genesis/FastTimedCPM1to9Payload.hs | 2 +- .../Genesis/InstantTimedCPM0Payload.hs | 2 +- .../Genesis/InstantTimedCPM1to9Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet0Payload.hs | 2 +- .../Genesis/Mainnet10to19Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet1Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet2Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet3Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet4Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet5Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet6Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet7Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet8Payload.hs | 2 +- .../BlockHeader/Genesis/Mainnet9Payload.hs | 2 +- .../Pact53TransitionTimedCPM0Payload.hs | 2 +- .../Pact53TransitionTimedCPM1to9Payload.hs | 2 +- .../Genesis/Pact5InstantTimedCPM0Payload.hs | 2 +- .../Pact5InstantTimedCPM1to9Payload.hs | 2 +- .../QuirkedGasPact5InstantTimedCPM0Payload.hs | 2 +- ...irkedGasPact5InstantTimedCPM1to9Payload.hs | 2 +- .../Genesis/RecapDevelopment0Payload.hs | 2 +- .../Genesis/RecapDevelopment10to19Payload.hs | 2 +- .../Genesis/RecapDevelopment1to9Payload.hs | 2 +- .../BlockHeader/Genesis/Testnet040Payload.hs | 2 +- .../Genesis/Testnet041to19Payload.hs | 2 +- src/Chainweb/BlockHeaderDB/RestAPI.hs | 4 +-- src/Chainweb/Chainweb.hs | 4 +-- src/Chainweb/Chainweb/ChainResources.hs | 12 +++---- src/Chainweb/Chainweb/Configuration.hs | 4 +-- src/Chainweb/Chainweb/MempoolSyncClient.hs | 6 ++-- src/Chainweb/Miner/Miners.hs | 4 +-- src/Chainweb/Miner/Pact.hs | 2 +- src/Chainweb/Pact/Backend/Compaction.hs | 4 +-- src/Chainweb/{ => Pact}/Block.hs | 4 +-- src/Chainweb/{ => Pact}/Mempool/CurrentTxs.hs | 8 ++--- src/Chainweb/{ => Pact}/Mempool/InMem.hs | 10 +++--- .../Mempool/InMem/ValidatingConfig.hs | 8 ++--- src/Chainweb/{ => Pact}/Mempool/InMemTypes.hs | 6 ++-- src/Chainweb/{ => Pact}/Mempool/Mempool.hs | 2 +- src/Chainweb/{ => Pact}/Mempool/P2pConfig.hs | 5 ++- src/Chainweb/{ => Pact}/Mempool/RestAPI.hs | 4 +-- .../{ => Pact}/Mempool/RestAPI/Client.hs | 6 ++-- .../{ => Pact}/Mempool/RestAPI/Server.hs | 6 ++-- src/Chainweb/Pact/PactService.hs | 8 ++--- src/Chainweb/Pact/PactService/ExecBlock.hs | 8 ++--- .../Pact/PactService/Pact4/ExecBlock.hs | 6 ++-- src/Chainweb/{ => Pact}/Payload.hs | 4 +-- .../{ => Pact}/Payload/PayloadStore.hs | 6 ++-- .../Payload/PayloadStore/InMemory.hs | 8 ++--- .../Payload/PayloadStore/RocksDB.hs | 8 ++--- src/Chainweb/{ => Pact}/Payload/RestAPI.hs | 8 ++--- .../{ => Pact}/Payload/RestAPI/Client.hs | 8 ++--- .../{ => Pact}/Payload/RestAPI/Server.hs | 10 +++--- src/Chainweb/Pact/RestAPI/Server.hs | 6 ++-- src/Chainweb/Pact/SPV.hs | 2 +- src/Chainweb/Pact/Types.hs | 6 ++-- src/Chainweb/Pact/Utils.hs | 2 +- src/Chainweb/Pact4/SPV.hs | 4 +-- src/Chainweb/Pact4/TransactionExec.hs | 2 +- src/Chainweb/Pact4/Types.hs | 2 +- .../PayloadProvider/Initialization.hs | 2 +- src/Chainweb/PayloadProvider/P2P/RestAPI.hs | 2 +- .../PayloadProvider/P2P/RestAPI/Server.hs | 2 +- src/Chainweb/PayloadProvider/Pact.hs | 6 ++-- .../PayloadProvider/Pact/Configuration.hs | 6 ++-- src/Chainweb/PayloadProvider/Pact/Genesis.hs | 2 +- src/Chainweb/PayloadProvider/SPV.hs | 4 +-- src/Chainweb/RestAPI.hs | 6 ++-- src/Chainweb/SPV/Argument.hs | 2 +- src/Chainweb/SPV/CreateProof.hs | 4 +-- src/Chainweb/SPV/EventProof.hs | 4 +-- src/Chainweb/SPV/OutputProof.hs | 4 +-- src/Chainweb/SPV/VerifyProof.hs | 2 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 4 +-- src/Chainweb/Version.hs | 2 +- test/lib/Chainweb/Test/Cut.hs | 2 +- test/lib/Chainweb/Test/Cut/TestBlockDb.hs | 8 ++--- test/lib/Chainweb/Test/Orphans/Internal.hs | 6 ++-- test/lib/Chainweb/Test/Pact/Utils.hs | 8 ++--- test/lib/Chainweb/Test/Pact4/Utils.hs | 4 +-- test/lib/Chainweb/Test/RestAPI/Client_.hs | 4 +-- test/lib/Chainweb/Test/TestVersions.hs | 2 +- test/lib/Chainweb/Test/Utils.hs | 2 +- test/lib/Chainweb/Test/Utils/BlockHeader.hs | 2 +- test/unit/Chainweb/Test/CutDB.hs | 6 ++-- test/unit/Chainweb/Test/Mempool.hs | 2 +- test/unit/Chainweb/Test/Mempool/Consensus.hs | 6 ++-- test/unit/Chainweb/Test/Mempool/InMem.hs | 6 ++-- test/unit/Chainweb/Test/Mempool/RestAPI.hs | 10 +++--- test/unit/Chainweb/Test/Mempool/Sync.hs | 8 ++--- test/unit/Chainweb/Test/Misc.hs | 2 +- .../Chainweb/Test/Pact/CheckpointerTest.hs | 2 +- test/unit/Chainweb/Test/Pact/CutFixture.hs | 6 ++-- .../Chainweb/Test/Pact/PactServiceTest.hs | 6 ++-- .../unit/Chainweb/Test/Pact/RemotePactTest.hs | 4 +-- test/unit/Chainweb/Test/Pact/SPVTest.hs | 18 +++++----- test/unit/Chainweb/Test/Pact4/NoCoinbase.hs | 2 +- test/unit/Chainweb/Test/RestAPI.hs | 8 ++--- test/unit/Chainweb/Test/Roundtrips.hs | 8 ++--- test/unit/Chainweb/Test/SPV.hs | 6 ++-- test/unit/Chainweb/Test/SPV/EventProof.hs | 2 +- test/unit/Test/Chainweb/SPV/Argument.hs | 2 +- 116 files changed, 262 insertions(+), 263 deletions(-) rename src/Chainweb/{ => Pact}/Block.hs (85%) rename src/Chainweb/{ => Pact}/Mempool/CurrentTxs.hs (96%) rename src/Chainweb/{ => Pact}/Mempool/InMem.hs (99%) rename src/Chainweb/{ => Pact}/Mempool/InMem/ValidatingConfig.hs (94%) rename src/Chainweb/{ => Pact}/Mempool/InMemTypes.hs (97%) rename src/Chainweb/{ => Pact}/Mempool/Mempool.hs (99%) rename src/Chainweb/{ => Pact}/Mempool/P2pConfig.hs (97%) rename src/Chainweb/{ => Pact}/Mempool/RestAPI.hs (98%) rename src/Chainweb/{ => Pact}/Mempool/RestAPI/Client.hs (97%) rename src/Chainweb/{ => Pact}/Mempool/RestAPI/Server.hs (97%) rename src/Chainweb/{ => Pact}/Payload.hs (99%) rename src/Chainweb/{ => Pact}/Payload/PayloadStore.hs (99%) rename src/Chainweb/{ => Pact}/Payload/PayloadStore/InMemory.hs (93%) rename src/Chainweb/{ => Pact}/Payload/PayloadStore/RocksDB.hs (96%) rename src/Chainweb/{ => Pact}/Payload/RestAPI.hs (97%) rename src/Chainweb/{ => Pact}/Payload/RestAPI/Client.hs (95%) rename src/Chainweb/{ => Pact}/Payload/RestAPI/Server.hs (96%) diff --git a/bench/Chainweb/MempoolBench.hs b/bench/Chainweb/MempoolBench.hs index ae9b5cc0a9..c952139f21 100644 --- a/bench/Chainweb/MempoolBench.hs +++ b/bench/Chainweb/MempoolBench.hs @@ -16,9 +16,9 @@ import PropertyMatchers ((?)) import Chainweb.BlockHash import Chainweb.BlockHeight import Chainweb.Graph (singletonChainGraph) -import Chainweb.Mempool.Mempool qualified as Mempool -import Chainweb.Mempool.InMem qualified as InMem -import Chainweb.Mempool.InMemTypes qualified as InMem +import Chainweb.Pact.Mempool.Mempool qualified as Mempool +import Chainweb.Pact.Mempool.InMem qualified as InMem +import Chainweb.Pact.Mempool.InMemTypes qualified as InMem import Chainweb.Pact.Transaction import Chainweb.Test.TestVersions import Chainweb.Utils diff --git a/bench/Chainweb/Pact/Backend/ForkingBench.hs b/bench/Chainweb/Pact/Backend/ForkingBench.hs index e0d26bc38e..5ae7953d20 100644 --- a/bench/Chainweb/Pact/Backend/ForkingBench.hs +++ b/bench/Chainweb/Pact/Backend/ForkingBench.hs @@ -28,7 +28,7 @@ import Chainweb.BlockHeaderDB.Internal import Chainweb.ChainId import Chainweb.Graph import Chainweb.Logger -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.Mempool import Chainweb.Miner.Pact import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils @@ -38,9 +38,9 @@ import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Pact.Utils (toTxCreationTime) import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.InMemory +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Payload.PayloadStore.InMemory import Chainweb.Storage.Table.HashMap hiding (toList) import Chainweb.Storage.Table.RocksDB import Chainweb.Test.TestVersions (slowForkingCpmTestVersion) diff --git a/bench/Chainweb/Pact/Backend/PactService.hs b/bench/Chainweb/Pact/Backend/PactService.hs index 30c540ac91..5f779586f9 100644 --- a/bench/Chainweb/Pact/Backend/PactService.hs +++ b/bench/Chainweb/Pact/Backend/PactService.hs @@ -27,9 +27,9 @@ import Chainweb.ChainId import Chainweb.Chainweb import Chainweb.Cut import Chainweb.Graph (singletonChainGraph) -import Chainweb.Mempool.Consensus -import Chainweb.Mempool.InMem -import Chainweb.Mempool.Mempool (InsertType (..), MempoolBackend (..)) +import Chainweb.Pact.Mempool.Consensus +import Chainweb.Pact.Mempool.InMem +import Chainweb.Pact.Mempool.Mempool (InsertType (..), MempoolBackend (..)) import Chainweb.Miner.Pact import Chainweb.Pact.Backend.Types (SQLiteEnv) import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) @@ -40,7 +40,7 @@ import Chainweb.Pact.Service.PactInProcApi import Chainweb.Pact.Service.PactQueue import Chainweb.Pact.Types import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb(..), addTestBlockDb, getCutTestBlockDb, setCutTestBlockDb, getParentTestBlockDb, mkTestBlockDbIO) import Chainweb.Test.Pact.CmdBuilder diff --git a/bench/Chainweb/Utils/Bench.hs b/bench/Chainweb/Utils/Bench.hs index 5a78ded068..5e3e6417f2 100644 --- a/bench/Chainweb/Utils/Bench.hs +++ b/bench/Chainweb/Utils/Bench.hs @@ -23,7 +23,7 @@ import Database.SQLite3.Direct (Database(..)) import Chainweb.WebBlockHeaderDB (WebBlockHeaderDb) import Chainweb.Pact.Types (ServiceEnv) import Control.DeepSeq (NFData(..)) -import Chainweb.Mempool.Mempool (MempoolBackend) +import Chainweb.Pact.Mempool.Mempool (MempoolBackend) import Control.Monad.IO.Class (liftIO) import Data.Text.IO qualified as Text diff --git a/bench/JSONEncoding.hs b/bench/JSONEncoding.hs index 68b7071ae1..53db7d078f 100644 --- a/bench/JSONEncoding.hs +++ b/bench/JSONEncoding.hs @@ -40,7 +40,7 @@ import Test.QuickCheck import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.Chainweb.Configuration -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.RestAPI.NodeInfo import Chainweb.Test.Orphans.Internal import Chainweb.Utils.Paging diff --git a/chainweb.cabal b/chainweb.cabal index ec4a97b553..f599a4f3b9 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -123,7 +123,7 @@ library cc-options: -DSQLITE_CORE exposed-modules: , Chainweb.Backup - , Chainweb.Block + , Chainweb.Pact.Block , Chainweb.BlockCreationTime , Chainweb.BlockHash , Chainweb.BlockHeader @@ -195,15 +195,15 @@ library , Chainweb.Logger , Chainweb.Logging.Config , Chainweb.Logging.Miner - , Chainweb.Mempool.CurrentTxs - , Chainweb.Mempool.InMem - , Chainweb.Mempool.InMem.ValidatingConfig - , Chainweb.Mempool.InMemTypes - , Chainweb.Mempool.Mempool - , Chainweb.Mempool.P2pConfig - , Chainweb.Mempool.RestAPI - , Chainweb.Mempool.RestAPI.Client - , Chainweb.Mempool.RestAPI.Server + , Chainweb.Pact.Mempool.CurrentTxs + , Chainweb.Pact.Mempool.InMem + , Chainweb.Pact.Mempool.InMem.ValidatingConfig + , Chainweb.Pact.Mempool.InMemTypes + , Chainweb.Pact.Mempool.Mempool + , Chainweb.Pact.Mempool.P2pConfig + , Chainweb.Pact.Mempool.RestAPI + , Chainweb.Pact.Mempool.RestAPI.Client + , Chainweb.Pact.Mempool.RestAPI.Server , Chainweb.MerkleLogHash , Chainweb.MerkleUniverse , Chainweb.Miner.Config @@ -219,13 +219,13 @@ library , Chainweb.NodeVersion , Chainweb.OpenAPIValidation , Chainweb.Parent - , Chainweb.Payload - , Chainweb.Payload.PayloadStore - , Chainweb.Payload.PayloadStore.InMemory - , Chainweb.Payload.PayloadStore.RocksDB - , Chainweb.Payload.RestAPI - , Chainweb.Payload.RestAPI.Server - , Chainweb.Payload.RestAPI.Client + , Chainweb.Pact.Payload + , Chainweb.Pact.Payload.PayloadStore + , Chainweb.Pact.Payload.PayloadStore.InMemory + , Chainweb.Pact.Payload.PayloadStore.RocksDB + , Chainweb.Pact.Payload.RestAPI + , Chainweb.Pact.Payload.RestAPI.Server + , Chainweb.Pact.Payload.RestAPI.Client , Chainweb.PayloadProvider , Chainweb.PayloadProvider.EVM , Chainweb.PayloadProvider.EVM.EngineAPI diff --git a/cwtools/ea/Ea.hs b/cwtools/ea/Ea.hs index cb342797f0..52de4ab2ee 100644 --- a/cwtools/ea/Ea.hs +++ b/cwtools/ea/Ea.hs @@ -37,8 +37,8 @@ import Chainweb.Pact.Types (defaultPactServiceConfig, GenesisConfig(..)) import Chainweb.Pact.Utils (toTxCreationTime, emptyPayload) import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Validations (defaultMaxTTLSeconds) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore.InMemory +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore.InMemory import Chainweb.Storage.Table.RocksDB import Chainweb.Time import Chainweb.Utils diff --git a/cwtools/header-dump/Main.hs b/cwtools/header-dump/Main.hs index 3a23e6943b..d880c5c228 100644 --- a/cwtools/header-dump/Main.hs +++ b/cwtools/header-dump/Main.hs @@ -32,9 +32,9 @@ import Chainweb.BlockHeight import Chainweb.ChainValue import Chainweb.Logger import Chainweb.Miner.Pact -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Payload.PayloadStore.RocksDB import Chainweb.Storage.Table.RocksDB import Chainweb.Time import Chainweb.TreeDB hiding (key) diff --git a/cwtools/txstream/Main.hs b/cwtools/txstream/Main.hs index 7c0d18ade2..5957ae393f 100644 --- a/cwtools/txstream/Main.hs +++ b/cwtools/txstream/Main.hs @@ -33,8 +33,8 @@ import Chainweb.Cut.CutHashes import Chainweb.CutDB.RestAPI.Client import Chainweb.HostAddress import Chainweb.Logger -import Chainweb.Payload -import Chainweb.Payload.RestAPI.Client +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.RestAPI.Client import Chainweb.TreeDB import Chainweb.Utils import Chainweb.Version diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 727be169ee..2ca6ef1a4d 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -87,7 +87,7 @@ import Chainweb.CutDB import Chainweb.Logger import Chainweb.Logging.Config import Chainweb.Logging.Miner -import Chainweb.Mempool.InMemTypes (MempoolStats(..)) +import Chainweb.Pact.Mempool.InMemTypes (MempoolStats(..)) import Chainweb.Pact.Backend.DbCache (DbCacheStats) import Chainweb.Pact.RestAPI.Server (PactCmdLog(..)) import Chainweb.Pact.Types diff --git a/src/Chainweb/BlockHeader.hs b/src/Chainweb/BlockHeader.hs index acbf434544..0d76f245db 100644 --- a/src/Chainweb/BlockHeader.hs +++ b/src/Chainweb/BlockHeader.hs @@ -112,7 +112,7 @@ import Chainweb.BlockHeight (BlockHeight) import Chainweb.BlockWeight (BlockWeight) import Chainweb.Version (ChainwebVersionCode) import Chainweb.Parent -import Chainweb.Payload (BlockPayloadHash) +import Chainweb.Pact.Payload(BlockPayloadHash) import Chainweb.Difficulty (HashTarget) import Control.Lens (Getter) diff --git a/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs index 8df4b1c015..3de441c4f4 100644 --- a/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Development0Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs index 975d6d246a..1d20e93d79 100644 --- a/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Development1to19Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/FastTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/FastTimedCPM0Payload.hs index 9efcf84a89..34f5267383 100644 --- a/src/Chainweb/BlockHeader/Genesis/FastTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/FastTimedCPM0Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/FastTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/FastTimedCPM1to9Payload.hs index e2c81a7ec4..71bb47c927 100644 --- a/src/Chainweb/BlockHeader/Genesis/FastTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/FastTimedCPM1to9Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs index f61601e7b9..b92ada0a8b 100644 --- a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM0Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs index b151e35709..4dd374172f 100644 --- a/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/InstantTimedCPM1to9Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet0Payload.hs index 9544fe7b5d..63af083156 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet0Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet10to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet10to19Payload.hs index df3e96f250..9907b05fde 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet10to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet10to19Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet1Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet1Payload.hs index 657f0f5a0e..331e6af01e 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet1Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet1Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet2Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet2Payload.hs index 6a5e84b38c..6ec2b0394c 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet2Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet2Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet3Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet3Payload.hs index 4f68c246ff..d189beea8e 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet3Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet3Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet4Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet4Payload.hs index 299e5d658b..2a3e9ead55 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet4Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet4Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet5Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet5Payload.hs index 4d24ec216b..7b2abbb432 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet5Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet5Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet6Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet6Payload.hs index 4f10d85dd2..e9d20375de 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet6Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet6Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet7Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet7Payload.hs index 865680ede4..813c852132 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet7Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet7Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet8Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet8Payload.hs index e357f2f93e..12686e4dfd 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet8Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet8Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Mainnet9Payload.hs b/src/Chainweb/BlockHeader/Genesis/Mainnet9Payload.hs index f6f1194604..d56fd82e9d 100644 --- a/src/Chainweb/BlockHeader/Genesis/Mainnet9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Mainnet9Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM0Payload.hs index 97dc94fe71..40d7c11766 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM0Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM1to9Payload.hs index 814b74f61d..419d4df22c 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact53TransitionTimedCPM1to9Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs index 73223246a0..e30fdc2eba 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM0Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs index 731b94558e..8a5da12aeb 100644 --- a/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Pact5InstantTimedCPM1to9Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs index 7322e7f259..9ddc46c0c0 100644 --- a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM0Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs index 5675ade959..81841fbf2a 100644 --- a/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/QuirkedGasPact5InstantTimedCPM1to9Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/RecapDevelopment0Payload.hs b/src/Chainweb/BlockHeader/Genesis/RecapDevelopment0Payload.hs index f4a8eff163..a34b83f4f4 100644 --- a/src/Chainweb/BlockHeader/Genesis/RecapDevelopment0Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/RecapDevelopment0Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/RecapDevelopment10to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/RecapDevelopment10to19Payload.hs index 49fd274c64..d97e671387 100644 --- a/src/Chainweb/BlockHeader/Genesis/RecapDevelopment10to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/RecapDevelopment10to19Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/RecapDevelopment1to9Payload.hs b/src/Chainweb/BlockHeader/Genesis/RecapDevelopment1to9Payload.hs index b12622a559..7d837cfe84 100644 --- a/src/Chainweb/BlockHeader/Genesis/RecapDevelopment1to9Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/RecapDevelopment1to9Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Testnet040Payload.hs b/src/Chainweb/BlockHeader/Genesis/Testnet040Payload.hs index e7d9b18a7d..a3746400d8 100644 --- a/src/Chainweb/BlockHeader/Genesis/Testnet040Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Testnet040Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeader/Genesis/Testnet041to19Payload.hs b/src/Chainweb/BlockHeader/Genesis/Testnet041to19Payload.hs index 87d45eb48c..1ada66b82e 100644 --- a/src/Chainweb/BlockHeader/Genesis/Testnet041to19Payload.hs +++ b/src/Chainweb/BlockHeader/Genesis/Testnet041to19Payload.hs @@ -8,7 +8,7 @@ import qualified Data.Text as T import qualified Data.Vector as V import GHC.Stack -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils (unsafeFromText, toText) payloadBlock :: HasCallStack => PayloadWithOutputs diff --git a/src/Chainweb/BlockHeaderDB/RestAPI.hs b/src/Chainweb/BlockHeaderDB/RestAPI.hs index 39890aabb7..93d48fc033 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI.hs @@ -97,12 +97,12 @@ import Network.HTTP.Media ((//), (/:)) import Servant.API -- internal modules -import Chainweb.Block +import Chainweb.Pact.Block import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.ChainId -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils import Chainweb.TreeDB diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 08cd101920..e8d1eb3b0a 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -144,8 +144,8 @@ import Chainweb.Cut import Chainweb.CutDB import Chainweb.HostAddress import Chainweb.Logger -import Chainweb.Mempool.InMem.ValidatingConfig -import Chainweb.Mempool.Mempool qualified as Mempool +import Chainweb.Pact.Mempool.InMem.ValidatingConfig +import Chainweb.Pact.Mempool.Mempool qualified as Mempool import Chainweb.Miner.Config import Chainweb.OpenAPIValidation qualified as OpenAPIValidation import Chainweb.Pact.RestAPI.Server (PactServerData(..)) diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 9b353c1c13..c7da605a21 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -86,13 +86,13 @@ import Chainweb.ChainId import Chainweb.Chainweb.Configuration -- FIXME this module should not depend on the global configuration import Chainweb.Logger -import Chainweb.Mempool.InMem qualified as Mempool -import Chainweb.Mempool.InMem.ValidatingConfig qualified as Mempool +import Chainweb.Pact.Mempool.InMem qualified as Mempool +import Chainweb.Pact.Mempool.InMem.ValidatingConfig qualified as Mempool import Chainweb.Pact.PactService qualified as Pact import Chainweb.Pact.RestAPI qualified as Pact import Chainweb.Pact.RestAPI.Server qualified as Pact import Chainweb.Pact.Types -import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.Pact.Payload.PayloadStore.RocksDB import Chainweb.PayloadProvider import Chainweb.PayloadProvider.EVM import Chainweb.PayloadProvider.Minimal @@ -112,9 +112,9 @@ import Chainweb.Version import Chainweb.Version.Guards (maxBlockGasLimit) import Pact.Core.Gas qualified as Pact import Control.Monad (forM) -import Chainweb.Payload.PayloadStore (CanReadablePayloadCas) -import qualified Chainweb.Payload.RestAPI as PactPayload.RestAPI -import qualified Chainweb.Payload.RestAPI.Server as PactPayload.RestAPI.Server +import Chainweb.Pact.Payload.PayloadStore (CanReadablePayloadCas) +import qualified Chainweb.Pact.Payload.RestAPI as PactPayload.RestAPI +import qualified Chainweb.Pact.Payload.RestAPI.Server as PactPayload.RestAPI.Server -- -------------------------------------------------------------------------- -- -- Payload P2P Network Resources diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index afb38b3db9..6abf6dc233 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -84,7 +84,7 @@ import Chainweb.HostAddress import Chainweb.Miner.Config import Chainweb.Pact.Types (RewindLimit) import Chainweb.Pact.Types (defaultReorgLimit) -import Chainweb.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) +import Chainweb.Pact.Payload.RestAPI (PayloadBatchLimit(..), defaultServicePayloadBatchLimit) import Chainweb.PayloadProvider.EVM (EvmProviderConfig, pEvmProviderConfig, defaultEvmProviderConfig, validateEvmProviderConfig) import Chainweb.PayloadProvider.Minimal (MinimalProviderConfig, defaultMinimalProviderConfig, pMinimalProviderConfig) import Chainweb.PayloadProvider.Pact.Configuration @@ -406,7 +406,7 @@ data ServiceApiConfig = ServiceApiConfig , _serviceApiPayloadBatchLimit :: PayloadBatchLimit -- ^ maximum size for payload batches on the service API. Default is - -- 'Chainweb.Payload.RestAPI.defaultServicePayloadBatchLimit'. + -- 'Chainweb.Pact.Payload.RestAPI.defaultServicePayloadBatchLimit'. , _serviceApiConfigHeaderStream :: !Bool -- ^ whether to serve a header update stream endpoint. } diff --git a/src/Chainweb/Chainweb/MempoolSyncClient.hs b/src/Chainweb/Chainweb/MempoolSyncClient.hs index 73d632efcd..4ca077f25e 100644 --- a/src/Chainweb/Chainweb/MempoolSyncClient.hs +++ b/src/Chainweb/Chainweb/MempoolSyncClient.hs @@ -37,9 +37,9 @@ import Chainweb.ChainId import Chainweb.Chainweb.ChainResources import Chainweb.Chainweb.PeerResources import Chainweb.Logger -import qualified Chainweb.Mempool.Mempool as Mempool -import Chainweb.Mempool.P2pConfig -import qualified Chainweb.Mempool.RestAPI.Client as MPC +import qualified Chainweb.Pact.Mempool.Mempool as Mempool +import Chainweb.Pact.Mempool.P2pConfig +import qualified Chainweb.Pact.Mempool.RestAPI.Client as MPC import Chainweb.RestAPI.NetworkID import Chainweb.Utils import Chainweb.Version diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index d651dbf348..5a49bdeb98 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -60,8 +60,8 @@ import Chainweb.CutDB import Chainweb.Difficulty import Chainweb.Graph import Chainweb.Logger -import Chainweb.Mempool.Mempool -import Chainweb.Mempool.Mempool qualified as Mempool +import Chainweb.Pact.Mempool.Mempool +import Chainweb.Pact.Mempool.Mempool qualified as Mempool import Chainweb.Miner.Config (MinerCount(..)) import Chainweb.Miner.Coordinator import Chainweb.Miner.Core diff --git a/src/Chainweb/Miner/Pact.hs b/src/Chainweb/Miner/Pact.hs index 0ce293eccf..34a7be78b2 100644 --- a/src/Chainweb/Miner/Pact.hs +++ b/src/Chainweb/Miner/Pact.hs @@ -45,7 +45,7 @@ import Data.Text (Text) -- internal modules -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Utils import qualified Pact.JSON.Encode as J diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index 57009349be..a7ec9f0d6e 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -75,8 +75,8 @@ import Chainweb.Pact.Backend.ChainwebPactDb () import Chainweb.Pact.Backend.PactState import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils -import Chainweb.Payload.PayloadStore (addNewPayload, lookupPayloadWithHeight) -import Chainweb.Payload.PayloadStore.RocksDB (newPayloadDb) +import Chainweb.Pact.Payload.PayloadStore (addNewPayload, lookupPayloadWithHeight) +import Chainweb.Pact.Payload.PayloadStore.RocksDB (newPayloadDb) import Chainweb.Utils (sshow, fromText, toText, int) import Chainweb.Version (ChainId, HasVersion(..), withVersion, ChainwebVersion(..), chainIdToText) import Chainweb.Version.Registry (findKnownVersion) diff --git a/src/Chainweb/Block.hs b/src/Chainweb/Pact/Block.hs similarity index 85% rename from src/Chainweb/Block.hs rename to src/Chainweb/Pact/Block.hs index 2d1e1be8f9..b43f1b6524 100644 --- a/src/Chainweb/Block.hs +++ b/src/Chainweb/Pact/Block.hs @@ -1,11 +1,11 @@ -- | A type for "blocks" including their header and payload and outputs. -- This is only for REST APIs; we do not use the outputs otherwise. -module Chainweb.Block +module Chainweb.Pact.Block (Block(..)) where import Chainweb.BlockHeader -import Chainweb.Payload +import Chainweb.Pact.Payload data Block = Block { _blockHeader :: !BlockHeader diff --git a/src/Chainweb/Mempool/CurrentTxs.hs b/src/Chainweb/Pact/Mempool/CurrentTxs.hs similarity index 96% rename from src/Chainweb/Mempool/CurrentTxs.hs rename to src/Chainweb/Pact/Mempool/CurrentTxs.hs index a5e1a0c43f..ca1838b42e 100644 --- a/src/Chainweb/Mempool/CurrentTxs.hs +++ b/src/Chainweb/Pact/Mempool/CurrentTxs.hs @@ -4,7 +4,7 @@ {-# LANGUAGE ScopedTypeVariables #-} -- | --- Module: Chainweb.Mempool.CurrentTxs +-- Module: Chainweb.Pact.Mempool.CurrentTxs -- Copyright: Copyright © 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -24,7 +24,7 @@ -- pact service during new block validation once it has been included in another -- block. -- -module Chainweb.Mempool.CurrentTxs +module Chainweb.Pact.Mempool.CurrentTxs ( CurrentTxs(..) , newCurrentTxs , currentTxsSize @@ -49,8 +49,8 @@ import System.Random -- internal imports --- import Chainweb.Mempool.InMemTypes -import Chainweb.Mempool.Mempool +-- import Chainweb.Pact.Mempool.InMemTypes +import Chainweb.Pact.Mempool.Mempool import Chainweb.Time import Chainweb.Utils diff --git a/src/Chainweb/Mempool/InMem.hs b/src/Chainweb/Pact/Mempool/InMem.hs similarity index 99% rename from src/Chainweb/Mempool/InMem.hs rename to src/Chainweb/Pact/Mempool/InMem.hs index 0326d8c180..d2f65e7487 100644 --- a/src/Chainweb/Mempool/InMem.hs +++ b/src/Chainweb/Pact/Mempool/InMem.hs @@ -13,7 +13,7 @@ {-# LANGUAGE TypeFamilies #-} -- | A mock in-memory mempool backend that does not persist to disk. -module Chainweb.Mempool.InMem +module Chainweb.Pact.Mempool.InMem ( -- * Initialization functions startInMemoryMempoolTest @@ -72,9 +72,9 @@ import System.Random -- internal imports import Chainweb.Logger -import Chainweb.Mempool.CurrentTxs -import Chainweb.Mempool.InMemTypes -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.CurrentTxs +import Chainweb.Pact.Mempool.InMemTypes +import Chainweb.Pact.Mempool.Mempool import Chainweb.Pact.Validations (defaultMaxTTLSeconds, defaultMaxCoinDecimalPlaces) import Chainweb.Time import Chainweb.Utils @@ -173,7 +173,7 @@ withInMemoryMempool l cfg = do monitor m = do let lf = logFunction l logFunctionText l Debug "Initialized Mempool Monitor" - runForeverThrottled lf "Chainweb.Mempool.InMem.withInMemoryMempool.monitor" 10 (10 * mega) $ do + runForeverThrottled lf "Chainweb.Pact.Mempool.InMem.withInMemoryMempool.monitor" 10 (10 * mega) $ do stats <- getMempoolStats m logFunctionJson l Info stats approximateThreadDelay 60_000_000 {- 1 minute -} diff --git a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs b/src/Chainweb/Pact/Mempool/InMem/ValidatingConfig.hs similarity index 94% rename from src/Chainweb/Mempool/InMem/ValidatingConfig.hs rename to src/Chainweb/Pact/Mempool/InMem/ValidatingConfig.hs index ae7ce78ac2..6e9bbe324d 100644 --- a/src/Chainweb/Mempool/InMem/ValidatingConfig.hs +++ b/src/Chainweb/Pact/Mempool/InMem/ValidatingConfig.hs @@ -4,20 +4,20 @@ {-# LANGUAGE OverloadedStrings #-} -- | --- Module: Chainweb.Mempool.InMem.ValidatingConfig +-- Module: Chainweb.Pact.Mempool.InMem.ValidatingConfig -- Copyright: Copyright © 2025 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz -- Stability: experimental -- -module Chainweb.Mempool.InMem.ValidatingConfig +module Chainweb.Pact.Mempool.InMem.ValidatingConfig ( validatingMempoolConfig ) where import Control.Lens import Chainweb.ChainId -import Chainweb.Mempool.InMemTypes -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.InMemTypes +import Chainweb.Pact.Mempool.Mempool import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Validations import Chainweb.Utils diff --git a/src/Chainweb/Mempool/InMemTypes.hs b/src/Chainweb/Pact/Mempool/InMemTypes.hs similarity index 97% rename from src/Chainweb/Mempool/InMemTypes.hs rename to src/Chainweb/Pact/Mempool/InMemTypes.hs index d469077c0f..bd6e9a9b7b 100644 --- a/src/Chainweb/Mempool/InMemTypes.hs +++ b/src/Chainweb/Pact/Mempool/InMemTypes.hs @@ -9,7 +9,7 @@ {-# LANGUAGE TypeFamilies #-} -- | An in-memory mempool backend that does not persist to disk. -module Chainweb.Mempool.InMemTypes +module Chainweb.Pact.Mempool.InMemTypes ( InMemConfig(..) , InMemoryMempool(..) , InMemoryMempoolData(..) @@ -39,8 +39,8 @@ import Numeric.Natural -- internal imports -import Chainweb.Mempool.CurrentTxs -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.CurrentTxs +import Chainweb.Pact.Mempool.Mempool import Chainweb.Time (Micros(..), Time(..)) import Chainweb.Utils (T2) diff --git a/src/Chainweb/Mempool/Mempool.hs b/src/Chainweb/Pact/Mempool/Mempool.hs similarity index 99% rename from src/Chainweb/Mempool/Mempool.hs rename to src/Chainweb/Pact/Mempool/Mempool.hs index 5a99da7a4e..3ae72acc5f 100644 --- a/src/Chainweb/Mempool/Mempool.hs +++ b/src/Chainweb/Pact/Mempool/Mempool.hs @@ -55,7 +55,7 @@ -- -- The mempool API is defined as a record-of-functions in 'MempoolBackend'. -module Chainweb.Mempool.Mempool +module Chainweb.Pact.Mempool.Mempool ( MempoolBackend(..) , MempoolPreBlockCheck , TransactionConfig(..) diff --git a/src/Chainweb/Mempool/P2pConfig.hs b/src/Chainweb/Pact/Mempool/P2pConfig.hs similarity index 97% rename from src/Chainweb/Mempool/P2pConfig.hs rename to src/Chainweb/Pact/Mempool/P2pConfig.hs index e9c4a541b5..474fbe206c 100644 --- a/src/Chainweb/Mempool/P2pConfig.hs +++ b/src/Chainweb/Pact/Mempool/P2pConfig.hs @@ -7,7 +7,7 @@ {-# LANGUAGE TypeApplications #-} -- | --- Module: Chainweb.Mempool.P2pConfig +-- Module: Chainweb.Pact.Mempool.P2pConfig -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -15,7 +15,7 @@ -- -- P2p Configuration for the Mempool. -- -module Chainweb.Mempool.P2pConfig +module Chainweb.Pact.Mempool.P2pConfig ( MempoolP2pConfig(..) , mempoolP2pConfigMaxSessionCount , mempoolP2pConfigSessionTimeout @@ -86,4 +86,3 @@ pMempoolP2pConfig cid = id <*< mempoolP2pConfigPollInterval .:: textOption % prefixLongCid cid "mempool-p2p-poll-interval" <> helpCid cid "poll interval for synchronizing mempools in seconds" - diff --git a/src/Chainweb/Mempool/RestAPI.hs b/src/Chainweb/Pact/Mempool/RestAPI.hs similarity index 98% rename from src/Chainweb/Mempool/RestAPI.hs rename to src/Chainweb/Pact/Mempool/RestAPI.hs index 6f3d73c714..bd57843150 100644 --- a/src/Chainweb/Mempool/RestAPI.hs +++ b/src/Chainweb/Pact/Mempool/RestAPI.hs @@ -13,7 +13,7 @@ {-# OPTIONS_GHC -fno-warn-orphans #-} -module Chainweb.Mempool.RestAPI +module Chainweb.Pact.Mempool.RestAPI ( Mempool_(..) , SomeMempool(..) , someMempoolVal @@ -43,7 +43,7 @@ import Servant ------------------------------------------------------------------------------ import Chainweb.ChainId -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.Mempool import Chainweb.RestAPI.Utils import Chainweb.Version diff --git a/src/Chainweb/Mempool/RestAPI/Client.hs b/src/Chainweb/Pact/Mempool/RestAPI/Client.hs similarity index 97% rename from src/Chainweb/Mempool/RestAPI/Client.hs rename to src/Chainweb/Pact/Mempool/RestAPI/Client.hs index 245c336ffb..c9ad28ea0b 100644 --- a/src/Chainweb/Mempool/RestAPI/Client.hs +++ b/src/Chainweb/Pact/Mempool/RestAPI/Client.hs @@ -10,7 +10,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -module Chainweb.Mempool.RestAPI.Client +module Chainweb.Pact.Mempool.RestAPI.Client ( insertClient , getPendingClient , memberClient @@ -36,8 +36,8 @@ import Servant.Client ------------------------------------------------------------------------------ import Chainweb.ChainId -import Chainweb.Mempool.Mempool -import Chainweb.Mempool.RestAPI +import Chainweb.Pact.Mempool.Mempool +import Chainweb.Pact.Mempool.RestAPI import Chainweb.Utils import Chainweb.Version diff --git a/src/Chainweb/Mempool/RestAPI/Server.hs b/src/Chainweb/Pact/Mempool/RestAPI/Server.hs similarity index 97% rename from src/Chainweb/Mempool/RestAPI/Server.hs rename to src/Chainweb/Pact/Mempool/RestAPI/Server.hs index 0ad518b9be..5189e6dde1 100644 --- a/src/Chainweb/Mempool/RestAPI/Server.hs +++ b/src/Chainweb/Pact/Mempool/RestAPI/Server.hs @@ -5,7 +5,7 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE OverloadedStrings #-} -module Chainweb.Mempool.RestAPI.Server +module Chainweb.Pact.Mempool.RestAPI.Server ( mempoolServer , someMempoolServer , someMempoolServers @@ -25,8 +25,8 @@ import Servant ------------------------------------------------------------------------------ import Chainweb.ChainId -import Chainweb.Mempool.Mempool -import Chainweb.Mempool.RestAPI +import Chainweb.Pact.Mempool.Mempool +import Chainweb.Pact.Mempool.RestAPI import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils import Chainweb.Utils diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 829a3e38c2..ee7407836b 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -47,7 +47,7 @@ import Chainweb.ChainId import Chainweb.Core.Brief import Chainweb.Counter import Chainweb.Logger -import Chainweb.Mempool.Mempool as Mempool +import Chainweb.Pact.Mempool.Mempool as Mempool import Chainweb.Miner.Pact import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact @@ -64,8 +64,8 @@ import Chainweb.Pact.Types import Chainweb.Pact.Validations qualified as Pact import Chainweb.Pact4.Backend.ChainwebPactDb qualified as Pact4 import Chainweb.Parent -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.PayloadProvider import Chainweb.PayloadProvider.P2P import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest @@ -117,7 +117,7 @@ import Prelude hiding (lookup) import System.LogLevel import Chainweb.Version.Guards (pact5) import Control.Concurrent.MVar (newMVar) -import Chainweb.Payload.RestAPI.Client (payloadClient) +import Chainweb.Pact.Payload.RestAPI.Client (payloadClient) withPactService :: (Logger logger, CanPayloadCas tbl) diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index 0d77846137..f20b018927 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -24,13 +24,13 @@ module Chainweb.Pact.PactService.ExecBlock ) where import Chainweb.Logger -import Chainweb.Mempool.Mempool(BlockFill (..), pactRequestKeyToTransactionHash, InsertError (..)) +import Chainweb.Pact.Mempool.Mempool(BlockFill (..), pactRequestKeyToTransactionHash, InsertError (..)) import Chainweb.Miner.Pact import Chainweb.Pact.Types import Chainweb.Pact.Transaction import Chainweb.Pact.TransactionExec -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Time import Chainweb.Utils import Chainweb.Version @@ -78,7 +78,7 @@ import qualified Data.List.NonEmpty as NEL import qualified Pact.Core.Errors as Pact import qualified Pact.Core.Evaluate as Pact import qualified Pact.Core.ChainData as Pact -import qualified Chainweb.Payload as Chainweb +import qualified Chainweb.Pact.Payload as Chainweb import qualified Chainweb.Pact.Types as Pact runCoinbase diff --git a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs index 0e9af5cadc..0f87997552 100644 --- a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs @@ -78,7 +78,7 @@ import qualified Pact.Types.SPV as Pact4 import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger -import Chainweb.Mempool.Mempool as Mempool +import Chainweb.Pact.Mempool.Mempool as Mempool import Chainweb.MinerReward import Chainweb.Miner.Pact @@ -88,8 +88,8 @@ import Chainweb.Pact4.NoCoinbase import qualified Chainweb.Pact4.Transaction as Pact4 import qualified Chainweb.Pact4.TransactionExec as Pact4 import qualified Chainweb.Pact4.Validations as Pact4 -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Time import Chainweb.Utils hiding (check) import Chainweb.Version diff --git a/src/Chainweb/Payload.hs b/src/Chainweb/Pact/Payload.hs similarity index 99% rename from src/Chainweb/Payload.hs rename to src/Chainweb/Pact/Payload.hs index acb6a2f101..09d568468d 100644 --- a/src/Chainweb/Payload.hs +++ b/src/Chainweb/Pact/Payload.hs @@ -19,7 +19,7 @@ {-# LANGUAGE UndecidableInstances #-} -- | --- Module: Chainweb.Payload +-- Module: Chainweb.Pact.Payload -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -31,7 +31,7 @@ -- depend on any Pact data structure. The reason for this is to allow changes to -- Pact without breaking the Merkle tree. -- -module Chainweb.Payload +module Chainweb.Pact.Payload ( -- * Block Chain Data diff --git a/src/Chainweb/Payload/PayloadStore.hs b/src/Chainweb/Pact/Payload/PayloadStore.hs similarity index 99% rename from src/Chainweb/Payload/PayloadStore.hs rename to src/Chainweb/Pact/Payload/PayloadStore.hs index 0272b63f19..9119018258 100644 --- a/src/Chainweb/Payload/PayloadStore.hs +++ b/src/Chainweb/Pact/Payload/PayloadStore.hs @@ -17,13 +17,13 @@ {-# LANGUAGE InstanceSigs #-} -- | --- Module: Chainweb.Payload.PayloadStore +-- Module: Chainweb.Pact.Payload.PayloadStore -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz -- Stability: experimental -- -module Chainweb.Payload.PayloadStore +module Chainweb.Pact.Payload.PayloadStore ( -- * Exceptions PayloadNotFoundException @@ -88,7 +88,7 @@ import GHC.Generics import Chainweb.ChainId import Chainweb.Crypto.MerkleLog import Chainweb.MerkleUniverse -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Version import Chainweb.Storage.Table diff --git a/src/Chainweb/Payload/PayloadStore/InMemory.hs b/src/Chainweb/Pact/Payload/PayloadStore/InMemory.hs similarity index 93% rename from src/Chainweb/Payload/PayloadStore/InMemory.hs rename to src/Chainweb/Pact/Payload/PayloadStore/InMemory.hs index df3b3753f6..9589365316 100644 --- a/src/Chainweb/Payload/PayloadStore/InMemory.hs +++ b/src/Chainweb/Pact/Payload/PayloadStore/InMemory.hs @@ -1,7 +1,7 @@ {-# LANGUAGE ScopedTypeVariables #-} -- | --- Module: Chainweb.Payload.PayloadStore.InMemory +-- Module: Chainweb.Pact.Payload.PayloadStore.InMemory -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -10,7 +10,7 @@ -- An in-memory block payload store. -- -- TODO: move to tests -module Chainweb.Payload.PayloadStore.InMemory +module Chainweb.Pact.Payload.PayloadStore.InMemory ( newTransactionDb , newPayloadDb ) where @@ -18,8 +18,8 @@ module Chainweb.Payload.PayloadStore.InMemory -- internal modules import Chainweb.BlockHeight -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Storage.Table import Chainweb.Storage.Table.HashMap(HashMapTable) import qualified Chainweb.Storage.Table.HashMap as HashMapTable diff --git a/src/Chainweb/Payload/PayloadStore/RocksDB.hs b/src/Chainweb/Pact/Payload/PayloadStore/RocksDB.hs similarity index 96% rename from src/Chainweb/Payload/PayloadStore/RocksDB.hs rename to src/Chainweb/Pact/Payload/PayloadStore/RocksDB.hs index 252e51b6c8..6e6c94c0d7 100644 --- a/src/Chainweb/Payload/PayloadStore/RocksDB.hs +++ b/src/Chainweb/Pact/Payload/PayloadStore/RocksDB.hs @@ -2,7 +2,7 @@ {-# LANGUAGE ScopedTypeVariables #-} -- | --- Module: Chainweb.Payload.PayloadStore.RocksDB +-- Module: Chainweb.Pact.Payload.PayloadStore.RocksDB -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -10,7 +10,7 @@ -- -- Content addressable block payload store that uses RocksDB as storage backend. -- -module Chainweb.Payload.PayloadStore.RocksDB +module Chainweb.Pact.Payload.PayloadStore.RocksDB ( newTransactionDb , newPayloadDb ) where @@ -18,8 +18,8 @@ module Chainweb.Payload.PayloadStore.RocksDB -- internal modules import Chainweb.BlockHeight -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Utils hiding (Codec) import Chainweb.Utils.Serialization diff --git a/src/Chainweb/Payload/RestAPI.hs b/src/Chainweb/Pact/Payload/RestAPI.hs similarity index 97% rename from src/Chainweb/Payload/RestAPI.hs rename to src/Chainweb/Pact/Payload/RestAPI.hs index 63715d91b8..dc5b6eec76 100644 --- a/src/Chainweb/Payload/RestAPI.hs +++ b/src/Chainweb/Pact/Payload/RestAPI.hs @@ -9,7 +9,7 @@ {-# LANGUAGE TypeOperators #-} -- | --- Module: Chainweb.Payload.RestAPI +-- Module: Chainweb.Pact.Payload.RestAPI -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -17,7 +17,7 @@ -- -- The Block Payload REST API -- -module Chainweb.Payload.RestAPI +module Chainweb.Pact.Payload.RestAPI ( -- * Batch size limits PayloadBatchLimit(..) @@ -69,8 +69,8 @@ import Servant.API import Chainweb.BlockHeader import Chainweb.ChainId -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils import Chainweb.Version diff --git a/src/Chainweb/Payload/RestAPI/Client.hs b/src/Chainweb/Pact/Payload/RestAPI/Client.hs similarity index 95% rename from src/Chainweb/Payload/RestAPI/Client.hs rename to src/Chainweb/Pact/Payload/RestAPI/Client.hs index 98b922cd1b..4d67864ca0 100644 --- a/src/Chainweb/Payload/RestAPI/Client.hs +++ b/src/Chainweb/Pact/Payload/RestAPI/Client.hs @@ -5,7 +5,7 @@ {-# LANGUAGE TypeApplications #-} -- | --- Module: Chainweb.Payload.RestAPI.Client +-- Module: Chainweb.Pact.Payload.RestAPI.Client -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -13,7 +13,7 @@ -- -- Client implementation of the block payload REST API -- -module Chainweb.Payload.RestAPI.Client +module Chainweb.Pact.Payload.RestAPI.Client ( payloadClient , payloadBatchClient , outputsClient @@ -30,8 +30,8 @@ import Servant.Client import Chainweb.BlockHeader import Chainweb.ChainId -import Chainweb.Payload -import Chainweb.Payload.RestAPI +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.RestAPI import Chainweb.RestAPI.Orphans () import Chainweb.Version import Chainweb.BlockHeight (BlockHeight) diff --git a/src/Chainweb/Payload/RestAPI/Server.hs b/src/Chainweb/Pact/Payload/RestAPI/Server.hs similarity index 96% rename from src/Chainweb/Payload/RestAPI/Server.hs rename to src/Chainweb/Pact/Payload/RestAPI/Server.hs index 48a36e73e9..68cc7d4965 100644 --- a/src/Chainweb/Payload/RestAPI/Server.hs +++ b/src/Chainweb/Pact/Payload/RestAPI/Server.hs @@ -9,7 +9,7 @@ {-# LANGUAGE TypeFamilies #-} -- | --- Module: Chainweb.Payload.RestAPI.Server +-- Module: Chainweb.Pact.Payload.RestAPI.Server -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz @@ -17,7 +17,7 @@ -- -- Server implementation of the block payload REST API -- -module Chainweb.Payload.RestAPI.Server +module Chainweb.Pact.Payload.RestAPI.Server ( somePayloadServer , somePayloadServers @@ -46,9 +46,9 @@ import Servant import Chainweb.BlockHeader import Chainweb.ChainId -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.RestAPI +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Payload.RestAPI import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils import Chainweb.Utils (int) diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index 19b6eab350..73edabc196 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -98,15 +98,15 @@ import Chainweb.Cut import qualified Chainweb.CutDB as CutDB import Chainweb.Graph import Chainweb.Logger -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.Mempool (InsertError(..), InsertType(..), MempoolBackend(..), TransactionHash(..), pactRequestKeyToTransactionHash) import Chainweb.Pact.RestAPI import Chainweb.Pact.RestAPI.EthSpv import Chainweb.Pact.RestAPI.SPV import Chainweb.Pact.Types import Chainweb.Pact.SPV qualified as SPV -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils import Chainweb.SPV (SpvException(..)) diff --git a/src/Chainweb/Pact/SPV.hs b/src/Chainweb/Pact/SPV.hs index c8ab2c7fe6..ac6bde93d2 100644 --- a/src/Chainweb/Pact/SPV.hs +++ b/src/Chainweb/Pact/SPV.hs @@ -28,7 +28,7 @@ import Chainweb.Crypto.MerkleLog import Chainweb.MerkleUniverse import Chainweb.Pact.Backend.Types import Chainweb.Parent -import Chainweb.Payload (TransactionOutput(..)) +import Chainweb.Pact.Payload(TransactionOutput(..)) import Chainweb.SPV (TransactionOutputProof(..), outputProofChainId) import Chainweb.SPV.VerifyProof (runTransactionOutputProof, checkProofAndExtractOutput) import Chainweb.Utils (decodeB64UrlNoPaddingText, unlessM) diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index b9b66176d6..c8c38830a1 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -171,14 +171,14 @@ import Chainweb.BlockHeader import Chainweb.BlockPayloadHash import Chainweb.Counter import Chainweb.Logger -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.Mempool import Chainweb.Miner.Pact (Miner, toMinerData, noMiner) import Chainweb.Pact.Backend.ChainwebPactDb import Chainweb.Pact.Backend.Types import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact4.ModuleCache -import Chainweb.Payload qualified as Chainweb -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload qualified as Chainweb +import Chainweb.Pact.Payload.PayloadStore import Chainweb.PayloadProvider.P2P import Chainweb.Storage.Table.Map import Chainweb.Time diff --git a/src/Chainweb/Pact/Utils.hs b/src/Chainweb/Pact/Utils.hs index fb7cf348a7..6551c842bf 100644 --- a/src/Chainweb/Pact/Utils.hs +++ b/src/Chainweb/Pact/Utils.hs @@ -45,7 +45,7 @@ import qualified Pact.JSON.Encode as J import Chainweb.ChainId import Chainweb.Miner.Pact -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Time import qualified Pact.Core.ChainData as P import qualified Pact.Core.Guards as P diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs index 1b3643990a..5d59b7bf91 100644 --- a/src/Chainweb/Pact4/SPV.hs +++ b/src/Chainweb/Pact4/SPV.hs @@ -67,8 +67,8 @@ import Chainweb.BlockHeight import Chainweb.Pact.Backend.Types (HeaderOracle) import Chainweb.Pact.Utils (aeson) import Chainweb.Pact4.Types(internalError) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.SPV import Chainweb.SPV.VerifyProof import Chainweb.TreeDB diff --git a/src/Chainweb/Pact4/TransactionExec.hs b/src/Chainweb/Pact4/TransactionExec.hs index 77bb46b68e..e9c8f671a0 100644 --- a/src/Chainweb/Pact4/TransactionExec.hs +++ b/src/Chainweb/Pact4/TransactionExec.hs @@ -144,7 +144,7 @@ import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger import qualified Chainweb.ChainId as Chainweb -import Chainweb.Mempool.Mempool (pactRequestKeyToTransactionHash) +import Chainweb.Pact.Mempool.Mempool (pactRequestKeyToTransactionHash) import Chainweb.Miner.Pact import Chainweb.Pact4.Templates import Chainweb.Pact4.Types diff --git a/src/Chainweb/Pact4/Types.hs b/src/Chainweb/Pact4/Types.hs index f86495d3a9..737d01929b 100644 --- a/src/Chainweb/Pact4/Types.hs +++ b/src/Chainweb/Pact4/Types.hs @@ -100,7 +100,7 @@ import Chainweb.Parent import System.LogLevel import qualified Data.Text as T import qualified Data.Text.Encoding as T -import Chainweb.Payload (PayloadWithOutputs, newBlockOutputs, blockPayload, payloadData, payloadWithOutputs, newBlockTransactions, Transaction (..), TransactionOutput (..), CoinbaseOutput (..)) +import Chainweb.Pact.Payload(PayloadWithOutputs, newBlockOutputs, blockPayload, payloadData, payloadWithOutputs, newBlockTransactions, Transaction (..), TransactionOutput (..), CoinbaseOutput (..)) import Chainweb.Pact.Types (Transactions(..), BlockCtx, guardCtx) import qualified Data.ByteString.Short as SB diff --git a/src/Chainweb/PayloadProvider/Initialization.hs b/src/Chainweb/PayloadProvider/Initialization.hs index f2a7a2fef3..6aab9f4cae 100644 --- a/src/Chainweb/PayloadProvider/Initialization.hs +++ b/src/Chainweb/PayloadProvider/Initialization.hs @@ -13,7 +13,7 @@ module Chainweb.PayloadProvider.Initialization import Chainweb.ChainId import Chainweb.Version -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Pact.Types (MemPoolAccess) import Data.Text qualified as T diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs index 37271cd577..7ba9abcd55 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs @@ -55,7 +55,7 @@ import Chainweb.BlockHeaderDB.RestAPI () import Chainweb.BlockHeight import Chainweb.BlockPayloadHash import Chainweb.ChainId -import Chainweb.Payload qualified as Pact +import Chainweb.Pact.Payload qualified as Pact import Chainweb.PayloadProvider.EVM.ExecutionPayload qualified as EVM import Chainweb.PayloadProvider.Minimal.Payload qualified as Minimal import Chainweb.Ranked diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI/Server.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI/Server.hs index 1125a87088..5a3e836627 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI/Server.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI/Server.hs @@ -10,7 +10,7 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -- | --- Module: Chainweb.Payload.RestAPI.Server +-- Module: Chainweb.Pact.Payload.RestAPI.Server -- Copyright: Copyright © 2018 - 2020 Kadena LLC. -- License: MIT -- Maintainer: Lars Kuhtz diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 5fe5d98b1c..8b3cd58856 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -30,15 +30,15 @@ import qualified Data.Vector as V import Chainweb.ChainId import Chainweb.Counter import Chainweb.Logger -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.Mempool import Chainweb.MerkleUniverse import qualified Chainweb.MinerReward as MinerReward import Chainweb.Pact.Backend.Utils import qualified Chainweb.Pact.PactService as PactService import qualified Chainweb.Pact.Transaction as Pact import Chainweb.Pact.Types -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.PayloadProvider import Chainweb.Utils import Chainweb.Version diff --git a/src/Chainweb/PayloadProvider/Pact/Configuration.hs b/src/Chainweb/PayloadProvider/Pact/Configuration.hs index 247f7a221c..3fbd644f54 100644 --- a/src/Chainweb/PayloadProvider/Pact/Configuration.hs +++ b/src/Chainweb/PayloadProvider/Pact/Configuration.hs @@ -30,11 +30,11 @@ module Chainweb.PayloadProvider.Pact.Configuration , invalidMiner ) where -import Chainweb.Mempool.Mempool qualified as Mempool -import Chainweb.Mempool.P2pConfig +import Chainweb.Pact.Mempool.Mempool qualified as Mempool +import Chainweb.Pact.Mempool.P2pConfig import Chainweb.Miner.Pact (Miner(..), MinerGuard(..), MinerId(..)) import Chainweb.Pact.Types (defaultPreInsertCheckTimeout) -import Chainweb.Payload (PayloadWithOutputs) +import Chainweb.Pact.Payload(PayloadWithOutputs) import Chainweb.Time hiding (second) import Chainweb.Utils import Chainweb.Version diff --git a/src/Chainweb/PayloadProvider/Pact/Genesis.hs b/src/Chainweb/PayloadProvider/Pact/Genesis.hs index 762cb37214..ac1013d445 100644 --- a/src/Chainweb/PayloadProvider/Pact/Genesis.hs +++ b/src/Chainweb/PayloadProvider/Pact/Genesis.hs @@ -17,7 +17,7 @@ import Chainweb.Version.EvmDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.RecapDevelopment import Chainweb.Version.Testnet04 -import Chainweb.Payload +import Chainweb.Pact.Payload import Control.Lens import Data.HashMap.Strict qualified as HM import GHC.Stack diff --git a/src/Chainweb/PayloadProvider/SPV.hs b/src/Chainweb/PayloadProvider/SPV.hs index 98afb57ba7..cd10efa419 100644 --- a/src/Chainweb/PayloadProvider/SPV.hs +++ b/src/Chainweb/PayloadProvider/SPV.hs @@ -45,7 +45,7 @@ import Chainweb.BlockPayloadHash import Chainweb.Crypto.MerkleLog import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.PayloadProvider.EVM.Header ({- IsMerkleLogEntry instances -}) import Chainweb.PayloadProvider.EVM.Receipt qualified as EVM import Control.Exception @@ -583,7 +583,7 @@ validateTrieProof p = case E.validateProof p of -- import Chainweb.Utils -- import Chainweb.Crypto.MerkleLog -- import Chainweb.MerkleUniverse --- import Chainweb.Payload +-- import Chainweb.Pact.Payload -- import Chainweb.BlockHeader -- import Data.MerkleLog qualified as ML -- import Control.Lens diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index 67c0ee50da..48f8453d8a 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -81,12 +81,12 @@ import Chainweb.CutDB import Chainweb.CutDB.RestAPI.Server import Chainweb.HostAddress import Chainweb.Logger (Logger) -import Chainweb.Mempool.Mempool (MempoolBackend) -import qualified Chainweb.Mempool.RestAPI.Server as Mempool +import Chainweb.Pact.Mempool.Mempool (MempoolBackend) +import qualified Chainweb.Pact.Mempool.RestAPI.Server as Mempool import qualified Chainweb.Miner.RestAPI.Server as Mining -- import qualified Chainweb.Pact.RestAPI.Server as PactAPI import qualified Chainweb.Pact.Transaction as Pact -import Chainweb.Payload.RestAPI +import Chainweb.Pact.Payload.RestAPI import Chainweb.RestAPI.Backup import Chainweb.RestAPI.Config import Chainweb.RestAPI.Health diff --git a/src/Chainweb/SPV/Argument.hs b/src/Chainweb/SPV/Argument.hs index 34d6a5e5e5..8505fb910c 100644 --- a/src/Chainweb/SPV/Argument.hs +++ b/src/Chainweb/SPV/Argument.hs @@ -37,7 +37,7 @@ import Chainweb.BlockPayloadHash import Chainweb.Crypto.MerkleLog import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.PayloadProvider.EVM.Header ({- IsMerkleLogEntry instances -}) import Chainweb.PayloadProvider.EVM.Receipt qualified as EVM import Control.Exception diff --git a/src/Chainweb/SPV/CreateProof.hs b/src/Chainweb/SPV/CreateProof.hs index 3230fced30..dab38b667c 100644 --- a/src/Chainweb/SPV/CreateProof.hs +++ b/src/Chainweb/SPV/CreateProof.hs @@ -44,8 +44,8 @@ import Chainweb.CutDB import Chainweb.Graph import Chainweb.MerkleUniverse import Chainweb.Parent -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.SPV import Chainweb.TreeDB import Chainweb.Utils diff --git a/src/Chainweb/SPV/EventProof.hs b/src/Chainweb/SPV/EventProof.hs index 002e56f6ca..82bb5d1824 100644 --- a/src/Chainweb/SPV/EventProof.hs +++ b/src/Chainweb/SPV/EventProof.hs @@ -134,8 +134,8 @@ import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.SPV import Chainweb.SPV.PayloadProof import Chainweb.TreeDB hiding (entries, root) diff --git a/src/Chainweb/SPV/OutputProof.hs b/src/Chainweb/SPV/OutputProof.hs index 804c038440..17e0f0e64c 100644 --- a/src/Chainweb/SPV/OutputProof.hs +++ b/src/Chainweb/SPV/OutputProof.hs @@ -57,8 +57,8 @@ import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.Crypto.MerkleLog import Chainweb.MerkleUniverse -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.SPV import Chainweb.SPV.PayloadProof import Chainweb.TreeDB diff --git a/src/Chainweb/SPV/VerifyProof.hs b/src/Chainweb/SPV/VerifyProof.hs index 13c89c07af..23433dbebd 100644 --- a/src/Chainweb/SPV/VerifyProof.hs +++ b/src/Chainweb/SPV/VerifyProof.hs @@ -32,7 +32,7 @@ import Chainweb.Crypto.MerkleLog (proofSubject) import Chainweb.CutDB import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.SPV import Chainweb.Version import Chainweb.Pact.Backend.Types (HeaderOracle (..)) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index b8da903df3..e974356e60 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -52,8 +52,8 @@ import Chainweb.ChainId import Chainweb.ChainValue import Chainweb.Difficulty (WindowWidth(..)) import Chainweb.MinerReward (blockMinerReward) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.PayloadProvider import Chainweb.Ranked import Chainweb.Storage.Table diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index c88502eb59..87413eb617 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -183,7 +183,7 @@ import Chainweb.Difficulty import Chainweb.Graph import Chainweb.HostAddress import Chainweb.MerkleUniverse -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Utils import Chainweb.Utils.Rule diff --git a/test/lib/Chainweb/Test/Cut.hs b/test/lib/Chainweb/Test/Cut.hs index 3f505309d8..c8298d022c 100644 --- a/test/lib/Chainweb/Test/Cut.hs +++ b/test/lib/Chainweb/Test/Cut.hs @@ -100,7 +100,7 @@ import Chainweb.ChainValue import Chainweb.Cut import Chainweb.Cut.Create import Chainweb.Graph -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Parent import Chainweb.Test.TestVersions import Chainweb.Test.Utils.BlockHeader diff --git a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs index 3af5ebf3f2..ef40b30e9c 100644 --- a/test/lib/Chainweb/Test/Cut/TestBlockDb.hs +++ b/test/lib/Chainweb/Test/Cut/TestBlockDb.hs @@ -30,16 +30,16 @@ import Control.Monad.IO.Class import Control.Monad.Trans.Resource import qualified Data.HashMap.Strict as HM -import Chainweb.Block +import Chainweb.Pact.Block import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.ChainId import Chainweb.Cut import Chainweb.Test.Utils import Chainweb.Test.Cut (GenBlockTime, testMine', MineFailure(BadAdjacents)) -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Payload.PayloadStore.RocksDB import Chainweb.Utils import Chainweb.Version import Chainweb.WebBlockHeaderDB diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index d1e159e4e1..cfb5d7bddf 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -122,8 +122,8 @@ import Chainweb.Cut.CutHashes import Chainweb.Difficulty import Chainweb.Graph import Chainweb.HostAddress -import Chainweb.Mempool.Mempool -import Chainweb.Mempool.RestAPI +import Chainweb.Pact.Mempool.Mempool +import Chainweb.Pact.Mempool.RestAPI import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse import Chainweb.Miner.Config @@ -131,7 +131,7 @@ import Chainweb.Miner.Pact import Chainweb.NodeVersion import Chainweb.Pact.RestAPI.SPV import Chainweb.Pact.Types -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.PayloadProvider import Chainweb.PowHash import Chainweb.RestAPI.NetworkID diff --git a/test/lib/Chainweb/Test/Pact/Utils.hs b/test/lib/Chainweb/Test/Pact/Utils.hs index caf43f8777..a08b1544a4 100644 --- a/test/lib/Chainweb/Test/Pact/Utils.hs +++ b/test/lib/Chainweb/Test/Pact/Utils.hs @@ -32,16 +32,16 @@ module Chainweb.Test.Pact.Utils import Chainweb.Chainweb (validatingMempoolConfig) import Chainweb.ChainId import Chainweb.Logger -import Chainweb.Mempool.InMem -import Chainweb.Mempool.Mempool (MempoolBackend (..)) +import Chainweb.Pact.Mempool.InMem +import Chainweb.Pact.Mempool.Mempool (MempoolBackend (..)) --import Chainweb.Pact.Backend.RelationalCheckpointer import Chainweb.Pact.Backend.Types (SQLiteEnv) import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) import Chainweb.Pact.PactService import Chainweb.Pact.Types import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Payload.PayloadStore.RocksDB import Chainweb.Storage.Table.RocksDB -- import Chainweb.Utils import Chainweb.Version diff --git a/test/lib/Chainweb/Test/Pact4/Utils.hs b/test/lib/Chainweb/Test/Pact4/Utils.hs index 4e61e677ae..7ca04e7cf7 100644 --- a/test/lib/Chainweb/Test/Pact4/Utils.hs +++ b/test/lib/Chainweb/Test/Pact4/Utils.hs @@ -190,8 +190,8 @@ import Chainweb.Pact.PactService import Chainweb.Pact.RestAPI.Server (validateCommand) import Chainweb.Pact.Types import Chainweb.Pact4.Types -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Test.Cut import Chainweb.Test.Cut.TestBlockDb import Chainweb.Test.Utils diff --git a/test/lib/Chainweb/Test/RestAPI/Client_.hs b/test/lib/Chainweb/Test/RestAPI/Client_.hs index d9926a45c1..02dbe58768 100644 --- a/test/lib/Chainweb/Test/RestAPI/Client_.hs +++ b/test/lib/Chainweb/Test/RestAPI/Client_.hs @@ -49,8 +49,8 @@ import Chainweb.BlockHeaderDB import Chainweb.BlockHeaderDB.RestAPI import Chainweb.Cut.CutHashes import Chainweb.CutDB.RestAPI -import Chainweb.Payload -import Chainweb.Payload.RestAPI +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.RestAPI import Chainweb.TreeDB import Chainweb.Utils.Paging import Chainweb.Version diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index abd65f5efb..993a73f629 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -59,7 +59,7 @@ import Chainweb.Version import Chainweb.Version.Registry import P2P.Peer -import Chainweb.Payload (PayloadWithOutputs_(_payloadWithOutputsPayloadHash), PayloadWithOutputs) +import Chainweb.Pact.Payload(PayloadWithOutputs_(_payloadWithOutputsPayloadHash), PayloadWithOutputs) import qualified Pact.Core.Names as Pact import qualified Pact.Core.Gas as Pact import qualified Data.List as L diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index d0aa4091e0..6a659db959 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -208,7 +208,7 @@ import Chainweb.Difficulty (targetToDifficulty) import Chainweb.Graph import Chainweb.HostAddress import Chainweb.Logger -import Chainweb.Mempool.Mempool (MempoolBackend(..), TransactionHash(..), BlockFill(..), mockBlockGasLimit) +import Chainweb.Pact.Mempool.Mempool (MempoolBackend(..), TransactionHash(..), BlockFill(..), mockBlockGasLimit) import Chainweb.MerkleUniverse import Chainweb.Miner.Config import Chainweb.Pact.Backend.Types(SQLiteEnv) diff --git a/test/lib/Chainweb/Test/Utils/BlockHeader.hs b/test/lib/Chainweb/Test/Utils/BlockHeader.hs index 82b7be9142..941db98b8a 100644 --- a/test/lib/Chainweb/Test/Utils/BlockHeader.hs +++ b/test/lib/Chainweb/Test/Utils/BlockHeader.hs @@ -41,7 +41,7 @@ import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.ChainValue import Chainweb.Parent -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Time import Chainweb.Utils import Chainweb.Version diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 9e5fff9fe9..ea368a8922 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -71,9 +71,9 @@ import Chainweb.CutDB.RestAPI.Server import Chainweb.Miner.Pact import Chainweb.Pact.Types import Chainweb.Parent -import Chainweb.Payload -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Payload.PayloadStore.RocksDB import Chainweb.PayloadProvider import Chainweb.Sync.WebBlockHeaderStore import Chainweb.Test.Orphans.Internal () diff --git a/test/unit/Chainweb/Test/Mempool.hs b/test/unit/Chainweb/Test/Mempool.hs index 18166d8def..7f05876a1f 100644 --- a/test/unit/Chainweb/Test/Mempool.hs +++ b/test/unit/Chainweb/Test/Mempool.hs @@ -51,7 +51,7 @@ import Pact.Parse (ParsedDecimal(..)) import Pact.Core.Gas.Types import Chainweb.BlockHash -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.Mempool import Chainweb.Test.Utils import qualified Chainweb.Time as Time import Chainweb.Utils (T2(..)) diff --git a/test/unit/Chainweb/Test/Mempool/Consensus.hs b/test/unit/Chainweb/Test/Mempool/Consensus.hs index 6361425d76..9907dc34d1 100644 --- a/test/unit/Chainweb/Test/Mempool/Consensus.hs +++ b/test/unit/Chainweb/Test/Mempool/Consensus.hs @@ -44,10 +44,10 @@ import Chainweb.BlockWeight import Chainweb.ChainId import Chainweb.Crypto.MerkleLog hiding (header) import Chainweb.Difficulty (targetToDifficulty) --- import Chainweb.Mempool.Consensus -import Chainweb.Mempool.Mempool +-- import Chainweb.Pact.Mempool.Consensus +import Chainweb.Pact.Mempool.Mempool import Chainweb.MerkleUniverse -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Test.Orphans.Time () import Chainweb.Test.Utils import Chainweb.Test.Utils.BlockHeader diff --git a/test/unit/Chainweb/Test/Mempool/InMem.hs b/test/unit/Chainweb/Test/Mempool/InMem.hs index 698754614f..04cd68c212 100644 --- a/test/unit/Chainweb/Test/Mempool/InMem.hs +++ b/test/unit/Chainweb/Test/Mempool/InMem.hs @@ -7,9 +7,9 @@ import Control.Concurrent.MVar import qualified Data.Vector as V import Test.Tasty ------------------------------------------------------------------------------ -import qualified Chainweb.Mempool.InMem as InMem -import Chainweb.Mempool.InMemTypes (InMemConfig(..)) -import Chainweb.Mempool.Mempool +import qualified Chainweb.Pact.Mempool.InMem as InMem +import Chainweb.Pact.Mempool.InMemTypes (InMemConfig(..)) +import Chainweb.Pact.Mempool.Mempool import Chainweb.Test.Mempool (InsertCheck, MempoolWithFunc(..)) import qualified Chainweb.Test.Mempool import Chainweb.Utils (Codec(..)) diff --git a/test/unit/Chainweb/Test/Mempool/RestAPI.hs b/test/unit/Chainweb/Test/Mempool/RestAPI.hs index a24d194cd2..ba3ac28508 100644 --- a/test/unit/Chainweb/Test/Mempool/RestAPI.hs +++ b/test/unit/Chainweb/Test/Mempool/RestAPI.hs @@ -21,10 +21,10 @@ import Test.Tasty import Chainweb.Chainweb.Configuration import Chainweb.Graph -import qualified Chainweb.Mempool.InMem as InMem -import Chainweb.Mempool.InMemTypes (InMemConfig(..)) -import Chainweb.Mempool.Mempool -import qualified Chainweb.Mempool.RestAPI.Client as MClient +import qualified Chainweb.Pact.Mempool.InMem as InMem +import Chainweb.Pact.Mempool.InMemTypes (InMemConfig(..)) +import Chainweb.Pact.Mempool.Mempool +import qualified Chainweb.Pact.Mempool.RestAPI.Client as MClient import Chainweb.RestAPI import Chainweb.Test.Mempool (InsertCheck, MempoolWithFunc(..)) import qualified Chainweb.Test.Mempool @@ -42,7 +42,7 @@ import Control.Monad ------------------------------------------------------------------------------ tests :: TestTree tests = withResource newPool Pool.destroyAllResources $ - \poolIO -> testGroup "Chainweb.Mempool.RestAPI" + \poolIO -> testGroup "Chainweb.Pact.Mempool.RestAPI" $ Chainweb.Test.Mempool.remoteTests $ MempoolWithFunc $ withRemoteMempool poolIO diff --git a/test/unit/Chainweb/Test/Mempool/Sync.hs b/test/unit/Chainweb/Test/Mempool/Sync.hs index 33fc9d6854..2a9f1ea1a5 100644 --- a/test/unit/Chainweb/Test/Mempool/Sync.hs +++ b/test/unit/Chainweb/Test/Mempool/Sync.hs @@ -20,9 +20,9 @@ import Test.QuickCheck hiding ((.&.)) import Test.QuickCheck.Monadic import Test.Tasty ------------------------------------------------------------------------------ -import Chainweb.Mempool.InMem -import Chainweb.Mempool.InMemTypes -import Chainweb.Mempool.Mempool +import Chainweb.Pact.Mempool.InMem +import Chainweb.Pact.Mempool.InMemTypes +import Chainweb.Pact.Mempool.Mempool import Chainweb.Test.Mempool (InsertCheck, MempoolWithFunc(..), lookupIsPending, mempoolProperty) import Chainweb.Utils (Codec(..)) @@ -30,7 +30,7 @@ import Chainweb.Utils (Codec(..)) tests :: TestTree -tests = testGroup "Chainweb.Mempool.sync" +tests = testGroup "Chainweb.Pact.Mempool.sync" [ mempoolProperty "Mempool.syncMempools" gen propSync $ MempoolWithFunc wf ] where diff --git a/test/unit/Chainweb/Test/Misc.hs b/test/unit/Chainweb/Test/Misc.hs index f63945d04b..e505dfdc2c 100644 --- a/test/unit/Chainweb/Test/Misc.hs +++ b/test/unit/Chainweb/Test/Misc.hs @@ -16,7 +16,7 @@ module Chainweb.Test.Misc ( tests ) where -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Test.Orphans.Internal () import Control.Concurrent (threadDelay) diff --git a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs index fad30d5777..1e5c54cef6 100644 --- a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs +++ b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs @@ -61,7 +61,7 @@ import Chainweb.Utils.Serialization (runGetS, runPutS) import Chainweb.Version import Chainweb.MinerReward import Control.Monad.State.Strict -import qualified Chainweb.Payload as Chainweb +import qualified Chainweb.Pact.Payload as Chainweb import Chainweb.Pact.Utils (emptyPayload) -- | A @DbAction f@ is a description of some action on the database together with an f-full of results for it. diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index a8860e10ec..090c6a5d1d 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -49,11 +49,11 @@ import Chainweb.Cut import Chainweb.Cut.CutHashes import Chainweb.CutDB import Chainweb.Logger -import Chainweb.Mempool.Mempool (MempoolBackend) +import Chainweb.Pact.Mempool.Mempool (MempoolBackend) import Chainweb.Pact.Types import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut import Chainweb.Test.CutDB diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index d4294d1c05..509d0a8299 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -26,11 +26,11 @@ import Chainweb.Chainweb import Chainweb.Cut import Chainweb.Graph (singletonChainGraph) import Chainweb.Logger -import Chainweb.Mempool.InMem qualified as Mempool -import Chainweb.Mempool.Mempool qualified as Mempool +import Chainweb.Pact.Mempool.InMem qualified as Mempool +import Chainweb.Pact.Mempool.Mempool qualified as Mempool import Chainweb.Pact.Types import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb), addTestBlockDb, getCutTestBlockDb, getParentTestBlockDb, mkTestBlockDb, setCutTestBlockDb) import Chainweb.Test.Pact.CmdBuilder diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index 9a44923e9f..dfe04d5e6a 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -105,11 +105,11 @@ import Pact.Core.SPV import Chainweb.ChainId import Chainweb.CutDB.RestAPI.Server (someCutGetServer) import Chainweb.Graph (petersenChainGraph, singletonChainGraph, twentyChainGraph) -import Chainweb.Mempool.Mempool (TransactionHash (..)) +import Chainweb.Pact.Mempool.Mempool (TransactionHash (..)) import Chainweb.Pact.RestAPI.Client import Chainweb.Pact.RestAPI.Server import Chainweb.Pact.Types -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.RestAPI.Utils (someServerApplication) import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Pact.CmdBuilder diff --git a/test/unit/Chainweb/Test/Pact/SPVTest.hs b/test/unit/Chainweb/Test/Pact/SPVTest.hs index a36879befa..22811540ed 100644 --- a/test/unit/Chainweb/Test/Pact/SPVTest.hs +++ b/test/unit/Chainweb/Test/Pact/SPVTest.hs @@ -24,14 +24,14 @@ module Chainweb.Test.Pact.SPVTest ) where import Data.ByteString.Base16 qualified as Base16 -import Chainweb.Block (Block (_blockPayloadWithOutputs)) +import Chainweb.Pact.Block (Block (_blockPayloadWithOutputs)) import System.Environment (lookupEnv, setEnv) import Control.Applicative ((<|>)) import Data.List qualified as List -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Pact.Service.BlockValidation import Chainweb.Pact.Service.PactInProcApi -import Chainweb.Mempool.Consensus +import Chainweb.Pact.Mempool.Consensus import Chainweb.Pact.PactService import Chainweb.Pact.Service.PactQueue import Chainweb.BlockCreationTime @@ -41,9 +41,9 @@ import Chainweb.Chainweb import Chainweb.Cut import Chainweb.Graph (singletonChainGraph) import Chainweb.Logger -import Chainweb.Mempool.Consensus -import Chainweb.Mempool.InMem -import Chainweb.Mempool.Mempool (InsertType (..), LookupResult(..), MempoolBackend (..), TransactionHash(..)) +import Chainweb.Pact.Mempool.Consensus +import Chainweb.Pact.Mempool.InMem +import Chainweb.Pact.Mempool.Mempool (InsertType (..), LookupResult(..), MempoolBackend (..), TransactionHash(..)) import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse (ChainwebMerkleHashAlgorithm) import Chainweb.Miner.Pact @@ -71,9 +71,9 @@ import Chainweb.Pact.TransactionExec import Chainweb.Pact.TransactionExec qualified import Chainweb.Pact.TransactionExec qualified as Pact5 import Chainweb.Pact.Types -import Chainweb.Payload -import Chainweb.Payload (PayloadWithOutputs_ (_payloadWithOutputsPayloadHash), Transaction (Transaction)) -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload(PayloadWithOutputs_ (_payloadWithOutputsPayloadHash), Transaction (Transaction)) +import Chainweb.Pact.Payload.PayloadStore import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb, _bdbWebBlockHeaderDb), addTestBlockDb, getCutTestBlockDb, getParentTestBlockDb, mkTestBlockDb, setCutTestBlockDb) import Chainweb.Test.Pact4.Utils (stdoutDummyLogger, defaultPactServiceConfig, withBlockHeaderDb) diff --git a/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs b/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs index f1d13dc076..9084404991 100644 --- a/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs +++ b/test/unit/Chainweb/Test/Pact4/NoCoinbase.hs @@ -24,7 +24,7 @@ import Test.Tasty.HUnit -- internal modules import Chainweb.Pact4.NoCoinbase -import Chainweb.Payload +import Chainweb.Pact.Payload tests :: TestTree tests = testGroup "Chainweb.Test.Pact4.NoCoinbase" diff --git a/test/unit/Chainweb/Test/RestAPI.hs b/test/unit/Chainweb/Test/RestAPI.hs index db2d057762..d743ac74db 100644 --- a/test/unit/Chainweb/Test/RestAPI.hs +++ b/test/unit/Chainweb/Test/RestAPI.hs @@ -49,7 +49,7 @@ import Text.Read (readEither) -- internal modules -import Chainweb.Block +import Chainweb.Pact.Block import Chainweb.BlockHash (BlockHash) import Chainweb.BlockHeader import Chainweb.BlockHeaderDB @@ -57,9 +57,9 @@ import Chainweb.BlockHeaderDB.Internal (unsafeInsertBlockHeaderDb) import Chainweb.BlockHeaderDB.RestAPI import Chainweb.ChainId import Chainweb.Graph -import Chainweb.Mempool.Mempool (MempoolBackend, MockTx) -import Chainweb.Payload.PayloadStore -import Chainweb.Payload.PayloadStore.RocksDB +import Chainweb.Pact.Mempool.Mempool (MempoolBackend, MockTx) +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Payload.PayloadStore.RocksDB import Chainweb.RestAPI import Chainweb.Test.RestAPI.Client_ import Chainweb.Test.RestAPI.Utils (isFailureResponse, clientErrorStatusCode) diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index e98bb01067..2c1af4f9ce 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -52,15 +52,15 @@ import Chainweb.Cut.Create import Chainweb.Cut.CutHashes import Chainweb.Difficulty import Chainweb.HostAddress -import Chainweb.Mempool.Mempool -import Chainweb.Mempool.RestAPI +import Chainweb.Pact.Mempool.Mempool +import Chainweb.Pact.Mempool.RestAPI import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse import Chainweb.Miner.Config import Chainweb.Miner.Pact import Chainweb.NodeVersion import Chainweb.Pact.Types -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.PowHash import Chainweb.RestAPI.NetworkID import Chainweb.RestAPI.NodeInfo @@ -336,7 +336,7 @@ jsonTestCases f = , testProperty "MockTx" $ f @MockTx ] - -- Chainweb.Payload + -- Chainweb.Pact.Payload , testGroup "Payload types" [ testProperty "Transaction" $ f @Transaction , testProperty "MinerData" $ f @MinerData diff --git a/test/unit/Chainweb/Test/SPV.hs b/test/unit/Chainweb/Test/SPV.hs index 741e226bd6..0ea5ea3ee3 100644 --- a/test/unit/Chainweb/Test/SPV.hs +++ b/test/unit/Chainweb/Test/SPV.hs @@ -63,10 +63,10 @@ import Chainweb.Crypto.MerkleLog import Chainweb.Cut hiding (join) import Chainweb.CutDB import Chainweb.Graph -import Chainweb.Mempool.Mempool (MockTx) +import Chainweb.Pact.Mempool.Mempool (MockTx) import Chainweb.MerkleUniverse -import Chainweb.Payload -import Chainweb.Payload.PayloadStore +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore import Chainweb.SPV import Chainweb.SPV.CreateProof import Chainweb.SPV.OutputProof diff --git a/test/unit/Chainweb/Test/SPV/EventProof.hs b/test/unit/Chainweb/Test/SPV/EventProof.hs index 320bb38813..2cc6f6cff3 100644 --- a/test/unit/Chainweb/Test/SPV/EventProof.hs +++ b/test/unit/Chainweb/Test/SPV/EventProof.hs @@ -56,7 +56,7 @@ import Test.Tasty.QuickCheck import Chainweb.Crypto.MerkleLog import Chainweb.MerkleUniverse -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.SPV.EventProof import Chainweb.SPV.OutputProof import Chainweb.SPV.PayloadProof diff --git a/test/unit/Test/Chainweb/SPV/Argument.hs b/test/unit/Test/Chainweb/SPV/Argument.hs index 5045fd76a6..c70472107e 100644 --- a/test/unit/Test/Chainweb/SPV/Argument.hs +++ b/test/unit/Test/Chainweb/SPV/Argument.hs @@ -22,7 +22,7 @@ import Chainweb.ChainId import Chainweb.Crypto.MerkleLog import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse -import Chainweb.Payload +import Chainweb.Pact.Payload import Chainweb.PayloadProvider.EVM.Genesis qualified as EVM import Chainweb.PayloadProvider.EVM.Header qualified as EVM import Chainweb.PayloadProvider.EVM.Receipt qualified as EVM From dedd2419d533c7a85a74a2294e9a976c0eeab4e8 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 3 Sep 2025 11:02:20 -0400 Subject: [PATCH 324/378] Fix recovery cut code --- src/Chainweb/CutDB.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index b0c06daf52..168fbe838a 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -502,7 +502,7 @@ synchronizeProviders logger wbh providers c = do -- try to recover from the fork automatically by removing ~`diameter` -- blocks from the cut let recoveryHeight = - _cutMinHeight c - max (_cutMinHeight c) (int (diameter (chainGraphAt maxBound))) + max (int (diameter (chainGraphAt maxBound))) (_cutMinHeight c) - int (diameter (chainGraphAt maxBound)) recoveryCut <- limitCut wbh recoveryHeight c let recoveryHeaders = HM.unionWith (\recoveryHeader _genesisHeader -> recoveryHeader) From ccc7d684a30d9f419faad62caa242f3661a36a29 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 28 Aug 2025 14:11:46 -0400 Subject: [PATCH 325/378] Stop node after initial cut is reached, if it's configured Initial cut configuration is essentially a recovery mode, so the node should not continue as usual after it's been reset to. Change-Id: Id00000005392c4e5d45fb6bf9d6299a02f2ef733 --- node/src/ChainwebNode.hs | 8 +- src/Chainweb/Chainweb.hs | 165 ++++++++++++------------- src/Chainweb/Chainweb/Configuration.hs | 18 --- src/Chainweb/Chainweb/CutResources.hs | 27 ++-- src/Chainweb/CutDB.hs | 51 ++++---- test/lib/Chainweb/Test/MultiNode.hs | 51 ++------ test/lib/Chainweb/Test/Utils.hs | 2 +- test/unit/Chainweb/Test/CutDB.hs | 4 +- 8 files changed, 138 insertions(+), 188 deletions(-) diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 2ca6ef1a4d..a66adfa7a6 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -318,7 +318,7 @@ node conf logger = do pactDbDir <- getPactDbDir conf dbBackupsDir <- getBackupsDir conf withRocksDb' <- - if _configOnlySync cwConf || _configReadOnlyReplay cwConf + if _configReadOnlyReplay cwConf then withReadOnlyRocksDb <$ logFunctionText logger Info "Opening RocksDB in read-only mode" else @@ -327,7 +327,7 @@ node conf logger = do logFunctionText logger Info $ "opened rocksdb in directory " <> sshow rocksDbDir logFunctionText logger Debug $ "backup config: " <> sshow (_configBackup cwConf) withChainweb cwConf logger rocksDb pactDbDir dbBackupsDir $ \case - Replayed _ _ -> return () + RewoundToCut _ -> return () StartedChainweb cw -> do let telemetryEnabled = _enableConfigEnabled $ _logConfigTelemetryBackend $ _nodeConfigLog conf @@ -366,7 +366,9 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do -- we don't log tx failures in replay let !txFailureHandler = - if _configOnlySync chainwebCfg || _configReadOnlyReplay chainwebCfg + if isJust (_cutInitialCutFile (_configCuts chainwebCfg)) + || isJust (_cutInitialBlockHeightLimit (_configCuts chainwebCfg)) + || _configReadOnlyReplay chainwebCfg then [dropLogHandler (Proxy :: Proxy PactTxFailureLog)] else [] diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index e8d1eb3b0a..7f74154f05 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -88,7 +88,6 @@ module Chainweb.Chainweb , CutConfig(..) , cutFetchTimeout , cutInitialBlockHeightLimit -, cutFastForwardBlockHeightLimit , defaultCutConfig ) where @@ -228,11 +227,13 @@ withChainweb c logger rocksDb defaultPactDbDir backupDir inner = %~ (\x -> L.nub $ x <> _versionBootstraps (c ^. configChainwebVersion)) $ c data StartedChainweb logger where - StartedChainweb - :: (Logger logger) - => !(Chainweb logger) - -> StartedChainweb logger - Replayed :: !Cut -> !(Maybe Cut) -> StartedChainweb logger + StartedChainweb + :: (Logger logger) + => !(Chainweb logger) + -> StartedChainweb logger + RewoundToCut + :: !Cut + -> StartedChainweb logger data ChainwebStatus = ProcessStarted @@ -328,8 +329,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba { _cutDbParamsLogLevel = Info , _cutDbParamsTelemetryLevel = Info , _cutDbParamsInitialHeightLimit = _cutInitialBlockHeightLimit cutConf - , _cutDbParamsFastForwardHeightLimit = _cutFastForwardBlockHeightLimit cutConf - , _cutDbParamsReadOnly = _configOnlySync conf || _configReadOnlyReplay conf + , _cutDbParamsReadOnly = _configReadOnlyReplay conf , _cutDbParamsInitialCutFile = _cutInitialCutFile cutConf } where @@ -369,89 +369,76 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba liftIO $ logg Debug "start initializing cut resources" liftIO $ logFunctionJson logger Info InitializingCutResources - cuts <- withCutResources cutLogger cutDbParams p2pConfig myInfo peerDb rocksDb webchain providers mgr - liftIO $ do - logg Debug "finished initializing cut resources" - - let !mLogger = setComponent "miner" logger - !mConf = _configMining conf - !mCutDb = _cutResCutDb cuts - !throt = _configThrottling conf - - -- initialize throttler - throttler <- mkGenericThrottler $ _throttlingRate throt - putPeerThrottler <- mkPutPeerThrottler $ _throttlingPeerRate throt - mempoolThrottler <- mkMempoolThrottler $ _throttlingMempoolRate throt - logg Debug "initialized throttlers" - - -- synchronize pact dbs with latest cut before we start the server - -- and clients and begin mining. - -- - -- This is a consistency check that validates the blocks in the - -- current cut. If it fails an exception is raised. Also, if it - -- takes long (for example, when doing a reset to a prior block - -- height) we want this to happen before we go online. - -- - let - -- pactSyncChains = - -- case _configSyncPactChains conf of - -- Just syncChains - -- | _configOnlySyncPact conf || _configReadOnlyReplay conf - -- -> HM.filterWithKey (\k _ -> elem k syncChains) cs - -- _ -> cs - - if _configReadOnlyReplay conf - then do - -- FIXME implement replay in payload provider - error "Chainweb.Chainweb.withChainwebInternal: pact replay is not supported" - -- logFunctionJson logger Info PactReplayInProgress - -- -- note that we don't use the "initial cut" from cutdb because its height depends on initialBlockHeightLimit. - -- highestCut <- - -- unsafeMkCut v <$> readHighestCutHeaders v (logFunctionText logger) webchain (cutHashesTable rocksDb) - -- lowerBoundCut <- - -- tryLimitCut webchain (fromMaybe 0 $ _cutInitialBlockHeightLimit $ _configCuts conf) highestCut - -- upperBoundCut <- forM (_cutFastForwardBlockHeightLimit $ _configCuts conf) $ \upperBound -> - -- tryLimitCut webchain upperBound highestCut - -- let - -- replayOneChain :: (ChainResources logger, (BlockHeader, Maybe BlockHeader)) -> IO () - -- replayOneChain (cr, (l, u)) = do - -- let chainPact = _chainResPact cr - -- let logCr = logFunctionText - -- $ addLabel ("component", "pact") - -- $ addLabel ("sub-component", "init") - -- $ _chainResLogger cr - -- void $ _pactReadOnlyReplay chainPact l u - -- logCr Info "pact db synchronized" - -- let bounds = - -- HM.intersectionWith (,) - -- pactSyncChains - -- (HM.mapWithKey - -- (\cid bh -> - -- (bh, (HM.! cid) . _cutMap <$> upperBoundCut)) - -- (_cutMap lowerBoundCut) - -- ) - -- mapConcurrently_ replayOneChain bounds - -- logg Info "finished fast forward replay" - -- logFunctionJson logger Info PactReplaySuccessful - -- inner $ Replayed lowerBoundCut upperBoundCut - else - if _configOnlySync conf - then do + initialCutOrCutResources <- withCutResources cutLogger cutDbParams p2pConfig myInfo peerDb rocksDb webchain providers mgr + liftIO $ case initialCutOrCutResources of + Left initialCut -> do + logg Info "no cut resources, chainweb will not continue" + inner (RewoundToCut initialCut) + Right cutResources -> do + logg Debug "finished initializing cut resources" + + let !mLogger = setComponent "miner" logger + !mConf = _configMining conf + !mCutDb = _cutResCutDb cutResources + !throt = _configThrottling conf + + -- initialize throttler + throttler <- mkGenericThrottler $ _throttlingRate throt + putPeerThrottler <- mkPutPeerThrottler $ _throttlingPeerRate throt + mempoolThrottler <- mkMempoolThrottler $ _throttlingMempoolRate throt + logg Debug "initialized throttlers" + + -- synchronize pact dbs with latest cut before we start the server + -- and clients and begin mining. + -- + -- This is a consistency check that validates the blocks in the + -- current cut. If it fails an exception is raised. Also, if it + -- takes long (for example, when doing a reset to a prior block + -- height) we want this to happen before we go online. + -- + let + -- pactSyncChains = + -- case _configSyncPactChains conf of + -- Just syncChains + -- | _configReadOnlyReplay conf + -- -> HM.filterWithKey (\k _ -> elem k syncChains) cs + -- _ -> cs + + if _configReadOnlyReplay conf + then do + -- FIXME implement replay in payload provider error "Chainweb.Chainweb.withChainwebInternal: pact replay is not supported" - -- initialCut <- _cut mCutDb - -- logg Info "start synchronizing Pact DBs to initial cut" - -- logFunctionJson logger Info InitialSyncInProgress - -- synchronizePactDb pactSyncChains initialCut - -- logg Info "finished synchronizing Pact DBs to initial cut" -- logFunctionJson logger Info PactReplayInProgress - -- logg Info "start replaying Pact DBs to fast forward cut" - -- fastForwardCutDb mCutDb - -- newCut <- _cut mCutDb - -- synchronizePactDb pactSyncChains newCut - -- logg Info "finished replaying Pact DBs to fast forward cut" + -- -- note that we don't use the "initial cut" from cutdb because its height depends on initialBlockHeightLimit. + -- highestCut <- + -- unsafeMkCut v <$> readHighestCutHeaders v (logFunctionText logger) webchain (cutHashesTable rocksDb) + -- lowerBoundCut <- + -- tryLimitCut webchain (fromMaybe 0 $ _cutInitialBlockHeightLimit $ _configCuts conf) highestCut + -- upperBoundCut <- forM (_cutFastForwardBlockHeightLimit $ _configCuts conf) $ \upperBound -> + -- tryLimitCut webchain upperBound highestCut + -- let + -- replayOneChain :: (ChainResources logger, (BlockHeader, Maybe BlockHeader)) -> IO () + -- replayOneChain (cr, (l, u)) = do + -- let chainPact = _chainResPact cr + -- let logCr = logFunctionText + -- $ addLabel ("component", "pact") + -- $ addLabel ("sub-component", "init") + -- $ _chainResLogger cr + -- void $ _pactReadOnlyReplay chainPact l u + -- logCr Info "pact db synchronized" + -- let bounds = + -- HM.intersectionWith (,) + -- pactSyncChains + -- (HM.mapWithKey + -- (\cid bh -> + -- (bh, (HM.! cid) . _cutMap <$> upperBoundCut)) + -- (_cutMap lowerBoundCut) + -- ) + -- mapConcurrently_ replayOneChain bounds + -- logg Info "finished fast forward replay" -- logFunctionJson logger Info PactReplaySuccessful - -- inner $ Replayed initialCut (Just newCut) - else do + -- inner $ Replayed lowerBoundCut upperBoundCut + else do logg Debug "start initializing miner resources" logFunctionJson logger Info InitializingMinerResources @@ -468,7 +455,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba inner $ StartedChainweb Chainweb { _chainwebHostAddress = haddr , _chainwebChains = cs - , _chainwebCutResources = cuts + , _chainwebCutResources = cutResources , _chainwebMiner = m , _chainwebCoordinator = mc , _chainwebLogger = logger diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index 6abf6dc233..c2c13c73d0 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -40,7 +40,6 @@ module Chainweb.Chainweb.Configuration , CutConfig(..) , cutFetchTimeout , cutInitialBlockHeightLimit -, cutFastForwardBlockHeightLimit , defaultCutConfig , pCutConfig @@ -68,7 +67,6 @@ module Chainweb.Chainweb.Configuration , configReorgLimit , configBackup , configServiceApi -, configOnlySync , configReadOnlyReplay , configSyncChains , configPayloadProviders @@ -341,7 +339,6 @@ instance FromJSON (ThrottlingConfig -> ThrottlingConfig) where data CutConfig = CutConfig { _cutFetchTimeout :: !Int , _cutInitialBlockHeightLimit :: !(Maybe BlockHeight) - , _cutFastForwardBlockHeightLimit :: !(Maybe BlockHeight) , _cutInitialCutFile :: !(Maybe FilePath) } deriving (Eq, Show) @@ -351,7 +348,6 @@ instance ToJSON CutConfig where toJSON o = object [ "fetchTimeout" .= _cutFetchTimeout o , "initialBlockHeightLimit" .= _cutInitialBlockHeightLimit o - , "fastForwardBlockHeightLimit" .= _cutFastForwardBlockHeightLimit o , "initialCutFile" .= _cutInitialCutFile o ] @@ -359,14 +355,12 @@ instance FromJSON (CutConfig -> CutConfig) where parseJSON = withObject "CutConfig" $ \o -> id <$< cutFetchTimeout ..: "fetchTimeout" % o <*< cutInitialBlockHeightLimit ..: "initialBlockHeightLimit" % o - <*< cutFastForwardBlockHeightLimit ..: "fastForwardBlockHeightLimit" % o <*< cutInitialCutFile ..: "initialCutFile" % o defaultCutConfig :: CutConfig defaultCutConfig = CutConfig { _cutFetchTimeout = 3_000_000 , _cutInitialBlockHeightLimit = Nothing - , _cutFastForwardBlockHeightLimit = Nothing , _cutInitialCutFile = Nothing } @@ -379,10 +373,6 @@ pCutConfig = id % long "initial-block-height-limit" <> help "Reset initial cut to this block height." <> metavar "INT" - <*< cutFastForwardBlockHeightLimit .:: fmap (Just . BlockHeight) . option auto - % long "fast-forward-block-height-limit" - <> help "When --only-sync-pact is given fast forward to this height. Ignored otherwise." - <> metavar "INT" <*< cutInitialCutFile .:: fmap Just . textOption % long "initial-cut-file" <> help "When --initial-cut-file is given, use the cut in the given file as the initial cut. Note that this will not contact the P2P network." @@ -535,8 +525,6 @@ data ChainwebConfiguration = ChainwebConfiguration , _configReadOnlyReplay :: !Bool -- ^ do a read-only replay using the cut db params for the block heights - , _configOnlySync :: !Bool - -- ^ exit after synchronizing pact dbs to the latest cut , _configSyncChains :: !(Maybe [ChainId]) -- ^ the only chains to be synchronized on startup to the latest cut. -- if unset, all chains will be synchronized. @@ -586,7 +574,6 @@ defaultChainwebConfiguration v = ChainwebConfiguration , _configThrottling = defaultThrottlingConfig , _configReorgLimit = defaultReorgLimit , _configServiceApi = defaultServiceApiConfig - , _configOnlySync = False , _configReadOnlyReplay = False , _configSyncChains = Nothing , _configBackup = defaultBackupConfig @@ -602,7 +589,6 @@ instance ToJSON ChainwebConfiguration where , "throttling" .= _configThrottling o , "reorgLimit" .= _configReorgLimit o , "serviceApi" .= _configServiceApi o - , "onlySync" .= _configOnlySync o , "readOnlyReplay" .= _configReadOnlyReplay o , "syncChains" .= _configSyncChains o , "backup" .= _configBackup o @@ -622,7 +608,6 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where <*< configThrottling %.: "throttling" % o <*< configReorgLimit ..: "reorgLimit" % o <*< configServiceApi %.: "serviceApi" % o - <*< configOnlySync ..: "onlySync" % o <*< configReadOnlyReplay ..: "readOnlyReplay" % o <*< configSyncChains ..: "syncChains" % o <*< configBackup %.: "backup" % o @@ -640,9 +625,6 @@ pChainwebConfiguration = id <*< parserOptionGroup "Cut Processing" (configCuts %:: pCutConfig) <*< parserOptionGroup "Service API" (configServiceApi %:: pServiceApiConfig) <*< parserOptionGroup "Mining Coordination" (configMining %:: pMiningConfig) - <*< configOnlySync .:: boolOption_ - % long "only-sync" - <> help "Terminate after synchronizing the pact databases to the latest cut" <*< configReadOnlyReplay .:: boolOption_ % long "read-only-replay" <> help "Replay the block history non-destructively" diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index f04aa3fac2..55ccf127af 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -32,6 +32,7 @@ module Chainweb.Chainweb.CutResources ) where import Control.Lens +import Control.Monad (forM) import Control.Monad.IO.Class import Control.Monad.Trans.Resource @@ -41,6 +42,7 @@ import qualified Network.HTTP.Client as HTTP -- internal modules +import Chainweb.Cut (Cut) import Chainweb.CutDB import qualified Chainweb.CutDB.Sync as C import Chainweb.Logger @@ -86,7 +88,7 @@ withCutResources -> WebBlockHeaderDb -> ChainMap ConfiguredPayloadProvider -> HTTP.Manager - -> ResourceT IO CutResources + -> ResourceT IO (Either Cut CutResources) withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain providers mgr = do -- initialize blockheader store @@ -95,17 +97,18 @@ withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain provide -- initialize cutHashes store let cutHashesStore = cutHashesTable rdb - cutDb <- withCutDb cutDbParams logger headerStore providers cutHashesStore - cutP2pNode <- liftIO $ mkP2pNode True "cut" $ - C.syncSession myInfo cutDb - headerP2pNode <- liftIO $ mkP2pNode False "header" $ - session 10 (_webBlockHeaderStoreQueue headerStore) - return CutResources - { _cutResPeerDb = peerDb - , _cutResCutDb = cutDb - , _cutResCutP2pNode = cutP2pNode - , _cutResHeaderP2pNode = headerP2pNode - } + initialCutOrCutDb <- withCutDb cutDbParams logger headerStore providers cutHashesStore + forM initialCutOrCutDb $ \cutDb -> do + cutP2pNode <- liftIO $ mkP2pNode True "cut" $ + C.syncSession myInfo cutDb + headerP2pNode <- liftIO $ mkP2pNode False "header" $ + session 10 (_webBlockHeaderStoreQueue headerStore) + return CutResources + { _cutResPeerDb = peerDb + , _cutResCutDb = cutDb + , _cutResCutP2pNode = cutP2pNode + , _cutResHeaderP2pNode = headerP2pNode + } where syncLogger = addLabel ("sub-component", "sync") logger diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 168fbe838a..31a2b8e3bb 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -407,11 +407,11 @@ withCutDb -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes - -> ResourceT IO CutDb + -> ResourceT IO (Either Cut CutDb) withCutDb config logger headerStore providers cutHashesStore = snd <$> allocate (startCutDb config logger headerStore providers cutHashesStore) - stopCutDb + (traverse_ stopCutDb) -- | Start a CutDB. This loads the initial cut from the database (falling back -- to the configured initial cut loading fails) and starts the cut validation @@ -430,7 +430,7 @@ startCutDb -> WebBlockHeaderStore -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes - -> IO CutDb + -> IO (Either Cut CutDb) startCutDb config logger headerStore providers cutHashesStore = mask_ $ do logg Debug "obtain initial cut" initialCut <- readInitialCut @@ -440,26 +440,31 @@ startCutDb config logger headerStore providers cutHashesStore = mask_ $ do (unCasify cutHashesStore) -- intentionally don't delete up to recovery cut, the initial cut could be useful later (Just $ over _1 succ $ casKey $ cutToCutHashes Nothing initialCut, Nothing) - cutVar <- newTVarIO recoveryCut - -- use the recovery cut for the pruning state, so that we write cuts more quickly after recovering - cutPruningStateVar <- newTVarIO $ initialCutPruningState recoveryCut - c <- readTVarIO cutVar - logg Info $ T.unlines $ - "got initial cut:" : [" " <> block | block <- cutToTextShort c] - queue <- newEmptyPQueue - cutAsync <- asyncWithUnmask $ \u -> u $ processor queue cutVar cutPruningStateVar - return CutDb - { _cutDbCut = cutVar - , _cutDbQueue = queue - , _cutDbAsync = cutAsync - , _cutDbLogFunction = logFunction logger - , _cutDbHeaderStore = headerStore - , _cutDbPayloadProviders = providers - , _cutDbQueueSize = _cutDbParamsBufferSize config - , _cutDbCutStore = cutHashesStore - , _cutDbReadOnly = _cutDbParamsReadOnly config - , _cutDbFastForwardHeightLimit = _cutDbParamsFastForwardHeightLimit config - } + if isNothing (_cutDbParamsInitialHeightLimit config) + && isNothing (_cutDbParamsInitialCutFile config) + then do + cutVar <- newTVarIO recoveryCut + -- use the recovery cut for the pruning state, so that we write cuts more quickly after recovering + cutPruningStateVar <- newTVarIO $ initialCutPruningState recoveryCut + c <- readTVarIO cutVar + logg Info $ T.unlines $ + "got initial cut:" : [" " <> block | block <- cutToTextShort c] + queue <- newEmptyPQueue + cutAsync <- asyncWithUnmask $ \u -> u $ processor queue cutVar cutPruningStateVar + return $ Right CutDb + { _cutDbCut = cutVar + , _cutDbQueue = queue + , _cutDbAsync = cutAsync + , _cutDbLogFunction = logFunction logger + , _cutDbHeaderStore = headerStore + , _cutDbPayloadProviders = providers + , _cutDbQueueSize = _cutDbParamsBufferSize config + , _cutDbCutStore = cutHashesStore + , _cutDbReadOnly = _cutDbParamsReadOnly config + , _cutDbFastForwardHeightLimit = _cutDbParamsFastForwardHeightLimit config + } + else + return (Left recoveryCut) where logg = logFunctionText logger wbhdb = _webBlockHeaderStoreCas headerStore diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index 6cfeea2e5f..1eb8f186a7 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -221,7 +221,7 @@ harvestConsensusState -> StartedChainweb logger -> RocksDb -> IO () -harvestConsensusState _ _ _ (Replayed _ _) _ = +harvestConsensusState _ _ _ (RewoundToCut _) _ = error "harvestConsensusState: doesn't work when replaying, replays don't do consensus" harvestConsensusState logger stateVar nid (StartedChainweb cw) rdb = do runChainweb cw (\_ -> return ()) `finally` do @@ -253,7 +253,7 @@ multiNode loglevel write bootstrapPeerInfoVar conf rdb pactDbDir nid inner = do StartedChainweb cw' -> when (nid == 0) $ putMVar bootstrapPeerInfoVar $ view (chainwebPeer . peerResPeer . peerInfo) cw' - Replayed _ _ -> return () + RewoundToCut _ -> return () inner nid cw namespacedNodeRocksDb where logger :: GenericLogger @@ -584,53 +584,24 @@ replayTest loglevel n rdb pactDbDir step = do Just stats1 <- consensusStateSummary <$> swapMVar stateVar emptyConsensusState assertGe "maximum cut height before reset" (Actual $ _statMaxHeight stats1) (Expected $ 10) tastylog $ sshow stats1 - tastylog $ "phase 2... resetting" - runNodesForSeconds loglevel logFun (multiConfig n & set (configCuts . cutInitialBlockHeightLimit) (Just 5)) n 30 rdb pactDbDir ct - state2 <- swapMVar stateVar emptyConsensusState - let stats2 = fromJuste $ consensusStateSummary state2 - tastylog $ sshow stats2 - assertGe "block count after reset" (Actual $ _statBlockCount stats2) (Expected $ _statBlockCount stats1) tastylog $ "phase 3... replaying" let replayInitialHeight = 5 firstReplayCompleteRef <- newIORef False runNodesForSeconds loglevel logFun (multiConfig n - & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight) - & set configOnlySync True) - n (Seconds 20) rdb pactDbDir $ \nid cw _ -> case cw of - Replayed l (Just u) -> do - writeIORef firstReplayCompleteRef True - _ <- flip HM.traverseWithKey (_cutMap l) $ \cid bh -> - assertEqual ("lower chain " <> sshow cid) replayInitialHeight (view blockHeight bh) - -- TODO: this is flaky, presumably because a node's cutdb - -- is not being cancelled synchronously enough - assertEqual "upper cut" (snd $ _stateCutMap state2 HM.! nid) u - _ <- flip HM.traverseWithKey (_cutMap u) $ \cid bh -> - assertGe ("upper chain " <> sshow cid) (Actual $ view blockHeight bh) (Expected replayInitialHeight) - return () - Replayed _ Nothing -> error "replayTest: no replay upper bound" - _ -> error "replayTest: not a replay" - assertEqual "first replay completion" True =<< readIORef firstReplayCompleteRef - let fastForwardHeight = 10 - tastylog $ "phase 4... replaying with fast-forward limit" - secondReplayCompleteRef <- newIORef False - runNodesForSeconds loglevel logFun - (multiConfig n - & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight) - & set (configCuts . cutFastForwardBlockHeightLimit) (Just fastForwardHeight) - & set configOnlySync True) + & set (configCuts . cutInitialBlockHeightLimit) (Just replayInitialHeight)) n (Seconds 20) rdb pactDbDir $ \_ cw _ -> case cw of - Replayed l (Just u) -> do - writeIORef secondReplayCompleteRef True - _ <- flip HM.traverseWithKey (_cutMap l) $ \cid bh -> + RewoundToCut rewoundToCut -> do + writeIORef firstReplayCompleteRef True + _ <- flip HM.traverseWithKey (_cutMap rewoundToCut) $ \cid bh -> assertEqual ("lower chain " <> sshow cid) replayInitialHeight (view blockHeight bh) - _ <- flip HM.traverseWithKey (_cutMap u) $ \cid bh -> - assertEqual ("upper chain " <> sshow cid) fastForwardHeight (view blockHeight bh) return () - Replayed _ Nothing -> do - error "replayTest: no replay upper bound" _ -> error "replayTest: not a replay" - assertEqual "second replay completion" True =<< readIORef secondReplayCompleteRef + assertEqual "replay completion" True =<< readIORef firstReplayCompleteRef + runNodesForSeconds loglevel logFun (multiConfig n) n 60 rdb pactDbDir ct + Just stats2 <- consensusStateSummary <$> swapMVar stateVar emptyConsensusState + assertGe "maximum cut height after reset" (Actual $ _statMaxHeight stats2) (Expected $ _statMaxHeight stats1) + assertLe "maximum cut height after reset" (Actual $ _statMaxHeight stats2) (Expected $ round @Double (int (_statMaxHeight stats1) * 2)) tastylog "done." -- -------------------------------------------------------------------------- -- diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index 6a659db959..949227c954 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -1015,7 +1015,7 @@ node rdb rawLogger nowServingRef peerInfoVar conf pactDbDir backupDir nid = do logFunctionText logger Info "write sample data" logFunctionText logger Info "shutdown node" return () - Replayed _ _ -> error "node: should not be a replay" + RewoundToCut _ -> error "node: should not be a replay" where logger = addLabel ("node", sshow nid) rawLogger diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index ea368a8922..988a5292cf 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -142,7 +142,7 @@ withTestCutDb rdb conf n providers logger = do webDb <- liftIO $ initWebBlockHeaderDb rocksDb mgr <- liftIO $ HTTP.newManager HTTP.defaultManagerSettings headerStore <- withLocalWebBlockHeaderStore mgr webDb - cutDb <- withCutDb (conf $ defaultCutDbParams cutFetchTimeout) logger headerStore providers cutHashesDb + Right cutDb <- withCutDb (conf $ defaultCutDbParams cutFetchTimeout) logger headerStore providers cutHashesDb liftIO $ synchronizeProviders webDb genesisCut liftIO $ logFunctionText logger Debug "GOING TO MINE AT THE START" @@ -299,7 +299,7 @@ startTestPayload rdb logger n = do mgr <- HTTP.newManager HTTP.defaultManagerSettings (hserver, hstore) <- startLocalWebBlockHeaderStore mgr webDb let disabledPayloadProviders = onAllChains DisabledPayloadProvider - cutDb <- startCutDb (defaultCutDbParams cutFetchTimeout) logger hstore disabledPayloadProviders cutHashesDb + Right cutDb <- startCutDb (defaultCutDbParams cutFetchTimeout) logger hstore disabledPayloadProviders cutHashesDb foldM_ (\c _ -> view _1 <$> mine logger cutDb c) genesisCut [0..n] return (hserver, cutDb) From 8786bb05967a174b9396ee024ed8f772c0e5ccfa Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 3 Sep 2025 19:02:56 -0400 Subject: [PATCH 326/378] Fix build --- node/src/ChainwebNode.hs | 1 + 1 file changed, 1 insertion(+) diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index a66adfa7a6..05f2df1409 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -56,6 +56,7 @@ import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Managed +import Data.Maybe import Data.Text (Text) import qualified Data.Text as T import Data.Time From f654248133cf87e0fc0740b45b98952c46bc96e9 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 11 Sep 2025 22:34:20 -0700 Subject: [PATCH 327/378] increase catchupStepSize to 500 --- src/Chainweb/CutDB/Sync.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/CutDB/Sync.hs b/src/Chainweb/CutDB/Sync.hs index 9eee976b40..90c612ecd8 100644 --- a/src/Chainweb/CutDB/Sync.hs +++ b/src/Chainweb/CutDB/Sync.hs @@ -82,7 +82,7 @@ getCut (CutClientEnv env) h = runClientThrowM (cutGetClientLimit (int h)) env -- times the number of chains. -- catchupStepSize :: CutHeight -catchupStepSize = 100 +catchupStepSize = 500 syncSession :: HasVersion From c3a0deb17570eefc443042141b7b282d6245fad4 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Thu, 11 Sep 2025 22:35:25 -0700 Subject: [PATCH 328/378] parallelize top level cut header fetching --- src/Chainweb/CutDB.hs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 31a2b8e3bb..af691e5d54 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -103,6 +103,7 @@ import Control.Monad.Trans.Resource hiding (throwM) import Data.Aeson (ToJSON, decodeStrict') import Data.Foldable +import Data.Either (partitionEithers) import Data.Function import Data.Functor.Of import Data.HashMap.Strict qualified as HM @@ -963,18 +964,17 @@ cutHashesToBlockHeaderMap conf logfun headerStore providers hs = -- for better error messages on validation failure let localPayload = _cutHashesLocalPayload hs - (headers :> missing) <- S.each (HM.toList $ _cutHashes hs) - & S.mapM (\(cid, bh) -> tryGetBlockHeader hdrs plds localPayload $ (cid, bh)) - & S.partitionEithers - & S.fold_ (\x (cid, h) -> HM.insert cid h x) mempty id - & S.fold (\x (cid, h) -> HM.insert cid h x) mempty id + (missing, headers) <- fmap partitionEithers + $ forConcurrently (HM.toList (_cutHashes hs)) + $ tryGetBlockHeader hdrs plds localPayload if null missing - then return $! Right headers - else do + then + return $ Right $! HM.fromList headers + else do when (isJust $ _cutHashesLocalPayload hs) $ logfun @Text Error "error validating locally mined cut; the mining loop will stall until unstuck by another mining node" - return $! Left missing + return $ Left $! HM.fromList missing origin = _cutOrigin hs priority = Priority (- int (_cutHashesHeight hs)) From 5c2bfaa6684547885003b3670478d0db8617a56f Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 14 Sep 2025 11:01:03 -0700 Subject: [PATCH 329/378] Turn CutClientEnv into a newtype --- src/Chainweb/CutDB/Sync.hs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Chainweb/CutDB/Sync.hs b/src/Chainweb/CutDB/Sync.hs index 90c612ecd8..f9cdef26d9 100644 --- a/src/Chainweb/CutDB/Sync.hs +++ b/src/Chainweb/CutDB/Sync.hs @@ -48,9 +48,7 @@ import P2P.Session -- -------------------------------------------------------------------------- -- -- Client Env -data CutClientEnv = CutClientEnv - { _envClientEnv :: !ClientEnv - } +newtype CutClientEnv = CutClientEnv { _envClientEnv :: ClientEnv } deriving (Generic) runClientThrowM :: ClientM a -> ClientEnv -> IO a From 4a3d649346b7e4461a008830ab761b982a58df2a Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 14 Sep 2025 12:38:39 -0700 Subject: [PATCH 330/378] Remove redundant imports and cleanup some imports lists --- chainweb.cabal | 10 +- src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 2 - src/Chainweb/Chainweb/ChainResources.hs | 1 - src/Chainweb/Chainweb/MempoolSyncClient.hs | 17 --- src/Chainweb/Cut/Create.hs | 19 +-- src/Chainweb/Miner/Coordinator.hs | 17 +-- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 18 +-- src/Chainweb/Pact/PactService.hs | 1 - src/Chainweb/Pact/PactService/Checkpointer.hs | 48 +++--- src/Chainweb/Pact/PactService/ExecBlock.hs | 55 ++++--- .../Pact/PactService/Pact4/ExecBlock.hs | 101 ++++++------- src/Chainweb/Pact/Payload/PayloadStore.hs | 27 +--- src/Chainweb/Pact/RestAPI/Server.hs | 39 +---- src/Chainweb/Pact/SPV.hs | 9 +- src/Chainweb/Pact/Types.hs | 4 - src/Chainweb/Pact/Validations.hs | 1 - src/Chainweb/Pact4/SPV.hs | 2 - src/Chainweb/Pact4/TransactionExec.hs | 101 ++++++------- src/Chainweb/Pact4/Types.hs | 79 ++++------ src/Chainweb/Pact4/Validations.hs | 3 - src/Chainweb/PayloadProvider/EVM.hs | 3 +- src/Chainweb/PayloadProvider/Minimal.hs | 36 +++-- .../PayloadProvider/Minimal/Payload.hs | 1 - src/Chainweb/PayloadProvider/Pact.hs | 39 +++-- .../PayloadProvider/Pact/Configuration.hs | 1 - src/Chainweb/PayloadProvider/Pact/Genesis.hs | 40 +++-- src/Chainweb/RestAPI.hs | 2 - src/Chainweb/RestAPI/Orphans.hs | 1 - src/Chainweb/SPV/CreateProof.hs | 19 +-- src/Chainweb/SPV/VerifyProof.hs | 3 - .../VerifierPlugin/Hyperlane/Message.hs | 8 +- src/Chainweb/Version/RecapDevelopment.hs | 6 +- test/lib/Chainweb/Test/MultiNode.hs | 10 +- test/lib/Chainweb/Test/Orphans/Internal.hs | 120 +++++---------- test/lib/Chainweb/Test/Pact/Utils.hs | 83 ++++------- test/lib/Chainweb/Test/Pact4/Utils.hs | 65 +++----- test/lib/Chainweb/Test/TestVersions.hs | 15 +- test/lib/Chainweb/Test/Utils.hs | 60 +++----- test/unit/Chainweb/Test/CutDB.hs | 81 ++++------ test/unit/Chainweb/Test/Mempool.hs | 22 ++- test/unit/Chainweb/Test/Mempool/RestAPI.hs | 31 ++-- test/unit/Chainweb/Test/Mining.hs | 30 +--- test/unit/Chainweb/Test/Misc.hs | 34 ++--- .../Chainweb/Test/Pact/CheckpointerTest.hs | 23 ++- .../Chainweb/Test/Pact/PactServiceTest.hs | 82 +++++----- .../unit/Chainweb/Test/Pact/RemotePactTest.hs | 141 +++++++++--------- .../Chainweb/Test/Pact/TransactionExecTest.hs | 20 +-- test/unit/Chainweb/Test/Roundtrips.hs | 52 +++---- test/unit/ChainwebTests.hs | 104 ++++++------- 49 files changed, 644 insertions(+), 1042 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index f599a4f3b9..d24a1dabe0 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -544,7 +544,6 @@ library chainweb-test-utils , bytestring >= 0.10.12 , case-insensitive >= 1.2 , chainweb-storage >= 0.1 - , chronos >= 1.1 , containers >= 0.5 , crypton-connection >=0.4 , data-dword >= 0.3 @@ -553,8 +552,6 @@ library chainweb-test-utils , directory >= 1.2 , exceptions , filepath >= 1.4 - , hashable - , hashes >=0.2.2.0 , http-client >= 0.5 , http-types >= 0.12 , lens >= 4.17 @@ -701,7 +698,6 @@ test-suite chainweb-tests , crypton-connection >=0.4 , data-dword >= 0.3 , data-ordlist >= 0.4.7 - , deepseq >= 1.4 , direct-sqlite >= 2.3.27 , ethereum , exceptions @@ -726,10 +722,8 @@ test-suite chainweb-tests , pact-tng:pact-request-api , pact-tng:test-utils , pact-tng:pact-repl - , patience >= 0.3 , prettyprinter , property-matchers ^>= 0.7 - , pretty-show , quickcheck-instances >= 0.3 , random >= 1.3 , raw-strings-qq >=1.1 @@ -739,10 +733,8 @@ test-suite chainweb-tests , scheduler >= 1.4 , servant-client >= 0.18.2 , sha-validation >=0.1 - , statistics >= 0.15 , stm , streaming >= 0.2.2 - , strict-concurrency >= 0.2 , tasty >= 1.0 , tasty-hedgehog >= 1.4.0.2 , tasty-hunit >= 0.9 @@ -757,12 +749,12 @@ test-suite chainweb-tests , wai >= 3.2 , warp >= 3.3.6 , warp-tls >= 3.4 - , yaml >= 0.11 if flag(ed25519) cpp-options: -DWITH_ED25519=1 test-suite compaction-tests import: warning-flags, debugging-flags + buildable: False default-language: Haskell2010 type: exitcode-stdio-1.0 ghc-options: diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index 56bcddbd31..b3be442c9c 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -45,7 +45,6 @@ import Data.Foldable import Data.Function import Data.Functor.Of import Data.IORef -import qualified Data.Map.Strict as Map import Data.Proxy import Data.Text.Encoding (decodeUtf8) import Numeric.Natural(Natural) @@ -73,7 +72,6 @@ import Chainweb.RestAPI.Utils import Chainweb.TreeDB import Chainweb.Utils.Paging import Chainweb.Version -import qualified Data.Text as Text -- -------------------------------------------------------------------------- -- -- Handler Tools diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 3b8fd843c5..4598c175bc 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -88,7 +88,6 @@ import Chainweb.Logger import Chainweb.Pact.Mempool.InMem qualified as Mempool import Chainweb.Pact.Mempool.InMem.ValidatingConfig qualified as Mempool import Chainweb.Pact.PactService qualified as Pact -import Chainweb.Pact.RestAPI qualified as Pact.RestAPI import Chainweb.Pact.RestAPI.Server qualified as Pact.RestAPI.Server import Chainweb.Pact.Types import qualified Chainweb.Pact.Payload.PayloadStore as Pact.Payload.PayloadStore diff --git a/src/Chainweb/Chainweb/MempoolSyncClient.hs b/src/Chainweb/Chainweb/MempoolSyncClient.hs index 4ca077f25e..8e70ca1e6e 100644 --- a/src/Chainweb/Chainweb/MempoolSyncClient.hs +++ b/src/Chainweb/Chainweb/MempoolSyncClient.hs @@ -19,36 +19,19 @@ module Chainweb.Chainweb.MempoolSyncClient import Chainweb.Time -import Control.Lens hiding ((.=), (<.>)) -import Control.Monad -import Control.Monad.Catch - -import qualified Data.Text as T - import qualified Network.HTTP.Client as HTTP import Prelude hiding (log) -import System.LogLevel - -- internal modules -import Chainweb.ChainId import Chainweb.Chainweb.ChainResources import Chainweb.Chainweb.PeerResources import Chainweb.Logger -import qualified Chainweb.Pact.Mempool.Mempool as Mempool import Chainweb.Pact.Mempool.P2pConfig -import qualified Chainweb.Pact.Mempool.RestAPI.Client as MPC -import Chainweb.RestAPI.NetworkID -import Chainweb.Utils import Chainweb.Version -import P2P.Node.Configuration import P2P.Session -import P2P.Node - -import qualified Servant.Client as Sv -- -------------------------------------------------------------------------- -- -- Mempool sync. diff --git a/src/Chainweb/Cut/Create.hs b/src/Chainweb/Cut/Create.hs index f02477aa26..bf078460bc 100644 --- a/src/Chainweb/Cut/Create.hs +++ b/src/Chainweb/Cut/Create.hs @@ -113,7 +113,6 @@ import Data.These import GHC.Generics (Generic) import GHC.Stack import Numeric.Natural - import Chainweb.BlockCreationTime import Chainweb.BlockHash import Chainweb.BlockHeader @@ -131,19 +130,17 @@ import Chainweb.Utils.Serialization import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB -import Data.Monoid +import Chainweb.Ranked (Ranked(_rankedHeight)) import Chainweb.TreeDB hiding (parent) -import Data.Function -import qualified Streaming.Prelude as S import Data.Foldable -import Data.Maybe -import qualified Data.List as List +import Data.Function +import Data.List qualified as List import Data.List.NonEmpty (NonEmpty) -import qualified Data.List.NonEmpty as NE -import Data.Map.Strict (Map) -import qualified Data.Map.Strict as Map -import qualified Streaming as S -import Chainweb.Ranked (Ranked(_rankedHeight)) +import Data.List.NonEmpty qualified as NE +import Data.Maybe +import Data.Monoid +import Streaming qualified as S +import Streaming.Prelude qualified as S -- -------------------------------------------------------------------------- -- -- Adjacent Parent Hashes diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index efbb6abcbe..98c3d6746d 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -1,5 +1,6 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE BlockArguments #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} @@ -14,14 +15,13 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE BlockArguments #-} -{-# LANGUAGE RecordWildCards #-} -- | -- Module: Chainweb.Miner.Coordinator @@ -65,7 +65,7 @@ import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainValue import Chainweb.Core.Brief -import Chainweb.Cut hiding (join) +import Chainweb.Cut import Chainweb.Cut.Create import Chainweb.Cut.CutHashes import Chainweb.CutDB @@ -79,37 +79,30 @@ import Chainweb.Storage.Table import Chainweb.Time (Micros(..), getCurrentTimeIntegral) import Chainweb.Utils hiding (check) import Chainweb.Version -import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB import Control.Applicative -import Control.Concurrent.Async import Control.Concurrent.STM (atomically, STM, retry) import Control.Concurrent.STM.TVar -import Control.Concurrent.STM.TMVar import Control.Exception.Safe import Control.Lens import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class import Control.Monad.Trans.Class -import Data.Foldable import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS import Data.Hashable +import Data.List qualified as List import Data.LogMessage (JsonLog(..), LogFunction, LogFunctionText) -import Data.Map.Strict qualified as M import Data.Maybe import Data.Text qualified as T import Data.Vector qualified as V import GHC.Generics (Generic) -import GHC.Stack import Numeric.Natural +import Pact.JSON.Encode qualified as J import Streaming.Prelude qualified as S import System.LogLevel (LogLevel(..)) import System.Random (randomRIO) -import qualified Data.Aeson as Aeson -import qualified Data.List as List -import qualified Pact.JSON.Encode as J -- -------------------------------------------------------------------------- -- -- Utils diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 3c89a7b6cb..ac322d8933 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -1,26 +1,26 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE EmptyCase #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RankNTypes #-} {-# LANGUAGE PartialTypeSignatures #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE ViewPatterns #-} -- TODO pact5: fix the orphan PactDbFor instance {-# OPTIONS_GHC -Wno-orphans #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE EmptyCase #-} -- | The database operations that manipulate and read the Pact state. diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index ee7407836b..2d7b9b28eb 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -68,7 +68,6 @@ import Chainweb.Pact.Payload import Chainweb.Pact.Payload.PayloadStore import Chainweb.PayloadProvider import Chainweb.PayloadProvider.P2P -import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest import Chainweb.Ranked import Chainweb.Storage.Table import Chainweb.Storage.Table.Map qualified as MapTable diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 945e4e7391..10039fa6a8 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -1,27 +1,27 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveFoldable #-} +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DeriveTraversable #-} +{-# LANGUAGE DerivingVia #-} {-# LANGUAGE EmptyCase #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE DerivingVia #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE BlockArguments #-} -{-# LANGUAGE DeriveFunctor #-} -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveTraversable #-} -{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Pact.PactService.Checkpointer @@ -62,19 +62,17 @@ import Data.Maybe import Data.Monoid hiding (Product (..)) import GHC.Stack import Prelude hiding (lookup) - -import qualified Pact.Core.Persistence.Types as Pact - +import Pact.Core.Persistence.Types qualified as Pact import Chainweb.BlockCreationTime import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.MinerReward -import qualified Chainweb.Pact.Backend.ChainwebPactDb as ChainwebPactDb +import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils -import qualified Chainweb.Pact.Backend.Utils as PactDb +import Chainweb.Pact.Backend.Utils qualified as PactDb import Chainweb.Pact.Types import Chainweb.Parent import Chainweb.PayloadProvider @@ -85,21 +83,19 @@ import Chainweb.Version import Chainweb.Version.Guards (pact5) import Control.Exception.Safe (MonadMask) import Data.List.NonEmpty (NonEmpty) -import qualified Data.List.NonEmpty as NE import Chainweb.Pact.Backend.ChainwebPactDb (lookupRankedBlockHash) import Control.Monad.State.Strict import System.LogLevel -import qualified Chainweb.Pact4.Backend.ChainwebPactDb as Pact4 +import Chainweb.Pact4.Backend.ChainwebPactDb qualified as Pact4 import Control.Concurrent.MVar -import qualified Pact.Types.Persistence as Pact4 +import Pact.Types.Persistence qualified as Pact4 import Chainweb.Pact.Backend.DbCache (defaultModuleCacheLimit) -import qualified Pact.Interpreter as Pact4 -import qualified Data.ByteString.Short as BS +import Pact.Interpreter qualified as Pact4 +import Data.ByteString.Short qualified as BS import Data.Coerce -import qualified Data.HashMap.Strict as HashMap -import qualified Pact.Types.Command as Pact4 -import qualified Pact.Types.Hash as Pact4 -import Data.Foldable (foldlM) +import Data.HashMap.Strict qualified as HashMap +import Pact.Types.Command qualified as Pact4 +import Pact.Types.Hash qualified as Pact4 import Data.Foldable1 (foldlMapM1) -- readFrom demands to know this context in case it's mining a block. diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index f20b018927..9b51d8f0d0 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -24,62 +24,59 @@ module Chainweb.Pact.PactService.ExecBlock ) where import Chainweb.Logger -import Chainweb.Pact.Mempool.Mempool(BlockFill (..), pactRequestKeyToTransactionHash, InsertError (..)) import Chainweb.Miner.Pact -import Chainweb.Pact.Types -import Chainweb.Pact.Transaction -import Chainweb.Pact.TransactionExec +import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Mempool.Mempool(BlockFill (..), pactRequestKeyToTransactionHash, InsertError (..)) +import Chainweb.Pact.NoCoinbase import Chainweb.Pact.Payload +import Chainweb.Pact.Payload qualified as Chainweb import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Transaction +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.TransactionExec +import Chainweb.Pact.Types +import Chainweb.Pact.Types qualified as Pact +import Chainweb.Pact.Validations qualified as Pact import Chainweb.Time import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Guards import Chronos qualified -import Control.DeepSeq -import Control.Exception (evaluate) import Control.Lens import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class -import Control.Monad.Reader import Control.Monad.State.Strict +import Data.Aeson qualified as Aeson import Data.ByteString (ByteString) +import Data.ByteString.Short qualified as SB import Data.Coerce import Data.Either (partitionEithers) import Data.Foldable +import Data.HashMap.Strict qualified as HashMap +import Data.List.NonEmpty qualified as NEL import Data.Maybe +import Data.Set qualified as S import Data.Text qualified as T +import Data.Text.Encoding qualified as T import Data.Vector (Vector) import Data.Vector qualified as V import Data.Void import Pact.Core.ChainData hiding (ChainId) +import Pact.Core.ChainData qualified as Pact import Pact.Core.Command.Types qualified as Pact -import Pact.Core.Persistence qualified as Pact +import Pact.Core.Errors qualified as Pact +import Pact.Core.Evaluate qualified as Pact +import Pact.Core.Gas qualified as P +import Pact.Core.Gas qualified as Pact import Pact.Core.Hash -import qualified Pact.Core.Gas as Pact -import qualified Pact.JSON.Encode as J +import Pact.Core.Hash qualified as Pact +import Pact.Core.Persistence qualified as Pact +import Pact.JSON.Encode qualified as J +import System.LogLevel import System.Timeout import Utils.Logging.Trace -import qualified Data.Set as S -import qualified Pact.Core.Gas as P -import qualified Data.Text.Encoding as T -import qualified Data.HashMap.Strict as HashMap -import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact -import qualified Chainweb.Pact.Transaction as Pact -import qualified Chainweb.Pact.Validations as Pact -import Chainweb.Pact.NoCoinbase -import Chainweb.Pact.Backend.Types -import qualified Data.ByteString.Short as SB -import qualified Pact.Core.Hash as Pact -import System.LogLevel -import qualified Data.Aeson as Aeson -import qualified Data.List.NonEmpty as NEL -import qualified Pact.Core.Errors as Pact -import qualified Pact.Core.Evaluate as Pact -import qualified Pact.Core.ChainData as Pact -import qualified Chainweb.Pact.Payload as Chainweb -import qualified Chainweb.Pact.Types as Pact runCoinbase :: (Logger logger) diff --git a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs index 0f87997552..bb40644700 100644 --- a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs @@ -14,6 +14,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Pact.PactService.Pact4.ExecBlock @@ -36,80 +37,62 @@ module Chainweb.Pact.PactService.Pact4.ExecBlock , checkParse ) where -import Chronos qualified +import Chainweb.BlockCreationTime +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.Logger +import Chainweb.Miner.Pact +import Chainweb.MinerReward +import Chainweb.Pact.Mempool.Mempool as Mempool +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Types (ServiceEnv(..), Transactions (..), bctxParentCreationTime, bctxParentHeight, _bctxIsGenesis, _bctxCurrentBlockHeight, BlockCtx, BlockInvalidError (..), TxInvalidError (..)) +import Chainweb.Pact4.Backend.ChainwebPactDb +import Chainweb.Pact4.ModuleCache +import Chainweb.Pact4.NoCoinbase +import Chainweb.Pact4.Transaction qualified as Pact4 +import Chainweb.Pact4.TransactionExec qualified as Pact4 +import Chainweb.Pact4.Types +import Chainweb.Pact4.Validations qualified as Pact4 +import Chainweb.Parent +import Chainweb.Utils hiding (check) +import Chainweb.Version +import Chainweb.Version.Guards import Control.Concurrent.MVar -import Control.DeepSeq import Control.Exception (evaluate) import Control.Lens import Control.Monad import Control.Monad.Catch +import Control.Monad.Except import Control.Monad.Reader import Control.Monad.State.Strict - -import System.LogLevel (LogLevel(..)) -import qualified Data.ByteString.Short as SB -import Data.List qualified as List +import Data.ByteString.Short qualified as SB +import Data.Coerce import Data.Either import Data.Foldable (toList) -import qualified Data.HashMap.Strict as HashMap -import qualified Data.Map as Map +import Data.HashMap.Strict qualified as HashMap +import Data.List qualified as List +import Data.List.NonEmpty qualified as NE +import Data.Map qualified as M +import Data.Map qualified as Map import Data.Maybe import Data.Text (Text) -import qualified Data.Text as T +import Data.Text qualified as T import Data.Vector (Vector) -import qualified Data.Vector as V - -import System.IO -import System.Timeout - -import Prelude hiding (lookup) - +import Data.Vector qualified as V import Pact.Compile (compileExps) -import Pact.Interpreter(PactDbEnv(..)) -import qualified Pact.JSON.Encode as J -import qualified Pact.Parse as Pact4 hiding (parsePact) -import qualified Pact.Types.Command as Pact4 +import Pact.Core.Command.Types qualified as Pact5 +import Pact.Core.Hash qualified as Pact5 +import Pact.JSON.Encode qualified as J +import Pact.Parse qualified as Pact4 hiding (parsePact) +import Pact.Types.Command qualified as Pact4 import Pact.Types.ExpParser (mkTextInfo, ParseEnv(..)) -import qualified Pact.Types.Hash as Pact4 +import Pact.Types.Hash qualified as Pact4 import Pact.Types.RPC -import qualified Pact.Types.Runtime as Pact4 -import qualified Pact.Types.SPV as Pact4 - -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Logger -import Chainweb.Pact.Mempool.Mempool as Mempool -import Chainweb.MinerReward -import Chainweb.Miner.Pact - --- import Chainweb.Pact.Types -import Chainweb.Pact4.SPV qualified as Pact4 -import Chainweb.Pact4.NoCoinbase -import qualified Chainweb.Pact4.Transaction as Pact4 -import qualified Chainweb.Pact4.TransactionExec as Pact4 -import qualified Chainweb.Pact4.Validations as Pact4 -import Chainweb.Pact.Payload -import Chainweb.Pact.Payload.PayloadStore -import Chainweb.Time -import Chainweb.Utils hiding (check) -import Chainweb.Version -import Chainweb.Version.Guards -import Chainweb.Pact4.Backend.ChainwebPactDb -import Data.Coerce -import Data.Word -import Control.Monad.Primitive -import qualified Data.Set as S -import Chainweb.Pact4.Types -import Chainweb.Pact4.ModuleCache -import Control.Monad.Except -import qualified Data.List.NonEmpty as NE -import Chainweb.Pact.Backend.Types (BlockHandle(..)) -import Chainweb.Parent -import Chainweb.BlockCreationTime -import qualified Data.Map as M -import Chainweb.Pact.Types (ServiceEnv(..), Transactions (..), transactionPairs, bctxParentCreationTime, bctxParentHeight, _bctxIsGenesis, _bctxCurrentBlockHeight, BlockCtx, BlockInvalidError (..), TxInvalidError (..)) -import qualified Pact.Core.Command.Types as Pact5 -import qualified Pact.Core.Hash as Pact5 +import Pact.Types.Runtime qualified as Pact4 +import Pact.Types.SPV qualified as Pact4 +import Prelude hiding (lookup) +import System.LogLevel (LogLevel(..)) -- | Update init cache at adjusted parent block height (APBH). -- Contents are merged with cache found at or before APBH. diff --git a/src/Chainweb/Pact/Payload/PayloadStore.hs b/src/Chainweb/Pact/Payload/PayloadStore.hs index 9119018258..3b04697278 100644 --- a/src/Chainweb/Pact/Payload/PayloadStore.hs +++ b/src/Chainweb/Pact/Payload/PayloadStore.hs @@ -5,6 +5,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PolyKinds #-} @@ -14,7 +15,6 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE InstanceSigs #-} -- | -- Module: Chainweb.Pact.Payload.PayloadStore @@ -72,31 +72,20 @@ module Chainweb.Pact.Payload.PayloadStore ) where import Control.Applicative +import Chainweb.BlockHeight +import Chainweb.Crypto.MerkleLog +import Chainweb.MerkleUniverse +import Chainweb.Pact.Payload +import Chainweb.Ranked +import Chainweb.Storage.Table import Control.DeepSeq import Control.Exception import Control.Lens import Control.Monad.IO.Class import Control.Monad.Trans.Maybe - import Data.Hashable -import Data.Foldable - -import GHC.Generics - --- internal modules - -import Chainweb.ChainId -import Chainweb.Crypto.MerkleLog -import Chainweb.MerkleUniverse -import Chainweb.Pact.Payload -import Chainweb.Version - -import Chainweb.Storage.Table -import Chainweb.BlockHeight -import Chainweb.PayloadProvider.Pact.Genesis -import Chainweb.BlockPayloadHash (RankedBlockPayloadHash) -import Chainweb.Ranked import Data.Maybe (isJust) +import GHC.Generics -- -------------------------------------------------------------------------- -- -- Exceptions diff --git a/src/Chainweb/Pact/RestAPI/Server.hs b/src/Chainweb/Pact/RestAPI/Server.hs index 73edabc196..5a92c0bca8 100644 --- a/src/Chainweb/Pact/RestAPI/Server.hs +++ b/src/Chainweb/Pact/RestAPI/Server.hs @@ -33,21 +33,16 @@ module Chainweb.Pact.RestAPI.Server ) where import Control.Applicative -import Control.Concurrent.STM (atomically) import Control.Concurrent.STM.TVar import Control.DeepSeq import Control.Lens hiding ((.=)) import Control.Monad -import Control.Monad.Catch hiding (Handler) import Control.Monad.Reader -import Control.Monad.State.Strict import Control.Monad.Trans.Except (ExceptT, runExceptT, except) import Data.Aeson as Aeson hiding (Success) import Data.Bifunctor (second) -import Data.ByteString (ByteString) import qualified Data.ByteString.Lazy as BSL -import qualified Data.ByteString.Lazy.Char8 as BSL8 import qualified Data.ByteString.Short as SB import Data.Either (partitionEithers, isRight) import Data.Foldable @@ -58,7 +53,6 @@ import qualified Data.List as L import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NEL import Data.Maybe -import Data.Singletons import Data.Text (Text) import qualified Data.Text as T import Data.Text.Encoding @@ -66,80 +60,49 @@ import Data.Validation import Data.Vector (Vector) import qualified Data.Vector as V -import Ethereum.Block -import Ethereum.Header hiding (blockHash) -import Ethereum.Misc (bytes) -import Ethereum.Receipt -import Ethereum.Receipt.ReceiptProof -import Ethereum.RLP (putRlpByteString) - import GHC.Generics import GHC.Stack -import Numeric.Natural - import Prelude hiding (init, lookup) import Servant -import qualified Streaming.Prelude as S - import System.LogLevel -- internal modules import Chainweb.BlockHash import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB import Chainweb.BlockHeight import Chainweb.ChainId -import Chainweb.Crypto.MerkleLog -import Chainweb.Cut -import qualified Chainweb.CutDB as CutDB -import Chainweb.Graph import Chainweb.Logger import Chainweb.Pact.Mempool.Mempool (InsertError(..), InsertType(..), MempoolBackend(..), TransactionHash(..), pactRequestKeyToTransactionHash) import Chainweb.Pact.RestAPI -import Chainweb.Pact.RestAPI.EthSpv -import Chainweb.Pact.RestAPI.SPV import Chainweb.Pact.Types -import Chainweb.Pact.SPV qualified as SPV import Chainweb.Pact.Payload import Chainweb.Pact.Payload.PayloadStore import Chainweb.RestAPI.Orphans () import Chainweb.RestAPI.Utils -import Chainweb.SPV (SpvException(..)) -import Chainweb.SPV.CreateProof -import Chainweb.SPV.EventProof -import Chainweb.SPV.OutputProof -import Chainweb.SPV.PayloadProof -import Chainweb.Pact.Transaction qualified as Pact hiding (parsePact) -import Chainweb.TreeDB qualified as TreeDB +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Utils import Chainweb.Version import Chainweb.Pact.Validations qualified as Pact -import Chainweb.Version.Guards (validPPKSchemes) import qualified Pact.JSON.Encode as J import qualified Pact.Core.Command.Types as Pact import qualified Pact.Core.Pretty as Pact -import qualified Chainweb.Pact.Transaction as Pact import qualified Chainweb.Pact.Types as Pact -import qualified Chainweb.Pact.Validations as Pact import Data.Coerce import qualified Pact.Core.Command.Server as Pact import qualified Pact.Core.Errors as Pact import qualified Pact.Core.Hash as Pact import qualified Pact.Core.Gas as Pact import qualified Pact.Core.Command.Client as Pact -import qualified Pact.Core.ChainData as Pact -import Chainweb.PayloadProvider.Pact (PactPayloadProvider (..)) import Chainweb.PayloadProvider.P2P import Chainweb.Pact.PactService import Chainweb.Pact.Backend.Types (Historical(..), throwIfNoHistory) -import qualified Pact.Core.StableEncoding as Pact import qualified Pact.Core.Info as Pact import Control.Concurrent (threadDelay) diff --git a/src/Chainweb/Pact/SPV.hs b/src/Chainweb/Pact/SPV.hs index ac6bde93d2..c232137426 100644 --- a/src/Chainweb/Pact/SPV.hs +++ b/src/Chainweb/Pact/SPV.hs @@ -12,8 +12,7 @@ module Chainweb.Pact.SPV (pactSPV) where import Control.Lens import Control.Monad (when) -import Control.Monad.Except (ExceptT, runExceptT, throwError) -import Control.Monad.IO.Class (liftIO) +import Control.Monad.Except (runExceptT, throwError) import Data.Aeson qualified as Aeson import Data.Text (Text) import Data.Text.Encoding qualified as Text @@ -24,14 +23,12 @@ import Pact.Core.PactValue (ObjectData(..), PactValue(..)) import Pact.Core.SPV (SPVSupport(..), ContProof (..)) import Pact.Core.StableEncoding (encodeStable) -import Chainweb.Crypto.MerkleLog import Chainweb.MerkleUniverse import Chainweb.Pact.Backend.Types -import Chainweb.Parent import Chainweb.Pact.Payload(TransactionOutput(..)) import Chainweb.SPV (TransactionOutputProof(..), outputProofChainId) -import Chainweb.SPV.VerifyProof (runTransactionOutputProof, checkProofAndExtractOutput) -import Chainweb.Utils (decodeB64UrlNoPaddingText, unlessM) +import Chainweb.SPV.VerifyProof (checkProofAndExtractOutput) +import Chainweb.Utils (decodeB64UrlNoPaddingText) import Chainweb.Version qualified as CW pactSPV :: HeaderOracle -> SPVSupport diff --git a/src/Chainweb/Pact/Types.hs b/src/Chainweb/Pact/Types.hs index c8c38830a1..da5c2d70fa 100644 --- a/src/Chainweb/Pact/Types.hs +++ b/src/Chainweb/Pact/Types.hs @@ -137,7 +137,6 @@ import Control.Applicative ((<|>)) import Control.DeepSeq import Control.Lens import Control.Monad.IO.Class -import Control.Monad.State.Strict import Data.Aeson hiding (Error, (.=)) import Data.Bool import Data.ByteString (ByteString) @@ -155,14 +154,12 @@ import GHC.Generics (Generic) import Pact.Core.Builtin qualified as Pact import Pact.Core.Capabilities qualified as Pact import Pact.Core.ChainData qualified as Pact -import Pact.Core.Command.Types (RequestKey) import Pact.Core.Errors qualified as Pact import Pact.Core.Evaluate qualified as Pact import Pact.Core.Gas.Types qualified as Pact import Pact.Core.Hash qualified as Pact import Pact.Core.Info qualified as Pact import Pact.Core.Literal qualified as Pact -import Pact.Core.Persistence import Pact.Core.StableEncoding qualified as Pact import Pact.JSON.Encode qualified as J import System.LogLevel @@ -187,7 +184,6 @@ import Chainweb.Version import Pact.Core.Command.Types qualified as Pact import Pact.Core.Persistence qualified as Pact -import Pact.Core.SPV qualified as Pact import Servant.API import Data.List.NonEmpty (NonEmpty) import Chainweb.PayloadProvider diff --git a/src/Chainweb/Pact/Validations.hs b/src/Chainweb/Pact/Validations.hs index a64daf425a..4aca2370ba 100644 --- a/src/Chainweb/Pact/Validations.hs +++ b/src/Chainweb/Pact/Validations.hs @@ -59,7 +59,6 @@ import qualified Pact.Core.Hash as Pact import qualified Chainweb.Pact.Transaction as Pact import Chainweb.Utils (ebool_, int) import Chainweb.Version.Guards (maxBlockGasLimit) -import Control.Monad (forM) import Numeric.Natural diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs index 5d59b7bf91..a66b6e3b31 100644 --- a/src/Chainweb/Pact4/SPV.hs +++ b/src/Chainweb/Pact4/SPV.hs @@ -47,8 +47,6 @@ import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import Text.Read (readMaybe) -import Crypto.Hash.Algorithms - import qualified Ethereum.Header as EthHeader import Ethereum.Misc import Ethereum.Receipt diff --git a/src/Chainweb/Pact4/TransactionExec.hs b/src/Chainweb/Pact4/TransactionExec.hs index e9c8f671a0..80d3ff2f0b 100644 --- a/src/Chainweb/Pact4/TransactionExec.hs +++ b/src/Chainweb/Pact4/TransactionExec.hs @@ -1,18 +1,20 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE DataKinds #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE MonoLocalBinds #-} {-# LANGUAGE TypeApplications #-} + -- | -- Module : Chainweb.Pact4.TransactionExec -- Copyright : Copyright © 2018 Kadena LLC. @@ -85,6 +87,29 @@ module Chainweb.Pact4.TransactionExec ) where +import Chainweb.BlockCreationTime +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.ChainId qualified as Chainweb +import Chainweb.Logger +import Chainweb.Miner.Pact +import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact5 +import Chainweb.Pact.Types (BlockCtx, guardCtx, _bctxCurrentBlockHeight, bctxParentCreationTime, bctxParentHash, bctxParentHeight) +import Chainweb.Pact4.Backend.ChainwebPactDb +import Chainweb.Pact4.ModuleCache +import Chainweb.Pact4.Templates +import Chainweb.Pact4.Transaction +import Chainweb.Pact4.Types +import Chainweb.Parent +import Chainweb.Ranked +import Chainweb.Time hiding (second) +import Chainweb.Utils +import Chainweb.VerifierPlugin +import Chainweb.Version as V +import Chainweb.Version.Guards as V +import Chainweb.Version.Utils as V +import Control.Concurrent.MVar import Control.DeepSeq import Control.Lens import Control.Monad @@ -93,30 +118,28 @@ import Control.Monad.Reader import Control.Monad.State.Strict import Control.Monad.Trans.Maybe import Control.Parallel.Strategies(using, rseq) - import Data.Aeson hiding ((.=), Error) -import qualified Data.Aeson as A +import Data.Aeson qualified as A import Data.Bifunctor -import qualified Data.ByteString as B -import qualified Data.ByteString.Short as SB +import Data.ByteString qualified as B +import Data.ByteString.Short qualified as SB import Data.Decimal (Decimal, roundTo) -import Data.Foldable (fold, for_, traverse_) +import Data.Foldable (fold, for_) import Data.IORef -import qualified Data.List as List -import qualified Data.Map.Strict as M +import Data.Int (Int64) +import Data.List qualified as List +import Data.Map.Strict qualified as M import Data.Maybe -import qualified Data.Set as S +import Data.Set (Set) +import Data.Set qualified as S import Data.Text (Text) -import qualified Data.Text as T -import System.LogLevel - --- internal Pact modules - -import Chainweb.Counter +import Data.Text qualified as T +import Pact.Core.Errors (VerifierError(..)) +import Pact.Core.Gas qualified as Pact5 import Pact.Eval (eval, liftTerm) import Pact.Gas (freeGasEnv) import Pact.Interpreter -import qualified Pact.JSON.Encode as J +import Pact.JSON.Encode (toJsonViaEncode) import Pact.JSON.Legacy.Value import Pact.Native.Capabilities (evalCap) import Pact.Native.Internal (appToCap) @@ -125,53 +148,19 @@ import Pact.Runtime.Capabilities (popCapStack) import Pact.Runtime.Utils (lookupModule) import Pact.Types.Capability import Pact.Types.Command +import Pact.Types.Gas qualified as Pact import Pact.Types.Hash as Pact import Pact.Types.KeySet import Pact.Types.PactValue import Pact.Types.Pretty import Pact.Types.RPC import Pact.Types.Runtime hiding (Info, catchesPactError) -import Pact.Types.Server import Pact.Types.SPV -import Pact.Types.Verifier - +import Pact.Types.Server import Pact.Types.Util as PU -import qualified Pact.Utils.StableHashMap as SHM - --- internal Chainweb modules - -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.Logger -import qualified Chainweb.ChainId as Chainweb -import Chainweb.Pact.Mempool.Mempool (pactRequestKeyToTransactionHash) -import Chainweb.Miner.Pact -import Chainweb.Pact4.Templates -import Chainweb.Pact4.Types -import Chainweb.Pact4.Transaction -import Chainweb.Utils -import Chainweb.VerifierPlugin -import Chainweb.Version as V -import Chainweb.Version.Guards as V -import Chainweb.Version.Utils as V -import Pact.JSON.Encode (toJsonViaEncode) -import Data.Set (Set) -import Chainweb.Pact4.ModuleCache -import Chainweb.Pact4.Backend.ChainwebPactDb - -import Pact.Core.Errors (VerifierError(..)) -import Chainweb.Parent -import Chainweb.Pact.Types (ServiceEnv, BlockCtx (_bctxParentHeight), guardCtx, _bctxCurrentBlockHeight, bctxParentCreationTime, bctxParentHash, bctxParentHeight) +import Pact.Types.Verifier +import Pact.Utils.StableHashMap qualified as SHM import System.LogLevel -import qualified Pact.Core.Gas as Pact5 -import qualified Pact.Types.Gas as Pact -import Data.Int (Int64) -import Chainweb.BlockCreationTime -import Chainweb.Time hiding (second) -import Chainweb.BlockHash -import Chainweb.Ranked -import Control.Concurrent.MVar -import qualified Chainweb.Pact.Backend.ChainwebPactDb as Pact5 -- | Convert context to datatype for Pact environment. -- diff --git a/src/Chainweb/Pact4/Types.hs b/src/Chainweb/Pact4/Types.hs index 737d01929b..72353b4c17 100644 --- a/src/Chainweb/Pact4/Types.hs +++ b/src/Chainweb/Pact4/Types.hs @@ -1,14 +1,15 @@ {-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE DataKinds #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} module Chainweb.Pact4.Types @@ -49,60 +50,42 @@ module Chainweb.Pact4.Types , getGasModel ) where +import Chainweb.Logger +import Chainweb.Miner.Pact +import Chainweb.Pact.Payload(PayloadWithOutputs, newBlockOutputs, blockPayload, payloadData, payloadWithOutputs, newBlockTransactions, Transaction (..), TransactionOutput (..), CoinbaseOutput (..)) +import Chainweb.Pact.Types (Transactions(..), BlockCtx, guardCtx) +import Chainweb.Pact4.Transaction qualified as Pact +import Chainweb.Utils +import Chainweb.Version +import Chainweb.Version.Guards import Control.Exception.Safe import Control.Lens import Control.Monad.Reader - import Data.Aeson hiding (Error,(.=)) -import qualified Data.Map.Strict as M +import Data.ByteString (ByteString) +import Data.ByteString.Short qualified as SB +import Data.DList (DList) +import Data.HashMap.Strict (HashMap) +import Data.HashSet (HashSet) +import Data.List.NonEmpty (NonEmpty) +import Data.Map.Strict qualified as M import Data.Text (Text) - --- internal pact modules - -import qualified Pact.JSON.Encode as J +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import GHC.Generics +import GHC.Stack (CallStack, HasCallStack, callStack) +import Pact.Gas.Table +import Pact.JSON.Encode qualified as J import Pact.Parse (ParsedDecimal) import Pact.Types.Command -import Pact.Types.ChainMeta import Pact.Types.Gas import Pact.Types.Info +import Pact.Types.Persistence qualified as Pact import Pact.Types.Pretty (viaShow) import Pact.Types.Runtime (PactError(..), PactErrorType(..)) +import Pact.Types.Runtime qualified as Pact import Pact.Types.Term - --- internal chainweb modules - -import Chainweb.BlockCreationTime -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.ChainId -import Chainweb.Miner.Pact -import Chainweb.Logger -import Chainweb.Time -import Chainweb.Utils -import Chainweb.Version - -import Pact.Gas.Table --- import Chainweb.Pact.Types -import Chainweb.Pact4.ModuleCache -import qualified Chainweb.Pact4.Transaction as Pact -import Chainweb.Version.Guards -import qualified Pact.Types.Persistence as Pact -import GHC.Stack (CallStack, HasCallStack, callStack) -import GHC.Generics -import Data.HashMap.Strict (HashMap) -import Data.HashSet (HashSet) -import Data.ByteString (ByteString) -import qualified Pact.Types.Runtime as Pact -import Data.DList (DList) -import Data.List.NonEmpty (NonEmpty) -import Chainweb.Parent import System.LogLevel -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import Chainweb.Pact.Payload(PayloadWithOutputs, newBlockOutputs, blockPayload, payloadData, payloadWithOutputs, newBlockTransactions, Transaction (..), TransactionOutput (..), CoinbaseOutput (..)) -import Chainweb.Pact.Types (Transactions(..), BlockCtx, guardCtx) -import qualified Data.ByteString.Short as SB -- | Indicates a computed gas charge (gas amount * gas price) diff --git a/src/Chainweb/Pact4/Validations.hs b/src/Chainweb/Pact4/Validations.hs index 6b62c1e5f7..e651f6d792 100644 --- a/src/Chainweb/Pact4/Validations.hs +++ b/src/Chainweb/Pact4/Validations.hs @@ -56,10 +56,8 @@ import Data.Word (Word8) -- internal modules -import Chainweb.BlockHeader import Chainweb.BlockCreationTime (BlockCreationTime(..)) import Chainweb.Pact.Types -import Chainweb.Pact.Utils (fromPactChainId) import Chainweb.Time (Seconds(..), Time(..), secondsToTimeSpan, scaleTimeSpan, second, add) import Chainweb.Pact4.Transaction import Chainweb.Version @@ -72,7 +70,6 @@ import qualified Pact.Types.Command as P import qualified Pact.Types.ChainMeta as P import qualified Pact.Types.KeySet as P import qualified Pact.Parse as P -import Chainweb.Pact4.Types import Chainweb.Utils (ebool_, int) import Chainweb.Parent import qualified Pact.Core.Gas.Types as Pact5 diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 16e8f84b0d..e5423ff897 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -67,9 +67,9 @@ import Chainweb.PayloadProvider.EVM.EngineAPI import Chainweb.PayloadProvider.EVM.EthRpcAPI import Chainweb.PayloadProvider.EVM.ExecutionPayload import Chainweb.PayloadProvider.EVM.Header qualified as EVM -import Chainweb.PayloadProvider.EVM.PayloadDB qualified as EvmDB import Chainweb.PayloadProvider.EVM.JsonRPC (JsonRpcHttpCtx, callMethodHttp) import Chainweb.PayloadProvider.EVM.JsonRPC qualified as RPC +import Chainweb.PayloadProvider.EVM.PayloadDB qualified as EvmDB import Chainweb.PayloadProvider.EVM.SPV import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) import Chainweb.PayloadProvider.EVM.Utils qualified as EVM @@ -92,7 +92,6 @@ import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Trans.Resource hiding (throwM) import Control.Monad.Writer -import Data.Aeson qualified as Aeson import Data.ByteString.Short qualified as BS import Data.List qualified as L import Data.LogMessage diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index cb086e77b6..e08995d383 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -85,25 +85,6 @@ module Chainweb.PayloadProvider.Minimal ) where import Configuration.Utils -import Control.Concurrent -import Control.Concurrent.Async -import Control.Concurrent.STM -import Control.Lens hiding ((.=)) -import Control.Monad -import Control.Monad.Catch -import Control.Monad.Except (throwError) -import Data.ByteString qualified as B -import Data.HashSet qualified as HS -import Data.LogMessage (LogFunction, LogFunctionText) -import Data.PQueue (PQueue) -import Data.Text qualified as T -import GHC.Generics (Generic) -import Network.HTTP.Client qualified as HTTP -import Numeric.Natural -import P2P.TaskQueue -import Servant.Client -import System.LogLevel - import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.BlockPayloadHash @@ -123,6 +104,23 @@ import Chainweb.Storage.Table.RocksDB import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version +import Control.Concurrent.Async +import Control.Concurrent.STM +import Control.Lens hiding ((.=)) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.Except (throwError) +import Data.ByteString qualified as B +import Data.HashSet qualified as HS +import Data.LogMessage (LogFunction, LogFunctionText) +import Data.PQueue (PQueue) +import Data.Text qualified as T +import GHC.Generics (Generic) +import Network.HTTP.Client qualified as HTTP +import Numeric.Natural +import P2P.TaskQueue +import Servant.Client +import System.LogLevel -- -------------------------------------------------------------------------- -- diff --git a/src/Chainweb/PayloadProvider/Minimal/Payload.hs b/src/Chainweb/PayloadProvider/Minimal/Payload.hs index 8f1f8b9b73..e2a2109a92 100644 --- a/src/Chainweb/PayloadProvider/Minimal/Payload.hs +++ b/src/Chainweb/PayloadProvider/Minimal/Payload.hs @@ -53,7 +53,6 @@ import Chainweb.Storage.Table import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version -import Chainweb.Version.Registry import Control.Lens (Getter) import Control.Lens.Getter (to) import Control.Monad diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 8b3cd58856..158e847fd9 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -1,22 +1,23 @@ -{-# LANGUAGE RankNTypes #-} {-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE InstanceSigs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} module Chainweb.PayloadProvider.Pact - ( PactPayloadProvider(..) - , withPactPayloadProvider - , pactMemPoolAccess - , decodeNewPayload - ) where +( PactPayloadProvider(..) +, withPactPayloadProvider +, pactMemPoolAccess +, decodeNewPayload +) where import Control.Concurrent.STM import Control.Exception.Safe @@ -24,27 +25,25 @@ import Data.LogMessage import Data.Vector (Vector) import System.LogLevel import Control.Lens -import qualified Network.HTTP.Client as HTTP -import qualified Data.Vector as V - +import Network.HTTP.Client qualified as HTTP +import Data.Vector qualified as V import Chainweb.ChainId import Chainweb.Counter import Chainweb.Logger import Chainweb.Pact.Mempool.Mempool import Chainweb.MerkleUniverse -import qualified Chainweb.MinerReward as MinerReward +import Chainweb.MinerReward qualified as MinerReward import Chainweb.Pact.Backend.Utils -import qualified Chainweb.Pact.PactService as PactService -import qualified Chainweb.Pact.Transaction as Pact -import Chainweb.Pact.Types +import Chainweb.Pact.PactService qualified as PactService +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Core.Brief import Chainweb.Pact.Payload import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Types import Chainweb.PayloadProvider import Chainweb.Utils import Chainweb.Version -import qualified Data.Pool as Pool -import Control.Monad.Trans.Resource (ResourceT, allocate) -import Chainweb.Core.Brief +import Control.Monad.Trans.Resource (ResourceT) data PactPayloadProvider logger tbl = PactPayloadProvider { pactPayloadProviderLogger :: logger diff --git a/src/Chainweb/PayloadProvider/Pact/Configuration.hs b/src/Chainweb/PayloadProvider/Pact/Configuration.hs index 3fbd644f54..478976da17 100644 --- a/src/Chainweb/PayloadProvider/Pact/Configuration.hs +++ b/src/Chainweb/PayloadProvider/Pact/Configuration.hs @@ -44,7 +44,6 @@ import Control.Monad import Data.Maybe import Data.Set qualified as Set import GHC.Generics hiding (from, to) -import Numeric.Natural (Natural) import Pact.Core.Gas qualified as Pact import Pact.Core.Guards qualified as Pact import Pact.Core.StableEncoding diff --git a/src/Chainweb/PayloadProvider/Pact/Genesis.hs b/src/Chainweb/PayloadProvider/Pact/Genesis.hs index ac1013d445..32f859207f 100644 --- a/src/Chainweb/PayloadProvider/Pact/Genesis.hs +++ b/src/Chainweb/PayloadProvider/Pact/Genesis.hs @@ -11,37 +11,35 @@ module Chainweb.PayloadProvider.Pact.Genesis ( genesisPayload ) where +import Chainweb.BlockHeader.Genesis.Development0Payload qualified as DN0 +import Chainweb.BlockHeader.Genesis.Development1to19Payload qualified as DNN +import Chainweb.BlockHeader.Genesis.Mainnet0Payload qualified as MN0 +import Chainweb.BlockHeader.Genesis.Mainnet10to19Payload qualified as MNKAD +import Chainweb.BlockHeader.Genesis.Mainnet1Payload qualified as MN1 +import Chainweb.BlockHeader.Genesis.Mainnet2Payload qualified as MN2 +import Chainweb.BlockHeader.Genesis.Mainnet3Payload qualified as MN3 +import Chainweb.BlockHeader.Genesis.Mainnet4Payload qualified as MN4 +import Chainweb.BlockHeader.Genesis.Mainnet5Payload qualified as MN5 +import Chainweb.BlockHeader.Genesis.Mainnet6Payload qualified as MN6 +import Chainweb.BlockHeader.Genesis.Mainnet7Payload qualified as MN7 +import Chainweb.BlockHeader.Genesis.Mainnet8Payload qualified as MN8 +import Chainweb.BlockHeader.Genesis.Mainnet9Payload qualified as MN9 +import Chainweb.BlockHeader.Genesis.RecapDevelopment0Payload qualified as RDN0 +import Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload qualified as RDNKAD +import Chainweb.BlockHeader.Genesis.RecapDevelopment1to9Payload qualified as RDNN +import Chainweb.BlockHeader.Genesis.Testnet040Payload qualified as T04N0 +import Chainweb.BlockHeader.Genesis.Testnet041to19Payload qualified as T04NN +import Chainweb.Pact.Payload import Chainweb.Version import Chainweb.Version.Development import Chainweb.Version.EvmDevelopment import Chainweb.Version.Mainnet import Chainweb.Version.RecapDevelopment import Chainweb.Version.Testnet04 -import Chainweb.Pact.Payload import Control.Lens import Data.HashMap.Strict qualified as HM import GHC.Stack -import qualified Chainweb.BlockHeader.Genesis.Mainnet0Payload as MN0 -import qualified Chainweb.BlockHeader.Genesis.Mainnet1Payload as MN1 -import qualified Chainweb.BlockHeader.Genesis.Mainnet2Payload as MN2 -import qualified Chainweb.BlockHeader.Genesis.Mainnet3Payload as MN3 -import qualified Chainweb.BlockHeader.Genesis.Mainnet4Payload as MN4 -import qualified Chainweb.BlockHeader.Genesis.Mainnet5Payload as MN5 -import qualified Chainweb.BlockHeader.Genesis.Mainnet6Payload as MN6 -import qualified Chainweb.BlockHeader.Genesis.Mainnet7Payload as MN7 -import qualified Chainweb.BlockHeader.Genesis.Mainnet8Payload as MN8 -import qualified Chainweb.BlockHeader.Genesis.Mainnet9Payload as MN9 -import qualified Chainweb.BlockHeader.Genesis.Mainnet10to19Payload as MNKAD -import qualified Chainweb.BlockHeader.Genesis.Development0Payload as DN0 -import qualified Chainweb.BlockHeader.Genesis.Development1to19Payload as DNN -import qualified Chainweb.BlockHeader.Genesis.Testnet040Payload as T04N0 -import qualified Chainweb.BlockHeader.Genesis.Testnet041to19Payload as T04NN -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment0Payload as RDN0 -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment1to9Payload as RDNN -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload as RDNKAD -import Chainweb.Utils - genesisPayload :: (HasCallStack, HasVersion) => ChainId -> Maybe PayloadWithOutputs diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index 48f8453d8a..3c82fc2406 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -84,8 +84,6 @@ import Chainweb.Logger (Logger) import Chainweb.Pact.Mempool.Mempool (MempoolBackend) import qualified Chainweb.Pact.Mempool.RestAPI.Server as Mempool import qualified Chainweb.Miner.RestAPI.Server as Mining --- import qualified Chainweb.Pact.RestAPI.Server as PactAPI -import qualified Chainweb.Pact.Transaction as Pact import Chainweb.Pact.Payload.RestAPI import Chainweb.RestAPI.Backup import Chainweb.RestAPI.Config diff --git a/src/Chainweb/RestAPI/Orphans.hs b/src/Chainweb/RestAPI/Orphans.hs index cacdb8785e..c081fc5b6d 100644 --- a/src/Chainweb/RestAPI/Orphans.hs +++ b/src/Chainweb/RestAPI/Orphans.hs @@ -32,7 +32,6 @@ module Chainweb.RestAPI.Orphans () where import Control.Monad -import Data.Bool import Data.Bifunctor import Data.Proxy import Data.Semigroup (Max(..), Min(..)) diff --git a/src/Chainweb/SPV/CreateProof.hs b/src/Chainweb/SPV/CreateProof.hs index dab38b667c..f3f7867ae5 100644 --- a/src/Chainweb/SPV/CreateProof.hs +++ b/src/Chainweb/SPV/CreateProof.hs @@ -1,5 +1,6 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -24,17 +25,9 @@ import Control.Applicative import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Catch - -import qualified Data.List.NonEmpty as N +import Data.List.NonEmpty qualified as N import Data.MerkleLog.Common -import qualified Data.MerkleLog.V1 as V1 - -import GHC.Stack - -import Prelude hiding (lookup) - --- internal modules - +import Data.MerkleLog.V1 qualified as V1 import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight @@ -44,14 +37,14 @@ import Chainweb.CutDB import Chainweb.Graph import Chainweb.MerkleUniverse import Chainweb.Parent -import Chainweb.Pact.Payload -import Chainweb.Pact.Payload.PayloadStore +import Chainweb.PayloadProvider import Chainweb.SPV import Chainweb.TreeDB import Chainweb.Utils import Chainweb.Version import Chainweb.WebBlockHeaderDB -import Chainweb.PayloadProvider +import GHC.Stack +import Prelude hiding (lookup) -- -------------------------------------------------------------------------- -- -- FIXME diff --git a/src/Chainweb/SPV/VerifyProof.hs b/src/Chainweb/SPV/VerifyProof.hs index 23433dbebd..da7e78ff7c 100644 --- a/src/Chainweb/SPV/VerifyProof.hs +++ b/src/Chainweb/SPV/VerifyProof.hs @@ -27,14 +27,11 @@ import Data.MerkleLog.V1 qualified as V1 -- internal modules import Chainweb.BlockHash -import Chainweb.BlockHeaderDB import Chainweb.Crypto.MerkleLog (proofSubject) -import Chainweb.CutDB import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse import Chainweb.Pact.Payload import Chainweb.SPV -import Chainweb.Version import Chainweb.Pact.Backend.Types (HeaderOracle (..)) import Chainweb.Parent import Chainweb.Utils (unlessM) diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs index 014d91cffa..16c302588b 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs @@ -1,8 +1,9 @@ {-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} -- | -- Chainweb.VerifierPlugin.Hyperlane.Message @@ -14,9 +15,8 @@ -- module Chainweb.VerifierPlugin.Hyperlane.Message (plugin) where -import Chainweb.Version.Guards import Chainweb.VerifierPlugin -import qualified Chainweb.VerifierPlugin.Hyperlane.Message.After225 as After225 +import Chainweb.VerifierPlugin.Hyperlane.Message.After225 qualified as After225 plugin :: VerifierPlugin plugin = VerifierPlugin $ \(cid, bh) proof caps gasRef -> diff --git a/src/Chainweb/Version/RecapDevelopment.hs b/src/Chainweb/Version/RecapDevelopment.hs index a54f6d675a..c214434fc9 100644 --- a/src/Chainweb/Version/RecapDevelopment.hs +++ b/src/Chainweb/Version/RecapDevelopment.hs @@ -21,9 +21,9 @@ import Chainweb.Utils.Rule import Chainweb.Version import Pact.Core.Names -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment0Payload as RDN0 -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment1to9Payload as RDNN -import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload as RDNKAD +-- import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment0Payload as RDN0 +-- import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment1to9Payload as RDNN +-- import qualified Chainweb.BlockHeader.Genesis.RecapDevelopment10to19Payload as RDNKAD import qualified Chainweb.Pact.Transactions.RecapDevelopmentTransactions as RecapDevnet import qualified Chainweb.Pact.Transactions.CoinV3Transactions as CoinV3 import qualified Chainweb.Pact.Transactions.CoinV4Transactions as CoinV4 diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index 1eb8f186a7..82a1e29db9 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -56,10 +56,12 @@ import Chainweb.Chainweb.Configuration import Chainweb.Chainweb.CutResources import Chainweb.Chainweb.PeerResources import Chainweb.Cut +import Chainweb.Cut.CutHashes import Chainweb.CutDB import Chainweb.Graph import Chainweb.Logger import Chainweb.Miner.Config +import Chainweb.Miner.Pact import Chainweb.Pact.Backend.PactState (allChains, getLatestBlockHeight, getLatestPactStateAtDiffable, TableDiffable(..), addChainIdLabel) import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..)) import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (ChainGrandHash(..)) @@ -67,6 +69,7 @@ import Chainweb.Pact.Backend.PactState.GrandHash.Calc qualified as GrandHash.Cal import Chainweb.Pact.Backend.PactState.GrandHash.Import qualified as GrandHash.Import import Chainweb.Pact.Backend.PactState.GrandHash.Utils qualified as GrandHash.Utils import Chainweb.PayloadProvider.Pact.Configuration (defaultPactProviderConfig, PactProviderConfig (_pactConfigGenesisPayload), _pactConfigMiner) +import Chainweb.Storage.Table (IterableTable(withTableIterator), Casify) import Chainweb.Storage.Table.RocksDB import Chainweb.Test.P2P.Peer.BootstrapConfig import Chainweb.Test.Utils @@ -78,12 +81,13 @@ import Chainweb.WebBlockHeaderDB import Control.Concurrent.Async import Control.DeepSeq import Control.Exception -import Control.Lens (set, view, (^?!), ix, (^.)) +import Control.Lens (set, view, (^?!), ix) import Control.Monad import Data.Aeson (ToJSON, object, (.=)) import Data.ByteString (ByteString) import Data.ByteString.Base16 qualified as Base16 import Data.Foldable +import Data.Function import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS import Data.IORef @@ -109,10 +113,6 @@ import System.IO.Temp import System.LogLevel import System.Timeout import Test.Tasty.HUnit -import Chainweb.Miner.Pact -import Chainweb.Storage.Table (IterableTable(withTableIterator), Casify) -import Chainweb.Cut.CutHashes -import Data.Function -- -------------------------------------------------------------------------- -- -- * Configuration diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index cfb5d7bddf..4fc272a827 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -6,6 +6,8 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} @@ -14,7 +16,6 @@ {-# LANGUAGE TypeOperators #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -{-# LANGUAGE LambdaCase #-} -- | -- Module: Chainweb.Test.Orphans.Internal @@ -43,71 +44,11 @@ module Chainweb.Test.Orphans.Internal , arbitraryMerkleHeaderProof , arbitraryMerkleBodyProof --- -- ** Output Proofs --- , arbitraryOutputMerkleProof --- , arbitraryOutputProof --- , mkTestOutputProof --- , arbitraryOutputEvents --- -- , arbitraryPayloadWithStructuredOutputs - --- ** Events Proofs --- , mkTestEventsProof --- , arbitraryEventsProof --- , EventPactValue(..) --- , ProofPactEvent(..) - -- ** Misc , arbitraryPage ) where import Control.Applicative -import Control.Lens (view) -import Control.Monad -import Control.Monad.Catch - -import Data.Aeson hiding (Error) -import qualified Data.ByteString as B -import qualified Data.ByteString.Short as BS -import Data.Foldable -import Data.Function -import Data.Functor ((<&>)) -import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS -import Data.Hash.Keccak -import Data.Kind -import qualified Data.List as L -import Data.MerkleLog.V1 -import Data.Streaming.Network.Internal -import qualified Data.Text as T -import qualified Data.Text.Encoding as T -import Data.Type.Equality -import qualified Data.Vector as V - -import GHC.Stack - -import Numeric.Natural - -import qualified Pact.JSON.Encode as J -import Pact.Core.Command.Types -import Pact.Core.Capabilities -import Pact.Core.PactValue -import Pact.Core.Literal - -import Prelude hiding (Applicative(..)) - -import System.IO.Unsafe - -import Test.QuickCheck.Arbitrary -import Test.QuickCheck.Exception (discard) -import Test.QuickCheck.Gen -import Test.QuickCheck.Modifiers - -import Unsafe.Coerce - -import Test.QuickCheck.Instances ({- Arbitrary V4.UUID -}) - --- internal modules - import Chainweb.BlockCreationTime import Chainweb.BlockHash import Chainweb.BlockHeader @@ -122,24 +63,23 @@ import Chainweb.Cut.CutHashes import Chainweb.Difficulty import Chainweb.Graph import Chainweb.HostAddress -import Chainweb.Pact.Mempool.Mempool -import Chainweb.Pact.Mempool.RestAPI import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse import Chainweb.Miner.Config import Chainweb.Miner.Pact import Chainweb.NodeVersion -import Chainweb.Pact.RestAPI.SPV -import Chainweb.Pact.Types +import Chainweb.Pact.Mempool.Mempool +import Chainweb.Pact.Mempool.RestAPI import Chainweb.Pact.Payload +import Chainweb.Pact.RestAPI.SPV +import Chainweb.Parent import Chainweb.PayloadProvider import Chainweb.PowHash +import Chainweb.Ranked import Chainweb.RestAPI.NetworkID import Chainweb.RestAPI.NodeInfo import Chainweb.RestAPI.Utils -import Chainweb.SPV import Chainweb.SPV.EventProof -import Chainweb.SPV.OutputProof import Chainweb.SPV.PayloadProof import Chainweb.Test.Orphans.Time () import Chainweb.Test.TestVersions @@ -149,30 +89,52 @@ import Chainweb.Utils.Paging import Chainweb.Utils.Rule (ruleElems) import Chainweb.Utils.Serialization import Chainweb.Version -import Chainweb.Version.RecapDevelopment import Chainweb.Version.Mainnet +import Chainweb.Version.RecapDevelopment import Chainweb.Version.Registry import Chainweb.Version.Testnet04 import Chainweb.Version.Utils - +import Control.Lens (view) +import Control.Monad +import Control.Monad.Catch +import Data.Aeson hiding (Error) +import Data.ByteString qualified as B +import Data.ByteString.Short qualified as BS +import Data.Foldable +import Data.Function +import Data.Functor ((<&>)) +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS +import Data.Kind +import Data.MerkleLog.V1 import Data.Singletons - +import Data.Streaming.Network.Internal +import Data.Text qualified as T +import Data.Text.Encoding qualified as T +import Data.Type.Equality +import GHC.Stack import Network.X509.SelfSigned - +import Numeric.Natural import P2P.Node.Configuration import P2P.Node.PeerDB import P2P.Peer import P2P.Test.Orphans () - +import Pact.Core.Capabilities +import Pact.Core.Command.Types +import Pact.Core.Literal +import Pact.Core.PactValue +import Pact.Core.StableEncoding +import Pact.JSON.Encode qualified as J +import Prelude hiding (Applicative(..)) +import System.IO.Unsafe import System.Logger.Types - +import Test.QuickCheck.Arbitrary +import Test.QuickCheck.Exception (discard) +import Test.QuickCheck.Gen +import Test.QuickCheck.Instances ({- Arbitrary V4.UUID -}) +import Test.QuickCheck.Modifiers +import Unsafe.Coerce import Utils.Logging -import Pact.Core.StableEncoding -import Pact.Core.Errors (pactErrorToOnChainError) -import Pact.Core.Info (spanInfoToLineInfo) -import Chainweb.PayloadProvider -import Chainweb.Parent -import Chainweb.Ranked -- -------------------------------------------------------------------------- -- -- Utils diff --git a/test/lib/Chainweb/Test/Pact/Utils.hs b/test/lib/Chainweb/Test/Pact/Utils.hs index a08b1544a4..48ebdc76d0 100644 --- a/test/lib/Chainweb/Test/Pact/Utils.hs +++ b/test/lib/Chainweb/Test/Pact/Utils.hs @@ -1,81 +1,60 @@ -{-# language - FlexibleContexts - , ImportQualifiedPost - , LambdaCase - , NumericUnderscores - , OverloadedStrings - , PackageImports - , TypeApplications -#-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE TypeApplications #-} module Chainweb.Test.Pact.Utils - -- ( initCheckpointer - -- , pactTxFrom4To5 +( +-- * Logging + getTestLogLevel +, testLogFn +, getTestLogger - -- * Logging - ( getTestLogLevel - , testLogFn - , getTestLogger +-- * Resources +, withMempool +, withBlockDbs - -- * Resources - , withMempool - , withBlockDbs +-- * Properties +, event +, successfulTx - -- * Properties - , event - , successfulTx - -- * Utilities - , coinModuleName - ) - where +-- * Utilities +, coinModuleName +) +where import Chainweb.Chainweb (validatingMempoolConfig) import Chainweb.ChainId import Chainweb.Logger import Chainweb.Pact.Mempool.InMem import Chainweb.Pact.Mempool.Mempool (MempoolBackend (..)) ---import Chainweb.Pact.Backend.RelationalCheckpointer -import Chainweb.Pact.Backend.Types (SQLiteEnv) -import Chainweb.Pact.Backend.Utils (openSQLiteConnection, closeSQLiteConnection, chainwebPragmas) import Chainweb.Pact.PactService -import Chainweb.Pact.Types -import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Payload.PayloadStore import Chainweb.Pact.Payload.PayloadStore.RocksDB +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.Types import Chainweb.Storage.Table.RocksDB --- import Chainweb.Utils import Chainweb.Version import Chainweb.WebBlockHeaderDB --- import Control.Concurrent hiding (throwTo) --- import Control.Exception (AsyncException (..), throwTo) --- import Control.Lens hiding (elements, only) --- import Control.Monad import Control.Monad.IO.Class -import Control.Monad.Trans.Resource (ResourceT, allocate) --- import Control.Monad.Trans.Resource qualified as Resource --- import Data.Aeson qualified as Aeson --- import Data.ByteString.Short qualified as SBS --- import Data.HashSet (HashSet) --- import Data.HashSet qualified as HashSet +import Control.Monad.Trans.Resource (ResourceT) import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as Text --- import Data.Text.Encoding qualified as Text import Data.Text.IO qualified as Text --- import Data.Vector (Vector) --- import Data.Vector qualified as Vector +import Pact.Core.Capabilities import Pact.Core.Command.Types qualified as Pact --- import Pact.Core.Hash qualified as Pact --- import Pact.Core.Pretty qualified as Pact import Pact.Core.Gas qualified as Pact --- import Pact.JSON.Encode qualified as J -import System.Environment (lookupEnv) -import System.LogLevel +import Pact.Core.Names +import Pact.Core.PactValue import PropertyMatchers ((?)) import PropertyMatchers qualified as P -import Pact.Core.PactValue -import Pact.Core.Capabilities -import Pact.Core.Names +import System.Environment (lookupEnv) +import System.LogLevel withBlockDbs :: HasVersion => RocksDb -> ResourceT IO (PayloadDb RocksDbTable, WebBlockHeaderDb) withBlockDbs rdb = do diff --git a/test/lib/Chainweb/Test/Pact4/Utils.hs b/test/lib/Chainweb/Test/Pact4/Utils.hs index 7ca04e7cf7..5bcc36fcb3 100644 --- a/test/lib/Chainweb/Test/Pact4/Utils.hs +++ b/test/lib/Chainweb/Test/Pact4/Utils.hs @@ -1,22 +1,21 @@ -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE DataKinds #-} {-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} -- | @@ -112,48 +111,33 @@ module Chainweb.Test.Pact4.Utils ) where -import Control.Arrow ((&&&)) -import Control.Concurrent.Async -import Control.Concurrent.MVar + import Control.Lens hiding ((.=)) import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class - import Data.Aeson (Value(..), object, (.=), Key) import Data.ByteString (ByteString) -import qualified Data.ByteString.Short as BS +import Data.ByteString.Short qualified as BS import Data.Decimal -import Data.Foldable -import qualified Data.HashMap.Strict as HM -import Data.IORef import Data.List qualified as List import Data.Map (Map) -import qualified Data.Map.Strict as M +import Data.Map.Strict qualified as M import Data.Maybe import Data.Text (Text) -import qualified Data.Text as T -import qualified Data.Text.Encoding as T +import Data.Text qualified as T +import Data.Text.Encoding qualified as T import Data.String -import qualified Data.Vector as V - +import Data.Vector qualified as V import Database.SQLite3.Direct (Database) - import GHC.Generics - import Streaming.Prelude qualified as S import System.LogLevel - import Test.Tasty -import Test.Tasty.HUnit(assertFailure) - --- internal pact modules - import Pact.ApiReq (ApiKeyPair(..), mkKeyPairs) -import Pact.Gas import Pact.JSON.Legacy.Value import Pact.Types.Capability -import qualified Pact.Types.ChainId as P +import Pact.Types.ChainId qualified as P import Pact.Types.ChainMeta import Pact.Types.Command import Pact.Types.Crypto @@ -162,7 +146,7 @@ import Pact.Types.Gas import Pact.Types.Hash import Pact.Types.Info (noInfo) import Pact.Types.KeySet -import qualified Pact.Types.Logger as P +import Pact.Types.Logger qualified as P import Pact.Types.Names import Pact.Types.PactValue import Pact.Types.RPC @@ -170,44 +154,31 @@ import Pact.Types.Runtime (PactEvent(..)) import Pact.Types.Term import Pact.Types.Util (parseB16TextOnly) import Pact.Types.Verifier - --- internal modules - import Chainweb.BlockHeader import Chainweb.BlockHeaderDB hiding (withBlockHeaderDb) import Chainweb.BlockHeight import Chainweb.ChainId import Chainweb.Graph import Chainweb.Logger -import Chainweb.Miner.Pact import Chainweb.Pact.Backend.Compaction qualified as Sigma import Chainweb.Pact.Backend.PactState qualified as PactState import Chainweb.Pact.Backend.PactState (TableDiffable(..), Table(..), PactRow(..)) import Chainweb.Pact.Backend.SQLite.DirectV2 - import Chainweb.Pact.Backend.Utils hiding (tbl, withSqliteDb) -import Chainweb.Pact.PactService -import Chainweb.Pact.RestAPI.Server (validateCommand) -import Chainweb.Pact.Types import Chainweb.Pact4.Types import Chainweb.Pact.Payload import Chainweb.Pact.Payload.PayloadStore -import Chainweb.Test.Cut import Chainweb.Test.Cut.TestBlockDb import Chainweb.Test.Utils import Chainweb.Test.Utils.BlockHeader import Chainweb.Test.TestVersions import Chainweb.Time -import qualified Chainweb.Pact4.Transaction as Pact4 import Chainweb.Utils -import Chainweb.Version (ChainwebVersion(..), chainIds) -import qualified Chainweb.Version as Version -import Chainweb.Version.Utils (someChainId) -import Chainweb.WebBlockHeaderDB - -import Chainweb.Storage.Table.RocksDB +import Chainweb.Version (ChainwebVersion(..)) +import Chainweb.Version qualified as Version import Chainweb.Pact.Backend.Types import Chainweb.Parent +import Chainweb.Storage.Table.RocksDB -- ----------------------------------------------------------------------- -- -- Keys diff --git a/test/lib/Chainweb/Test/TestVersions.hs b/test/lib/Chainweb/Test/TestVersions.hs index 993a73f629..27de2f605f 100644 --- a/test/lib/Chainweb/Test/TestVersions.hs +++ b/test/lib/Chainweb/Test/TestVersions.hs @@ -26,23 +26,19 @@ module Chainweb.Test.TestVersions ) where import Control.Lens hiding (elements) -import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HM import qualified Data.HashSet as HS -import qualified Data.List as List import qualified Data.Set as Set import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN -import qualified Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM0Payload as PIN0 -import qualified Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM1to9Payload as PINN +-- import qualified Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM0Payload as PIN0 +-- import qualified Chainweb.BlockHeader.Genesis.Pact5InstantTimedCPM1to9Payload as PINN import qualified Chainweb.BlockHeader.Genesis.Pact53TransitionTimedCPM0Payload as PIT0 import qualified Chainweb.BlockHeader.Genesis.Pact53TransitionTimedCPM1to9Payload as PITN -import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM0Payload as QPIN0 -import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM1to9Payload as QPINN -- import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM0Payload as QPIN0 -- import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM1to9Payload as QPINN - -import System.IO.Unsafe +-- import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM0Payload as QPIN0 +-- import qualified Chainweb.BlockHeader.Genesis.QuirkedGasPact5InstantTimedCPM1to9Payload as QPINN -- internal modules @@ -56,10 +52,9 @@ import Chainweb.Time import Chainweb.Utils import Chainweb.Utils.Rule import Chainweb.Version -import Chainweb.Version.Registry import P2P.Peer -import Chainweb.Pact.Payload(PayloadWithOutputs_(_payloadWithOutputsPayloadHash), PayloadWithOutputs) +import Chainweb.Pact.Payload(PayloadWithOutputs_(_payloadWithOutputsPayloadHash)) import qualified Pact.Core.Names as Pact import qualified Pact.Core.Gas as Pact import qualified Data.List as L diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index 949227c954..cbf89a2a0d 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -1,6 +1,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NumericUnderscores #-} @@ -134,47 +135,41 @@ import Control.Concurrent import Control.Concurrent.STM import Control.Lens import Control.Monad -import Control.Monad.Catch (finally, bracket) +import Control.Monad.Catch (finally) import Control.Monad.IO.Class import Control.Monad.Trans.Resource import Control.Retry - import Data.Aeson (FromJSON, ToJSON) import Data.Bifunctor hiding (second) -import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as BL +import Data.ByteString qualified as B +import Data.ByteString.Lazy qualified as BL import Data.Coerce (coerce) import Data.Foldable import Data.IORef import Data.List (sortOn, isInfixOf) import Data.Pool (Pool) -import qualified Data.Text as T -import qualified Data.Text.IO as T +import Data.Text qualified as T +import Data.Text.IO qualified as T import Data.Tree -import qualified Data.Tree.Lens as LT +import Data.Tree.Lens qualified as LT import Data.Word - -import qualified Network.Connection as HTTP -import qualified Network.HTTP.Client as HTTP -import qualified Network.HTTP.Client.TLS as HTTP -import qualified Network.HTTP.Types as HTTP +import Network.Connection qualified as HTTP +import Network.HTTP.Client qualified as HTTP +import Network.HTTP.Client.TLS qualified as HTTP +import Network.HTTP.Types qualified as HTTP import Network.Socket (close) -import qualified Network.TLS as HTTP -import qualified Network.Wai as W -import qualified Network.Wai.Handler.Warp as W +import Network.TLS qualified as HTTP +import Network.Wai qualified as W +import Network.Wai.Handler.Warp qualified as W import Network.Wai.Handler.WarpTLS as W (runTLSSocket) - import Numeric.Natural - import Servant.Client (BaseUrl(..), ClientEnv, Scheme(..), mkClientEnv, runClientM) - import System.Directory (removeDirectoryRecursive, removeFile) import System.Environment (withArgs) import System.IO import System.IO.Temp qualified as Temp import System.LogLevel import System.Random (randomIO) - import Test.QuickCheck.Arbitrary import Test.QuickCheck.Gen import Test.QuickCheck.Property (Property, (===)) @@ -183,13 +178,8 @@ import Test.Tasty import Test.Tasty.Golden import Test.Tasty.HUnit import Test.Tasty.QuickCheck (property, discard, (.&&.)) - import Text.Printf (printf) - import UnliftIO.Async - --- internal modules - import Chainweb.BlockCreationTime import Chainweb.BlockHeader import Chainweb.BlockHeaderDB @@ -208,7 +198,7 @@ import Chainweb.Difficulty (targetToDifficulty) import Chainweb.Graph import Chainweb.HostAddress import Chainweb.Logger -import Chainweb.Pact.Mempool.Mempool (MempoolBackend(..), TransactionHash(..), BlockFill(..), mockBlockGasLimit) +import Chainweb.Pact.Mempool.Mempool (TransactionHash(..), BlockFill(..), mockBlockGasLimit) import Chainweb.MerkleUniverse import Chainweb.Miner.Config import Chainweb.Pact.Backend.Types(SQLiteEnv) @@ -218,8 +208,7 @@ import Chainweb.PayloadProvider.Pact.Configuration import Chainweb.RestAPI import Chainweb.RestAPI.NetworkID import Chainweb.Test.Pact.Utils (getTestLogLevel, getTestLogger) -import Chainweb.Test.P2P.Peer.BootstrapConfig - (testBootstrapCertificate, testBootstrapKey, testBootstrapPeerConfig) +import Chainweb.Test.P2P.Peer.BootstrapConfig (testBootstrapCertificate, testBootstrapKey, testBootstrapPeerConfig) import Chainweb.Test.Utils.BlockHeader import Chainweb.Time import Chainweb.TreeDB @@ -228,24 +217,19 @@ import Chainweb.Utils.Serialization import Chainweb.Test.TestVersions import Chainweb.Version import Chainweb.Version.Utils - import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB - -import qualified Database.RocksDB.Internal as R - +import Database.RocksDB.Internal qualified as R import Network.X509.SelfSigned - import P2P.Node.Configuration -import qualified P2P.Node.PeerDB as P2P +import P2P.Node.PeerDB qualified as P2P import P2P.Peer - import Chainweb.Test.Utils.APIValidation import Data.Semigroup -import qualified Pact.Core.Command.Types as Pact5 -import qualified Pact.Core.Errors as Pact5 -import qualified Pact.Core.Hash as Pact5 -import qualified Pact.Core.Gas as Pact +import Pact.Core.Command.Types qualified as Pact5 +import Pact.Core.Errors qualified as Pact5 +import Pact.Core.Hash qualified as Pact5 +import Pact.Core.Gas qualified as Pact import Chainweb.Ranked (Ranked(_rankedHeight)) -- -------------------------------------------------------------------------- -- diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 988a5292cf..e8a287f17f 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -1,14 +1,15 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} -- | -- Module: Chainweb.Test.CutDB @@ -31,76 +32,58 @@ module Chainweb.Test.CutDB , tests ) where -import Control.Concurrent.Async -import Control.Concurrent.STM as STM -import Control.Lens hiding (elements) -import Control.Monad -import Control.Monad.Catch -import Control.Monad.IO.Class -import Control.Monad.Trans.Resource - -import Data.Foldable -import Data.Function -import qualified Data.HashMap.Strict as HM -import Data.Semigroup -import Data.Text(Text) -import qualified Data.Vector as V - -import GHC.Stack - -import qualified Network.HTTP.Client as HTTP - -import Numeric.Natural - -import qualified Streaming.Prelude as S - -import Test.QuickCheck -import Test.Tasty - --- internal modules - +import Chainweb.Chainweb.Configuration (defaultPayloadProviderConfig, defaultServiceApiConfig) import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.ChainId +import Chainweb.Chainweb.ChainResources (withPayloadProviderResources, providerResPayloadProvider) import Chainweb.Cut import Chainweb.Cut.CutHashes -import Chainweb.Graph -import Chainweb.Test.Cut import Chainweb.CutDB import Chainweb.CutDB.RestAPI.Server -import Chainweb.Miner.Pact -import Chainweb.Pact.Types -import Chainweb.Parent +import Chainweb.Graph +import Chainweb.Logger import Chainweb.Pact.Payload import Chainweb.Pact.Payload.PayloadStore -import Chainweb.Pact.Payload.PayloadStore.RocksDB +import Chainweb.Pact.Types +import Chainweb.Parent import Chainweb.PayloadProvider +import Chainweb.Storage.Table +import Chainweb.Storage.Table.RocksDB import Chainweb.Sync.WebBlockHeaderStore +import Chainweb.Test.Cut import Chainweb.Test.Orphans.Internal () +import Chainweb.Test.Pact.Utils (getTestLogger) import Chainweb.Test.Sync.WebBlockHeaderStore -import Chainweb.Test.Utils hiding (awaitBlockHeight) import Chainweb.Test.TestVersions (barebonesTestVersion) +import Chainweb.Test.Utils hiding (awaitBlockHeight) import Chainweb.Time import Chainweb.TreeDB (MaxRank(..)) import Chainweb.Utils import Chainweb.Version import Chainweb.Version.Utils import Chainweb.WebBlockHeaderDB --- import Chainweb.WebPactExecutionService - -import Chainweb.Storage.Table -import Chainweb.Storage.Table.RocksDB -import Data.LogMessage +import Control.Concurrent.Async +import Control.Concurrent.STM as STM +import Control.Lens hiding (elements) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource +import Data.Foldable +import Data.Function +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS +import Data.Semigroup import Data.TaskMap - -import Test.Tasty.HUnit -import Chainweb.Logger +import Data.Vector qualified as V +import GHC.Stack +import Network.HTTP.Client qualified as HTTP +import Streaming.Prelude qualified as S import System.LogLevel -import Chainweb.Chainweb.ChainResources (withPayloadProviderResources, providerResPayloadProvider) -import P2P.Node.Configuration (defaultP2pConfiguration) -import Chainweb.Chainweb.Configuration (PayloadProviderConfig(PayloadProviderConfig), defaultPayloadProviderConfig, defaultServiceApiConfig) -import Chainweb.Test.Pact.Utils (getTestLogger) -import qualified Data.HashSet as HS +import Test.QuickCheck +import Test.Tasty +import Test.Tasty.HUnit -- -------------------------------------------------------------------------- -- -- Create a random Cut DB with the respective Payload Store diff --git a/test/unit/Chainweb/Test/Mempool.hs b/test/unit/Chainweb/Test/Mempool.hs index 7f05876a1f..fd2cb8efae 100644 --- a/test/unit/Chainweb/Test/Mempool.hs +++ b/test/unit/Chainweb/Test/Mempool.hs @@ -1,5 +1,6 @@ {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} @@ -29,13 +30,13 @@ import Control.Monad.Trans.Except import Data.Bifunctor (bimap) import Data.Decimal (Decimal, DecimalRaw(..)) import Data.Function (on) -import qualified Data.HashSet as HashSet +import Data.HashSet qualified as HashSet import Data.IORef import Data.List (sort, sortBy) -import qualified Data.List.Ordered as OL +import Data.List.Ordered qualified as OL import Data.Ord (Down(..)) import Data.Vector (Vector) -import qualified Data.Vector as V +import Data.Vector qualified as V import GHC.Stack import Prelude hiding (lookup) import System.Timeout (timeout) @@ -44,22 +45,17 @@ import Test.QuickCheck.Monadic import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck hiding ((.&.)) - --- internal modules - -import Pact.Parse (ParsedDecimal(..)) import Pact.Core.Gas.Types - import Chainweb.BlockHash import Chainweb.Pact.Mempool.Mempool import Chainweb.Test.Utils -import qualified Chainweb.Time as Time -import Chainweb.Utils (T2(..)) -import Chainweb.PayloadProvider +import Chainweb.Time qualified as Time +import Chainweb.BlockCreationTime import Chainweb.MinerReward import Chainweb.Parent -import Chainweb.BlockCreationTime -import Control.Lens (from, view) +import Chainweb.PayloadProvider +import Chainweb.Utils (T2(..)) +import Control.Lens (view) ------------------------------------------------------------------------------ -- | Several operations (reintroduce, validate, confirm) can only be performed diff --git a/test/unit/Chainweb/Test/Mempool/RestAPI.hs b/test/unit/Chainweb/Test/Mempool/RestAPI.hs index ba3ac28508..270771d660 100644 --- a/test/unit/Chainweb/Test/Mempool/RestAPI.hs +++ b/test/unit/Chainweb/Test/Mempool/RestAPI.hs @@ -1,45 +1,36 @@ {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE ImportQualifiedPost #-} + module Chainweb.Test.Mempool.RestAPI (tests) where import Control.Concurrent -import Control.Concurrent.STM -import Control.Exception import Control.Monad.IO.Class import Control.Monad.Trans.Resource - -import qualified Data.Pool as Pool -import qualified Data.Vector as V - -import qualified Network.HTTP.Client as HTTP +import Data.Pool qualified as Pool +import Data.Vector qualified as V +import Network.HTTP.Client qualified as HTTP import Network.Wai (Application) - import Servant.Client (BaseUrl(..), ClientEnv, Scheme(..), mkClientEnv) - import Test.Tasty - --- internal modules - import Chainweb.Chainweb.Configuration import Chainweb.Graph -import qualified Chainweb.Pact.Mempool.InMem as InMem +import Chainweb.Pact.Mempool.InMem qualified as InMem import Chainweb.Pact.Mempool.InMemTypes (InMemConfig(..)) import Chainweb.Pact.Mempool.Mempool -import qualified Chainweb.Pact.Mempool.RestAPI.Client as MClient +import Chainweb.Pact.Mempool.RestAPI.Client qualified as MClient import Chainweb.RestAPI import Chainweb.Test.Mempool (InsertCheck, MempoolWithFunc(..)) -import qualified Chainweb.Test.Mempool -import Chainweb.Test.Utils (withTestAppServer) +import Chainweb.Test.Mempool qualified import Chainweb.Test.TestVersions +import Chainweb.Test.Utils (withTestAppServer) import Chainweb.Utils (Codec(..), withAsyncR, resourceToBracket) import Chainweb.Version import Chainweb.Version.Utils - -import Chainweb.Storage.Table.RocksDB - -import Network.X509.SelfSigned import Control.Monad +import Network.X509.SelfSigned ------------------------------------------------------------------------------ + tests :: TestTree tests = withResource newPool Pool.destroyAllResources $ \poolIO -> testGroup "Chainweb.Pact.Mempool.RestAPI" diff --git a/test/unit/Chainweb/Test/Mining.hs b/test/unit/Chainweb/Test/Mining.hs index 27452d2113..1bc26a99f3 100644 --- a/test/unit/Chainweb/Test/Mining.hs +++ b/test/unit/Chainweb/Test/Mining.hs @@ -17,36 +17,8 @@ module Chainweb.Test.Mining ( tests ) where -import Control.Concurrent -import Control.Concurrent.Async -import Control.Concurrent.STM.TVar -import Control.Lens - -import Data.Foldable -import qualified Data.HashMap.Strict as HM -import Data.Maybe -import qualified Data.Text as T - -import GHC.Stack - -import System.LogLevel - -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - -import Chainweb.Chainweb.MinerResources -import Chainweb.Graph -import Chainweb.Logger -import Chainweb.Miner.Config -import Chainweb.Miner.Coordinator -import Chainweb.Miner.Pact -import Chainweb.Test.CutDB hiding (tests) -import Chainweb.Test.TestVersions (barebonesTestVersion) - import Chainweb.Storage.Table.RocksDB -import Chainweb.Version (withVersion) +import Test.Tasty -- -------------------------------------------------------------------------- -- -- diff --git a/test/unit/Chainweb/Test/Misc.hs b/test/unit/Chainweb/Test/Misc.hs index e505dfdc2c..6370f64dcd 100644 --- a/test/unit/Chainweb/Test/Misc.hs +++ b/test/unit/Chainweb/Test/Misc.hs @@ -1,5 +1,5 @@ -{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} @@ -13,35 +13,25 @@ -- Miscellaneous tests. -- module Chainweb.Test.Misc - ( tests - ) where +( tests +) where import Chainweb.Pact.Payload +import Chainweb.BlockHash +import Chainweb.ChainId +import Chainweb.MerkleUniverse +import Chainweb.Parent import Chainweb.Test.Orphans.Internal () - +import Chainweb.Utils (HasTextRepresentation(..)) +import Chainweb.Utils.Serialization import Control.Concurrent (threadDelay) +import Control.Lens import Control.Scheduler (Comp(..), scheduleWork, terminateWith, withScheduler) - +import Data.HashMap.Strict qualified as HM +import PropertyMatchers qualified as P import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck -import Chainweb.BlockHash -import Data.HashMap.Strict qualified as HM -import Chainweb.ChainId - -import PropertyMatchers ((?)) -import PropertyMatchers qualified as P --- import Chainweb.MerkleLogHash (unsafeMerkleLogHash) -import Data.Function ((&)) --- import qualified Data.ByteString as BS -import Chainweb.Utils (HasTextRepresentation(..)) -import Chainweb.MerkleLogHash (unsafeMerkleLogHash) -import qualified Data.ByteString as BS -import Control.Lens -import Control.Monad -import Chainweb.Parent -import Chainweb.Utils.Serialization -import Chainweb.MerkleUniverse --- diff --git a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs index 1e5c54cef6..3e543bed91 100644 --- a/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs +++ b/test/unit/Chainweb/Test/Pact/CheckpointerTest.hs @@ -1,17 +1,17 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE GADTs #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE QuantifiedConstraints #-} -{-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE ImportQualifiedPost #-} module Chainweb.Test.Pact.CheckpointerTest (tests) where @@ -41,7 +41,6 @@ import PropertyMatchers qualified as P import Test.Tasty import Test.Tasty.HUnit (assertEqual, testCase) import Test.Tasty.Hedgehog - import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.Graph (singletonChainGraph) @@ -55,13 +54,13 @@ import Chainweb.Parent import Chainweb.PayloadProvider import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions -import Chainweb.Test.Utils hiding (withTempSQLiteResource) +import Chainweb.Test.Utils import Chainweb.Utils import Chainweb.Utils.Serialization (runGetS, runPutS) import Chainweb.Version import Chainweb.MinerReward import Control.Monad.State.Strict -import qualified Chainweb.Pact.Payload as Chainweb +import Chainweb.Pact.Payload qualified as Chainweb import Chainweb.Pact.Utils (emptyPayload) -- | A @DbAction f@ is a description of some action on the database together with an f-full of results for it. diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index 509d0a8299..d205c2263e 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -1,63 +1,77 @@ -{-# language - DataKinds - , FlexibleContexts - , ImpredicativeTypes - , ImportQualifiedPost - , LambdaCase - , NumericUnderscores - , OverloadedStrings - , PackageImports - , ScopedTypeVariables - , TypeApplications - , TemplateHaskell - , RecordWildCards - , TupleSections -#-} - {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE ImpredicativeTypes #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} module Chainweb.Test.Pact.PactServiceTest - ( tests - ) where +( tests +) where +import Chainweb.BlockHash (BlockHash) import Chainweb.BlockHeader +import Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload qualified as IN0 +import Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload qualified as INN +import Chainweb.BlockHeight (BlockHeight) import Chainweb.ChainId import Chainweb.Chainweb +import Chainweb.Core.Brief import Chainweb.Cut import Chainweb.Graph (singletonChainGraph) import Chainweb.Logger +import Chainweb.Pact.Backend.Types (throwIfNoHistory, Historical) import Chainweb.Pact.Mempool.InMem qualified as Mempool import Chainweb.Pact.Mempool.Mempool qualified as Mempool -import Chainweb.Pact.Types -import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.PactService qualified as PactService +import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer +import Chainweb.Pact.PactService.ExecBlock qualified as PactService import Chainweb.Pact.Payload +import Chainweb.Pact.Transaction qualified as Pact +import Chainweb.Pact.Types +import Chainweb.Parent +import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.Pact (pactMemPoolAccess) import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb), addTestBlockDb, getCutTestBlockDb, getParentTestBlockDb, mkTestBlockDb, setCutTestBlockDb) import Chainweb.Test.Pact.CmdBuilder -import Chainweb.Test.Pact.Utils hiding (withTempSQLiteResource) +import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions import Chainweb.Test.Utils import Chainweb.Time import Chainweb.Utils import Chainweb.Version - import Control.Concurrent.Async (forConcurrently) import Control.Lens hiding (only) import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Resource - import Data.ByteString.Lazy qualified as LBS +import Data.ByteString.Short qualified as SB import Data.Decimal +import Data.Foldable (toList) +import Data.HashMap.Strict (HashMap) +import Data.HashMap.Strict qualified as HashMap import Data.HashSet qualified as HashSet +import Data.List qualified as List +import Data.Pool qualified as Pool import Data.Text qualified as T import Data.Text.IO qualified as Text import Data.Vector (Vector) import Data.Vector qualified as Vector - import Pact.Core.Capabilities +import Pact.Core.ChainData (TxCreationTime (..)) import Pact.Core.Command.Types import Pact.Core.Gas.Types +import Pact.Core.Hash qualified as Pact import Pact.Core.Names import Pact.Core.PactValue import PropertyMatchers ((?)) @@ -65,26 +79,6 @@ import PropertyMatchers qualified as P import Test.Tasty import Test.Tasty.HUnit (assertBool, assertEqual, testCase) import Text.Printf (printf) -import qualified Data.Pool as Pool -import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 -import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN -import Chainweb.PayloadProvider.Pact (pactMemPoolAccess) -import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer -import Chainweb.Pact.Backend.Types (throwIfNoHistory, Historical) -import qualified Chainweb.Pact.PactService as PactService -import qualified Chainweb.Pact.PactService.ExecBlock as PactService -import Chainweb.Parent -import Chainweb.PayloadProvider -import Chainweb.Core.Brief -import Data.Foldable (toList) -import Pact.Core.ChainData (TxCreationTime (..)) -import Pact.Core.Hash qualified as Pact -import qualified Data.List as List -import Data.HashMap.Strict (HashMap) -import qualified Data.ByteString.Short as SB -import Chainweb.BlockHash (BlockHash) -import Chainweb.BlockHeight (BlockHeight) -import qualified Data.HashMap.Strict as HashMap data Fixture = Fixture { _fixtureBlockDb :: TestBlockDb diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index dfe04d5e6a..a58afcc9ac 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -1,48 +1,67 @@ -{-# language - ConstraintKinds - , DataKinds - , DeriveAnyClass - , DerivingStrategies - , FlexibleContexts - , FlexibleInstances - , ImplicitParams - , ImportQualifiedPost - , ImpredicativeTypes - , LambdaCase - , MultiParamTypeClasses - , NamedFieldPuns - , NumericUnderscores - , OverloadedStrings - , PackageImports - , PartialTypeSignatures - , PatternSynonyms - , RecordWildCards - , ScopedTypeVariables - , TemplateHaskell - , TupleSections - , TypeApplications - , UndecidableInstances - , ViewPatterns -#-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImplicitParams #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE ImpredicativeTypes #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ViewPatterns #-} -- temporary {-# OPTIONS_GHC -Wno-partial-type-signatures #-} module Chainweb.Test.Pact.RemotePactTest - ( tests - , mkFixture - , Fixture(..) - , HasFixture(..) - , poll - , pollWithDepth - , PollException(..) - , ClientException(..) - , _FailureResponse - , send - , local - , textContains - ) where - +( tests +, mkFixture +, Fixture(..) +, HasFixture(..) +, poll +, pollWithDepth +, PollException(..) +, ClientException(..) +, _FailureResponse +, send +, local +, textContains +) where + +import Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload qualified as IN0 +import Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload qualified as INN +import Chainweb.ChainId +import Chainweb.CutDB.RestAPI.Server (someCutGetServer) +import Chainweb.Graph (petersenChainGraph, singletonChainGraph, twentyChainGraph) +import Chainweb.Pact.Mempool.Mempool (TransactionHash (..)) +import Chainweb.Pact.Payload +import Chainweb.Pact.RestAPI.Client +import Chainweb.Pact.RestAPI.Server +import Chainweb.Pact.Types +import Chainweb.RestAPI.Utils (someServerApplication) +import Chainweb.Storage.Table.RocksDB +import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Test.Pact.CutFixture (advanceAllChains, advanceAllChains_, advanceToForkHeight) +import Chainweb.Test.Pact.CutFixture qualified as CutFixture +import Chainweb.Test.Pact.Utils +import Chainweb.Test.TestVersions +import Chainweb.Test.Utils +import Chainweb.Utils +import Chainweb.Version +import Chainweb.Version.Mainnet (mainnet) import Control.Concurrent.Async hiding (poll) import Control.Exception (evaluate) import Control.Exception.Safe @@ -51,8 +70,8 @@ import Control.Monad (replicateM_) import Control.Monad.IO.Class (liftIO) import Control.Monad.Trans.Resource (ResourceT, allocate, runResourceT) import Data.Aeson qualified as A -import Data.Aeson.Lens qualified as A import Data.Aeson.KeyMap qualified as A.KeyMap +import Data.Aeson.Lens qualified as A import Data.ByteString.Base16 qualified as B16 import Data.Foldable (forM_, traverse_) import Data.HashMap.Strict qualified as HashMap @@ -63,8 +82,8 @@ import Data.Map.Strict qualified as Map import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as T -import Data.Text.IO qualified as T import Data.Text.Encoding qualified as T +import Data.Text.IO qualified as T import Data.Text.Lazy qualified as TL import Data.Text.Lazy.Encoding qualified as TL import Data.Vector qualified as V @@ -78,14 +97,6 @@ import Network.TLS qualified as TLS import Network.Wai.Handler.Warp qualified as W import Network.Wai.Handler.WarpTLS qualified as W import Network.X509.SelfSigned -import Prettyprinter qualified as PP -import PropertyMatchers ((?)) -import PropertyMatchers qualified as P -import Servant.Client -import System.IO.Unsafe (unsafePerformIO) -import Test.Tasty -import Test.Tasty.HUnit (testCaseSteps, testCase, assertFailure) - import Pact.Core.Capabilities import Pact.Core.ChainData qualified as Pact import Pact.Core.Command.Client (SubmitBatch(..)) @@ -101,29 +112,13 @@ import Pact.Core.Hash import Pact.Core.Names import Pact.Core.PactValue import Pact.Core.SPV - -import Chainweb.ChainId -import Chainweb.CutDB.RestAPI.Server (someCutGetServer) -import Chainweb.Graph (petersenChainGraph, singletonChainGraph, twentyChainGraph) -import Chainweb.Pact.Mempool.Mempool (TransactionHash (..)) -import Chainweb.Pact.RestAPI.Client -import Chainweb.Pact.RestAPI.Server -import Chainweb.Pact.Types -import Chainweb.Pact.Payload -import Chainweb.RestAPI.Utils (someServerApplication) -import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.Pact.CmdBuilder -import Chainweb.Test.Pact.CutFixture (advanceAllChains, advanceAllChains_, advanceToForkHeight) -import Chainweb.Test.Pact.CutFixture qualified as CutFixture -import Chainweb.Test.Pact.Utils -import Chainweb.Test.TestVersions -import Chainweb.Test.Utils -import Chainweb.Utils -import Chainweb.Version -import Chainweb.Version.Mainnet (mainnet) - -import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as IN0 -import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM1to9Payload as INN +import Prettyprinter qualified as PP +import PropertyMatchers ((?)) +import PropertyMatchers qualified as P +import Servant.Client +import System.IO.Unsafe (unsafePerformIO) +import Test.Tasty +import Test.Tasty.HUnit (testCaseSteps, testCase) -- generating this cert and making an HTTP manager take quite a while relative diff --git a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs index e2819b7d85..c0d7bd14d1 100644 --- a/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs +++ b/test/unit/Chainweb/Test/Pact/TransactionExecTest.hs @@ -17,16 +17,23 @@ module Chainweb.Test.Pact.TransactionExecTest (tests) where import Chainweb.BlockHeader +import Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload qualified as PIN0 import Chainweb.Graph (petersenChainGraph) +import Chainweb.Logger import Chainweb.Miner.Pact (Miner(..), MinerId(..), MinerGuard(..), noMiner) +import Chainweb.Pact.Backend.InMemDb qualified as InMemDb +import Chainweb.Pact.Backend.Types import Chainweb.Pact.PactService (initialPayloadState, withPactService) import Chainweb.Pact.PactService.Checkpointer (readFrom, mkFakeParentCreationTime) -import Chainweb.Pact.Types +import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer import Chainweb.Pact.Transaction import Chainweb.Pact.TransactionExec +import Chainweb.Pact.Types +import Chainweb.Parent import Chainweb.Storage.Table.RocksDB import Chainweb.Test.Cut.TestBlockDb (TestBlockDb (_bdbPayloadDb), mkTestBlockDb) import Chainweb.Test.Pact.CmdBuilder +import Chainweb.Test.Pact.Utils import Chainweb.Test.TestVersions import Chainweb.Test.Utils import Chainweb.Version @@ -34,6 +41,7 @@ import Control.Lens hiding (only) import Control.Monad.Except import Control.Monad.IO.Class import Control.Monad.Reader +import Control.Monad.State.Strict import Control.Monad.Trans.Resource import Data.Decimal import Data.HashMap.Strict qualified as HashMap @@ -44,7 +52,6 @@ import Data.String (fromString) import Data.Text qualified as T import Data.Text.Encoding qualified as T import Data.Text.IO qualified as T -import Chainweb.Test.Pact.Utils hiding (withTempSQLiteResource) import GHC.Stack import Pact.Core.Capabilities import Pact.Core.Command.Types @@ -53,6 +60,7 @@ import Pact.Core.Errors import Pact.Core.Evaluate import Pact.Core.Gas.TableGasModel import Pact.Core.Gas.Types +import Pact.Core.Guards qualified as Pact import Pact.Core.Hash import Pact.Core.Names import Pact.Core.PactValue @@ -60,20 +68,12 @@ import Pact.Core.Persistence hiding (pactDb) import Pact.Core.SPV (noSPVSupport) import Pact.Core.Signer import Pact.Core.Verifiers -import Pact.Core.Guards qualified as Pact import Pact.JSON.Encode qualified as J import PropertyMatchers ((?), pattern (:=>)) import PropertyMatchers qualified as P import Test.Tasty import Test.Tasty.HUnit (assertEqual, testCase) import Text.Printf -import Chainweb.Logger -import Chainweb.Pact.Backend.InMemDb qualified as InMemDb -import Chainweb.Pact.Backend.Types -import Control.Monad.State.Strict -import Chainweb.Parent -import qualified Chainweb.BlockHeader.Genesis.InstantTimedCPM0Payload as PIN0 -import qualified Chainweb.Pact.PactService.Checkpointer as Checkpointer tests :: RocksDb -> TestTree tests baseRdb = testGroup "Pact5 TransactionExecTest" diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index 2c1af4f9ce..9a95ccd69b 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -1,6 +1,8 @@ {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ImportQualifiedPost #-} + {-# OPTIONS_GHC -Wno-orphans #-} -- | @@ -17,77 +19,57 @@ module Chainweb.Test.Roundtrips ) where import Chainweb.Pact.RestAPI.SPV - import Control.Monad.Catch - -import Crypto.Hash.Algorithms - import Data.Aeson import Data.Aeson.Encoding import Data.Bifunctor -import qualified Data.ByteString.Lazy as BL -import qualified Data.HashMap.Strict as HM +import Data.ByteString.Lazy qualified as BL +import Data.HashMap.Strict qualified as HM import Data.Int -import qualified Data.Text as T - -import qualified Pact.JSON.Encode as J -import Pact.Parse - -import Test.QuickCheck -import Test.QuickCheck.Instances () -import Test.Tasty -import Test.Tasty.QuickCheck - --- internal modules - +import Data.Text qualified as T +import Pact.JSON.Encode qualified as J import Chainweb.BlockHash import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB.RestAPI import Chainweb.BlockHeight import Chainweb.BlockWeight import Chainweb.ChainId -import Chainweb.Chainweb import Chainweb.Chainweb.Configuration import Chainweb.Cut.Create import Chainweb.Cut.CutHashes import Chainweb.Difficulty import Chainweb.HostAddress -import Chainweb.Pact.Mempool.Mempool -import Chainweb.Pact.Mempool.RestAPI import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse import Chainweb.Miner.Config import Chainweb.Miner.Pact import Chainweb.NodeVersion -import Chainweb.Pact.Types +import Chainweb.Pact.Mempool.Mempool +import Chainweb.Pact.Mempool.RestAPI import Chainweb.Pact.Payload import Chainweb.PowHash +import Chainweb.Ranked import Chainweb.RestAPI.NetworkID import Chainweb.RestAPI.NodeInfo -import Chainweb.SPV import Chainweb.SPV.EventProof -import Chainweb.SPV.PayloadProof --- import Chainweb.Test.Orphans.Internal (EventPactValue(..), ProofPactEvent(..), arbitraryBlockHeaderVersion, arbitraryBlockHeaderVersionHeight, arbitraryBlockHashRecordVersionHeightChain, arbitraryBlockHeaderVersionHeightChain) -import Chainweb.Test.Orphans.Internal (arbitraryBlockHeaderVersion, arbitraryBlockHeaderVersionHeight, arbitraryBlockHashRecordVersionHeightChain, arbitraryBlockHeaderVersionHeightChain) --- import Chainweb.Test.SPV.EventProof hiding (tests) +import Chainweb.Test.Orphans.Internal () import Chainweb.Test.Utils import Chainweb.Time import Chainweb.Utils import Chainweb.Utils.Paging import Chainweb.Version - +import Chainweb.Version.Mainnet (mainnet) import Network.X509.SelfSigned - import P2P.Node import P2P.Node.Configuration import P2P.Peer import P2P.Test.Orphans () - -import Utils.Logging -import Control.Lens (view) -import Chainweb.Version.Mainnet (mainnet) import Pact.Core.Gas -import Chainweb.Ranked +import Pact.Parse +import Test.QuickCheck +import Test.QuickCheck.Instances () +import Test.Tasty +import Test.Tasty.QuickCheck +import Utils.Logging -- -------------------------------------------------------------------------- -- -- Roundrip Tests diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index 4cc8567394..a1180373f2 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Main @@ -14,75 +15,56 @@ module Main ( main, setTestLogLevel ) where +-- import qualified Chainweb.Test.BlockHeaderDB.PruneForks (tests) +-- import qualified Chainweb.Test.Mempool.Consensus (tests) +-- import qualified Chainweb.Test.Pact.SPVTest +-- import qualified Chainweb.Test.SPV (tests) +-- import qualified Chainweb.Test.SPV.EventProof (properties) +import Chainweb.Storage.Table.RocksDB +import Chainweb.Test.BlockHeader.Genesis qualified (tests) +import Chainweb.Test.BlockHeader.Validation qualified (tests) +import Chainweb.Test.BlockHeaderDB qualified (tests) +import Chainweb.Test.Chainweb.Utils.Paging qualified (properties) +import Chainweb.Test.Cut qualified (properties) +import Chainweb.Test.CutDB qualified (tests) +import Chainweb.Test.Difficulty qualified (properties) +import Chainweb.Test.HostAddress qualified (properties) +import Chainweb.Test.Mempool.InMem qualified (tests) +import Chainweb.Test.Mempool.RestAPI qualified (tests) +import Chainweb.Test.Mempool.Sync qualified (tests) +import Chainweb.Test.MinerReward qualified (tests) +import Chainweb.Test.Mining qualified (tests) +import Chainweb.Test.Misc qualified (tests) +import Chainweb.Test.Pact.CheckpointerTest qualified +import Chainweb.Test.Pact.HyperlanePluginTests qualified +import Chainweb.Test.Pact.PactServiceTest qualified +import Chainweb.Test.Pact.RemotePactTest qualified +import Chainweb.Test.Pact.TransactionExecTest qualified +import Chainweb.Test.Pact.TransactionTests qualified +import Chainweb.Test.Pact4.NoCoinbase qualified +import Chainweb.Test.Pact4.RewardsTest qualified +import Chainweb.Test.Pact4.SQLite qualified +import Chainweb.Test.Pact4.TransactionTests qualified +import Chainweb.Test.Pact4.VerifierPluginTest qualified +import Chainweb.Test.RestAPI qualified (tests) +import Chainweb.Test.Roundtrips qualified (tests) +import Chainweb.Test.Sync.WebBlockHeaderStore qualified (properties) +import Chainweb.Test.TreeDB qualified (properties) +import Chainweb.Test.TreeDB.RemoteDB qualified +import Chainweb.Test.Version qualified (tests) import Control.Monad.IO.Class import Control.Monad.Trans.Resource - +import Data.Test.PQueue qualified (properties) +import Data.Test.Word.Encoding qualified (properties) +import P2P.Test.Node qualified (properties) +import P2P.Test.TaskQueue qualified (properties) import System.Environment import System.LogLevel - +import Test.Chainweb.SPV.Argument qualified import Test.Tasty import Test.Tasty.JsonReporter import Test.Tasty.QuickCheck --- chainweb modules - -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB -import Chainweb.Storage.Table.RocksDB -import Chainweb.Version.Development -import Chainweb.Version.RecapDevelopment -import Chainweb.Version.Registry - --- chainweb-test-tools modules - -import Chainweb.Test.Utils - (independentSequentialTestGroup, toyChainId, withToyDB, toyVersion) - --- internal modules - -import qualified Chainweb.Test.BlockHeader.Genesis (tests) -import qualified Chainweb.Test.BlockHeader.Validation (tests) -import qualified Chainweb.Test.BlockHeaderDB (tests) --- import qualified Chainweb.Test.BlockHeaderDB.PruneForks (tests) -import qualified Chainweb.Test.Chainweb.Utils.Paging (properties) -import qualified Chainweb.Test.Cut (properties) -import qualified Chainweb.Test.CutDB (tests) -import qualified Chainweb.Test.Difficulty (properties) -import qualified Chainweb.Test.HostAddress (properties) --- import qualified Chainweb.Test.Mempool.Consensus (tests) -import qualified Chainweb.Test.Mempool.InMem (tests) -import qualified Chainweb.Test.Mempool.RestAPI (tests) -import qualified Chainweb.Test.Mempool.Sync (tests) -import qualified Chainweb.Test.MinerReward (tests) -import qualified Chainweb.Test.Mining (tests) -import qualified Chainweb.Test.Misc (tests) -import qualified Chainweb.Test.Pact.CheckpointerTest -import qualified Chainweb.Test.Pact.HyperlanePluginTests -import qualified Chainweb.Test.Pact.PactServiceTest -import qualified Chainweb.Test.Pact.RemotePactTest --- import qualified Chainweb.Test.Pact.SPVTest -import qualified Chainweb.Test.Pact.TransactionExecTest -import qualified Chainweb.Test.Pact.TransactionTests -import qualified Chainweb.Test.Pact4.NoCoinbase -import qualified Chainweb.Test.Pact4.RewardsTest -import qualified Chainweb.Test.Pact4.SQLite -import qualified Chainweb.Test.Pact4.VerifierPluginTest -import qualified Chainweb.Test.Pact4.TransactionTests -import qualified Chainweb.Test.RestAPI (tests) -import qualified Chainweb.Test.Roundtrips (tests) --- import qualified Chainweb.Test.SPV (tests) --- import qualified Chainweb.Test.SPV.EventProof (properties) -import qualified Chainweb.Test.Sync.WebBlockHeaderStore (properties) -import qualified Chainweb.Test.TreeDB (properties) -import qualified Chainweb.Test.TreeDB.RemoteDB -import qualified Chainweb.Test.Version (tests) -import qualified Data.Test.PQueue (properties) -import qualified Data.Test.Word.Encoding (properties) -import qualified P2P.Test.Node (properties) -import qualified P2P.Test.TaskQueue (properties) -import Chainweb.Version (withVersion) -import qualified Test.Chainweb.SPV.Argument - setTestLogLevel :: LogLevel -> IO () setTestLogLevel l = setEnv "CHAINWEB_TEST_LOG_LEVEL" (show l) From 9b2d57d47c6732af20fb650466d8bff256a2c49c Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 14 Sep 2025 20:15:26 -0700 Subject: [PATCH 331/378] rebase fixes --- src/Chainweb/BlockHeaderDB/Internal.hs | 1 - src/Chainweb/BlockHeaderDB/RemoteDB.hs | 1 - src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 1 + src/Chainweb/CutDB.hs | 2 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 1 - 5 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index f641429c81..52f01d059f 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -48,7 +48,6 @@ import Control.Exception.Safe import Control.DeepSeq import Control.Lens hiding (children) import Control.Monad -import Control.Monad.Trans.Maybe import Control.Monad.Trans.Resource hiding (throwM) import Data.Aeson diff --git a/src/Chainweb/BlockHeaderDB/RemoteDB.hs b/src/Chainweb/BlockHeaderDB/RemoteDB.hs index 6931a8c014..c54aaae966 100644 --- a/src/Chainweb/BlockHeaderDB/RemoteDB.hs +++ b/src/Chainweb/BlockHeaderDB/RemoteDB.hs @@ -21,7 +21,6 @@ module Chainweb.BlockHeaderDB.RemoteDB , remoteDb ) where -import Control.Error.Util (hush) import Control.Lens import Control.Monad.Catch (handle, throwM) diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index b3be442c9c..9ada676a3e 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -46,6 +46,7 @@ import Data.Function import Data.Functor.Of import Data.IORef import Data.Proxy +import qualified Data.Text as Text import Data.Text.Encoding (decodeUtf8) import Numeric.Natural(Natural) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index af691e5d54..7b64cde5f4 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -101,7 +101,7 @@ import Control.Monad.Morph import Control.Monad.STM import Control.Monad.Trans.Resource hiding (throwM) -import Data.Aeson (ToJSON, decodeStrict') +import Data.Aeson (ToJSON) import Data.Foldable import Data.Either (partitionEithers) import Data.Function diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index e974356e60..c8197c49d1 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -70,7 +70,6 @@ import Control.Monad import Control.Monad.Catch import Data.Foldable import Data.Hashable -import Data.HashSet qualified as HS import Data.LogMessage import Data.PQueue import Data.TaskMap From 417a1c73a190b12623ea43a9abc8f5172a9490fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20S=C3=B6ldner?= Date: Mon, 15 Sep 2025 10:17:57 +0200 Subject: [PATCH 332/378] remove errors package --- chainweb.cabal | 1 - src/Chainweb/Pact/Mempool/InMem.hs | 2 +- src/Chainweb/Pact4/SPV.hs | 3 +-- src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index d24a1dabe0..8dc2706bbc 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -408,7 +408,6 @@ library , direct-sqlite >= 2.3.27 , directory >= 1.3 , dlist >= 0.8 - , errors >= 2.3 , ethereum:{ethereum, secp256k1} >= 0.1 , exceptions >= 0.8 , file-embed >= 0.0 diff --git a/src/Chainweb/Pact/Mempool/InMem.hs b/src/Chainweb/Pact/Mempool/InMem.hs index d2f65e7487..be20204bb5 100644 --- a/src/Chainweb/Pact/Mempool/InMem.hs +++ b/src/Chainweb/Pact/Mempool/InMem.hs @@ -33,7 +33,6 @@ import Control.Applicative ((<|>)) import Control.Concurrent.Async import Control.Concurrent.MVar import Control.DeepSeq -import Control.Error.Util (hush) import Control.Exception (evaluate, mask_) import Control.Monad import Control.Monad.IO.Class @@ -493,6 +492,7 @@ insertCheckInMem' cfg lock txs now <- getCurrentTimeIntegral badmap <- withMVarMasked lock $ readIORef . _inmemBadMap curTxIdx <- withMVarMasked lock $ readIORef . _inmemCurrentTxs + let hush = either (const Nothing) Just let withHashes :: Vector (T2 TransactionHash t) withHashes = flip V.mapMaybe txs $ \tx -> diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs index a66b6e3b31..f8007079d6 100644 --- a/src/Chainweb/Pact4/SPV.hs +++ b/src/Chainweb/Pact4/SPV.hs @@ -30,7 +30,6 @@ module Chainweb.Pact4.SPV import GHC.Stack -import Control.Error import Control.Lens hiding (index) import Control.Monad import Control.Monad.Catch @@ -412,7 +411,7 @@ getTxIdx headerOracle pdb bh th = do & S.mapM toTxHash & sindex (== th) - r & note "unable to find transaction at the given block height" + r & maybe (Left "unable to find transaction at the given block height") Right & fmap int & return where diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs index d8ba043094..f82a198c5d 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Message/After225.hs @@ -18,7 +18,6 @@ module Chainweb.VerifierPlugin.Hyperlane.Message.After225 (runPlugin) where import Control.Lens -import Control.Error import Control.Exception (evaluate) import Control.Monad (unless) import Control.Monad.Except From fc320daa80ba91eaff12bf1525929b9767ead936 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 15 Sep 2025 21:27:04 -0700 Subject: [PATCH 333/378] Small documentation fixes in payload P2P REST API --- src/Chainweb/PayloadProvider/P2P/RestAPI.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs index 7ba9abcd55..496ce0af39 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs @@ -166,8 +166,8 @@ instance MimeRender OctetStream a => MimeRender OctetStream (RestPayload a) wher -- -- @GET \/chainweb\/\\/\\/chain\/\\/payload\/\@ -- --- * For Pact the a parameter is PayloadData --- * For EVM the a parameter is Header +-- * For Pact the result is the PayloadData +-- * For EVM the result is the EVM execution header -- type ServicePayloadGetApi_ (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) = "payload" @@ -189,10 +189,10 @@ servicePayloadGetApi = Proxy -- -------------------------------------------------------------------------- -- -- Payload GET API --- | @GET \/chainweb\/\\/\\/chain\/\\/height/\/payload\/\@ +-- | @GET \/chainweb\/\\/\\/chain\/\\/height\/\\/payload\/\@ -- --- * For Pact the a parameter is PayloadData --- * For EVM the a parameter is Header +-- * For Pact the result is the PayloadData +-- * For EVM the result is the EVM execution header -- type PayloadGetApi_ (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) = "height" From c638bd87df2ab3a4ace9955b0d2bd68e0f5a527d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 15 Sep 2025 21:27:46 -0700 Subject: [PATCH 334/378] Turn some datatypes into newtypes --- src/Chainweb/Version.hs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 87413eb617..5ba4d2da39 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -413,17 +413,18 @@ instance NFData PactUpgrade where pact4Upgrade :: [Pact4.Transaction] -> PactUpgrade pact4Upgrade txs = Pact4Upgrade txs False -data TxIdxInBlock = TxBlockIdx Word +newtype TxIdxInBlock = TxBlockIdx Word deriving stock (Eq, Ord, Show, Generic) deriving anyclass (Hashable, NFData) makePrisms ''TxIdxInBlock --- The type of quirks, i.e. special validation behaviors that are in some +-- | The type of quirks, i.e. special validation behaviors that are in some -- sense one-offs which can't be expressed as upgrade transactions and must be -- preserved. -data VersionQuirks = VersionQuirks - { _quirkGasFees :: (ChainMap (HashMap (BlockHeight, TxIdxInBlock) Gas)) +-- +newtype VersionQuirks = VersionQuirks + { _quirkGasFees :: ChainMap (HashMap (BlockHeight, TxIdxInBlock) Gas) } deriving stock (Show, Eq, Ord, Generic) deriving anyclass (NFData) From 9b250c9fdebfb4c51d080aee484d37d1630a98ac Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 14 Sep 2025 11:06:18 -0700 Subject: [PATCH 335/378] Add RankedBlockHeaderDB and simplify TreeDB a bit --- src/Chainweb/BlockHeaderDB/Internal.hs | 201 +++++++++++++++---- src/Chainweb/BlockHeaderDB/RemoteDB.hs | 3 +- src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 10 +- src/Chainweb/CutDB.hs | 4 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 8 +- src/Chainweb/TreeDB.hs | 46 +++-- 6 files changed, 199 insertions(+), 73 deletions(-) diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index 52f01d059f..4fa67e5370 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -1,13 +1,17 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} @@ -25,6 +29,19 @@ -- Internal BlockHeader DB implementation. This module must be imported only by -- modules within the @Chainweb.BlockHeader@ namespace. -- +-- Whenever possible prefer 'RankedBlockHeaderDb' over 'BlockHeaderDb' as it is +-- more efficient. +-- +-- TODO: +-- Consider renaming RankedBlockHeaderDb to BlockHeaderDb and BlockHeaderDb to +-- UnrankedBlockHeaderDb. Or remove the unranked version alltogether. +-- +-- Ideally, we would just inlcude the rank as the first 4 bytes in the block +-- hash, but that ship has probably sailed. (4 bytes are sufficient for about +-- 4G of blocks or 4000 years of block history at 2 blocks per minute. +-- Even when producint 1 block per ms, e.g. during tests, this would still +-- be sufficient for about 49 days.) +-- module Chainweb.BlockHeaderDB.Internal ( -- * Internal Types @@ -34,6 +51,7 @@ module Chainweb.BlockHeaderDB.Internal -- * Chain Database Handle , Configuration(..) , BlockHeaderDb(..) +, RankedBlockHeaderDb(..) , initBlockHeaderDb , closeBlockHeaderDb , withBlockHeaderDb @@ -41,22 +59,27 @@ module Chainweb.BlockHeaderDB.Internal -- * Insertion , insertBlockHeaderDb , unsafeInsertBlockHeaderDb + +-- * Misc +, type RankedBlockHeaderCas ) where import Control.Arrow -import Control.Exception.Safe import Control.DeepSeq +import Control.Exception.Safe import Control.Lens hiding (children) import Control.Monad +import Control.Monad.IO.Class import Control.Monad.Trans.Resource hiding (throwM) import Data.Aeson import Data.Function import Data.Hashable +import Data.HashSet qualified as HS import Data.Maybe import qualified Data.Text.Encoding as T -import GHC.Generics +import GHC.Generics (Generic) import Prelude hiding (lookup) @@ -69,6 +92,8 @@ import Chainweb.BlockHeader import Chainweb.BlockHeader.Validation import Chainweb.BlockHeight import Chainweb.ChainId +import Chainweb.Parent +import Chainweb.Ranked qualified as R import Chainweb.TreeDB import Chainweb.Utils hiding (Codec) import Chainweb.Utils.Paging @@ -79,8 +104,6 @@ import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB import Numeric.Additive -import Control.Monad.Except -import Control.Monad.IO.Class -- -------------------------------------------------------------------------- -- -- | Configuration of the chain DB. @@ -93,11 +116,16 @@ data Configuration = Configuration -- -------------------------------------------------------------------------- -- -- Ranked Block Header -newtype RankedBlockHeader = RankedBlockHeader { _getRankedBlockHeader :: BlockHeader } +newtype RankedBlockHeader = RankedBlockHeader + { _getRankedBlockHeader :: BlockHeader } deriving (Show, Generic) deriving anyclass (NFData) deriving newtype (Hashable, Eq, ToJSON, FromJSON) +instance R.IsRanked RankedBlockHeader where + rank = view blockHeight . _getRankedBlockHeader + {-# INLINE rank #-} + instance HasChainId RankedBlockHeader where _chainId = _chainId . _getRankedBlockHeader {-# INLINE _chainId #-} @@ -116,6 +144,20 @@ instance IsCasValue RankedBlockHeader where = RankedBlockHash (view blockHeight bh) (view blockHash bh) {-# INLINE casKey #-} +type RankedBlockHeaderCas tbl = Cas tbl RankedBlockHeader + +instance HasVersion => TreeDbEntry RankedBlockHeader where + type Key RankedBlockHeader = RankedBlockHash + key = _rankedBlockHash . _getRankedBlockHeader + {-# INLINE key #-} + rank = int . view blockHeight . _getRankedBlockHeader + {-# INLINE rank #-} + parent e + | isGenesisBlockHeader (_getRankedBlockHeader e) = Nothing + | otherwise = Just $ RankedBlockHash + (pred $ view blockHeight $ _getRankedBlockHeader e) + (unwrapParent $ view blockParent $ _getRankedBlockHeader e) + -- -------------------------------------------------------------------------- -- -- BlockRank @@ -160,7 +202,30 @@ instance HasChainId BlockHeaderDb where {-# INLINE _chainId #-} instance (k ~ CasKeyType BlockHeader, HasVersion) => ReadableTable BlockHeaderDb k BlockHeader where - tableLookup db k = either (\_ -> Nothing) Just <$> lookup db k + tableLookup = lookup + {-# INLINE tableLookup #-} + +-- -------------------------------------------------------------------------- -- +-- RankedBlockHeaderDb + +-- | A rangked block header db uses the same underlying storage as a +-- 'BlockHeaderDb' but always includes the header rank in the key, which +-- results in more efficient queries, because it bypasses a lookup in the rank +-- table. +-- +newtype RankedBlockHeaderDb = RankedBlockHeaderDb + { _rankedBlockHeaderDb :: BlockHeaderDb } + deriving (Generic) + +instance HasChainId RankedBlockHeaderDb where + _chainId = _chainId . _rankedBlockHeaderDb + {-# INLINE _chainId #-} + +instance + (k ~ CasKeyType RankedBlockHeader, HasVersion) + => ReadableTable RankedBlockHeaderDb k RankedBlockHeader + where + tableLookup = lookup {-# INLINE tableLookup #-} -- -------------------------------------------------------------------------- -- @@ -260,47 +325,100 @@ withBlockHeaderDb db cid = snd <$> allocate start closeBlockHeaderDb instance HasVersion => TreeDb BlockHeaderDb where type DbEntry BlockHeaderDb = BlockHeader - lookup db h = runExceptT $ do - -- lookup rank - r <- liftIO (tableLookup (_chainDbRankTable db) h) >>= \case - Nothing -> throwError "" - Just v -> return v - ExceptT $ lookupRanked db (int r) h + lookup db h = do + liftIO (tableLookup (_chainDbRankTable db) h) >>= \case + Nothing -> return Nothing + Just v -> fmap _getRankedBlockHeader + <$> lookup (RankedBlockHeaderDb db) (RankedBlockHash (int v) h) {-# INLINEABLE lookup #-} - lookupRanked db r h = runExceptT $ do - rh <- liftIO (tableLookup (_chainDbCas db) (RankedBlockHash (int r) h)) >>= \case - Nothing -> throwError "" - Just v -> return v - return $! _getRankedBlockHeader rh + lookupRanked db r h = fmap _getRankedBlockHeader + <$> lookupRanked (RankedBlockHeaderDb db) r (RankedBlockHash (int r) h) {-# INLINEABLE lookupRanked #-} - entries db k l mir mar f = withSeekTreeDb db k mir $ \it -> f $ do + entries db k l mir mar f = do + rk <- mapM (mapM (getRankedKey db)) k + entries (RankedBlockHeaderDb db) rk l mir mar + $ f . S.map _getRankedBlockHeader + {-# INLINEABLE entries #-} + + branchEntries db k l mir mar lower upper f = do + -- we use the ranked implementation as it is more efficient + -- TODO run these queries in parallel or batch them; usually it is a + -- relatively small number. + let mapSetM a = fmap HS.fromList . mapM a . HS.toList + rk <- mapM (mapM (getRankedKey db)) k + rlow <- mapSetM (mapM (getRankedKey db)) lower + rup <- mapSetM (mapM (getRankedKey db)) upper + chainBranchEntries (RankedBlockHeaderDb db) rk l mir mar rlow rup + $ f . S.map _getRankedBlockHeader + {-# INLINEABLE branchEntries #-} + + keys db k l mir mar f = do + rk <- mapM (mapM (getRankedKey db)) k + keys (RankedBlockHeaderDb db) rk l mir mar + $ f . S.map _rankedBlockHashHash + {-# INLINEABLE keys #-} + + maxEntry db = _getRankedBlockHeader <$> maxEntry (RankedBlockHeaderDb db) + {-# INLINEABLE maxEntry #-} + + maxRank db = maxRank (RankedBlockHeaderDb db) + {-# INLINEABLE maxRank #-} + +getRankedKey + :: HasVersion + => BlockHeaderDb + -> BlockHash + -> IO RankedBlockHash +getRankedKey db h = do + liftIO (tableLookup (_chainDbRankTable db) h) >>= \case + Nothing -> throwM $ TreeDbKeyNotFound @BlockHeaderDb h "getRankedKey.lookup" + Just v -> return $ RankedBlockHash (int v) h +{-# INLINE getRankedKey #-} + +-- -------------------------------------------------------------------------- -- +-- TreeDB instance for RankedBlockHeaderDb + +instance HasVersion => TreeDb RankedBlockHeaderDb where + type DbEntry RankedBlockHeaderDb = RankedBlockHeader + + lookup db h = tableLookup (_chainDbCas $ _rankedBlockHeaderDb db) h + {-# INLINEABLE lookup #-} + + -- If the rank is inconsistent with the height in the key 'Nothing' is + -- returned. This is consistent with the behavior of the unraked + -- BlockHeaderDb instance. + -- + lookupRanked db r h + | int r /= R._rankedHeight h = return Nothing + | otherwise = lookup db h + {-# INLINEABLE lookupRanked #-} + + entries db k l mir mar f = withSeekRanked db k mir $ \it -> f $ do iterToValueStream it - & S.map _getRankedBlockHeader - & maybe id (\x -> S.takeWhile (\a -> int (view blockHeight a) <= x)) mar + & maybe id (\x -> S.takeWhile (\a -> int (rank a) <= x)) mar & limitStream l {-# INLINEABLE entries #-} branchEntries = chainBranchEntries {-# INLINEABLE branchEntries #-} - keys db k l mir mar f = withSeekTreeDb db k mir $ \it -> f $ do + keys db k l mir mar f = withSeekRanked db k mir $ \it -> f $ do iterToKeyStream it & maybe id (\x -> S.takeWhile (\a -> int (_rankedBlockHashHeight a) <= x)) mar - & S.map _rankedBlockHashHash & limitStream l {-# INLINEABLE keys #-} - maxEntry db = withTableIterator (_chainDbCas db) $ \it -> do + maxEntry db = withTableIterator (_chainDbCas $ _rankedBlockHeaderDb db) $ \it -> do iterLast it iterValue it >>= \case - Just (RankedBlockHeader !r) -> return r + Just !r -> return r Nothing -> throwM $ InternalInvariantViolation "BlockHeaderDb.maxEntry: empty block header db" {-# INLINEABLE maxEntry #-} - maxRank db = withTableIterator (_chainDbCas db) $ \it -> do + maxRank db = withTableIterator (_chainDbCas $ _rankedBlockHeaderDb db) $ \it -> do iterLast it iterKey it >>= \case Just (RankedBlockHash !r _) -> return $! int r @@ -308,15 +426,19 @@ instance HasVersion => TreeDb BlockHeaderDb where $ InternalInvariantViolation "BlockHeaderDb.maxRank: empty block header db" {-# INLINEABLE maxRank #-} -withSeekTreeDb - :: BlockHeaderDb - -> Maybe (NextItem BlockHash) +-- -------------------------------------------------------------------------- -- + +withSeekRanked + :: RankedBlockHeaderDb + -> Maybe (NextItem RankedBlockHash) -> Maybe MinRank -> (RocksDbTableIter RankedBlockHash RankedBlockHeader -> IO a) -> IO a -withSeekTreeDb db k mir kont = - withTableIterator (_chainDbCas db) (\it -> seekTreeDb db k mir it >> kont it) -{-# INLINE withSeekTreeDb #-} +withSeekRanked db k mir kont = + withTableIterator (_chainDbCas $ _rankedBlockHeaderDb db) $ \it -> do + seekRanked k mir it + kont it +{-# INLINE withSeekRanked #-} -- | If @k@ is not 'Nothing', @seekTreeDb d k mir@ seeks key @k@ in @db@. If the -- key doesn't exist it throws @TreeDbKeyNotFound@. Otherwise if @k@ was @@ -330,13 +452,12 @@ withSeekTreeDb db k mir kont = -- If both @k@ and @minr@ are 'Nothing' it returns an iterator that points to -- the first entry in @d@. -- -seekTreeDb - :: BlockHeaderDb - -> Maybe (NextItem BlockHash) +seekRanked + :: Maybe (NextItem RankedBlockHash) -> Maybe MinRank -> RocksDbTableIter RankedBlockHash RankedBlockHeader -> IO () -seekTreeDb db k mir it = do +seekRanked k mir it = do case k of Nothing -> case mir of Nothing -> return () @@ -344,18 +465,14 @@ seekTreeDb db k mir it = do $ RankedBlockHash (BlockHeight $ int $ _getMinRank r) nullBlockHash Just a -> do - -- Seek to cursor let x = _getNextItem a - r <- tableLookup (_chainDbRankTable db) x >>= \case - Nothing -> throwM $ TreeDbKeyNotFound @BlockHeaderDb x "seekTreeDb.lookup" - (Just !b) -> return b - iterSeek it (RankedBlockHash r x) + iterSeek it x -- if we don't find the cursor, throw exception iterKey it >>= \case - Just (RankedBlockHash _ b) | b == x -> return () - _ -> throwM $ TreeDbKeyNotFound @BlockHeaderDb x "seekTreeDb.iterKey" + Just b | b == x -> return () + _ -> throwM $ TreeDbKeyNotFound @RankedBlockHeaderDb x "seekTreeDb.iterKey" -- If the cursor is exclusive, then advance the iterator when (isExclusive a) $ iterNext it diff --git a/src/Chainweb/BlockHeaderDB/RemoteDB.hs b/src/Chainweb/BlockHeaderDB/RemoteDB.hs index c54aaae966..100bffc2a0 100644 --- a/src/Chainweb/BlockHeaderDB/RemoteDB.hs +++ b/src/Chainweb/BlockHeaderDB/RemoteDB.hs @@ -62,8 +62,7 @@ instance HasVersion => TreeDb RemoteDb where maxEntry = error "Chainweb.TreeDB.RemoteDB.RemoteDb.maxEntry: not implemented" -- If other default functions rely on this, it could be quite inefficient. - lookup (RemoteDb env alog cid) k = do - over _Left (\e -> "client error: " <> sshow e) <$> runClientM client env + lookup (RemoteDb env alog cid) k = either (const Nothing) Just <$> runClientM client env where client = logServantError alog "failed to query tree db entry" $ headerClient cid k diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index 9ada676a3e..bcffc96abc 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -86,12 +86,11 @@ checkKey -> DbKey db -> m (DbKey db) checkKey !db !k = liftIO (lookup db k) >>= \case - Left m -> throwError $ err404Msg $ object $ concat + Nothing -> throwError $ err404Msg $ object $ concat [ ["reason" .= ("key not found" :: String)] - , ("details" .= m) <$ guard (not (Text.null m)) , ["key" .= k] ] - Right _ -> pure k + Just !_ -> pure k err404Msg :: ToJSON msg => msg -> ServerError err404Msg msg = setErrJSON msg err404 @@ -263,12 +262,11 @@ headerHandler -> DbKey db -> Handler (DbEntry db) headerHandler db k = liftIO (lookup db k) >>= \case - Left m -> throwError $ err404Msg $ object $ concat + Nothing -> throwError $ err404Msg $ object $ concat [ ["reason" .= ("key not found" :: String)] - , ("details" .= m) <$ guard (not (Text.null m)) , ["key" .= k] ] - Right e -> pure e + Just !e -> pure e -- -------------------------------------------------------------------------- -- -- BlockHeaderDB API Server diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 7b64cde5f4..8e402a5fa5 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -1017,8 +1017,8 @@ memberOfHeader -> IO Bool memberOfHeader db cid h ctx = do lookup chainDb h >>= \case - Left{} -> return False - Right lh -> seekAncestor chainDb ctx (int $ view blockHeight lh) >>= \case + Nothing -> return False + Just !lh -> seekAncestor chainDb ctx (int $ view blockHeight lh) >>= \case Nothing -> return False Just x -> return $ view blockHash x == h where diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index c8197c49d1..8d848cade8 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -526,7 +526,7 @@ getBlockHeaderInternal logg Debug $ "getBlockHeaderInternal: got block header for " <> sshow h return bh - where + where mgr = _webBlockHeaderStoreMgr headerStore cas = WebBlockHeaderCas $ _webBlockHeaderStoreCas headerStore @@ -596,11 +596,11 @@ getBlockHeaderInternal !r <- trace logfun (traceLabel "pullOrigin") k 0 $ TDB.lookup (rDb cid originEnv) k case r of - Left err -> do + Nothing -> do logg Warn $ taskMsg k - $ "failed to pull from origin " <> sshow origin <> " with " <> err + $ "failed to pull from origin " <> sshow origin <> " key " <> sshow k return Nothing - Right v -> do + Just !v -> do logg Debug $ taskMsg ck "received from origin" return $ Just v diff --git a/src/Chainweb/TreeDB.hs b/src/Chainweb/TreeDB.hs index 6db5c97000..87d5202693 100644 --- a/src/Chainweb/TreeDB.hs +++ b/src/Chainweb/TreeDB.hs @@ -1,7 +1,10 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveFoldable #-} +{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} @@ -9,6 +12,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE UndecidableInstances #-} @@ -172,10 +176,12 @@ _getMaxRank (MaxRank (Max r)) = r newtype LowerBound k = LowerBound { _getLowerBound :: k } deriving stock (Eq, Show, Ord, Generic) deriving anyclass (Hashable) + deriving (Functor, Foldable, Traversable) newtype UpperBound k = UpperBound { _getUpperBound :: k } deriving stock (Eq, Show, Ord, Generic) deriving anyclass (Hashable) + deriving (Functor, Foldable, Traversable) data BranchBounds db = BranchBounds { _branchBoundsLower :: !(HS.HashSet (LowerBound (DbKey db))) @@ -235,6 +241,9 @@ class (Typeable db, TreeDbEntry (DbEntry db)) => TreeDb db where -- | Lookup a single entry by its key. -- + -- A missing key is reported by returning 'Nothing'. Database errors are + -- reported as exceptions. + -- -- Implementations are expected to provide \(\mathcal{O}(1)\) performance. -- -- prop> lookup db k == S.head_ (entries db k 1 Nothing Nothing) @@ -242,19 +251,22 @@ class (Typeable db, TreeDbEntry (DbEntry db)) => TreeDb db where lookup :: db -> DbKey db - -> IO (Either T.Text (DbEntry db)) + -> IO (Maybe (DbEntry db)) -- | Lookup a single entry by its key and rank. For some instances of -- the lookup can be implemented more efficiently when the rank is know. -- Otherwise the default implementation just ignores the rank parameter -- falls back to 'lookup'. -- + -- A missing key is reported by returning 'Nothing'. Database errors are + -- reported as exceptions. + -- lookupRanked :: db -> Natural -> DbKey db - -> IO (Either T.Text (DbEntry db)) - lookupRanked db _ = lookup db + -> IO (Maybe (DbEntry db)) + lookupRanked db _ k = lookup db k {-# INLINEABLE lookupRanked #-} -- ---------------------------------------------------------------------- -- @@ -505,8 +517,8 @@ chainBranchEntries db k l mir mar@(Just (MaxRank (Max m))) lower upper f = do defaultBranchEntries db k l mir mar lower upper' f where start (UpperBound u) = lookup db u >>= \case - Left{} -> return mempty - Right e -> seekAncestor db e m >>= \case + Nothing -> return mempty + Just e -> seekAncestor db e m >>= \case Nothing -> return mempty Just x -> return $ HS.singleton (UpperBound $! key x) {-# INLINEABLE chainBranchEntries #-} @@ -642,8 +654,8 @@ lookupM -> DbKey db -> IO (DbEntry db) lookupM db k = lookup db k >>= \case - Left e -> throwM $ TreeDbKeyNotFound @db k $ "lookupM: " <> e - Right !x -> return x + Nothing -> throwM $ TreeDbKeyNotFound @db k $ "lookupM: " <> sshow k + Just !x -> return x {-# INLINEABLE lookupM #-} lookupRankedM @@ -654,8 +666,8 @@ lookupRankedM -> DbKey db -> IO (DbEntry db) lookupRankedM db r k = lookupRanked db r k >>= \case - Left e -> throwM $ TreeDbKeyNotFound @db k $ "lookupRankedM (" <> sshow r <> "): " <> e - Right !x -> return x + Nothing -> throwM $ TreeDbKeyNotFound @db k $ "lookupRankedM (" <> sshow r <> "): " <> sshow k + Just !x -> return x {-# INLINEABLE lookupRankedM #-} -- | Lookup all entries in a stream of database keys and return the stream @@ -676,7 +688,7 @@ lookupStream => db -> S.Stream (Of (DbKey db)) IO r -> S.Stream (Of (DbEntry db)) IO r -lookupStream db = S.catMaybes . S.mapM (fmap (either (\_ -> Nothing) Just) . lookup db) +lookupStream db = S.catMaybes . S.mapM (lookup db) data GenesisParent = GenesisParentThrow @@ -702,8 +714,8 @@ lookupParentM g db e = case parent e of _ -> throwM $ InternalInvariantViolation "Chainweb.TreeDB.lookupParentM: Called getParentEntry on genesis block" Just p -> lookup db p >>= \case - Left m -> throwM $ TreeDbParentMissing @db e ("lookupParentM: " <> m) - Right !x -> return x + Nothing -> throwM $ TreeDbParentMissing @db e ("lookupParentM: " <> sshow p) + Just !x -> return x -- | Replace all entries in the stream by their parent entries. -- If the genesis block is part of the stream it is replaced by itself. @@ -722,8 +734,8 @@ lookupParentStreamM g db = S.mapMaybeM $ \e -> case parent e of GenesisParentThrow -> throwM $ InternalInvariantViolation "Chainweb.TreeDB.lookupParentStreamM: Called getParentEntry on genesis block. Most likely this means that the genesis headers haven't been generated correctly. If you are using a development or testing chainweb version consider resetting the databases." Just p -> lookup db p >>= \case - Left m -> throwM $ TreeDbParentMissing @db e $ "lookupParentStreamM: " <> m - Right !x -> return (Just x) + Nothing -> throwM $ TreeDbParentMissing @db e $ "lookupParentStreamM: " <> sshow p + Just !x -> return (Just x) -- | Interpret a given `BlockHeaderDb` as a native Haskell `Tree`. Should be -- used only for debugging purposes. @@ -1043,10 +1055,10 @@ ancestorOfEntry -- ^ the context, i.e. the branch of the chain that contains the member -> IO Bool ancestorOfEntry db h ctx = lookup db h >>= \case - Left{} -> return False - Right lh -> seekAncestor db ctx (rank lh) >>= \case + Nothing -> return False + Just !lh -> seekAncestor db ctx (rank lh) >>= \case Nothing -> return False - Just x -> return $ key x == h + Just !x -> return $ key x == h -- | @ancestorOfEntry db h ctx@ returns @True@ if @h@ is an ancestor of @ctx@ -- in @db@. From ff543c045977b8781103086e3916798d63d7ab93 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 15 Sep 2025 21:28:44 -0700 Subject: [PATCH 336/378] Export RankedBlockHeader --- src/Chainweb/BlockHeaderDB.hs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/BlockHeaderDB.hs b/src/Chainweb/BlockHeaderDB.hs index 9c462b0f8c..e52cc32862 100644 --- a/src/Chainweb/BlockHeaderDB.hs +++ b/src/Chainweb/BlockHeaderDB.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE ExplicitNamespaces #-} + -- | -- Module: Chainweb.BlockHeaderDB -- Copyright: Copyright © 2018 Kadena LLC. @@ -9,12 +11,20 @@ -- module Chainweb.BlockHeaderDB ( +-- * Ranked Block Header + RankedBlockHeader(..) +, BlockRank(..) + -- * Chain Database Handle - Configuration(..) +, Configuration(..) , BlockHeaderDb +, RankedBlockHeaderDb(..) , initBlockHeaderDb , closeBlockHeaderDb , withBlockHeaderDb + +-- * Misc +, type RankedBlockHeaderCas ) where -- internal imports From 1b7c50ca6d2fa68ede08e7f0302f0aafc1ec2d8e Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 15 Sep 2025 21:29:20 -0700 Subject: [PATCH 337/378] Remote redundant language pragma in BlockHeaderDB --- src/Chainweb/BlockHeaderDB/Internal.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index 4fa67e5370..45c70d6d31 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -11,7 +11,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} From 03ea1eff4bfa17421131c52e554313dbb1b107f4 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 17 Sep 2025 11:44:43 -0400 Subject: [PATCH 338/378] Typo fix Change-Id: Id00000002c8388ed82aeb7af9ff26f32292f4310 --- src/Chainweb/PayloadProvider/EVM.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index e5423ff897..c3327a7d58 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1051,7 +1051,7 @@ data EvmNewPayloadExeception { _inconsistentPayloadBlockValue :: !BlockValue , _inconsistentPayloadFees :: !Stu } - | InconsistenNewPayloadHash (Expected EVM.BlockHash) (Actual EVM.BlockHash) + | InconsistentNewPayloadHash (Expected EVM.BlockHash) (Actual EVM.BlockHash) deriving (Show, Eq, Generic) instance Exception EvmNewPayloadExeception From 97e4e2c4daf7d842445a5d430f8ed9f2416c2421 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 17 Sep 2025 11:46:04 -0400 Subject: [PATCH 339/378] Typo fix 2 --- src/Chainweb/PayloadProvider/EVM.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index c3327a7d58..e418986e59 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1179,7 +1179,7 @@ awaitNewPayload p = do -- response unless (newEvmBlockHash == EVM._hdrHash pldHdr) $ do lf Warn $ "Inconsitent new payload hash for " <> brief (phdr, pheight) - throwM $ InconsistenNewPayloadHash + throwM $ InconsistentNewPayloadHash (Expected newEvmBlockHash) (Actual (EVM._hdrHash pldHdr)) From 0676353e873456a1a4535dfac3a26807cfcd77e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20S=C3=B6ldner?= Date: Thu, 28 Aug 2025 09:13:39 +0200 Subject: [PATCH 340/378] add blockhistory migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate BlockHistory table to add missing column for the new payload design. Specifically: Constraints such as the uniqueness of the blockhash column must be satisfied at the time the column is added. SQLite does not allow adding columns with unique constraints via ALTER TABLE (see SQLite docs). This PR introduces a migration that: Runs only if tableNeedsMigration indicates it’s required. Performs a row-wise migration of existing data from the old BlockHistory table and the BlockHeaderDb (backed by RocksDB). Migrates data in chunks of 10,000 entries, each chunk committed in its own transaction for safety and performance. Note The migration module is temporary and can be removed once all deployments have successfully migrated. --- chainweb.cabal | 33 +++ src/Chainweb/BlockHeader/Internal.hs | 1 + src/Chainweb/Chainweb/ChainResources.hs | 1 + src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 14 +- src/Chainweb/Pact/Backend/Compaction.hs | 19 +- src/Chainweb/Pact/Backend/PactState.hs | 12 +- src/Chainweb/Pact/Backend/Utils.hs | 8 +- src/Chainweb/Pact4/Backend/ChainwebPactDb.hs | 4 +- src/Chainweb/PayloadProvider/Pact.hs | 46 ++-- .../Pact/BlockHistoryMigration.hs | 144 ++++++++++++ .../BlockHistoryMigrationTests.hs | 128 +++++++++++ .../Test/Pact/BlockHistoryMigrationTest.hs | 210 ++++++++++++++++++ test/unit/ChainwebTests.hs | 2 + 13 files changed, 581 insertions(+), 41 deletions(-) create mode 100644 src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs create mode 100644 test/blockhistory-migration/BlockHistoryMigrationTests.hs create mode 100644 test/unit/Chainweb/Test/Pact/BlockHistoryMigrationTest.hs diff --git a/chainweb.cabal b/chainweb.cabal index 8dc2706bbc..c80747c65c 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -246,6 +246,7 @@ library , Chainweb.PayloadProvider.Pact , Chainweb.PayloadProvider.Pact.Configuration , Chainweb.PayloadProvider.Pact.Genesis + , Chainweb.PayloadProvider.Pact.BlockHistoryMigration , Chainweb.PayloadProvider.P2P , Chainweb.PayloadProvider.P2P.RestAPI , Chainweb.PayloadProvider.P2P.RestAPI.Client @@ -642,6 +643,7 @@ test-suite chainweb-tests Chainweb.Test.Mining Chainweb.Test.Misc Chainweb.Test.Pact.CheckpointerTest + Chainweb.Test.Pact.BlockHistoryMigrationTest Chainweb.Test.Pact.CutFixture Chainweb.Test.Pact.HyperlanePluginTests Chainweb.Test.Pact.PactServiceTest @@ -751,6 +753,37 @@ test-suite chainweb-tests if flag(ed25519) cpp-options: -DWITH_ED25519=1 +test-suite blockhistory-migration-tests + default-language: Haskell2010 + type: exitcode-stdio-1.0 + hs-source-dirs: test/blockhistory-migration + main-is: BlockHistoryMigrationTests.hs + + build-depends: + -- internal + , chainweb + , chainweb:chainweb-test-utils + + -- external + , base >= 4.12 && < 5 + , chainweb-storage >= 0.1 + , loglevel >= 0.1 + , tasty >= 1.0 + , tasty-hunit >= 0.9 + , temporary >= 1.3 + , filepath + , directory + , mtl + , bytestring + , direct-sqlite + , resourcet + , streaming + , HUnit + , lens + + if flag(ed25519) + cpp-options: -DWITH_ED25519=1 + test-suite compaction-tests import: warning-flags, debugging-flags buildable: False diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index 66cfcb1913..b62e68130a 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -62,6 +62,7 @@ module Chainweb.BlockHeader.Internal , encodeEpochStartTime , decodeEpochStartTime , epochStart +, makeGenesisBlockHeader -- * FeatureFlags , FeatureFlags diff --git a/src/Chainweb/Chainweb/ChainResources.hs b/src/Chainweb/Chainweb/ChainResources.hs index 4598c175bc..21eaa4dac1 100644 --- a/src/Chainweb/Chainweb/ChainResources.hs +++ b/src/Chainweb/Chainweb/ChainResources.hs @@ -374,6 +374,7 @@ withPayloadProviderResources logger cid serviceApiConfig peerStuff rdb rewindLim pp <- withPactPayloadProvider cid + rdb (view _4 <$> peerStuff) logger Nothing diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index ac322d8933..273a0f1d90 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -717,7 +717,7 @@ commitBlockStateToDatabase db blockInfo blockHandle = throwOnDbError $ do , SInt (fromIntegral t) ] where - stmt = "INSERT INTO BlockHistory ('blockheight', 'hash', 'payloadhash', 'endingtxid') VALUES (?,?,?,?);" + stmt = "INSERT INTO BlockHistory2 ('blockheight', 'hash', 'payloadhash', 'endingtxid') VALUES (?,?,?,?);" createUserTable :: SQ3.Utf8 -> ExceptT LocatedSQ3Error IO () createUserTable tablename = do @@ -838,7 +838,7 @@ initSchema sql = createBlockHistoryTable :: ExceptT LocatedSQ3Error IO () createBlockHistoryTable = do exec_ sql - "CREATE TABLE IF NOT EXISTS BlockHistory \ + "CREATE TABLE IF NOT EXISTS BlockHistory2 \ \(blockheight UNSIGNED BIGINT NOT NULL, \ \ endingtxid UNSIGNED BIGINT NOT NULL, \ \ hash BLOB NOT NULL, \ @@ -883,7 +883,7 @@ getSerialiser = do getPayloadsAfter :: HasCallStack => SQLiteEnv -> Parent BlockHeight -> ExceptT LocatedSQ3Error IO [Ranked BlockPayloadHash] getPayloadsAfter db parentHeight = do - qry db "SELECT blockheight, payloadhash FROM BlockHistory WHERE blockheight > ?" + qry db "SELECT blockheight, payloadhash FROM BlockHistory2 WHERE blockheight > ?" [SInt (fromIntegral @BlockHeight @Int64 (unwrapParent parentHeight))] [RInt, RBlob] >>= traverse \case @@ -900,7 +900,7 @@ getEarliestBlock db = do [] -> return Nothing (!o:_) -> return (Just o) where - qtext = "SELECT blockheight, hash FROM BlockHistory ORDER BY blockheight ASC LIMIT 1" + qtext = "SELECT blockheight, hash FROM BlockHistory2 ORDER BY blockheight ASC LIMIT 1" go [SInt hgt, SBlob blob] = let hash = either error id $ runGetEitherS decodeBlockHash blob @@ -915,7 +915,7 @@ lookupBlockWithHeight db bheight = do [] -> return Nothing res -> error $ "Invalid result, " <> sshow res where - qtext = "SELECT hash FROM BlockHistory WHERE blockheight = ?;" + qtext = "SELECT hash FROM BlockHistory2 WHERE blockheight = ?;" lookupBlockHash :: HasCallStack => SQ3.Database -> BlockHash -> ExceptT LocatedSQ3Error IO (Maybe BlockHeight) lookupBlockHash db hash = do @@ -924,7 +924,7 @@ lookupBlockHash db hash = do [] -> return $ Nothing res -> error $ "Invalid result, " <> sshow res where - qtext = "SELECT blockheight FROM BlockHistory WHERE hash = ?;" + qtext = "SELECT blockheight FROM BlockHistory2 WHERE hash = ?;" lookupRankedBlockHash :: HasCallStack => SQ3.Database -> RankedBlockHash -> IO Bool lookupRankedBlockHash db rankedBHash = throwOnDbError $ do @@ -935,4 +935,4 @@ lookupRankedBlockHash db rankedBHash = throwOnDbError $ do [[SInt n]] -> return $! n == 1 res -> error $ "Invalid result, " <> sshow res where - qtext = "SELECT COUNT(*) FROM BlockHistory WHERE blockheight = ? AND hash = ?;" + qtext = "SELECT COUNT(*) FROM BlockHistory2 WHERE blockheight = ? AND hash = ?;" diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index a7ec9f0d6e..f0e1782073 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -234,7 +234,7 @@ compactPactState logger rt targetBlockHeight srcDb targetDb = do do log LL.Info "Compacting BlockHistory" activeRow <- getBlockHistoryRowAt logger srcDb targetBlockHeight - throwOnDbError $ exec' targetDb "INSERT INTO BlockHistory VALUES (?1, ?2, ?3)" activeRow + throwOnDbError $ exec' targetDb "INSERT INTO BlockHistory2 ('blockheight', 'hash', 'payloadhash', 'endingtxid') VALUES (?1, ?2, ?3, ?4)" activeRow -- Compact VersionedTableMutation -- This is extremely fast and low residency @@ -497,10 +497,11 @@ createCheckpointerTables db logger = do log "Creating Checkpointer table BlockHistory" inTx db $ throwOnDbError $ exec_ db $ mconcat - [ "CREATE TABLE IF NOT EXISTS BlockHistory " + [ "CREATE TABLE IF NOT EXISTS BlockHistory2 " , "(blockheight UNSIGNED BIGINT NOT NULL" - , ", hash BLOB NOT NULL" , ", endingtxid UNSIGNED BIGINT NOT NULL" + , ", hash BLOB NOT NULL" + , ", payloadhash BLOB NOT NULL" , ");" ] @@ -530,7 +531,7 @@ createCheckpointerTables db logger = do -- We have to delete from these tables because of the way the test harnesses work. -- Ideally in the future this can be removed. - forM_ ["BlockHistory", "VersionedTableCreation", "VersionedTableMutation", "TransactionIndex"] $ \tblname -> do + forM_ ["BlockHistory2", "VersionedTableCreation", "VersionedTableMutation", "TransactionIndex"] $ \tblname -> do log $ "Deleting from table " <> fromUtf8 tblname throwOnDbError $ exec_ db $ "DELETE FROM " <> tbl tblname @@ -541,7 +542,7 @@ createCheckpointerIndexes db logger = do log "Creating BlockHistory index" inTx db $ throwOnDbError $ exec_ db - "CREATE UNIQUE INDEX IF NOT EXISTS BlockHistory_blockheight_unique_ix ON BlockHistory (blockheight)" + "CREATE UNIQUE INDEX IF NOT EXISTS BlockHistory_blockheight_unique_ix ON BlockHistory2 (blockheight)" log "Creating VersionedTableCreation index" inTx db $ throwOnDbError $ exec_ db @@ -586,18 +587,18 @@ createUserTableIndex db tblname = do , tbl tblname, " (txid DESC)" ] --- | Returns the active @(blockheight, hash, endingtxid)@ from BlockHistory +-- | Returns the active @(blockheight, hash, endingtxid)@ from BlockHistory2 getBlockHistoryRowAt :: (Logger logger) => logger -> Database -> BlockHeight -> IO [SType] getBlockHistoryRowAt logger db target = do - r <- throwOnDbError $ qry db "SELECT blockheight, hash, endingtxid FROM BlockHistory WHERE blockheight = ?1" [SInt (int target)] [RInt, RBlob, RInt] + r <- throwOnDbError $ qry db "SELECT blockheight, hash, payloadhash, endingtxid FROM BlockHistory2 WHERE blockheight = ?1" [SInt (int target)] [RInt, RBlob, RBlob, RInt] case r of - [row@[SInt bh, SBlob _hash, SInt _endingTxId]] -> do + [row@[SInt bh, SBlob _hash, SBlob _phash, SInt _endingTxId]] -> do unless (target == int bh) $ do - exitLog logger "BlockHeight mismatch in BlockHistory query. This is a bug in the compaction tool. Please report it on the issue tracker or discord." + exitLog logger "BlockHeight mismatch in BlockHistory2 query. This is a bug in the compaction tool. Please report it on the issue tracker or discord." pure row _ -> do exitLog logger "getBlockHistoryRowAt query: invalid query" diff --git a/src/Chainweb/Pact/Backend/PactState.hs b/src/Chainweb/Pact/Backend/PactState.hs index 202c70523c..579d524c47 100644 --- a/src/Chainweb/Pact/Backend/PactState.hs +++ b/src/Chainweb/Pact/Backend/PactState.hs @@ -93,20 +93,20 @@ import Control.Monad.Trans.Resource excludedTables :: [Utf8] excludedTables = checkpointerTables ++ compactionTables where - checkpointerTables = ["BlockHistory", "VersionedTableCreation", "VersionedTableMutation", "TransactionIndex"] + checkpointerTables = ["BlockHistory2", "VersionedTableCreation", "VersionedTableMutation", "TransactionIndex"] compactionTables = ["CompactGrandHash", "CompactActiveRow"] -- | Get the latest blockheight on chain. getLatestBlockHeight :: Database -> IO BlockHeight getLatestBlockHeight db = do - let qryText = "SELECT MAX(blockheight) FROM BlockHistory" + let qryText = "SELECT MAX(blockheight) FROM BlockHistory2" throwOnDbError $ qry db qryText [] [RInt] >>= \case [[SInt bh]] -> pure (BlockHeight (int bh)) _ -> error "getLatestBlockHeight: expected int" getEarliestBlockHeight :: Database -> IO BlockHeight getEarliestBlockHeight db = do - let qryText = "SELECT MIN(blockheight) FROM BlockHistory" + let qryText = "SELECT MIN(blockheight) FROM BlockHistory2" throwOnDbError $ qry db qryText [] [RInt] >>= \case [[SInt bh]] -> pure (BlockHeight (int bh)) _ -> error "getEarliestBlockHeight: expected int" @@ -116,13 +116,13 @@ getEarliestBlockHeight db = do -- Throws an exception if it doesn't. ensureBlockHeightExists :: Database -> BlockHeight -> IO () ensureBlockHeightExists db bh = do - r <- throwOnDbError $ qry db "SELECT blockheight FROM BlockHistory WHERE blockheight = ?1" [SInt (fromIntegral bh)] [RInt] + r <- throwOnDbError $ qry db "SELECT blockheight FROM BlockHistory2 WHERE blockheight = ?1" [SInt (fromIntegral bh)] [RInt] case r of [[SInt rBH]] -> do when (fromIntegral bh /= rBH) $ do error "ensureBlockHeightExists: malformed query" _ -> do - error $ "ensureBlockHeightExists: empty BlockHistory: height=" ++ show bh + error $ "ensureBlockHeightExists: empty BlockHistory2: height=" ++ show bh getLatestCommonBlockHeight :: (Logger logger) => logger @@ -251,7 +251,7 @@ getEndingTxId :: () -> IO Int64 getEndingTxId db bh = do r <- throwOnDbError $ qry db - "SELECT endingtxid FROM BlockHistory WHERE blockheight=?" + "SELECT endingtxid FROM BlockHistory2 WHERE blockheight=?" [SInt (int bh)] [RInt] case r of diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index 7941543c2e..bcdb953a28 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -344,7 +344,7 @@ doLookupSuccessful db curHeight hashes = throwOnDbError $ do qtext = Utf8 $ BS.intercalate " " [ "SELECT blockheight, payloadhash, hash, txhash" , "FROM TransactionIndex" - , "INNER JOIN BlockHistory USING (blockheight)" + , "INNER JOIN BlockHistory2 USING (blockheight)" , "WHERE txhash IN (" <> params <> ")" <> " AND blockheight < ?;" ] qvals @@ -382,7 +382,7 @@ getEndTxId cid sql pc getEndTxId' :: HasCallStack => SQLiteEnv -> Parent RankedBlockHash -> IO (Historical Pact.TxId) getEndTxId' sql (Parent rbh) = throwOnDbError $ do r <- qry sql - "SELECT endingtxid FROM BlockHistory WHERE blockheight = ? and hash = ?;" + "SELECT endingtxid FROM BlockHistory2 WHERE blockheight = ? and hash = ?;" [ SInt $ fromIntegral $ _rankedBlockHashHeight rbh , SBlob $ runPutS (encodeBlockHash $ _rankedBlockHashHash rbh) ] @@ -423,7 +423,7 @@ rewindDbToGenesis => SQLiteEnv -> IO () rewindDbToGenesis db = throwOnDbError $ do - exec_ db "DELETE FROM BlockHistory;" + exec_ db "DELETE FROM BlockHistory2;" exec_ db "DELETE FROM [SYS:KeySets];" exec_ db "DELETE FROM [SYS:Modules];" exec_ db "DELETE FROM [SYS:Namespaces];" @@ -471,7 +471,7 @@ rewindDbToBlock db bh endingTxId = throwOnDbError $ do deleteHistory :: ExceptT LocatedSQ3Error IO () deleteHistory = - exec' db "DELETE FROM BlockHistory WHERE blockheight > ?" + exec' db "DELETE FROM BlockHistory2 WHERE blockheight > ?" [SInt (fromIntegral bh)] vacuumTablesAtRewind :: HashSet BS.ByteString -> ExceptT LocatedSQ3Error IO () diff --git a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs index 026b866ef0..94b26f7c62 100644 --- a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs @@ -854,7 +854,7 @@ commitBlockStateToDatabase db blockHash payloadHash bh blockState = do ] where stmt = - "INSERT INTO BlockHistory ('blockheight','hash','payloadhash','endingtxid') VALUES (?,?,?,?);" + "INSERT INTO BlockHistory2 ('blockheight','hash','payloadhash','endingtxid') VALUES (?,?,?,?);" createUserTable :: Text -> IO () createUserTable (toUtf8 -> tablename) = do @@ -911,7 +911,7 @@ lookupBlockHash db hash = do [] -> return $ Nothing res -> error $ "Invalid result, " <> sshow res where - qtext = "SELECT blockheight FROM BlockHistory WHERE hash = ?;" + qtext = "SELECT blockheight FROM BlockHistory2 WHERE hash = ?;" headerOracleForBlock :: BlockHandlerEnv logger -> HeaderOracle headerOracleForBlock env = HeaderOracle diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 158e847fd9..f425a61167 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -19,31 +19,38 @@ module Chainweb.PayloadProvider.Pact , decodeNewPayload ) where -import Control.Concurrent.STM -import Control.Exception.Safe -import Data.LogMessage -import Data.Vector (Vector) -import System.LogLevel -import Control.Lens -import Network.HTTP.Client qualified as HTTP -import Data.Vector qualified as V +import Chainweb.BlockHeaderDB (withBlockHeaderDb) import Chainweb.ChainId +import Chainweb.Core.Brief +import Chainweb.Core.Brief import Chainweb.Counter import Chainweb.Logger -import Chainweb.Pact.Mempool.Mempool import Chainweb.MerkleUniverse import Chainweb.MinerReward qualified as MinerReward import Chainweb.Pact.Backend.Utils +import Chainweb.Pact.Mempool.Mempool import Chainweb.Pact.PactService qualified as PactService -import Chainweb.Pact.Transaction qualified as Pact -import Chainweb.Core.Brief import Chainweb.Pact.Payload import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Types import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.Pact.BlockHistoryMigration +import Chainweb.Storage.Table.RocksDB import Chainweb.Utils import Chainweb.Version -import Control.Monad.Trans.Resource (ResourceT) +import Control.Concurrent.STM +import Control.Exception.Safe +import Control.Lens +import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource (ResourceT, allocate) +import Data.LogMessage +import Data.Pool qualified as Pool +import Data.Vector (Vector) +import Data.Vector qualified as V +import Network.HTTP.Client qualified as HTTP +import System.LogLevel data PactPayloadProvider logger tbl = PactPayloadProvider { pactPayloadProviderLogger :: logger @@ -105,6 +112,8 @@ withPactPayloadProvider => Logger logger => HasVersion => ChainId + -> RocksDb + -- ^ Temporary requirement for the `migrateBlockHistoryTable` step. -> Maybe HTTP.Manager -> logger -> Maybe (Counter "txFailures") @@ -114,8 +123,19 @@ withPactPayloadProvider -> PactServiceConfig -> Maybe PayloadWithOutputs -> ResourceT IO (PactPayloadProvider logger tbl) -withPactPayloadProvider cid http logger txFailuresCounter mp pdb pactDbDir config maybeGenesisPayload = do +withPactPayloadProvider cid rdb http logger txFailuresCounter mp pdb pactDbDir config maybeGenesisPayload = do readWriteSqlenv <- withSqliteDb cid logger pactDbDir False + + -- perform the database migration of the `BlockHeader` Table. + bhdb <- withBlockHeaderDb rdb cid + + liftIO $ do + needsMigration <- tableNeedsMigration logger readWriteSqlenv + when needsMigration $ + -- We cleanup potential old state and start migrating the entire database + -- from scratch. + migrateBlockHistoryTable logger readWriteSqlenv bhdb True + readOnlySqlPool <- withReadSqlitePool cid pactDbDir PactPayloadProvider logger <$> PactService.withPactService cid http mpa logger txFailuresCounter pdb readOnlySqlPool readWriteSqlenv config diff --git a/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs b/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs new file mode 100644 index 0000000000..f44ed9bbf0 --- /dev/null +++ b/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs @@ -0,0 +1,144 @@ +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} + +module Chainweb.PayloadProvider.Pact.BlockHistoryMigration where + +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB (BlockHeaderDb) +import Chainweb.Logger +import Chainweb.Pact.Backend.Types (SQLiteEnv) +import Chainweb.TreeDB +import Chainweb.Version (HasVersion) +import Control.Lens +import Control.Monad.IO.Class +import System.Logger qualified as L +import System.LogLevel +import Chainweb.Pact.Backend.Utils +import Chainweb.Pact.Backend.PactState (qryStream) +import Streaming qualified as S +import Streaming.Prelude qualified as S +import Chainweb.Utils (sshow, whenM) +import Control.Exception.Safe +import Chainweb.Utils.Serialization (runPutS, runGetS) +import Data.IORef (newIORef, readIORef, modifyIORef') +import Control.Monad +import Data.Time.Clock (getCurrentTime, diffUTCTime) + +-- | Migrates the @BlockHistory@ table if it is outdated or missing required columns. +-- +-- Due to SQLite limitations, we cannot simply add the missing column and migrate +-- the data afterward. Specifically, constraints (such as the uniqueness of the +-- @blockhash@ column) must be satisfied at the time the column is added, and +-- SQLite does not allow adding columns with unique constraints via @ALTER TABLE@. +-- See for more details. +-- +-- The migration is performed only if 'tableNeedsMigration' indicates it's necessary. +-- It proceeds in a row-wise migration of existing data from the old @BlockHistory@ table and +-- the 'BlockHeaderDb' (backed by RocksDb), performed in chunks of 10,000 entries. +-- +-- Each chunk is committed in a transaction block. +-- +-- NOTE: THIS Module can be deleted after migration !!! +-- +migrateBlockHistoryTable + :: HasVersion + => Logger logger + => logger -- ^ logger + -> SQLiteEnv -- ^ sqlite database + -> BlockHeaderDb -- ^ block header database + -> Bool -- ^ cleanup table after migration + -> IO () +migrateBlockHistoryTable logger sdb bhdb cleanup + = L.withLoggerLabel ("component", "migrateBlockHistoryTable") logger $ \lf -> + whenM (tableNeedsMigration lf sdb) $ do + let logf serv msg = liftIO $ logFunctionText lf serv msg + + nBlockHistory2 <- nTableEntries "BlockHistory2" + nBlockHistory <- nTableEntries "BlockHistory" + + when (nBlockHistory2 <= nBlockHistory) $ do + logf Info "Partial migration detected, cleaning up and restarting." + throwOnDbError $ exec_ sdb "DELETE FROM BlockHistory2" + + let qstmt = "SELECT blockheight, endingtxid, hash from BlockHistory ORDER BY blockheight,endingtxid ASC" + rty = [RInt, RInt, RBlob] + + start <- getCurrentTime + + e <- qryStream sdb qstmt [] rty $ \rs -> do + remainingRowsRef <- newIORef nBlockHistory + rs + & S.chunksOf 10_000 + & S.mapsM_ (\chunk -> do + remainingRows <- readIORef remainingRowsRef + let perc = (1.0 - fromIntegral @_ @Double remainingRows / fromIntegral @_ @Double nBlockHistory) * 100.0 + logf Info $ "Table migration: Process remaining rows: " <> sshow remainingRows <> " ("<> sshow perc <> ")" + r <- tx $ flip S.mapM_ chunk $ \case + rr@[SInt bh, SInt _, SBlob h] -> do + let rowBlockHeight = fromIntegral bh + rowBlockHash <- runGetS decodeBlockHash h + blockHeader <- lookupRanked bhdb rowBlockHeight rowBlockHash >>= \case + Nothing -> do + error $ "BlockHeader Entry missing for " + <> "blockHeight=" + <> sshow rowBlockHeight + <> ", blockHash=" + <> sshow rowBlockHash + Just blockHeader -> return blockHeader + + let bph = view blockPayloadHash blockHeader + enc = runPutS $ encodeBlockPayloadHash bph + + throwOnDbError $ exec' sdb "INSERT INTO BlockHistory2 (blockheight, endingtxid, hash, payloadhash) VALUES (?, ?, ?, ?)" + $ rr ++ [SBlob enc] + _ -> error "unexpected row shape" + + modifyIORef' remainingRowsRef (\old -> max 0 (old - 10_000)) + pure r) + + case e of + Left e' -> error $ "Table migration failure: " <> sshow e' + Right () -> do + end <- getCurrentTime + logf Info $ "Elapsed Time: " <> sshow (diffUTCTime end start) + + when cleanup $ do + logf Info "Data migration completed, cleaning up" + throwOnDbError $ exec_ sdb "DROP TABLE BlockHistory" + + logf Info "Table migration successful" + + where + tx = bracket_ + (throwOnDbError $ exec_ sdb "BEGIN TRANSACTION") + (throwOnDbError $ exec_ sdb "COMMIT TRANSACTION") + nTableEntries tname = throwOnDbError $ qry_ sdb ("SELECT count(*) from " <> tname) [RInt] >>= \case + [[SInt n]] -> pure n + _ -> error "unexpected row shape" + + +-- | Checks whether the @BlockHistory@ table is (still) existing +-- and therefore requires a data migration step. +tableNeedsMigration + :: Logger logger + => logger + -> SQLiteEnv + -> IO Bool +tableNeedsMigration lf sqe = do + let logf serv msg = liftIO $ logFunctionText lf serv msg + + r <- throwOnDbError $ qry sqe "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'BlockHistory';" [] [RInt] + case r of + [[SInt _]] -> do + logf Info "BlockHistory table exists, need to migrate data." + pure True + _ -> do + logf Info "BlockHistory table does not exists. No table migration required." + pure False diff --git a/test/blockhistory-migration/BlockHistoryMigrationTests.hs b/test/blockhistory-migration/BlockHistoryMigrationTests.hs new file mode 100644 index 0000000000..831f4e2e02 --- /dev/null +++ b/test/blockhistory-migration/BlockHistoryMigrationTests.hs @@ -0,0 +1,128 @@ +{-# LANGUAGE RankNTypes, OverloadedStrings #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} + +module Main +( main +) where + +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB +import Chainweb.Graph +import Chainweb.Logger +import Chainweb.Pact.Backend.ChainwebPactDb +import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb +import Chainweb.Pact.Backend.PactState (qryStream) +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils +import Chainweb.PayloadProvider.Pact.BlockHistoryMigration (migrateBlockHistoryTable) +import Chainweb.Storage.Table.RocksDB +import Chainweb.Test.MultiNode qualified +import Chainweb.Test.Pact.Utils +import Chainweb.Test.TestVersions +import Chainweb.Test.Utils +import Chainweb.TreeDB (lookupRankedM) +import Chainweb.Utils.Serialization +import Chainweb.Version +import Chainweb.Version.EvmTestnet (evmTestnet) +import Chainweb.Version.Mainnet +import Control.Lens +import Control.Monad.Except +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource +import Data.ByteString qualified as BS +import Data.Function +import Database.SQLite3.Direct qualified as SQL +import Streaming qualified as S +import Streaming.Prelude qualified as S +import System.Directory +import System.Environment (lookupEnv) +import System.FilePath +import System.IO.Temp +import System.LogLevel +import Test.HUnit (assertFailure) +import Test.Tasty +import Test.Tasty.HUnit + +loglevel :: LogLevel +loglevel = Warn + +cid :: ChainId +cid = unsafeChainId 1 + +prepareSQLite :: SQLiteEnv -> IO () +prepareSQLite sql = throwOnDbError $ do + exec_ sql + "CREATE TABLE IF NOT EXISTS BlockHistory \ + \(blockheight UNSIGNED BIGINT NOT NULL, \ + \ endingtxid UNSIGNED BIGINT NOT NULL, \ + \ hash BLOB NOT NULL, \ + \ CONSTRAINT blockHeightConstraint UNIQUE (blockheight), \ + \ CONSTRAINT hashConstraint UNIQUE (hash));" + + liftIO $ ChainwebPactDb.initSchema sql + +requireEnv :: String -> IO String +requireEnv name = + lookupEnv name >>= maybe (fail $ name ++ " not set") pure + +main :: IO () +main = withSystemTempDirectory "sql-db" $ \dbDir -> do + logger <- getTestLogger + + -- /Volumes/Extreme Pro/db-chainweb-node-mainnet/0/sqlite/pact-v1-chain-1.sqlite + dbpath <- requireEnv "SQLITE_CHAIN1_FILEPATH" + -- /Volumes/Extreme Pro/db-chainweb-node-mainnet/0/rocksDb + rocksdbPath <- requireEnv "ROCKSDB_DIRPATH" + + copyFile dbpath (dbDir "pact-v1-chain-1.sqlite") + + sql <- startSqliteDb cid logger dbDir False + + prepareSQLite sql + n <- nTableEntries sql "BlockHistory" + n2 <- nTableEntries sql "BlockHistory2" + + assert (n > 0) "BlockHistory Table is not empty" + assert (n2 == 0) "BlockHistory2 Table is empty" + + withRocksDb rocksdbPath modernDefaultOptions $ \rocksdb -> do + withVersion Mainnet01 $ runResourceT $ do + bhdb <- withBlockHeaderDb rocksdb cid + liftIO $ do + migrateBlockHistoryTable logger sql bhdb False + + let qstmt = "SELECT A.blockheight, A.endingtxid, \ + \ B.hash AS b_hash, B.payloadhash AS b_payload_hash, \ + \ A.hash AS a_hash \ + \ FROM BlockHistory AS A INNER JOIN BlockHistory2 AS B \ + \ ON A.blockheight = B.blockheight AND A.endingtxid = B.endingtxid \ + \ ORDER BY A.blockheight, A.endingtxid" + rty = [RInt, RInt, RBlob, RBlob, RBlob] + + qryStream sql qstmt [] rty $ \rs -> do + rs & flip S.mapM_ $ \[SInt a_bh, SInt a_etxid, SBlob b_hash, SBlob b_payload, SBlob a_hash] -> do + + assert (a_hash == b_hash) $ + "Hash mismatch at block " ++ show a_bh ++ " / txid " ++ show a_etxid + + let rowBlockHeight = fromIntegral a_bh + rowBlockHash <- runGetS decodeBlockHash a_hash + blockHeader <- lookupRankedM bhdb rowBlockHeight rowBlockHash + let bph = view blockPayloadHash blockHeader + enc = runPutS $ encodeBlockPayloadHash bph + + assert (b_payload == enc) $ + "Payload Hash mismatch at block " ++ show a_bh ++ " / txid " ++ show a_etxid + + n2' <- nTableEntries sql "BlockHistory2" + assert (n == n2') "BlockHistory2 has the same number of rows as BlockHistory" + + where + assert p msg = + if p then pure () else Test.HUnit.assertFailure msg + + nTableEntries sql tname = throwOnDbError $ qry_ sql ("SELECT count(*) from " <> tname) [RInt] >>= \case + [[SInt n]] -> pure n + _ -> error "unexpected row shape" diff --git a/test/unit/Chainweb/Test/Pact/BlockHistoryMigrationTest.hs b/test/unit/Chainweb/Test/Pact/BlockHistoryMigrationTest.hs new file mode 100644 index 0000000000..ce69d8858f --- /dev/null +++ b/test/unit/Chainweb/Test/Pact/BlockHistoryMigrationTest.hs @@ -0,0 +1,210 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} + +module Chainweb.Test.Pact.BlockHistoryMigrationTest + (tests) +where + +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB +import Chainweb.BlockHeaderDB.Internal +import Chainweb.Logger +import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb +import Chainweb.Pact.Backend.PactState +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils +import Chainweb.PayloadProvider.Pact.BlockHistoryMigration (migrateBlockHistoryTable) +import Chainweb.Storage.Table.RocksDB +import Chainweb.Test.Pact.Utils +import Chainweb.Test.Utils +import Chainweb.TreeDB +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Chainweb.Version +import Chainweb.Version.Mainnet +import Control.Lens +import Control.Monad.IO.Class +import Control.Monad.Trans.Resource +import Data.Aeson (throwDecodeStrict') +import Data.ByteString qualified as BS +import Data.ByteString.Base64 qualified as B64 +import Data.Foldable (traverse_) +import Data.Int (Int64) +import Data.Maybe (fromJust) +import Database.SQLite3.Direct +import Streaming.Prelude qualified as S +import Test.Tasty +import Test.Tasty.HUnit hiding (assert) + +cid :: ChainId +cid = unsafeChainId 0 + +createLegacyBlockHistoryTable :: SQLiteEnv -> IO () +createLegacyBlockHistoryTable sql = throwOnDbError $ + exec_ sql + "CREATE TABLE IF NOT EXISTS BlockHistory \ + \(blockheight UNSIGNED BIGINT NOT NULL, \ + \ endingtxid UNSIGNED BIGINT NOT NULL, \ + \ hash BLOB NOT NULL, \ + \ CONSTRAINT blockHeightConstraint UNIQUE (blockheight), \ + \ CONSTRAINT hashConstraint UNIQUE (hash));" + + +initSchema :: SQLiteEnv -> IO () +initSchema sql = do + ChainwebPactDb.initSchema sql -- create the BlockHistory2 table + createLegacyBlockHistoryTable sql -- create the legacy BlockHistory table + +withSetup + :: TestName + -> (SQLiteEnv -> IO ()) + -> (HasVersion => GenericLogger -> SQLiteEnv -> BlockHeaderDb -> Bool -> IO ()) + -> TestTree +withSetup n setup action = withResourceT (withTempChainSqlite cid) $ \sqlIO -> do + testCase n $ do + logger <- getTestLogger + (sql, _sqlReadPool) <- sqlIO + + _ <- setup sql + + withTempRocksDb "chainweb-tests" $ \rdb -> do + withVersion Mainnet01 $ runResourceT $ do + bhdb <- withBlockHeaderDb rdb cid + liftIO $ action logger sql bhdb True + +tests :: HasCallStack => TestTree +tests = testGroup "BlockHistory Table Migration" [ + withSetup "test with empty BlockHistoryTable" + initSchema + migrateBlockHistoryTable + , withSetup "test successful migration cleanup" + initSchema + $ \lf sdb bhdb cleanup -> do + let qryIO = throwOnDbError $ qry sdb "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'BlockHistory'" [] [RInt] + [[SInt p]] <- qryIO + assertExpectation "Table should be present" (Expected 1) (Actual p) + migrateBlockHistoryTable lf sdb bhdb cleanup + post <- qryIO + assertExpectation "Table should not be present" (Expected []) (Actual post) + , withSetup "test migration" + initSchema + $ \lf sdb bhdb _cleanup -> do + traverse_ (unsafeInsertBlockHeaderDb bhdb) blockHeaders + traverse_ (unsafeInsertEntry sdb) sqliteData + + -- Disable original table cleanup for migration verification. + migrateBlockHistoryTable lf sdb bhdb False + + verifyMigration sdb bhdb + + -- Re-run verification + migrateBlockHistoryTable lf sdb bhdb False + + verifyMigration sdb bhdb + , withSetup "test migration with one missing row" + initSchema + $ \lf sdb bhdb _cleanup -> do + traverse_ (unsafeInsertBlockHeaderDb bhdb) blockHeaders + traverse_ (unsafeInsertEntry sdb) sqliteData + + -- Disable original table cleanup for migration verification. + migrateBlockHistoryTable lf sdb bhdb False + + verifyMigration sdb bhdb + + -- remove single row from BlockHistory2 table + let (rbh,_,_) = head sqliteData + throwOnDbError $ exec' sdb "DELETE FROM BlockHistory2 where blockheight=?" [SInt rbh] + + n <- nTableEntries sdb "BlockHistory2" + assert (n == 9) $ "BlockHistory2 should contain 9 entries, actual: " <> sshow n + + -- Re-run verification + migrateBlockHistoryTable lf sdb bhdb False + + verifyMigration sdb bhdb + ] + + +-- | Blockheader from mainnet01 of chain 1 +-- +-- Obtained by curl -H 'https://api.chainweb.com/chainweb/0.0/mainnet01/chain/1/header?limit=10' | jq +blockHeaders :: [BlockHeader] +blockHeaders = fromJust $ traverse throwDecodeStrict' [ + "\"AAAAAAAAAAAAJ41tFZYFAMhy366rCocPqnIPH492fe3J_cMShzHQwiyBVMT5RnKFAwADAAAAUsNRCUkMKQBRDVIv0JLDfDllYNg2QO0EXaWcRfMQ6g0EAAAAOu6kxMXpuwpm9uiIVhiEFHSbTdhanjHyiEh2G0EZV_wGAAAABQeojaqG63-R_1bILrd0LI6kDkS74hNBB0Bs3Y211bD__________________________________________5Apaf08O5Hgi1zH2gsox_oE6ABpi70UKojUZoVlAm8bAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAAGlRRahfD2xrvjGZFIW9TpOF4o4MVlGMQcYoAUIGZgxS\"", + "\"jw3vOXLHNX9X8Pf8F5YFAGlRRahfD2xrvjGZFIW9TpOF4o4MVlGMQcYoAUIGZgxSAwADAAAAtMyy7k6lrQOsGg2oXh20kOC3zudjwsaL9F5Ogx0XNBoEAAAAyeWKDE27xM45-LgdwpnVMM6E4pAoYryRrJJFgRiaKgQGAAAAOXehM2W5YuiV0iWlDGIPMdaRv721zMUbj3caJqZJNBX__________________________________________3CgdhATU9VNY_SB4Z9xQYmupqf0-0oZU5WKfk0I_SJJAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAABwrUPRfEh4gKXiQaLRVoqUK1aDCluIWbmZQzt9lUSDV\"", + "\"NSpftriT22gDWwz9F5YFABwrUPRfEh4gKXiQaLRVoqUK1aDCluIWbmZQzt9lUSDVAwADAAAAe0GGnclF0G2-_AMlCLGjKJVgzSBj1V9dTiqtsCC-IB4EAAAAuekHplt74MFfLHPsu7cJz9HbWNb_vBY-MR1y5RRDiYsGAAAA7OcrWyX2Gk7zrwmdI6HALWBvCihE6PwaS7TELscDGCH__________________________________________7sfFm8JbPK3qCTNeRvk4tzVrddPKuqFw-tI68WMfCvbAQAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAALpshckMhpgcor3YL9kXfHzYpq1iMsbjLA-2QKABndRt\"", + "\"h1RefsOyecrGlGX9F5YFALpshckMhpgcor3YL9kXfHzYpq1iMsbjLA-2QKABndRtAwADAAAAUYQRTGxyJpPIvFxR8mRa-wtGsBD9ymVwuPv9ta90DCEEAAAAhcayV2lxaq0ExtZZAKbHGptweEhk44dh3rk_6QGEff8GAAAAxu26E8QHPhi3GfZjOtisZyzMuR6E4-ekdfO-eWd06YT__________________________________________-nZJfKtvRk1CPK-MEaEfWAy0WrGA5g7b0xFyq1LykpHAQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAALO4obrG733x3raJyCjCQKWTp9IxoVDXC00thjQhnq5Z\"", + "\"ApXp4xPFfLL6MHT9F5YFALO4obrG733x3raJyCjCQKWTp9IxoVDXC00thjQhnq5ZAwADAAAAKuCNwclDosE_LMTCJPR-m8cLSTlQodWupf1c7PacZMEEAAAAlO6bNRGAwyiMdOAcDBushxBQheVa6Ra7TAd6OECzVVUGAAAAY00AQMeyYamYt6RnqEmQkAJVSt_OyNTZPA8ySAbs1c___________________________________________7Rres8j0FBEYwtUcS5UBNQumgtf0PG6j7y4VRMafRdmAQAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAAJr-V0iWi6yBDNGqEU0oJLNUrVOACJmITKsOvRz4H90R\"", + "\"0t473zHYtI-uNbf9F5YFAJr-V0iWi6yBDNGqEU0oJLNUrVOACJmITKsOvRz4H90RAwADAAAAumaN584K3DrEDxwpyuETFMsQ6ECseJVda54NIeQn8TkEAAAAv9sZzSMjqRD_uVMGoM-5kVnNqkRTYGqH6O-C7dIqXJsGAAAAcsRGLYQHN1NIYa1sXlj0eFaYwDLG7Vg3zqps05VEvGj__________________________________________5-87RGWTTcz0_voXhENxoJO6YTSH0GpwPkzCAxBCSvwAQAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAAERk0j0pZ1OduMRVwNDrexiPbxnTPTKIjwwNmsZ-gl6H\"", + "\"vhmHzX6Tuzdm--H9F5YFAERk0j0pZ1OduMRVwNDrexiPbxnTPTKIjwwNmsZ-gl6HAwADAAAA7mB5gbJFX_TnYhstzsiLBEhe0kT5nhh_JpPHfe7gMMEEAAAAxC3P3K1B1gDjwyLJPABbGt2qLuF93Y7QyiTNctqm94cGAAAA8JQk7mo7jCppLZnaHG6Doz6dOxrrKLXypB8CdzuAKHX__________________________________________65nMrh2AwrQTKIvl7YVg3ulCXikvFzPX4dVpPT9-s6cAQAAAAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAAMpV6GV2nJeFmSVBjo7QzWvF5W2LPS9uKFUCKJfU29Qr\"", + "\"XDW_QcCklR_DbUD-F5YFAMpV6GV2nJeFmSVBjo7QzWvF5W2LPS9uKFUCKJfU29QrAwADAAAAlk-iO8laTJ5Gy-U7pE-P3EDr548IUL1sWOfZv9DGHn0EAAAAhFuMKuqHuk4fA2BOBCTvTEFrOZEs691JLXuINP3BSu4GAAAA85F8Vdy_NKXopcOdG4pcEpAw8G2Xk4wWfp29d_T5xk7__________________________________________7g-M1b3FLNbXKogETFOeA6w2xPeMWVODCbJl8PHDEGDAQAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAAF8yBC9hIAHcsIUtgH5-RFdC2-QAmkmuB93qDIlXLWBB\"", + "\"415OTHBmkcV-oJn-F5YFAF8yBC9hIAHcsIUtgH5-RFdC2-QAmkmuB93qDIlXLWBBAwADAAAAPT37vtcbWdrZUi21FIrCyeUl4rU6g4W6syR3iM-mSyUEAAAAUcfuhJG94kMymAtpftR3Vbv3dNuCCpIOaf2PZULHemwGAAAAumhex2eCzZTd9mVjd8QBug13FN2Y_65X68Guel01bLb__________________________________________9lYTtyLATglm7r7WHyqgYL3tqtOdVjaYqlv2w6JpDmmAQAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAAE8hMzYm6jX5iR2HwqCIreHN4eHJNBwlAxdUp0A4wEJ1\"", + "\"uD82CpEfylAigrn-F5YFAE8hMzYm6jX5iR2HwqCIreHN4eHJNBwlAxdUp0A4wEJ1AwADAAAAB4vWZj8UZzuvPJkCq3-_32I8pCkdePIRlYz3UIudbowEAAAAFYSAHoaThaLUdIFRcFYSkNm3xk0s7nzDMfY-wB-hp5YGAAAAwT2FOsosNkjFLH4-l-WFevFkAnJiFU-8IpGqpYb0_tr__________________________________________18PgFBxcI7IqYgDsHrSRiwWiAcrodG0YQBKFQuhxxcZAQAAAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAAAAAAAAAFAAAAACeNbRWWBQAAAAAAAAAAAO8gTCqlCDrGsVZkvavO_Cr6GX5y6iCcp7XvtXCSsyae\"" + ] + +unsafeInsertEntry :: SQLiteEnv -> (Int64, BS.ByteString, Int64) -> IO () +unsafeInsertEntry sql (bh, h, txid) = case B64.decode h of + Right h' -> + throwOnDbError $ exec' sql "INSERT INTO BlockHistory (blockheight, endingtxid, hash) VALUES (?, ?, ?)" + [SInt bh, SInt txid, SBlob h' ] + Left _ -> error "error decoding hash" + + +sqliteData :: [(Int64, BS.ByteString, Int64)] +sqliteData = [ + (0, "aVFFqF8PbGu+MZkUhb1Ok4XijgxWUYxBxigBQgZmDFI=", 6), + (1, "HCtQ9F8SHiApeJBotFWipQrVoMKW4hZuZlDO32VRINU=", 7), + (2, "umyFyQyGmByivdgv2Rd8fNimrWIyxuMsD7ZAoAGd1G0=", 8), + (3, "s7ihusbvffHetonIKMJApZOn0jGhUNcLTS2GNCGerlk=", 9), + (4, "mv5XSJaLrIEM0aoRTSgks1StU4AImYhMqw69HPgf3RE=", 10), + (5, "RGTSPSlnU524xFXA0Ot7GI9vGdM9MoiPDA2axn6CXoc=", 11), + (6, "ylXoZXacl4WZJUGOjtDNa8XlbYs9L24oVQIol9Tb1Cs=", 12), + (7, "XzIEL2EgAdywhS2Afn5EV0Lb5ACaSa4H3eoMiVctYEE=", 13), + (8, "TyEzNibqNfmJHYfCoIit4c3h4ck0HCUDF1SnQDjAQnU=", 14), + (9, "7yBMKqUIOsaxVmS9q878KvoZfnLqIJynte+1cJKzJp4=", 15)] + + +verifyMigration :: HasVersion => SQLiteEnv -> BlockHeaderDb -> IO () +verifyMigration sql bhdb = do + let qstmt = "SELECT A.blockheight, A.endingtxid, \ + \ B.hash AS b_hash, B.payloadhash AS b_payload_hash, \ + \ A.hash AS a_hash \ + \ FROM BlockHistory AS A INNER JOIN BlockHistory2 AS B \ + \ ON A.blockheight = B.blockheight AND A.endingtxid = B.endingtxid \ + \ ORDER BY A.blockheight, A.endingtxid" + rty = [RInt, RInt, RBlob, RBlob, RBlob] + + n <- nTableEntries sql "BlockHistory" + + _ <- qryStream sql qstmt [] rty $ \rs -> do + rs & flip S.mapM_ $ \case + [SInt a_bh, SInt a_etxid, SBlob b_hash, SBlob b_payload, SBlob a_hash] -> do + assert (a_hash == b_hash) $ + "Hash mismatch at block " ++ show a_bh ++ " / txid " ++ show a_etxid + + let rowBlockHeight = fromIntegral a_bh + rowBlockHash <- runGetS decodeBlockHash a_hash + blockHeader <- lookupRankedM bhdb rowBlockHeight rowBlockHash + let bph = view blockPayloadHash blockHeader + enc = runPutS $ encodeBlockPayloadHash bph + + assert (b_payload == enc) $ + "Payload Hash mismatch at block " ++ show a_bh ++ " / txid " ++ show a_etxid + + n2' <- nTableEntries sql "BlockHistory2" + assert (n == n2') "BlockHistory2 has the same number of rows as BlockHistory" + _ -> error "unexpected result shape" + pure () + +assert :: Bool -> String -> IO () +assert = flip assertBool + +nTableEntries :: SQLiteEnv -> Utf8 -> IO Int64 +nTableEntries sql tname = throwOnDbError $ qry_ sql ("SELECT count(*) from " <> tname) [RInt] >>= \case + [[SInt n]] -> pure n + _ -> error "unexpected row shape" diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index a1180373f2..ed35f2e6b9 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -35,6 +35,7 @@ import Chainweb.Test.Mempool.Sync qualified (tests) import Chainweb.Test.MinerReward qualified (tests) import Chainweb.Test.Mining qualified (tests) import Chainweb.Test.Misc qualified (tests) +import Chainweb.Test.Pact.BlockHistoryMigrationTest qualified (tests) import Chainweb.Test.Pact.CheckpointerTest qualified import Chainweb.Test.Pact.HyperlanePluginTests qualified import Chainweb.Test.Pact.PactServiceTest qualified @@ -100,6 +101,7 @@ suite rdb = ] , Chainweb.Test.CutDB.tests rdb , Chainweb.Test.Pact.CheckpointerTest.tests + , Chainweb.Test.Pact.BlockHistoryMigrationTest.tests , Chainweb.Test.Pact.TransactionExecTest.tests rdb , Chainweb.Test.Pact.PactServiceTest.tests rdb -- TODO: PP From 95ce63197bcfae21fd5e5e4da813c593b9704178 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 19 Sep 2025 16:02:38 -0400 Subject: [PATCH 341/378] Move Minimal payload provider logging to Debug level --- src/Chainweb/PayloadProvider/Minimal.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index e08995d383..8b36da95cb 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -229,7 +229,7 @@ newMinimalPayloadProvider logger c rdb mgr conf store <- newPayloadStore mgr (logFunction pldStoreLogger) pdb payloadClient var <- newEmptyTMVarIO candidates <- emptyTable - logFunctionText logger Info "minimal payload provider started" + logFunctionText logger Debug "minimal payload provider started" return MinimalPayloadProvider { _minimalChainId = _chainId c , _minimalPayloadVar = var @@ -385,7 +385,7 @@ minimalPrefetchPayloads -> [Ranked ConsensusPayload] -> IO () minimalPrefetchPayloads p h ps = do - logg p Info "prefetch payloads" + logg p Debug "prefetch payloads" mapConcurrently_ (try @_ @TaskException . getPayloadForContext p h) ps -- | @@ -416,7 +416,7 @@ minimalSyncToBlock -> ForkInfo -> IO ConsensusState minimalSyncToBlock p h i = do - logg p Info "syncToBlock called" + logg p Debug "syncToBlock called" validatePayloads p h i -- FIXME: is this right place to prune the candidate store? @@ -425,10 +425,10 @@ minimalSyncToBlock p h i = do -- Produce new block case _forkInfoNewBlockCtx i of Nothing -> do - logg p Info $ "no new payload for sync state: " <> sshow latestState + logg p Debug $ "no new payload for sync state: " <> sshow latestState return () Just ctx -> do - logg p Info $ "create new payload for sync state: " <> sshow latestState + logg p Debug $ "create new payload for sync state: " <> sshow latestState atomically $ writeTMVar (_minimalPayloadVar p) $ makeNewPayload p latestState ctx From c998d1da02fed83e0ed47df1d209232f493023e6 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 22 Sep 2025 17:16:17 -0400 Subject: [PATCH 342/378] Change some names in getBranch Change-Id: Id00000007a01544a082202e1f6f756658ae72b31 --- src/Chainweb/TreeDB.hs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/Chainweb/TreeDB.hs b/src/Chainweb/TreeDB.hs index 87d5202693..380ad46447 100644 --- a/src/Chainweb/TreeDB.hs +++ b/src/Chainweb/TreeDB.hs @@ -541,22 +541,24 @@ getBranch db lowerBounds upperBounds = do let mar = getMax $ fromJuste $ foldMap' (foldMap' (Just . Max . rank)) [lowers, uppers] - go mar (active mar lowers mempty) (active mar uppers mempty) + go mar (activate mar lowers mempty) (activate mar uppers mempty) where getEntriesHs :: (a -> Key (DbEntry db)) -> HS.HashSet a -> IO (HS.HashSet (DbEntry db)) getEntriesHs f = streamToHashSet_ . lookupStream db . S.map f . S.each getParentsHs = streamToHashSet_ . lookupParentStreamM GenesisParentNone db . S.each + -- The newly active members of the set after advancing the highest ones + -- downwards. -- prop> all ((==) r . rank) $ snd (active r s c) -- - active + activate :: Natural -> HS.HashSet (DbEntry db) -> HS.HashSet (DbEntry db) -> (HS.HashSet (DbEntry db), HS.HashSet (DbEntry db)) - active r s c = - let (a, b) = hsPartition (\x -> rank x < r) s - in (a, c <> b) + activate r inactive active = + let (stillInactive, newlyActive) = hsPartition (\x -> rank x < r) inactive + in (stillInactive, active <> newlyActive) -- | The size of the input sets decreases monotonically. Space complexity is -- linear in the input and constant in the output size. @@ -566,15 +568,15 @@ getBranch db lowerBounds upperBounds = do -> (HS.HashSet (DbEntry db), HS.HashSet (DbEntry db)) -> (HS.HashSet (DbEntry db), HS.HashSet (DbEntry db)) -> S.Stream (Of (DbEntry db)) IO () - go r (ls0, ls1) (us0, us1) - | HS.null us0 && HS.null us1 = return () + go r (lowerInactive, lowerActive) (upperInactive, upperActive) + | HS.null upperInactive && HS.null upperActive = return () | otherwise = do - let us1' = us1 `HS.difference` ls1 - mapM_ S.yield us1' - us1p <- liftIO $ getParentsHs us1' - ls1p <- liftIO $ getParentsHs ls1 + let upperActiveFiltered = upperActive `HS.difference` lowerActive + mapM_ S.yield upperActiveFiltered + upperActive' <- liftIO $ getParentsHs upperActiveFiltered + lowerActive' <- liftIO $ getParentsHs lowerActive let r' = pred r - go r' (active r' ls0 ls1p) (active r' us0 us1p) + go r' (activate r' lowerInactive lowerActive') (activate r' upperInactive upperActive') hsPartition :: Hashable a From 326541e1157153433dbfeb1b0e427f52f502ff43 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 27 Sep 2025 15:26:56 -0700 Subject: [PATCH 343/378] some code cleanup --- chainweb.cabal | 7 - src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 51 +++---- src/Chainweb/Cut.hs | 60 +++------ src/Chainweb/CutDB.hs | 127 +++++++++--------- src/Chainweb/Miner/Coordinator.hs | 43 ++---- src/Chainweb/Pact/Backend/Compaction.hs | 66 ++++----- src/Chainweb/PayloadProvider.hs | 4 +- src/Chainweb/PayloadProvider/EVM.hs | 12 +- src/Chainweb/PayloadProvider/EVM/PayloadDB.hs | 2 - src/Chainweb/PayloadProvider/Pact.hs | 4 +- .../VerifierPlugin/Hyperlane/Message.hs | 6 +- .../BlockHistoryMigrationTests.hs | 44 +++--- test/unit/Chainweb/Test/CutDB.hs | 5 - test/unit/Test/Chainweb/SPV/Argument.hs | 21 +-- 14 files changed, 180 insertions(+), 272 deletions(-) diff --git a/chainweb.cabal b/chainweb.cabal index c80747c65c..b9243dfebb 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -700,7 +700,6 @@ test-suite chainweb-tests , data-dword >= 0.3 , data-ordlist >= 0.4.7 , direct-sqlite >= 2.3.27 - , ethereum , exceptions , ghc-compact >= 0.1 , hashable >= 1.3 @@ -767,15 +766,9 @@ test-suite blockhistory-migration-tests -- external , base >= 4.12 && < 5 , chainweb-storage >= 0.1 - , loglevel >= 0.1 - , tasty >= 1.0 - , tasty-hunit >= 0.9 , temporary >= 1.3 , filepath , directory - , mtl - , bytestring - , direct-sqlite , resourcet , streaming , HUnit diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index bcffc96abc..2845091fc5 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -3,10 +3,11 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} @@ -31,48 +32,39 @@ module Chainweb.BlockHeaderDB.RestAPI.Server ) where import Control.Applicative +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB +import Chainweb.BlockHeaderDB.RestAPI +import Chainweb.ChainId +import Chainweb.CutDB (CutDb, blockDiffStream) +import Chainweb.Difficulty (showTargetHex) +import Chainweb.PowHash (powHashBytes) +import Chainweb.RestAPI.Orphans () +import Chainweb.RestAPI.Utils +import Chainweb.TreeDB +import Chainweb.Utils.Paging +import Chainweb.Version import Control.Lens hiding (children, (.=)) import Control.Monad import Control.Monad.Except (MonadError(..)) import Control.Monad.IO.Class - import Data.Aeson import Data.Binary.Builder (fromByteString, fromLazyByteString) -import qualified Data.ByteString as BS -import qualified Data.ByteString.Base16 as B16 +import Data.ByteString qualified as BS +import Data.ByteString.Base16 qualified as B16 import Data.ByteString.Short (fromShort) import Data.Foldable import Data.Function import Data.Functor.Of import Data.IORef import Data.Proxy -import qualified Data.Text as Text import Data.Text.Encoding (decodeUtf8) -import Numeric.Natural(Natural) - import Network.Wai.EventSource (ServerEvent(..), eventSourceAppIO) - +import Numeric.Natural(Natural) import Prelude hiding (lookup) - import Servant.API import Servant.Server - -import qualified Streaming.Prelude as SP - --- internal modules - -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB -import Chainweb.BlockHeaderDB.RestAPI -import Chainweb.ChainId -import Chainweb.CutDB (CutDb, blockDiffStream) -import Chainweb.Difficulty (showTargetHex) -import Chainweb.PowHash (powHashBytes) -import Chainweb.RestAPI.Orphans () -import Chainweb.RestAPI.Utils -import Chainweb.TreeDB -import Chainweb.Utils.Paging -import Chainweb.Version +import Streaming.Prelude qualified as SP -- -------------------------------------------------------------------------- -- -- Handler Tools @@ -306,9 +298,8 @@ someBlockHeaderDbServers :: HasVersion => ChainMap BlockHeaderDb -> SomeServer -someBlockHeaderDbServers cdbs = ifoldMap - (\cid cdb -> someBlockHeaderDbServer (someBlockHeaderDbVal cid cdb)) - cdbs +someBlockHeaderDbServers = ifoldMap $ \cid cdb -> + someBlockHeaderDbServer (someBlockHeaderDbVal cid cdb) someP2pBlockHeaderDbServer :: HasVersion => SomeBlockHeaderDb -> SomeServer someP2pBlockHeaderDbServer (SomeBlockHeaderDb (db :: BlockHeaderDb_ v c)) @@ -323,7 +314,7 @@ someP2pBlockHeaderDbServers = ifoldMap someBlockStreamServer :: HasVersion => CutDb -> SomeServer someBlockStreamServer cdb = runIdentity $ do - SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal + SomeChainwebVersionT @v _ <- return someChainwebVersionVal Identity $ SomeServer (Proxy @(BlockStreamApi v)) $ blockStreamHandler cdb blockStreamHandler :: HasVersion => CutDb -> Tagged Handler Application diff --git a/src/Chainweb/Cut.hs b/src/Chainweb/Cut.hs index d1946f8734..0b8a5a2607 100644 --- a/src/Chainweb/Cut.hs +++ b/src/Chainweb/Cut.hs @@ -1,21 +1,13 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE BangPatterns #-} -{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ParallelListComp #-} {-# LANGUAGE RankNTypes #-} -{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} @@ -76,50 +68,32 @@ module Chainweb.Cut ) where +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeight +import Chainweb.BlockWeight +import Chainweb.ChainId +import Chainweb.Graph +import Chainweb.Parent +import Chainweb.Utils +import Chainweb.Version +import Chainweb.Version.Utils import Control.DeepSeq import Control.Exception hiding (catch) import Control.Lens hiding ((:>), (.=)) import Control.Monad hiding (join) import Control.Monad.Catch -import Control.Monad.State.Strict - -import Data.Bifoldable import Data.Foldable import Data.Function -import Data.Functor.Of -import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS -import qualified Data.List as List -import Data.Maybe (fromMaybe) -import Data.Monoid +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS +import Data.List qualified as List import Data.Ord import Data.Text (Text) -import qualified Data.Text as T - +import Data.Text qualified as T import GHC.Generics (Generic) -import GHC.Stack - -import Numeric.Natural - import Prelude hiding (lookup) -import qualified Streaming.Prelude as S - --- internal modules - -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeight -import Chainweb.BlockWeight -import Chainweb.ChainId -import Chainweb.Graph -import Chainweb.TreeDB -import Chainweb.Utils -import Chainweb.Version -import Chainweb.Version.Utils -import Chainweb.WebBlockHeaderDB -import Chainweb.Parent - -- -------------------------------------------------------------------------- -- -- Cut @@ -165,7 +139,7 @@ data Cut = Cut' deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) -_cutHeaders :: Cut -> (HM.HashMap ChainId BlockHeader) +_cutHeaders :: Cut -> HM.HashMap ChainId BlockHeader _cutHeaders = _cutHeaders' {-# INLINE cutHeaders #-} @@ -253,7 +227,7 @@ unsafeMkCut hdrs = Cut' , _cutWeight' = sum $ view blockWeight <$> hdrs , _cutMinHeight' = minimum $ view blockHeight <$> hdrs , _cutMaxHeight' = maximum $ view blockHeight <$> hdrs - , _cutIsTransition' = minheight < lastGraphChange (maxheight) + , _cutIsTransition' = minheight < lastGraphChange maxheight } where minheight = minimum $ view blockHeight <$> hdrs diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 8e402a5fa5..952ffbe9be 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -89,21 +89,48 @@ module Chainweb.CutDB , getQueueStats ) where +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB.Internal +import Chainweb.BlockHeight +import Chainweb.BlockWeight +import Chainweb.ChainId +import Chainweb.Core.Brief +import Chainweb.Cut +import Chainweb.Cut.Create +import Chainweb.Cut.CutHashes +import Chainweb.Graph +import Chainweb.Logger +import Chainweb.Parent +import Chainweb.PayloadProvider +import Chainweb.Ranked +import Chainweb.Storage.Table +import Chainweb.Storage.Table.HashMap +import Chainweb.Storage.Table.RocksDB +import Chainweb.Sync.WebBlockHeaderStore +import Chainweb.TreeDB +import Chainweb.Utils hiding (Codec, check) +import Chainweb.Utils.Serialization +import Chainweb.Version +import Chainweb.Version.Utils +import Chainweb.WebBlockHeaderDB import Control.Applicative import Control.Concurrent.Async import Control.Concurrent.STM.TVar import Control.DeepSeq +import Control.Exception (asyncExceptionFromException, asyncExceptionToException) import Control.Exception.Safe import Control.Lens hiding ((:>)) import Control.Monad import Control.Monad.IO.Class import Control.Monad.Morph import Control.Monad.STM +import Control.Monad.Trans.Maybe import Control.Monad.Trans.Resource hiding (throwM) - import Data.Aeson (ToJSON) -import Data.Foldable +import Data.ByteString.Lazy qualified as BS import Data.Either (partitionEithers) +import Data.Foldable import Data.Function import Data.Functor.Of import Data.HashMap.Strict qualified as HM @@ -112,58 +139,19 @@ import Data.LogMessage import Data.Maybe import Data.Monoid import Data.Ord +import Data.PQueue +import Data.TaskMap qualified as TM import Data.Text (Text) import Data.Text qualified as T import Data.These - import GHC.Generics hiding (to) - import Numeric.Natural - +import P2P.TaskQueue import Prelude hiding (lookup) - import Streaming.Prelude qualified as S - import System.LogLevel import System.Timeout - --- internal modules - -import Chainweb.BlockHash -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB.Internal -import Chainweb.BlockHeight -import Chainweb.BlockWeight -import Chainweb.ChainId -import Chainweb.Cut -import Chainweb.Cut.CutHashes -import Chainweb.Cut.Create -import Chainweb.Graph -import Chainweb.PayloadProvider -import Chainweb.Storage.Table -import Chainweb.Storage.Table.HashMap -import Chainweb.Storage.Table.RocksDB -import Chainweb.Sync.WebBlockHeaderStore -import Chainweb.TreeDB -import Chainweb.Utils hiding (Codec, check) -import Chainweb.Utils.Serialization -import Chainweb.Version -import Chainweb.Version.Utils -import Chainweb.WebBlockHeaderDB - -import Data.PQueue -import Data.TaskMap qualified as TM - -import P2P.TaskQueue - import Utils.Logging.Trace -import Chainweb.Ranked -import Control.Monad.Trans.Maybe -import Chainweb.Logger -import Chainweb.Core.Brief -import Chainweb.Parent -import Control.Exception (asyncExceptionFromException, asyncExceptionToException) -import qualified Data.ByteString.Lazy as BS -- -------------------------------------------------------------------------- -- -- Cut DB Configuration @@ -516,51 +504,66 @@ synchronizeProviders logger wbh providers c = do (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) mapConcurrently_ (runMaybeT . syncOne True) recoveryHeaders return recoveryCut - where + where syncOne :: Bool -> BlockHeader -> MaybeT IO () syncOne recovery hdr = case providers ^?! atChain (_chainId hdr) of ConfiguredPayloadProvider provider -> do - let loggr = logger & providerLogger provider . chainLogger hdr - liftIO $ logFunctionText loggr Info $ + let pLogger = providerLogger provider . chainLogger hdr $ logger + let pLog l = liftIO . logFunctionText pLogger l + pLog Info $ (if recovery then "recover" else "sync") <> " payload provider to " <> sshow (view blockHeight hdr) <> ":" <> sshow (view blockHash hdr) finfo <- liftIO $ forkInfoForHeader wbh hdr Nothing Nothing - liftIO $ logFunctionText loggr Debug $ "syncToBlock with fork info " <> sshow finfo + pLog Debug $ "syncToBlock with fork info " <> sshow finfo r <- liftIO (syncToBlock provider Nothing finfo) `catch` \(e :: SomeException) -> do - liftIO $ logFunctionText loggr Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e + pLog Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e empty + + -- Check result of syncToBlock + -- if r == _forkInfoTargetState finfo - then return () - else do - liftIO $ logFunctionText loggr Info + + -- We are done + -- + then return () + + -- We are not in sync and need to resolve (could be a fork a + -- payload provider that is not caught up yet) + -- + else do + pLog Info $ "resolving fork on startup, from " <> brief r <> " to " <> brief (_forkInfoTargetState finfo) + + -- Query the trace from the fork point to the target block + -- bhdb <- liftIO $ getWebBlockHeaderDb wbh cid let ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest r ppBlock <- liftIO (lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH)) `catch` \case e@(TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do - liftIO $ logFunctionText loggr Warn $ "PP block is missing: " <> brief ppRBH <> ", error: " <> sshow e + pLog Warn $ "PP block is missing: " <> brief ppRBH <> ", error: " <> sshow e MaybeT $ return Nothing _ -> empty (forkBlocksDescendingStream S.:> forkPoint) <- liftIO $ S.toList $ branchDiff_ bhdb ppBlock hdr let forkBlocksAscending = reverse $ snd $ partitionHereThere forkBlocksDescendingStream - let newTrace = - zipWith - (\prent child -> - ConsensusPayload (view blockPayloadHash child) Nothing <$ - blockHeaderToEvaluationCtx (Parent prent)) - (forkPoint : forkBlocksAscending) - forkBlocksAscending + let newTrace = zipWith + (\prent child -> + ConsensusPayload (view blockPayloadHash child) Nothing <$ + blockHeaderToEvaluationCtx (Parent prent)) + (forkPoint : forkBlocksAscending) + forkBlocksAscending + let newForkInfo = finfo { _forkInfoTrace = newTrace } + -- if this fails, there is no way for the payload provider -- to sync to the block without using the ordinary cut pipeline. -- so, we don't care. r' <- liftIO $ syncToBlock provider Nothing newForkInfo let syncSucceeded = _forkInfoTargetState finfo == r' - when (not syncSucceeded) $ do + unless syncSucceeded $ do liftIO $ logFunctionText logger (if recovery then Error else Warn) $ "unexpected " <> (if recovery then "recovery" else "initial sync") <> " result state" <> "; expected: " <> brief (_forkInfoTargetState finfo) @@ -575,7 +578,7 @@ synchronizeProviders logger wbh providers c = do DisabledPayloadProvider -> do liftIO $ logFunctionText logger Info $ "payload provider disabled, not synced, on chain: " <> toText (_chainId hdr) - where + where cid = _chainId hdr diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 98c3d6746d..3b9092e3b9 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -1,27 +1,20 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BlockArguments #-} -{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ViewPatterns #-} -- | -- Module: Chainweb.Miner.Coordinator @@ -245,8 +238,8 @@ instance Brief ParentState where -- -------------------------------------------------------------------------- -- -- Mining State -newMiningState :: HasVersion => Cut -> IO (ChainMap (TVar (Maybe ParentState))) -newMiningState c = do +newMiningState :: HasVersion => IO (ChainMap (TVar (Maybe ParentState))) +newMiningState = do states <- forM cids $ \cid -> do var <- newTVarIO Nothing return (cid, var) @@ -267,12 +260,11 @@ newMiningState c = do -- updateForCut :: HasVersion - => LogFunctionText - -> (ChainValue BlockHash -> IO BlockHeader) - -> (ChainMap (TVar (Maybe ParentState))) + => (ChainValue BlockHash -> IO BlockHeader) + -> ChainMap (TVar (Maybe ParentState)) -> Cut -> IO () -updateForCut lf hdb ms c = do +updateForCut hdb ms c = do forM_ (HM.keys (c ^. cutMap)) forChain where forChain cid = do @@ -390,8 +382,7 @@ newMiningCoordination -> CutDb -> IO (MiningCoordination logger) newMiningCoordination logger conf cdb = do - c <- _cut cdb - state <- newMiningState c + state <- newMiningState caches <- newPayloadCaches return $ MiningCoordination { _coordLogger = logger @@ -441,13 +432,6 @@ runCoordination mr = do cdb = _coordCutDb mr - providers = view cutDbPayloadProviders $ _coordCutDb mr - withProvider :: ChainId -> (forall p. PayloadProvider p => p -> a) -> a - withProvider cid k = case providers ^?! atChain cid of - ConfiguredPayloadProvider p -> k p - DisabledPayloadProvider -> - error $ "payload provider disabled on chain " <> sshow cid <> ", which is illegal for miners" - -- Update the work state -- updateWork = runForever lf "miningCoordination" $ do @@ -455,7 +439,7 @@ runCoordination mr = do eventStream cdb caches & S.chain (\e -> lf Debug $ "coordination event: " <> brief e) & S.mapM_ \case - CutEvent c -> updateForCut lf f state c + CutEvent c -> updateForCut f state c NewPayloadEvent _ -> return () -- There is a race with solved events. Does it matter? -- We could synchronize those by delivering those via an @@ -464,8 +448,8 @@ runCoordination mr = do initializeState = do lf Debug "initialize mining state" - curCut <- _cut $ cdb - updateForCut lf f state curCut + curCut <- _cut cdb + updateForCut f state curCut lf Debug "done initializing mining state for all chains" -- | Note that this stream is lossy. It always delivers the latest available @@ -600,7 +584,7 @@ randomWork logFun cdb caches parentStateVars = do go [] = do - logFun @T.Text Info $ "randomWork: no work is ready. Awaiting work" + logFun @T.Text Info "randomWork: no work is ready. Awaiting work" -- We shall check for the following conditions: -- @@ -641,8 +625,7 @@ randomWork logFun cdb caches parentStateVars = do -- FIXME: throw a proper exception and log what is going on go ((cid, var):t) = do - readyCheck <- atomically $ - Just <$> awaitWorkReady cid var <|> pure Nothing + readyCheck <- atomically $ optional $ awaitWorkReady cid var case readyCheck of Just (parents, payload) -> do ct <- BlockCreationTime <$> getCurrentTimeIntegral @@ -678,7 +661,7 @@ randomWork logFun cdb caches parentStateVars = do [ (cid, p) | (cid, Just p) <- itoList parentStates ] payloads <- forM (chainIntersect (,) chainsWithParents caches) - (\(parent, cache) -> Just <$> awaitLatestPayloadForParentStateSTM cache parent <|> return Nothing) + (\(parent, cache) -> optional $ awaitLatestPayloadForParentStateSTM cache parent) let chainsWithMissingPayloads = [ cid | (cid, Nothing) <- itoList payloads ] c <- _cutStm cdb @@ -780,4 +763,4 @@ logMinedBlock lf bh np = do } publish :: CutDb -> CutHashes -> IO () -publish cdb ch = addCutHashes cdb ch +publish = addCutHashes diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index f0e1782073..436fda4199 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -34,36 +34,7 @@ module Chainweb.Pact.Backend.Compaction ) where -import "base" Control.Exception hiding (Handler) -import "base" Control.Monad (forM, forM_, unless, void, when) -import "base" Control.Monad.IO.Class (MonadIO(liftIO)) -import Control.Monad.Trans.Resource (runResourceT) -import "base" Data.Function ((&)) -import "base" Data.Int (Int64) -import "base" Data.Maybe (fromMaybe) -import "base" Prelude hiding (log) -import "base" System.Exit (exitFailure) -import "base" System.IO (Handle) -import "base" System.IO qualified as IO -import "chainweb-storage" Chainweb.Storage.Table (Iterator(..), Entry(..), withTableIterator, unCasify, tableInsert) -import "chainweb-storage" Chainweb.Storage.Table.RocksDB (RocksDb, withRocksDb, withReadOnlyRocksDb, modernDefaultOptions) -import "direct-sqlite" Database.SQLite3 qualified as Lite -import "direct-sqlite" Database.SQLite3.Direct (Utf8(..), Database) -import "directory" System.Directory (createDirectoryIfMissing, doesDirectoryExist) -import "filepath" System.FilePath (()) -import "lens" Control.Lens (set, over, (^.), _3, view) -import "loglevel" System.LogLevel qualified as LL -import "monad-control" Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp) -import "optparse-applicative" Options.Applicative qualified as O -import "rocksdb-haskell-kadena" Database.RocksDB.Types (Options(..), Compression(..)) -import "streaming" Streaming qualified as S -import "streaming" Streaming.Prelude qualified as S -import "text" Data.Text (Text) -import "text" Data.Text qualified as Text -import "unliftio" UnliftIO.Async (pooledForConcurrently_) -import "yet-another-logger" System.Logger hiding (Logger) -import "yet-another-logger" System.Logger qualified as YAL -import "yet-another-logger" System.Logger.Backend.ColorOption (useColor) +import Control.Exception hiding (Handler) import Chainweb.BlockHash import Chainweb.BlockHeader (blockHeight, blockHash, blockPayloadHash) import Chainweb.BlockHeaderDB.Internal (BlockHeaderDb(..), RankedBlockHeader(..)) @@ -77,13 +48,42 @@ import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils import Chainweb.Pact.Payload.PayloadStore (addNewPayload, lookupPayloadWithHeight) import Chainweb.Pact.Payload.PayloadStore.RocksDB (newPayloadDb) +import Chainweb.Storage.Table (Iterator(..), Entry(..), withTableIterator, unCasify, tableInsert) +import Chainweb.Storage.Table.RocksDB (RocksDb, withRocksDb, withReadOnlyRocksDb, modernDefaultOptions) import Chainweb.Utils (sshow, fromText, toText, int) import Chainweb.Version (ChainId, HasVersion(..), withVersion, ChainwebVersion(..), chainIdToText) -import Chainweb.Version.Registry (findKnownVersion) import Chainweb.Version.Mainnet (mainnet) +import Chainweb.Version.Registry (findKnownVersion) import Chainweb.Version.Testnet04 (testnet04) import Chainweb.WebBlockHeaderDB (getWebBlockHeaderDb, initWebBlockHeaderDb) +import Control.Lens (set, over, (^.), _3, view) +import Control.Monad (forM, forM_, unless, void, when) +import Control.Monad.IO.Class (MonadIO(liftIO)) +import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp) +import Control.Monad.Trans.Resource (runResourceT) +import Data.Function ((&)) +import Data.Int (Int64) import Data.LogMessage (SomeLogMessage, logText) +import Data.Maybe (fromMaybe) +import Data.Text (Text) +import Data.Text qualified as Text +import Database.RocksDB.Types (Options(..), Compression(..)) +import Database.SQLite3 qualified as Lite +import Database.SQLite3.Direct (Utf8(..), Database) +import Options.Applicative qualified as O +import Prelude hiding (log) +import Streaming qualified as S +import Streaming.Prelude qualified as S +import System.Directory (createDirectoryIfMissing, doesDirectoryExist) +import System.Exit (exitFailure) +import System.FilePath (()) +import System.IO (Handle) +import System.IO qualified as IO +import System.LogLevel qualified as LL +import System.Logger hiding (Logger) +import System.Logger qualified as YAL +import System.Logger.Backend.ColorOption (useColor) +import UnliftIO.Async (pooledForConcurrently_) withDefaultLogger :: LL.LogLevel -> (YAL.Logger SomeLogMessage -> IO a) -> IO a withDefaultLogger ll f = withHandleBackend_ logText handleCfg $ \b -> @@ -377,7 +377,7 @@ compact cfg = withVersion cfg.chainwebVersion $ do pure targetBlockHeight -- Compact RocksDB. - when (not cfg.noRocksDb) $ do + unless cfg.noRocksDb $ do withRocksDbFileLogger cfg.logDir LL.Debug $ \logger -> do withReadOnlyRocksDb (rocksDir cfg.fromDir) modernDefaultOptions $ \srcRocksDb -> do withRocksDb (rocksDir cfg.toDir) (modernDefaultOptions { compression = NoCompression }) $ \targetRocksDb -> do @@ -388,7 +388,7 @@ compact cfg = withVersion cfg.chainwebVersion $ do { keepFullTransactionIndex = cfg.keepFullTransactionIndex , compactThese = _compactThese } - when (not cfg.noPactState) $ do + unless cfg.noPactState $ do forChains_ cfg.concurrent cids $ \cid -> do withPerChainFileLogger cfg.logDir cid LL.Debug $ \logger -> runResourceT $ do srcDb <- withChainDb cid logger (pactDir cfg.fromDir) diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 7a5452209a..c1232dc3dc 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -551,8 +551,8 @@ instance ToJSON ForkInfo where -- -------------------------------------------------------------------------- -- -- Hints for querying Payloads -data Hints = Hints - { _hintsOrigin :: !PeerInfo +newtype Hints = Hints + { _hintsOrigin :: PeerInfo } hintsProperties :: forall e kv . KeyValue e kv => Hints -> [kv] diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index e418986e59..92e92cd3a6 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1,6 +1,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} @@ -12,20 +13,17 @@ {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE TemplateHaskell #-} {-# OPTIONS_GHC -Wprepositive-qualified-module #-} -{-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE DerivingVia #-} -{-# LANGUAGE RankNTypes #-} -- | -- Module: Chainweb.PayloadProvider.EVM @@ -1296,7 +1294,7 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do -- If we don't know that base, there's nothing we can do Nothing -> do lf Warn $ "unknown base " <> brief rankedBaseHash - lf Info $ sshow forkInfo + lf Info $ encodeToText forkInfo Just _ -> case trace of -- Case of an empty trace: @@ -1307,7 +1305,7 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do -- either succeed or fail. -- [] -> do - lf Debug $ "empty trace" + lf Debug "empty trace" updateEvm p trgState pctx [] l -> do diff --git a/src/Chainweb/PayloadProvider/EVM/PayloadDB.hs b/src/Chainweb/PayloadProvider/EVM/PayloadDB.hs index 627d9b6a08..67dbcf2068 100644 --- a/src/Chainweb/PayloadProvider/EVM/PayloadDB.hs +++ b/src/Chainweb/PayloadProvider/EVM/PayloadDB.hs @@ -7,13 +7,11 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -- | diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index f425a61167..f15a1d3b8a 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -22,7 +22,6 @@ module Chainweb.PayloadProvider.Pact import Chainweb.BlockHeaderDB (withBlockHeaderDb) import Chainweb.ChainId import Chainweb.Core.Brief -import Chainweb.Core.Brief import Chainweb.Counter import Chainweb.Logger import Chainweb.MerkleUniverse @@ -44,9 +43,8 @@ import Control.Exception.Safe import Control.Lens import Control.Monad import Control.Monad.IO.Class -import Control.Monad.Trans.Resource (ResourceT, allocate) +import Control.Monad.Trans.Resource (ResourceT) import Data.LogMessage -import Data.Pool qualified as Pool import Data.Vector (Vector) import Data.Vector qualified as V import Network.HTTP.Client qualified as HTTP diff --git a/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs b/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs index 16c302588b..d642d1f56b 100644 --- a/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs +++ b/src/Chainweb/VerifierPlugin/Hyperlane/Message.hs @@ -1,9 +1,5 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE ViewPatterns #-} -- | -- Chainweb.VerifierPlugin.Hyperlane.Message @@ -19,5 +15,5 @@ import Chainweb.VerifierPlugin import Chainweb.VerifierPlugin.Hyperlane.Message.After225 qualified as After225 plugin :: VerifierPlugin -plugin = VerifierPlugin $ \(cid, bh) proof caps gasRef -> +plugin = VerifierPlugin $ \(_cid, _bh) proof caps gasRef -> After225.runPlugin proof caps gasRef diff --git a/test/blockhistory-migration/BlockHistoryMigrationTests.hs b/test/blockhistory-migration/BlockHistoryMigrationTests.hs index 831f4e2e02..8c14dd4c77 100644 --- a/test/blockhistory-migration/BlockHistoryMigrationTests.hs +++ b/test/blockhistory-migration/BlockHistoryMigrationTests.hs @@ -1,6 +1,7 @@ -{-# LANGUAGE RankNTypes, OverloadedStrings #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} module Main ( main @@ -9,8 +10,6 @@ module Main import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeaderDB -import Chainweb.Graph -import Chainweb.Logger import Chainweb.Pact.Backend.ChainwebPactDb import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb import Chainweb.Pact.Backend.PactState (qryStream) @@ -18,35 +17,20 @@ import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils import Chainweb.PayloadProvider.Pact.BlockHistoryMigration (migrateBlockHistoryTable) import Chainweb.Storage.Table.RocksDB -import Chainweb.Test.MultiNode qualified import Chainweb.Test.Pact.Utils -import Chainweb.Test.TestVersions -import Chainweb.Test.Utils import Chainweb.TreeDB (lookupRankedM) import Chainweb.Utils.Serialization import Chainweb.Version -import Chainweb.Version.EvmTestnet (evmTestnet) import Chainweb.Version.Mainnet import Control.Lens -import Control.Monad.Except import Control.Monad.IO.Class import Control.Monad.Trans.Resource -import Data.ByteString qualified as BS -import Data.Function -import Database.SQLite3.Direct qualified as SQL -import Streaming qualified as S import Streaming.Prelude qualified as S import System.Directory import System.Environment (lookupEnv) import System.FilePath import System.IO.Temp -import System.LogLevel import Test.HUnit (assertFailure) -import Test.Tasty -import Test.Tasty.HUnit - -loglevel :: LogLevel -loglevel = Warn cid :: ChainId cid = unsafeChainId 1 @@ -101,20 +85,22 @@ main = withSystemTempDirectory "sql-db" $ \dbDir -> do \ ORDER BY A.blockheight, A.endingtxid" rty = [RInt, RInt, RBlob, RBlob, RBlob] - qryStream sql qstmt [] rty $ \rs -> do - rs & flip S.mapM_ $ \[SInt a_bh, SInt a_etxid, SBlob b_hash, SBlob b_payload, SBlob a_hash] -> do + _ <- qryStream sql qstmt [] rty $ \rs -> do + rs & flip S.mapM_ $ \case + [SInt a_bh, SInt a_etxid, SBlob b_hash, SBlob b_payload, SBlob a_hash] -> do - assert (a_hash == b_hash) $ - "Hash mismatch at block " ++ show a_bh ++ " / txid " ++ show a_etxid + assert (a_hash == b_hash) $ + "Hash mismatch at block " ++ show a_bh ++ " / txid " ++ show a_etxid - let rowBlockHeight = fromIntegral a_bh - rowBlockHash <- runGetS decodeBlockHash a_hash - blockHeader <- lookupRankedM bhdb rowBlockHeight rowBlockHash - let bph = view blockPayloadHash blockHeader - enc = runPutS $ encodeBlockPayloadHash bph + let rowBlockHeight = fromIntegral a_bh + rowBlockHash <- runGetS decodeBlockHash a_hash + blockHeader <- lookupRankedM bhdb rowBlockHeight rowBlockHash + let bph = view blockPayloadHash blockHeader + enc = runPutS $ encodeBlockPayloadHash bph - assert (b_payload == enc) $ - "Payload Hash mismatch at block " ++ show a_bh ++ " / txid " ++ show a_etxid + assert (b_payload == enc) $ + "Payload Hash mismatch at block " ++ show a_bh ++ " / txid " ++ show a_etxid + r -> error $ "unexpected row shape: " ++ show r n2' <- nTableEntries sql "BlockHistory2" assert (n == n2') "BlockHistory2 has the same number of rows as BlockHistory" diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index e8a287f17f..8ef9e0b339 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -1,5 +1,4 @@ {-# LANGUAGE AllowAmbiguousTypes #-} -{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} @@ -7,8 +6,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -- | @@ -46,7 +43,6 @@ import Chainweb.Logger import Chainweb.Pact.Payload import Chainweb.Pact.Payload.PayloadStore import Chainweb.Pact.Types -import Chainweb.Parent import Chainweb.PayloadProvider import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB @@ -387,7 +383,6 @@ tryMineForChain cutDb c cid = do return $ Right (c', cid, newPayload) Left e -> return $ Left e where - parent = Parent $ c ^?! ixg cid -- parent to mine on wdb = view cutDbWebBlockHeaderDb cutDb -- | picks a random block header from a web chain. The result header is diff --git a/test/unit/Test/Chainweb/SPV/Argument.hs b/test/unit/Test/Chainweb/SPV/Argument.hs index c70472107e..691fec8f31 100644 --- a/test/unit/Test/Chainweb/SPV/Argument.hs +++ b/test/unit/Test/Chainweb/SPV/Argument.hs @@ -1,9 +1,10 @@ +{-# LANGUAGE GADTs #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE TypeApplications #-} -- | -- Module: Test.Chainweb.SPV.Argument @@ -23,31 +24,23 @@ import Chainweb.Crypto.MerkleLog import Chainweb.MerkleLogHash import Chainweb.MerkleUniverse import Chainweb.Pact.Payload -import Chainweb.PayloadProvider.EVM.Genesis qualified as EVM import Chainweb.PayloadProvider.EVM.Header qualified as EVM import Chainweb.PayloadProvider.EVM.Receipt qualified as EVM import Chainweb.PayloadProvider.Pact.Genesis qualified as Pact import Chainweb.SPV.Argument -import Chainweb.Utils import Chainweb.Version import Chainweb.Version.EvmDevelopment import Chainweb.Version.Mainnet -import Chainweb.Version.Registry import Control.Lens import Control.Monad import Data.Aeson -import Data.ByteString qualified as B import Data.Coerce -import Data.Hash.SHA2 -import Data.List qualified as L import Data.List.NonEmpty qualified as NE import Data.MerkleLog qualified as ML import Data.MerkleLog.V1 qualified as MLV1 import Data.Text qualified as T import Data.Typeable import Data.Vector qualified as V -import Ethereum.Block qualified as E -import Ethereum.Misc qualified as E import Numeric.Natural import Test.Tasty import Test.Tasty.HUnit @@ -255,7 +248,7 @@ test_evmHeaderArguments = withVersion evmDevnet $ test_evmHeaderArgument simpleTestData 0 test_evmHeaderArgument :: HasVersion => ReceiptTestData -> Natural -> IO () -test_evmHeaderArgument d idx = do +test_evmHeaderArgument d _idx = do let trieProof = EVM.rpcReceiptTrieProof rs (EVM.TransactionIndex 0) trieProofRoot <- validateTrieProof trieProof assertEqual "receipt proof gives correct root hash" @@ -319,8 +312,8 @@ test_evmHeaderArgument d idx = do -- -------------------------------------------------------------------------- -- -- EVM Test Data -testReceipt :: EVM.Receipt -testReceipt = EVM.fromRpcReceipt testRpcReceipt +-- testReceipt :: EVM.Receipt +-- testReceipt = EVM.fromRpcReceipt testRpcReceipt testRpcReceipt :: EVM.RpcReceipt testRpcReceipt = case eitherDecodeStrictText jsonReceiptStr of From a49fcd887fb9759d7f69920455c7680df1801719 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Fri, 5 Sep 2025 14:25:20 -0400 Subject: [PATCH 344/378] Incremental block header pruning Change-Id: Id0000000d46625a13262e9c582c5b4a9a3bc1c56 --- chainweb.cabal | 4 +- node/src/ChainwebNode.hs | 4 + src/Chainweb/BlockHeaderDB/PruneForks.hs | 428 ++++++++++++----- src/Chainweb/Chainweb.hs | 213 +++++---- src/Chainweb/Chainweb/Configuration.hs | 38 ++ src/Chainweb/Chainweb/CutResources.hs | 9 +- src/Chainweb/Utils.hs | 22 +- src/Chainweb/WebBlockHeaderDB.hs | 42 +- test/lib/Chainweb/Test/MultiNode.hs | 2 +- test/lib/Chainweb/Test/Utils.hs | 12 +- .../Chainweb/Test/BlockHeaderDB/PruneForks.hs | 451 ++++++++++++++++++ test/unit/Chainweb/Test/CutDB.hs | 2 +- test/unit/Chainweb/Test/TreeDB.hs | 2 +- test/unit/ChainwebTests.hs | 3 +- 14 files changed, 977 insertions(+), 255 deletions(-) create mode 100644 test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs diff --git a/chainweb.cabal b/chainweb.cabal index b9243dfebb..b44dab817b 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -113,6 +113,7 @@ common warning-flags library import: warning-flags, debugging-flags + ghc-options: -ddump-simpl -dsuppress-all -ddump-to-file -dno-suppress-type-signatures default-language: Haskell2010 hs-source-dirs: src other-modules: @@ -630,7 +631,7 @@ test-suite chainweb-tests Chainweb.Test.BlockHeader.Genesis Chainweb.Test.BlockHeader.Validation Chainweb.Test.BlockHeaderDB - -- Chainweb.Test.BlockHeaderDB.PruneForks + Chainweb.Test.BlockHeaderDB.PruneForks Chainweb.Test.Chainweb.Utils.Paging Chainweb.Test.CutDB Chainweb.Test.Difficulty @@ -726,6 +727,7 @@ test-suite chainweb-tests , property-matchers ^>= 0.7 , quickcheck-instances >= 0.3 , random >= 1.3 + , random-shuffle >= 0.0.4 , raw-strings-qq >=1.1 , resource-pool >= 0.4 , resourcet >= 1.3 diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 05f2df1409..4d282d3e13 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -79,6 +79,7 @@ import System.Mem -- internal modules import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB.PruneForks (PruneStats(..)) import Chainweb.Chainweb import Chainweb.Chainweb.Configuration import Chainweb.Chainweb.CutResources @@ -409,6 +410,8 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do $ mkTelemetryLogger @P2pNodeStats mgr teleLogConfig topLevelStatusBackend <- managed $ mkTelemetryLogger @ChainwebStatus mgr teleLogConfig + pruneStatsBackend <- managed + $ mkTelemetryLogger @PruneStats mgr teleLogConfig logger <- managed $ L.withLogger (_logConfigLogger logCfg) $ logHandles @@ -433,6 +436,7 @@ withNodeLogger logCfg chainwebCfg v f = runManaged $ do , logHandler dbStatsBackend , logHandler p2pNodeStatsBackend , logHandler topLevelStatusBackend + , logHandler pruneStatsBackend ] ]) baseBackend diff --git a/src/Chainweb/BlockHeaderDB/PruneForks.hs b/src/Chainweb/BlockHeaderDB/PruneForks.hs index 87b3200363..9d059d9250 100644 --- a/src/Chainweb/BlockHeaderDB/PruneForks.hs +++ b/src/Chainweb/BlockHeaderDB/PruneForks.hs @@ -1,5 +1,6 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} @@ -8,6 +9,12 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE DerivingVia #-} -- | -- Module: Chainweb.BlockHeaderDB.PruneForks @@ -19,19 +26,23 @@ -- Prune old forks from BlockHeader DB -- module Chainweb.BlockHeaderDB.PruneForks -( pruneForksLogg -, pruneForks +( pruneForks , pruneForks_ +, safeDepth +, pruneForksJob +, DoPrune(..) +, PruneStats(..) ) where -import Control.DeepSeq -import Control.Exception (evaluate) -import Control.Lens (view) +import Control.DeepSeq (NFData) +import Control.Exception (evaluate, throw) +import Control.Lens (ix, view, (^?!), iforM_, (%~)) import Control.Monad import Control.Monad.Catch import Data.Function -import qualified Data.List as L +import Data.HashSet (HashSet) +import Data.HashSet qualified as HashSet import Data.Maybe import Data.Semigroup import qualified Data.Text as T @@ -43,7 +54,7 @@ import Numeric.Natural import Prelude hiding (lookup) -import qualified Streaming.Prelude as S +import Streaming.Prelude qualified as S import System.LogLevel @@ -55,39 +66,71 @@ import Chainweb.BlockHeaderDB.Internal import Chainweb.BlockHeight import Chainweb.Logger import Chainweb.Parent +import Chainweb.Ranked import Chainweb.TreeDB import Chainweb.Utils hiding (Codec) import Chainweb.Version import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB +import Chainweb.Time qualified import Data.LogMessage +import Chainweb.WebBlockHeaderDB +import Chainweb.Cut +import Chainweb.CutDB +import Control.Monad.Cont +import Data.Void (Void) +import Data.Foldable (toList) +import Data.Functor ((<&>)) +import Data.Ord +import Control.Concurrent +import Chainweb.BlockHeader.Validation (validateIntrinsicM) +import Data.Aeson (FromJSON, ToJSON) -- -------------------------------------------------------------------------- -- -- Chain Database Pruning -pruneForksLogg +safeDepth :: BlockHeight +safeDepth = 10000 + +data PruneStats = PruneStats + { blocksPruned :: !Int + } + deriving stock Generic + deriving anyclass (NFData, ToJSON, FromJSON) + deriving LogMessage via (JsonLog PruneStats) + +-- | `ForcePrune` restarts from genesis. `PruneDryRun` does the prune without deleting +-- anything, but it also records the prune as having happened. +-- Prune +data DoPrune = Prune | ForcePrune | PruneDryRun + deriving (Show, Eq, Ord) + deriving (ToJSON, FromJSON) via (JsonTextRepresentation "DoPrune" DoPrune) + +instance HasTextRepresentation DoPrune where + toText Prune = "prune" + toText ForcePrune = "force-prune" + toText PruneDryRun = "prune-dry-run" + fromText "prune"= return Prune + fromText "force-prune" = return ForcePrune + fromText "prune-dry-run" = return PruneDryRun + fromText t = + throwM $ TextFormatException $ "HasTextRepresentation DoPrune: invalid DoPrune value " <> sshow t + +pruneForksJob :: HasVersion => Logger logger => logger - -> BlockHeaderDb + -> IO Cut + -> WebBlockHeaderDb + -> DoPrune -> Natural - -- ^ The depth at which pruning starts. Block at this depth are used as - -- pivots and actual deletion starts at (depth - 1). - -- - -- Note, that the max rank isn't necessarly included in the current best - -- cut. So one, should choose a depth for which one is confident that - -- all forks are resolved. - - -> (Bool -> BlockHeader -> IO ()) - -- ^ Deletion call back. This hook is called /after/ the entry is - -- deleted from the database. It's main purpose is to delete any - -- resources that were related to the deleted header and that are not - -- needed any more. The first parameter indicates whether the block - -- header got deleted from the chain database. - - -> IO Int -pruneForksLogg = pruneForks . logFunctionText + -> IO Void +pruneForksJob logger getCut wbhdb doPrune depth = do + runForever (logFunction logger) "prune_forks" $ do + initialCut <- getCut + void $ pruneForks logger initialCut wbhdb doPrune depth + threadDelay (1_000_000 * 60 * 60) -- | Prunes most block headers from forks that are older than the given number -- of blocks. @@ -98,49 +141,70 @@ pruneForksLogg = pruneForks . logFunctionText -- This function doesn't guarantee to delete all blocks on forks. A small number -- of fork blocks may not get deleted. -- --- The function takes a callback that are invoked on each block header in the --- database starting from the given depth. The callback takes a parameter that --- indicates whether the related block is pruned or not. --- pruneForks :: HasVersion - => LogFunctionText - -> BlockHeaderDb + => Logger logger + => logger + -> Cut + -> WebBlockHeaderDb + -> DoPrune -> Natural -- ^ The depth at which pruning starts. Block at this depth are used as - -- pivots and actual deletion starts at (depth - 1). + -- initial live set and actual deletion starts at (depth - 1). -- -- Note, that the max rank isn't necessarly included in the current best -- cut. So one, should choose a depth for which one is confident that -- all forks are resolved. - -> (Bool -> BlockHeader -> IO ()) - -- ^ Deletion call back. This hook is called /after/ the entry is - -- deleted from the database. It's main purpose is to delete any - -- resources that were related to the deleted header and that are not - -- needed any more. The first parameter indicates whether the block - -- header got deleted from the chain database. - -> IO Int -pruneForks logg cdb depth callback = do - hdr <- maxEntry cdb - if - | int (view blockHeight hdr) <= depth -> do - logg Info +pruneForks logger initialCut wbhdb doPrune depth = do + let highestSafePruneTarget = + _cutMinHeight initialCut - min (int depth) (_cutMinHeight initialCut) + + (resumptionPoint, startedFrom) <- tableLookup (_webCurrentPruneJob wbhdb) () >>= \case + Just j | doPrune /= ForcePrune -> return j + _ -> return (highestSafePruneTarget, highestSafePruneTarget) + + -- the lower bound is different from the job start point because some + -- heights have already been pruned + lowerBound <- tableLookup (_webHighestPruned wbhdb) () >>= \case + Just highestPruned | doPrune /= ForcePrune -> return highestPruned + _ -> return 0 + + tableInsert (_webHighestPruned wbhdb) () lowerBound + + numPruned <- if + | int (_cutMinHeight initialCut) <= depth -> do + logFunctionText logger Info $ "Skipping database pruning because the maximum block height " - <> sshow (view blockHeight hdr) <> " is not larger than then requested depth " + <> sshow (_cutMinHeight initialCut) <> " is not larger than then requested depth " <> sshow depth return 0 - | int (view blockHeight hdr) <= int genHeight + depth -> do - logg Info $ "Skipping database pruning because there are not yet" - <> " enough block headers on the chain" + | lowerBound > resumptionPoint -> do + -- as far as I know, this can only happen if we write highestPruned + -- below and we stop before we can delete the current job. if the + -- lower bound is already pruned, the prune job is done. + logFunctionText logger Info + $ "Skipping database pruning because the lower bound is already pruned" return 0 | otherwise -> do - let mar = MaxRank $ Max $ int (view blockHeight hdr) - depth - pruneForks_ logg cdb mar (MinRank $ Min $ int genHeight) callback - where - cid = _chainId cdb - genHeight = genesisHeight cid + numPruned <- + pruneForks_ logger wbhdb doPrune PruneJob + { resumptionPoint + , startedFrom + , lowerBound + } + logFunctionText logger Info + $ "Pruned " + <> sshow numPruned + <> " blocks, now pruned up to " + <> sshow startedFrom + + return numPruned + + tableInsert (_webHighestPruned wbhdb) () startedFrom + tableDelete (_webCurrentPruneJob wbhdb) () + return numPruned data PruneForksException = PruneForksDbInvariantViolation BlockHeight [BlockHeight] T.Text @@ -148,6 +212,24 @@ data PruneForksException instance Exception PruneForksException +data PruneState = PruneState + { liveSet :: HashSet BlockHash + , prevHeight :: BlockHeight + , prevRecordedHeight :: BlockHeight + , numPruned :: Int + , pendingDeletes :: ChainMap [RankedBlockHash] + , pendingDeleteCount :: Int + } deriving Show + +data PruneJob = PruneJob + { resumptionPoint :: BlockHeight + , startedFrom :: BlockHeight + -- this is the lower bound we actually work down to; we know inductively + -- that the interval [genesis, lowerbound] is already pruned. + , lowerBound :: BlockHeight + } + deriving Show + -- | Prune forks between the given min rank and max rank. -- -- Only block headers that are ancestors of a block header that has block height @@ -160,75 +242,173 @@ instance Exception PruneForksException -- pruneForks_ :: (HasCallStack, HasVersion) - => LogFunctionText - -> BlockHeaderDb - -> MaxRank - -> MinRank - -> (Bool -> BlockHeader -> IO ()) + => Logger logger + => logger + -> WebBlockHeaderDb + -> DoPrune + -> PruneJob -> IO Int -pruneForks_ logg _ (MaxRank (Max mar)) _ _ - | mar <= 1 = 0 <$ logg Warn ("Skipping database pruning for max bound of " <> sshow mar) -pruneForks_ logg cdb mar mir callback = do - logg Debug $ "Pruning block header database for chain " <> sshow (_chainId cdb) - <> " with upper bound " <> sshow (_getMaxRank mar) - <> " and lower bound " <> sshow (_getMinRank mir) +pruneForks_ logger wbhdb doPrune pruneJob = do + logFunctionText logger Info $ "Pruning block header database job " + <> sshow pruneJob -- parent hashes of all blocks at height max rank @mar@. + -- it's fine if we miss some chains here, because we never remove chains + -- from the chain graph. -- - !pivots <- entries cdb Nothing Nothing (Just $ MinRank $ Min $ _getMaxRank mar) (Just mar) - $ fmap (force . L.nub) . S.toList_ . S.map (unwrapParent . view blockParent) - -- the set of pivots is expected to be very small. In fact it is - -- almost always a singleton set. - - if null pivots - then do - logg Warn - $ "Skipping database pruning because of an empty set of block headers at upper pruning bound " <> sshow mar + !initialLiveSet <- webEntries wbhdb (Just $ MinRank $ Min $ _getMaxRank mar) (Just mar) + $ S.foldMap_ + $ \blk -> + HashSet.singleton (view (blockParent . _Parent) blk) + <> foldMap HashSet.singleton (getAdjs blk) + -- the initial live set is expected to be very small. In fact it is + -- almost always a singleton set on each chain. + logFunctionText logger Debug $ + "Initial live set at " <> sshow mar <> ": " <> sshow initialLiveSet + + let initialPruneState = PruneState + { liveSet = initialLiveSet + , prevHeight = resumptionPoint pruneJob + , prevRecordedHeight = resumptionPoint pruneJob + , numPruned = 0 + , pendingDeletes = noDeletes + , pendingDeleteCount = 0 + } + + now <- Chainweb.Time.getCurrentTimeIntegral + + if null initialLiveSet + then do + logFunctionText logger Warn + $ "Skipping database pruning because of an empty set of headers at upper pruning bound " + <> sshow mar return 0 - else - withReverseHeaderStream cdb (mar - 1) mir - $ S.foldM_ go (return (pivots, int (_getMaxRank mar), 0)) (\(_,_,!n) -> evaluate n) - . progress 200000 reportProgress + else do + withWebReverseHeaderStream wbhdb (max 1 mar - 1) + $ pruneBlocks now initialPruneState where - reportProgress i a = logg Info - $ "inspected " <> sshow i - <> " block headers. Current height " - <> sshow (view blockHeight a) - - go :: ([BlockHash], BlockHeight, Int) -> BlockHeader -> IO ([BlockHash], BlockHeight, Int) - go ([], _, _) cur = throwM $ InternalInvariantViolation - $ "PruneForks.pruneForks_: no pivots left at block " <> encodeToText (ObjectEncoded cur) - go (!pivots, !prevHeight, !n) !cur - - -- This checks some structural consistency. It's not a comprehensive - -- check. Comprehensive checks on various levels are availabe through - -- callbacks that are offered in the module "Chainweb.Chainweb.PruneChainDatabase" - | prevHeight /= curHeight && prevHeight /= curHeight + 1 = - throwM $ InternalInvariantViolation - $ "PruneForks.pruneForks_: detected a corrupted database. Some block headers are missing" - <> ". Current pivots: " <> encodeToText pivots - <> ". Current header: " <> encodeToText (ObjectEncoded cur) - <> ". Previous height: " <> sshow prevHeight - | view blockHash cur `elem` pivots = do - callback False cur - let !pivots' = force $ L.nub $ unwrapParent (view blockParent cur) : L.delete (view blockHash cur) pivots - return (pivots', curHeight, n) - | otherwise = do - deleteHdr cur - callback True cur - return (pivots, curHeight, n+1) - where - curHeight = view blockHeight cur - - deleteHdr k = do - -- TODO: make this atomic (create boilerplate to combine queries for - -- different tables) - casDelete (_chainDbCas cdb) (RankedBlockHeader k) - tableDelete (_chainDbRankTable cdb) (view blockHash k) - logg Debug - $ "pruned block header " <> encodeToText (view blockHash k) - <> " at height " <> sshow (view blockHeight k) + !noDeletes = onAllChains [] + mar = MaxRank $ int $ resumptionPoint pruneJob + !action = case doPrune of + PruneDryRun -> "Would have pruned " + _ -> "Pruned " + getAdjs bh = + view _Parent <$> _getBlockHashRecord (view blockAdjacentHashes bh) + + executePendingDeletes PruneState{..} = do + iforM_ pendingDeletes $ \cid pendingDeletesForCid -> do + let cdb = _webBlockHeaderDb wbhdb ^?! atChain cid + case doPrune of + PruneDryRun -> return () + _ -> do + tableDeleteBatch (_chainDbCas cdb) pendingDeletesForCid + tableDeleteBatch (_chainDbRankTable cdb) (_ranked <$> pendingDeletesForCid) + logFunctionText logger Info $ + action <> sshow pendingDeleteCount <> " block headers at height " <> sshow prevHeight + logFunctionText logger Debug $ + action <> sshow pendingDeletes <> ", at height " <> sshow prevHeight + logFunction logger Info $ + PruneStats pendingDeleteCount + + deleteBatchSize = 1000 + checkpointSize = 1000 + + pruneBlocks now initialState = + go initialState + where + go state strm = + if pendingDeleteCount state >= deleteBatchSize + || prevRecordedHeight state - prevHeight state > checkpointSize + then do + -- first execute pending deletes, then record checkpoint. that + -- way we never miss deletes. + executePendingDeletes state + -- make a checkpoint of our progress. we want to resume a level + -- higher, because we really don't prune the first height, we + -- use it for the initial live set. + tableInsert (_webCurrentPruneJob wbhdb) () + (succ $ prevHeight state, startedFrom pruneJob) + go + state + { prevRecordedHeight = prevHeight state + , pendingDeletes = noDeletes + , pendingDeleteCount = 0 + } + strm + else do + S.uncons strm >>= \case + Nothing -> do + executePendingDeletes state + return $! numPruned state + Just (block, strm') -> do + pruneBlock state block strm' + + pruneBlock :: PruneState -> BlockHeader -> S.Stream (S.Of BlockHeader) IO () -> IO Int + pruneBlock PruneState {liveSet, prevHeight} _ _ | HashSet.null liveSet = + throw $ InternalInvariantViolation + $ "PruneForks.pruneForks_: no live blocks left at height " <> sshow prevHeight + pruneBlock PruneState{..} cur strm + -- This checks some structural consistency. It's not a comprehensive + -- check. Comprehensive checks on various levels are available through + -- callbacks that are offered in the module "Chainweb.Chainweb.PruneChainDatabase" + | prevHeight /= curHeight && prevHeight /= curHeight + 1 = + throw $ InternalInvariantViolation + $ "PruneForks.pruneForks_: detected a corrupted database. " + <> "Some block headers are missing." + <> "Current live set: " <> encodeToText liveSet + <> ". Current header: " <> encodeToText (ObjectEncoded cur) + <> ". Previous height: " <> sshow prevHeight + -- if we are at or beyond the lower bound, + -- and we have no more pending fork tips to prune, + -- we're done early. + | curHeight <= lowerBound pruneJob + , curHeight < prevHeight + = do + when (pendingDeleteCount > 0) $ + executePendingDeletes PruneState{..} + return numPruned + -- the current block is live. + -- its parents take its place as live blocks. + | curHash `elem` liveSet = do + let !liveSet' = + flip (foldl' (flip HashSet.insert)) curAdjs $ + HashSet.insert curParent $ + HashSet.delete curHash liveSet + + validateIntrinsicM now cur + + go + PruneState + { liveSet = liveSet' + , prevHeight = curHeight + , prevRecordedHeight + , numPruned + , pendingDeletes + , pendingDeleteCount + } strm + -- the current block is not live, delete it. + | otherwise = do + let !newDelete = view rankedBlockHash cur + let !(!pendingDeletes', !pendingDeleteCount') = + (pendingDeletes & ix cid %~ (newDelete :) + , pendingDeleteCount + 1) + let !numPruned' = numPruned + 1 + go + PruneState + { liveSet + , prevHeight = curHeight + , prevRecordedHeight + , numPruned = numPruned' + , pendingDeletes = pendingDeletes' + , pendingDeleteCount = pendingDeleteCount' + } strm + where + curParent = view (blockParent . _Parent) cur + curAdjs = getAdjs cur + !cid = view chainId cur + !curHeight = view blockHeight cur + !curHash = view blockHash cur -- -------------------------------------------------------------------------- -- -- Utils @@ -238,16 +418,24 @@ pruneForks_ logg cdb mar mir callback = do withReverseHeaderStream :: BlockHeaderDb -> MaxRank - -> MinRank -> (S.Stream (S.Of BlockHeader) IO () -> IO a) -> IO a -withReverseHeaderStream db mar mir inner = withTableIterator headerTbl $ \it -> do - iterSeek it $ RankedBlockHash (BlockHeight $ int $ _getMaxRank mar + 1) nullBlockHash +withReverseHeaderStream db mar inner = withTableIterator headerTbl $ \it -> do + + iterSeek it $ + RankedBlockHash (BlockHeight $ int $ _getMaxRank mar + 1) nullBlockHash iterPrev it + inner $ iterToReverseValueStream it & S.map _getRankedBlockHeader - & S.takeWhile (\a -> int (view blockHeight a) >= mir) where headerTbl = _chainDbCas db -{-# INLINE withReverseHeaderStream #-} +withWebReverseHeaderStream + :: WebBlockHeaderDb + -> MaxRank + -> (S.Stream (S.Of BlockHeader) IO () -> IO a) + -> IO a +withWebReverseHeaderStream wbhdb mar inner = do + runContT (forM (_webBlockHeaderDb wbhdb) (\db -> ContT $ withReverseHeaderStream db mar)) + $ \streamPerChain -> inner $ mergeN (\bh -> (Down (view blockHeight bh), view chainId bh)) $ toList streamPerChain diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 7f74154f05..6a4388fa3f 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -132,6 +132,7 @@ import System.LogLevel import Chainweb.Backup import Chainweb.BlockHeaderDB (BlockHeaderDb) +import Chainweb.BlockHeaderDB.PruneForks qualified as PruneForks import Chainweb.ChainId import Chainweb.Chainweb.ChainResources import Chainweb.Chainweb.Configuration @@ -361,7 +362,7 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba :: ChainMap (ChainResources logger) -> IO () global cs = runResourceT $ do - let !webchain = mkWebBlockHeaderDb (fmap _chainResBlockHeaderDb cs) + let !webchain = mkWebBlockHeaderDb rocksDb (fmap _chainResBlockHeaderDb cs) -- !pact = mkWebPactExecutionService (HM.map _chainResPact cs) !providers = payloadProvidersForAllChains cs !cutLogger = setComponent "cut" logger @@ -369,112 +370,118 @@ withChainwebInternal conf logger peerRes serviceSock rocksDb defaultPactDbDir ba liftIO $ logg Debug "start initializing cut resources" liftIO $ logFunctionJson logger Info InitializingCutResources - initialCutOrCutResources <- withCutResources cutLogger cutDbParams p2pConfig myInfo peerDb rocksDb webchain providers mgr - liftIO $ case initialCutOrCutResources of - Left initialCut -> do + withCutResources cutLogger cutDbParams p2pConfig myInfo peerDb rocksDb webchain providers mgr >>= \case + Left initialCut -> liftIO $ do logg Info "no cut resources, chainweb will not continue" inner (RewoundToCut initialCut) Right cutResources -> do - logg Debug "finished initializing cut resources" - - let !mLogger = setComponent "miner" logger - !mConf = _configMining conf - !mCutDb = _cutResCutDb cutResources - !throt = _configThrottling conf - - -- initialize throttler - throttler <- mkGenericThrottler $ _throttlingRate throt - putPeerThrottler <- mkPutPeerThrottler $ _throttlingPeerRate throt - mempoolThrottler <- mkMempoolThrottler $ _throttlingMempoolRate throt - logg Debug "initialized throttlers" - - -- synchronize pact dbs with latest cut before we start the server - -- and clients and begin mining. - -- - -- This is a consistency check that validates the blocks in the - -- current cut. If it fails an exception is raised. Also, if it - -- takes long (for example, when doing a reset to a prior block - -- height) we want this to happen before we go online. - -- - let - -- pactSyncChains = - -- case _configSyncPactChains conf of - -- Just syncChains - -- | _configReadOnlyReplay conf - -- -> HM.filterWithKey (\k _ -> elem k syncChains) cs - -- _ -> cs - - if _configReadOnlyReplay conf - then do - -- FIXME implement replay in payload provider - error "Chainweb.Chainweb.withChainwebInternal: pact replay is not supported" - -- logFunctionJson logger Info PactReplayInProgress - -- -- note that we don't use the "initial cut" from cutdb because its height depends on initialBlockHeightLimit. - -- highestCut <- - -- unsafeMkCut v <$> readHighestCutHeaders v (logFunctionText logger) webchain (cutHashesTable rocksDb) - -- lowerBoundCut <- - -- tryLimitCut webchain (fromMaybe 0 $ _cutInitialBlockHeightLimit $ _configCuts conf) highestCut - -- upperBoundCut <- forM (_cutFastForwardBlockHeightLimit $ _configCuts conf) $ \upperBound -> - -- tryLimitCut webchain upperBound highestCut - -- let - -- replayOneChain :: (ChainResources logger, (BlockHeader, Maybe BlockHeader)) -> IO () - -- replayOneChain (cr, (l, u)) = do - -- let chainPact = _chainResPact cr - -- let logCr = logFunctionText - -- $ addLabel ("component", "pact") - -- $ addLabel ("sub-component", "init") - -- $ _chainResLogger cr - -- void $ _pactReadOnlyReplay chainPact l u - -- logCr Info "pact db synchronized" - -- let bounds = - -- HM.intersectionWith (,) - -- pactSyncChains - -- (HM.mapWithKey - -- (\cid bh -> - -- (bh, (HM.! cid) . _cutMap <$> upperBoundCut)) - -- (_cutMap lowerBoundCut) - -- ) - -- mapConcurrently_ replayOneChain bounds - -- logg Info "finished fast forward replay" - -- logFunctionJson logger Info PactReplaySuccessful - -- inner $ Replayed lowerBoundCut upperBoundCut - else do - logg Debug "start initializing miner resources" - logFunctionJson logger Info InitializingMinerResources - - withMiningCoordination mLogger mConf mCutDb $ \mc -> - - -- Miner resources are used by the test-miner when in-node - -- mining is configured or by the mempool noop-miner (which - -- keeps the mempool updated) in production setups. - -- - withMinerResources mLogger (_miningInNode mConf) cs mCutDb mc $ \m -> do - logFunctionJson logger Info ChainwebStarted - logg Debug "finished initializing miner resources" - let !haddr = _peerConfigAddr $ _p2pConfigPeer $ _configP2p conf - inner $ StartedChainweb Chainweb - { _chainwebHostAddress = haddr - , _chainwebChains = cs - , _chainwebCutResources = cutResources - , _chainwebMiner = m - , _chainwebCoordinator = mc - , _chainwebLogger = logger - , _chainwebPeer = peerRes - , _chainwebManager = mgr - -- , _chainwebPactData = pactData - , _chainwebThrottler = throttler - , _chainwebPutPeerThrottler = putPeerThrottler - , _chainwebMempoolThrottler = mempoolThrottler - , _chainwebConfig = conf - , _chainwebServiceSocket = serviceSock - , _chainwebBackup = BackupEnv - { _backupRocksDb = rocksDb - , _backupDir = backupDir - , _backupPactDbDir = defaultPactDbDir - , _backupChainIds = cids - , _backupLogger = backupLogger + _ <- withAsyncR $ PruneForks.pruneForksJob + logger + (cutResources ^. cutResCutDb . cut) + (cutResources ^. cutResCutDb . cutDbWebBlockHeaderDb) + (_configPruneCommand $ _configPrune conf) + (int PruneForks.safeDepth) + liftIO $ do + logg Debug "finished initializing cut resources" + + let !mLogger = setComponent "miner" logger + !mConf = _configMining conf + !mCutDb = _cutResCutDb cutResources + !throt = _configThrottling conf + + -- initialize throttler + throttler <- mkGenericThrottler $ _throttlingRate throt + putPeerThrottler <- mkPutPeerThrottler $ _throttlingPeerRate throt + mempoolThrottler <- mkMempoolThrottler $ _throttlingMempoolRate throt + logg Debug "initialized throttlers" + + -- synchronize pact dbs with latest cut before we start the server + -- and clients and begin mining. + -- + -- This is a consistency check that validates the blocks in the + -- current cut. If it fails an exception is raised. Also, if it + -- takes long (for example, when doing a reset to a prior block + -- height) we want this to happen before we go online. + -- + let + -- pactSyncChains = + -- case _configSyncPactChains conf of + -- Just syncChains + -- | _configReadOnlyReplay conf + -- -> HM.filterWithKey (\k _ -> elem k syncChains) cs + -- _ -> cs + + if _configReadOnlyReplay conf + then do + -- FIXME implement replay in payload provider + error "Chainweb.Chainweb.withChainwebInternal: pact replay is not supported" + -- logFunctionJson logger Info PactReplayInProgress + -- -- note that we don't use the "initial cut" from cutdb because its height depends on initialBlockHeightLimit. + -- highestCut <- + -- unsafeMkCut v <$> readHighestCutHeaders v (logFunctionText logger) webchain (cutHashesTable rocksDb) + -- lowerBoundCut <- + -- tryLimitCut webchain (fromMaybe 0 $ _cutInitialBlockHeightLimit $ _configCuts conf) highestCut + -- upperBoundCut <- forM (_cutFastForwardBlockHeightLimit $ _configCuts conf) $ \upperBound -> + -- tryLimitCut webchain upperBound highestCut + -- let + -- replayOneChain :: (ChainResources logger, (BlockHeader, Maybe BlockHeader)) -> IO () + -- replayOneChain (cr, (l, u)) = do + -- let chainPact = _chainResPact cr + -- let logCr = logFunctionText + -- $ addLabel ("component", "pact") + -- $ addLabel ("sub-component", "init") + -- $ _chainResLogger cr + -- void $ _pactReadOnlyReplay chainPact l u + -- logCr Info "pact db synchronized" + -- let bounds = + -- HM.intersectionWith (,) + -- pactSyncChains + -- (HM.mapWithKey + -- (\cid bh -> + -- (bh, (HM.! cid) . _cutMap <$> upperBoundCut)) + -- (_cutMap lowerBoundCut) + -- ) + -- mapConcurrently_ replayOneChain bounds + -- logg Info "finished fast forward replay" + -- logFunctionJson logger Info PactReplaySuccessful + -- inner $ Replayed lowerBoundCut upperBoundCut + else do + logg Debug "start initializing miner resources" + logFunctionJson logger Info InitializingMinerResources + + withMiningCoordination mLogger mConf mCutDb $ \mc -> + + -- Miner resources are used by the test-miner when in-node + -- mining is configured or by the mempool noop-miner (which + -- keeps the mempool updated) in production setups. + -- + withMinerResources mLogger (_miningInNode mConf) cs mCutDb mc $ \m -> do + logFunctionJson logger Info ChainwebStarted + logg Debug "finished initializing miner resources" + let !haddr = _peerConfigAddr $ _p2pConfigPeer $ _configP2p conf + inner $ StartedChainweb Chainweb + { _chainwebHostAddress = haddr + , _chainwebChains = cs + , _chainwebCutResources = cutResources + , _chainwebMiner = m + , _chainwebCoordinator = mc + , _chainwebLogger = logger + , _chainwebPeer = peerRes + , _chainwebManager = mgr + -- , _chainwebPactData = pactData + , _chainwebThrottler = throttler + , _chainwebPutPeerThrottler = putPeerThrottler + , _chainwebMempoolThrottler = mempoolThrottler + , _chainwebConfig = conf + , _chainwebServiceSocket = serviceSock + , _chainwebBackup = BackupEnv + { _backupRocksDb = rocksDb + , _backupDir = backupDir + , _backupPactDbDir = defaultPactDbDir + , _backupChainIds = cids + , _backupLogger = backupLogger + } } - } -- synchronizePactDb :: HM.HashMap ChainId (ChainResources logger) -> Cut -> IO () -- synchronizePactDb cs targetCut = do diff --git a/src/Chainweb/Chainweb/Configuration.hs b/src/Chainweb/Chainweb/Configuration.hs index c2c13c73d0..ca4d4d4eb8 100644 --- a/src/Chainweb/Chainweb/Configuration.hs +++ b/src/Chainweb/Chainweb/Configuration.hs @@ -57,6 +57,10 @@ module Chainweb.Chainweb.Configuration , BackupConfig(..) , defaultBackupConfig +-- * Pruning configuration +, PruneConfig(..) +, defaultPruneConfig + -- * Chainweb Configuration , ChainwebConfiguration(..) , configChainwebVersion @@ -76,6 +80,7 @@ module Chainweb.Chainweb.Configuration ) where +import Chainweb.BlockHeaderDB.PruneForks qualified as PruneForks import Chainweb.BlockHeight import Chainweb.Difficulty import Chainweb.HostAddress @@ -505,6 +510,34 @@ pBackupConfig = id where backup = Just "backup" +-- -------------------------------------------------------------------------- -- +-- Pruning Configuration + +data PruneConfig = PruneConfig + { _configPruneCommand :: PruneForks.DoPrune + } + deriving (Show, Eq) + +defaultPruneConfig :: PruneConfig +defaultPruneConfig = PruneConfig + { _configPruneCommand = PruneForks.Prune + } +makeLenses ''PruneConfig + +instance ToJSON PruneConfig where + toJSON cfg = object [ "command" .= _configPruneCommand cfg ] +instance FromJSON PruneConfig where + parseJSON = withObject "PruneConfig" $ \o -> + PruneConfig <$> o .: "command" +instance FromJSON (PruneConfig -> PruneConfig) where + parseJSON = withObject "PruneConfig" $ \o -> id + <$< configPruneCommand ..: "command" % o + +pPruneConfig :: MParser PruneConfig +pPruneConfig = id + <$< parserOptionGroup "Pruning" + (configPruneCommand .:: textOption (long "pruning-command")) + -- -------------------------------------------------------------------------- -- -- Chainweb Configuration @@ -518,6 +551,7 @@ data ChainwebConfiguration = ChainwebConfiguration , _configBackup :: !BackupConfig , _configServiceApi :: !ServiceApiConfig , _configPayloadProviders :: PayloadProviderConfig + , _configPrune :: !PruneConfig -- The following properties are deprecated: history replay should not be -- part of normal operation mode. It should probably use a completely @@ -575,6 +609,7 @@ defaultChainwebConfiguration v = ChainwebConfiguration , _configReorgLimit = defaultReorgLimit , _configServiceApi = defaultServiceApiConfig , _configReadOnlyReplay = False + , _configPrune = defaultPruneConfig , _configSyncChains = Nothing , _configBackup = defaultBackupConfig , _configPayloadProviders = defaultPayloadProviderConfig @@ -593,6 +628,7 @@ instance ToJSON ChainwebConfiguration where , "syncChains" .= _configSyncChains o , "backup" .= _configBackup o , "payloadProviders" .= _configPayloadProviders o + , "pruning" .= _configPrune o ] instance FromJSON ChainwebConfiguration where @@ -612,6 +648,7 @@ instance FromJSON (ChainwebConfiguration -> ChainwebConfiguration) where <*< configSyncChains ..: "syncChains" % o <*< configBackup %.: "backup" % o <*< configPayloadProviders %.: "payloadProviders" % o + <*< configPrune %.: "pruning" % o pChainwebConfiguration :: MParser ChainwebConfiguration pChainwebConfiguration = id @@ -636,6 +673,7 @@ pChainwebConfiguration = id -- FIXME support payload providers <*< configPayloadProviders %:: pPayloadProviderConfig + <*< configPrune %:: pPruneConfig parseVersion :: MParser ChainwebVersion parseVersion = constructVersion diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 55ccf127af..62de120968 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -8,6 +8,7 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Chainweb.CutResources @@ -38,13 +39,13 @@ import Control.Monad.Trans.Resource import Prelude hiding (log) -import qualified Network.HTTP.Client as HTTP +import Network.HTTP.Client qualified as HTTP -- internal modules import Chainweb.Cut (Cut) import Chainweb.CutDB -import qualified Chainweb.CutDB.Sync as C +import Chainweb.CutDB.Sync qualified as C import Chainweb.Logger import Chainweb.RestAPI.NetworkID import Chainweb.Sync.WebBlockHeaderStore @@ -58,9 +59,11 @@ import P2P.Node.Configuration import P2P.Peer import P2P.TaskQueue import Chainweb.PayloadProvider -import qualified Data.Text as T +import Data.Text qualified as T import P2P.Session (P2pSession) import P2P.Node.PeerDB (PeerDb) +import Chainweb.Utils +import Chainweb.BlockHeaderDB.PruneForks qualified as PruneForks -- -------------------------------------------------------------------------- -- -- Cuts Resources diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index 95558674c1..a019c68516 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -184,6 +184,7 @@ module Chainweb.Utils , reverseStream , foldChunksM , foldChunksM_ +, mergeN , progress -- * Type Level @@ -1077,14 +1078,14 @@ tryAllSynchronous = trySynchronous -- -- An info-level message is logged when processing starts and stops. -- -runForever :: (LogLevel -> T.Text -> IO ()) -> T.Text -> IO () -> IO () +runForever :: (LogLevel -> T.Text -> IO ()) -> T.Text -> IO () -> IO a runForever logfun name a = mask $ \umask -> do logfun Debug $ "start " <> name let go = do forever (umask a) `catchAllSynchronous` \e -> logfun Error $ name <> " failed: " <> sshow e <> ". Restarting ..." go - void go `finally` logfun Debug (name <> " stopped") + go `finally` logfun Debug (name <> " stopped") -- | Repeatedly run a computation 'forever' at the given rate until it is -- stopped by receiving 'SomeAsyncException'. @@ -1103,7 +1104,7 @@ runForeverThrottled -> Word64 -- ^ rate limit (usec / call) -> IO () - -> IO () + -> IO a runForeverThrottled logfun name burst rate a = mask $ \umask -> do tokenBucket <- newTokenBucket logfun Debug $ "start " <> name @@ -1112,7 +1113,7 @@ runForeverThrottled logfun name burst rate a = mask $ \umask -> do forever (umask runThrottled) `catchAllSynchronous` \e -> logfun Error $ name <> " failed: " <> sshow e <> ". Restarting ..." go - void go `finally` logfun Debug (name <> " stopped") + go `finally` logfun Debug (name <> " stopped") -- -------------------------------------------------------------------------- -- -- Configuration wrapper to enable and disable components @@ -1294,6 +1295,19 @@ foldChunksM_ foldChunksM_ f seed = fmap (fst . S.lazily) . foldChunksM f seed {-# INLINE foldChunksM_ #-} +-- | Left-biased k-way merge of streams, using a binary tree of merges. O(n log k) where k +-- is the number of streams and n is the total elements. +mergeN :: forall m k a. (Monad m, Ord k) => (a -> k) -> [S.Stream (S.Of a) m ()] -> S.Stream (S.Of a) m () +mergeN f = go + where + go [strm] = strm + go strms = go (mergePairs strms) + where + mergePairs (a:b:xs) = + let !x = () <$ S.mergeOn f a b + in x : mergePairs xs + mergePairs xs = xs + -- | Progress reporting for long-running streams. I calls an action -- every @n@ streams items. -- diff --git a/src/Chainweb/WebBlockHeaderDB.hs b/src/Chainweb/WebBlockHeaderDB.hs index a5f020e2db..89e7948d27 100644 --- a/src/Chainweb/WebBlockHeaderDB.hs +++ b/src/Chainweb/WebBlockHeaderDB.hs @@ -6,6 +6,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} @@ -19,7 +20,7 @@ -- Collect the 'BlockHeaderDB's for all chains in a single data structure. -- module Chainweb.WebBlockHeaderDB -( WebBlockHeaderDb +( WebBlockHeaderDb(..) , mkWebBlockHeaderDb , initWebBlockHeaderDb , getWebBlockHeaderDb @@ -71,6 +72,7 @@ import Chainweb.Version import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB import Chainweb.Ranked(Ranked(..)) +import Chainweb.Utils.Serialization -- -------------------------------------------------------------------------- -- -- Web Chain Database @@ -87,20 +89,21 @@ import Chainweb.Ranked(Ranked(..)) -- data WebBlockHeaderDb = WebBlockHeaderDb { _webBlockHeaderDb :: !(ChainMap BlockHeaderDb) + , _webCurrentPruneJob :: !(RocksDbTable () (BlockHeight, BlockHeight)) + , _webHighestPruned :: !(RocksDbTable () BlockHeight) } webBlockHeaderDb :: Getter WebBlockHeaderDb (ChainMap BlockHeaderDb) webBlockHeaderDb = to _webBlockHeaderDb --- | Returns all blocks in all block header databases. --- -webEntries :: HasVersion => WebBlockHeaderDb -> (S.Stream (Of BlockHeader) IO () -> IO a) -> IO a -webEntries db f = go (view (webBlockHeaderDb . to toList) db) mempty +-- | Returns all blocks in all block header databases, ordered ascending by +-- (block height, chain ID). +webEntries :: HasVersion => WebBlockHeaderDb -> Maybe MinRank -> Maybe MaxRank -> (S.Stream (Of BlockHeader) IO () -> IO a) -> IO a +webEntries db mir mar f = go (view (webBlockHeaderDb . to toList) db) mempty where - go [] s = f s - go (h:t) s = entries h Nothing Nothing Nothing Nothing $ \x -> - go t (() <$ S.mergeOn (view blockCreationTime) s x) - -- FIXME: should we include the rank in the order? + go [] s = f (mergeN (\bh -> (view blockHeight bh, view chainId bh)) s) + go (h:t) s = entries h Nothing Nothing mir mar $ \x -> + go t (void x : s) type instance Index WebBlockHeaderDb = ChainId type instance IxValue WebBlockHeaderDb = BlockHeaderDb @@ -119,7 +122,7 @@ initWebBlockHeaderDb :: HasVersion => RocksDb -> IO WebBlockHeaderDb -initWebBlockHeaderDb db = WebBlockHeaderDb +initWebBlockHeaderDb db = mkWebBlockHeaderDb db <$!> tabulateChainsM (\cid -> initBlockHeaderDb (conf cid db)) where conf cid = Configuration (genesisBlockHeader cid) @@ -127,9 +130,24 @@ initWebBlockHeaderDb db = WebBlockHeaderDb -- | FIXME: this needs some consistency checks -- mkWebBlockHeaderDb - :: ChainMap BlockHeaderDb + :: RocksDb + -> ChainMap BlockHeaderDb -> WebBlockHeaderDb -mkWebBlockHeaderDb m = WebBlockHeaderDb m +mkWebBlockHeaderDb db m = + WebBlockHeaderDb m pruneJobTable highestPrunedTable + where + pruneJobTable = newTable + db + (Chainweb.Storage.Table.RocksDB.Codec (\(l, u) -> runPutS $ encodeBlockHeight l >> encodeBlockHeight u) + (runGetS $ (,) <$> decodeBlockHeight <*> decodeBlockHeight)) + (Chainweb.Storage.Table.RocksDB.Codec (\_ -> mempty) (runGetS (pure ()))) + ["BlockHeader", "prune-job"] + + highestPrunedTable = newTable + db + (Chainweb.Storage.Table.RocksDB.Codec (runPutS . encodeBlockHeight) (runGetS decodeBlockHeight)) + (Chainweb.Storage.Table.RocksDB.Codec (\_ -> mempty) (runGetS (pure ()))) + ["BlockHeader", "highest-pruned"] getWebBlockHeaderDb :: (MonadThrow m, HasVersion) diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index 82a1e29db9..c7bdb3ae49 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -689,7 +689,7 @@ sampleConsensusState -> IO ConsensusState sampleConsensusState nid bhdb cutdb ct s = do numStoredCuts <- withTableIterator ct $ \i -> iterToEntryStream i & S.length_ - !hashes' <- webEntries bhdb + !hashes' <- webEntries bhdb Nothing Nothing $ S.fold_ (flip HS.insert) (_stateBlockHashes s) id . S.map (view blockHash) !c <- _cut cutdb diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index cbf89a2a0d..59da8d2128 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -47,7 +47,6 @@ module Chainweb.Test.Utils -- * Data Generation , toyBlockHeaderDb , toyChainId -, toyGenesis , toyVersion , genesisBlockHeaderForChain , withToyDB @@ -331,23 +330,20 @@ toyVersion = barebonesTestVersion singletonChainGraph toyChainId :: ChainId toyChainId = withVersion toyVersion someChainId -toyGenesis :: ChainId -> BlockHeader -toyGenesis cid = withVersion toyVersion genesisBlockHeader cid - -- | Initialize an length-1 `BlockHeaderDb` for testing purposes. -- -- Borrowed from TrivialSync.hs -- -toyBlockHeaderDb :: RocksDb -> ChainId -> IO (BlockHeader, BlockHeaderDb) -toyBlockHeaderDb db cid = withVersion toyVersion $ (g,) <$> testBlockHeaderDb db g +toyBlockHeaderDb :: HasVersion => RocksDb -> ChainId -> IO (BlockHeader, BlockHeaderDb) +toyBlockHeaderDb db cid = (g,) <$> testBlockHeaderDb db g where - g = toyGenesis cid + g = genesisBlockHeader cid -- | Given a function that accepts a Genesis Block and -- an initialized `BlockHeaderDb`, perform some action -- and cleanly close the DB. -- -withToyDB :: RocksDb -> ChainId -> ResourceT IO (BlockHeader, BlockHeaderDb) +withToyDB :: HasVersion => RocksDb -> ChainId -> ResourceT IO (BlockHeader, BlockHeaderDb) withToyDB db cid = snd <$> allocate (toyBlockHeaderDb db cid) (closeBlockHeaderDb . snd) diff --git a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs new file mode 100644 index 0000000000..637987a874 --- /dev/null +++ b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs @@ -0,0 +1,451 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE PartialTypeSignatures #-} + +-- | +-- Module: Chainweb.Test.BlockHeaderDB.PruneForks +-- Copyright: Copyright © 2020 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +-- TODO +-- +module Chainweb.Test.BlockHeaderDB.PruneForks +( tests +) where + +import Control.Concurrent.STM +import Control.Lens +import Control.Monad +import Control.Monad.Catch +import Control.Monad.Trans.Resource +import Control.Monad.IO.Class + +-- import Data.CAS +-- import Data.CAS.RocksDB +import Data.HashMap.Strict qualified as HM +import Data.Text qualified as T +import Data.Tree (Tree) +import Data.Tree qualified as Tree + +import Numeric.Natural + +import System.LogLevel +import System.Random + +import Test.Tasty +import Test.Tasty.HUnit + +-- internal modules + +import Chainweb.BlockHash +import Chainweb.BlockHeader +import Chainweb.BlockHeader.Validation +import Chainweb.BlockHeaderDB +import Chainweb.BlockHeaderDB.Internal +import Chainweb.BlockHeaderDB.PruneForks +import Chainweb.Logger +import Chainweb.Test.Utils +import Chainweb.Test.Utils.BlockHeader +import Chainweb.Utils +import Chainweb.Version +import Chainweb.Storage.Table.RocksDB +import Chainweb.CutDB +import Chainweb.Test.CutDB (withTestCutDb) +import Chainweb.PayloadProvider (ConfiguredPayloadProvider(DisabledPayloadProvider)) +import Chainweb.Test.Pact.Utils +import Chainweb.Storage.Table +import Chainweb.Cut (unsafeMkCut, genesisCut) +import Chainweb.Cut.CutHashes +import Chainweb.Core.Brief +import Chainweb.Test.TestVersions +import Chainweb.Graph +import Chainweb.WebBlockHeaderDB +import Control.Monad.State.Strict +import Chainweb.Parent +import Data.HashMap.Strict (HashMap) +import Data.Maybe (mapMaybe, catMaybes, fromMaybe) +import Chainweb.Cut.Create +import Hedgehog +import Hedgehog.Gen (shuffle) +import qualified Data.HashSet as HS +import System.Random.Shuffle (shuffleM) +import Streaming.Prelude qualified as S +import Control.Monad.Except +import Streaming qualified as S +import PropertyMatchers qualified as P +import Chainweb.ChainValue +import qualified Chainweb.TreeDB as TreeDB +import Chainweb.Ranked +import Chainweb.Version.Utils (avgBlockHeightAtCutHeight, chainIdsAt) + +-- -------------------------------------------------------------------------- -- +-- Utils + +-- | Log level for the tests in this module. Set to 'Debug' for debugging any +-- issues with the test. +-- +testLogLevel :: LogLevel +testLogLevel = Warn + +-- logg :: Show a => (String -> t) -> a -> T.Text -> t +-- logg s l msg +-- | l >= testLogLevel = s $ "[" <> sshow l <> "] " <> T.unpack msg +-- | otherwise = return () + +withDbs + :: HasVersion + => RocksDb + -> ResourceT IO CutDb +withDbs rdb = do + -- create unique namespace for each test so that they so that test can + -- run in parallel. + -- x <- randomIO :: IO Int + -- rdb <- rio >>= testRocksDb (sshow x) + testLogger <- liftIO getTestLogger + (_, cutDb) <- withTestCutDb rdb id 0 (onAllChains DisabledPayloadProvider) testLogger + return cutDb + +selectHighCuts :: Casify RocksDbTable CutHashes -> Int -> Maybe (CasKeyType CutHashes) -> IO ([CutHashes], CasKeyType CutHashes) +selectHighCuts cutTable candidateCount maybeMaxCutHeight = do + (highestCuts, nextCutHeight) <- withTableIterator cutTable $ \iter -> do + case maybeMaxCutHeight of + Nothing -> + iterLast iter + Just ch -> do + iterSeek iter ch + iterPrev iter + + cuts <- fmap catMaybes $ replicateM candidateCount $ do + iterValue iter >>= \case + Just r -> do + iterPrev iter + return (Just r) + Nothing -> return Nothing + nextCutHeight <- iterKey iter >>= \case + Nothing -> iterNext iter >> fromJuste <$> iterKey iter + Just ch -> return ch + return (cuts, nextCutHeight) + (,nextCutHeight) <$> shuffleM highestCuts + +findM :: Monad m => [t] -> (t -> m (Maybe a)) -> m (Maybe a) +findM (x:xs) f = do + y <- f x + case y of + Nothing -> findM xs f + Just u -> return $ Just u +findM [] _ = return Nothing + +selectNewBlockParents + :: HasVersion + => Casify RocksDbTable CutHashes + -> WebBlockHeaderDb + -> Maybe (CasKeyType CutHashes) + -> Int + -> IO CutExtension +selectNewBlockParents cutTable wbhdb maybeMaxCutHeight candCutCount = do + (candCutHashesList, newMaxCutHeight) <- selectHighCuts cutTable candCutCount maybeMaxCutHeight + shuffledChains <- shuffleM $ HS.toList $ chainIdsAt (round $ avgBlockHeightAtCutHeight $ maybe maxBound (view _1) maybeMaxCutHeight) + r <- findM candCutHashesList $ \candCutHashes -> do + candCutBlocks <- liftIO $ + iforM (candCutHashes ^. cutHashes) (lookupRankedWebBlockHeaderDb wbhdb) + let candCut = unsafeMkCut candCutBlocks + let exts = mapMaybe (getCutExtension candCut) shuffledChains + case exts of + ext:_ -> return $ Just ext + [] -> return Nothing + case r of + Just r' -> return r' + Nothing -> selectNewBlockParents cutTable wbhdb (Just newMaxCutHeight) candCutCount + +progressArbitraryFork + :: (HasVersion, MonadIO m, MonadState Nonce m) + => Casify RocksDbTable CutHashes + -> WebBlockHeaderDb + -> Int + -> m () +progressArbitraryFork cutTable wbhdb candCutCount = do + cutExt <- liftIO $ selectNewBlockParents cutTable wbhdb Nothing candCutCount + let parent = _cutExtensionParent cutExt + let bhdb = wbhdb ^?! webBlockHeaderDb . ix (parent ^. chainId) + adjsForParent <- liftIO $ iforMOf (itraversed . _Parent) + (_getBlockHashRecord $ _cutExtensionAdjacentHashes cutExt) + (\c blk -> lookupRankedWebBlockHeaderDb wbhdb c + (Ranked (view (_Parent . blockHeight) parent) blk)) + nextNonce <- get + let newBlock = testBlockHeader adjsForParent nextNonce parent + liftIO $ unsafeInsertBlockHeaderDb bhdb newBlock + newCut <- liftIO $ tryMonotonicCutExtension (_cutExtensionCut cutExt) newBlock + liftIO $ casInsert cutTable (cutToCutHashes Nothing $ fromJuste newCut) + put (succ nextNonce) + return () + +pruneTestRandom :: (HasVersion, _) => _ +pruneTestRandom baseRdb f = runResourceT $ do + rdb <- withTestRocksDb "" baseRdb + liftIO $ do + let t = cutHashesTable rdb + wbhdb <- initWebBlockHeaderDb rdb + let lookupChainBlk (ChainValue c bhsh) = do + db <- getWebBlockHeaderDb wbhdb c + TreeDB.lookup db bhsh + let validateAllBlocks = do + void $ webEntries wbhdb Nothing Nothing $ \blks -> do + blks + & S.hoist liftIO + & S.mapM_ (\blk -> + validateAllParentsExist lookupChainBlk blk + >>= \case + Right r -> P.succeed r + Left e -> P.fail "successful validation" (e, "failed on block: " <> sshow blk) + ) + casInsert t (cutToCutHashes Nothing genesisCut) + lgr <- getTestLogger + logFunctionText lgr Info "inserting blocks..." + (_, finalNonce) <- flip runStateT (Nonce 0) $ f t wbhdb + logFunctionText lgr Info $ "final nonce " <> sshow finalNonce + logFunctionText lgr Info "validating initial blocks..." + validateAllBlocks + highestCut <- readHighestCutHeaders (logFunctionText lgr) wbhdb t + numPruned <- pruneForks lgr (unsafeMkCut highestCut) wbhdb Prune 0 + logFunctionText lgr Info "validating pruned blocks..." + validateAllBlocks + return numPruned + +pruneTestWithOnePivot :: _ +pruneTestWithOnePivot rdb = withVersion (barebonesTestVersion pairChainGraph) $ do + _ <- pruneTestRandom rdb $ \t wbhdb -> do + replicateM_ 4000 $ do + progressArbitraryFork t wbhdb 2 + replicateM_ 10 $ do + progressArbitraryFork t wbhdb 1 + return () + +pruneTestWithLotsOfPivots :: _ +pruneTestWithLotsOfPivots rdb = withVersion (barebonesTestVersion pairChainGraph) $ do + _ <- pruneTestRandom rdb $ \t wbhdb -> do + replicateM_ 4000 $ do + progressArbitraryFork t wbhdb 16 + return () + +pruneTestWithManyForks :: _ +pruneTestWithManyForks rdb = withVersion (barebonesTestVersion pairChainGraph) $ do + _ <- pruneTestRandom rdb $ \t wbhdb -> do + replicateM_ 4000 $ do + progressArbitraryFork t wbhdb 16 + return () + +pruneTestWithManyChains :: _ +pruneTestWithManyChains rdb = withVersion (barebonesTestVersion petersenChainGraph) $ do + _ <- pruneTestRandom rdb $ \t wbhdb -> do + replicateM_ 1000 $ do + progressArbitraryFork t wbhdb 14 + return () + +pruneTestWithManyManyChains :: _ +pruneTestWithManyManyChains rdb = withVersion (barebonesTestVersion d4k4ChainGraph) $ do + _ <- pruneTestRandom rdb $ \t wbhdb -> do + replicateM_ 200 $ do + progressArbitraryFork t wbhdb 1 + replicateM_ 400 $ do + progressArbitraryFork t wbhdb 140 + replicateM_ 400 $ do + progressArbitraryFork t wbhdb 1 + return () + +pruneTestSmall :: _ +pruneTestSmall rdb = withVersion (barebonesTestVersion petersenChainGraph) $ do + _ <- pruneTestRandom rdb $ \t wbhdb -> do + replicateM_ 20 $ do + progressArbitraryFork t wbhdb 1 + return () + +createForks + :: HasVersion + => BlockHeaderDb + -> BlockHeader + -> IO ([BlockHeader], [BlockHeader]) +createForks bdb h = (,) + <$> insert bdb h (Nonce 1) 10 + <*> insert bdb h (Nonce 2) 5 + +insert + :: HasVersion + => BlockHeaderDb + -> BlockHeader + -> Nonce + -> Natural + -> IO [BlockHeader] +insert bdb h n l = do + hdrs <- insertN_ n l h bdb + return hdrs + +cid :: ChainId +cid = unsafeChainId 0 + +delHdr :: BlockHeaderDb -> BlockHeader -> IO () +delHdr cdb k = do + tableDelete (_chainDbCas cdb) (casKey $ RankedBlockHeader k) + tableDelete (_chainDbRankTable cdb) (view blockHash k) + +-- -------------------------------------------------------------------------- -- +-- Test cases + +tests :: RocksDb -> TestTree +tests rdb = + testGroup "Chainweb.BlockHeaderDb.PruneForks" + [ testCaseSteps "simple 1" + $ withVersion (barebonesTestVersion singletonChainGraph) + $ singleForkTest rdb 1 5 + , testCaseSteps "simple 2" + $ withVersion (barebonesTestVersion singletonChainGraph) + $ singleForkTest rdb 2 5 + , testCaseSteps "simple 3" + $ withVersion (barebonesTestVersion singletonChainGraph) + $ singleForkTest rdb 4 5 + , testCaseSteps "simple 4" + $ withVersion (barebonesTestVersion singletonChainGraph) + $ singleForkTest rdb 5 0 + , testCaseSteps "skipping: max bound 1" + $ withVersion (barebonesTestVersion singletonChainGraph) + $ singleForkTest rdb 9 0 + , testCaseSteps "skipping: depth == max block height" + $ withVersion (barebonesTestVersion singletonChainGraph) + $ singleForkTest rdb 10 0 + , testCaseSteps "fail on missing header 5" $ failTest rdb 5 + , testCaseSteps "fail on missing header 6" $ failTest rdb 6 + -- failTest <= 4: succeeds because of second branch + -- failTest 7: empty upper bound warning + -- failTest 8: succeeds because deleted block at height 8 is above upper pruning bound + + -- , pruneWithChecksTests rdb + -- , failPruningChecksTests rdb + , testCaseSteps "full gc" $ testFullGc rdb + + , testCase "small" $ pruneTestSmall rdb + + , testCase "one pivot" $ pruneTestWithOnePivot rdb + + , testCase "lots of pivots" $ pruneTestWithLotsOfPivots rdb + + , testCase "many forks" $ pruneTestWithManyForks rdb + + , testCase "many chains" $ pruneTestWithManyChains rdb + , testCase "many many chains" $ pruneTestWithManyManyChains rdb + + ] + +-- pruneWithChecksTests :: IO RocksDb -> TestTree +-- pruneWithChecksTests rio = testGroup "prune with checks" $ go <$> +-- [ [CheckPayloads] +-- , [CheckPayloadsExist] +-- , [CheckIntrinsic] +-- , [CheckInductive] +-- , [CheckFull] +-- , [minBound .. maxBound] +-- ] +-- where +-- go checks = testCaseSteps (sshow checks) $ testPruneWithChecks rio checks + +-- failPruningChecksTests :: IO RocksDb -> TestTree +-- failPruningChecksTests rio = testGroup "fail pruning checks" +-- [ testCaseSteps "CheckIntrinsic" $ failIntrinsicCheck rio [CheckIntrinsic] 7 +-- , testCaseSteps "CheckInductive" $ failIntrinsicCheck rio [CheckInductive] 7 +-- , testCaseSteps "CheckFull" $ failIntrinsicCheck rio [CheckFull] 7 +-- ] + +singleForkTest + :: HasVersion + => RocksDb + -> Natural + -> Int + -> (String -> IO ()) + -> IO () +singleForkTest rdb d expect step = runResourceT $ do + cdb <- withDbs rdb + liftIO $ do + let db = cdb ^?! cutDbBlockHeaderDb cid + let h = genesisBlockHeader cid + (f0, f1) <- createForks db h + let f0Cut = cutToCutHashes Nothing $ unsafeMkCut (HM.singleton cid (last f0)) + addCutHashes cdb f0Cut + atomically $ do + curCut <- _cutStm cdb + guard (cutToCutHashes Nothing curCut == f0Cut) + initialCut <- _cut cdb + let wbhdb = view cutDbWebBlockHeaderDb cdb + n <- pruneForks logg initialCut wbhdb Prune d + assertHeaders db f0 + when (expect > 0) $ assertPrunedHeaders db f1 + assertEqual "" expect n + where + logg = genericLogger testLogLevel (step . T.unpack) + +assertHeaders :: HasVersion => BlockHeaderDb -> [BlockHeader] -> IO () +assertHeaders db f = + unlessM (fmap and $ mapM (tableMember db) $ view blockHash <$> f) $ + assertFailure "missing block header that should not have been pruned" + +assertPrunedHeaders :: HasVersion => BlockHeaderDb -> [BlockHeader] -> IO () +assertPrunedHeaders db f = + forM_ f $ \bh -> do + whenM (tableMember db (casKey bh)) $ + assertFailure $ "failed to prune some block header: " <> T.unpack (brief bh) + +-- -------------------------------------------------------------------------- -- +-- Header Pruning Tests + +failTest :: RocksDb -> Natural -> (String -> IO ()) -> IO () +failTest rio n step = withVersion (barebonesTestVersion singletonChainGraph) $ runResourceT $ do + cdb <- withDbs rio + liftIO $ do + let db = cdb ^?! cutDbBlockHeaderDb cid + let h = genesisBlockHeader cid + (f0, _) <- createForks db h + delHdr db $ f0 !! (int n) + initialCut <- _cut cdb + let wbhdb = view cutDbWebBlockHeaderDb cdb + try (pruneForks logg initialCut wbhdb Prune 2) >>= \case + Left (InternalInvariantViolation{}) -> return () + Right x -> assertFailure + $ "missing expected InternalInvariantViolation" + <> ". Instead pruning succeeded and deleted " + <> sshow x <> " headers" + return () + where + logg = genericLogger testLogLevel (step . T.unpack) + +-- -------------------------------------------------------------------------- -- +-- GC Tests + +testFullGc :: RocksDb -> (String -> IO ()) -> IO () +testFullGc rdb step = withVersion (barebonesTestVersion singletonChainGraph) $ runResourceT $ do + cdb <- withDbs rdb + let db = cdb ^?! cutDbBlockHeaderDb cid + let h = genesisBlockHeader cid + liftIO $ do + (f0, f1) <- createForks db h + -- fullGc logger rdb (barebonesTestVersion singletonChainGraph) + assertHeaders db f0 + assertPrunedHeaders db f1 + where + logger = genericLogger testLogLevel (step . T.unpack) + +testPruneWithChecks :: RocksDb -> (String -> IO ()) -> IO () +testPruneWithChecks rdb step = withVersion (barebonesTestVersion singletonChainGraph) $ runResourceT $ do + cdb <- withDbs rdb + let db = cdb ^?! cutDbBlockHeaderDb cid + let h = genesisBlockHeader cid + liftIO $ do + (f0, f1) <- createForks db h + -- pruneAllChains logger rdb (barebonesTestVersion singletonChainGraph) + assertHeaders db f0 + assertPrunedHeaders db f1 + where + logger = genericLogger testLogLevel (step . T.unpack) diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 8ef9e0b339..38e7ff936c 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -397,7 +397,7 @@ randomBlockHeader -> IO BlockHeader randomBlockHeader cutDb = do curCut <- _cut cutDb - allBlockHeaders <- webEntries (view cutDbWebBlockHeaderDb cutDb) $ \s -> s + allBlockHeaders <- webEntries (view cutDbWebBlockHeaderDb cutDb) Nothing Nothing $ \s -> s & S.filter (checkHeight curCut) & S.toList_ generate $ elements allBlockHeaders diff --git a/test/unit/Chainweb/Test/TreeDB.hs b/test/unit/Chainweb/Test/TreeDB.hs index f2bb1d86e7..73c90f16c2 100644 --- a/test/unit/Chainweb/Test/TreeDB.hs +++ b/test/unit/Chainweb/Test/TreeDB.hs @@ -321,7 +321,7 @@ prop_forkEntry f i j = do e <- forkEntry db (head $ reverse (g : a)) (head $ reverse $ (g : b)) return $ e === g where - g = view (from isoBH) $ toyGenesis toyChainId + g = view (from isoBH) $ genesisBlockHeader toyChainId t = Node g [] a = take (int i) $ branch (Nonce 0) g b = take (int j) $ branch (Nonce 1) g diff --git a/test/unit/ChainwebTests.hs b/test/unit/ChainwebTests.hs index ed35f2e6b9..569e6fe296 100644 --- a/test/unit/ChainwebTests.hs +++ b/test/unit/ChainwebTests.hs @@ -24,6 +24,7 @@ import Chainweb.Storage.Table.RocksDB import Chainweb.Test.BlockHeader.Genesis qualified (tests) import Chainweb.Test.BlockHeader.Validation qualified (tests) import Chainweb.Test.BlockHeaderDB qualified (tests) +import Chainweb.Test.BlockHeaderDB.PruneForks qualified (tests) import Chainweb.Test.Chainweb.Utils.Paging qualified (properties) import Chainweb.Test.Cut qualified (properties) import Chainweb.Test.CutDB qualified (tests) @@ -96,7 +97,7 @@ suite rdb = , testGroup "BlockHeaderDb" [ Chainweb.Test.BlockHeaderDB.tests rdb , Chainweb.Test.TreeDB.RemoteDB.tests - -- , Chainweb.Test.BlockHeaderDB.PruneForks.tests + , Chainweb.Test.BlockHeaderDB.PruneForks.tests rdb , testProperties "Chainweb.Test.TreeDB" Chainweb.Test.TreeDB.properties ] , Chainweb.Test.CutDB.tests rdb From 12bacf56b0ce0f3e9465193b1217e991638a2093 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Wed, 10 Sep 2025 13:23:54 -0400 Subject: [PATCH 345/378] Add standalone-pruner Change-Id: Id000000059c94a223ea1cc5ae33d341889e0c139 --- .github/workflows/applications.yml | 2 ++ cwtools/cwtools.cabal | 22 ++++++++++++++ cwtools/standalone-pruner/Main.hs | 46 ++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 cwtools/standalone-pruner/Main.hs diff --git a/.github/workflows/applications.yml b/.github/workflows/applications.yml index 73e403d212..0737407710 100644 --- a/.github/workflows/applications.yml +++ b/.github/workflows/applications.yml @@ -369,6 +369,7 @@ jobs: exe:ea \ exe:genconf \ exe:known-graphs \ + exe:standalone-pruner \ exe:pact-diff \ test:chainweb-tests \ test:multi-node-network-tests \ @@ -417,6 +418,7 @@ jobs: cp $(cabal list-bin multi-node-network-tests) artifacts/chainweb cp $(cabal list-bin pact-diff) artifacts/chainweb cp $(cabal list-bin remote-tests) artifacts/chainweb + cp $(cabal list-bin standalone-pruner) artifacts/chainweb cp README.md artifacts/chainweb cp CHANGELOG.md artifacts/chainweb cp LICENSE artifacts/chainweb diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index c0c88b0a21..3bb9fe5f2d 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -87,6 +87,28 @@ executable b64 , optparse-applicative , text +executable standalone-pruner + import: warning-flags, debugging-flags + default-language: Haskell2010 + ghc-options: + -threaded + hs-source-dirs: standalone-pruner + main-is: Main.hs + build-depends: + -- internal + , chainweb + , chainweb-storage + + -- external + , base >= 4.12 && < 5 + , lens + , lens-aeson + , loglevel + , optparse-applicative + , resourcet + , text + , time + executable calculate-release import: warning-flags, debugging-flags default-language: Haskell2010 diff --git a/cwtools/standalone-pruner/Main.hs b/cwtools/standalone-pruner/Main.hs new file mode 100644 index 0000000000..16e63bac57 --- /dev/null +++ b/cwtools/standalone-pruner/Main.hs @@ -0,0 +1,46 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE ApplicativeDo #-} + +module Main where + +import Options.Applicative + +import Control.Monad +import Data.Text.IO qualified as T +import System.LogLevel + +import Chainweb.BlockHeaderDB.PruneForks qualified as PruneForks +import Chainweb.Cut (unsafeMkCut) +import Chainweb.CutDB (readHighestCutHeaders, cutHashesTable) +import Chainweb.Logger +import Chainweb.Storage.Table.RocksDB (withRocksDb, modernDefaultOptions) +import Chainweb.Utils +import Chainweb.Version +import Chainweb.Version.Registry +import Chainweb.WebBlockHeaderDB + +main :: IO () +main = join $ + execParser $ info + (parser <**> helper) + (fullDesc + <> progDesc "Prune a chainweb-node block header database (rocksdb)" + <> header "standalone-pruner") + +parser :: Parser (IO ()) +parser = do + version <- option (findKnownVersion =<< textReader) (long "chainweb-version" <> short 'v') + dbDir <- textOption (long "rocksdb-path") + logLevel <- flag' Debug (long "verbose") <|> flag' Warn (long "quiet") <|> pure Info + doPrune <- + flag' PruneForks.ForcePrune (long "force") + <|> flag' PruneForks.PruneDryRun (long "dry-run") + <|> pure PruneForks.Prune + return $ withVersion version $ do + withRocksDb dbDir modernDefaultOptions $ \rdb -> do + let logger = genericLogger logLevel T.putStrLn + wbhdb <- initWebBlockHeaderDb rdb + let cutHashesStore = cutHashesTable rdb + initialCut <- unsafeMkCut <$> readHighestCutHeaders (logFunctionText logger) wbhdb cutHashesStore + PruneForks.pruneForks logger initialCut wbhdb doPrune (int PruneForks.safeDepth) + return () From 4e61f899c581665b47aaabc1a20888d851994037 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 25 Sep 2025 15:23:56 -0400 Subject: [PATCH 346/378] Fix legacy SPV errors and use BlockCtx in Pact 4 SPV too Change-Id: Id0000000d9acf019b74d8731fc451472afce44f0 --- src/Chainweb/Pact4/SPV.hs | 56 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Chainweb/Pact4/SPV.hs b/src/Chainweb/Pact4/SPV.hs index f8007079d6..f348d39ea3 100644 --- a/src/Chainweb/Pact4/SPV.hs +++ b/src/Chainweb/Pact4/SPV.hs @@ -62,6 +62,7 @@ import Chainweb.BlockHeader import Chainweb.BlockHeaderDB import Chainweb.BlockHeight import Chainweb.Pact.Backend.Types (HeaderOracle) +import Chainweb.Pact.Types(BlockCtx(..), _bctxCurrentBlockHeight) import Chainweb.Pact.Utils (aeson) import Chainweb.Pact4.Types(internalError) import Chainweb.Pact.Payload @@ -84,20 +85,19 @@ import qualified Pact.Types.Runtime as Pact4 import qualified Pact.Types.SPV as Pact4 import Chainweb.MerkleUniverse (ChainwebMerkleHashAlgorithm) -forkedThrower :: CW.HasVersion => BlockHeader -> Text -> ExceptT Text IO a -forkedThrower bh = - if CW.chainweb219Pact (CW._chainId bh) (view blockHeight bh) +forkedThrower :: CW.HasVersion => BlockCtx -> Text -> ExceptT Text IO a +forkedThrower bctx = + if CW.chainweb219Pact (CW._chainId bctx) (_bctxCurrentBlockHeight bctx) then throwError else internalError -catchAndDisplaySPVError :: CW.HasVersion => BlockHeader -> ExceptT Text IO a -> ExceptT Text IO a -catchAndDisplaySPVError bh = - if CW.chainweb219Pact (CW._chainId bh) (view blockHeight bh) - then flip catch $ \case - -- only error we expect - SpvExceptionVerificationFailed _ -> throwError ("spv verification failed: target header is not in the chain") - spvErr -> throwM spvErr - else id +catchAndDisplaySPVError :: CW.HasVersion => BlockCtx -> ExceptT Text IO a -> ExceptT Text IO a +catchAndDisplaySPVError bctx = + if CW.chainweb219Pact (CW._chainId bctx) (_bctxCurrentBlockHeight bctx) + then id + else flip catchError $ + -- the only error we expect + \_msg -> throwM $ SpvExceptionVerificationFailed "target header is not in the chain" -- | Spv support for pact -- @@ -105,12 +105,12 @@ pactSPV :: CW.HasVersion => HeaderOracle -- ^ handle into the cutdb - -> BlockHeader + -> BlockCtx -- ^ the context for verifying the proof -> Pact4.SPVSupport -pactSPV headerOracle bh = Pact4.SPVSupport - (verifySPV headerOracle bh) - (verifyCont headerOracle bh) +pactSPV headerOracle bctx = Pact4.SPVSupport + (verifySPV headerOracle bctx) + (verifyCont headerOracle bctx) -- | SPV transaction verification support. Calls to 'verify-spv' in Pact -- will thread through this function and verify an SPV receipt, making the @@ -120,7 +120,7 @@ verifySPV :: CW.HasVersion => HeaderOracle -- ^ handle into the cut db - -> BlockHeader + -> BlockCtx -- ^ the context for verifying the proof -> Text -- ^ TXOUT or TXIN - defines the type of proof @@ -128,10 +128,10 @@ verifySPV -> Pact4.Object Pact4.Name -- ^ the proof object to validate -> IO (Either Text (Pact4.Object Pact4.Name)) -verifySPV headerOracle bh typ proof = runExceptT $ go typ proof +verifySPV headerOracle bctx typ proof = runExceptT $ go typ proof where cid = CW._chainId headerOracle - enableBridge = CW.enableSPVBridge cid (view blockHeight bh) + enableBridge = CW.enableSPVBridge cid (_bctxCurrentBlockHeight bctx) mkSPVResult' cr j | enableBridge = @@ -152,7 +152,7 @@ verifySPV headerOracle bh typ proof = runExceptT $ go typ proof "TXOUT" -> do u <- except $ extractProof enableBridge o unless (view outputProofChainId u == cid) $ - forkedThrower bh "cannot redeem spv proof on wrong target chain" + forkedThrower bctx "cannot redeem spv proof on wrong target chain" -- SPV proof verification is a 3 step process: -- @@ -163,11 +163,11 @@ verifySPV headerOracle bh typ proof = runExceptT $ go typ proof -- 3. Extract tx outputs as a pact object and return the -- object. - TransactionOutput p <- catchAndDisplaySPVError bh $ + TransactionOutput p <- catchAndDisplaySPVError bctx $ checkProofAndExtractOutput headerOracle u q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of - Nothing -> forkedThrower bh "unable to decode spv transaction output" + Nothing -> forkedThrower bctx "unable to decode spv transaction output" Just cr -> return cr case Pact4._crResult q of @@ -258,22 +258,22 @@ verifyCont :: CW.HasVersion => HeaderOracle -- ^ handle into the cut db - -> BlockHeader + -> BlockCtx -- ^ the context for verifying the proof -> Pact4.ContProof -- ^ bytestring of 'TransactionOutputP roof' object to validate -> IO (Either Text Pact4.PactExec) -verifyCont headerOracle bh (Pact4.ContProof cp) = runExceptT $ do +verifyCont headerOracle bctx (Pact4.ContProof cp) = runExceptT $ do let errorMessageType = - if CW.chainweb221Pact (CW._chainId bh) (view blockHeight bh) + if CW.chainweb221Pact (CW._chainId bctx) (_bctxCurrentBlockHeight bctx) then Simplified else Legacy t <- decodeB64UrlNoPaddingTextWithFixedErrorMessage errorMessageType $ Text.decodeUtf8 cp case decodeStrict' t of - Nothing -> forkedThrower bh "unable to decode continuation proof" + Nothing -> forkedThrower bctx "unable to decode continuation proof" Just u | view outputProofChainId u /= cid -> - forkedThrower bh "cannot redeem continuation proof on wrong target chain" + forkedThrower bctx "cannot redeem continuation proof on wrong target chain" | otherwise -> do -- Cont proof verification is a 3 step process: @@ -285,11 +285,11 @@ verifyCont headerOracle bh (Pact4.ContProof cp) = runExceptT $ do -- 3. Extract continuation 'PactExec' from decoded result -- and return the cont exec object - TransactionOutput p <- catchAndDisplaySPVError bh $ + TransactionOutput p <- catchAndDisplaySPVError bctx $ checkProofAndExtractOutput headerOracle u q <- case decodeStrict' p :: Maybe (Pact4.CommandResult Pact4.Hash) of - Nothing -> forkedThrower bh "unable to decode spv transaction output" + Nothing -> forkedThrower bctx "unable to decode spv transaction output" Just cr -> return cr case Pact4._crContinuation q of From 5e64a6317a19546243bda54dd1a1f3ab1ef5be6e Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 27 Sep 2025 18:45:25 -0700 Subject: [PATCH 347/378] Fix fork resolution in initialization of payload providers --- src/Chainweb/CutDB.hs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 952ffbe9be..88db9c244a 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -546,6 +546,12 @@ synchronizeProviders logger wbh providers c = do MaybeT $ return Nothing _ -> empty + -- FIXME: this stream can be very long. We should limit it and + -- proceed iteratively if necessary. + -- Ideally, we check the length based on block heights before + -- we compute the full trace below. We also have to be careful, + -- that we compute the fork point only once and iterate from + -- there, otherwise the complexity would become quadratic. (forkBlocksDescendingStream S.:> forkPoint) <- liftIO $ S.toList $ branchDiff_ bhdb ppBlock hdr let forkBlocksAscending = reverse $ snd $ partitionHereThere forkBlocksDescendingStream @@ -556,7 +562,11 @@ synchronizeProviders logger wbh providers c = do (forkPoint : forkBlocksAscending) forkBlocksAscending - let newForkInfo = finfo { _forkInfoTrace = newTrace } + let newForkInfo = finfo + { _forkInfoTrace = newTrace + , _forkInfoBasePayloadHash = + Parent (view blockPayloadHash forkPoint) + } -- if this fails, there is no way for the payload provider -- to sync to the block without using the ordinary cut pipeline. From 28efdc07693ec305c7104cb65c4e2852738bd2b9 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 30 Sep 2025 01:32:52 -0700 Subject: [PATCH 348/378] fix ForkInfo creation in WebBlockHeaderStore --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 8d848cade8..9200b334c7 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -502,7 +502,11 @@ getBlockHeaderInternal blockHeaderToEvaluationCtx (Parent prent)) (forkPoint : forkBlocksAscending) forkBlocksAscending - let newForkInfo = finfo { _forkInfoTrace = newTrace } + let newForkInfo = finfo + { _forkInfoTrace = newTrace + , _forkInfoBasePayloadHash = + Parent $ view blockPayloadHash forkPoint + } r' <- syncToBlock provider hints newForkInfo `catch` \(e :: SomeException) -> do logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation retry for " <> sshow h <> " failed with: " <> sshow e throwM e From 41cb354ba32ebcda9450fd87191d8fceccf5b2bd Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Tue, 23 Sep 2025 15:26:40 -0400 Subject: [PATCH 349/378] Use BlockHistory if ConsensusState table is empty Otherwise, existing pact databases will trigger rewinds to genesis, because they will see no current ConsensusState rows. Change-Id: Id0000000f26422ea9dd60fb52574244b3e9c5e8b --- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 32 ++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 273a0f1d90..4c81dbae35 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -77,6 +77,7 @@ module Chainweb.Pact.Backend.ChainwebPactDb , lookupRankedBlockHash , getPayloadsAfter , getEarliestBlock + , getLatestBlock , getConsensusState , setConsensusState , throwOnDbError @@ -785,7 +786,7 @@ setConsensusState db cs = do getConsensusState :: SQ3.Database -> ExceptT LocatedSQ3Error IO (Maybe ConsensusState) getConsensusState db = do - qry db "SELECT blockheight, hash, payloadhash, safety FROM ConsensusState ORDER BY safety ASC;" + maybeState <- qry db "SELECT blockheight, hash, payloadhash, safety FROM ConsensusState ORDER BY safety ASC;" [] [RInt, RBlob, RBlob, RText] >>= \case [final, latest, safe] -> return $ Just ConsensusState { _consensusStateFinal = readRow "final" final @@ -794,6 +795,13 @@ getConsensusState db = do } [] -> return Nothing inv -> error $ "invalid contents of the ConsensusState table: " <> sshow inv + case maybeState of + Nothing -> do + getLatestBlock db >>= \case + Nothing -> return Nothing + Just latest -> + return $ Just $ ConsensusState latest latest latest + Just s -> return (Just s) where readRow expectedType [SInt height, SBlob hash, SBlob payloadHash, SText type'] | expectedType == type' = SyncState @@ -907,6 +915,28 @@ getEarliestBlock db = do in return (RankedBlockHash (fromIntegral hgt) hash) go _ = fail "Chainweb.Pact.Backend.RelationalCheckpointer.doGetEarliest: impossible. This is a bug in chainweb-node." +-- | Get the checkpointer's idea of the latest block. +getLatestBlock :: HasCallStack => SQLiteEnv -> ExceptT LocatedSQ3Error IO (Maybe SyncState) +getLatestBlock db = do + r <- qry db qtext [] [RInt, RBlob, RBlob] >>= mapM go + case r of + [] -> return Nothing + (!o:_) -> return (Just o) + where + qtext = "SELECT blockheight, hash, payloadhash FROM BlockHistory2 ORDER BY blockheight DESC LIMIT 1" + + go [SInt hgt, SBlob blob, SBlob pBlob] = + let hash = either error id $ runGetEitherS decodeBlockHash blob + in let pHash = either error id $ runGetEitherS decodeBlockPayloadHash pBlob + in return $ SyncState + { _syncStateBlockHash = hash + , _syncStateBlockPayloadHash = pHash + , _syncStateHeight = int hgt + } + go r = fail $ + "Chainweb.Pact.Backend.RelationalCheckpointer.doGetLatest: impossible. This is a bug in chainweb-node. Details: " + <> sshow r + lookupBlockWithHeight :: HasCallStack => SQ3.Database -> BlockHeight -> ExceptT LocatedSQ3Error IO (Maybe (Ranked BlockHash)) lookupBlockWithHeight db bheight = do qry db qtext [SInt $ fromIntegral bheight] [RBlob] >>= \case From 7eb5ab9cdfc2c899672141e79ffb8f4e4abdc2f8 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Mon, 29 Sep 2025 10:45:55 -0400 Subject: [PATCH 350/378] Update src/Chainweb/Pact/Backend/ChainwebPactDb.hs Co-authored-by: rsoeldner --- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 4c81dbae35..32a48a692a 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -934,7 +934,7 @@ getLatestBlock db = do , _syncStateHeight = int hgt } go r = fail $ - "Chainweb.Pact.Backend.RelationalCheckpointer.doGetLatest: impossible. This is a bug in chainweb-node. Details: " + "Chainweb.Pact.Backend.ChainwebPactDb.getLatestBlock: impossible. This is a bug in chainweb-node. Details: " <> sshow r lookupBlockWithHeight :: HasCallStack => SQ3.Database -> BlockHeight -> ExceptT LocatedSQ3Error IO (Maybe (Ranked BlockHash)) From c925def3521efe583e5195f516b8037c4cd36427 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 4 Oct 2025 13:36:16 -0700 Subject: [PATCH 351/378] update Dockerfile --- .dockerignore | 1 + Dockerfile | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index 2a57ffc73f..9b44aabadf 100644 --- a/.dockerignore +++ b/.dockerignore @@ -24,4 +24,5 @@ hie.log out* prof-data tags +tmp tmp* diff --git a/Dockerfile b/Dockerfile index 4ea3f03289..4f4127a32a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,8 +46,8 @@ # support this we would have to define dedicated runtime images and build # images. -ARG UBUNTU_VERSION=22.04 -ARG GHC_VERSION=9.10.1 +ARG UBUNTU_VERSION=24.04 +ARG GHC_VERSION=9.12.2 ARG PROJECT_NAME=chainweb # ############################################################################ # @@ -68,10 +68,10 @@ RUN < Date: Wed, 1 Oct 2025 13:48:34 -0400 Subject: [PATCH 352/378] Delete sql savepoints from Pact PP, use sql transactions It seems that since adding the read-only connection pool we get misuse errors from sqlite occasionally when releasing savepoints. I can't tell how exactly savepoints are implemented, and there is no sqlite documentation on the interaction between multiple readers and savepoints for read transactions and the WAL file all together, *but*, the simple policy that transactions are only begun from PactService.hs seems sufficient to give us the same guarantees as we got from savepoints anyway. Change-Id: Id000000058775f60cd5949497a34d0fb06584190 --- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 50 +++++----- src/Chainweb/Pact/Backend/Utils.hs | 93 ++++--------------- src/Chainweb/Pact/PactService.hs | 60 ++++++------ src/Chainweb/Pact/PactService/Checkpointer.hs | 49 +++++----- .../Pact/BlockHistoryMigration.hs | 5 +- 5 files changed, 96 insertions(+), 161 deletions(-) diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 32a48a692a..1d389d458c 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -45,7 +45,7 @@ -- | +v--------------------------------------------+ +v----------------------------------------------+ | -- | Pact5Db tx Pact5Db tx | -- +v------------------------------------------------------------------------------------------------------------------------+ --- SQLite tx (withSavepoint) +-- SQLite tx (withTransaction) -- (in some cases multiple blocks in tx) -- -- @@ -763,19 +763,18 @@ createVersionedTable tablename db = do setConsensusState :: SQ3.Database -> ConsensusState -> ExceptT LocatedSQ3Error IO () setConsensusState db cs = do - withSavepoint db SetConsensusSavePoint $ do - exec' db - "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ - \(?, ?, ?, ?);" - (toRow "final" $ _consensusStateFinal cs) - exec' db - "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ - \(?, ?, ?, ?);" - (toRow "safe" $ _consensusStateSafe cs) - exec' db - "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ - \(?, ?, ?, ?);" - (toRow "latest" $ _consensusStateLatest cs) + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "final" $ _consensusStateFinal cs) + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "safe" $ _consensusStateSafe cs) + exec' db + "INSERT INTO ConsensusState (blockheight, hash, payloadhash, safety) VALUES \ + \(?, ?, ?, ?);" + (toRow "latest" $ _consensusStateLatest cs) where toRow safety SyncState {..} = [ SInt $ fromIntegral @BlockHeight @Int64 _syncStateHeight @@ -816,18 +815,17 @@ getConsensusState db = do -- | Create all tables that exist pre-genesis -- TODO: migrate this logic to the checkpointer itself? initSchema :: SQLiteEnv -> IO () -initSchema sql = - withSavepoint sql InitSchemaSavePoint $ throwOnDbError $ do - createConsensusStateTable - createBlockHistoryTable - createTableCreationTable - createTableMutationTable - createTransactionIndexTable - create (toUtf8 $ Pact.renderDomain Pact.DKeySets) - create (toUtf8 $ Pact.renderDomain Pact.DModules) - create (toUtf8 $ Pact.renderDomain Pact.DNamespaces) - create (toUtf8 $ Pact.renderDomain Pact.DDefPacts) - create (toUtf8 $ Pact.renderDomain Pact.DModuleSource) +initSchema sql = throwOnDbError $ do + createConsensusStateTable + createBlockHistoryTable + createTableCreationTable + createTableMutationTable + createTransactionIndexTable + create (toUtf8 $ Pact.renderDomain Pact.DKeySets) + create (toUtf8 $ Pact.renderDomain Pact.DModules) + create (toUtf8 $ Pact.renderDomain Pact.DNamespaces) + create (toUtf8 $ Pact.renderDomain Pact.DDefPacts) + create (toUtf8 $ Pact.renderDomain Pact.DModuleSource) where create tablename = do createVersionedTable tablename sql diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index bcdb953a28..b4782565f3 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -37,14 +37,12 @@ module Chainweb.Pact.Backend.Utils , rewindDbToBlock , rewindDbToGenesis , getEndTxId - -- * Savepoints - , withSavepoint - , SavepointName(..) + -- * Transactions + , withTransaction -- * SQLite conversions and assertions , toUtf8 , fromUtf8 , asStringUtf8 - , convSavepointName -- * SQLite runners , withSqliteDb , withReadSqlitePool @@ -147,88 +145,29 @@ asStringUtf8 = toUtf8 . asString -- -------------------------------------------------------------------------- -- -- -withSavepoint +withTransaction :: (HasCallStack, MonadMask m, MonadIO m) => SQLiteEnv - -> SavepointName -> m a -> m a -withSavepoint db name action = fmap fst $ generalBracket - (liftIO $ beginSavepoint db name) +withTransaction db action = fmap fst $ generalBracket + (liftIO $ beginTransaction db) (\_ -> liftIO . \case - ExitCaseSuccess {} -> commitSavepoint db name - _ -> abortSavepoint db name + ExitCaseSuccess {} -> commitTransaction db + _ -> rollbackTransaction db ) $ \_ -> action -beginSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO () -beginSavepoint db name = - throwOnDbError $ exec_ db $ "SAVEPOINT [" <> convSavepointName name <> "];" +beginTransaction :: HasCallStack => SQLiteEnv -> IO () +beginTransaction db = + throwOnDbError $ exec_ db $ "BEGIN TRANSACTION;" -commitSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO () -commitSavepoint db name = - throwOnDbError $ exec_ db $ "RELEASE SAVEPOINT [" <> convSavepointName name <> "];" +commitTransaction :: HasCallStack => SQLiteEnv -> IO () +commitTransaction db = + throwOnDbError $ exec_ db $ "COMMIT TRANSACTION;" -convSavepointName :: SavepointName -> SQ3.Utf8 -convSavepointName = toUtf8 . toText - --- | @rollbackSavepoint n@ rolls back all database updates since the most recent --- savepoint with the name @n@ and restarts the transaction. --- --- /NOTE/ that the savepoint is not removed from the savepoint stack. In order to --- also remove the savepoint @rollbackSavepoint n >> commitSavepoint n@ can be --- used to release the (empty) transaction. --- --- Cf. for details about --- savepoints. --- -rollbackSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO () -rollbackSavepoint db name = - throwOnDbError $ exec_ db $ "ROLLBACK TRANSACTION TO SAVEPOINT [" <> convSavepointName name <> "];" - --- | @abortSavepoint n@ rolls back all database updates since the most recent --- savepoint with the name @n@ and removes it from the savepoint stack. -abortSavepoint :: HasCallStack => SQLiteEnv -> SavepointName -> IO () -abortSavepoint db name = do - rollbackSavepoint db name - commitSavepoint db name - -data SavepointName - = ReadFromSavepoint - | ReadFromNSavepoint - | RestoreAndSaveSavePoint - | RewindSavePoint - | InitSchemaSavePoint - | ValidateBlockSavePoint - | SetConsensusSavePoint - | RunGenesisSavePoint - deriving (Eq, Ord, Enum, Bounded) - -instance Show SavepointName where - show = T.unpack . toText - -instance HasTextRepresentation SavepointName where - toText ReadFromSavepoint = "read-from" - toText ReadFromNSavepoint = "read-from-n" - toText RestoreAndSaveSavePoint = "restore-and-save" - toText RewindSavePoint = "rewind" - toText InitSchemaSavePoint = "init-schema" - toText ValidateBlockSavePoint = "validate-block" - toText SetConsensusSavePoint = "set-consensus" - toText RunGenesisSavePoint = "run-genesis" - {-# INLINE toText #-} - - fromText "read-from" = pure ReadFromSavepoint - fromText "read-from-n" = pure ReadFromNSavepoint - fromText "restore-and-save" = pure RestoreAndSaveSavePoint - fromText "rewind" = pure RewindSavePoint - fromText "init-schema" = pure InitSchemaSavePoint - fromText "validate-block" = pure ValidateBlockSavePoint - fromText "set-consensus" = pure SetConsensusSavePoint - fromText "run-genesis" = pure RunGenesisSavePoint - fromText t = throwM $ TextFormatException - $ "failed to decode SavepointName " <> t - <> ". Valid names are " <> T.intercalate ", " (toText @SavepointName <$> [minBound .. maxBound]) - {-# INLINE fromText #-} +rollbackTransaction :: HasCallStack => SQLiteEnv -> IO () +rollbackTransaction db = + throwOnDbError $ exec_ db $ "ROLLBACK TRANSACTION;" chainwebPragmas :: [Pact4.Pragma] chainwebPragmas = diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 2d7b9b28eb..ae52e7786d 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -52,7 +52,7 @@ import Chainweb.Miner.Pact import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact import Chainweb.Pact.Backend.Types -import Chainweb.Pact.Backend.Utils (withSavepoint, SavepointName (..)) +import Chainweb.Pact.Backend.Utils (withTransaction) import Chainweb.Pact.NoCoinbase qualified as Pact import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer import Chainweb.Pact.PactService.ExecBlock @@ -140,7 +140,8 @@ withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb read traverse_ cancel refresherThread ) - liftIO $ ChainwebPactDb.initSchema readWriteSqlenv + liftIO $ withTransaction readWriteSqlenv $ + ChainwebPactDb.initSchema readWriteSqlenv candidatePdb <- liftIO MapTable.emptyTable moduleInitCacheVar <- liftIO $ newMVar mempty @@ -194,7 +195,7 @@ runGenesisIfNeeded -> ServiceEnv tbl -> IO () runGenesisIfNeeded logger serviceEnv = do - withSavepoint (_psReadWriteSql serviceEnv) RunGenesisSavePoint $ do + withTransaction (_psReadWriteSql serviceEnv) $ do latestBlock <- fmap _consensusStateLatest <$> Checkpointer.getConsensusState (_psReadWriteSql serviceEnv) when (maybe True (isGenesisBlockHeader' cid . Parent . _syncStateBlockHash) latestBlock) $ do logFunctionText logger Debug "running genesis" @@ -262,7 +263,7 @@ execNewGenesisBlock -> ServiceEnv tbl -> Vector Pact.Transaction -> IO PayloadWithOutputs -execNewGenesisBlock logger serviceEnv newTrans = do +execNewGenesisBlock logger serviceEnv newTrans = withTransaction (_psReadWriteSql serviceEnv) $ do let cid = _chainId serviceEnv let parentCreationTime = Parent (implicitVersion ^?! versionGenesis . genesisTime . atChain cid) historicalBlock <- Checkpointer.readFrom logger cid (_psReadWriteSql serviceEnv) parentCreationTime @@ -328,7 +329,7 @@ execReadOnlyReplay logger serviceEnv blocks = do let isPayloadEmpty = V.null (_payloadWithOutputsTransactions payload) let isUpgradeBlock = isJust $ implicitVersion ^? versionUpgrades . atChain cid . ix (_evaluationCtxCurrentHeight evalCtx) if isPayloadEmpty && not isUpgradeBlock - then Pool.withResource readSqlPool $ \sql -> do + then Pool.withResource readSqlPool $ \sql -> withTransaction sql $ do hist <- Checkpointer.readFrom logger cid @@ -372,7 +373,7 @@ execLocal logger serviceEnv cwtx preflight sigVerify rdepth = do pure $ Historical LocalTimeout where - doLocal = Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> do + doLocal = Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> withTransaction sql $ do fakeNewBlockCtx <- liftIO Checkpointer.mkFakeParentCreationTime Checkpointer.readFromNthParent logger cid sql fakeNewBlockCtx (fromIntegral rewindDepth) $ Checkpointer.readPact5 "Pact 4 cannot execute local calls" $ \blockEnv blockHandle -> do @@ -505,7 +506,7 @@ syncToFork -> ForkInfo -> IO ConsensusState syncToFork logger serviceEnv hints forkInfo = do - (rewoundTxs, validatedTxs, newConsensusState) <- withSavepoint sql ValidateBlockSavePoint $ do + (rewoundTxs, validatedTxs, newConsensusState) <- withTransaction sql $ do pactConsensusState <- fromJuste <$> Checkpointer.getConsensusState sql let atTarget = _syncStateBlockHash (_consensusStateLatest pactConsensusState) == @@ -719,13 +720,15 @@ refreshPayloads logger serviceEnv = do logFunctionText logger Debug $ "refreshing payloads for " <> brief (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) - maybeRefreshedBlockInProgress <- Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> - Checkpointer.readFrom logger cid sql - (_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress) - (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) - $ Checkpointer.readPact5 "Pact 4 cannot make new blocks" $ \blockEnv _bh -> do - let dbEnv = view psBlockDbEnv blockEnv - continueBlock logger serviceEnv dbEnv blockInProgress + maybeRefreshedBlockInProgress <- + Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> + withTransaction sql $ + Checkpointer.readFrom logger cid sql + (_bctxParentCreationTime $ _blockInProgressBlockCtx blockInProgress) + (_bctxParentRankedBlockHash $ _blockInProgressBlockCtx blockInProgress) + $ Checkpointer.readPact5 "Pact 4 cannot make new blocks" $ \blockEnv _bh -> do + let dbEnv = view psBlockDbEnv blockEnv + continueBlock logger serviceEnv dbEnv blockInProgress case maybeRefreshedBlockInProgress of -- the block's parent was removed NoHistory -> logOutraced @@ -788,19 +791,20 @@ execPreInsertCheckReq logger serviceEnv txs = do let requestKeys = V.map Pact.cmdToRequestKey txs logFunctionText logger Info $ "(pre-insert check " <> sshow requestKeys <> ")" fakeParentCreationTime <- Checkpointer.mkFakeParentCreationTime - let act sql = Checkpointer.readFromLatest logger cid sql fakeParentCreationTime $ Checkpointer.PactRead - { pact5Read = \blockEnv bh -> do - forM txs $ \tx -> - fmap (either Just (\_ -> Nothing)) $ runExceptT $ do - -- it's safe to use initialBlockHandle here because it's - -- only used to check for duplicate pending txs in a block - () <- mapExceptT liftIO - $ Pact.validateParsedChainwebTx logger blockEnv tx - evalStateT (attemptBuyGas blockEnv tx) bh - -- pessimistically, if we're catching up and not even past the Pact - -- 5 activation, just badlist everything as in-the-future. - , pact4Read = \_ -> return $ Just InsertErrorTimeInFuture <$ txs - } + let act sql = withTransaction sql $ + Checkpointer.readFromLatest logger cid sql fakeParentCreationTime $ Checkpointer.PactRead + { pact5Read = \blockEnv bh -> do + forM txs $ \tx -> + fmap (either Just (\_ -> Nothing)) $ runExceptT $ do + -- it's safe to use initialBlockHandle here because it's + -- only used to check for duplicate pending txs in a block + () <- mapExceptT liftIO + $ Pact.validateParsedChainwebTx logger blockEnv tx + evalStateT (attemptBuyGas blockEnv tx) bh + -- pessimistically, if we're catching up and not even past the Pact + -- 5 activation, just badlist everything as in-the-future. + , pact4Read = \_ -> return $ Just InsertErrorTimeInFuture <$ txs + } Pool.withResource (view psReadSqlPool serviceEnv) $ \sql -> timeoutYield timeoutLimit (act sql) >>= \case Just r -> do @@ -852,7 +856,7 @@ execLookupPactTxs logger serviceEnv confDepth txs = do where depth = maybe 0 (fromIntegral . _confirmationDepth) confDepth cid = _chainId serviceEnv - go ctx = Pool.withResource (_psReadSqlPool serviceEnv) $ \sql -> + go ctx = Pool.withResource (_psReadSqlPool serviceEnv) $ \sql -> withTransaction sql $ Checkpointer.readFromNthParent logger cid sql ctx depth -- not sure about this, disallows looking up pact txs if we haven't -- caught up to pact 5 diff --git a/src/Chainweb/Pact/PactService/Checkpointer.hs b/src/Chainweb/Pact/PactService/Checkpointer.hs index 10039fa6a8..bd653c8589 100644 --- a/src/Chainweb/Pact/PactService/Checkpointer.hs +++ b/src/Chainweb/Pact/PactService/Checkpointer.hs @@ -136,24 +136,23 @@ readFromNthParent -> PactRead a -> IO (Historical a) readFromNthParent logger cid sql parentCreationTime n doRead = do - withSavepoint sql ReadFromNSavepoint $ do - latest <- - _consensusStateLatest . fromMaybe (error "readFromNthParent is illegal to call before genesis") - <$> getConsensusState sql - if genesisHeight cid + fromIntegral @Word @BlockHeight n > _syncStateHeight latest - then do - logFunctionText logger Warn $ "readFromNthParent asked to rewind beyond genesis, to " - <> sshow (int (_syncStateHeight latest) - int n :: Integer) - return NoHistory - else do - let targetHeight = _syncStateHeight latest - fromIntegral @Word @BlockHeight n - lookupBlockWithHeight sql targetHeight >>= \case - -- this case for shallow nodes without enough history - Nothing -> do - logFunctionText logger Warn "readFromNthParent asked to rewind beyond known blocks" - return NoHistory - Just nthBlock -> - readFrom logger cid sql parentCreationTime (Parent nthBlock) doRead + latest <- + _consensusStateLatest . fromMaybe (error "readFromNthParent is illegal to call before genesis") + <$> getConsensusState sql + if genesisHeight cid + fromIntegral @Word @BlockHeight n > _syncStateHeight latest + then do + logFunctionText logger Warn $ "readFromNthParent asked to rewind beyond genesis, to " + <> sshow (int (_syncStateHeight latest) - int n :: Integer) + return NoHistory + else do + let targetHeight = _syncStateHeight latest - fromIntegral @Word @BlockHeight n + lookupBlockWithHeight sql targetHeight >>= \case + -- this case for shallow nodes without enough history + Nothing -> do + logFunctionText logger Warn "readFromNthParent asked to rewind beyond known blocks" + return NoHistory + Just nthBlock -> + readFrom logger cid sql parentCreationTime (Parent nthBlock) doRead -- read-only rewind to a target block. -- if that target block is missing, return Nothing. @@ -175,7 +174,7 @@ readFrom logger cid sql parentCreationTime parent pactRead = do , _bctxMinerReward = blockMinerReward (childBlockHeight cid parent) , _bctxChainId = cid } - liftIO $ withSavepoint sql ReadFromSavepoint $ do + liftIO $ do !latestHeader <- maybe (genesisRankedParentBlockHash cid) (Parent . _syncStateRankedBlockHash . _consensusStateLatest) <$> ChainwebPactDb.throwOnDbError (ChainwebPactDb.getConsensusState sql) -- is the parent the latest header, i.e., can we get away without rewinding? @@ -228,8 +227,7 @@ rewindTo -> Parent RankedBlockHash -> IO () rewindTo cid sql ancestor = do - withSavepoint sql RewindSavePoint $ - void $ PactDb.rewindDbTo cid sql ancestor + void $ PactDb.rewindDbTo cid sql ancestor data PactRead a = PactRead @@ -283,11 +281,10 @@ restoreAndSave -> NonEmpty (RunnableBlock m q) -> m q restoreAndSave logger cid sql parent blocks = do - withSavepoint sql RestoreAndSaveSavePoint $ do - -- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above. - startTxId <- liftIO $ PactDb.rewindDbTo cid sql parent - let startBlockHeight = childBlockHeight cid parent - foldState1 (fmap executeBlock blocks) (T2 startBlockHeight startTxId) + -- TODO PP: check first if we're rewinding past "final" point? same with rewindTo above. + startTxId <- liftIO $ PactDb.rewindDbTo cid sql parent + let startBlockHeight = childBlockHeight cid parent + foldState1 (fmap executeBlock blocks) (T2 startBlockHeight startTxId) where executeBlock :: RunnableBlock m q -> T2 BlockHeight Pact.TxId -> m (q, T2 BlockHeight Pact.TxId) diff --git a/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs b/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs index f44ed9bbf0..97366beff3 100644 --- a/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs +++ b/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs @@ -80,7 +80,7 @@ migrateBlockHistoryTable logger sdb bhdb cleanup remainingRows <- readIORef remainingRowsRef let perc = (1.0 - fromIntegral @_ @Double remainingRows / fromIntegral @_ @Double nBlockHistory) * 100.0 logf Info $ "Table migration: Process remaining rows: " <> sshow remainingRows <> " ("<> sshow perc <> ")" - r <- tx $ flip S.mapM_ chunk $ \case + r <- withTransaction sdb $ flip S.mapM_ chunk $ \case rr@[SInt bh, SInt _, SBlob h] -> do let rowBlockHeight = fromIntegral bh rowBlockHash <- runGetS decodeBlockHash h @@ -116,9 +116,6 @@ migrateBlockHistoryTable logger sdb bhdb cleanup logf Info "Table migration successful" where - tx = bracket_ - (throwOnDbError $ exec_ sdb "BEGIN TRANSACTION") - (throwOnDbError $ exec_ sdb "COMMIT TRANSACTION") nTableEntries tname = throwOnDbError $ qry_ sdb ("SELECT count(*) from " <> tname) [RInt] >>= \case [[SInt n]] -> pure n _ -> error "unexpected row shape" From 6957d97cec70299f796a70ffa169fee407af5a7e Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 1 Oct 2025 17:37:08 -0700 Subject: [PATCH 353/378] improve brief instance for lists --- src/Chainweb/Core/Brief.hs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index 065c9a8104..8298428f8f 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -83,7 +83,15 @@ instance Brief a => Brief (Maybe a) where brief Nothing = "nothing" instance Brief a => Brief [a] where - brief l = "[" <> (T.intercalate "," $ brief <$> l) <> "]" + brief l + | length l < 50 = mconcat ["[", T.intercalate "," (brief <$> l), "]"] + | otherwise = mconcat + [ "[" + , T.intercalate "," (brief <$> take 20 l) + , " ... " + , T.intercalate "," (brief <$> drop (length l - 20) l) + , "]" + ] instance Brief Int where brief = sshow From 081a833edeb99a5b38c601828bafe0fa7b4213f7 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 30 Sep 2025 15:49:00 -0700 Subject: [PATCH 354/378] factor out and improve forkinfo synchronization --- chainweb.cabal | 1 + src/Chainweb/ChainValue.hs | 21 +- src/Chainweb/Chainweb/CutResources.hs | 5 +- src/Chainweb/Core/Brief.hs | 1 - src/Chainweb/CutDB.hs | 156 ++++++------- src/Chainweb/Pact/PactService.hs | 207 +++++++++-------- src/Chainweb/PayloadProvider.hs | 39 ++-- src/Chainweb/PayloadProvider/EVM.hs | 148 ++++++++++-- src/Chainweb/PayloadProvider/Pact.hs | 3 +- src/Chainweb/Sync/ForkInfo.hs | 274 +++++++++++++++++++++++ src/Chainweb/Sync/WebBlockHeaderStore.hs | 109 ++++----- src/Chainweb/TreeDB.hs | 4 - test/unit/Chainweb/Test/CutDB.hs | 2 +- 13 files changed, 657 insertions(+), 313 deletions(-) create mode 100644 src/Chainweb/Sync/ForkInfo.hs diff --git a/chainweb.cabal b/chainweb.cabal index b44dab817b..2529f980d5 100644 --- a/chainweb.cabal +++ b/chainweb.cabal @@ -273,6 +273,7 @@ library , Chainweb.SPV.RestAPI , Chainweb.SPV.RestAPI.Server , Chainweb.SPV.RestAPI.Client + , Chainweb.Sync.ForkInfo , Chainweb.Sync.WebBlockHeaderStore , Chainweb.Time , Chainweb.TreeDB diff --git a/src/Chainweb/ChainValue.hs b/src/Chainweb/ChainValue.hs index fe01f64f73..10d44ba15b 100644 --- a/src/Chainweb/ChainValue.hs +++ b/src/Chainweb/ChainValue.hs @@ -1,11 +1,10 @@ {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} @@ -31,17 +30,13 @@ module Chainweb.ChainValue ) where import Control.DeepSeq +import Chainweb.ChainId +import Chainweb.Storage.Table +import Chainweb.Utils (HasTextRepresentation(..)) import Control.Lens - import Data.Hashable - import GHC.Generics - --- internal modules - -import Chainweb.ChainId - -import Chainweb.Storage.Table +import Data.Text qualified as T -- -------------------------------------------------------------------------- -- -- Tag Values With a ChainId @@ -63,6 +58,12 @@ instance TraversableWithIndex ChainId ChainValue where instance FoldableWithIndex ChainId ChainValue instance FunctorWithIndex ChainId ChainValue +instance HasTextRepresentation a => HasTextRepresentation (ChainValue a) where + toText (ChainValue cid a) = toText cid <> ":" <> toText a + fromText t = case T.breakOn ":" t of + (c, r) -> ChainValue <$> fromText c <*> fromText (T.drop 1 r) + {-# INLINE toText #-} + -- | If a type is already an instance of 'IsCasValue', adding the chain does -- preserve this property. By also wrapping the key it is possible to shard -- the CAS by chain value. diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 62de120968..79f3f08d9c 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -1,5 +1,6 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} @@ -7,8 +8,6 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Chainweb.CutResources @@ -62,8 +61,6 @@ import Chainweb.PayloadProvider import Data.Text qualified as T import P2P.Session (P2pSession) import P2P.Node.PeerDB (PeerDb) -import Chainweb.Utils -import Chainweb.BlockHeaderDB.PruneForks qualified as PruneForks -- -------------------------------------------------------------------------- -- -- Cuts Resources diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index 8298428f8f..56cf14b452 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -5,7 +5,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE UndecidableInstances #-} -- | diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 88db9c244a..e37099ce7f 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -95,18 +95,17 @@ import Chainweb.BlockHeaderDB.Internal import Chainweb.BlockHeight import Chainweb.BlockWeight import Chainweb.ChainId -import Chainweb.Core.Brief import Chainweb.Cut import Chainweb.Cut.Create import Chainweb.Cut.CutHashes import Chainweb.Graph import Chainweb.Logger -import Chainweb.Parent import Chainweb.PayloadProvider import Chainweb.Ranked import Chainweb.Storage.Table import Chainweb.Storage.Table.HashMap import Chainweb.Storage.Table.RocksDB +import Chainweb.Sync.ForkInfo import Chainweb.Sync.WebBlockHeaderStore import Chainweb.TreeDB import Chainweb.Utils hiding (Codec, check) @@ -481,110 +480,67 @@ startCutDb config logger headerStore providers cutHashesStore = mask_ $ do blockHeaders <- iforM rankedBlockHashes (lookupRankedWebBlockHeaderDb wbhdb) return $ unsafeMkCut blockHeaders +-- | Sync all configured payload providers with consensus. +-- synchronizeProviders :: (Logger logger, HasVersion) - => logger -> WebBlockHeaderDb -> ChainMap ConfiguredPayloadProvider -> Cut -> IO Cut + => logger + -> WebBlockHeaderDb + -> ChainMap ConfiguredPayloadProvider + -> Cut + -> IO Cut synchronizeProviders logger wbh providers c = do let startHeaders = HM.unionWith - (\startHeader _genesisHeader -> startHeader) + const (_cutHeaders c) (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) - syncsSuccessful <- mapConcurrently (runMaybeT . syncOne False) startHeaders + syncsSuccessful <- mapConcurrently (runMaybeT . syncOne) startHeaders + + logFunctionText logger Info $ "finished synchronizing payload all providers" + <> "; failed: " <> sshow (length (HM.filter isNothing syncsSuccessful)) + if all isJust syncsSuccessful - then return c - else do + then do + return c + else do -- try to recover from the fork automatically by removing ~`diameter` -- blocks from the cut let recoveryHeight = max (int (diameter (chainGraphAt maxBound))) (_cutMinHeight c) - int (diameter (chainGraphAt maxBound)) recoveryCut <- limitCut wbh recoveryHeight c let recoveryHeaders = HM.unionWith - (\recoveryHeader _genesisHeader -> recoveryHeader) + const (_cutHeaders recoveryCut) (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) - mapConcurrently_ (runMaybeT . syncOne True) recoveryHeaders + mapConcurrently_ (runMaybeT . syncOne) recoveryHeaders return recoveryCut where - syncOne :: Bool -> BlockHeader -> MaybeT IO () - syncOne recovery hdr = case providers ^?! atChain (_chainId hdr) of + syncOne :: BlockHeader -> MaybeT IO () + syncOne hdr = case providers ^?! atChain (_chainId hdr) of ConfiguredPayloadProvider provider -> do let pLogger = providerLogger provider . chainLogger hdr $ logger - let pLog l = liftIO . logFunctionText pLogger l - pLog Info $ - (if recovery then "recover" else "sync") <> " payload provider to " + let pLog :: MonadIO m => LogLevel -> Text -> m () + pLog l = liftIO . logFunctionText pLogger l + pLog Info $ "syncing payload provider to " <> sshow (view blockHeight hdr) - <> ":" <> sshow (view blockHash hdr) - finfo <- liftIO $ forkInfoForHeader wbh hdr Nothing Nothing - pLog Debug $ "syncToBlock with fork info " <> sshow finfo - r <- liftIO (syncToBlock provider Nothing finfo) `catch` \(e :: SomeException) -> do - pLog Warn $ "syncToBlock for " <> sshow finfo <> " failed with :" <> sshow e + <> "." <> toText (view blockHash hdr) + finfo <- liftIO $ forkInfoForHeader wbh hdr Nothing Nothing True + pLog Debug $ "syncToBlock with fork info " <> encodeToText finfo + + -- FIXME does this really cover all failure scenarios? What if + -- the payload provider is only slow in syncing? Also the protocol + -- allows partial results -- in which case we should retry. + bhdb <- liftIO $ getWebBlockHeaderDb wbh cid + liftIO (resolveForkInfo pLog bhdb provider Nothing finfo) `catch` \(e :: SomeException) -> do + pLog Warn $ "resolveFork for failed" + <> "; finfo: " <> encodeToText finfo + <> "; failure: " <> sshow e + -- pLog Error "It is recommend using --initial-block-height-limit to recover from fork manually." empty + pLog Info $ "payload provider synced to " + <> sshow (view blockHeight hdr) + <> "." <> toText (view blockHash hdr) - -- Check result of syncToBlock - -- - if r == _forkInfoTargetState finfo - - -- We are done - -- - then return () - - -- We are not in sync and need to resolve (could be a fork a - -- payload provider that is not caught up yet) - -- - else do - pLog Info - $ "resolving fork on startup, from " <> brief r - <> " to " <> brief (_forkInfoTargetState finfo) - - -- Query the trace from the fork point to the target block - -- - bhdb <- liftIO $ getWebBlockHeaderDb wbh cid - let ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest r - ppBlock <- liftIO (lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH)) `catch` \case - e@(TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do - pLog Warn $ "PP block is missing: " <> brief ppRBH <> ", error: " <> sshow e - MaybeT $ return Nothing - _ -> empty - - -- FIXME: this stream can be very long. We should limit it and - -- proceed iteratively if necessary. - -- Ideally, we check the length based on block heights before - -- we compute the full trace below. We also have to be careful, - -- that we compute the fork point only once and iterate from - -- there, otherwise the complexity would become quadratic. - (forkBlocksDescendingStream S.:> forkPoint) <- liftIO $ - S.toList $ branchDiff_ bhdb ppBlock hdr - let forkBlocksAscending = reverse $ snd $ partitionHereThere forkBlocksDescendingStream - let newTrace = zipWith - (\prent child -> - ConsensusPayload (view blockPayloadHash child) Nothing <$ - blockHeaderToEvaluationCtx (Parent prent)) - (forkPoint : forkBlocksAscending) - forkBlocksAscending - - let newForkInfo = finfo - { _forkInfoTrace = newTrace - , _forkInfoBasePayloadHash = - Parent (view blockPayloadHash forkPoint) - } - - -- if this fails, there is no way for the payload provider - -- to sync to the block without using the ordinary cut pipeline. - -- so, we don't care. - r' <- liftIO $ syncToBlock provider Nothing newForkInfo - let syncSucceeded = _forkInfoTargetState finfo == r' - unless syncSucceeded $ do - liftIO $ logFunctionText logger (if recovery then Error else Warn) - $ "unexpected " <> (if recovery then "recovery" else "initial sync") <> " result state" - <> "; expected: " <> brief (_forkInfoTargetState finfo) - <> "; actual: " <> brief r - <> "; PP latest block: " <> brief ppBlock - <> "; target block: " <> brief hdr - <> "; fork blocks: " <> brief forkBlocksAscending - <> if recovery - then ". recommend using --initial-block-height-limit to recover from fork manually." - else "" - empty DisabledPayloadProvider -> do liftIO $ logFunctionText logger Info $ "payload provider disabled, not synced, on chain: " <> toText (_chainId hdr) @@ -742,31 +698,41 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar cutPru loggCutId logFun Info newCut $ "writing cut at bh " <> sshow resultCutMaxHeight casInsert cutHashesStore (cutToCutHashes Nothing resultCut) + -- ensure that payload providers are in sync with the *merged* -- cut, so that they produce payloads on the correct parents. iforM_ (_cutMap resultCut) $ \cid bh -> do + let clog l = loggChainId logFun l cid -- avoid asking for syncToBlock when we know that we're already -- in sync, otherwise some payload providers misbehave. when (Just bh /= curCut ^? ixg cid) $ case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do - finfo <- forkInfoForHeader hdrStore bh Nothing Nothing - r <- syncToBlock provider Nothing finfo - unless (r == _forkInfoTargetState finfo) $ do - error $ "unexpected result state" - <> "; expected: " <> sshow (_forkInfoTargetState finfo) - <> "; actual: " <> sshow r + -- During this final sync we also enable payload production. + finfo <- forkInfoForHeader hdrStore bh Nothing Nothing True + + -- Note, that this really should be super quick and + -- should never fail. + -- + -- FIXME: Can't we go to the merge cut directly? + -- FIXME: we could we trigger this with only a + -- single node in the system? + clog Info "Syncing paylooad provider with merged cut" + resolveForkInfo clog (hdrStore ^?! ixg cid) provider Nothing finfo `catch` + -- FIXME calling error is not OK! + \(e :: SomeException) -> error + $ "Failed to sync to merge cut (on chain " <> sshow cid <> "): " <> sshow e _ -> return () let cutDiff = cutDiffToTextShort curCut resultCut let currentCutIdMsg = T.unwords [ "current cut is now" , cutIdToTextShort (_cutId resultCut) <> "," - , "diff:" + , "diff:" -- ??? ] let catOverflowing x xs = if length xs == 1 then T.unwords (x : xs) - else T.intercalate "\n" (x : (map (" " <>) xs)) + else T.intercalate "\n" (x : map (" " <>) xs) logFun @T.Text Info $ catOverflowing currentCutIdMsg cutDiff atomically $ writeTVar cutVar resultCut ) @@ -949,7 +915,7 @@ cutHashesToBlockHeaderMap conf logfun headerStore providers hs = Nothing -> "from " <> maybe "unknown origin" (\p -> "origin " <> toText p) origin Just _ -> "which was locally mined - the mining loop will stall until unstuck by another miner" - logfun (maybe Warn (\_ -> Error) (_cutHashesLocalPayload hs)) + logfun (maybe Warn (const Error) (_cutHashesLocalPayload hs)) $ "Timeout while processing cut " <> cutIdToTextShort hsid <> " at height " <> sshow (_cutHashesHeight hs) @@ -1084,3 +1050,7 @@ getQueueStats db = QueueStats -- Logging loggCutId :: HasCutId c => LogFunction -> LogLevel -> c -> T.Text -> IO () loggCutId logFun l c msg = logFun @T.Text l $ "cut " <> cutIdToTextShort (_cutId c) <> ": " <> msg + +-- Logging +loggChainId :: HasChainId c => LogFunction -> LogLevel -> c -> T.Text -> IO () +loggChainId logFun l c msg = logFun @T.Text l $ "chain " <> toText (_chainId c) <> ": " <> msg diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index ae52e7786d..5d53345fe3 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -510,112 +510,129 @@ syncToFork logger serviceEnv hints forkInfo = do pactConsensusState <- fromJuste <$> Checkpointer.getConsensusState sql let atTarget = _syncStateBlockHash (_consensusStateLatest pactConsensusState) == - _latestBlockHash (forkInfo._forkInfoTargetState) - -- check if some past block had the target as its parent; if so, that - -- means we can rewind to it - latestBlockRewindable <- - isJust <$> Checkpointer.lookupBlockHash sql (_latestBlockHash forkInfo._forkInfoTargetState) + _latestBlockHash forkInfo._forkInfoTargetState + if atTarget - then do + then do -- no work to do at all except set consensus state -- TODO PP: disallow rewinding final? logFunctionText logger Debug $ "no work done to move to " <> brief forkInfo._forkInfoTargetState Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState return (mempty, mempty, forkInfo._forkInfoTargetState) - else if latestBlockRewindable - then do - -- we just have to rewind and set the final + safe blocks - -- TODO PP: disallow rewinding final? - logFunctionText logger Debug $ "pure rewind to " <> brief forkInfo._forkInfoTargetState - rewoundTxs <- getRewoundTxs (Parent $ forkInfo._forkInfoTargetState._consensusStateLatest._syncStateHeight) - Checkpointer.rewindTo cid sql (Parent $ _syncStateRankedBlockHash (_consensusStateLatest forkInfo._forkInfoTargetState)) - Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState - return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) - else do - let traceBlockHashesAscending = - drop 1 (unwrapParent . _evaluationCtxRankedParentHash <$> forkInfo._forkInfoTrace) <> - [_syncStateRankedBlockHash forkInfo._forkInfoTargetState._consensusStateLatest] - logFunctionText logger Debug $ - "playing blocks to move to " <> brief forkInfo._forkInfoTargetState - <> " using trace blocks " <> brief traceBlockHashesAscending - findForkChainAscending (reverse $ zip forkInfo._forkInfoTrace traceBlockHashesAscending) >>= \case - Nothing -> do - logFunctionText logger Info $ - "impossible to move to " <> brief forkInfo._forkInfoTargetState - <> " from " <> brief pactConsensusState <> " with trace " <> brief (align forkInfo._forkInfoTrace traceBlockHashesAscending) - -- error: we have no way to get to the target block. just report - -- our current state and do nothing else. - return (mempty, mempty, pactConsensusState) - Just forkChainBottomToTop -> do - logFunctionText logger Debug $ "fork chain found: " <> brief forkChainBottomToTop - rewoundTxs <- getRewoundTxs (Parent $ forkInfo._forkInfoTargetState._consensusStateLatest._syncStateHeight) - -- the happy case: we can find a way to get to the target block - -- look up all of the payloads to see if we've run them before - -- even then we still have to run them, because they aren't in the checkpointer - knownPayloads <- liftIO $ - tableLookupBatch' pdb (each . _2) ((\e -> (e, _evaluationCtxRankedPayloadHash $ fst e)) <$> forkChainBottomToTop) - - let unknownPayloads = NEL.filter (isNothing . snd) knownPayloads - when (not (null unknownPayloads)) - $ logFunctionText logger Debug $ "unknown blocks in context: " <> sshow (length unknownPayloads) - - runnableBlocks <- forM knownPayloads $ \((evalCtx, rankedBHash), maybePayload) -> do - logFunctionText logger Debug $ "running block: " <> brief rankedBHash - payload <- case maybePayload of - -- fetch payload if missing - Nothing -> getPayloadForContext logger serviceEnv hints evalCtx - Just payload -> return payload - let expectedPayloadHash = _consensusPayloadHash $ _evaluationCtxPayload evalCtx - let blockCtx = blockCtxOfEvaluationCtx cid evalCtx - if guardCtx pact5 blockCtx - then - return $ - (Checkpointer.Pact5RunnableBlock $ \chainwebPactDb -> do - (_, pwo, validatedTxs) <- - Pact.execExistingBlock logger serviceEnv - (BlockEnv blockCtx chainwebPactDb) - (CheckablePayload expectedPayloadHash payload) - -- add payload immediately after executing the block, because this is when we learn it's valid - liftIO $ addNewPayload - (_payloadStoreTable $ _psPdb serviceEnv) - (_evaluationCtxCurrentHeight evalCtx) - pwo - return $ (DList.singleton validatedTxs, (_ranked rankedBHash, expectedPayloadHash)) - ) - else - return $ - (Checkpointer.Pact4RunnableBlock $ \blockDbEnv -> do - (_, pwo) <- - Pact4.execBlock logger serviceEnv - (Pact4.BlockEnv blockCtx blockDbEnv) - (CheckablePayload expectedPayloadHash payload) - -- add payload immediately after executing the block, because this is when we learn it's valid - liftIO $ addNewPayload - (_payloadStoreTable $ _psPdb serviceEnv) - (_evaluationCtxCurrentHeight evalCtx) - pwo - -- don't remove pact 4 txs from the mempool, who cares when we can't make Pact 4 blocks anymore? - return $ (mempty, (_ranked rankedBHash, expectedPayloadHash)) - ) + else do + -- check if some past block had the target as its parent; if so, that + -- means we can rewind to it + -- + -- FIXME: why the parent? Why not the block itself? + -- + latestBlockRewindable <- + isJust <$> Checkpointer.lookupBlockHash sql (_latestBlockHash forkInfo._forkInfoTargetState) + + if latestBlockRewindable + then do + -- we just have to rewind and set the final + safe blocks + -- TODO PP: disallow rewinding final? + logFunctionText logger Debug $ "pure rewind to " <> brief forkInfo._forkInfoTargetState + rewoundTxs <- getRewoundTxs (Parent forkInfo._forkInfoTargetState._consensusStateLatest._syncStateHeight) + Checkpointer.rewindTo cid sql (Parent $ _syncStateRankedBlockHash (_consensusStateLatest forkInfo._forkInfoTargetState)) + Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState + return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) + else do + let traceBlockHashesAscending = + + -- Why do we drop the first entry? That seems fishy. + drop 1 (unwrapParent . _evaluationCtxRankedParentHash <$> forkInfo._forkInfoTrace) <> + [_syncStateRankedBlockHash forkInfo._forkInfoTargetState._consensusStateLatest] + + -- FIXME: we sometimes get stuck in a loop with a fork into trace + -- that is too short, i.e. the forkpoint is too far ahead. + + logFunctionText logger Debug $ "playing blocks" + <> "; from: " <> brief pactConsensusState + <> "; target: " <> brief forkInfo._forkInfoTargetState + <> "; trace: " <> brief traceBlockHashesAscending + + findForkChainAscending (reverse $ zip forkInfo._forkInfoTrace traceBlockHashesAscending) >>= \case + + Nothing -> do + logFunctionText logger Info $ + "impossible to move" + <> "; from: " <> brief pactConsensusState + <> "; target: " <> brief forkInfo._forkInfoTargetState + <> "; trace: " <> brief (align forkInfo._forkInfoTrace traceBlockHashesAscending) + -- error: we have no way to get to the target block. just report + -- our current state and do nothing else. + return (mempty, mempty, pactConsensusState) + + Just forkChainBottomToTop -> do + logFunctionText logger Debug $ "fork chain found: " <> brief forkChainBottomToTop + rewoundTxs <- getRewoundTxs (Parent forkInfo._forkInfoTargetState._consensusStateLatest._syncStateHeight) + -- the happy case: we can find a way to get to the target block + -- look up all of the payloads to see if we've run them before + -- even then we still have to run them, because they aren't in the checkpointer + knownPayloads <- liftIO $ + tableLookupBatch' pdb (each . _2) ((\e -> (e, _evaluationCtxRankedPayloadHash $ fst e)) <$> forkChainBottomToTop) + + let unknownPayloads = NEL.filter (isNothing . snd) knownPayloads + unless (null unknownPayloads) + $ logFunctionText logger Debug $ "unknown blocks in context: " <> sshow (length unknownPayloads) + + runnableBlocks <- forM knownPayloads $ \((evalCtx, rankedBHash), maybePayload) -> do + logFunctionText logger Debug $ "running block: " <> brief rankedBHash + payload <- case maybePayload of + -- fetch payload if missing + Nothing -> getPayloadForContext logger serviceEnv hints evalCtx + Just payload -> return payload + let expectedPayloadHash = _consensusPayloadHash $ _evaluationCtxPayload evalCtx + let blockCtx = blockCtxOfEvaluationCtx cid evalCtx + if guardCtx pact5 blockCtx + then + return $ + Checkpointer.Pact5RunnableBlock $ \chainwebPactDb -> do + (_, pwo, validatedTxs) <- + Pact.execExistingBlock logger serviceEnv + (BlockEnv blockCtx chainwebPactDb) + (CheckablePayload expectedPayloadHash payload) + -- add payload immediately after executing the block, because this is when we learn it's valid + liftIO $ addNewPayload + (_payloadStoreTable $ _psPdb serviceEnv) + (_evaluationCtxCurrentHeight evalCtx) + pwo + return $ (DList.singleton validatedTxs, (_ranked rankedBHash, expectedPayloadHash)) + else + return $ + Checkpointer.Pact4RunnableBlock $ \blockDbEnv -> do + (_, pwo) <- + Pact4.execBlock logger serviceEnv + (Pact4.BlockEnv blockCtx blockDbEnv) + (CheckablePayload expectedPayloadHash payload) + -- add payload immediately after executing the block, because this is when we learn it's valid + liftIO $ addNewPayload + (_payloadStoreTable $ _psPdb serviceEnv) + (_evaluationCtxCurrentHeight evalCtx) + pwo + -- don't remove pact 4 txs from the mempool, who cares when we can't make Pact 4 blocks anymore? + return $ (mempty, (_ranked rankedBHash, expectedPayloadHash)) + + runExceptT (Checkpointer.restoreAndSave logger cid sql + (knownPayloads ^. head1 . _1 . _1 . to _evaluationCtxRankedParentHash) + runnableBlocks + ) >>= \case + Left err -> do + logFunctionText logger Error $ "Error in execValidateBlock: " <> sshow err + return (mempty, mempty, pactConsensusState) + Right (DList.toList -> blockResults) -> do + let validatedTxs = msum blockResults + Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState + return (rewoundTxs, validatedTxs, forkInfo._forkInfoTargetState) - runExceptT (Checkpointer.restoreAndSave logger cid sql - (knownPayloads ^. head1 . _1 . _1 . to _evaluationCtxRankedParentHash) - runnableBlocks - ) >>= \case - Left err -> do - logFunctionText logger Error $ "Error in execValidateBlock: " <> sshow err - return (mempty, mempty, pactConsensusState) - Right (DList.toList -> blockResults) -> do - let validatedTxs = msum blockResults - Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState - return (rewoundTxs, validatedTxs, forkInfo._forkInfoTargetState) liftIO $ mpaProcessFork memPoolAccess (V.concat rewoundTxs, validatedTxs) case forkInfo._forkInfoNewBlockCtx of Just newBlockCtx | Just _ <- _psMiner serviceEnv , pact5 cid (_rankedHeight (_latestRankedBlockHash newConsensusState)) , _syncStateBlockHash (_consensusStateLatest newConsensusState) == - _latestBlockHash (forkInfo._forkInfoTargetState) -> do + _latestBlockHash forkInfo._forkInfoTargetState -> do -- if we're at the target block we were sent, and we were -- told to start mining, we produce an empty block -- immediately. then we set up a separate thread @@ -636,7 +653,7 @@ syncToFork logger serviceEnv hints forkInfo = do _ -> return () return newConsensusState - where + where memPoolAccess = view psMempoolAccess serviceEnv sql = view psReadWriteSql serviceEnv @@ -715,7 +732,7 @@ refreshPayloads refreshPayloads logger serviceEnv = do -- note that if this is empty, we wait; taking from it is the way to make us stop let logOutraced = - liftIO $ logFunctionText logger Debug $ "Refresher outraced by new block" + liftIO $ logFunctionText logger Debug "Refresher outraced by new block" (_, blockInProgress) <- liftIO $ atomically $ readTMVar payloadVar logFunctionText logger Debug $ "refreshing payloads for " <> diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index c1232dc3dc..fc34683019 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -130,8 +130,7 @@ import Chainweb.Ranked -- -------------------------------------------------------------------------- -- -- Exceptions -data PayloadProviderException - = InvalidForkInfo T.Text +newtype PayloadProviderException = InvalidForkInfo T.Text deriving (Show, Eq, Generic) instance Exception PayloadProviderException @@ -305,22 +304,9 @@ data EvaluationCtx p = EvaluationCtx -- Kda value for the EVM provider it is in Stu. Also the recipient and -- the mechanism how it is credited is provider specific. , _evaluationCtxPayload :: !p - -- -- ^ Payload hash of the block that is validated. This is used as a - -- -- checksum for the payload validation. For the last block of a ForkInfo - -- -- structure this value must match the respective value in the target - -- -- sync state. - -- -- - -- -- The BlockPayloadHash is first computed when the respective payload is - -- -- created for mining and before it is included in a block. - -- , _evaluationCtxPayloadData :: !(Maybe EncodedPayloadData) - -- -- ^ Optional external payload data. This may be - -- -- the complete, self contained block payload or it may just contain - -- -- complementary data that aids with the validation. - -- -- - -- -- The main purpose of this field is to allow consensus to gossip around - -- -- payload data along with new cuts in the P2P network, which allows for - -- -- more efficient synchronization and a reduction of block propagation - -- -- latencies. + -- ^ The 'BlockPayloadHash' for the payload to be evaluated. It may also + -- include some additional payload data. This should be of type + -- `ConsensusPayload` in most cases. } deriving (Functor, Show, Eq, Ord) @@ -410,9 +396,26 @@ instance ToJSON NewBlockCtx where {-# INLINE toEncoding #-} {-# INLINE toJSON #-} +-- | Payload in an Evaluation Context that is sent to a Payload Provider. +-- data ConsensusPayload = ConsensusPayload { _consensusPayloadHash :: !BlockPayloadHash + -- ^ Payload hash of the block that is validated. This is used as a + -- checksum for the payload validation. For the last block of a ForkInfo + -- structure this value must match the respective value in the target + -- sync state. + -- + -- The BlockPayloadHash is first computed when the respective payload is + -- created for mining and before it is included in a block. , _consensusPayloadData :: !(Maybe EncodedPayloadData) + -- ^ Optional external payload data. This may be the complete, self + -- contained block payload or it may just contain complementary data + -- that aids with the validation. + -- + -- The main purpose of this field is to allow consensus to gossip around + -- payload data along with new cuts in the P2P network, which allows for + -- more efficient synchronization and a reduction of block propagation + -- latencies. } deriving (Show, Eq, Ord) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 92e92cd3a6..97af873d57 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -519,9 +519,9 @@ withEvmPayloadProvider logger c rdb mgr conf | FromSing @_ @p (SEvmProvider ecid) <- payloadProviderTypeForChain c = do engineCtx <- liftIO $ mkEngineCtx (_evmConfEngineJwtSecret conf) (_engineUri $ _evmConfEngineUri conf) - SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal + SomeChainwebVersionT @v _ <- return someChainwebVersionVal SomeChainIdT @c _ <- return $ someChainIdVal c - let pldCli h = Rest.payloadClient @v @c @p h + let pldCli = Rest.payloadClient @v @c @p genPld <- liftIO $ checkExecutionClient logger c engineCtx (EVM.ChainId (fromSNat ecid)) liftIO $ logFunctionText logger Info $ "genesis payload block hash: " <> sshow (EVM._hdrPayloadHash genPld) @@ -530,7 +530,7 @@ withEvmPayloadProvider logger c rdb mgr conf store <- liftIO $ newPayloadStore mgr (logFunction pldStoreLogger) pdb pldCli pldVar <- liftIO newEmptyTMVarIO pldIdVar <- liftIO newEmptyTMVarIO - candidates <- liftIO $ emptyTable + candidates <- liftIO emptyTable stateVar <- liftIO $ newTVarIO (T2 (genesisState c) Nothing) lock <- liftIO $ newMVar () let p = EvmPayloadProvider @@ -625,7 +625,7 @@ getPayloadTimeout = 30_000_000 -- | Scheduler for listening for new payloads. -- payloadListener :: (Logger logger, HasVersion) => EvmPayloadProvider logger -> IO () -payloadListener p = case (_evmMinerAddress p) of +payloadListener p = case _evmMinerAddress p of Nothing -> do lf Info "New payload creation is disabled." return () @@ -774,7 +774,7 @@ forkchoiceUpdate p t fcs attr = do waitTime = Micros 100_000 go remaining | remaining <= 0 = do - lf Warn $ "forkchoiceUpdate timed out while EVM is syncing" + lf Warn "forkchoiceUpdate timed out while EVM is syncing" throwM $ ForkchoiceUpdatedTimeoutException t | otherwise = do lf Info $ briefJson $ object @@ -822,11 +822,20 @@ newPayload -> IO () newPayload p t request = go t where - lf = loggS p "forkchoiceUpdate" + lf = loggS p "newPayload" waitTime = Micros 500_000 go remaining | remaining <= 0 = do - lf Warn $ "newPayload timed out while EVM is syncing" + lf Warn "newPayload timed out while EVM is syncing" + + -- FIXME FIXME FIXME + -- this is an experiment. Check comment in validatePayload for + -- details. It seems that when reth returns SYNCING we are actually + -- stuck (it does not resolve itself). So, we try to force it to + -- sync and hope that when we come back the newPaylaod call is + -- successful. + -- updateEvm p trgState Nothing [] + throwM $ NewPayloadTimeoutException t | otherwise = do lf Info $ briefJson $ object @@ -970,7 +979,7 @@ updateEvm p state nctx plds = lookupConsensusState p state plds >>= \case -- There is a race here: If we fail updating the variable -- the EVM is ahead. That could cause payload updates to -- fail and possibly stale mining. - lf Info $ "update state and payload ID variable" + lf Info "update state and payload ID variable" lf Debug $ briefJson $ object [ "newState" .= state , "newBlockCtx" .= nctx @@ -1029,7 +1038,7 @@ mkPayloadAttributes pheight phash parentTimestamp addr nctx = PayloadAttributesV withdrawal = WithdrawalV1 { _withdrawalValidatorIndex = 0 , _withdrawalIndex = int pheight + 1 - , _withdrawalAmount = int $ reward + , _withdrawalAmount = int reward , _withdrawalAddress = addr } @@ -1134,7 +1143,7 @@ awaitNewPayload p = do atomically (tryReadTMVar (_evmPayloadVar p)) >>= \case Just (T2 curEvmBlockHash _) | curEvmBlockHash == newEvmBlockHash -> do - lf Info $ "the new execution payload is the same as the current payload. No update." + lf Info "The new execution payload is the same as the current payload. No update." x -> do lf Debug $ "checking new execution payload for " <> toText pid <> "; execution payload: " <> briefJson resp @@ -1188,8 +1197,8 @@ awaitNewPayload p = do lf Debug $ "write new payload to payload var for evm hash " <> briefJson newEvmBlockHash atomically $ writeTMVar (_evmPayloadVar p) $ T2 newEvmBlockHash NewPayload { _newPayloadTxCount = int $ length (_executionPayloadV1Transactions v1) - , _newPayloadSize = int $ sum $ (BS.length . _transactionBytes) - <$> (_executionPayloadV1Transactions v1) + , _newPayloadSize = int $ sum $ BS.length . _transactionBytes + <$> _executionPayloadV1Transactions v1 , _newPayloadParentHeight = Parent $ _syncStateHeight sstate , _newPayloadParentHash = Parent $ _syncStateBlockHash sstate , _newPayloadBlockPayloadHash = EVM._hdrPayloadHash pldHdr @@ -1321,16 +1330,18 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do -- It could also make sense to check the empty trace case first -- and skip any ctx validation. - unknowns' <- dropWhile (isJust . snd) . zip l - <$> tableLookupBatch p (_evaluationCtxRankedPayloadHash <$> l) + ctxBlockHashes <- tableLookupBatch p (_evaluationCtxRankedPayloadHash <$> l) + let (knowns', unknowns') = span (isJust . snd) $ zip l ctxBlockHashes -- assert db invariant unless (all (isNothing . snd) unknowns') $ error "Chainweb.PayloadProviders.EVM.syncToBlock: detected corrupted payload database" + let knowns = fst <$> knowns' let unknowns = fst <$> unknowns' lf Debug $ "unknown blocks in context: " <> sshow (length unknowns) + lf Debug $ "known blocks in context: " <> sshow (length knowns) -- fetch all unknown payloads -- @@ -1338,11 +1349,112 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do -- unknowns in batches without redundant local lookups. Then -- validate all payloads together before sending them to the -- EVM and inserting them into the DB. + + -- FIXME FIXME FIXME + -- We also must make sure that the EVM has synced all + -- payloads that are known to the payload provider. Normally + -- that happens at startup. However, at startup that happens + -- only up to the latest. But there is no guarantee that the + -- latest cut isn't behind the latest known payload of the + -- provider (e.g. when the cut got reset). In those cases it + -- can happen that during catchup we encounter blocks that + -- are known to the provider but not yet to the EVM. + -- + -- What can we do? + -- - We can just proceed and update the evm (updateEvm) in + -- case validatePayload times out (SYNCING). + -- - We can keep track of the latest state of the EVM. + -- - We can check each time the latest state of the EVM. + -- - We can always update the the EVM, which should be fast + -- if is is already in sync, though it would still be a + -- network roundtrip. + -- - Altenatively, we can make sure that the forkpoint + -- reflects the state of the EVM and not the state of the + -- payload provider. However, that could mean that we + -- revalidate blocks that we did validate before. + -- + -- The first option is somewhat inefficient on long catchups + -- with small per chain chunks sizes. -- + -- For now we try the second option: + + -- Check that the last known is known to the EVM according + -- to the current EVM state. + + -- FIXME: the ForkInfo trace is based on the forkpoint with + -- the payload provider (and not the EVM state). Therefore + -- the knowns may not contain what we expect, in particular + -- they are likely just empty (because the forkpoint is the + -- last known payload provider block). + -- + -- So, this check fails and we skip the updateEvm call. The + -- correct approach is to call evmUpdate whenever the first + -- trace entry is ahead of the evm state. + case unknowns of + (firstUnknown:_) -> do + let lastKnownPayloadHash = case reverse knowns of + [] -> unwrapParent $ _forkInfoBasePayloadHash forkInfo + (lastKnown:_) -> _consensusPayloadHash $ _evaluationCtxPayload lastKnown + + let lastKnownHeight = firstUnknown._evaluationCtxParentHeight.unwrapParent + let lastKnownHash = firstUnknown._evaluationCtxParentHash.unwrapParent + when (_latestHeight curState < lastKnownHeight) $ do + lf Warn $ "Chainweb.PayloadProviders.EVM.syncToBlock: last known block in the trace is ahead of the current EVM state" + <> "; last known height: " <> sshow lastKnownHeight + <> "; last known hash: " <> brief lastKnownHash + <> "; current state height: " <> sshow (_latestHeight curState) + <> "; current state hash: " <> brief (_latestBlockHash curState) + + -- call evmUpdate to update the EVM to the last unknown + -- block without providing any payload attributes or + -- payloads, so the validation state does not change. + -- + -- We are updating consensus here but that is ok, + -- because it is still below the head of the canonical + -- chain that we go asked to update to. It is also still + -- within the range of known to be valid blocks. We do + -- not update the safe or final state at this point. + let lastKnownConsensusState = ConsensusState + { _consensusStateLatest = SyncState + { _syncStateHeight = lastKnownHeight + , _syncStateBlockHash = lastKnownHash + , _syncStateBlockPayloadHash = lastKnownPayloadHash + } + , _consensusStateSafe = curState._consensusStateSafe + , _consensusStateFinal = curState._consensusStateFinal + } + updateEvm p lastKnownConsensusState Nothing [] + _ -> return () + plds <- forM unknowns $ \ctx -> do pld <- getPayloadForContext p hints ctx + -- FIXME FIXME FIXME + -- + -- The follwoing can result in timeouts. Even on empty + -- blocks. But why? Well, see the commant above! + -- + -- It seems that it is a problem when we have a header + -- in the payload db (we know it) but the EVM does not + -- yet know about it. That's a corner case that can + -- happen when syncing after the EVM and the payload + -- provider got out of sync. In those cases it is + -- probably the best to update the EVM first. Normally, + -- that happens at startup, but iwhen the cut got reset, + -- the payload provider may know validated blocks beyond + -- the latest cut. Whe consensus moves the cut beyond + -- the latest block that is known to the EVM, but the + -- payload provider already knows those blocks we get + -- into situation where the blocks in the fork info + -- trace do not cover the missing blocks in the EVM. + -- + -- Can we skip the newPayload call in it -- at least + -- sometimes? If we instruct the EVM to just go to those + -- blocks it will sync all by itself. However, we still + -- must guarantee that we validated the Chainweb Merkle + -- root of all payloads that the payload provider knows + -- about. validatePayload p pld ctx return (_evaluationCtxRankedPayloadHash ctx, pld) @@ -1532,7 +1644,7 @@ getLogEntry p e = do logs <- getLogEntries p (int $ _xEventBlockHeight e) case filter ftx logs of [] -> throwM $ InvalidTransactionIndex e - tx -> case tx L.!? (int $ _xEventEventIndex e) of + tx -> case tx L.!? int (_xEventEventIndex e) of Nothing -> throwM $ InvalidEventIndex e Just l -> return $ fromRpcLogEntry l where @@ -1546,9 +1658,9 @@ getSpvProof -> XEventId -> IO SpvProof getSpvProof p e = do - le <- getLogEntry p e - lf Info $ "got logEntry: " <> encodeToText le - ld <- parseXLogData e le + entry <- getLogEntry p e + lf Info $ "got logEntry: " <> encodeToText entry + ld <- parseXLogData e entry lf Info $ "got logData: " <> sshow ld return $ SpvProof $ object [ "origin" .= object diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index f15a1d3b8a..92dc7a9549 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -63,8 +63,7 @@ instance HasChainId (PactPayloadProvider logger tbl) where instance (Logger logger, CanPayloadCas tbl) => PayloadProvider (PactPayloadProvider logger tbl) where prefetchPayloads _pp _hints _forkInfo = return () - syncToBlock (PactPayloadProvider logger e) hints forkInfo = - PactService.syncToFork logger e hints forkInfo + syncToBlock (PactPayloadProvider logger e) = PactService.syncToFork logger e latestPayloadSTM (PactPayloadProvider _logger e) = do (_, bip) <- readTMVar (_psMiningPayloadVar e) diff --git a/src/Chainweb/Sync/ForkInfo.hs b/src/Chainweb/Sync/ForkInfo.hs new file mode 100644 index 0000000000..0703b95db0 --- /dev/null +++ b/src/Chainweb/Sync/ForkInfo.hs @@ -0,0 +1,274 @@ +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE DeriveGeneric #-} + +-- | +-- Module: Chainweb.Sync.ForkInfo +-- Copyright: Copyright © 2025 Kadena LLC. +-- License: MIT +-- Maintainer: Lars Kuhtz +-- Stability: experimental +-- +module Chainweb.Sync.ForkInfo +( resolveForkInfo +) where + +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB +import Chainweb.Core.Brief +import Chainweb.Parent +import Chainweb.PayloadProvider +import Chainweb.Ranked +import Chainweb.TreeDB +import Chainweb.Utils +import Chainweb.Version +import Control.Lens +import Control.Monad.Catch +import Data.List qualified as L +import Data.LogMessage +import Data.Text qualified as T +import Data.These (partitionHereThere, These (..)) +import GHC.Generics (Generic) +import GHC.Stack +import Streaming.Prelude qualified as S +import System.LogLevel + +newtype ForkInfoSyncFailure = ForkInfoSyncFailure T.Text + deriving (Show, Eq, Ord, Generic) + +instance Exception ForkInfoSyncFailure + +-- -------------------------------------------------------------------------- -- +-- Synchronize Payload Provider +-- +-- 1. Submit initial ForkInfo +-- 2. If result is not as requested: let ph be the height of the payload provider state. +-- 3. Efficiently compute fork point (starting at min of ph and target height) +-- 4. Get chunk size prefix of branch from fork point to target block +-- 5. Submit new ForkInfo with that prefix +-- 6. If result height is smaller than fork point retry from start and eventually fail. +-- 7. If result is on prefix continue with next prefix chunk from result. +-- 8. If result is larger than fork point and not on branch compute new fork +-- point. If that forkpoint is on prefix continue with next prefix chunk form +-- forkpoint. Otherwise retry from start and eventually fail. +-- +-- We do not include a new block context for chunked forkinfo resolutions. +-- Mining should be performed only on a fully caught up payload provider. + +forkInfoChunkSize :: Int +forkInfoChunkSize = 1000 + +-- -------------------------------------------------------------------------- -- + +-- | Resolve a ForkInfo for an existing block header against a PayloadProvider. +-- +-- Typically an initial forkInfo is either for a block that has been validated +-- before or for a single new block header. In the former case the trace is +-- typically empty, in the latter case it contains a single entry. +-- +-- TODO: trace this operation! +-- TODO: check the size of trace and if needed take appropriate measures +-- +resolveForkInfo + :: HasCallStack + => HasVersion + => PayloadProvider p + => LogFunctionText + -> BlockHeaderDb -- FIXME use RankedBlockHeaderDb + -> p + -> Maybe Hints + -> ForkInfo + -> IO () +resolveForkInfo logg bhdb provider hints finfo = do + + -- Attempt payload validation for the given ForkInfo + -- + -- We assume that this forkInfo has a trace of "reasonable" size + -- + r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do + logg Warn $ "getBlockHeaderInternal payload validation for " <> sshow h + <> " failed with: " <> sshow e + throwM e + + -- Check result of syncToBlock + -- + if r == _forkInfoTargetState finfo + + -- We are done + -- + then return () + + -- We are not in sync and need to resolve (could be a fork a + -- payload provider that is not caught up yet) + -- + else do + resolveForkInfoForProviderState logg bhdb provider hints finfo r + where + h = _latestRankedBlockHash . _forkInfoTargetState $ finfo + +-- | Resolve a ForkInfo with respect to the current state of the payload +-- provider. +-- +-- If necessarily resolution is done iteratively in chunks. FIXME chunking +-- is not yet implemented. +-- +-- The operation fails if no progress is made in a chunk. +-- +resolveForkInfoForProviderState + :: HasCallStack + => HasVersion + => PayloadProvider p + => LogFunctionText + -> BlockHeaderDb -- FIXME use RankedBlockHeaderDb + -> p + -> Maybe Hints + -> ForkInfo + -> ConsensusState + -> IO () +resolveForkInfoForProviderState logg bhdb provider hints finfo ppState + | ppRBH == h = do + -- nothing to do, we are at the target + logg Info "resolveForkInfo: payload provider is at target block" + return () + | otherwise = do + logg Info $ "resolveForkInfo: payload provider state: " <> brief ppState + <> "; target state: " <> brief (_forkInfoTargetState finfo) + + -- FIXME this isn't yet available for new blocks that are currently + -- being validated. In that case it should be provided by the caller. + hdr <- lookupRankedM bhdb (int $ _rankedHeight h) (_ranked h) + `catch` \(e :: SomeException) -> do + logg Warn $ "validatePayload: target block is missing: " <> brief h + throwM e + + -- Lookup the state of the Payload Provider and query the trace + -- from the fork point to the target block. + -- + -- This really should only happen if the payload provider db comes from an + -- external source or the payload provider db is outdated and resides on a + -- fork that got already pruned. In this case we can only guess where a + -- common fork point might be -- which, in worst case, might be the + -- genesis. We should either do an exponential search or just fail. + -- + + -- Before we do the potentially expensive branch diff call, we check + -- whether the ppState is in the trace of the finfo. + -- TODO this could be done more efficiently, but for now it is fine. + let idx = L.elemIndex ppRBH + $ unwrapParent . _evaluationCtxRankedParentHash <$> _forkInfoTrace finfo + + newForkInfo <- case idx of + Just i -> do + logg Info $ "resolveForkInfo: found payload provider state in trace at index " <> sshow i + return finfo + { _forkInfoTrace = drop (i + 1) (_forkInfoTrace finfo) + , _forkInfoBasePayloadHash = Parent $ _ranked $ latestRankedBlockPayloadHash ppState + } + Nothing -> do + logg Info "resolveForkInfo: payload provider state not in trace, computing fork point" + ppBlock <- lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH) `catch` \case + e@(TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do + logg Warn $ "Payload provider block is missing: " <> brief ppRBH + throwM e + e -> throwM e + + -- FIXME: this stream can be very long if the payload provider + -- is out of sync. We should limit it and proceed iteratively if + -- necessary. Ideally, we check the length based on block + -- heights before we compute the full trace below. We also have + -- to be careful, that we compute the fork point only once and + -- iterate from there, otherwise the complexity would become + -- quadratic. + -- + -- Note that as long as no other CL is interfering with us we + -- can proceed in chunks starting form the fork point. According + -- to the payload provider protocol, the payload provider may + -- process the ForkInfo only partially, in which case the result + -- is our branch and the forkpoint is forwarded. If we find that + -- the result is on a fork it means that there is interference. + -- We may check whether we still made progress, in which case we + -- are fine. If we find that we do not make progress we raise an + -- error. + + (forkBlocksDescendingStream S.:> forkPoint) <- S.toList + $ branchDiff_ bhdb ppBlock hdr + + let forkBlocksAscending = reverse + $ snd + $ partitionHereThere forkBlocksDescendingStream + + let l = length forkBlocksAscending + logg Info $ "resolveForkInfo: fork point: " <> brief forkPoint + <> "; fork length: " <> sshow l + <> "; fork blocks: " <> brief forkBlocksAscending + + let newTrace = zipWith + (\prent child -> + ConsensusPayload (view blockPayloadHash child) Nothing <$ + blockHeaderToEvaluationCtx (Parent prent) + ) + (forkPoint : forkBlocksAscending) + forkBlocksAscending + + return finfo + { _forkInfoTrace = newTrace + , _forkInfoBasePayloadHash = + Parent (view blockPayloadHash forkPoint) + } + + -- FXIME: when we limit the trace we still need to adjust + -- the target. We also need to make sure that we eventually + -- reach the target. For that we should remember the + -- remaining trace. After each chunk we concatenate the + -- remaining trace form the chunk with the overall remaining + -- trace and compute the next chunk. + -- Also, do not request payload creation on non-final chunks. + -- + -- let (nextChunk, remainingTrace) = splitAt forkInfoChunkSize forkBlocksAscending + + -- Retry payload validation with the new ForkInfo + -- + newState <- syncToBlock provider hints newForkInfo `catch` \(e :: SomeException) -> do + logg Warn $ "getBlockHeaderInternal payload validation retry for " + <> brief h + <> " failed with: " + <> sshow e + throwM e + + -- check if we made progress + -- + let delta :: Int = int (_rankedHeight (_latestRankedBlockHash newState)) + - int (_rankedHeight (_latestRankedBlockHash ppState)) + + if delta > 0 + then do + logg Info $ "resolveForkInfo: made progress" + <> "; delta: " <> sshow delta + <> "; previous payload provider state: " <> brief ppState + <> "; new payload provider state: " <> brief newState + <> "; target state: " <> brief (_forkInfoTargetState newForkInfo) + -- continue. + -- TODO compute the new fork info here. + resolveForkInfoForProviderState logg bhdb provider hints newForkInfo newState + else do + logg Warn $ "resolveForkInfo: no progress" + <> "; delta: " <> sshow delta + <> "; previous payload provider state: " <> brief ppState + <> "; new payload provider state: " <> brief newState + <> "; target state: " <> brief (_forkInfoTargetState newForkInfo) + + -- If this fails, there is no way for the payload provider to + -- sync to the block without using the ordinary cut pipeline. + -- so, we raise an exception. + -- + throwM $ ForkInfoSyncFailure $ "unexpected result state" + <> "; delta: " <> sshow delta + <> "; previous payload provider state: " <> brief ppState + <> "; new payload provider state: " <> brief newState + <> "; target state: " <> brief (_forkInfoTargetState newForkInfo) + where + h = _latestRankedBlockHash . _forkInfoTargetState $ finfo + ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest ppState + diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 9200b334c7..73afe913b1 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -16,7 +16,6 @@ {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -{-# LANGUAGE TupleSections #-} -- | -- Module: Chainweb.Sync.WebBlockHeaderStore @@ -54,9 +53,10 @@ import Chainweb.Difficulty (WindowWidth(..)) import Chainweb.MinerReward (blockMinerReward) import Chainweb.Pact.Payload import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Parent import Chainweb.PayloadProvider -import Chainweb.Ranked import Chainweb.Storage.Table +import Chainweb.Sync.ForkInfo import Chainweb.Time import Chainweb.TreeDB import Chainweb.TreeDB qualified as TDB @@ -82,10 +82,6 @@ import P2P.TaskQueue import Servant.Client import System.LogLevel import Utils.Logging.Trace -import Chainweb.Parent -import Streaming.Prelude qualified as S -import Data.These (partitionHereThere, These (..)) -import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- -- Response Timeout Constants @@ -241,10 +237,20 @@ forkInfoForHeader :: HasVersion => WebBlockHeaderDb -> BlockHeader + -- ^ The block header of the target consensus state of the fork info. -> Maybe EncodedPayloadData -> Maybe (Parent BlockHeader) + -- ^ Parent header of the given header if available. But this is not + -- checked! + -- If not provided it will be looked up in the database by this function. + -- + -- TODO: we should probably check that this value is indeed the parent + -- of the given header. + -> Bool + -- ^ Whether to request payload production on the target state of + -- the fork info. -> IO ForkInfo -forkInfoForHeader wdb hdr pldData parentHdr +forkInfoForHeader wdb hdr pldData parentHdr createPayload -- FIXME do we need this case??? We never add genesis headers... | isGenesisBlockHeader hdr = do @@ -253,7 +259,7 @@ forkInfoForHeader wdb hdr pldData parentHdr { _forkInfoTrace = [] , _forkInfoBasePayloadHash = Parent $ _latestPayloadHash state , _forkInfoTargetState = state - , _forkInfoNewBlockCtx = Just nbctx + , _forkInfoNewBlockCtx = nbctx } | otherwise = do @@ -269,15 +275,17 @@ forkInfoForHeader wdb hdr pldData parentHdr { _forkInfoTrace = [consensusPayload <$ blockHeaderToEvaluationCtx phdr] , _forkInfoBasePayloadHash = view blockPayloadHash <$> phdr , _forkInfoTargetState = state - , _forkInfoNewBlockCtx = Just nbctx + , _forkInfoNewBlockCtx = nbctx } where pld = view blockPayloadHash hdr - nbctx = NewBlockCtx - { _newBlockCtxMinerReward = blockMinerReward (height + 1) - , _newBlockCtxParentCreationTime = Parent $ view blockCreationTime hdr - } + nbctx + | createPayload = Just NewBlockCtx + { _newBlockCtxMinerReward = blockMinerReward (height + 1) + , _newBlockCtxParentCreationTime = Parent $ view blockCreationTime hdr + } + | otherwise = Nothing height = view blockHeight hdr -- -------------------------------------------------------------------------- -- @@ -331,6 +339,9 @@ getBlockHeaderInternal = do logg Debug $ "getBlockHeaderInternal: " <> sshow h !bh <- memoInsert cas memoMap h $ \k@(ChainValue cid k') -> do + -- assertion: h == k + + let taskLog l = logg l . taskMsg k -- query BlockHeader via -- @@ -371,7 +382,7 @@ getBlockHeaderInternal -- let isGenesisParentHash p = _chainValueValue p == genesisParentBlockHash p queryAdjacentParent p = Concurrently $ unless (isGenesisParentHash p) $ void $ do - logg Debug $ taskMsg k + taskLog Debug $ "getBlockHeaderInternal.getPrerequisiteHeader (adjacent) for " <> sshow h <> ": " <> sshow p getBlockHeaderInternal @@ -389,7 +400,7 @@ getBlockHeaderInternal -- after payload validation when the header is finally added to the db. -- queryParent p = Concurrently $ do - logg Debug $ taskMsg k + taskLog Debug $ "getBlockHeaderInternal.getPrerequisiteHeader (parent) for " <> sshow h <> ": " <> sshow p parentHdr <- Parent <$> getBlockHeaderInternal @@ -435,7 +446,7 @@ getBlockHeaderInternal -- -- This requires to provide a CPS version of memoInsert. - logg Debug $ taskMsg k $ "getBlockHeaderInternal got pre-requesites for " <> sshow h + taskLog Debug $ "getBlockHeaderInternal got pre-requesites for " <> sshow h -- ------------------------------------------------------------------ -- -- Validation @@ -462,70 +473,33 @@ getBlockHeaderInternal -- 4. Validate block payload -- - -- Pact validation is done in the context of a particular header. Just - -- because the payload does already exist in the store doesn't mean that - -- validation succeeds in the context of a particular block header. + -- Payload validation is done in the context of a particular header. + -- Just because the payload does already exist in the store doesn't mean + -- that validation succeeds in the context of a particular block header. -- -- If we reach this point in the code we are certain that the header -- isn't yet in the block header database and thus we still must -- validate the payload for this block header. -- + -- Compute a ForkInfo FOR A SINGLE BLOCK. + -- -- Do not produce payloads at this point; we may not stick around at -- this block. - finfo <- forkInfoForHeader wdb header pld (Just parentHdr) <&> forkInfoNewBlockCtx .~ Nothing + -- + finfo <- forkInfoForHeader wdb header pld (Just parentHdr) False - logg Debug $ taskMsg k $ - "getBlockHeaderInternal validate payload for " <> sshow h + taskLog Debug $ "getBlockHeaderInternal validate payload for " <> sshow h case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do - r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do - logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation for " <> sshow h <> " failed with : " <> sshow e - throwM e - if r /= _forkInfoTargetState finfo - then do - bhdb <- getWebBlockHeaderDb wdb cid - let ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest r - ppBlock <- lookupRankedM bhdb (int $ _rankedHeight ppRBH) (_ranked ppRBH) `catch` \case - e@(TreeDbKeyNotFound {} :: TreeDbException BlockHeaderDb) -> do - logfun Warn $ "PP block is missing: " <> brief ppRBH - throwM e - e -> throwM e - - (forkBlocksDescendingStream S.:> forkPoint) <- - S.toList $ branchDiff_ bhdb ppBlock (unwrapParent parentHdr) - let forkBlocksAscending = reverse $ snd $ partitionHereThere (That header : forkBlocksDescendingStream) - let newTrace = - zipWith - (\prent child -> - ConsensusPayload (view blockPayloadHash child) Nothing <$ - blockHeaderToEvaluationCtx (Parent prent)) - (forkPoint : forkBlocksAscending) - forkBlocksAscending - let newForkInfo = finfo - { _forkInfoTrace = newTrace - , _forkInfoBasePayloadHash = - Parent $ view blockPayloadHash forkPoint - } - r' <- syncToBlock provider hints newForkInfo `catch` \(e :: SomeException) -> do - logg Warn $ taskMsg k $ "getBlockHeaderInternal payload validation retry for " <> sshow h <> " failed with: " <> sshow e - throwM e - unless (r' == _forkInfoTargetState finfo) $ do - throwM $ GetBlockHeaderFailure $ "unexpected result state" - <> "; expected: " <> brief (_forkInfoTargetState finfo) - <> "; actual: " <> brief r - <> "; PP latest block: " <> brief ppBlock - <> "; target block: " <> brief header - <> "; target block parent: " <> brief (unwrapParent parentHdr) - <> "; fork blocks: " <> brief forkBlocksAscending - else - return () + bhdb <- getWebBlockHeaderDb wdb cid + resolveForkInfo taskLog bhdb provider hints finfo DisabledPayloadProvider -> do - logg Debug $ taskMsg k $ "getBlockHeaderInternal payload provider disabled" + taskLog Debug "getBlockHeaderInternal payload provider disabled" - logg Debug $ taskMsg k "getBlockHeaderInternal pact validation succeeded" + taskLog Debug "getBlockHeaderInternal pact validation succeeded" - logg Debug $ taskMsg k $ "getBlockHeaderInternal return header " <> sshow h + taskLog Debug $ "getBlockHeaderInternal return header " <> sshow h return $! chainValue header logg Debug $ "getBlockHeaderInternal: got block header for " <> sshow h return bh @@ -544,7 +518,7 @@ getBlockHeaderInternal logg :: LogFunctionText logg = logfun @T.Text - taskMsg k msg = "header task " <> sshow k <> ": " <> msg + taskMsg k msg = "header task " <> toText k <> ": " <> msg traceLabel subfun = "Chainweb.Sync.WebBlockHeaderStore.getBlockHeaderInternal." <> subfun @@ -690,3 +664,4 @@ instance (HasVersion, CasKeyType (ChainValue BlockHeader) ~ k) => Table WebBlock -- instance is available only locally. -- -- The instance requires that memoCache doesn't delete from the cas. + diff --git a/src/Chainweb/TreeDB.hs b/src/Chainweb/TreeDB.hs index 380ad46447..b80df04cab 100644 --- a/src/Chainweb/TreeDB.hs +++ b/src/Chainweb/TreeDB.hs @@ -1,8 +1,6 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DeriveAnyClass #-} -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DerivingStrategies #-} @@ -12,7 +10,6 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE UndecidableInstances #-} @@ -66,7 +63,6 @@ module Chainweb.TreeDB , lookupM , lookupRankedM , lookupStreamM -, lookupParentM -- * Misc Utils , forkEntry diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 38e7ff936c..ebef7a2de9 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -137,7 +137,7 @@ withTestCutDb rdb conf n providers logger = do where syncOne hdr = forM_ (providers ^? atChain (_chainId hdr)) $ \case ConfiguredPayloadProvider provider -> do - finfo <- forkInfoForHeader wbh hdr Nothing Nothing + finfo <- forkInfoForHeader wbh hdr Nothing Nothing True r <- syncToBlock provider Nothing finfo `catch` \(e :: SomeException) -> do throwM e unless (r == _forkInfoTargetState finfo) $ do From 12b5be8dbcea55d8964d18596b0dddb4a44623da Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 3 Oct 2025 13:50:21 -0700 Subject: [PATCH 355/378] give CutDB and WebBlockHeaderDB a proper logger --- node/src/ChainwebNode.hs | 10 +- src/Chainweb/BlockHeaderDB/RestAPI/Server.hs | 4 +- src/Chainweb/Chainweb.hs | 12 +- src/Chainweb/Chainweb/CutResources.hs | 10 +- src/Chainweb/Chainweb/MinerResources.hs | 17 +-- src/Chainweb/CutDB.hs | 82 ++++++----- src/Chainweb/CutDB/RestAPI/Server.hs | 22 +-- src/Chainweb/CutDB/Sync.hs | 2 +- src/Chainweb/Miner/Coordinator.hs | 14 +- src/Chainweb/Miner/Miners.hs | 8 +- src/Chainweb/RestAPI.hs | 28 ++-- src/Chainweb/RestAPI/NodeInfo.hs | 6 +- src/Chainweb/SPV/CreateProof.hs | 2 +- src/Chainweb/SPV/RestAPI/Server.hs | 22 +-- src/Chainweb/Sync/WebBlockHeaderStore.hs | 62 +++++--- test/lib/Chainweb/Test/MultiNode.hs | 12 +- test/lib/Chainweb/Test/Utils.hs | 138 +++++++++--------- .../Chainweb/Test/BlockHeaderDB/PruneForks.hs | 98 +++++-------- test/unit/Chainweb/Test/CutDB.hs | 50 ++++--- test/unit/Chainweb/Test/Mempool/RestAPI.hs | 2 +- test/unit/Chainweb/Test/Pact/CutFixture.hs | 63 ++++---- test/unit/Chainweb/Test/RestAPI.hs | 20 +-- 22 files changed, 344 insertions(+), 340 deletions(-) diff --git a/node/src/ChainwebNode.hs b/node/src/ChainwebNode.hs index 4d282d3e13..e8de488124 100644 --- a/node/src/ChainwebNode.hs +++ b/node/src/ChainwebNode.hs @@ -203,7 +203,7 @@ runCutMonitor :: HasVersion => Logger logger => logger - -> CutDb + -> CutDb logger -> IO () runCutMonitor logger db = L.withLoggerLabel ("component", "cut-monitor") logger $ \l -> runMonitorLoop "ChainwebNode.runCutMonitor" l $ do @@ -272,17 +272,17 @@ runRtsMonitor logger = L.withLoggerLabel ("component", "rts-monitor") logger go logFunctionText l Warn "RTS Stats isn't enabled. Run with '+RTS -T' to enable it." True -> do runMonitorLoop "Chainweb.Node.runRtsMonitor" l $ do - logFunctionText l Debug $ "logging RTS stats" + logFunctionText l Debug "logging RTS stats" stats <- getRTSStats logFunctionJson logger Info stats approximateThreadDelay 60_000_000 {- 1 minute -} -runQueueMonitor :: Logger logger => logger -> CutDb -> IO () +runQueueMonitor :: Logger logger => logger -> CutDb logger -> IO () runQueueMonitor logger cutDb = L.withLoggerLabel ("component", "queue-monitor") logger go where go l = do runMonitorLoop "ChainwebNode.runQueueMonitor" l $ do - logFunctionText l Debug $ "logging cut queue stats" + logFunctionText l Debug "logging cut queue stats" stats <- getQueueStats cutDb logFunctionJson logger Info stats approximateThreadDelay 60_000_000 {- 1 minute -} @@ -297,7 +297,7 @@ runDatabaseMonitor logger rocksDbDir pactDbDir = L.withLoggerLabel ("component", where go l = do runMonitorLoop "ChainwebNode.runDatabaseMonitor" l $ do - logFunctionText l Debug $ "logging database stats" + logFunctionText l Debug "logging database stats" logFunctionJson l Info . DbStats "rocksDb" =<< sizeOf rocksDbDir logFunctionJson l Info . DbStats "pactDb" =<< sizeOf pactDbDir approximateThreadDelay 1_200_000_000 {- 20 minutes -} diff --git a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs index 2845091fc5..1e2a46f246 100644 --- a/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs +++ b/src/Chainweb/BlockHeaderDB/RestAPI/Server.hs @@ -312,12 +312,12 @@ someP2pBlockHeaderDbServers = ifoldMap -- -------------------------------------------------------------------------- -- -- BlockHeader Event Stream -someBlockStreamServer :: HasVersion => CutDb -> SomeServer +someBlockStreamServer :: HasVersion => CutDb l -> SomeServer someBlockStreamServer cdb = runIdentity $ do SomeChainwebVersionT @v _ <- return someChainwebVersionVal Identity $ SomeServer (Proxy @(BlockStreamApi v)) $ blockStreamHandler cdb -blockStreamHandler :: HasVersion => CutDb -> Tagged Handler Application +blockStreamHandler :: HasVersion => CutDb l -> Tagged Handler Application blockStreamHandler db = Tagged $ \req resp -> do streamRef <- newIORef $ SP.map f $ SP.mapM g $ SP.concat $ blockDiffStream db eventSourceAppIO (run streamRef) req resp diff --git a/src/Chainweb/Chainweb.hs b/src/Chainweb/Chainweb.hs index 6a4388fa3f..60e8bf09b6 100644 --- a/src/Chainweb/Chainweb.hs +++ b/src/Chainweb/Chainweb.hs @@ -168,7 +168,7 @@ import P2P.Peer data Chainweb logger = Chainweb { _chainwebHostAddress :: !HostAddress , _chainwebChains :: !(ChainMap (ChainResources logger)) - , _chainwebCutResources :: !CutResources + , _chainwebCutResources :: !(CutResources logger) , _chainwebMiner :: !(Maybe (MinerResources logger)) , _chainwebCoordinator :: !(Maybe (MiningCoordination logger)) , _chainwebLogger :: !logger @@ -579,13 +579,13 @@ runChainweb cw nowServing = do p2pValidationMiddleware <- if _p2pConfigValidateSpec (_configP2p $ _chainwebConfig cw) then do - logg Warn $ "OpenAPI spec validation enabled on P2P API, make sure this is what you want" + logg Warn "OpenAPI spec validation enabled on P2P API, make sure this is what you want" mkValidationMiddleware else return id serviceApiValidationMiddleware <- if _serviceApiConfigValidateSpec (_configServiceApi $ _chainwebConfig cw) then do - logg Warn $ "OpenAPI spec validation enabled on service API, make sure this is what you want" + logg Warn "OpenAPI spec validation enabled on service API, make sure this is what you want" mkValidationMiddleware else return id @@ -635,7 +635,7 @@ runChainweb cw nowServing = do -- collect server resources proj :: forall a . (ChainResources logger -> a) -> [(ChainId, a)] - proj f = chains & mapped . _2 %~ f + proj f = chains <&> _2 %~ f peerDb = _peerResDb (_chainwebPeer cw) @@ -696,7 +696,7 @@ runChainweb cw nowServing = do logFunctionCounter (_chainwebLogger cw) Info . (:[]) =<< roll clientClosedConnectionsCounter - chainwebServerDbs :: ChainwebServerDbs Pact.Transaction + chainwebServerDbs :: ChainwebServerDbs logger Pact.Transaction chainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Just cutDb , _chainwebServerBlockHeaderDbs = chainDbsToServe @@ -808,7 +808,7 @@ runChainweb cw nowServing = do -- Cut DB and Miner - cutDb :: CutDb + cutDb :: CutDb logger cutDb = _cutResCutDb $ _chainwebCutResources cw cutPeerDb :: PeerDb diff --git a/src/Chainweb/Chainweb/CutResources.hs b/src/Chainweb/Chainweb/CutResources.hs index 79f3f08d9c..fa3e3ba796 100644 --- a/src/Chainweb/Chainweb/CutResources.hs +++ b/src/Chainweb/Chainweb/CutResources.hs @@ -65,9 +65,9 @@ import P2P.Node.PeerDB (PeerDb) -- -------------------------------------------------------------------------- -- -- Cuts Resources -data CutResources = CutResources +data CutResources l = CutResources { _cutResPeerDb :: !PeerDb - , _cutResCutDb :: !CutDb + , _cutResCutDb :: !(CutDb l) , _cutResCutP2pNode :: !P2pNode -- ^ P2P Network for pushing and synchronizing cuts. , _cutResHeaderP2pNode :: !P2pNode @@ -88,11 +88,11 @@ withCutResources -> WebBlockHeaderDb -> ChainMap ConfiguredPayloadProvider -> HTTP.Manager - -> ResourceT IO (Either Cut CutResources) + -> ResourceT IO (Either Cut (CutResources logger)) withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain providers mgr = do -- initialize blockheader store - headerStore <- liftIO $ newWebBlockHeaderStore mgr webchain (logFunction logger) + headerStore <- liftIO $ newWebBlockHeaderStore mgr webchain logger -- initialize cutHashes store let cutHashesStore = cutHashesTable rdb @@ -128,7 +128,7 @@ withCutResources logger cutDbParams p2pConfig myInfo peerDb rdb webchain provide -- | The networks that are used by the cut DB. -- -cutNetworks :: HasVersion => CutResources -> [IO ()] +cutNetworks :: HasVersion => CutResources l -> [IO ()] cutNetworks cuts = [ p2pRunNode (_cutResCutP2pNode cuts) , p2pRunNode (_cutResHeaderP2pNode cuts) diff --git a/src/Chainweb/Chainweb/MinerResources.hs b/src/Chainweb/Chainweb/MinerResources.hs index ea6fbc3fd1..01850b7e8d 100644 --- a/src/Chainweb/Chainweb/MinerResources.hs +++ b/src/Chainweb/Chainweb/MinerResources.hs @@ -1,14 +1,9 @@ -{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE NumericUnderscores #-} -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Chainweb.MinerResources @@ -62,14 +57,14 @@ withMiningCoordination => HasVersion => logger -> MiningConfig - -> CutDb + -> CutDb logger -> (Maybe (MiningCoordination logger) -> IO a) -> IO a withMiningCoordination logger conf cdb inner | not (_coordinationEnabled coordConf) = inner Nothing | otherwise = do coord <- newMiningCoordination logger coordConf cdb - fmap snd $ concurrently + snd <$> concurrently -- maintain mining state (runCoordination coord) -- run inner computation @@ -85,7 +80,7 @@ withMiningCoordination logger conf cdb inner -- data MinerResources logger = MinerResources { _minerResLogger :: !logger - , _minerResCutDb :: !CutDb + , _minerResCutDb :: !(CutDb logger) , _minerChainResources :: !(ChainMap (ChainResources logger)) , _minerResConfig :: !NodeMiningConfig , _minerResCoordination :: !(Maybe (MiningCoordination logger)) @@ -98,7 +93,7 @@ withMinerResources :: logger -> NodeMiningConfig -> ChainMap (ChainResources logger) - -> CutDb + -> CutDb logger -> Maybe (MiningCoordination logger) -> (Maybe (MinerResources logger) -> IO a) -> IO a @@ -139,7 +134,7 @@ runMiner mr where enabled = _nodeMiningEnabled $ _minerResConfig mr - cdb :: CutDb + cdb :: CutDb logger cdb = _minerResCutDb mr conf :: NodeMiningConfig diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index e37099ce7f..84a0d234f5 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -256,12 +256,12 @@ instance Exception CutDbStopped where -- | This is a singleton DB that contains the latest chainweb cut as only entry. -- -data CutDb = CutDb +data CutDb l = CutDb { _cutDbCut :: !(TVar Cut) , _cutDbQueue :: !(PQueue (Down CutHashes)) , _cutDbAsync :: !(Async ()) , _cutDbLogFunction :: !LogFunction - , _cutDbHeaderStore :: !WebBlockHeaderStore + , _cutDbHeaderStore :: !(WebBlockHeaderStore l) , _cutDbPayloadProviders :: !(ChainMap ConfiguredPayloadProvider) , _cutDbCutStore :: !(Casify RocksDbTable CutHashes) , _cutDbQueueSize :: !Natural @@ -269,30 +269,30 @@ data CutDb = CutDb , _cutDbFastForwardHeightLimit :: !(Maybe BlockHeight) } -cutDbPayloadProviders :: Getter CutDb (ChainMap ConfiguredPayloadProvider) +cutDbPayloadProviders :: Getter (CutDb l) (ChainMap ConfiguredPayloadProvider) cutDbPayloadProviders = to _cutDbPayloadProviders {-# INLINE cutDbPayloadProviders #-} -- We export the 'WebBlockHeaderDb' read-only -- -cutDbWebBlockHeaderDb :: Getter CutDb WebBlockHeaderDb +cutDbWebBlockHeaderDb :: Getter (CutDb l) WebBlockHeaderDb cutDbWebBlockHeaderDb = to $ _webBlockHeaderStoreCas . _cutDbHeaderStore {-# INLINE cutDbWebBlockHeaderDb #-} -cutDbWebBlockHeaderStore :: Getter CutDb WebBlockHeaderStore +cutDbWebBlockHeaderStore :: Getter (CutDb l) (WebBlockHeaderStore l) cutDbWebBlockHeaderStore = to _cutDbHeaderStore {-# INLINE cutDbWebBlockHeaderStore #-} -- | Access the blockerheaderdb via the cutdb for a given chain id -- -cutDbBlockHeaderDb :: HasChainId cid => cid -> Fold CutDb BlockHeaderDb +cutDbBlockHeaderDb :: HasChainId cid => cid -> Fold (CutDb l) BlockHeaderDb cutDbBlockHeaderDb cid = cutDbWebBlockHeaderDb . ixg (_chainId cid) -- | Get the current 'Cut', which represent the latest chainweb state. -- -- This the main API method of chainweb-consensus. -- -_cut :: CutDb -> IO Cut +_cut :: CutDb l -> IO Cut _cut = readTVarIO . _cutDbCut {-# INLINE _cut #-} @@ -300,30 +300,30 @@ _cut = readTVarIO . _cutDbCut -- -- This the main API method of chainweb-consensus. -- -cut :: Getter CutDb (IO Cut) +cut :: Getter (CutDb l) (IO Cut) cut = to _cut -addCutHashes :: CutDb -> CutHashes -> IO () +addCutHashes :: CutDb l -> CutHashes -> IO () addCutHashes db = pQueueInsertLimit (_cutDbQueue db) (_cutDbQueueSize db) . Down -- | An 'STM' version of '_cut'. -- -- @_cut db@ is generally more efficient than as @atomically (_cut db)@. -- -_cutStm :: CutDb -> STM Cut +_cutStm :: CutDb l -> STM Cut _cutStm = readTVar . _cutDbCut -- | An 'STM' version of 'cut'. -- -- @_cut db@ is generally more efficient than as @atomically (_cut db)@. -- -cutStm :: Getter CutDb (STM Cut) +cutStm :: Getter (CutDb l) (STM Cut) cutStm = to _cutStm -- | A common idiom to spin while waiting for a guaranteed new `Cut`, different -- from the given one. -- -awaitNewCutStm :: CutDb -> Cut -> STM Cut +awaitNewCutStm :: CutDb l -> Cut -> STM Cut awaitNewCutStm cdb c = do c' <- _cutStm cdb when (c' == c) retry @@ -332,24 +332,24 @@ awaitNewCutStm cdb c = do -- | A common idiom to spin while waiting for a guaranteed new `Cut`, different -- from the given one. -- -awaitNewCut :: CutDb -> Cut -> IO Cut +awaitNewCut :: CutDb l -> Cut -> IO Cut awaitNewCut cdb c = atomically $ awaitNewCutStm cdb c -- | As in `awaitNewCut`, but only updates when the specified `ChainId` has -- grown. -- -awaitNewCutByChainId :: CutDb -> ChainId -> Cut -> IO Cut +awaitNewCutByChainId :: CutDb l -> ChainId -> Cut -> IO Cut awaitNewCutByChainId cdb cid c = atomically $ awaitNewCutByChainIdStm cdb cid c {-# INLINE awaitNewCutByChainId #-} -- | As in `awaitNewCut`, but only updates when the header at the specified -- `ChainId` has changed, and only returns that new header. -awaitNewBlock :: CutDb -> ChainId -> BlockHash -> IO BlockHeader +awaitNewBlock :: CutDb l -> ChainId -> BlockHash -> IO BlockHeader awaitNewBlock cdb cid bHash = atomically $ awaitNewBlockStm cdb cid bHash -- | As in `awaitNewCut`, but only updates when the header at the specified -- `ChainId` has changed, and only returns that new header. -awaitNewBlockStm :: CutDb -> ChainId -> BlockHash -> STM BlockHeader +awaitNewBlockStm :: CutDb l -> ChainId -> BlockHash -> STM BlockHeader awaitNewBlockStm cdb cid bHash = do c <- _cutStm cdb case HM.lookup cid (_cutMap c) of @@ -359,7 +359,7 @@ awaitNewBlockStm cdb cid bHash = do -- | As in `awaitNewCut`, but only updates when the specified `ChainId` has -- grown. -- -awaitNewCutByChainIdStm :: CutDb -> ChainId -> Cut -> STM Cut +awaitNewCutByChainIdStm :: CutDb l -> ChainId -> Cut -> STM Cut awaitNewCutByChainIdStm cdb cid c = do c' <- _cutStm cdb let !b0 = HM.lookup cid $ _cutMap c @@ -384,7 +384,7 @@ pruneCuts logfun conf curAvgBlockHeight cutHashesStore = do deleteRangeRocksDb (unCasify cutHashesStore) (Nothing, Just (pruneCutHeight, 0, maxBound :: CutId)) -cutDbQueueSize :: CutDb -> IO Natural +cutDbQueueSize :: CutDb l -> IO Natural cutDbQueueSize = pQueueSize . _cutDbQueue withCutDb @@ -392,10 +392,10 @@ withCutDb => Logger logger => CutDbParams -> logger - -> WebBlockHeaderStore + -> WebBlockHeaderStore logger -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes - -> ResourceT IO (Either Cut CutDb) + -> ResourceT IO (Either Cut (CutDb logger)) withCutDb config logger headerStore providers cutHashesStore = snd <$> allocate (startCutDb config logger headerStore providers cutHashesStore) @@ -415,10 +415,10 @@ startCutDb => HasVersion => CutDbParams -> logger - -> WebBlockHeaderStore + -> WebBlockHeaderStore logger -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes - -> IO (Either Cut CutDb) + -> IO (Either Cut (CutDb logger)) startCutDb config logger headerStore providers cutHashesStore = mask_ $ do logg Debug "obtain initial cut" initialCut <- readInitialCut @@ -580,7 +580,7 @@ readHighestCutHeaders logg wbhdb cutHashesStore = withTableIterator (unCasify cu Left e -> throwM e Right hm -> return hm -fastForwardCutDb :: HasVersion => CutDb -> IO () +fastForwardCutDb :: HasVersion => CutDb l -> IO () fastForwardCutDb cutDb = do highestCutHeaders <- readHighestCutHeaders (_cutDbLogFunction cutDb) wbhdb (_cutDbCutStore cutDb) @@ -593,7 +593,7 @@ fastForwardCutDb cutDb = do -- | Stop the cut validation pipeline. -- -stopCutDb :: CutDb -> IO () +stopCutDb :: CutDb l -> IO () stopCutDb db = do currentCut <- readTVarIO (_cutDbCut db) unless (_cutDbReadOnly db) $ @@ -627,9 +627,10 @@ cutAvgBlockHeight = BlockHeight . round . avgBlockHeightAtCutHeight . _cutHeight -- processCuts :: HasVersion + => Logger l => CutDbParams -> LogFunction - -> WebBlockHeaderStore + -> WebBlockHeaderStore l -> ChainMap ConfiguredPayloadProvider -> Casify RocksDbTable CutHashes -> PQueue (Down CutHashes) @@ -802,7 +803,7 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar cutPru -- produced faster than they are consumed from the stream, the stream skips over -- cuts and always returns the latest cut in the db. -- -cutStream :: MonadIO m => CutDb -> S.Stream (Of Cut) m r +cutStream :: MonadIO m => CutDb l -> S.Stream (Of Cut) m r cutStream db = liftIO (_cut db) >>= \c -> S.yield c >> go c where go cur = do @@ -817,10 +818,10 @@ cutStream db = liftIO (_cut db) >>= \c -> S.yield c >> go c -- cuts. Blocks of the same chain are sorted by block height. -- cutStreamToHeaderStream - :: forall m r + :: forall m r l . MonadIO m => HasVersion - => CutDb + => CutDb l -> S.Stream (Of Cut) m r -> S.Stream (Of BlockHeader) m r cutStreamToHeaderStream db s = S.for (go Nothing s) $ \(T2 p n) -> @@ -847,10 +848,10 @@ cutStreamToHeaderStream db s = S.for (go Nothing s) $ \(T2 p n) -> -- cuts. Blocks of the same chain are sorted by block height. -- cutStreamToHeaderDiffStream - :: forall m r + :: forall m r l . MonadIO m => HasVersion - => CutDb + => CutDb l -> S.Stream (Of Cut) m r -> S.Stream (Of (Either BlockHeader BlockHeader)) m r cutStreamToHeaderDiffStream db s = S.for (cutUpdates Nothing s) $ \(T2 p n) -> @@ -891,17 +892,18 @@ uniqueBlockNumber :: HasVersion => BlockHeader -> Natural uniqueBlockNumber bh = chainIdInt (_chainId bh) + int (view blockHeight bh) * order (_chainGraph bh) -blockStream :: (MonadIO m, HasVersion) => CutDb -> S.Stream (Of BlockHeader) m r +blockStream :: (MonadIO m, HasVersion) => CutDb l -> S.Stream (Of BlockHeader) m r blockStream db = cutStreamToHeaderStream db $ cutStream db -blockDiffStream :: (MonadIO m, HasVersion) => CutDb -> S.Stream (Of (Either BlockHeader BlockHeader)) m r +blockDiffStream :: (MonadIO m, HasVersion) => CutDb l -> S.Stream (Of (Either BlockHeader BlockHeader)) m r blockDiffStream db = cutStreamToHeaderDiffStream db $ cutStream db cutHashesToBlockHeaderMap :: HasVersion + => Logger l => CutDbParams -> LogFunction - -> WebBlockHeaderStore + -> WebBlockHeaderStore l -> ChainMap ConfiguredPayloadProvider -> CutHashes -> IO (Maybe (HM.HashMap ChainId BlockHeader)) @@ -987,7 +989,7 @@ cutHashesToBlockHeaderMap conf logfun headerStore providers hs = memberOfHeader :: HasVersion - => CutDb + => CutDb l -> ChainId -> BlockHash -- ^ the block hash to look up (the member) @@ -1005,7 +1007,7 @@ memberOfHeader db cid h ctx = do member :: HasVersion - => CutDb + => CutDb l -> ChainId -> BlockHash -> IO Bool @@ -1020,15 +1022,15 @@ member db cid h = do -- | 'CutDb' with type level 'ChainwebVersionName' -- -newtype CutDbT (v :: ChainwebVersionT) = CutDbT CutDb +newtype CutDbT l (v :: ChainwebVersionT) = CutDbT (CutDb l) deriving (Generic) -data SomeCutDb = forall v . KnownChainwebVersionSymbol v => SomeCutDb (CutDbT v) +data SomeCutDb = forall l v . KnownChainwebVersionSymbol v => SomeCutDb (CutDbT l v) -someCutDbVal :: HasVersion => CutDb -> SomeCutDb +someCutDbVal :: HasVersion => CutDb l -> SomeCutDb someCutDbVal db = case implicitVersion of FromSingChainwebVersion (SChainwebVersion :: Sing v) -> - SomeCutDb $ CutDbT @v db + SomeCutDb $ CutDbT @_ @v db -- -------------------------------------------------------------------------- -- -- Queue Stats @@ -1041,7 +1043,7 @@ data QueueStats = QueueStats deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData, ToJSON) -getQueueStats :: CutDb -> IO QueueStats +getQueueStats :: CutDb l -> IO QueueStats getQueueStats db = QueueStats <$> cutDbQueueSize db <*> pQueueSize (_webBlockHeaderStoreQueue $ view cutDbWebBlockHeaderStore db) diff --git a/src/Chainweb/CutDB/RestAPI/Server.hs b/src/Chainweb/CutDB/RestAPI/Server.hs index ecee58fb7f..455754fcbb 100644 --- a/src/Chainweb/CutDB/RestAPI/Server.hs +++ b/src/Chainweb/CutDB/RestAPI/Server.hs @@ -65,7 +65,7 @@ import P2P.Peer -- -------------------------------------------------------------------------- -- -- Handlers -cutGetHandler :: HasVersion => CutDb -> Maybe MaxRank -> IO CutHashes +cutGetHandler :: HasVersion => CutDb l -> Maybe MaxRank -> IO CutHashes cutGetHandler db Nothing = liftIO $ cutToCutHashes Nothing <$> _cut db cutGetHandler db (Just (MaxRank (Max mar))) = liftIO $ do !c <- _cut db @@ -73,7 +73,7 @@ cutGetHandler db (Just (MaxRank (Max mar))) = liftIO $ do !c' <- limitCut (view cutDbWebBlockHeaderDb db) bh c return $! cutToCutHashes Nothing c' -cutPutHandler :: PeerDb -> CutDb -> CutHashes -> Handler NoContent +cutPutHandler :: PeerDb -> CutDb l -> CutHashes -> Handler NoContent cutPutHandler pdb db c = case _peerAddr <$> _cutOrigin c of Nothing -> throwError $ setErrText "Cut is missing an origin entry" err400 Just addr -> do @@ -86,17 +86,17 @@ cutPutHandler pdb db c = case _peerAddr <$> _cutOrigin c of -- Cut API Server cutServer - :: forall (v :: ChainwebVersionT) + :: forall (v :: ChainwebVersionT) l . HasVersion => PeerDb - -> CutDbT v + -> CutDbT l v -> Server (CutApi v) cutServer pdb (CutDbT db) = liftIO . cutGetHandler db :<|> cutPutHandler pdb db cutGetServer - :: forall (v :: ChainwebVersionT) + :: forall (v :: ChainwebVersionT) l . HasVersion - => CutDbT v + => CutDbT l v -> Server (CutGetApi v) cutGetServer (CutDbT db) = liftIO . cutGetHandler db @@ -104,21 +104,21 @@ cutGetServer (CutDbT db) = liftIO . cutGetHandler db -- Some Cut Server someCutServerT :: HasVersion => PeerDb -> SomeCutDb -> SomeServer -someCutServerT pdb (SomeCutDb (db :: CutDbT v)) = +someCutServerT pdb (SomeCutDb (db :: CutDbT l v)) = SomeServer (Proxy @(CutApi v)) (cutServer pdb db) -someCutServer :: HasVersion => PeerDb -> CutDb -> SomeServer +someCutServer :: HasVersion => PeerDb -> CutDb l -> SomeServer someCutServer pdb = someCutServerT pdb . someCutDbVal someCutGetServerT :: HasVersion => SomeCutDb -> SomeServer -someCutGetServerT (SomeCutDb (db :: CutDbT v)) = +someCutGetServerT (SomeCutDb (db :: CutDbT l v)) = SomeServer (Proxy @(CutGetApi v)) (cutGetServer db) -someCutGetServer :: HasVersion => CutDb -> SomeServer +someCutGetServer :: HasVersion => CutDb l -> SomeServer someCutGetServer = someCutGetServerT . someCutDbVal -- -------------------------------------------------------------------------- -- -- Run Server -serveCutOnPort :: HasVersion => Port -> PeerDb -> CutDb -> IO () +serveCutOnPort :: HasVersion => Port -> PeerDb -> CutDb l -> IO () serveCutOnPort p pdb = run (int p) . someServerApplication . someCutServer pdb diff --git a/src/Chainweb/CutDB/Sync.hs b/src/Chainweb/CutDB/Sync.hs index f9cdef26d9..123f1bec44 100644 --- a/src/Chainweb/CutDB/Sync.hs +++ b/src/Chainweb/CutDB/Sync.hs @@ -85,7 +85,7 @@ catchupStepSize = 500 syncSession :: HasVersion => PeerInfo - -> CutDb + -> CutDb l -> P2pSession syncSession p db logg env pinf = do race_ diff --git a/src/Chainweb/Miner/Coordinator.hs b/src/Chainweb/Miner/Coordinator.hs index 3b9092e3b9..bbf9af0941 100644 --- a/src/Chainweb/Miner/Coordinator.hs +++ b/src/Chainweb/Miner/Coordinator.hs @@ -288,7 +288,7 @@ updateForCut hdb ms c = do updateForSolved :: HasVersion => LogFunction - -> CutDb + -> CutDb l -> PayloadCache -> TVar (Maybe ParentState) -> SolvedWork @@ -368,7 +368,7 @@ updateForSolved lf cdb payloadCache var sw = do -- data MiningCoordination logger = MiningCoordination { _coordLogger :: !logger - , _coordCutDb :: !CutDb + , _coordCutDb :: !(CutDb logger) , _coordParentState :: !(ChainMap (TVar (Maybe ParentState))) , _coordConf :: !CoordinationConfig , _coordPayloadCache :: !PayloadCaches @@ -379,7 +379,7 @@ newMiningCoordination => HasVersion => logger -> CoordinationConfig - -> CutDb + -> CutDb logger -> IO (MiningCoordination logger) newMiningCoordination logger conf cdb = do state <- newMiningState @@ -463,7 +463,7 @@ runCoordination mr = do -- eventStream :: MonadIO m - => CutDb + => CutDb l -> PayloadCaches -> S.Stream (S.Of MiningStateEvent) m r eventStream cdb caches = do @@ -504,7 +504,7 @@ instance Brief MiningStateEvent where -- condition and either warn or throttle cut processing if it ever happens. -- awaitEvent - :: CutDb + :: CutDb l -> PayloadCaches -> Cut -> V.Vector Int @@ -533,7 +533,7 @@ awaitEvent cdb caches c p = randomWork :: HasVersion => LogFunction - -> CutDb + -> CutDb l -> PayloadCaches -> ChainMap (TVar (Maybe ParentState)) -> IO MiningWork @@ -762,5 +762,5 @@ logMinedBlock lf bh np = do , _minedBlockDiscoveredAt = now } -publish :: CutDb -> CutHashes -> IO () +publish :: CutDb l -> CutHashes -> IO () publish = addCutHashes diff --git a/src/Chainweb/Miner/Miners.hs b/src/Chainweb/Miner/Miners.hs index 5a49bdeb98..c95620d66c 100644 --- a/src/Chainweb/Miner/Miners.hs +++ b/src/Chainweb/Miner/Miners.hs @@ -1,15 +1,13 @@ -{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeOperators #-} -{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Miner.Miners @@ -89,7 +87,7 @@ localTest => HasVersion => LogFunction -> MiningCoordination logger - -> CutDb + -> CutDb logger -> MWC.GenIO -> MinerCount -> IO () @@ -143,7 +141,7 @@ localPOW => HasVersion => LogFunctionText -> MiningCoordination logger - -> CutDb + -> CutDb logger -> IO () localPOW lf coord cdb = runForever lf "Chainweb.Miner.Miners.localPOW" $ do c <- _cut cdb diff --git a/src/Chainweb/RestAPI.hs b/src/Chainweb/RestAPI.hs index 3c82fc2406..66459a6d08 100644 --- a/src/Chainweb/RestAPI.hs +++ b/src/Chainweb/RestAPI.hs @@ -133,8 +133,8 @@ serveSocketTls settings certChain key = runTLSSocket tlsSettings settings -- | Datatype for collectively passing all storage backends to -- functions that run a chainweb server. -- -data ChainwebServerDbs t = ChainwebServerDbs - { _chainwebServerCutDb :: !(Maybe CutDb) +data ChainwebServerDbs l t = ChainwebServerDbs + { _chainwebServerCutDb :: !(Maybe (CutDb l)) , _chainwebServerBlockHeaderDbs :: !(ChainMap BlockHeaderDb) , _chainwebServerMempools :: !(ChainMap (MempoolBackend t)) , _chainwebServerPayloads :: !(ChainMap SomeServer) @@ -142,7 +142,7 @@ data ChainwebServerDbs t = ChainwebServerDbs } deriving (Generic) -emptyChainwebServerDbs :: ChainwebServerDbs t +emptyChainwebServerDbs :: ChainwebServerDbs l t emptyChainwebServerDbs = ChainwebServerDbs { _chainwebServerCutDb = Nothing , _chainwebServerBlockHeaderDbs = mempty @@ -201,7 +201,7 @@ someChainwebServer :: HasVersion => Show t => ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> SomeServer someChainwebServer config dbs = maybe mempty (someCutServer cutPeerDb) cuts @@ -226,7 +226,7 @@ someChainwebServerWithHashesAndSpvApi :: HasVersion => Show t => ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> SomeServer someChainwebServerWithHashesAndSpvApi config dbs = maybe mempty (someCutServer cutPeerDb) cuts @@ -251,7 +251,7 @@ chainwebApplication :: HasVersion => Show t => ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> Application chainwebApplication config dbs = chainwebP2pMiddlewares @@ -266,7 +266,7 @@ chainwebApplicationWithHashesAndSpvApi :: HasVersion => Show t => ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> Application chainwebApplicationWithHashesAndSpvApi config dbs = chainwebP2pMiddlewares @@ -278,7 +278,7 @@ serveChainwebOnPort => Show t => Port -> ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> IO () serveChainwebOnPort p c dbs = run (int p) $ chainwebApplication c dbs @@ -287,7 +287,7 @@ serveChainweb => Show t => Settings -> ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> IO () serveChainweb s c dbs = runSettings s $ chainwebApplication c dbs @@ -297,7 +297,7 @@ serveChainwebSocket => Settings -> Socket -> ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> Middleware -> IO () serveChainwebSocket settings sock c dbs m = @@ -311,7 +311,7 @@ serveChainwebSocketTls -> X509KeyPem -> Socket -> ChainwebConfiguration - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> Middleware -> IO () serveChainwebSocketTls settings certChain key sock c dbs m = @@ -344,7 +344,7 @@ servePeerDbSocketTls settings certChain key sock nid pdb m = someServiceApiServer :: Logger logger => HasVersion - => ChainwebServerDbs t + => ChainwebServerDbs l t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) @@ -370,7 +370,7 @@ someServiceApiServer dbs mr (HeaderStream hs) backupEnv pbl = serviceApiApplication :: Logger logger => HasVersion - => ChainwebServerDbs t + => ChainwebServerDbs l t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) @@ -386,7 +386,7 @@ serveServiceApiSocket => HasVersion => Settings -> Socket - -> ChainwebServerDbs t + -> ChainwebServerDbs l t -> Maybe (MiningCoordination logger) -> HeaderStream -> Maybe (BackupEnv logger) diff --git a/src/Chainweb/RestAPI/NodeInfo.hs b/src/Chainweb/RestAPI/NodeInfo.hs index f572bb569c..37009b641d 100644 --- a/src/Chainweb/RestAPI/NodeInfo.hs +++ b/src/Chainweb/RestAPI/NodeInfo.hs @@ -48,7 +48,7 @@ type NodeInfoApi = "info" :> Get '[JSON] NodeInfo someNodeInfoApi :: SomeApi someNodeInfoApi = SomeApi (Proxy @NodeInfoApi) -someNodeInfoServer :: HasVersion => CutDb -> SomeServer +someNodeInfoServer :: HasVersion => CutDb l -> SomeServer someNodeInfoServer c = SomeServer (Proxy @NodeInfoApi) (nodeInfoHandler $ someCutDbVal c) @@ -83,7 +83,7 @@ data NodeInfo = NodeInfo deriving anyclass (ToJSON, FromJSON) nodeInfoHandler :: HasVersion => SomeCutDb -> Server NodeInfoApi -nodeInfoHandler (SomeCutDb (CutDbT db :: CutDbT v)) = do +nodeInfoHandler (SomeCutDb (CutDbT db :: CutDbT l v)) = do curCut <- liftIO $ _cut db let ch = cutToCutHashes Nothing curCut let curHeight = maximum $ map _rankedHeight $ HM.elems $ _cutHashes ch @@ -100,7 +100,7 @@ nodeInfoHandler (SomeCutDb (CutDbT db :: CutDbT v)) = do , nodeLatestBehaviorHeight = latestBehaviorAt , nodeGenesisHeights = map (\c -> (chainIdToText c, genesisHeight c)) $ HS.toList chainIds , nodeHistoricalChains = - ruleElems $ fmap (HM.toList . HM.map HS.toList . toAdjacencySets) $ _versionGraphs implicitVersion + ruleElems $ HM.toList . HM.map HS.toList . toAdjacencySets <$> _versionGraphs implicitVersion , nodeServiceDate = T.pack <$> _versionServiceDate implicitVersion , nodeBlockDelay = _versionBlockDelay implicitVersion , nodePayloadProviders = _versionPayloadProviderTypes implicitVersion <&> \case diff --git a/src/Chainweb/SPV/CreateProof.hs b/src/Chainweb/SPV/CreateProof.hs index f3f7867ae5..a1d243bc5c 100644 --- a/src/Chainweb/SPV/CreateProof.hs +++ b/src/Chainweb/SPV/CreateProof.hs @@ -101,7 +101,7 @@ import Prelude hiding (lookup) -- createTransactionOutputProof :: HasCallStack - => CutDb + => CutDb l -- ^ Block Header Database -> ChainId -- ^ target chain. The proof asserts that the subject is included in diff --git a/src/Chainweb/SPV/RestAPI/Server.hs b/src/Chainweb/SPV/RestAPI/Server.hs index f6a46bfb57..501680c06a 100644 --- a/src/Chainweb/SPV/RestAPI/Server.hs +++ b/src/Chainweb/SPV/RestAPI/Server.hs @@ -52,7 +52,7 @@ import Chainweb.MerkleUniverse spvGetTransactionOutputProofHandler :: HasVersion - => CutDb + => CutDb l -> ChainId -- ^ the target chain of the proof. This is the chain for which inclusion -- is proved. @@ -83,7 +83,7 @@ spvGetTransactionOutputProofHandler db tcid scid bh i = -- spvGetEventProofHandler :: HasVersion - => CutDb + => CutDb l -> ChainId -- ^ the target chain of the proof. This is the chain for which inclusion -- is proved. @@ -101,7 +101,7 @@ spvGetEventProofHandler -- ^ The event index in the transaction -> Handler FakeEventProof spvGetEventProofHandler db tcid scid bh i e = do - (try getProof) >>= \case + try getProof >>= \case Left err -> do let msg = BL8.pack (displayException err) throwError $ case err of @@ -136,10 +136,10 @@ spvGetEventProofHandler db tcid scid bh i e = do -- SPV API Server spvServer - :: forall v (c :: ChainIdT) + :: forall v (c :: ChainIdT) l . KnownChainIdSymbol c => HasVersion - => CutDbT v + => CutDbT l v -> Server (SpvApi v c) spvServer (CutDbT db) = spvGetEventProofHandler db tcid @@ -152,19 +152,19 @@ spvServer (CutDbT db) -- Application for a single Chain spvApp - :: forall v c + :: forall v c l . KnownChainwebVersionSymbol v => HasVersion => KnownChainIdSymbol c - => CutDbT v + => CutDbT l v -> Application spvApp db = serve (Proxy @(SpvApi v c)) (spvServer @v @c db) spvApiLayout - :: forall v c + :: forall v c l . KnownChainwebVersionSymbol v => KnownChainIdSymbol c - => CutDbT v + => CutDbT l v -> IO () spvApiLayout _ = T.putStrLn $ layout (Proxy @(SpvApi v c)) @@ -174,7 +174,7 @@ someSpvServer => HasVersion => SomeCutDb -> SomeServer -someSpvServer (SomeCutDb (db :: CutDbT v)) +someSpvServer (SomeCutDb (db :: CutDbT l v)) = SomeServer (Proxy @(SpvApi v c)) (spvServer @v @c db) -- -------------------------------------------------------------------------- -- @@ -182,7 +182,7 @@ someSpvServer (SomeCutDb (db :: CutDbT v)) someSpvServers :: HasVersion - => CutDb + => CutDb l -> SomeServer someSpvServers db = mconcat $ flip fmap cids $ \(FromSingChainId (SChainId :: Sing c)) -> someSpvServer @c (someCutDbVal db) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 73afe913b1..52fe5e72a6 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -82,6 +82,8 @@ import P2P.TaskQueue import Servant.Client import System.LogLevel import Utils.Logging.Trace +import Chainweb.Logger +import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- -- Response Timeout Constants @@ -145,11 +147,11 @@ data WebBlockPayloadStore tbl = WebBlockPayloadStore -- would be possible to run this on top of any CAS and API that offers -- a simple GET. -- -data WebBlockHeaderStore = WebBlockHeaderStore +data WebBlockHeaderStore l = WebBlockHeaderStore { _webBlockHeaderStoreCas :: !WebBlockHeaderDb , _webBlockHeaderStoreMemo :: !(TaskMap (ChainValue BlockHash) (ChainValue BlockHeader)) , _webBlockHeaderStoreQueue :: !(PQueue (Task ClientEnv (ChainValue BlockHeader))) - , _webBlockHeaderStoreLogFunction :: !LogFunction + , _webBlockHeaderStoreLogger :: l , _webBlockHeaderStoreMgr :: !HTTP.Manager } @@ -306,11 +308,13 @@ instance Exception GetBlockHeaderFailure -- iterative algorithm is preferable. -- getBlockHeaderInternal - :: HasVersion + :: forall l candidateHeaderCas candidatePldTbl + . HasVersion => BlockHeaderCas candidateHeaderCas -- ^ CandidateHeaderCas is a content addressed store for BlockHeaders => ReadableTable candidatePldTbl BlockPayloadHash EncodedPayloadData - => WebBlockHeaderStore + => Logger l + => WebBlockHeaderStore l -- ^ Block Header Store for all Chains -> candidateHeaderCas -- ^ Ephemeral store for block headers under consideration @@ -341,7 +345,7 @@ getBlockHeaderInternal !bh <- memoInsert cas memoMap h $ \k@(ChainValue cid k') -> do -- assertion: h == k - let taskLog l = logg l . taskMsg k + let taskLog = taskLogFun k -- query BlockHeader via -- @@ -512,14 +516,22 @@ getBlockHeaderInternal queue = _webBlockHeaderStoreQueue headerStore wdb = _webBlockHeaderStoreCas headerStore + logger = _webBlockHeaderStoreLogger headerStore + + taskLogger :: forall a . Brief a => T.Text -> ChainValue a -> l + taskLogger taskType k + = addLabel (taskType, brief (_chainValueValue k)) + . addLabel ("chain", toText (_chainValueChainId k)) + $ logger + + taskLogFun k = logFunction (taskLogger "header" k) + logfun :: LogFunction - logfun = _webBlockHeaderStoreLogFunction headerStore + logfun = logFunction logger logg :: LogFunctionText logg = logfun @T.Text - taskMsg k msg = "header task " <> toText k <> ": " <> msg - traceLabel subfun = "Chainweb.Sync.WebBlockHeaderStore.getBlockHeaderInternal." <> subfun @@ -543,14 +555,16 @@ getBlockHeaderInternal queryBlockHeaderTask ck@(ChainValue cid k) = newTask (sshow ck) priority $ \l env -> chainValue <$> do - l @T.Text Debug $ taskMsg ck "query remote block header" + l @T.Text Debug $ taskMsg "query remote block header" let taskEnv = setResponseTimeout taskResponseTimeout env !r <- trace l (traceLabel "queryBlockHeaderTask") k (let Priority i = priority in i) $ TDB.lookupM (rDb cid taskEnv) k `catchAllSynchronous` \e -> do - l @T.Text Debug $ taskMsg ck $ "failed: " <> sshow e + l @T.Text Debug $ taskMsg $ "failed: " <> sshow e throwM e - l @T.Text Debug $ taskMsg ck "received remote block header" + l @T.Text Debug $ taskMsg "received remote block header" return r + where + taskMsg msg = "[" <> toText cid <> ":" <> brief k <> "] " <> msg rDb :: ChainId -> ClientEnv -> RemoteDb rDb cid env = RemoteDb @@ -566,21 +580,23 @@ getBlockHeaderInternal -> Maybe PeerInfo -> IO (Maybe BlockHeader) pullOrigin ck Nothing = do - logg Debug $ taskMsg ck "no origin" + taskLogFun ck Debug "no origin" return Nothing pullOrigin ck@(ChainValue cid k) (Just origin) = do let originEnv = setResponseTimeout pullOriginResponseTimeout $ peerInfoClientEnv mgr origin - logg Debug $ taskMsg ck "lookup origin" + tlog Debug "lookup origin" !r <- trace logfun (traceLabel "pullOrigin") k 0 $ TDB.lookup (rDb cid originEnv) k case r of Nothing -> do - logg Warn $ taskMsg k - $ "failed to pull from origin " <> sshow origin <> " key " <> sshow k + tlog Warn $ "failed to pull from origin " + <> sshow origin <> " key " <> sshow k return Nothing Just !v -> do - logg Debug $ taskMsg ck "received from origin" + tlog Debug "received from origin" return $ Just v + where + tlog = taskLogFun ck -- pullOriginDeps _ Nothing = return () -- pullOriginDeps ck@(ChainValue cid k) (Just origin) = do @@ -596,14 +612,15 @@ getBlockHeaderInternal -- return () newWebBlockHeaderStore - :: HTTP.Manager + :: Logger l + => HTTP.Manager -> WebBlockHeaderDb - -> LogFunction - -> IO WebBlockHeaderStore -newWebBlockHeaderStore mgr wdb logfun = do + -> l + -> IO (WebBlockHeaderStore l) +newWebBlockHeaderStore mgr wdb logger = do m <- new queue <- newEmptyPQueue - return $! WebBlockHeaderStore wdb m queue logfun mgr + return $! WebBlockHeaderStore wdb m queue logger mgr newWebPayloadStore :: HTTP.Manager @@ -620,7 +637,8 @@ getBlockHeader :: HasVersion => BlockHeaderCas candidateHeaderCas => ReadableTable candidatePldTbl BlockPayloadHash EncodedPayloadData - => WebBlockHeaderStore + => Logger l + => WebBlockHeaderStore l -> candidateHeaderCas -> candidatePldTbl -> ChainMap ConfiguredPayloadProvider diff --git a/test/lib/Chainweb/Test/MultiNode.hs b/test/lib/Chainweb/Test/MultiNode.hs index c7bdb3ae49..ca1abe23fa 100644 --- a/test/lib/Chainweb/Test/MultiNode.hs +++ b/test/lib/Chainweb/Test/MultiNode.hs @@ -201,7 +201,7 @@ multiBootstrapConfig conf = conf & set (configP2p . p2pConfigPeer) peerConfig & set (configP2p . p2pConfigKnownPeers) [] where - peerConfig = (head $ testBootstrapPeerConfig $ _configChainwebVersion conf) + peerConfig = head (testBootstrapPeerConfig $ _configChainwebVersion conf) & set peerConfigPort 0 -- Normally, the port of bootstrap nodes is hard-coded. But in -- test-suites that may run concurrently we want to use a port that is @@ -486,7 +486,7 @@ pactImportTest logLevel n rocksDb pactDir step = do let db = pactCopyConns ^?! ix cid getPactStateAtDiffable db snapshotBlockHeight let stateDiff = Map.filter (not . P.isSame) (P.diff srcStateAt tgtStateAt) - when (not (null stateDiff)) $ do + unless (null stateDiff) $ do forM_ (Map.toList stateDiff) $ \(tbl, delta) -> do case delta of P.Same _ -> do @@ -582,9 +582,9 @@ replayTest loglevel n rdb pactDbDir step = do let ct = harvestConsensusState (genericLogger loglevel logFun) stateVar runNodesForSeconds loglevel logFun (multiConfig n) n 60 rdb pactDbDir ct Just stats1 <- consensusStateSummary <$> swapMVar stateVar emptyConsensusState - assertGe "maximum cut height before reset" (Actual $ _statMaxHeight stats1) (Expected $ 10) + assertGe "maximum cut height before reset" (Actual $ _statMaxHeight stats1) (Expected 10) tastylog $ sshow stats1 - tastylog $ "phase 3... replaying" + tastylog "phase 3... replaying" let replayInitialHeight = 5 firstReplayCompleteRef <- newIORef False runNodesForSeconds loglevel logFun @@ -683,7 +683,7 @@ sampleConsensusState => Int -- ^ node Id -> WebBlockHeaderDb - -> CutDb + -> CutDb l -> Casify RocksDbTable CutHashes -> ConsensusState -> IO ConsensusState @@ -712,7 +712,7 @@ consensusStateSummary :: HasVersion => ConsensusState -> Maybe Stats consensusStateSummary s | HM.null (_stateCutMap s) = Nothing | otherwise = Just Stats - { _statBlockCount = int $ hashCount + { _statBlockCount = int hashCount , _statMaxHeight = maxHeight , _statMinHeight = minHeight , _statMedHeight = medHeight diff --git a/test/lib/Chainweb/Test/Utils.hs b/test/lib/Chainweb/Test/Utils.hs index 59da8d2128..0b67bbda41 100644 --- a/test/lib/Chainweb/Test/Utils.hs +++ b/test/lib/Chainweb/Test/Utils.hs @@ -1,7 +1,6 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NumericUnderscores #-} @@ -30,6 +29,7 @@ module Chainweb.Test.Utils , withResourceT , independentSequentialTestGroup , unsafeHeadOf +, noopLogger , TestPact5CommandResult @@ -131,6 +131,47 @@ module Chainweb.Test.Utils ) where import Control.Concurrent +import Chainweb.BlockCreationTime +import Chainweb.BlockHeader +import Chainweb.BlockHeaderDB +import Chainweb.BlockHeaderDB.Internal +import Chainweb.BlockHeight +import Chainweb.BlockWeight +import Chainweb.ChainId +import Chainweb.Chainweb +import Chainweb.Chainweb.Configuration +import Chainweb.Chainweb.PeerResources +import Chainweb.Crypto.MerkleLog hiding (header) +import Chainweb.Cut.CutHashes +import Chainweb.CutDB +import Chainweb.CutDB.RestAPI.Client +import Chainweb.Difficulty (targetToDifficulty) +import Chainweb.Graph +import Chainweb.HostAddress +import Chainweb.Logger +import Chainweb.MerkleUniverse +import Chainweb.Miner.Config +import Chainweb.Pact.Backend.Types(SQLiteEnv) +import Chainweb.Pact.Backend.Utils +import Chainweb.Pact.Mempool.Mempool (TransactionHash(..), BlockFill(..), mockBlockGasLimit) +import Chainweb.Parent +import Chainweb.PayloadProvider.Pact.Configuration +import Chainweb.Ranked (Ranked(_rankedHeight)) +import Chainweb.RestAPI +import Chainweb.RestAPI.NetworkID +import Chainweb.Storage.Table +import Chainweb.Storage.Table.RocksDB +import Chainweb.Test.P2P.Peer.BootstrapConfig (testBootstrapCertificate, testBootstrapKey, testBootstrapPeerConfig) +import Chainweb.Test.Pact.Utils (getTestLogLevel, getTestLogger) +import Chainweb.Test.TestVersions +import Chainweb.Test.Utils.APIValidation +import Chainweb.Test.Utils.BlockHeader +import Chainweb.Time +import Chainweb.TreeDB +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Chainweb.Version +import Chainweb.Version.Utils import Control.Concurrent.STM import Control.Lens import Control.Monad @@ -147,11 +188,13 @@ import Data.Foldable import Data.IORef import Data.List (sortOn, isInfixOf) import Data.Pool (Pool) +import Data.Semigroup import Data.Text qualified as T import Data.Text.IO qualified as T import Data.Tree import Data.Tree.Lens qualified as LT import Data.Word +import Database.RocksDB.Internal qualified as R import Network.Connection qualified as HTTP import Network.HTTP.Client qualified as HTTP import Network.HTTP.Client.TLS qualified as HTTP @@ -161,7 +204,15 @@ import Network.TLS qualified as HTTP import Network.Wai qualified as W import Network.Wai.Handler.Warp qualified as W import Network.Wai.Handler.WarpTLS as W (runTLSSocket) +import Network.X509.SelfSigned import Numeric.Natural +import P2P.Node.Configuration +import P2P.Node.PeerDB qualified as P2P +import P2P.Peer +import Pact.Core.Command.Types qualified as Pact5 +import Pact.Core.Errors qualified as Pact5 +import Pact.Core.Gas qualified as Pact +import Pact.Core.Hash qualified as Pact5 import Servant.Client (BaseUrl(..), ClientEnv, Scheme(..), mkClientEnv, runClientM) import System.Directory (removeDirectoryRecursive, removeFile) import System.Environment (withArgs) @@ -179,64 +230,13 @@ import Test.Tasty.HUnit import Test.Tasty.QuickCheck (property, discard, (.&&.)) import Text.Printf (printf) import UnliftIO.Async -import Chainweb.BlockCreationTime -import Chainweb.BlockHeader -import Chainweb.BlockHeaderDB -import Chainweb.BlockHeaderDB.Internal -import Chainweb.BlockHeight -import Chainweb.BlockWeight -import Chainweb.ChainId -import Chainweb.Chainweb -import Chainweb.Chainweb.Configuration -import Chainweb.Chainweb.PeerResources -import Chainweb.Crypto.MerkleLog hiding (header) -import Chainweb.Cut.CutHashes -import Chainweb.CutDB -import Chainweb.CutDB.RestAPI.Client -import Chainweb.Difficulty (targetToDifficulty) -import Chainweb.Graph -import Chainweb.HostAddress -import Chainweb.Logger -import Chainweb.Pact.Mempool.Mempool (TransactionHash(..), BlockFill(..), mockBlockGasLimit) -import Chainweb.MerkleUniverse -import Chainweb.Miner.Config -import Chainweb.Pact.Backend.Types(SQLiteEnv) -import Chainweb.Pact.Backend.Utils -import Chainweb.Parent -import Chainweb.PayloadProvider.Pact.Configuration -import Chainweb.RestAPI -import Chainweb.RestAPI.NetworkID -import Chainweb.Test.Pact.Utils (getTestLogLevel, getTestLogger) -import Chainweb.Test.P2P.Peer.BootstrapConfig (testBootstrapCertificate, testBootstrapKey, testBootstrapPeerConfig) -import Chainweb.Test.Utils.BlockHeader -import Chainweb.Time -import Chainweb.TreeDB -import Chainweb.Utils -import Chainweb.Utils.Serialization -import Chainweb.Test.TestVersions -import Chainweb.Version -import Chainweb.Version.Utils -import Chainweb.Storage.Table -import Chainweb.Storage.Table.RocksDB -import Database.RocksDB.Internal qualified as R -import Network.X509.SelfSigned -import P2P.Node.Configuration -import P2P.Node.PeerDB qualified as P2P -import P2P.Peer -import Chainweb.Test.Utils.APIValidation -import Data.Semigroup -import Pact.Core.Command.Types qualified as Pact5 -import Pact.Core.Errors qualified as Pact5 -import Pact.Core.Hash qualified as Pact5 -import Pact.Core.Gas qualified as Pact -import Chainweb.Ranked (Ranked(_rankedHeight)) -- -------------------------------------------------------------------------- -- type Step = String -> IO () withResource' :: IO a -> (IO a -> TestTree) -> TestTree -withResource' create act = withResource create (\_ -> return ()) act +withResource' create = withResource create (\_ -> return ()) -- this makes ResourceT general enough to allocate resources both for test -- groups and within tests. @@ -350,6 +350,9 @@ withToyDB db cid mockBlockFill :: BlockFill mockBlockFill = BlockFill mockBlockGasLimit mempty 0 +noopLogger :: GenericLogger +noopLogger = genericLogger Quiet (\_ -> return ()) + -- -------------------------------------------------------------------------- -- -- BlockHeaderDb Generation @@ -535,9 +538,9 @@ starBlockHeaderDbs n dbs = do testHost :: String testHost = "localhost" -data TestClientEnv t = TestClientEnv +data TestClientEnv l t = TestClientEnv { _envClientEnv :: !ClientEnv - , _envCutDb :: !(Maybe CutDb) + , _envCutDb :: !(Maybe (CutDb l)) , _envBlockHeaderDbs :: !(ChainMap BlockHeaderDb) , _envPeerDbs :: ![(NetworkId, P2P.PeerDb)] } @@ -546,21 +549,21 @@ pattern BlockHeaderDbsTestClientEnv :: HasVersion => ClientEnv -> ChainMap BlockHeaderDb - -> TestClientEnv t + -> TestClientEnv l t pattern BlockHeaderDbsTestClientEnv { _cdbEnvClientEnv, _cdbEnvBlockHeaderDbs } <- TestClientEnv _cdbEnvClientEnv Nothing _cdbEnvBlockHeaderDbs [] pattern PeerDbsTestClientEnv :: ClientEnv -> [(NetworkId, P2P.PeerDb)] - -> TestClientEnv t + -> TestClientEnv l t pattern PeerDbsTestClientEnv { _pdbEnvClientEnv, _pdbEnvPeerDbs } <- TestClientEnv _pdbEnvClientEnv Nothing _ _pdbEnvPeerDbs pattern PayloadTestClientEnv :: ClientEnv - -> CutDb - -> TestClientEnv t + -> CutDb l + -> TestClientEnv l t pattern PayloadTestClientEnv { _pEnvClientEnv, _pEnvCutDb } <- TestClientEnv _pEnvClientEnv (Just _pEnvCutDb) _ [] @@ -610,7 +613,7 @@ withChainwebTestServer shouldValidateSpec tls app = do let settings = W.setBeforeMainLoop (putMVar readyVar ()) $ - W.setOnExceptionResponse verboseOnExceptionResponse $ + W.setOnExceptionResponse verboseOnExceptionResponse W.defaultSettings if | tls -> do @@ -628,15 +631,14 @@ withChainwebTestServer shouldValidateSpec tls app = do W.responseLBS HTTP.internalServerError500 [] ("exception: " <> sshow exn) clientEnvWithChainwebTestServer - :: forall t - . Show t + :: Show t => ToJSON t => FromJSON t => HasVersion => ShouldValidateSpec -> Bool - -> ChainwebServerDbs t - -> ResourceT IO (TestClientEnv t) + -> ChainwebServerDbs l t + -> ResourceT IO (TestClientEnv l t) clientEnvWithChainwebTestServer shouldValidateSpec tls dbs = do -- FIXME: Hashes API got removed from the P2P API. We use an application that -- includes this API for testing. We should create comprehensive tests for the @@ -662,7 +664,7 @@ withPeerDbsServer => ShouldValidateSpec -> Bool -> [(NetworkId, P2P.PeerDb)] - -> ResourceT IO (TestClientEnv t) + -> ResourceT IO (TestClientEnv l t) withPeerDbsServer shouldValidateSpec tls peerDbs = clientEnvWithChainwebTestServer shouldValidateSpec tls emptyChainwebServerDbs @@ -676,8 +678,8 @@ withPayloadServer => HasVersion => ShouldValidateSpec -> Bool - -> CutDb - -> ResourceT IO (TestClientEnv t) + -> CutDb l + -> ResourceT IO (TestClientEnv l t) withPayloadServer shouldValidateSpec tls cutDb = clientEnvWithChainwebTestServer shouldValidateSpec tls emptyChainwebServerDbs @@ -692,7 +694,7 @@ withBlockHeaderDbsServer => ShouldValidateSpec -> Bool -> ChainMap BlockHeaderDb - -> ResourceT IO (TestClientEnv t) + -> ResourceT IO (TestClientEnv l t) withBlockHeaderDbsServer shouldValidateSpec tls chainDbs = clientEnvWithChainwebTestServer shouldValidateSpec tls emptyChainwebServerDbs diff --git a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs index 637987a874..29f16deae2 100644 --- a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs +++ b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs @@ -1,11 +1,11 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TupleSections #-} -{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE TypeFamilies #-} -- | -- Module: Chainweb.Test.BlockHeaderDB.PruneForks @@ -21,69 +21,53 @@ module Chainweb.Test.BlockHeaderDB.PruneForks ) where import Control.Concurrent.STM -import Control.Lens -import Control.Monad -import Control.Monad.Catch -import Control.Monad.Trans.Resource -import Control.Monad.IO.Class - --- import Data.CAS --- import Data.CAS.RocksDB -import Data.HashMap.Strict qualified as HM -import Data.Text qualified as T -import Data.Tree (Tree) -import Data.Tree qualified as Tree - -import Numeric.Natural - -import System.LogLevel -import System.Random - -import Test.Tasty -import Test.Tasty.HUnit - --- internal modules - import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeader.Validation import Chainweb.BlockHeaderDB import Chainweb.BlockHeaderDB.Internal import Chainweb.BlockHeaderDB.PruneForks +import Chainweb.ChainValue +import Chainweb.Core.Brief +import Chainweb.Cut (unsafeMkCut, genesisCut) +import Chainweb.Cut.Create +import Chainweb.Cut.CutHashes +import Chainweb.CutDB +import Chainweb.Graph import Chainweb.Logger -import Chainweb.Test.Utils -import Chainweb.Test.Utils.BlockHeader -import Chainweb.Utils -import Chainweb.Version +import Chainweb.Parent +import Chainweb.PayloadProvider (ConfiguredPayloadProvider(DisabledPayloadProvider)) +import Chainweb.Ranked +import Chainweb.Storage.Table import Chainweb.Storage.Table.RocksDB -import Chainweb.CutDB import Chainweb.Test.CutDB (withTestCutDb) -import Chainweb.PayloadProvider (ConfiguredPayloadProvider(DisabledPayloadProvider)) import Chainweb.Test.Pact.Utils -import Chainweb.Storage.Table -import Chainweb.Cut (unsafeMkCut, genesisCut) -import Chainweb.Cut.CutHashes -import Chainweb.Core.Brief import Chainweb.Test.TestVersions -import Chainweb.Graph +import Chainweb.Test.Utils +import Chainweb.Test.Utils.BlockHeader +import Chainweb.TreeDB qualified as TreeDB +import Chainweb.Utils +import Chainweb.Version +import Chainweb.Version.Utils (avgBlockHeightAtCutHeight, chainIdsAt) import Chainweb.WebBlockHeaderDB +import Control.Lens +import Control.Monad +import Control.Monad.Catch +import Control.Monad.IO.Class import Control.Monad.State.Strict -import Chainweb.Parent -import Data.HashMap.Strict (HashMap) -import Data.Maybe (mapMaybe, catMaybes, fromMaybe) -import Chainweb.Cut.Create -import Hedgehog -import Hedgehog.Gen (shuffle) -import qualified Data.HashSet as HS -import System.Random.Shuffle (shuffleM) -import Streaming.Prelude qualified as S -import Control.Monad.Except -import Streaming qualified as S +import Control.Monad.Trans.Resource +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS +import Data.Maybe (mapMaybe, catMaybes) +import Data.Text qualified as T +import Numeric.Natural import PropertyMatchers qualified as P -import Chainweb.ChainValue -import qualified Chainweb.TreeDB as TreeDB -import Chainweb.Ranked -import Chainweb.Version.Utils (avgBlockHeightAtCutHeight, chainIdsAt) +import Streaming qualified as S +import Streaming.Prelude qualified as S +import System.LogLevel +import System.Random.Shuffle (shuffleM) +import Test.Tasty +import Test.Tasty.HUnit -- -------------------------------------------------------------------------- -- -- Utils @@ -102,7 +86,7 @@ testLogLevel = Warn withDbs :: HasVersion => RocksDb - -> ResourceT IO CutDb + -> ResourceT IO (CutDb GenericLogger) withDbs rdb = do -- create unique namespace for each test so that they so that test can -- run in parallel. @@ -282,9 +266,7 @@ insert -> Nonce -> Natural -> IO [BlockHeader] -insert bdb h n l = do - hdrs <- insertN_ n l h bdb - return hdrs +insert bdb h n l = insertN_ n l h bdb cid :: ChainId cid = unsafeChainId 0 @@ -408,7 +390,7 @@ failTest rio n step = withVersion (barebonesTestVersion singletonChainGraph) $ r let db = cdb ^?! cutDbBlockHeaderDb cid let h = genesisBlockHeader cid (f0, _) <- createForks db h - delHdr db $ f0 !! (int n) + delHdr db $ f0 !! int n initialCut <- _cut cdb let wbhdb = view cutDbWebBlockHeaderDb cdb try (pruneForks logg initialCut wbhdb Prune 2) >>= \case diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index ebef7a2de9..76b0d5c9a8 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -114,13 +114,13 @@ withTestCutDb -- create blocks with a well-defined set of test transactions. -- -> logger - -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb) + -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb logger) withTestCutDb rdb conf n providers logger = do rocksDb <- liftIO $ testRocksDb "withTestCutDb" rdb let cutHashesDb = cutHashesTable rocksDb webDb <- liftIO $ initWebBlockHeaderDb rocksDb mgr <- liftIO $ HTTP.newManager HTTP.defaultManagerSettings - headerStore <- withLocalWebBlockHeaderStore mgr webDb + headerStore <- withLocalWebBlockHeaderStore logger mgr webDb Right cutDb <- withCutDb (conf $ defaultCutDbParams cutFetchTimeout) logger headerStore providers cutHashesDb liftIO $ synchronizeProviders webDb genesisCut @@ -130,7 +130,7 @@ withTestCutDb rdb conf n providers logger = do where synchronizeProviders :: WebBlockHeaderDb -> Cut -> IO () synchronizeProviders wbh c = do - let startHeaders = HM.unionWith (\startHeader _genesisHeader -> startHeader) + let startHeaders = HM.unionWith const (_cutHeaders c) (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) mapConcurrently_ syncOne startHeaders @@ -175,7 +175,7 @@ withTestCutDb rdb conf n providers logger = do -- predicate for a given 'Cut' and the results of '_cutStm'. -- awaitCut - :: CutDb + :: CutDb l -> (Cut -> Bool) -> IO Cut awaitCut cdb k = atomically $ do @@ -218,7 +218,7 @@ awaitCut cdb k = atomically $ do -- id -- awaitBlockHeight - :: CutDb + :: CutDb l -> BlockHeight -> ChainId -> IO Cut @@ -243,7 +243,7 @@ withTestCutDbWithoutPact -> Int -- ^ number of blocks in the chainweb in addition to the genesis blocks -> logger - -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb) + -> ResourceT IO (Casify RocksDbTable CutHashes, CutDb logger) withTestCutDbWithoutPact rdb conf n = withTestCutDb rdb conf n (onAllChains DisabledPayloadProvider) @@ -255,7 +255,7 @@ withTestPayloadResource => RocksDb -> Int -> logger - -> ResourceT IO CutDb + -> ResourceT IO (CutDb logger) withTestPayloadResource rdb n logger = view _2 . snd <$> allocate start stopTestPayload where @@ -270,40 +270,44 @@ startTestPayload => RocksDb -> logger -> Int - -> IO (Async (), CutDb) + -> IO (Async (), CutDb logger) startTestPayload rdb logger n = do rocksDb <- testRocksDb "startTestPayload" rdb let cutHashesDb = cutHashesTable rocksDb webDb <- initWebBlockHeaderDb rocksDb mgr <- HTTP.newManager HTTP.defaultManagerSettings - (hserver, hstore) <- startLocalWebBlockHeaderStore mgr webDb + (hserver, hstore) <- startLocalWebBlockHeaderStore logger mgr webDb let disabledPayloadProviders = onAllChains DisabledPayloadProvider Right cutDb <- startCutDb (defaultCutDbParams cutFetchTimeout) logger hstore disabledPayloadProviders cutHashesDb foldM_ (\c _ -> view _1 <$> mine logger cutDb c) genesisCut [0..n] return (hserver, cutDb) -stopTestPayload :: (Async (), CutDb) -> IO () +stopTestPayload :: (Async (), CutDb logger) -> IO () stopTestPayload (hserver, cutDb) = do stopCutDb cutDb cancel hserver withLocalWebBlockHeaderStore - :: HTTP.Manager + :: Logger logger + => logger + -> HTTP.Manager -> WebBlockHeaderDb - -> ResourceT IO WebBlockHeaderStore -withLocalWebBlockHeaderStore mgr webDb = do + -> ResourceT IO (WebBlockHeaderStore logger) +withLocalWebBlockHeaderStore logger mgr webDb = do queue <- withNoopQueueServer mem <- liftIO new - return $ WebBlockHeaderStore webDb mem queue (\_ _ -> return ()) mgr + return $ WebBlockHeaderStore webDb mem queue logger mgr startLocalWebBlockHeaderStore - :: HTTP.Manager + :: Logger logger + => logger + -> HTTP.Manager -> WebBlockHeaderDb - -> IO (Async (), WebBlockHeaderStore) -startLocalWebBlockHeaderStore mgr webDb = do + -> IO (Async (), WebBlockHeaderStore logger) +startLocalWebBlockHeaderStore logger mgr webDb = do (server, queue) <- startNoopQueueServer mem <- new - return (server, WebBlockHeaderStore webDb mem queue (\_ _ -> return ()) mgr) + return (server, WebBlockHeaderStore webDb mem queue logger mgr) -- | Build a linear chainweb (no forks, assuming single threaded use of the -- cutDb). No POW or poison delay is applied. Block times are real times. @@ -313,7 +317,7 @@ mine => HasVersion => Logger logger => logger - -> CutDb + -> CutDb logger -> Cut -> IO (Cut, ChainId, NewPayload) mine logger cutDb c = do @@ -351,7 +355,7 @@ getRandomUnblockedChain c = do isUnblocked h = let bh = view blockHeight h cid = view blockChainId h - in all (>= bh) $ fmap (view blockHeight) $ toList $ cutAdjs c cid + in all ((>= bh) . view blockHeight) $ cutAdjs c cid -- | Build a linear chainweb (no forks). No POW or poison delay is applied. -- Block times are real times. @@ -361,7 +365,7 @@ tryMineForChain => HasVersion -- ^ The miner. For testing you may use 'defaultMiner'. -- miner. - => CutDb + => CutDb l -> Cut -> ChainId -> IO (Either MineFailure (Cut, ChainId, NewPayload)) @@ -393,7 +397,7 @@ tryMineForChain cutDb c cid = do randomBlockHeader :: HasCallStack => HasVersion - => CutDb + => CutDb l -> IO BlockHeader randomBlockHeader cutDb = do curCut <- _cut cutDb @@ -413,7 +417,7 @@ randomTransaction => HasVersion => CanReadablePayloadCas tbl => PayloadDb tbl - -> CutDb + -> CutDb l -> IO (BlockHeader, Int, Transaction, TransactionOutput) randomTransaction payloadDb cutDb = do bh <- randomBlockHeader cutDb diff --git a/test/unit/Chainweb/Test/Mempool/RestAPI.hs b/test/unit/Chainweb/Test/Mempool/RestAPI.hs index 270771d660..32b05a8b22 100644 --- a/test/unit/Chainweb/Test/Mempool/RestAPI.hs +++ b/test/unit/Chainweb/Test/Mempool/RestAPI.hs @@ -102,7 +102,7 @@ newPool = Pool.newPool $ Pool.defaultPoolConfig serverMempools :: ChainMap (MempoolBackend t) - -> ChainwebServerDbs t + -> ChainwebServerDbs l t serverMempools mempools = emptyChainwebServerDbs { _chainwebServerMempools = mempools } diff --git a/test/unit/Chainweb/Test/Pact/CutFixture.hs b/test/unit/Chainweb/Test/Pact/CutFixture.hs index 090c6a5d1d..f760d83dce 100644 --- a/test/unit/Chainweb/Test/Pact/CutFixture.hs +++ b/test/unit/Chainweb/Test/Pact/CutFixture.hs @@ -1,26 +1,24 @@ -{-# language - BangPatterns - , ConstraintKinds - , DataKinds - , DeriveAnyClass - , DerivingStrategies - , FlexibleContexts - , FlexibleInstances - , ImplicitParams - , ImportQualifiedPost - , LambdaCase - , MultiParamTypeClasses - , NumericUnderscores - , OverloadedStrings - , PackageImports - , RankNTypes - , RecordWildCards - , ScopedTypeVariables - , TemplateHaskell - , TupleSections - , TypeApplications - , ViewPatterns -#-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ConstraintKinds #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE ImplicitParams #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE ViewPatterns #-} -- | A fixture which provides access to the internals of a running node, with -- multiple chains. Usually, you initialize it with `mkFixture`, insert @@ -77,12 +75,12 @@ import Data.Maybe (fromMaybe) import Data.Text qualified as Text import Data.Vector (Vector) import GHC.Stack -import qualified Chainweb.Pact.PactService as PactService -import Chainweb.PayloadProvider.Pact +import Chainweb.Pact.PactService qualified as PactService import Chainweb.PayloadProvider +import Chainweb.PayloadProvider.Pact data Fixture = Fixture - { _fixtureCutDb :: CutDb + { _fixtureCutDb :: CutDb GenericLogger , _fixturePayloadDb :: PayloadDb RocksDbTable , _fixtureWebBlockHeaderDb :: WebBlockHeaderDb , _fixtureLogger :: GenericLogger @@ -98,7 +96,12 @@ instance HasFixture Fixture where instance HasFixture a => HasFixture (IO a) where cutFixture = (>>= cutFixture) -mkFixture :: HasVersion => (ChainId -> PayloadWithOutputs) -> PactServiceConfig -> RocksDb -> ResourceT IO Fixture +mkFixture + :: HasVersion + => (ChainId -> PayloadWithOutputs) + -> PactServiceConfig + -> RocksDb + -> ResourceT IO Fixture mkFixture genesisPayloadFor pactServiceConfig baseRdb = do logger <- liftIO getTestLogger testRdb <- liftIO $ testRocksDb "withBlockDbs" baseRdb @@ -152,7 +155,7 @@ advanceAllChains fx = do addNewPayload _fixturePayloadDb latestBlockHeight pwo - return $ (newCut, (cid, commandResults) : acc) + return (newCut, (cid, commandResults) : acc) ) (latestCut, []) (HashSet.toList (chainIdsAt (latestBlockHeight + 1))) @@ -189,7 +192,7 @@ mine :: HasCallStack => HasVersion => ChainId - -> CutDb + -> CutDb l -> Cut -> IO (Cut, ChainId, NewPayload) mine cid cutDb c = do @@ -209,7 +212,7 @@ mine cid cutDb c = do tryMineForChain :: HasCallStack => HasVersion - => CutDb + => CutDb l -> Cut -> ChainId -> IO (Either MineFailure (Cut, ChainId, NewPayload)) diff --git a/test/unit/Chainweb/Test/RestAPI.hs b/test/unit/Chainweb/Test/RestAPI.hs index d743ac74db..8a66d5c61e 100644 --- a/test/unit/Chainweb/Test/RestAPI.hs +++ b/test/unit/Chainweb/Test/RestAPI.hs @@ -1,13 +1,12 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeAbstractions #-} +{-# LANGUAGE TypeApplications #-} {-# options_ghc -fno-warn-unused-local-binds -fno-warn-unused-imports #-} -{-# LANGUAGE RankNTypes #-} -{-# LANGUAGE TypeApplications #-} -{-# LANGUAGE TypeAbstractions #-} -- | -- Module: Chainweb.Test.RestAPI @@ -141,22 +140,23 @@ version = barebonesTestVersion singletonChainGraph -- | The type of 'TestClientEnv' that is used everywhere in this file -- -type TestClientEnv_ = TestClientEnv MockTx +type TestClientEnv_ l = TestClientEnv l MockTx mkEnv - :: (Logger logger, HasVersion) + :: forall logger . HasVersion + => Logger logger => logger -> HTTP.Manager -> RocksDb -> Bool -> ChainMap BlockHeaderDb - -> ResourceT IO TestClientEnv_ + -> ResourceT IO (TestClientEnv_ logger) mkEnv logger mgr rdb tls dbs = do let mkPayloadServer cid = do pp <- newMinimalPayloadProvider logger cid rdb (Just mgr) defaultMinimalProviderConfig let pdb = view minimalPayloadDb pp let queue = view minimalPayloadQueue pp - SomeChainwebVersionT @v' _ <- return $ someChainwebVersionVal + SomeChainwebVersionT @v' _ <- return someChainwebVersionVal SomeChainIdT @c' _ <- return $ someChainIdVal cid -- let serv = liftIO $ somePayloadServer @_ @v' @c' @'MinimalProvider -- logger defaultP2pConfiguration myInfo peerDb pdb queue mgr @@ -177,7 +177,7 @@ simpleSessionTests rdb tls = withVersion version $ $ httpHeaderTests env (head $ toList chainIds) : (simpleClientSession env <$> toList chainIds) -httpHeaderTests :: IO TestClientEnv_ -> ChainId -> TestTree +httpHeaderTests :: IO (TestClientEnv_ l) -> ChainId -> TestTree httpHeaderTests envIO cid = testGroup ("http header tests for chain " <> sshow cid) [ testCase "headerClient" $ go $ \h -> headerClient' cid (key h) @@ -214,7 +214,7 @@ httpHeaderTests envIO cid = (d <= 2) return res -simpleClientSession :: IO TestClientEnv_ -> ChainId -> TestTree +simpleClientSession :: IO (TestClientEnv_ l) -> ChainId -> TestTree simpleClientSession envIO cid = testCaseSteps ("simple session for chain " <> sshow cid) $ \step -> do env <- _envClientEnv <$> envIO From b7d7cb56777afc05410cf6ffd9094d403d442950 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 3 Oct 2025 15:26:52 -0700 Subject: [PATCH 356/378] more logging cleanup --- src/Chainweb/BlockHeaderDB/Internal.hs | 5 +++++ src/Chainweb/BlockHeight.hs | 27 ++++++++++++++++-------- src/Chainweb/Ranked.hs | 26 +++++++++++++++-------- src/Chainweb/Sync/ForkInfo.hs | 21 ++++++++++++------ src/Chainweb/Sync/WebBlockHeaderStore.hs | 25 ++++++++++++---------- src/Chainweb/TreeDB.hs | 11 +++++----- 6 files changed, 74 insertions(+), 41 deletions(-) diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index 45c70d6d31..1c55c6480b 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -143,6 +143,11 @@ instance IsCasValue RankedBlockHeader where = RankedBlockHash (view blockHeight bh) (view blockHash bh) {-# INLINE casKey #-} +instance HasTextRepresentation RankedBlockHeader where + toText= toText + fromText = fromText + {-# INLINE toText #-} + type RankedBlockHeaderCas tbl = Cas tbl RankedBlockHeader instance HasVersion => TreeDbEntry RankedBlockHeader where diff --git a/src/Chainweb/BlockHeight.hs b/src/Chainweb/BlockHeight.hs index a9f1dbad0a..cfd8a5e17f 100644 --- a/src/Chainweb/BlockHeight.hs +++ b/src/Chainweb/BlockHeight.hs @@ -4,7 +4,9 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} @@ -37,19 +39,16 @@ module Chainweb.BlockHeight ) where import Control.DeepSeq - +import Chainweb.Crypto.MerkleLog +import Chainweb.MerkleUniverse +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Control.Monad.Catch import Data.Aeson import Data.Hashable +import Data.Text qualified as T import Data.Word - import GHC.Generics (Generic) - --- Internal imports - -import Chainweb.Crypto.MerkleLog -import Chainweb.MerkleUniverse -import Chainweb.Utils.Serialization - import Numeric.Additive -- -------------------------------------------------------------------------- -- @@ -63,8 +62,18 @@ newtype BlockHeight = BlockHeight { getBlockHeight :: Word64 } , AdditiveSemigroup, AdditiveAbelianSemigroup, AdditiveMonoid , Num, Integral, Real, Enum, Bounded ) + instance Show BlockHeight where show (BlockHeight b) = show b +instance HasTextRepresentation BlockHeight where + toText = sshow + fromText t = case reads (T.unpack t) of + [(h, "")] -> return (BlockHeight h) + _ -> throwM $ TextFormatException $ "BlockHeight: failed to parse: " <> t + + {-# INLINE toText #-} + {-# INLINE fromText #-} + instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag BlockHeight where type Tag BlockHeight = 'BlockHeightTag toMerkleNode = encodeMerkleInputNode encodeBlockHeight diff --git a/src/Chainweb/Ranked.hs b/src/Chainweb/Ranked.hs index 0f873f925c..46681922b2 100644 --- a/src/Chainweb/Ranked.hs +++ b/src/Chainweb/Ranked.hs @@ -1,16 +1,16 @@ +{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MagicHash #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE MagicHash #-} -{-# LANGUAGE KindSignatures #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TemplateHaskell #-} -{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} -- | -- Module: Chainweb.Ranked @@ -40,17 +40,16 @@ module Chainweb.Ranked import Chainweb.BlockHeight import Chainweb.Utils import Chainweb.Utils.Serialization - import Control.DeepSeq import Control.Lens hiding ((.=)) import Control.Monad - import Data.Aeson import Data.Hashable import Data.Typeable (Proxy(..), Typeable, typeRep) - import GHC.Generics (Generic) import GHC.TypeLits +import Data.Text qualified as T +import Control.Monad.Catch -- -------------------------------------------------------------------------- -- -- BlockHeight Ranked Data @@ -84,6 +83,15 @@ decodeRanked decodeA = Ranked <*> decodeA {-# INLINE decodeRanked #-} +instance HasTextRepresentation a => HasTextRepresentation (Ranked a) where + toText a = toText (_rankedHeight a) <> "." <> toText (_ranked a) + fromText t = case T.break (== '.') t of + (h, m) + | T.null m -> throwM $ TextFormatException $ "Ranked: failed to parse: " <> t + | otherwise -> Ranked <$> fromText h <*> fromText (T.tail m) + {-# INLINE toText #-} + {-# INLINE fromText #-} + -- -------------------------------------------------------------------------- -- -- Has Rank Class diff --git a/src/Chainweb/Sync/ForkInfo.hs b/src/Chainweb/Sync/ForkInfo.hs index 0703b95db0..85febe02da 100644 --- a/src/Chainweb/Sync/ForkInfo.hs +++ b/src/Chainweb/Sync/ForkInfo.hs @@ -1,8 +1,8 @@ +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE DeriveGeneric #-} -- | -- Module: Chainweb.Sync.ForkInfo @@ -28,13 +28,16 @@ import Control.Lens import Control.Monad.Catch import Data.List qualified as L import Data.LogMessage +import Data.Maybe import Data.Text qualified as T -import Data.These (partitionHereThere, These (..)) +import Data.These (partitionHereThere) import GHC.Generics (Generic) import GHC.Stack import Streaming.Prelude qualified as S import System.LogLevel +-- -------------------------------------------------------------------------- -- + newtype ForkInfoSyncFailure = ForkInfoSyncFailure T.Text deriving (Show, Eq, Ord, Generic) @@ -83,13 +86,19 @@ resolveForkInfo -> IO () resolveForkInfo logg bhdb provider hints finfo = do + logg Info $ "resolveForkInfo: starting resolution" + <> "; target state: " <> brief (_forkInfoTargetState finfo) + <> "; base payload: " <> brief (_forkInfoBasePayloadHash finfo) + <> "; trace length: " <> sshow (length $ _forkInfoTrace finfo) + <> "; payload requested: " <> sshow (isJust $ _forkInfoNewBlockCtx finfo) + -- Attempt payload validation for the given ForkInfo -- -- We assume that this forkInfo has a trace of "reasonable" size -- r <- syncToBlock provider hints finfo `catch` \(e :: SomeException) -> do - logg Warn $ "getBlockHeaderInternal payload validation for " <> sshow h - <> " failed with: " <> sshow e + logg Warn $ "getBlockHeaderInternal payload validation for " + <> toText h <> " failed with: " <> sshow e throwM e -- Check result of syncToBlock @@ -232,9 +241,7 @@ resolveForkInfoForProviderState logg bhdb provider hints finfo ppState -- newState <- syncToBlock provider hints newForkInfo `catch` \(e :: SomeException) -> do logg Warn $ "getBlockHeaderInternal payload validation retry for " - <> brief h - <> " failed with: " - <> sshow e + <> brief h <> " failed with: " <> sshow e throwM e -- check if we made progress diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 52fe5e72a6..595821702a 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -387,8 +387,9 @@ getBlockHeaderInternal let isGenesisParentHash p = _chainValueValue p == genesisParentBlockHash p queryAdjacentParent p = Concurrently $ unless (isGenesisParentHash p) $ void $ do taskLog Debug - $ "getBlockHeaderInternal.getPrerequisiteHeader (adjacent) for " <> sshow h - <> ": " <> sshow p + $ "getBlockHeaderInternal.getPrerequisiteHeader (adjacent)" + <> "; header: " <> toText (brief <$> h) + <> "; queried adjacent parent: " <> toText (brief <$> p) getBlockHeaderInternal headerStore candidateHeaderCas @@ -405,8 +406,9 @@ getBlockHeaderInternal -- queryParent p = Concurrently $ do taskLog Debug - $ "getBlockHeaderInternal.getPrerequisiteHeader (parent) for " <> sshow h - <> ": " <> sshow p + $ "getBlockHeaderInternal.getPrerequisiteHeader (parent)" + <> "; header: " <> toText (brief <$> h) + <> "; queried parent: " <> toText (brief <$> p) parentHdr <- Parent <$> getBlockHeaderInternal headerStore candidateHeaderCas @@ -424,10 +426,11 @@ getBlockHeaderInternal let hints = Hints <$> maybeOrigin' pld <- tableLookup candidatePldTbl (view blockPayloadHash header) let prefetchProviderPayloads = case providers ^?! atChain cid of - ConfiguredPayloadProvider _provider -> return () + ConfiguredPayloadProvider provider -> do -- TODO PP -- prefetchPayloads provider hints -- [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] + return () DisabledPayloadProvider -> return () parentHdr <- runConcurrently @@ -450,7 +453,7 @@ getBlockHeaderInternal -- -- This requires to provide a CPS version of memoInsert. - taskLog Debug $ "getBlockHeaderInternal got pre-requesites for " <> sshow h + taskLog Debug $ "getBlockHeaderInternal got pre-requesites for " <> toText h -- ------------------------------------------------------------------ -- -- Validation @@ -493,19 +496,19 @@ getBlockHeaderInternal -- finfo <- forkInfoForHeader wdb header pld (Just parentHdr) False - taskLog Debug $ "getBlockHeaderInternal validate payload for " <> sshow h + taskLog Debug $ "getBlockHeaderInternal: validate payload for " <> toText h case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do bhdb <- getWebBlockHeaderDb wdb cid resolveForkInfo taskLog bhdb provider hints finfo DisabledPayloadProvider -> do - taskLog Debug "getBlockHeaderInternal payload provider disabled" + taskLog Debug "getBlockHeaderInternal: payload provider disabled" - taskLog Debug "getBlockHeaderInternal pact validation succeeded" + taskLog Debug "getBlockHeaderInternal payload validation succeeded" - taskLog Debug $ "getBlockHeaderInternal return header " <> sshow h + taskLog Debug $ "getBlockHeaderInternal return header " <> toText h return $! chainValue header - logg Debug $ "getBlockHeaderInternal: got block header for " <> sshow h + logg Debug $ "getBlockHeaderInternal: got block header for " <> toText h return bh where diff --git a/src/Chainweb/TreeDB.hs b/src/Chainweb/TreeDB.hs index b80df04cab..8ebbf20a39 100644 --- a/src/Chainweb/TreeDB.hs +++ b/src/Chainweb/TreeDB.hs @@ -208,6 +208,7 @@ instance class ( Show e , Show (Key e) + , HasTextRepresentation (Key e) , Eq e , Eq (Key e) , Hashable e @@ -262,7 +263,7 @@ class (Typeable db, TreeDbEntry (DbEntry db)) => TreeDb db where -> Natural -> DbKey db -> IO (Maybe (DbEntry db)) - lookupRanked db _ k = lookup db k + lookupRanked db _ = lookup db {-# INLINEABLE lookupRanked #-} -- ---------------------------------------------------------------------- -- @@ -652,7 +653,7 @@ lookupM -> DbKey db -> IO (DbEntry db) lookupM db k = lookup db k >>= \case - Nothing -> throwM $ TreeDbKeyNotFound @db k $ "lookupM: " <> sshow k + Nothing -> throwM $ TreeDbKeyNotFound @db k $ "lookupM " <> toText k Just !x -> return x {-# INLINEABLE lookupM #-} @@ -664,7 +665,7 @@ lookupRankedM -> DbKey db -> IO (DbEntry db) lookupRankedM db r k = lookupRanked db r k >>= \case - Nothing -> throwM $ TreeDbKeyNotFound @db k $ "lookupRankedM (" <> sshow r <> "): " <> sshow k + Nothing -> throwM $ TreeDbKeyNotFound @db k $ "lookupRankedM " <> sshow r <> "." <> toText k Just !x -> return x {-# INLINEABLE lookupRankedM #-} @@ -712,7 +713,7 @@ lookupParentM g db e = case parent e of _ -> throwM $ InternalInvariantViolation "Chainweb.TreeDB.lookupParentM: Called getParentEntry on genesis block" Just p -> lookup db p >>= \case - Nothing -> throwM $ TreeDbParentMissing @db e ("lookupParentM: " <> sshow p) + Nothing -> throwM $ TreeDbParentMissing @db e ("lookupParentM " <> toText p) Just !x -> return x -- | Replace all entries in the stream by their parent entries. @@ -732,7 +733,7 @@ lookupParentStreamM g db = S.mapMaybeM $ \e -> case parent e of GenesisParentThrow -> throwM $ InternalInvariantViolation "Chainweb.TreeDB.lookupParentStreamM: Called getParentEntry on genesis block. Most likely this means that the genesis headers haven't been generated correctly. If you are using a development or testing chainweb version consider resetting the databases." Just p -> lookup db p >>= \case - Nothing -> throwM $ TreeDbParentMissing @db e $ "lookupParentStreamM: " <> sshow p + Nothing -> throwM $ TreeDbParentMissing @db e $ "lookupParentStreamM " <> toText p Just !x -> return (Just x) -- | Interpret a given `BlockHeaderDb` as a native Haskell `Tree`. Should be From 2693f775d1c133ceeddcfcdcdfb9f88fbaf33365 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 3 Oct 2025 18:26:49 -0700 Subject: [PATCH 357/378] provide candidate headers for forkinfo resolution --- .../src/Chainweb/Storage/Table.hs | 17 +++++++++++ src/Chainweb/BlockHeader.hs | 1 + src/Chainweb/BlockHeader/Internal.hs | 6 ++-- src/Chainweb/CutDB.hs | 7 ++--- src/Chainweb/Sync/ForkInfo.hs | 29 ++++++++++++------- src/Chainweb/Sync/WebBlockHeaderStore.hs | 2 +- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/libs/chainweb-storage/src/Chainweb/Storage/Table.hs b/libs/chainweb-storage/src/Chainweb/Storage/Table.hs index 1efc7fe37e..c75bc81afa 100644 --- a/libs/chainweb-storage/src/Chainweb/Storage/Table.hs +++ b/libs/chainweb-storage/src/Chainweb/Storage/Table.hs @@ -49,6 +49,7 @@ module Chainweb.Storage.Table , tableLookupM , casLookupM , TableException (..) + , NullCas(..) ) where @@ -76,6 +77,9 @@ class Eq (CasKeyType v) => IsCasValue v where type CasKeyType v casKey :: v -> CasKeyType v +-- -------------------------------------------------------------------------- -- +-- Readble Stores + -- | Read-Only View of a Key-Value Store -- class ReadableTable t k v | t -> k v where @@ -100,6 +104,13 @@ class ReadableTable t k v => Table t k v | t -> k v where tableDeleteBatch t ks = traverse_ (tableDelete t) ks type Table1 t = forall k v. Table (t k v) k v +data NullCas k v = NullCas +instance ReadableTable (NullCas k v) k v where + tableLookup _ _ = return Nothing + +-- -------------------------------------------------------------------------- -- +-- Writeable Stores + type Cas t v = Table t (CasKeyType v) v casInsert :: (IsCasValue v, Cas t v) => t -> v -> IO () @@ -114,6 +125,9 @@ casDelete t = tableDelete t . casKey casDeleteBatch :: (IsCasValue v, Cas t v) => t -> [v] -> IO () casDeleteBatch t = tableDeleteBatch t . fmap casKey +-- -------------------------------------------------------------------------- -- +-- Iterable Stores + class (Table t k v, Iterator i k v) => IterableTable t i k v | t -> i k v where -- the created iterator must be positioned at the start of the table. withTableIterator :: t -> (i -> IO a) -> IO a @@ -141,6 +155,9 @@ type Iterator1 i = forall k v. Iterator (i k v) k v type CasIterator i v = Iterator i (CasKeyType v) v +-- -------------------------------------------------------------------------- -- +-- Utils + -- | A newtype wrapper that takes only a single type constructor. This useful in -- situations where a Higher Order type constructor for a CAS is required. A -- type synonym doesn't work in this situation because type synonyms must be diff --git a/src/Chainweb/BlockHeader.hs b/src/Chainweb/BlockHeader.hs index 0d76f245db..9958a2c646 100644 --- a/src/Chainweb/BlockHeader.hs +++ b/src/Chainweb/BlockHeader.hs @@ -101,6 +101,7 @@ module Chainweb.BlockHeader -- * CAS Constraint , I.BlockHeaderCas +, I.ReadableBlockHeaderCas ) where diff --git a/src/Chainweb/BlockHeader/Internal.hs b/src/Chainweb/BlockHeader/Internal.hs index b62e68130a..eb6cb767dc 100644 --- a/src/Chainweb/BlockHeader/Internal.hs +++ b/src/Chainweb/BlockHeader/Internal.hs @@ -4,6 +4,7 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE EmptyCase #-} {-# LANGUAGE FlexibleContexts #-} @@ -23,9 +24,6 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE DeriveFunctor #-} -{-# LANGUAGE DeriveFoldable #-} -{-# LANGUAGE DeriveTraversable #-} -- | -- Module: Chainweb.BlockHeader @@ -133,6 +131,7 @@ module Chainweb.BlockHeader.Internal , newBlockHeader -- * CAS Constraint +, ReadableBlockHeaderCas , BlockHeaderCas -- * Misc @@ -396,6 +395,7 @@ instance IsCasValue BlockHeader where casKey = _blockHash {-# INLINE casKey #-} +type ReadableBlockHeaderCas tbl = ReadableCas tbl BlockHeader type BlockHeaderCas tbl = Cas tbl BlockHeader -- | Used for quickly identifying "which block" this is. diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 84a0d234f5..5a51759d03 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -527,11 +527,8 @@ synchronizeProviders logger wbh providers c = do finfo <- liftIO $ forkInfoForHeader wbh hdr Nothing Nothing True pLog Debug $ "syncToBlock with fork info " <> encodeToText finfo - -- FIXME does this really cover all failure scenarios? What if - -- the payload provider is only slow in syncing? Also the protocol - -- allows partial results -- in which case we should retry. bhdb <- liftIO $ getWebBlockHeaderDb wbh cid - liftIO (resolveForkInfo pLog bhdb provider Nothing finfo) `catch` \(e :: SomeException) -> do + liftIO (resolveForkInfo pLog bhdb NullCas provider Nothing finfo) `catch` \(e :: SomeException) -> do pLog Warn $ "resolveFork for failed" <> "; finfo: " <> encodeToText finfo <> "; failure: " <> sshow e @@ -719,7 +716,7 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar cutPru -- FIXME: we could we trigger this with only a -- single node in the system? clog Info "Syncing paylooad provider with merged cut" - resolveForkInfo clog (hdrStore ^?! ixg cid) provider Nothing finfo `catch` + resolveForkInfo clog (hdrStore ^?! ixg cid) NullCas provider Nothing finfo `catch` -- FIXME calling error is not OK! \(e :: SomeException) -> error $ "Failed to sync to merge cut (on chain " <> sshow cid <> "): " <> sshow e diff --git a/src/Chainweb/Sync/ForkInfo.hs b/src/Chainweb/Sync/ForkInfo.hs index 85febe02da..86a19c9610 100644 --- a/src/Chainweb/Sync/ForkInfo.hs +++ b/src/Chainweb/Sync/ForkInfo.hs @@ -3,6 +3,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE FlexibleContexts #-} -- | -- Module: Chainweb.Sync.ForkInfo @@ -35,6 +36,8 @@ import GHC.Generics (Generic) import GHC.Stack import Streaming.Prelude qualified as S import System.LogLevel +import Control.Applicative +import Chainweb.Storage.Table -- -------------------------------------------------------------------------- -- @@ -77,14 +80,16 @@ forkInfoChunkSize = 1000 resolveForkInfo :: HasCallStack => HasVersion + => ReadableBlockHeaderCas hdrCas => PayloadProvider p => LogFunctionText -> BlockHeaderDb -- FIXME use RankedBlockHeaderDb + -> hdrCas -> p -> Maybe Hints -> ForkInfo -> IO () -resolveForkInfo logg bhdb provider hints finfo = do +resolveForkInfo logg bhdb candidateHdrs provider hints finfo = do logg Info $ "resolveForkInfo: starting resolution" <> "; target state: " <> brief (_forkInfoTargetState finfo) @@ -113,7 +118,7 @@ resolveForkInfo logg bhdb provider hints finfo = do -- payload provider that is not caught up yet) -- else do - resolveForkInfoForProviderState logg bhdb provider hints finfo r + resolveForkInfoForProviderState logg bhdb candidateHdrs provider hints finfo r where h = _latestRankedBlockHash . _forkInfoTargetState $ finfo @@ -128,16 +133,18 @@ resolveForkInfo logg bhdb provider hints finfo = do resolveForkInfoForProviderState :: HasCallStack => HasVersion + => ReadableBlockHeaderCas hdrCas => PayloadProvider p => LogFunctionText -> BlockHeaderDb -- FIXME use RankedBlockHeaderDb + -> hdrCas -> p -> Maybe Hints -> ForkInfo -> ConsensusState -> IO () -resolveForkInfoForProviderState logg bhdb provider hints finfo ppState - | ppRBH == h = do +resolveForkInfoForProviderState logg bhdb candidateHdrs provider hints finfo ppState + | ppRBH == trgHash = do -- nothing to do, we are at the target logg Info "resolveForkInfo: payload provider is at target block" return () @@ -145,11 +152,11 @@ resolveForkInfoForProviderState logg bhdb provider hints finfo ppState logg Info $ "resolveForkInfo: payload provider state: " <> brief ppState <> "; target state: " <> brief (_forkInfoTargetState finfo) - -- FIXME this isn't yet available for new blocks that are currently - -- being validated. In that case it should be provided by the caller. - hdr <- lookupRankedM bhdb (int $ _rankedHeight h) (_ranked h) + hdr :: BlockHeader <- do + casLookupM candidateHdrs (_ranked trgHash) + <|> lookupRankedM bhdb (int $ _rankedHeight trgHash) (_ranked trgHash) `catch` \(e :: SomeException) -> do - logg Warn $ "validatePayload: target block is missing: " <> brief h + logg Warn $ "validatePayload: target block is missing: " <> brief trgHash throwM e -- Lookup the state of the Payload Provider and query the trace @@ -241,7 +248,7 @@ resolveForkInfoForProviderState logg bhdb provider hints finfo ppState -- newState <- syncToBlock provider hints newForkInfo `catch` \(e :: SomeException) -> do logg Warn $ "getBlockHeaderInternal payload validation retry for " - <> brief h <> " failed with: " <> sshow e + <> brief trgHash <> " failed with: " <> sshow e throwM e -- check if we made progress @@ -258,7 +265,7 @@ resolveForkInfoForProviderState logg bhdb provider hints finfo ppState <> "; target state: " <> brief (_forkInfoTargetState newForkInfo) -- continue. -- TODO compute the new fork info here. - resolveForkInfoForProviderState logg bhdb provider hints newForkInfo newState + resolveForkInfoForProviderState logg bhdb candidateHdrs provider hints newForkInfo newState else do logg Warn $ "resolveForkInfo: no progress" <> "; delta: " <> sshow delta @@ -276,6 +283,6 @@ resolveForkInfoForProviderState logg bhdb provider hints finfo ppState <> "; new payload provider state: " <> brief newState <> "; target state: " <> brief (_forkInfoTargetState newForkInfo) where - h = _latestRankedBlockHash . _forkInfoTargetState $ finfo + trgHash = _latestRankedBlockHash . _forkInfoTargetState $ finfo ppRBH = _syncStateRankedBlockHash $ _consensusStateLatest ppState diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 595821702a..d546cc9d3e 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -500,7 +500,7 @@ getBlockHeaderInternal case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do bhdb <- getWebBlockHeaderDb wdb cid - resolveForkInfo taskLog bhdb provider hints finfo + resolveForkInfo taskLog bhdb candidateHeaderCas provider hints finfo DisabledPayloadProvider -> do taskLog Debug "getBlockHeaderInternal: payload provider disabled" From fecc0df0a1c1495a654a5c4d27a1fd286b01a551 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Fri, 3 Oct 2025 19:04:51 -0700 Subject: [PATCH 358/378] Add queried headers to the candidate headers --- src/Chainweb/BlockHash.hs | 1 + src/Chainweb/PayloadProvider.hs | 13 ++++++------- src/Chainweb/Sync/WebBlockHeaderStore.hs | 8 +++++++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Chainweb/BlockHash.hs b/src/Chainweb/BlockHash.hs index 454f009a7e..3cee11d29a 100644 --- a/src/Chainweb/BlockHash.hs +++ b/src/Chainweb/BlockHash.hs @@ -355,3 +355,4 @@ instance FromJSON RankedBlockHash where <$> o .: "height" <*> o .: "hash" {-# INLINE parseJSON #-} + diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index fc34683019..6dad91e67c 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GADTs #-} @@ -7,9 +8,8 @@ {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeFamilies #-} -- | -- Module: Chainweb.PayloadProvider @@ -106,9 +106,9 @@ import Chainweb.BlockHeight import Chainweb.BlockPayloadHash import Chainweb.MinerReward import Chainweb.Parent +import Chainweb.Ranked import Chainweb.Utils import Chainweb.Version - import Control.Concurrent.STM import Control.DeepSeq (NFData) import Control.Lens hiding ((.=)) @@ -117,15 +117,14 @@ import Control.Monad.Catch import Control.Monad.IO.Class import Data.Aeson import Data.ByteString qualified as B +import Data.Function +import Data.Hashable +import Data.Maybe import Data.Text qualified as T import GHC.Generics (Generic) import Numeric.Natural import P2P.Peer import Streaming.Prelude qualified as S -import Data.Function -import Data.Hashable -import Data.Maybe -import Chainweb.Ranked -- -------------------------------------------------------------------------- -- -- Exceptions diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index d546cc9d3e..0aadcd05e5 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -355,6 +355,9 @@ getBlockHeaderInternal -- - cut origin, or -- - task queue of P2P network -- + -- we inser the header into the candidate cas, so that the + -- payload provider can use it. + -- (maybeOrigin', header) <- tableLookup candidateHeaderCas k' >>= \case Just !x -> return (maybeOrigin, x) Nothing -> pullOrigin k maybeOrigin >>= \case @@ -362,8 +365,11 @@ getBlockHeaderInternal t <- queryBlockHeaderTask k pQueueInsert queue t (ChainValue _ !x) <- awaitTask t + casInsert candidateHeaderCas x return (Nothing, x) - Just !x -> return (maybeOrigin, x) + Just !x -> do + casInsert candidateHeaderCas x + return (maybeOrigin, x) -- Check that the chain id is correct. The candidate cas is indexed just -- by the block hash. So, if this fails it is most likely a bug in code From ce5e053546aa5bb1f236adc60b178135948bd6e2 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sat, 4 Oct 2025 15:11:31 -0700 Subject: [PATCH 359/378] more log message formatting fixes --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 0aadcd05e5..7c0bdd582f 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -341,7 +341,7 @@ getBlockHeaderInternal maybeOrigin h = do - logg Debug $ "getBlockHeaderInternal: " <> sshow h + logg Debug $ "getBlockHeaderInternal: " <> toText h !bh <- memoInsert cas memoMap h $ \k@(ChainValue cid k') -> do -- assertion: h == k @@ -563,7 +563,7 @@ getBlockHeaderInternal -- addNewPayload (_webBlockPayloadStoreCas payloadStore) (view blockHeight hdr) outs queryBlockHeaderTask ck@(ChainValue cid k) - = newTask (sshow ck) priority $ \l env -> chainValue <$> do + = newTask (TaskId (toText ck)) priority $ \l env -> chainValue <$> do l @T.Text Debug $ taskMsg "query remote block header" let taskEnv = setResponseTimeout taskResponseTimeout env !r <- trace l (traceLabel "queryBlockHeaderTask") k (let Priority i = priority in i) @@ -599,7 +599,7 @@ getBlockHeaderInternal case r of Nothing -> do tlog Warn $ "failed to pull from origin " - <> sshow origin <> " key " <> sshow k + <> toText origin <> " key " <> toText k return Nothing Just !v -> do tlog Debug "received from origin" From d37c178195e06c93ae39f12cf7dc96fa96ad45a4 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Oct 2025 02:13:21 -0700 Subject: [PATCH 360/378] More code cleanups --- .../src/Chainweb/Storage/Table.hs | 8 ++++---- src/Chainweb/CutDB/Sync.hs | 4 ++++ src/Chainweb/Pact/PactService.hs | 18 +++++++++--------- src/Chainweb/Pact/Payload/RestAPI/Client.hs | 8 ++++---- src/Chainweb/PayloadProvider/EVM.hs | 14 +++++++------- src/Chainweb/PayloadProvider/Minimal.hs | 3 ++- .../PayloadProvider/Minimal/Payload.hs | 5 +---- 7 files changed, 31 insertions(+), 29 deletions(-) diff --git a/libs/chainweb-storage/src/Chainweb/Storage/Table.hs b/libs/chainweb-storage/src/Chainweb/Storage/Table.hs index c75bc81afa..1c613decd5 100644 --- a/libs/chainweb-storage/src/Chainweb/Storage/Table.hs +++ b/libs/chainweb-storage/src/Chainweb/Storage/Table.hs @@ -98,10 +98,10 @@ type ReadableTable1 t = forall k v. ReadableTable (t k v) k v class ReadableTable t k v => Table t k v | t -> k v where tableInsert :: t -> k -> v -> IO () tableInsertBatch :: t -> [(k, v)] -> IO () - tableInsertBatch t kvs = traverse_ (uncurry (tableInsert t)) kvs + tableInsertBatch t = traverse_ (uncurry (tableInsert t)) tableDelete :: t -> k -> IO () tableDeleteBatch :: t -> [k] -> IO () - tableDeleteBatch t ks = traverse_ (tableDelete t) ks + tableDeleteBatch t = traverse_ (tableDelete t) type Table1 t = forall k v. Table (t k v) k v data NullCas k v = NullCas @@ -146,9 +146,9 @@ class Iterator i k v | i -> k v where iterPrev :: i -> IO () iterEntry :: i -> IO (Maybe (Entry k v)) iterKey :: i -> IO (Maybe k) - iterKey i = (fmap . fmap) (\(Entry k _) -> k) $ iterEntry i + iterKey i = fmap (\(Entry k _) -> k) <$> iterEntry i iterValue :: i -> IO (Maybe v) - iterValue i = (fmap . fmap) (\(Entry _ v) -> v) $ iterEntry i + iterValue i = fmap (\(Entry _ v) -> v) <$> iterEntry i iterValid :: i -> IO Bool iterValid i = isJust <$> iterKey i type Iterator1 i = forall k v. Iterator (i k v) k v diff --git a/src/Chainweb/CutDB/Sync.hs b/src/Chainweb/CutDB/Sync.hs index 123f1bec44..70639d9d68 100644 --- a/src/Chainweb/CutDB/Sync.hs +++ b/src/Chainweb/CutDB/Sync.hs @@ -79,6 +79,10 @@ getCut (CutClientEnv env) h = runClientThrowM (cutGetClientLimit (int h)) env -- This number MUST BE STRICTLY SMALLER than 'Chainweb.CutDB.farAheadThreshold' -- times the number of chains. -- +-- FIXME FIXME FIXME: +-- The current number works for at least 25 chains. It does not work on current +-- mainet. The type should be changed to BlockHeight to avoid these issues. +-- catchupStepSize :: CutHeight catchupStepSize = 500 diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index 5d53345fe3..e697b7d350 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -1,21 +1,21 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} -{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE OverloadedRecordDot #-} -{-# LANGUAGE TypeAbstractions #-} -- | -- Module: Chainweb.Pact.PactService @@ -779,22 +779,22 @@ getPayloadForContext getPayloadForContext logger serviceEnv h ctx = do mapM_ insertPayloadData (_consensusPayloadData $ _evaluationCtxPayload ctx) - pld <- liftIO $ getPayload + pld <- getPayload pdb candPdb (Priority $ negate $ int $ _evaluationCtxCurrentHeight ctx) (_hintsOrigin <$> h) (_evaluationCtxRankedPayloadHash ctx) - liftIO $ tableInsert candPdb rh pld + tableInsert candPdb rh pld return pld - where + where rh = _evaluationCtxRankedPayloadHash ctx pdb = view psPdb serviceEnv candPdb = view psCandidatePdb serviceEnv insertPayloadData (EncodedPayloadData epld) = case decodePayloadData epld of - Right pld -> liftIO $ tableInsert candPdb rh pld - Left e -> do + Right pld -> tableInsert candPdb rh pld + Left e -> logFunctionText logger Warn $ "failed to decode encoded payload from evaluation ctx: " <> sshow e execPreInsertCheckReq diff --git a/src/Chainweb/Pact/Payload/RestAPI/Client.hs b/src/Chainweb/Pact/Payload/RestAPI/Client.hs index 4d67864ca0..d031f2f145 100644 --- a/src/Chainweb/Pact/Payload/RestAPI/Client.hs +++ b/src/Chainweb/Pact/Payload/RestAPI/Client.hs @@ -55,7 +55,7 @@ payloadClient -> Maybe BlockHeight -> ClientM PayloadData payloadClient c k h = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + SomeChainwebVersionT (_ :: Proxy v) <- return someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ payloadClient_ @v @c k h @@ -79,7 +79,7 @@ payloadBatchClient -> BatchBody -> ClientM PayloadDataList payloadBatchClient c k = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + SomeChainwebVersionT (_ :: Proxy v) <- return someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ payloadBatchClient_ @v @c k @@ -102,7 +102,7 @@ outputsClient -> Maybe BlockHeight -> ClientM PayloadWithOutputs outputsClient c k h = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + SomeChainwebVersionT (_ :: Proxy v) <- return someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ outputsClient_ @v @c k h @@ -123,6 +123,6 @@ outputsBatchClient -> BatchBody -> ClientM PayloadWithOutputsList outputsBatchClient c k = runIdentity $ do - SomeChainwebVersionT (_ :: Proxy v) <- return $ someChainwebVersionVal + SomeChainwebVersionT (_ :: Proxy v) <- return someChainwebVersionVal SomeChainIdT (_ :: Proxy c) <- return $ someChainIdVal c return $ outputsBatchClient_ @v @c k diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 97af873d57..a317e7b1bf 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -126,7 +126,7 @@ payloadDbConfiguration -> RocksDb -> EVM.Header -> EvmDB.Configuration -payloadDbConfiguration c rdb hdr = EvmDB.configuration c rdb hdr +payloadDbConfiguration = EvmDB.configuration -- -------------------------------------------------------------------------- -- -- Configuration @@ -303,7 +303,7 @@ latestStateIO = fmap (_consensusStateLatest . sfst) . stateIO isPayloadRequestedIO :: EvmPayloadProvider logger -> IO Bool isPayloadRequestedIO p = case _evmMinerAddress p of Nothing -> return False - Just _ -> (isJust . ssnd) <$> stateIO p + Just _ -> isJust . ssnd <$> stateIO p newBlockCtxIO :: EvmPayloadProvider logger -> IO (Maybe NewBlockCtx) newBlockCtxIO p = case _evmMinerAddress p of @@ -370,7 +370,7 @@ lookupConsensusState p cs plds = do -- Fill in payloads that are missing in the database from the candidate -- payloads. -- - case go <$> (zip [lrh, srh, frh] r0) of + case go <$> zip [lrh, srh, frh] r0 of [Nothing, _, _] -> do return Nothing [Just l, Just s, Just f] -> return $ Just ForkchoiceStateV1 @@ -399,7 +399,7 @@ loggS -> LogLevel -> T.Text -> IO () -loggS p s l t = logFunctionText logger l t +loggS p s = logFunctionText logger where logger = _evmLogger p & addLabel ("sub-component", s) @@ -409,7 +409,7 @@ logg -> LogLevel -> T.Text -> IO () -logg p l t = logFunctionText (_evmLogger p) l t +logg p = logFunctionText (_evmLogger p) -- -------------------------------------------------------------------------- -- -- Exceptions @@ -1433,7 +1433,7 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do -- FIXME FIXME FIXME -- -- The follwoing can result in timeouts. Even on empty - -- blocks. But why? Well, see the commant above! + -- blocks. But why? Well, see the comment above! -- -- It seems that it is a problem when we have a header -- in the payload db (we know it) but the EVM does not @@ -1518,7 +1518,7 @@ pruneCandidates p = do -- relevant during catchup. Otherwise we anyways go block by block.) -- 3. With the EVM provider we can validate the payloads with respect to the -- evaluation contexts in batches before sending them to the execution --- client.contexts in batches before sending them to the execution client. +-- client. -- getPayloadForContext :: Logger logger diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index 8b36da95cb..91603e4ae6 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -342,6 +342,7 @@ instance PayloadProvider MinimalPayloadProvider where syncToBlock = minimalSyncToBlock latestPayloadSTM = minimalLatestPayloadStm latestPayloadIO = minimalLatestPayloadIO + eventProof = error "Chainweb.PayloadProvider.Minimal.eventProof: not implemented" -- | Fetch a payload for an evaluation context and insert it into the candidate -- table. @@ -437,7 +438,7 @@ minimalSyncToBlock p h i = do latestState = _consensusStateLatest $ _forkInfoTargetState i logg :: MinimalPayloadProvider -> LogLevel -> T.Text -> IO () -logg p l t = _minimalLogger p l t +logg = _minimalLogger makeNewPayload :: HasVersion diff --git a/src/Chainweb/PayloadProvider/Minimal/Payload.hs b/src/Chainweb/PayloadProvider/Minimal/Payload.hs index e2a2109a92..751c51af09 100644 --- a/src/Chainweb/PayloadProvider/Minimal/Payload.hs +++ b/src/Chainweb/PayloadProvider/Minimal/Payload.hs @@ -1,13 +1,10 @@ {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE KindSignatures #-} -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} @@ -112,7 +109,7 @@ account bs encodeAccount :: HasCallStack => Account -> Put encodeAccount (Account bs) = do let l = BS.length bs - void $ when (l > int (maxBound @Word16)) $ + when (l > int (maxBound @Word16)) $ error "Chainweb.PayloadProvider.Minimal.encodePayload: account is too large" putWord16le (int l) putShortByteString bs From 727781ca0a715273920d13257c6d44b2970466e9 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Oct 2025 02:14:30 -0700 Subject: [PATCH 361/378] Prefetch payloads in batches from origins --- src/Chainweb/Pact/PactService.hs | 67 ++++++++- src/Chainweb/PayloadProvider.hs | 8 ++ src/Chainweb/PayloadProvider/EVM.hs | 76 +++++++++- src/Chainweb/PayloadProvider/Minimal.hs | 63 +++++++-- src/Chainweb/PayloadProvider/P2P.hs | 131 +++++++++++++++++- src/Chainweb/PayloadProvider/P2P/RestAPI.hs | 6 +- .../PayloadProvider/P2P/RestAPI/Client.hs | 25 ++-- src/Chainweb/PayloadProvider/Pact.hs | 12 +- src/Chainweb/Sync/WebBlockHeaderStore.hs | 5 +- 9 files changed, 348 insertions(+), 45 deletions(-) diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index e697b7d350..a60e42e5ff 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -37,35 +37,39 @@ module Chainweb.Pact.PactService , withPactService , execNewGenesisBlock , makeEmptyBlock + , getPayloadsForConsensusPayloads ) where import Control.Concurrent.Async import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeight +import Chainweb.BlockPayloadHash import Chainweb.ChainId import Chainweb.Core.Brief import Chainweb.Counter import Chainweb.Logger -import Chainweb.Pact.Mempool.Mempool as Mempool import Chainweb.Miner.Pact import Chainweb.Pact.Backend.ChainwebPactDb qualified as ChainwebPactDb import Chainweb.Pact.Backend.ChainwebPactDb qualified as Pact import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils (withTransaction) +import Chainweb.Pact.Mempool.Mempool as Mempool import Chainweb.Pact.NoCoinbase qualified as Pact import Chainweb.Pact.PactService.Checkpointer qualified as Checkpointer import Chainweb.Pact.PactService.ExecBlock import Chainweb.Pact.PactService.ExecBlock qualified as Pact import Chainweb.Pact.PactService.Pact4.ExecBlock qualified as Pact4 +import Chainweb.Pact.Payload +import Chainweb.Pact.Payload.PayloadStore +import Chainweb.Pact.Payload.RestAPI +import Chainweb.Pact.Payload.RestAPI.Client import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.TransactionExec qualified as Pact import Chainweb.Pact.Types import Chainweb.Pact.Validations qualified as Pact import Chainweb.Pact4.Backend.ChainwebPactDb qualified as Pact4 import Chainweb.Parent -import Chainweb.Pact.Payload -import Chainweb.Pact.Payload.PayloadStore import Chainweb.PayloadProvider import Chainweb.PayloadProvider.P2P import Chainweb.Ranked @@ -74,6 +78,8 @@ import Chainweb.Storage.Table.Map qualified as MapTable import Chainweb.Time import Chainweb.Utils hiding (check) import Chainweb.Version +import Chainweb.Version.Guards (pact5) +import Control.Concurrent.MVar (newMVar) import Control.Concurrent.STM import Control.Exception.Safe (mask) import Control.Lens hiding ((:>)) @@ -113,10 +119,8 @@ import Pact.Core.Hash qualified as Pact import Pact.Core.StableEncoding qualified as Pact import Pact.JSON.Encode qualified as J import Prelude hiding (lookup) +import Servant.Client (ClientM) import System.LogLevel -import Chainweb.Version.Guards (pact5) -import Control.Concurrent.MVar (newMVar) -import Chainweb.Pact.Payload.RestAPI.Client (payloadClient) withPactService :: (Logger logger, CanPayloadCas tbl) @@ -133,7 +137,13 @@ withPactService -> GenesisConfig -> ResourceT IO (ServiceEnv tbl) withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb readSqlPool readWriteSqlenv config pactGenesis = do - payloadStore <- liftIO $ newPayloadStore http (logFunction chainwebLogger) pdb (\rph -> payloadClient cid (_ranked rph) (Just $ rank rph)) + payloadStore <- liftIO $ newPayloadStore + http + (logFunction chainwebLogger) + pdb + (\rph -> payloadClient cid (_ranked rph) (Just $ rank rph)) + batchClient + (_, miningPayloadVar) <- allocate newEmptyTMVarIO (\v -> do refresherThread <- fmap (view _1) <$> atomically (tryReadTMVar v) @@ -175,6 +185,16 @@ withPactService cid http memPoolAccess chainwebLogger txFailuresCounter pdb read GeneratingGenesis -> return () _ -> liftIO $ initialPayloadState chainwebLogger pse return pse + where + batchClient :: [RankedBlockPayloadHash] -> ClientM [Maybe PayloadData] + batchClient rhs = do + let body = WithHeights + [ (_rankedBlockPayloadHashHeight r, _rankedBlockPayloadHashHash r) + | r <- rhs + ] + rs <- _payloadDataList <$> payloadBatchClient cid body + let rs' = HM.fromList $ (\pd -> (view payloadDataPayloadHash pd, pd)) <$> rs + return $ (\x -> HM.lookup (_rankedBlockPayloadHashHash x) rs') <$> rhs initialPayloadState :: Logger logger @@ -797,6 +817,39 @@ getPayloadForContext logger serviceEnv h ctx = do Left e -> logFunctionText logger Warn $ "failed to decode encoded payload from evaluation ctx: " <> sshow e +getPayloadsForConsensusPayloads + :: CanReadablePayloadCas tbl + => Logger logger + => logger + -> ServiceEnv tbl + -> Maybe Hints + -> [Ranked ConsensusPayload] + -> IO [(RankedBlockPayloadHash, PayloadData)] +getPayloadsForConsensusPayloads logger serviceEnv h cps = do + insertPayloadDataBatch (view psCandidatePdb serviceEnv) + plds <- getPayloads + (view psPdb serviceEnv) + (view psCandidatePdb serviceEnv) + (Priority $ negate $ int $ _rankedHeight $ head cps) + (_hintsOrigin <$> h) + (fmap _consensusPayloadHash <$> cps) + tableInsertBatch (view psCandidatePdb serviceEnv) plds + return plds + where + insertPayloadDataBatch tbl = do + plds <- mapM decodePld cps + tableInsertBatch tbl $ catMaybes plds + + decodePld rcp@(Ranked _ cp) = + case mapM decodePayloadData $ _encodedPayloadData <$> _consensusPayloadData cp of + Right pld -> + return $ (_consensusPayloadHash <$> rcp,) <$> pld + Left e -> do + lf Warn $ "failed to decode encoded payloads from evaluation ctx: " <> sshow e + return Nothing + + lf = logFunctionText logger + execPreInsertCheckReq :: (Logger logger) => HasVersion diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index 6dad91e67c..c077a1747c 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -47,6 +47,7 @@ module Chainweb.PayloadProvider , _evaluationCtxCurrentHeight , _evaluationCtxRankedPayloadHash , _evaluationCtxRankedParentHash +, _evaluationCtxRankedPayload -- * Fork Info , ForkInfo(..) @@ -326,6 +327,13 @@ _evaluationCtxRankedParentHash ctx = Parent $ RankedBlockHash (unwrapParent $ _evaluationCtxParentHeight ctx) (unwrapParent $ _evaluationCtxParentHash ctx) +_evaluationCtxRankedPayload + :: EvaluationCtx ConsensusPayload + -> Ranked ConsensusPayload +_evaluationCtxRankedPayload ctx = Ranked + (_evaluationCtxCurrentHeight ctx) + (_evaluationCtxPayload ctx) + evaluationCtxProperties :: forall e kv p . (KeyValue e kv, ToJSON p) => EvaluationCtx p -> [kv] evaluationCtxProperties a = [ "parentCreationTime" .= _evaluationCtxParentCreationTime a diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index a317e7b1bf..a9807833ea 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -73,6 +73,7 @@ import Chainweb.PayloadProvider.EVM.Utils (decodeRlpM) import Chainweb.PayloadProvider.EVM.Utils qualified as EVM import Chainweb.PayloadProvider.EVM.Utils qualified as Utils import Chainweb.PayloadProvider.P2P +import Chainweb.PayloadProvider.P2P.RestAPI import Chainweb.PayloadProvider.P2P.RestAPI.Client qualified as Rest import Chainweb.Ranked import Chainweb.Storage.Table @@ -91,6 +92,7 @@ import Control.Monad import Control.Monad.Trans.Resource hiding (throwM) import Control.Monad.Writer import Data.ByteString.Short qualified as BS +import Data.HashMap.Strict qualified as HM import Data.List qualified as L import Data.LogMessage import Data.Maybe @@ -521,13 +523,24 @@ withEvmPayloadProvider logger c rdb mgr conf SomeChainwebVersionT @v _ <- return someChainwebVersionVal SomeChainIdT @c _ <- return $ someChainIdVal c + let pldCli = Rest.payloadClient @v @c @p + -- FIXME move the following two definitions elsewhere + let payloadRankedPayloadHash pld = RankedBlockPayloadHash + { _rankedBlockPayloadHashHeight = int $ EVM._hdrNumber $ _payloadHeader pld + , _rankedBlockPayloadHashHash = EVM._hdrPayloadHash $ _payloadHeader pld + } + let pldCliBatch rhs = do + rs <- _payloadList <$> Rest.payloadBatchClient @v @c @p rhs + let rs' = HM.fromList $ (\pld -> (payloadRankedPayloadHash pld, pld)) <$> rs + return $ (`HM.lookup` rs') <$> rhs + genPld <- liftIO $ checkExecutionClient logger c engineCtx (EVM.ChainId (fromSNat ecid)) liftIO $ logFunctionText logger Info $ "genesis payload block hash: " <> sshow (EVM._hdrPayloadHash genPld) liftIO $ logFunctionText logger Debug $ "genesis payload from execution client: " <> sshow genPld pdb <- liftIO $ initPayloadDb $ payloadDbConfiguration c rdb genPld - store <- liftIO $ newPayloadStore mgr (logFunction pldStoreLogger) pdb pldCli + store <- liftIO $ newPayloadStore mgr (logFunction pldStoreLogger) pdb pldCli pldCliBatch pldVar <- liftIO newEmptyTMVarIO pldIdVar <- liftIO newEmptyTMVarIO candidates <- liftIO emptyTable @@ -1427,6 +1440,9 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do _ -> return () + -- TODO should be fetch as a batch? we did that already + -- during prefetch. So, not sure if it makes sense to try it + -- here again. plds <- forM unknowns $ \ctx -> do pld <- getPayloadForContext p hints ctx @@ -1547,6 +1563,46 @@ getPayloadForContext p h ctx = do lf :: LogFunctionText lf = loggS p "getPayloadForContext" +getPayloadForContexts + :: Logger logger + => EvmPayloadProvider logger + -> Maybe Hints + -> [EvaluationCtx ConsensusPayload] + -> IO [(RankedBlockPayloadHash, Payload)] +getPayloadForContexts p h ctxs = + getPayloadsForConsensusPayloads p h $ _evaluationCtxRankedPayload <$> ctxs + +getPayloadsForConsensusPayloads + :: Logger logger + => EvmPayloadProvider logger + -> Maybe Hints + -> [Ranked ConsensusPayload] + -> IO [(RankedBlockPayloadHash, Payload)] +getPayloadsForConsensusPayloads p h cps = do + insertPayloadDataBatch (_evmCandidatePayloads p) + plds <- getPayloads + (_evmPayloadStore p) + (_evmCandidatePayloads p) + (Priority $ negate $ int $ _rankedHeight $ head cps) + (_hintsOrigin <$> h) + (fmap _consensusPayloadHash <$> cps) + tableInsertBatch (_evmCandidatePayloads p) plds + return plds + where + insertPayloadDataBatch tbl = do + plds <- mapM decodePld cps + tableInsertBatch tbl $ catMaybes plds + + decodePld rcp@(Ranked _ cp) = case mapM decodePayloadData $ _consensusPayloadData cp of + Right pld -> + return $ (_consensusPayloadHash <$> rcp,) <$> pld + Left e -> do + lf Warn $ "failed to decode encoded payloads from evaluation ctx: " <> sshow e + return Nothing + + lf :: LogFunctionText + lf = loggS p "getPayloadsForConsensusPayloads" + newPayloadTimeout :: Micros newPayloadTimeout = 30_000_000 @@ -1598,13 +1654,25 @@ validatePayload p pld ctx = do -- Payload Provider API Instance instance Logger logger => PayloadProvider (EvmPayloadProvider logger) where - - -- FIXME - prefetchPayloads _ _ _ = return () + prefetchPayloads = evmPrefetchPayloads syncToBlock = evmSyncToBlock latestPayloadSTM p = ssnd <$> readTMVar (_evmPayloadVar p) eventProof = getSpvProof +evmPrefetchPayloads + :: HasVersion + => Logger logger + => EvmPayloadProvider logger + -> Maybe Hints + -> [Ranked ConsensusPayload] + -> IO () +evmPrefetchPayloads p h ps = do + lf Info $ "query: " <> sshow (length ps) + rs <- getPayloadsForConsensusPayloads p h ps + lf Info $ "got: " <> sshow (length rs) + where + lf = loggS p "prefetchPayloads" + -- -------------------------------------------------------------------------- -- -- SPV diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index 91603e4ae6..cc41f31850 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -1,12 +1,10 @@ {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE KindSignatures #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} @@ -19,6 +17,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE TupleSections #-} -- | -- Module: Chainweb.IdleProvider @@ -84,7 +83,6 @@ module Chainweb.PayloadProvider.Minimal , newMinimalPayloadProvider ) where -import Configuration.Utils import Chainweb.BlockHeader import Chainweb.BlockHeight import Chainweb.BlockPayloadHash @@ -104,6 +102,7 @@ import Chainweb.Storage.Table.RocksDB import Chainweb.Utils import Chainweb.Utils.Serialization import Chainweb.Version +import Configuration.Utils import Control.Concurrent.Async import Control.Concurrent.STM import Control.Lens hiding ((.=)) @@ -111,6 +110,7 @@ import Control.Monad import Control.Monad.Catch import Control.Monad.Except (throwError) import Data.ByteString qualified as B +import Data.HashMap.Strict qualified as HM import Data.HashSet qualified as HS import Data.LogMessage (LogFunction, LogFunctionText) import Data.PQueue (PQueue) @@ -121,6 +121,7 @@ import Numeric.Natural import P2P.TaskQueue import Servant.Client import System.LogLevel +import Data.Maybe -- -------------------------------------------------------------------------- -- @@ -221,12 +222,21 @@ newMinimalPayloadProvider logger c rdb mgr conf | payloadProviderTypeForChain c /= MinimalProvider = error "Chainweb.PayloadProvider.Minimal.configuration: chain does not use minimal provider" | otherwise = do - SomeChainwebVersionT @v _ <- return $ someChainwebVersionVal + SomeChainwebVersionT @v _ <- return someChainwebVersionVal SomeChainIdT @c _ <- return $ someChainIdVal c - let payloadClient h = Rest.payloadClient @v @c @'MinimalProvider h + let payloadClient = Rest.payloadClient @v @c @'MinimalProvider + + let payloadRankedPayloadHash pld = RankedBlockPayloadHash + { _rankedBlockPayloadHashHeight = view payloadBlockHeight pld + , _rankedBlockPayloadHashHash = view payloadHash pld + } + let payloadBatchClient rhs = do + rs <- Rest.payloadBatchClient @v @c @'MinimalProvider rhs + let rs' = HM.fromList $ (\pld -> (payloadRankedPayloadHash pld, pld)) <$> rs + return $ (`HM.lookup` rs') <$> rhs pdb <- PDB.initPayloadDb $ PDB.configuration c rdb - store <- newPayloadStore mgr (logFunction pldStoreLogger) pdb payloadClient + store <- newPayloadStore mgr (logFunction pldStoreLogger) pdb payloadClient payloadBatchClient var <- newEmptyTMVarIO candidates <- emptyTable logFunctionText logger Debug "minimal payload provider started" @@ -372,8 +382,39 @@ getPayloadForContext p h ctx = do lf :: LogFunctionText lf = _minimalLogger p --- | Concurrently fetch all payloads in an evaluation context and insert them --- into the candidate table. +getPayloadsForConsensusPayloads + :: MinimalPayloadProvider + -> Maybe Hints + -> [Ranked ConsensusPayload] + -> IO [(RankedBlockPayloadHash, Payload)] +getPayloadsForConsensusPayloads p h ctxs = do + insertPayloadDataBatch (_minimalCandidatePayloads p) + plds <- Rest.getPayloads + (_minimalPayloadStore p) + (_minimalCandidatePayloads p) + (Priority $ negate $ int $ _rankedHeight $ head ctxs) + (_hintsOrigin <$> h) + (fmap _consensusPayloadHash <$> ctxs) + tableInsertBatch (_minimalCandidatePayloads p) plds + return plds + where + insertPayloadDataBatch tbl = do + plds <- mapM decodePld ctxs + tableInsertBatch tbl $ catMaybes plds + + decodePld rcp@(Ranked _ cp) = case mapM decodePayloadData $ _consensusPayloadData cp of + Right pld -> + return $ (_consensusPayloadHash <$> rcp,) <$> pld + Left e -> do + lf Warn $ "failed to decode encoded payloads from evaluation ctx: " <> sshow e + return Nothing + + lf :: LogFunctionText + lf = _minimalLogger p + + +-- | Fetch all payloads in an evaluation context and insert them into the +-- candidate table. -- -- This version blocks until all payloads are fetched (or a timeout occurs). -- @@ -386,8 +427,10 @@ minimalPrefetchPayloads -> [Ranked ConsensusPayload] -> IO () minimalPrefetchPayloads p h ps = do - logg p Debug "prefetch payloads" - mapConcurrently_ (try @_ @TaskException . getPayloadForContext p h) ps + logg p Info $ "prefetch payloads: query " <> sshow (length ps) + -- mapConcurrently_ (try @_ @TaskException . getPayloadForContext p h) ps + rs <- getPayloadsForConsensusPayloads p h ps + logg p Info $ "prefetch payloads: got " <> sshow (length rs) -- | -- diff --git a/src/Chainweb/PayloadProvider/P2P.hs b/src/Chainweb/PayloadProvider/P2P.hs index 7adeea5908..0d2587cf1d 100644 --- a/src/Chainweb/PayloadProvider/P2P.hs +++ b/src/Chainweb/PayloadProvider/P2P.hs @@ -9,7 +9,6 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE TypeOperators #-} -{-# LANGUAGE TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -26,6 +25,7 @@ module Chainweb.PayloadProvider.P2P ( PayloadStore(..) , newPayloadStore , getPayload +, getPayloads , getPayloadSimple ) where @@ -47,6 +47,9 @@ import Servant.Client import System.LogLevel import Utils.Logging.Trace import Chainweb.Storage.Table.HashMap (emptyTable) +import Control.Concurrent.Async +import Control.Exception +import Chainweb.Core.Brief -- -------------------------------------------------------------------------- -- -- Response Timeout Constants @@ -113,6 +116,7 @@ data PayloadStore tbl a = PayloadStore -- the store between payload providers with the same payload type. This -- could make it easier to limit concurrency across chains. If this is -- needed the ChainId parameter would have to be re-added. + , _payloadStoreGetPayloadBatchClient :: !([RankedBlockPayloadHash] -> ClientM [Maybe a]) } -- FIXME: not sure whether the following instances are a good idea... @@ -171,8 +175,9 @@ newPayloadStore -- ^ initialized Payload DB. In particular it is expected that genesis -- payloads are already available. -> (RankedBlockPayloadHash -> ClientM a) + -> ([RankedBlockPayloadHash] -> ClientM [Maybe a]) -> IO (PayloadStore tbl a) -newPayloadStore mgr logfun payloadDb cli = do +newPayloadStore mgr logfun payloadDb cli bcli = do payloadTaskQueue <- newEmptyPQueue payloadMemo <- new return $! PayloadStore @@ -182,6 +187,7 @@ newPayloadStore mgr logfun payloadDb cli = do , _payloadStoreLogFunction = logfun , _payloadStoreMgr = mgr , _payloadStoreGetPayloadClient = cli + , _payloadStoreGetPayloadBatchClient = bcli } -- -------------------------------------------------------------------------- -- @@ -223,7 +229,7 @@ getPayload -> RankedBlockPayloadHash -> IO a getPayload s candidateStore priority maybeOrigin payloadHash = do - logfun Debug $ "getPayload: " <> sshow payloadHash + logfun Debug $ "getPayload: " <> toText payloadHash tableLookup candidateStore payloadHash >>= \case Just !x -> return x Nothing -> tableLookup tbl payloadHash >>= \case @@ -247,7 +253,7 @@ getPayload s candidateStore priority maybeOrigin payloadHash = do traceLogfun :: forall m . LogMessage m => LogLevel -> m -> IO () traceLogfun = _payloadStoreLogFunction s - taskMsg k msg = "payload task " <> sshow k <> " @ " <> sshow payloadHash <> ": " <> msg + taskMsg k msg = "payload task " <> toText k <> " @ " <> toText payloadHash <> ": " <> msg traceLabel subfun = "Chainweb.PayloadProvider.P2P.getPayload." <> subfun @@ -284,7 +290,122 @@ getPayload s candidateStore priority maybeOrigin payloadHash = do -- | Query a block payload via the task queue -- queryPayloadTask :: RankedBlockPayloadHash -> IO (Task ClientEnv a) - queryPayloadTask k = newTask (sshow k) priority $ \logg env -> do + queryPayloadTask k = newTask (TaskId $ toText k) priority $ \logg env -> do + logg @T.Text Debug $ taskMsg k "query remote block payload" + let taskEnv = setResponseTimeout taskResponseTimeout env + !r <- trace traceLogfun (traceLabel "queryPayloadTask") (rankedHashJson k) (let Priority i = priority in i) + $ runClientM (_payloadStoreGetPayloadClient s k) taskEnv + case r of + (Right !x) -> do + logg @T.Text Debug $ taskMsg k "received remote payload" + return x + Left (e :: ClientError) -> do + logg @T.Text Debug $ taskMsg k $ "failed: " <> sshow e + throwM e + +-- | Query a payloads either from the local store, or the origin, or P2P network. +-- +-- The payloads are only queried and not inserted into the local store. We want to +-- insert it only after it got validate by the payload provider. +-- +getPayloads + :: forall a tbl candidateTbl + . ReadableTable tbl RankedBlockPayloadHash a + => Table candidateTbl RankedBlockPayloadHash a + => PayloadStore tbl a + -> candidateTbl + -> Priority + -- ^ larger values get precedence in the P2P task queue. A good + -- heuristics is to use the negated block height. + -> Maybe PeerInfo + -- ^ Peer from with the BlockPayloadHash originated, if available. + -> [RankedBlockPayloadHash] + -> IO [(RankedBlockPayloadHash, a)] +getPayloads s candidateStore priority maybeOrigin payloadHashes = do + logfun Debug $ "getPayloads: " <> brief payloadHashes + (missing, plds0) <- collect @_ @a payloadHashes <$> tableLookupBatch candidateStore payloadHashes + (missing', plds1) <- collect @_ @a missing <$> tableLookupBatch tbl missing + + -- FIXME We do this output of hte memo map, so this is not shared and we + -- may call this more than once for the same batch. It's not clear + -- whether this can actually happen in practice, since the resolution of + -- payloads is implicitely guarded by the resolution of headers. + -- But we should double check that. + -- Generally, it might be useful to move memoization up to the header + -- level, since there should be no sharing below that level. + -- + (missing'', plds2) <- collect @_ @a missing' + <$> pullOriginBatch missing' maybeOrigin + + plds3 <- forConcurrently missing'' $ \k -> do + v <- memo memoMap k $ \_ -> do + t <- queryPayloadTask k + pQueueInsert queue t + awaitTask t + return (k, v) + return $! mconcat [plds0, plds1, plds2, plds3] + + where + maybeMgr = _payloadStoreMgr s + tbl = _payloadStoreTable s + memoMap = _payloadStoreMemo s + queue = _payloadStoreQueue s + + logfun :: LogLevel -> T.Text -> IO () + logfun = _payloadStoreLogFunction s + + collect :: [k] -> [Maybe v] -> ([k], [(k, v)]) + collect [] [] = ([], []) + collect (k:ks) (v:vs) = case v of + Just v' -> (ks, (k, v'):vs') + Nothing -> (k:ks', vs') + where + (ks', vs') = collect ks vs + collect _ _ = throw $ InternalInvariantViolation "Chainweb.PayloadProvider.P2P.collect: length mismatch" + + traceLogfun :: forall m . LogMessage m => LogLevel -> m -> IO () + traceLogfun = _payloadStoreLogFunction s + + taskMsg ks msg = "payload task " <> "batch" <> " @ " <> brief payloadHashes <> ": " <> msg + + traceLabel subfun = + "Chainweb.PayloadProvider.P2P.getPayload." <> subfun + + -- Only used for logging trace telemetry + rankedHashJson rh = object + [ "height" .= _rankedBlockPayloadHashHeight rh + , "hash" .= _rankedBlockPayloadHashHash rh + ] + + -- | Try to pull block payloads from the given origin peer + -- + -- FIXME: respect maximum batch sizes of P2P API. + -- + pullOriginBatch :: [RankedBlockPayloadHash] -> Maybe PeerInfo -> IO [Maybe a] + pullOriginBatch ks Nothing = do + logfun Debug $ taskMsg ks "no origin" + return [Nothing | _ <- ks] + pullOriginBatch ks (Just origin) + | Nothing <- maybeMgr = do + logfun Debug $ taskMsg ks "no origin" + return [Nothing | _ <- ks] + | Just mgr <- maybeMgr = do + let originEnv = setResponseTimeout pullOriginResponseTimeout $ peerInfoClientEnv mgr origin + logfun Debug $ taskMsg ks "lookup origin" + !r <- trace traceLogfun (traceLabel "pullOrigin") (rankedHashJson <$> ks) 0 + $ runClientM (_payloadStoreGetPayloadBatchClient s ks) originEnv + case r of + (Right !x) -> do + logfun Debug $ taskMsg ks "received from origin" + return x + Left (e :: ClientError) -> do + logfun Debug $ taskMsg ks $ "failed to receive from origin: " <> sshow e + return [Nothing | _ <- ks] + + -- | Query a block payload via the task queue + -- + queryPayloadTask :: RankedBlockPayloadHash -> IO (Task ClientEnv a) + queryPayloadTask k = newTask (TaskId $ toText k) priority $ \logg env -> do logg @T.Text Debug $ taskMsg k "query remote block payload" let taskEnv = setResponseTimeout taskResponseTimeout env !r <- trace traceLogfun (traceLabel "queryPayloadTask") (rankedHashJson k) (let Priority i = priority in i) diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs index 496ce0af39..721f8cc8bf 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI.hs @@ -1,16 +1,13 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingVia #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE ImportQualifiedPost #-} -{-# LANGUAGE KindSignatures #-} {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeAbstractions #-} @@ -33,6 +30,7 @@ module Chainweb.PayloadProvider.P2P.RestAPI , BatchBody(..) , IsPayloadProvider(..) , RestPayload(..) +, PayloadList(..) -- * Payload API , type PayloadApi @@ -322,7 +320,7 @@ instance IsPayloadProvider (EvmProvider n) where p2pPayloadBatchLimit = 20 -- FIXME batch = PayloadList . catMaybes -newtype PayloadList = PayloadList { _headerList :: [EVM.Payload] } +newtype PayloadList = PayloadList { _payloadList :: [EVM.Payload] } deriving (Show, Eq, Generic) deriving newtype (ToJSON, FromJSON, EVM.RLP) diff --git a/src/Chainweb/PayloadProvider/P2P/RestAPI/Client.hs b/src/Chainweb/PayloadProvider/P2P/RestAPI/Client.hs index 884fde9b91..b3d8d38a51 100644 --- a/src/Chainweb/PayloadProvider/P2P/RestAPI/Client.hs +++ b/src/Chainweb/PayloadProvider/P2P/RestAPI/Client.hs @@ -15,7 +15,7 @@ -- module Chainweb.PayloadProvider.P2P.RestAPI.Client ( payloadClient --- , payloadBatchClient +, payloadBatchClient -- , outputsClient -- , outputsBatchClient ) where @@ -64,16 +64,19 @@ payloadClient rh = _restPayload <$> client (payloadGetApi @v @c @p) height hash -- provider :: PayloadProviderType -- provider = payloadProviderTypeForChain v c --- -- -------------------------------------------------------------------------- -- --- -- Post Payload Batch Client --- --- payloadBatchClient_ --- :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProvider) --- . KnownChainwebVersionSymbol v --- => KnownChainIdSymbol c --- => BatchBody --- -> ClientM PayloadDataList --- payloadBatchClient_ = client (payloadPostApi @v @c) +-- -------------------------------------------------------------------------- -- +-- Post Payload Batch Client + +payloadBatchClient + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) (p :: PayloadProviderType) + . KnownChainwebVersionSymbol v + => IsPayloadProvider p + => KnownChainIdSymbol c + => [RankedBlockPayloadHash] + -> ClientM (PayloadBatchType p) +payloadBatchClient bb = _restPayload + <$> client (payloadPostApi @v @c @p) (BatchBody bb) + -- -- -- The query may return any number (including none) of the requested payload -- -- data. Results are returned in any order. diff --git a/src/Chainweb/PayloadProvider/Pact.hs b/src/Chainweb/PayloadProvider/Pact.hs index 92dc7a9549..85fd285777 100644 --- a/src/Chainweb/PayloadProvider/Pact.hs +++ b/src/Chainweb/PayloadProvider/Pact.hs @@ -61,7 +61,17 @@ instance HasChainId (PactPayloadProvider logger tbl) where chainId = _PactPayloadProvider . _2 . chainId instance (Logger logger, CanPayloadCas tbl) => PayloadProvider (PactPayloadProvider logger tbl) where - prefetchPayloads _pp _hints _forkInfo = return () + prefetchPayloads p h ps = do + lf Info $ "prefetch: query: " <> sshow (length ps) + rs <- PactService.getPayloadsForConsensusPayloads + (pactPayloadProviderLogger p) + (pactPayloadProviderServiceEnv p) + h + ps + lf Info $ "prefetch: got: " <> sshow (length rs) + where + lf = logFunctionText (pactPayloadProviderLogger p) + syncToBlock (PactPayloadProvider logger e) = PactService.syncToFork logger e diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 7c0bdd582f..5666e11f17 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -433,9 +433,8 @@ getBlockHeaderInternal pld <- tableLookup candidatePldTbl (view blockPayloadHash header) let prefetchProviderPayloads = case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do - -- TODO PP - -- prefetchPayloads provider hints - -- [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] + prefetchPayloads provider hints + [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] return () DisabledPayloadProvider -> return () From 1db989e02a480f87ec93584a1823f0328d58f40f Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Oct 2025 11:52:26 -0700 Subject: [PATCH 362/378] Update src/Chainweb/ChainValue.hs Co-authored-by: rsoeldner --- src/Chainweb/ChainValue.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Chainweb/ChainValue.hs b/src/Chainweb/ChainValue.hs index 10d44ba15b..1050edef34 100644 --- a/src/Chainweb/ChainValue.hs +++ b/src/Chainweb/ChainValue.hs @@ -61,7 +61,7 @@ instance FunctorWithIndex ChainId ChainValue instance HasTextRepresentation a => HasTextRepresentation (ChainValue a) where toText (ChainValue cid a) = toText cid <> ":" <> toText a fromText t = case T.breakOn ":" t of - (c, r) -> ChainValue <$> fromText c <*> fromText (T.drop 1 r) + (c, r) -> ChainValue <$> fromText c <*> fromText (T.tail r) {-# INLINE toText #-} -- | If a type is already an instance of 'IsCasValue', adding the chain does From 541f1dd97894c4f716e6453c536c8921c96e4d86 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Oct 2025 12:04:15 -0700 Subject: [PATCH 363/378] union == unionWith const --- src/Chainweb/CutDB.hs | 6 ++---- test/unit/Chainweb/Test/CutDB.hs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index 5a51759d03..ed59484d72 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -490,8 +490,7 @@ synchronizeProviders -> Cut -> IO Cut synchronizeProviders logger wbh providers c = do - let startHeaders = HM.unionWith - const + let startHeaders = HM.union (_cutHeaders c) (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) syncsSuccessful <- mapConcurrently (runMaybeT . syncOne) startHeaders @@ -508,8 +507,7 @@ synchronizeProviders logger wbh providers c = do let recoveryHeight = max (int (diameter (chainGraphAt maxBound))) (_cutMinHeight c) - int (diameter (chainGraphAt maxBound)) recoveryCut <- limitCut wbh recoveryHeight c - let recoveryHeaders = HM.unionWith - const + let recoveryHeaders = HM.union (_cutHeaders recoveryCut) (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) mapConcurrently_ (runMaybeT . syncOne) recoveryHeaders diff --git a/test/unit/Chainweb/Test/CutDB.hs b/test/unit/Chainweb/Test/CutDB.hs index 76b0d5c9a8..9f29c8f3e9 100644 --- a/test/unit/Chainweb/Test/CutDB.hs +++ b/test/unit/Chainweb/Test/CutDB.hs @@ -130,7 +130,7 @@ withTestCutDb rdb conf n providers logger = do where synchronizeProviders :: WebBlockHeaderDb -> Cut -> IO () synchronizeProviders wbh c = do - let startHeaders = HM.unionWith const + let startHeaders = HM.union (_cutHeaders c) (imap (\cid () -> genesisBlockHeader cid) (HS.toMap chainIds)) mapConcurrently_ syncOne startHeaders From 55dda270bdc682083e50f1316490331dc27c6c04 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Oct 2025 12:04:36 -0700 Subject: [PATCH 364/378] small code cleanups in Chainweb.Version --- src/Chainweb/Version.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 5ba4d2da39..3295f6a1c1 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -566,7 +566,7 @@ instance Ord ChainwebVersion where instance Eq ChainwebVersion where v == v' = and - [ compare v v' == EQ + [ v == v' , _versionUpgrades v == _versionUpgrades v' , _versionGenesis v == _versionGenesis v' ] @@ -706,7 +706,7 @@ payloadProviderTypeForChain c = implicitVersion ^?! versionPayloadProviderTypes . atChain c instance (HasVersion, HasChainId p) => HasPayloadProviderType p where - _payloadProviderType p = payloadProviderTypeForChain p + _payloadProviderType = payloadProviderTypeForChain {-# INLINE _payloadProviderType #-} -------------------------------------------------------------------------- -- @@ -760,7 +760,7 @@ genesisHeightAndGraph c = -- the chain was in every graph down to the bottom, -- so the bottom has the genesis graph (False, z) -> ruleZipperHere z - (True, (BetweenZipper _ above)) + (True, BetweenZipper _ above) -- the chain is not in this graph, and there is no graph above -- which could have it | [] <- above -> missingChainError @@ -813,7 +813,7 @@ latestBehaviorAt = foldlOf' behaviorChanges max 0 implicitVersion + 1 ] onAllChains :: HasVersion => a -> ChainMap a -onAllChains a = tabulateChains (\_ -> a) +onAllChains a = tabulateChains (const a) tabulateChains :: HasVersion => (ChainId -> a) -> ChainMap a tabulateChains f = runIdentity $ tabulateChainsM (Identity . f) From a291c101db0ee2602a277ddb3fdacd6d7d1cf0ea Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Oct 2025 20:16:29 -0700 Subject: [PATCH 365/378] replace prefetch by batched queries in fork info resolution --- src/Chainweb/PayloadProvider/Minimal.hs | 7 ++++++- src/Chainweb/Sync/WebBlockHeaderStore.hs | 26 ++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index cc41f31850..c5eb173f25 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -525,7 +525,12 @@ validatePayloads -> Maybe Hints -> ForkInfo -> IO () -validatePayloads p h i = mapConcurrently_ go (_forkInfoTrace i) +validatePayloads p h i = do + -- First try to fetch all payloads in a batch. This will insert them into + -- the candidate table. Afet this the getPayloadForContext call below should + -- be not hit the network any more. + void $ getPayloadsForConsensusPayloads p h (_evaluationCtxRankedPayload <$> _forkInfoTrace i) + mapConcurrently_ go (_forkInfoTrace i) where go ctx = do pld <- getPayloadForContext p h diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 5666e11f17..fc0c8eb43a 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -431,10 +431,32 @@ getBlockHeaderInternal -- Get the Payload Provider and let hints = Hints <$> maybeOrigin' pld <- tableLookup candidatePldTbl (view blockPayloadHash header) + let prefetchProviderPayloads = case providers ^?! atChain cid of ConfiguredPayloadProvider provider -> do - prefetchPayloads provider hints - [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] + + -- FIXME: + -- It is is not clear if this is useful, since we prefetch + -- just a single payload here. It would be more useful to + -- do this after we discover the fork point and have a + -- longer list of payloads to prefetch. + -- + -- In theory we could make an educated guess here about what + -- else might be missing. But there might be better + -- strategies for that. + -- + -- FIXME: at the moment we fetch payloads in batches during + -- fork info resolution. This would be redudant and probably + -- not beneficial if we already scheduled individual + -- prefetches here. + -- + -- An alternative would be if prefetch would trigger full + -- fork info resultion. But at the moment it can't because + -- we don't yet have th required consensus headers + -- available. + -- + -- prefetchPayloads provider hints + -- [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] return () DisabledPayloadProvider -> return () From 16dfe717034a80a3e9bf3045f421cd7782eba1ed Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Sun, 5 Oct 2025 22:20:43 -0700 Subject: [PATCH 366/378] adjust prefetching based on benchmarks and cleanup code comments --- src/Chainweb/PayloadProvider/EVM.hs | 117 +++++++++++------------ src/Chainweb/PayloadProvider/Minimal.hs | 16 +++- src/Chainweb/Sync/WebBlockHeaderStore.hs | 35 +++---- 3 files changed, 85 insertions(+), 83 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index a9807833ea..40c8e4218c 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -1363,46 +1363,25 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do -- validate all payloads together before sending them to the -- EVM and inserting them into the DB. - -- FIXME FIXME FIXME - -- We also must make sure that the EVM has synced all - -- payloads that are known to the payload provider. Normally - -- that happens at startup. However, at startup that happens - -- only up to the latest. But there is no guarantee that the - -- latest cut isn't behind the latest known payload of the - -- provider (e.g. when the cut got reset). In those cases it - -- can happen that during catchup we encounter blocks that - -- are known to the provider but not yet to the EVM. - -- - -- What can we do? - -- - We can just proceed and update the evm (updateEvm) in - -- case validatePayload times out (SYNCING). - -- - We can keep track of the latest state of the EVM. - -- - We can check each time the latest state of the EVM. - -- - We can always update the the EVM, which should be fast - -- if is is already in sync, though it would still be a - -- network roundtrip. - -- - Altenatively, we can make sure that the forkpoint - -- reflects the state of the EVM and not the state of the - -- payload provider. However, that could mean that we - -- revalidate blocks that we did validate before. - -- - -- The first option is somewhat inefficient on long catchups - -- with small per chain chunks sizes. - -- - -- For now we try the second option: - -- Check that the last known is known to the EVM according - -- to the current EVM state. - - -- FIXME: the ForkInfo trace is based on the forkpoint with - -- the payload provider (and not the EVM state). Therefore - -- the knowns may not contain what we expect, in particular - -- they are likely just empty (because the forkpoint is the - -- last known payload provider block). + -- Before we proceed to the validation we need to make sure + -- that the EVM has synced all payloads that are known to + -- the payload provider. Normally that happens at startup. + -- However, at startup that happens only up to the latest + -- cut. But there is no guarantee that the latest cut isn't + -- behind the latest known payload of the provider (e.g. + -- when the cut got reset). In those cases it can happen + -- that during catchup we encounter blocks that are known to + -- the provider but not yet to the EVM. To resolve this we + -- check that the last known payload is known to the EVM + -- according to the current EVM state. -- - -- So, this check fails and we skip the updateEvm call. The - -- correct approach is to call evmUpdate whenever the first + -- The ForkInfo trace is based on the forkpoint with the + -- payload provider and not the EVM state. Therefore when + -- the EVM state is behind we need to advance the EVM to the + -- fork point first. We call evmUpdate whenever the first -- trace entry is ahead of the evm state. + -- case unknowns of (firstUnknown:_) -> do let lastKnownPayloadHash = case reverse knowns of @@ -1439,38 +1418,52 @@ evmSyncToBlock p hints forkInfo = withLock (_evmLock p) $ do updateEvm p lastKnownConsensusState Nothing [] _ -> return () + -- If we have more than one unknown we first try to fetch + -- all payloads in a batch and inject them into the + -- candidate store. This will insert them into the candidate + -- table. After this the getPayloadForContext call below + -- should be not hit the network any more. This is currently + -- only beneficial when synchronizing long forks or a with a + -- consensus that is ahead, like during replay or + -- reinitialization of the payload provider. + -- + when (length unknowns > 1) $ + void $ getPayloadsForConsensusPayloads p hints + (_evaluationCtxRankedPayload <$> unknowns) - -- TODO should be fetch as a batch? we did that already - -- during prefetch. So, not sure if it makes sense to try it - -- here again. plds <- forM unknowns $ \ctx -> do pld <- getPayloadForContext p hints ctx - -- FIXME FIXME FIXME - -- -- The follwoing can result in timeouts. Even on empty - -- blocks. But why? Well, see the comment above! + -- blocks. But why? Well, see the comment about + -- synchronizing the EVM with the payload provider + -- above! + -- + -- The issue is when we have a header in the payload db + -- (we know it) but the EVM does not yet know about it. + -- That's a corner case that can happen when syncing + -- after the EVM and the payload provider got out of + -- sync. In those cases it is the best to update the EVM + -- first. Normally, that happens at startup, but when + -- the cut got reset, the payload provider may know + -- validated blocks beyond the latest cut. Whe consensus + -- moves the cut beyond the latest block that is known + -- to the EVM, but the payload provider already knows + -- those blocks we get into a situation where the blocks + -- in the fork info trace do not cover the missing + -- blocks in the EVM. + -- + -- At the moment this is solved by the code above that + -- calls updateEvm to move the EVM state up to the last + -- unknown block in the trace. -- - -- It seems that it is a problem when we have a header - -- in the payload db (we know it) but the EVM does not - -- yet know about it. That's a corner case that can - -- happen when syncing after the EVM and the payload - -- provider got out of sync. In those cases it is - -- probably the best to update the EVM first. Normally, - -- that happens at startup, but iwhen the cut got reset, - -- the payload provider may know validated blocks beyond - -- the latest cut. Whe consensus moves the cut beyond - -- the latest block that is known to the EVM, but the - -- payload provider already knows those blocks we get - -- into situation where the blocks in the fork info - -- trace do not cover the missing blocks in the EVM. + -- Can we skip the newPayload call during validation -- + -- at least sometimes? If we instruct the EVM to just go + -- to those blocks it will sync all by itself. However, + -- we still must guarantee that we validated the + -- Chainweb Merkle root of all payloads that the payload + -- provider knows about. -- - -- Can we skip the newPayload call in it -- at least - -- sometimes? If we instruct the EVM to just go to those - -- blocks it will sync all by itself. However, we still - -- must guarantee that we validated the Chainweb Merkle - -- root of all payloads that the payload provider knows - -- about. validatePayload p pld ctx return (_evaluationCtxRankedPayloadHash ctx, pld) diff --git a/src/Chainweb/PayloadProvider/Minimal.hs b/src/Chainweb/PayloadProvider/Minimal.hs index c5eb173f25..cc7e7f0856 100644 --- a/src/Chainweb/PayloadProvider/Minimal.hs +++ b/src/Chainweb/PayloadProvider/Minimal.hs @@ -526,10 +526,18 @@ validatePayloads -> ForkInfo -> IO () validatePayloads p h i = do - -- First try to fetch all payloads in a batch. This will insert them into - -- the candidate table. Afet this the getPayloadForContext call below should - -- be not hit the network any more. - void $ getPayloadsForConsensusPayloads p h (_evaluationCtxRankedPayload <$> _forkInfoTrace i) + + -- If the trace is larger than one we first try to fetch all payloads in a + -- batch and insert them into the candidate store. This will insert them + -- into the candidate table. After this the getPayloadForContext call below + -- should be not hit the network any more. This is currently only beneficial + -- when synchronizing long forks or a with a consensus that is ahead, like + -- during replay or reinitialization of the payload provider. + -- + when (length (_forkInfoTrace i) > 1) $ + void $ getPayloadsForConsensusPayloads p h + (_evaluationCtxRankedPayload <$> _forkInfoTrace i) + mapConcurrently_ go (_forkInfoTrace i) where go ctx = do diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index fc0c8eb43a..59f15ed6a8 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -436,27 +436,28 @@ getBlockHeaderInternal ConfiguredPayloadProvider provider -> do -- FIXME: - -- It is is not clear if this is useful, since we prefetch - -- just a single payload here. It would be more useful to - -- do this after we discover the fork point and have a - -- longer list of payloads to prefetch. + -- It is is not clear how useful this is, since we prefetch + -- just a single payload here. In theory we could make an + -- educated guess here about what else might be missing. But + -- there are better strategies for that. -- - -- In theory we could make an educated guess here about what - -- else might be missing. But there might be better - -- strategies for that. + -- For an efficient prefetch we would have to do a full fork + -- resolution. After we discover the fork point we would + -- have the full list of payloads to prefetch. But at the + -- moment we cannot do that because we don't yet have the + -- required consensus headers available. -- - -- FIXME: at the moment we fetch payloads in batches during - -- fork info resolution. This would be redudant and probably - -- not beneficial if we already scheduled individual - -- prefetches here. + -- So, the proper solution is to implement batched header + -- prefetching based on the current fork height and the + -- height of the target header. After that we can also + -- prefetch the payloads of those headers in a single batch. -- - -- An alternative would be if prefetch would trigger full - -- fork info resultion. But at the moment it can't because - -- we don't yet have th required consensus headers - -- available. + -- Another option would be to provide an API that allows + -- fetching payloads from the canonical chain based on + -- height. -- - -- prefetchPayloads provider hints - -- [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] + prefetchPayloads provider hints + [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] return () DisabledPayloadProvider -> return () From fe657dadde9f6f4d0aad883ad1ac8bf16be29ab8 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 6 Oct 2025 16:35:49 -0700 Subject: [PATCH 367/378] small fixes from code review --- src/Chainweb/BlockHeaderDB/Internal.hs | 7 +------ src/Chainweb/ChainValue.hs | 15 ++++++++++----- src/Chainweb/Core/Brief.hs | 25 ++++++++++--------------- src/Chainweb/Version.hs | 5 ++++- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/Chainweb/BlockHeaderDB/Internal.hs b/src/Chainweb/BlockHeaderDB/Internal.hs index 1c55c6480b..df6f163314 100644 --- a/src/Chainweb/BlockHeaderDB/Internal.hs +++ b/src/Chainweb/BlockHeaderDB/Internal.hs @@ -143,11 +143,6 @@ instance IsCasValue RankedBlockHeader where = RankedBlockHash (view blockHeight bh) (view blockHash bh) {-# INLINE casKey #-} -instance HasTextRepresentation RankedBlockHeader where - toText= toText - fromText = fromText - {-# INLINE toText #-} - type RankedBlockHeaderCas tbl = Cas tbl RankedBlockHeader instance HasVersion => TreeDbEntry RankedBlockHeader where @@ -387,7 +382,7 @@ getRankedKey db h = do instance HasVersion => TreeDb RankedBlockHeaderDb where type DbEntry RankedBlockHeaderDb = RankedBlockHeader - lookup db h = tableLookup (_chainDbCas $ _rankedBlockHeaderDb db) h + lookup db = tableLookup (_chainDbCas $ _rankedBlockHeaderDb db) {-# INLINEABLE lookup #-} -- If the rank is inconsistent with the height in the key 'Nothing' is diff --git a/src/Chainweb/ChainValue.hs b/src/Chainweb/ChainValue.hs index 1050edef34..7428734f40 100644 --- a/src/Chainweb/ChainValue.hs +++ b/src/Chainweb/ChainValue.hs @@ -32,11 +32,12 @@ module Chainweb.ChainValue import Control.DeepSeq import Chainweb.ChainId import Chainweb.Storage.Table -import Chainweb.Utils (HasTextRepresentation(..)) +import Chainweb.Utils (HasTextRepresentation(..), EncodingException (TextFormatException)) import Control.Lens +import Control.Monad.Catch import Data.Hashable -import GHC.Generics import Data.Text qualified as T +import GHC.Generics -- -------------------------------------------------------------------------- -- -- Tag Values With a ChainId @@ -59,10 +60,14 @@ instance FoldableWithIndex ChainId ChainValue instance FunctorWithIndex ChainId ChainValue instance HasTextRepresentation a => HasTextRepresentation (ChainValue a) where - toText (ChainValue cid a) = toText cid <> ":" <> toText a - fromText t = case T.breakOn ":" t of - (c, r) -> ChainValue <$> fromText c <*> fromText (T.tail r) + toText (ChainValue cid a) = toText cid <> "@" <> toText a + fromText t = case T.breakOn "@" t of + (c, r) + | T.null r -> throwM $ + TextFormatException $ "ChainValue: faialed to parse: " <> t + | otherwise -> ChainValue <$> fromText c <*> fromText (T.tail r) {-# INLINE toText #-} + {-# INLINE fromText #-} -- | If a type is already an instance of 'IsCasValue', adding the chain does -- preserve this property. By also wrapping the key it is possible to shard diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index 56cf14b452..40b148b28b 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -117,7 +117,7 @@ instance Brief BlockHeader where brief bh = brief (view chainId bh) <> "@" <> brief (view blockHeight bh) - <> ":" <> brief (view blockHash bh) + <> "." <> brief (view blockHash bh) instance Brief (Parent BlockHeader) where brief = brief . unwrapParent deriving via (CryptoHash AdjacentsHashAlgorithm) @@ -127,32 +127,28 @@ instance Brief ConsensusPayload where instance Brief p => Brief (EvaluationCtx p) where brief ec = "payload:" <> brief (_evaluationCtxPayload ec) - <> ":parent:" <> brief (_evaluationCtxRankedParentHash ec) + <> "/parent:" <> brief (_evaluationCtxRankedParentHash ec) deriving via (BriefText (CryptoHash a)) instance (IncrementalHash a, Coercible a BS.ShortByteString) => Brief (CryptoHash a) instance Brief CutHashes where - brief c = T.intercalate ":" - [ brief (_cutHashesId c) - , brief (_cutHashesHeight c) - , brief (L.sort $ HM.toList $ _cutHashes c) - ] + brief c = brief (_cutHashesHeight c) + <> "." <> brief (_cutHashesId c) + <> ":" <> brief (L.sort $ HM.toList $ _cutHashes c) instance Brief Cut where brief = brief . cutToCutHashes Nothing instance Brief NewPayload where brief np = brief (_newPayloadChainId np) - <> ":" <> brief (_newPayloadRankedParentHash np) + <> "@" <> brief (_newPayloadRankedParentHash np) instance Brief SyncState where - brief ss = T.intercalate ":" - [ brief (_syncStateHeight ss) - , brief (_syncStateBlockHash ss) - , brief (_syncStateBlockPayloadHash ss) - ] + brief ss = brief (_syncStateHeight ss) + <> "." <> brief (_syncStateBlockHash ss) + <> "." <> brief (_syncStateBlockPayloadHash ss) instance Brief ConsensusState where brief cs = @@ -188,8 +184,7 @@ instance Show a => Brief (ShowBrief a) where newtype BriefBase64ByteString = BriefBase64ByteString B.ByteString instance Brief BriefBase64ByteString where brief (BriefBase64ByteString bytes) = T.take 6 - $ encodeB64UrlNoPaddingText - $ bytes + $ encodeB64UrlNoPaddingText bytes newtype BriefBase64ShortByteString = BriefBase64ShortByteString BS.ShortByteString instance Brief BriefBase64ShortByteString where diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index 3295f6a1c1..a7b5856537 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -545,6 +545,8 @@ withVersion = withDict @HasVersion instance Show ChainwebVersion where show = show . _versionName +-- FIXME: why not compare on the version code? That should be unique! +-- Otherwise, at least the genesis hashes should be unique. instance Ord ChainwebVersion where v `compare` v' = fold [ _versionCode v `compare` _versionCode v' @@ -564,9 +566,10 @@ instance Ord ChainwebVersion where , _versionVerifierPluginNames v `compare` _versionVerifierPluginNames v' ] +-- FIXME this instance is not consistent with the Ord instance above instance Eq ChainwebVersion where v == v' = and - [ v == v' + [ compare v v' == EQ , _versionUpgrades v == _versionUpgrades v' , _versionGenesis v == _versionGenesis v' ] From eaffea308f6bb64073fd2e81531dc4c6bf270082 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 6 Oct 2025 18:21:44 -0700 Subject: [PATCH 368/378] Avoid prefetching locally mined blocks --- src/Chainweb/Sync/WebBlockHeaderStore.hs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Chainweb/Sync/WebBlockHeaderStore.hs b/src/Chainweb/Sync/WebBlockHeaderStore.hs index 59f15ed6a8..4956670b69 100644 --- a/src/Chainweb/Sync/WebBlockHeaderStore.hs +++ b/src/Chainweb/Sync/WebBlockHeaderStore.hs @@ -84,6 +84,7 @@ import System.LogLevel import Utils.Logging.Trace import Chainweb.Logger import Chainweb.Core.Brief +import Data.Maybe -- -------------------------------------------------------------------------- -- -- Response Timeout Constants @@ -456,9 +457,11 @@ getBlockHeaderInternal -- fetching payloads from the canonical chain based on -- height. -- - prefetchPayloads provider hints - [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] - return () + -- FIXME: ensure that we do not prefetch locally mined blocks + -- can we check the origin hints for that? + when (isJust maybeOrigin && isNothing pld) $ do + prefetchPayloads provider hints + [flip ConsensusPayload Nothing <$> view rankedBlockPayloadHash header] DisabledPayloadProvider -> return () parentHdr <- runConcurrently From 636cdda4d472d85e49b2cb9aad068b57071613d6 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 6 Oct 2025 21:43:16 -0700 Subject: [PATCH 369/378] Simplify HasTextRepresentation instance for BlockHeight --- src/Chainweb/BlockHeight.hs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Chainweb/BlockHeight.hs b/src/Chainweb/BlockHeight.hs index cfd8a5e17f..f4c5d42e97 100644 --- a/src/Chainweb/BlockHeight.hs +++ b/src/Chainweb/BlockHeight.hs @@ -4,9 +4,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE MultiParamTypeClasses #-} -{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} @@ -43,10 +41,8 @@ import Chainweb.Crypto.MerkleLog import Chainweb.MerkleUniverse import Chainweb.Utils import Chainweb.Utils.Serialization -import Control.Monad.Catch import Data.Aeson import Data.Hashable -import Data.Text qualified as T import Data.Word import GHC.Generics (Generic) import Numeric.Additive @@ -66,10 +62,8 @@ newtype BlockHeight = BlockHeight { getBlockHeight :: Word64 } instance Show BlockHeight where show (BlockHeight b) = show b instance HasTextRepresentation BlockHeight where - toText = sshow - fromText t = case reads (T.unpack t) of - [(h, "")] -> return (BlockHeight h) - _ -> throwM $ TextFormatException $ "BlockHeight: failed to parse: " <> t + toText = toText . getBlockHeight + fromText = fmap BlockHeight . fromText {-# INLINE toText #-} {-# INLINE fromText #-} From f0eae96bddae84763edd91ece5b3a0afcf694a9d Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 6 Oct 2025 22:16:05 -0700 Subject: [PATCH 370/378] Add _forkInfoTraceBlockHashes function for ForkInfo --- src/Chainweb/PayloadProvider.hs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index c077a1747c..e56bd73112 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -61,6 +61,7 @@ module Chainweb.PayloadProvider , assertForkInfoInvariants , _forkInfoBaseHeight , _forkInfoBaseRankedPayloadHash +, _forkInfoTraceBlockHashes -- * New Payload , NewPayload(..) @@ -534,6 +535,23 @@ _forkInfoBaseRankedPayloadHash fi = RankedBlockPayloadHash <$> _forkInfoBaseHeight fi <*> _forkInfoBasePayloadHash fi + +-- | Return the block hashes of the block in a fork info trace. +-- +_forkInfoTraceBlockHashes :: ForkInfo -> [RankedBlockHash] +_forkInfoTraceBlockHashes forkInfo = + -- The trace entries contain the respective parent block hashes. So, in + -- order to get the block hashes we need to shift the trace by one and add + -- the hash of the last block in the trace. The hash of the last block in + -- the trace can be found in the target state + drop 1 trace <> [top] + where + trace = unwrapParent . _evaluationCtxRankedParentHash <$> _forkInfoTrace forkInfo + top = _syncStateRankedBlockHash + . _consensusStateLatest + . _forkInfoTargetState + $ forkInfo + assertForkInfoInvariants :: MonadThrow m => ForkInfo -> m () assertForkInfoInvariants forkInfo = do when (null (_forkInfoTrace forkInfo)) $ From 4554ddea060261f88f22e2f2e135da1afe32e7d5 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 6 Oct 2025 22:30:34 -0700 Subject: [PATCH 371/378] Add pldRankedBlockPayloadHash getter EVM execution payload --- .../PayloadProvider/EVM/ExecutionPayload.hs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Chainweb/PayloadProvider/EVM/ExecutionPayload.hs b/src/Chainweb/PayloadProvider/EVM/ExecutionPayload.hs index 6ae066985d..24fdc651db 100644 --- a/src/Chainweb/PayloadProvider/EVM/ExecutionPayload.hs +++ b/src/Chainweb/PayloadProvider/EVM/ExecutionPayload.hs @@ -82,6 +82,8 @@ module Chainweb.PayloadProvider.EVM.ExecutionPayload , pldExpectedBlobVersionedHashes , _pldRequests , pldRequests +, _pldRankedBlockPayloadHash +, pldRankedBlockPayloadHash ) where import Chainweb.PayloadProvider.EVM.EngineAPI @@ -98,6 +100,7 @@ import Data.Word import Ethereum.Misc import Ethereum.RLP import GHC.Generics (Generic) +import Chainweb.Utils (int) -- -------------------------------------------------------------------------- -- -- | Execution Payload Data @@ -576,3 +579,13 @@ pldRequests :: Getter Payload (Maybe [EVM.ExecutionRequest]) pldRequests = to _pldRequests {-# INLINE pldRequests #-} +_pldRankedBlockPayloadHash :: Payload -> RankedBlockPayloadHash +_pldRankedBlockPayloadHash pld = RankedBlockPayloadHash + { _rankedBlockPayloadHashHeight = int $ EVM._hdrNumber $ _payloadHeader pld + , _rankedBlockPayloadHashHash = EVM._hdrPayloadHash $ _payloadHeader pld + } +{-# INLINE _pldRankedBlockPayloadHash #-} + +pldRankedBlockPayloadHash :: Getter Payload RankedBlockPayloadHash +pldRankedBlockPayloadHash = to _pldRankedBlockPayloadHash +{-# INLINE pldRankedBlockPayloadHash #-} From d517ea39fd818af8cf10dcb7880b0983e5284fe3 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Mon, 6 Oct 2025 23:20:37 -0700 Subject: [PATCH 372/378] Address issues from code review --- src/Chainweb/CutDB.hs | 21 ++++++++++++--------- src/Chainweb/Pact/PactService.hs | 23 +++++++---------------- src/Chainweb/PayloadProvider/EVM.hs | 24 ++++++++++++++---------- src/Chainweb/PayloadProvider/P2P.hs | 8 ++++++++ src/Chainweb/Sync/ForkInfo.hs | 11 +++++++++++ src/P2P/TaskQueue.hs | 6 ++++-- 6 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/Chainweb/CutDB.hs b/src/Chainweb/CutDB.hs index ed59484d72..f13758d318 100644 --- a/src/Chainweb/CutDB.hs +++ b/src/Chainweb/CutDB.hs @@ -530,7 +530,6 @@ synchronizeProviders logger wbh providers c = do pLog Warn $ "resolveFork for failed" <> "; finfo: " <> encodeToText finfo <> "; failure: " <> sshow e - -- pLog Error "It is recommend using --initial-block-height-limit to recover from fork manually." empty pLog Info $ "payload provider synced to " <> sshow (view blockHeight hdr) @@ -707,17 +706,21 @@ processCuts conf logFun headerStore providers cutHashesStore queue cutVar cutPru -- During this final sync we also enable payload production. finfo <- forkInfoForHeader hdrStore bh Nothing Nothing True - -- Note, that this really should be super quick and + -- Note, that this sync really should be super quick and -- should never fail. - -- - -- FIXME: Can't we go to the merge cut directly? - -- FIXME: we could we trigger this with only a - -- single node in the system? + -- TODO: It would be nicer to go to the merge cut directly. clog Info "Syncing paylooad provider with merged cut" resolveForkInfo clog (hdrStore ^?! ixg cid) NullCas provider Nothing finfo `catch` - -- FIXME calling error is not OK! - \(e :: SomeException) -> error - $ "Failed to sync to merge cut (on chain " <> sshow cid <> "): " <> sshow e + \(e :: SomeException) -> do + clog Error + $ "Failed to sync payload provider to the merge cut." + <> " This should never happen. It may indicated a broken payload provider or a corrupted database." + <> " Fork info: " <> encodeToText finfo + <> " Failure: " <> sshow e + throwM $ InternalInvariantViolation + $ "Failed to sync payload provider to the merge cut." + <> " This should never happen. It may indicated a broken payload provider or a corrupted database." + <> " Failure: " <> sshow e _ -> return () let cutDiff = cutDiffToTextShort curCut resultCut let currentCutIdMsg = T.unwords diff --git a/src/Chainweb/Pact/PactService.hs b/src/Chainweb/Pact/PactService.hs index a60e42e5ff..2ab7cea1aa 100644 --- a/src/Chainweb/Pact/PactService.hs +++ b/src/Chainweb/Pact/PactService.hs @@ -540,11 +540,7 @@ syncToFork logger serviceEnv hints forkInfo = do Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState return (mempty, mempty, forkInfo._forkInfoTargetState) else do - -- check if some past block had the target as its parent; if so, that - -- means we can rewind to it - -- - -- FIXME: why the parent? Why not the block itself? - -- + -- check if the target is in our history latestBlockRewindable <- isJust <$> Checkpointer.lookupBlockHash sql (_latestBlockHash forkInfo._forkInfoTargetState) @@ -558,18 +554,11 @@ syncToFork logger serviceEnv hints forkInfo = do Checkpointer.setConsensusState sql forkInfo._forkInfoTargetState return (rewoundTxs, mempty, forkInfo._forkInfoTargetState) else do - let traceBlockHashesAscending = + let traceBlockHashesAscending = _forkInfoTraceBlockHashes forkInfo - -- Why do we drop the first entry? That seems fishy. - drop 1 (unwrapParent . _evaluationCtxRankedParentHash <$> forkInfo._forkInfoTrace) <> - [_syncStateRankedBlockHash forkInfo._forkInfoTargetState._consensusStateLatest] - - -- FIXME: we sometimes get stuck in a loop with a fork into trace - -- that is too short, i.e. the forkpoint is too far ahead. - - logFunctionText logger Debug $ "playing blocks" + logFunctionText logger Debug $ "playing blocks from fork info trace" <> "; from: " <> brief pactConsensusState - <> "; target: " <> brief forkInfo._forkInfoTargetState + <> "; target: " <> brief (_forkInfoTargetState forkInfo) <> "; trace: " <> brief traceBlockHashesAscending findForkChainAscending (reverse $ zip forkInfo._forkInfoTrace traceBlockHashesAscending) >>= \case @@ -595,7 +584,9 @@ syncToFork logger serviceEnv hints forkInfo = do let unknownPayloads = NEL.filter (isNothing . snd) knownPayloads unless (null unknownPayloads) - $ logFunctionText logger Debug $ "unknown blocks in context: " <> sshow (length unknownPayloads) + $ logFunctionText logger Debug $ "unknown blocks in context" + <> "; count: " <> sshow (length unknownPayloads) + <> "; hashes: " <> brief (snd . fst <$> unknownPayloads) runnableBlocks <- forM knownPayloads $ \((evalCtx, rankedBHash), maybePayload) -> do logFunctionText logger Debug $ "running block: " <> brief rankedBHash diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 40c8e4218c..846c462fc1 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -24,6 +24,7 @@ {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wprepositive-qualified-module #-} +{-# LANGUAGE AllowAmbiguousTypes #-} -- | -- Module: Chainweb.PayloadProvider.EVM @@ -112,6 +113,7 @@ import Network.URI.Static import P2P.Session (ClientEnv) import P2P.TaskQueue import System.LogLevel +import Servant.Client (ClientM) -- -------------------------------------------------------------------------- -- -- Payload Database @@ -525,16 +527,7 @@ withEvmPayloadProvider logger c rdb mgr conf SomeChainIdT @c _ <- return $ someChainIdVal c let pldCli = Rest.payloadClient @v @c @p - - -- FIXME move the following two definitions elsewhere - let payloadRankedPayloadHash pld = RankedBlockPayloadHash - { _rankedBlockPayloadHashHeight = int $ EVM._hdrNumber $ _payloadHeader pld - , _rankedBlockPayloadHashHash = EVM._hdrPayloadHash $ _payloadHeader pld - } - let pldCliBatch rhs = do - rs <- _payloadList <$> Rest.payloadBatchClient @v @c @p rhs - let rs' = HM.fromList $ (\pld -> (payloadRankedPayloadHash pld, pld)) <$> rs - return $ (`HM.lookup` rs') <$> rhs + let pldCliBatch = mkPayloadBatchClient @v @c genPld <- liftIO $ checkExecutionClient logger c engineCtx (EVM.ChainId (fromSNat ecid)) liftIO $ logFunctionText logger Info $ "genesis payload block hash: " <> sshow (EVM._hdrPayloadHash genPld) @@ -571,6 +564,17 @@ withEvmPayloadProvider logger c rdb mgr conf where pldStoreLogger = addLabel ("sub-component", "payloadStore") logger +mkPayloadBatchClient + :: forall (v :: ChainwebVersionT) (c :: ChainIdT) + . KnownChainwebVersionSymbol v + => KnownChainIdSymbol c + => [RankedBlockPayloadHash] + -> ClientM [Maybe Payload] +mkPayloadBatchClient rhs = do + rs <- _payloadList <$> Rest.payloadBatchClient @v @c @(EvmProvider _) rhs + let rs' = HM.fromList $ (\pld -> (_pldRankedBlockPayloadHash pld, pld)) <$> rs + return $ (`HM.lookup` rs') <$> rhs + -- | Checks the availability of the Execution Client -- -- - asserts API availability diff --git a/src/Chainweb/PayloadProvider/P2P.hs b/src/Chainweb/PayloadProvider/P2P.hs index 0d2587cf1d..d0f2b35632 100644 --- a/src/Chainweb/PayloadProvider/P2P.hs +++ b/src/Chainweb/PayloadProvider/P2P.hs @@ -117,6 +117,14 @@ data PayloadStore tbl a = PayloadStore -- could make it easier to limit concurrency across chains. If this is -- needed the ChainId parameter would have to be re-added. , _payloadStoreGetPayloadBatchClient :: !([RankedBlockPayloadHash] -> ClientM [Maybe a]) + -- ^ HTTP client for querying payloads in batches (provided by the + -- PayloadProvider) + -- + -- There is a default Payload REST API in + -- "Chainweb.PayloadProvider.P2P.RestAPI" that can be used in common + -- cases. + -- + -- The same considerations apply as for '_payloadStoreGetPayloadClient'. } -- FIXME: not sure whether the following instances are a good idea... diff --git a/src/Chainweb/Sync/ForkInfo.hs b/src/Chainweb/Sync/ForkInfo.hs index 86a19c9610..d1916d3fe7 100644 --- a/src/Chainweb/Sync/ForkInfo.hs +++ b/src/Chainweb/Sync/ForkInfo.hs @@ -209,6 +209,17 @@ resolveForkInfoForProviderState logg bhdb candidateHdrs provider hints finfo ppS -- error. (forkBlocksDescendingStream S.:> forkPoint) <- S.toList + + -- Note that this assumes that the parent of hdr is in the + -- block header DB. With the current cut pipeline that is + -- always the case, because we never validate a header for + -- which the parent hasn't already been validated. In case + -- that this changes in the future we need to adjust the + -- branch diff to start from parent of the lowest processed + -- block. That block would certainly be on the canonical + -- chain, since we wouldn't validate block that aren't + -- extending the current head. + -- $ branchDiff_ bhdb ppBlock hdr let forkBlocksAscending = reverse diff --git a/src/P2P/TaskQueue.hs b/src/P2P/TaskQueue.hs index 5206f2e57a..278a361aba 100644 --- a/src/P2P/TaskQueue.hs +++ b/src/P2P/TaskQueue.hs @@ -204,8 +204,10 @@ session_ limit q logFun env = E.mask $ \restore -> do return False | otherwise -> do logg task' Warn - $ "task finally failed with: " <> sshow e <> " after " <> sshow attempts <> " attempts, limit: " - <> sshow limit + $ "task finally failed" + <> "; attempts: " <> sshow attempts + <> "; limit: " <> sshow limit + <> "; failure: " <> sshow e putResult (_taskResult task') $! Left $! _taskFailures task' logg task l m = logFun @T.Text l $ sshow (_taskId task) <> ": " <> m From 30e879945a72ad251cd16f8372f896e1cf823b01 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Tue, 7 Oct 2025 13:14:23 -0700 Subject: [PATCH 373/378] Make HasTextRepresentation newtype derivable --- src/Chainweb/Backup.hs | 35 +++--- src/Chainweb/BlockHash.hs | 8 +- src/Chainweb/BlockHeight.hs | 8 +- src/Chainweb/BlockPayloadHash.hs | 7 +- src/Chainweb/ChainId.hs | 56 +++------- src/Chainweb/Core/Brief.hs | 1 + src/Chainweb/Logging/Config.hs | 7 +- src/Chainweb/MerkleLogHash.hs | 2 +- src/Chainweb/MinerReward.hs | 34 +----- src/Chainweb/OpenAPIValidation.hs | 2 +- src/Chainweb/Pact/Backend/Compaction.hs | 47 ++++---- src/Chainweb/Pact/Backend/PactState.hs | 4 +- src/Chainweb/Pact/Backend/PactState/Diff.hs | 10 +- .../Pact/Backend/PactState/GrandHash/Calc.hs | 12 +- .../Backend/PactState/GrandHash/Import.hs | 35 +++--- .../Pact/Backend/PactState/GrandHash/Utils.hs | 12 +- src/Chainweb/Pact/Backend/Utils.hs | 2 +- src/Chainweb/Pact/PactService/ExecBlock.hs | 2 +- .../Pact/PactService/Pact4/ExecBlock.hs | 12 +- src/Chainweb/Pact/Payload.hs | 16 +-- src/Chainweb/Pact/Utils.hs | 25 ++--- src/Chainweb/Pact/Validations.hs | 37 +++---- src/Chainweb/Pact4/Validations.hs | 50 ++++----- src/Chainweb/PayloadProvider.hs | 12 +- src/Chainweb/PayloadProvider/EVM.hs | 6 +- src/Chainweb/PayloadProvider/EVM/EngineAPI.hs | 5 +- src/Chainweb/PayloadProvider/EVM/Utils.hs | 14 +-- .../Pact/BlockHistoryMigration.hs | 1 - src/Chainweb/RestAPI/Backup.hs | 21 ++-- src/Chainweb/RestAPI/NetworkID.hs | 24 ++-- src/Chainweb/RestAPI/NodeInfo.hs | 2 +- src/Chainweb/RestAPI/Orphans.hs | 9 +- src/Chainweb/SPV/EventProof.hs | 6 +- src/Chainweb/Time.hs | 39 +------ src/Chainweb/Utils.hs | 103 ++++++++++++------ src/Chainweb/Utils/Paging.hs | 2 +- src/Chainweb/Version.hs | 39 +++---- test/lib/Chainweb/Test/Orphans/Internal.hs | 2 +- test/lib/Chainweb/Test/Pact/CmdBuilder.hs | 2 +- test/lib/Chainweb/Test/Utils/APIValidation.hs | 2 +- .../Chainweb/Test/BlockHeaderDB/PruneForks.hs | 6 +- test/unit/Chainweb/Test/Misc.hs | 5 +- .../Chainweb/Test/Pact/PactServiceTest.hs | 2 +- .../unit/Chainweb/Test/Pact/RemotePactTest.hs | 6 +- test/unit/Chainweb/Test/Roundtrips.hs | 2 +- 45 files changed, 301 insertions(+), 433 deletions(-) diff --git a/src/Chainweb/Backup.hs b/src/Chainweb/Backup.hs index 4c09036274..84c24ec9da 100644 --- a/src/Chainweb/Backup.hs +++ b/src/Chainweb/Backup.hs @@ -1,6 +1,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -17,7 +18,12 @@ module Chainweb.Backup ) where import Control.Lens - +import Chainweb.ChainId +import Chainweb.Logger +import Chainweb.Pact.Backend.Utils(chainDbFileName, withSqliteDb) +import Chainweb.Storage.Table.RocksDB +import Chainweb.Utils +import Chainweb.Version (HasVersion) import Control.Concurrent.Async import Control.Monad import Control.Monad.Catch @@ -25,25 +31,16 @@ import Control.Monad.IO.Class import Control.Monad.Trans.Resource import Data.HashSet(HashSet) import Data.String -import qualified Data.Text as T -import qualified Data.Text.IO as T -import qualified Data.Text.Lazy as TL -import qualified Data.Text.Lazy.Encoding as TL +import Data.Text qualified as T +import Data.Text.IO qualified as T +import Data.Text.Lazy qualified as TL +import Data.Text.Lazy.Encoding qualified as TL +import Pact.Types.SQLite +import Servant import System.Directory import System.FilePath import System.LogLevel -import Pact.Types.SQLite -import Servant - -import Chainweb.ChainId -import Chainweb.Logger -import Chainweb.Pact.Backend.Utils(chainDbFileName, withSqliteDb) -import Chainweb.Utils - -import Chainweb.Storage.Table.RocksDB -import Chainweb.Version (HasVersion) - data BackupOptions = BackupOptions { _backupIdentifier :: !FilePath , _backupPact :: !Bool @@ -103,10 +100,10 @@ makeBackup env options = do forConcurrently_ (_backupChainIds env) $ \cid -> runResourceT $ do db <- withSqliteDb cid (_backupLogger env) (_backupPactDbDir env) False liftIO $ void $ qry db - ("VACUUM main INTO ?") + "VACUUM main INTO ?" [SText $ fromString (thisBackup "0" "sqlite" chainDbFileName cid)] [] - logCr Info $ "pact databases backed up" + logCr Info "pact databases backed up" checkBackup :: Logger logger => BackupEnv logger -> FilePath -> IO (Maybe BackupStatus) checkBackup env name = do @@ -115,6 +112,6 @@ checkBackup env name = do exists <- doesFileExist (thisBackup "status") if exists then - fmap Just . fromText =<< T.readFile (thisBackup "status") + fmap Just . fromTextM =<< T.readFile (thisBackup "status") else return Nothing diff --git a/src/Chainweb/BlockHash.hs b/src/Chainweb/BlockHash.hs index 3cee11d29a..9321c88eca 100644 --- a/src/Chainweb/BlockHash.hs +++ b/src/Chainweb/BlockHash.hs @@ -293,13 +293,7 @@ type AdjacentsHashSize = DigestSize AdjacentsHashAlgorithm newtype AdjacentsHash = AdjacentsHash (CryptoHash Sha2_512_256) deriving stock (Show, Generic) deriving anyclass (NFData) - deriving newtype (Eq, Ord, Hashable, ToJSON, FromJSON) - -instance HasTextRepresentation AdjacentsHash where - toText (AdjacentsHash h) = toText h - fromText = fmap AdjacentsHash . fromText - {-# INLINE toText #-} - {-# INLINE fromText #-} + deriving newtype (Eq, Ord, Hashable, ToJSON, FromJSON, HasTextRepresentation) encodeAdjacentsHash :: AdjacentsHash -> Put encodeAdjacentsHash (AdjacentsHash w) = encodeCryptoHash w diff --git a/src/Chainweb/BlockHeight.hs b/src/Chainweb/BlockHeight.hs index f4c5d42e97..46ffde148b 100644 --- a/src/Chainweb/BlockHeight.hs +++ b/src/Chainweb/BlockHeight.hs @@ -57,17 +57,11 @@ newtype BlockHeight = BlockHeight { getBlockHeight :: Word64 } ( Hashable, ToJSON, FromJSON , AdditiveSemigroup, AdditiveAbelianSemigroup, AdditiveMonoid , Num, Integral, Real, Enum, Bounded + , HasTextRepresentation ) instance Show BlockHeight where show (BlockHeight b) = show b -instance HasTextRepresentation BlockHeight where - toText = toText . getBlockHeight - fromText = fmap BlockHeight . fromText - - {-# INLINE toText #-} - {-# INLINE fromText #-} - instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag BlockHeight where type Tag BlockHeight = 'BlockHeightTag toMerkleNode = encodeMerkleInputNode encodeBlockHeight diff --git a/src/Chainweb/BlockPayloadHash.hs b/src/Chainweb/BlockPayloadHash.hs index ecaec15520..8402652ad7 100644 --- a/src/Chainweb/BlockPayloadHash.hs +++ b/src/Chainweb/BlockPayloadHash.hs @@ -87,6 +87,7 @@ newtype BlockPayloadHash_ a = BlockPayloadHash (MerkleLogHash a) deriving anyclass (NFData) deriving newtype (Bytes, Hashable, ToJSON, FromJSON) deriving newtype (ToJSONKey, FromJSONKey) + deriving newtype (HasTextRepresentation) deriving (IsMerkleLogEntry a ChainwebHashTag) via MerkleRootLogEntry a 'BlockPayloadHashTag encodeBlockPayloadHash :: MerkleHashAlgorithm a => BlockPayloadHash_ a -> Put @@ -97,12 +98,6 @@ decodeBlockPayloadHash => Get (BlockPayloadHash_ a) decodeBlockPayloadHash = BlockPayloadHash <$!> decodeMerkleLogHash -instance MerkleHashAlgorithm a => HasTextRepresentation (BlockPayloadHash_ a) where - toText (BlockPayloadHash h) = toText h - fromText = fmap BlockPayloadHash . fromText - {-# INLINE toText #-} - {-# INLINE fromText #-} - nullBlockPayloadHash :: MerkleHashAlgorithm a => BlockPayloadHash_ a nullBlockPayloadHash = BlockPayloadHash nullHashBytes {-# INLINE nullBlockPayloadHash #-} diff --git a/src/Chainweb/ChainId.hs b/src/Chainweb/ChainId.hs index 2d41508ecf..1cfa12f3d1 100644 --- a/src/Chainweb/ChainId.hs +++ b/src/Chainweb/ChainId.hs @@ -3,10 +3,13 @@ {-# LANGUAGE DeriveFoldable #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} @@ -17,8 +20,6 @@ {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE DeriveTraversable #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module: Chainweb.ChainId @@ -35,8 +36,6 @@ module Chainweb.ChainId , pattern ChainId , HasChainId(..) , checkChainId -, chainIdToText -, chainIdFromText -- * Serialization , encodeChainId @@ -76,33 +75,26 @@ module Chainweb.ChainId ) where import Control.DeepSeq +import Chainweb.Crypto.MerkleLog +import Chainweb.MerkleUniverse +import Chainweb.Utils +import Chainweb.Utils.Serialization +import Configuration.Utils hiding (Lens') import Control.Lens hiding ((.=)) -import Control.Monad.Catch (Exception, MonadThrow) - -import Data.Aeson +import Control.Monad.Catch (Exception, MonadThrow, displayException) import Data.Aeson.Types (toJSONKeyText) -import Data.Hashable (Hashable(..)) import Data.HashMap.Strict (HashMap) -import qualified Data.HashMap.Strict as HM +import Data.HashMap.Strict qualified as HM +import Data.Hashable (Hashable(..)) import Data.Kind import Data.Proxy import Data.Semialign -import qualified Data.Text as T +import Data.Singletons hiding (Index) +import Data.Text qualified as T import Data.Word (Word32) - import GHC.Generics (Generic) import GHC.TypeLits hiding (Mod) --- internal imports - -import Chainweb.Crypto.MerkleLog -import Chainweb.MerkleUniverse -import Chainweb.Utils -import Chainweb.Utils.Serialization - -import Data.Singletons hiding (Index) -import Configuration.Utils hiding (Lens') - -- -------------------------------------------------------------------------- -- -- Exceptions @@ -134,7 +126,7 @@ instance Exception ChainIdException newtype ChainId :: Type where ChainId' :: Word32 -> ChainId deriving stock (Show, Read, Eq, Ord, Generic) - deriving newtype (Hashable, ToJSON, FromJSON, NFData) + deriving newtype (Hashable, ToJSON, FromJSON, NFData, HasTextRepresentation) pattern ChainId :: Word32 -> ChainId pattern ChainId n <- ChainId' n @@ -145,7 +137,7 @@ instance ToJSONKey ChainId where {-# INLINE toJSONKey #-} instance FromJSONKey ChainId where - fromJSONKey = FromJSONKeyTextParser (either fail return . eitherFromText) + fromJSONKey = FromJSONKeyTextParser (either (fail . displayException) return . fromText) {-# INLINE fromJSONKey #-} class HasChainId a where @@ -189,20 +181,6 @@ checkChainId expected actual = _chainId <$> check ChainIdMismatch (_chainId <$> expected) (_chainId <$> actual) {-# INLINE checkChainId #-} -chainIdToText :: ChainId -> T.Text -chainIdToText (ChainId' i) = sshow i -{-# INLINE chainIdToText #-} - -chainIdFromText :: MonadThrow m => T.Text -> m ChainId -chainIdFromText = fmap ChainId' . treadM -{-# INLINE chainIdFromText #-} - -instance HasTextRepresentation ChainId where - toText = chainIdToText - {-# INLINE toText #-} - fromText = chainIdFromText - {-# INLINE fromText #-} - -- -------------------------------------------------------------------------- -- -- Serialization @@ -294,7 +272,7 @@ newtype ChainMap a = ChainMap (HashMap ChainId a) deriving stock (Traversable, Generic) deriving newtype (Eq, Hashable, Functor, Foldable, NFData, Ord, Show) instance Semigroup (ChainMap a) where - (<>) = chainZip (\f _s -> f) + (<>) = chainZip const instance Monoid (ChainMap a) where mempty = ChainMap mempty instance FunctorWithIndex ChainId ChainMap where @@ -361,7 +339,7 @@ suffixHelpCid cid = suffixHelp (Just s) where s = "chain-" <> T.unpack (toText cid) -helpCid :: ChainId -> String -> (Mod f a) +helpCid :: ChainId -> String -> Mod f a helpCid cid t = suffixHelp (Just "the respective chain") t <> mconcat [ hidden <> internal | chainIdInt @Int cid /= 0 ] diff --git a/src/Chainweb/Core/Brief.hs b/src/Chainweb/Core/Brief.hs index 40b148b28b..66448c5773 100644 --- a/src/Chainweb/Core/Brief.hs +++ b/src/Chainweb/Core/Brief.hs @@ -195,3 +195,4 @@ instance Brief BriefBase64ShortByteString where newtype BriefText a = BriefText a instance HasTextRepresentation a => Brief (BriefText a) where brief (BriefText t) = toTextShort t + diff --git a/src/Chainweb/Logging/Config.hs b/src/Chainweb/Logging/Config.hs index 52273f9c52..756ab6a9e0 100644 --- a/src/Chainweb/Logging/Config.hs +++ b/src/Chainweb/Logging/Config.hs @@ -66,14 +66,9 @@ import Utils.Logging.Config -- newtype ClusterId = ClusterId T.Text deriving (Show, Eq, Ord, Generic) - deriving newtype (IsString, ToJSON, FromJSON) + deriving newtype (IsString, ToJSON, FromJSON, HasTextRepresentation) deriving anyclass (NFData) -instance HasTextRepresentation ClusterId where - toText (ClusterId t) = t - fromText = return . ClusterId - - -- | General logging config -- data LogConfig = LogConfig diff --git a/src/Chainweb/MerkleLogHash.hs b/src/Chainweb/MerkleLogHash.hs index 5a0af28903..e3d4b06f9e 100644 --- a/src/Chainweb/MerkleLogHash.hs +++ b/src/Chainweb/MerkleLogHash.hs @@ -162,5 +162,5 @@ instance MerkleHashAlgorithm a => FromJSON (MerkleLogHash a) where instance MerkleHashAlgorithm a => FromJSONKey (MerkleLogHash a) where fromJSONKey = FromJSONKeyTextParser - $ either fail return . eitherFromText + $ either (fail . displayException) return . fromText {-# INLINE fromJSONKey #-} diff --git a/src/Chainweb/MinerReward.hs b/src/Chainweb/MinerReward.hs index 536a7ca046..c07678099e 100644 --- a/src/Chainweb/MinerReward.hs +++ b/src/Chainweb/MinerReward.hs @@ -110,13 +110,7 @@ import Numeric.Natural -- newtype Stu = Stu { _stu :: Natural } deriving stock (Show, Generic) - deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData) - -instance HasTextRepresentation Stu where - toText = toText . _stu - fromText = fmap Stu . fromText - {-# INLINEABLE toText #-} - {-# INLINEABLE fromText #-} + deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData, HasTextRepresentation) instance ToJSON Stu where toJSON = toJSON . toText @@ -147,13 +141,7 @@ divideStu s n = round $ s % fromIntegral n -- newtype MStu = MStu { _mstu :: Natural } deriving stock (Show, Generic) - deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData) - -instance HasTextRepresentation MStu where - toText = toText . _mstu - fromText = fmap MStu . fromText - {-# INLINEABLE toText #-} - {-# INLINEABLE fromText #-} + deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData, HasTextRepresentation) instance ToJSON MStu where toJSON = toJSON . toText @@ -185,13 +173,7 @@ divideMStu s n = round $ s % fromIntegral n -- newtype GStu = GStu { _gstu :: Natural } deriving stock (Show, Generic) - deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData) - -instance HasTextRepresentation GStu where - toText = toText . _gstu - fromText = fmap GStu . fromText - {-# INLINEABLE toText #-} - {-# INLINEABLE fromText #-} + deriving newtype (Eq, Ord, Enum, Num, Real, Integral, NFData, HasTextRepresentation) instance ToJSON GStu where toJSON = toJSON . toText @@ -278,13 +260,7 @@ kdaToGStu (Kda { _kda = s }) = GStu $ round (s * 1e9) newtype MinerReward = MinerReward { _minerReward :: GStu } deriving (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via JsonTextRepresentation "MinerReward" MinerReward - -instance HasTextRepresentation MinerReward where - toText (MinerReward (GStu n)) = toText n - fromText t = MinerReward . GStu <$> fromText t - {-# INLINE toText #-} - {-# INLINE fromText #-} - + deriving newtype (HasTextRepresentation) minerRewardKda :: MinerReward -> Kda minerRewardKda (MinerReward d) = gstuToKda d @@ -370,7 +346,7 @@ mkMinerRewards = let rewards = M.fromList . V.toList . V.map formatRow $ vs in if minerRewardsHash rewards == expectedMinerRewardsHash then rewards - else error $ "hash of miner rewards table does not match expected hash" + else error "hash of miner rewards table does not match expected hash" where formatRow :: (Word64, CsvDecimal) -> (BlockHeight, GStu) formatRow (a, b) = (BlockHeight $ int a, kdaToGStu (Kda $ _csvDecimal b)) diff --git a/src/Chainweb/OpenAPIValidation.hs b/src/Chainweb/OpenAPIValidation.hs index 9d2ea3f5ae..b5d5876022 100644 --- a/src/Chainweb/OpenAPIValidation.hs +++ b/src/Chainweb/OpenAPIValidation.hs @@ -45,7 +45,7 @@ mkValidationMiddleware logger mgr = do findPact pactSpec rawVersion rawChainId rest = do let reqVersion = ChainwebVersionName (T.decodeUtf8 rawVersion) guard (reqVersion == _versionName implicitVersion) - reqChainId <- chainIdFromText (T.decodeUtf8 rawChainId) + reqChainId <- fromTextM (T.decodeUtf8 rawChainId) guard (HS.member reqChainId chainIds) return (BS8.intercalate "/" ("":rest), pactSpec) diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index 436fda4199..eea05b8429 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -1,21 +1,20 @@ -{-# language - BangPatterns - , CPP - , DeriveAnyClass - , DeriveGeneric - , DerivingStrategies - , DuplicateRecordFields - , FlexibleContexts - , GeneralizedNewtypeDeriving - , ImportQualifiedPost - , LambdaCase - , NumericUnderscores - , OverloadedRecordDot - , OverloadedStrings - , PackageImports - , ScopedTypeVariables - , TypeApplications -#-} +{-# LANGUAGE BangPatterns#-} +{-# LANGUAGE CPP#-} +{-# LANGUAGE DeriveAnyClass#-} +{-# LANGUAGE DeriveGeneric#-} +{-# LANGUAGE DerivingStrategies#-} +{-# LANGUAGE DuplicateRecordFields#-} +{-# LANGUAGE FlexibleContexts#-} +{-# LANGUAGE GeneralizedNewtypeDeriving#-} +{-# LANGUAGE ImportQualifiedPost#-} +{-# LANGUAGE LambdaCase#-} +{-# LANGUAGE NumericUnderscores#-} +{-# LANGUAGE OverloadedRecordDot#-} +{-# LANGUAGE OverloadedStrings#-} +{-# LANGUAGE PackageImports#-} +{-# LANGUAGE ScopedTypeVariables#-} +{-# LANGUAGE TypeApplications#-} + module Chainweb.Pact.Backend.Compaction ( @@ -50,8 +49,8 @@ import Chainweb.Pact.Payload.PayloadStore (addNewPayload, lookupPayloadWithHeigh import Chainweb.Pact.Payload.PayloadStore.RocksDB (newPayloadDb) import Chainweb.Storage.Table (Iterator(..), Entry(..), withTableIterator, unCasify, tableInsert) import Chainweb.Storage.Table.RocksDB (RocksDb, withRocksDb, withReadOnlyRocksDb, modernDefaultOptions) -import Chainweb.Utils (sshow, fromText, toText, int) -import Chainweb.Version (ChainId, HasVersion(..), withVersion, ChainwebVersion(..), chainIdToText) +import Chainweb.Utils (sshow, fromTextM, toText, int) +import Chainweb.Version (ChainId, HasVersion(..), withVersion, ChainwebVersion(..)) import Chainweb.Version.Mainnet (mainnet) import Chainweb.Version.Registry (findKnownVersion) import Chainweb.Version.Testnet04 (testnet04) @@ -118,11 +117,11 @@ withPerChainFileLogger ld chainId ll f = do IO.hSetBuffering h IO.LineBuffering withLogger defaultLoggerConfig b $ \l -> do let logger = setComponent "compaction" - $ over setLoggerScope (("chain", chainIdToText chainId) :) + $ over setLoggerScope (("chain", toText chainId) :) $ set setLoggerLevel (l2l ll) l f logger where - cid = Text.unpack (chainIdToText chainId) + cid = Text.unpack (toText chainId) withHandleBackend_' :: (MonadIO m, MonadBaseControl IO m) => (msg -> Text) @@ -193,7 +192,7 @@ getConfig = do parseVersion = fromMaybe (error "ChainwebVersion parse failed") . (>>= findKnownVersion) - . fromText + . fromTextM main :: IO () main = do @@ -738,7 +737,7 @@ compactRocksDb logger cids minBlockHeight srcDb targetDb = do targetWbhdb <- initWebBlockHeaderDb targetDb forM_ cids $ \cid -> do let log' = logFunctionText (addChainIdLabel cid logger) - log' LL.Info $ "Starting chain " <> chainIdToText cid + log' LL.Info $ "Starting chain " <> toText cid srcBlockHeaderDb <- getWebBlockHeaderDb srcWbhdb cid targetBlockHeaderDb <- getWebBlockHeaderDb targetWbhdb cid diff --git a/src/Chainweb/Pact/Backend/PactState.hs b/src/Chainweb/Pact/Backend/PactState.hs index 579d524c47..4a3e2b560c 100644 --- a/src/Chainweb/Pact/Backend/PactState.hs +++ b/src/Chainweb/Pact/Backend/PactState.hs @@ -55,7 +55,7 @@ module Chainweb.Pact.Backend.PactState import Chainweb.BlockHeight (BlockHeight(..)) import Chainweb.Logger (Logger, addLabel) -import Chainweb.Utils (T2(..), int) +import Chainweb.Utils (T2(..), int, HasTextRepresentation (toText)) import Chainweb.Version import Chainweb.Version.Utils (chainIdsAt) import Control.Exception (bracket) @@ -402,7 +402,7 @@ addChainIdLabel :: (Logger logger) => ChainId -> logger -> logger -addChainIdLabel cid = addLabel ("chainId", chainIdToText cid) +addChainIdLabel cid = addLabel ("chainId", toText cid) allChains :: HasVersion => [ChainId] allChains = List.sort $ F.toList $ chainIdsAt (BlockHeight maxBound) diff --git a/src/Chainweb/Pact/Backend/PactState/Diff.hs b/src/Chainweb/Pact/Backend/PactState/Diff.hs index 48839d210e..a64e3a72b1 100644 --- a/src/Chainweb/Pact/Backend/PactState/Diff.hs +++ b/src/Chainweb/Pact/Backend/PactState/Diff.hs @@ -28,12 +28,13 @@ import Chainweb.BlockHeight (BlockHeight) import Chainweb.Logger (logFunctionText, logFunctionJson) import Chainweb.Pact.Backend.Compaction qualified as C import Chainweb.Pact.Backend.PactState (TableDiffable(..), getLatestPactStateAtDiffable, doesPactDbExist, withChainDb, allChains) -import Chainweb.Utils (fromText, toText) -import Chainweb.Version (ChainwebVersion(..), withVersion, ChainId, chainIdToText) +import Chainweb.Utils (fromTextM, toText) +import Chainweb.Version (ChainwebVersion(..), withVersion, ChainId) import Chainweb.Version.Mainnet (mainnet) import Chainweb.Version.Registry (findKnownVersion) import Control.Monad (forM_, when, void) import Control.Monad.IO.Class (MonadIO(liftIO)) +import Control.Monad.Trans.Resource (runResourceT) import Data.Aeson ((.=)) import Data.Aeson qualified as Aeson import Data.ByteString (ByteString) @@ -50,7 +51,6 @@ import Options.Applicative import Streaming.Prelude (Stream, Of) import System.Exit (exitFailure) import System.LogLevel (LogLevel(..)) -import Control.Monad.Trans.Resource (runResourceT) data PactDiffConfig = PactDiffConfig { firstDbDir :: FilePath @@ -114,7 +114,7 @@ main = do logText Info $ case isDifferent of Difference -> "[Non-empty diff]" NoDifference -> "[Empty diff]" - logText Info $ "[Finished chain " <> chainIdToText cid <> "]" + logText Info $ "[Finished chain " <> toText cid <> "]" atomicModifyIORef' isDifferentRef $ \m -> (M.insert cid isDifferent m, ()) @@ -139,7 +139,7 @@ main = do <*> strOption (long "log-dir" <> help "Directory where logs will be placed" <> value ".") parseChainwebVersion :: Text -> ChainwebVersion - parseChainwebVersion = fromMaybe (error "ChainwebVersion parse failed") . (>>= findKnownVersion) . fromText + parseChainwebVersion = fromMaybe (error "ChainwebVersion parse failed") . (>>= findKnownVersion) . fromTextM -- | We don't include the entire rowdata in the diff, only the rowkey. -- This is just a space-saving measure. diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs index 8dd505420a..3240e5a98e 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Calc.hs @@ -24,7 +24,7 @@ module Chainweb.Pact.Backend.PactState.GrandHash.Calc where import Chainweb.BlockHeight (BlockHeight(..)) -import Chainweb.ChainId (ChainId, chainIdToText) +import Chainweb.ChainId (ChainId) import Chainweb.Logger (Logger, logFunctionText) import Chainweb.Pact.Backend.Compaction qualified as C import Chainweb.Pact.Backend.PactState (allChains) @@ -34,7 +34,7 @@ import Chainweb.Pact.Backend.PactState.GrandHash.Algorithm (ChainGrandHash(..)) import Chainweb.Pact.Backend.PactState.GrandHash.Utils (resolveLatestCutHeaders, resolveCutHeadersAtHeights, computeGrandHashesAt, withConnections, hex, rocksParser, cwvParser) import Chainweb.Pact.Backend.Types import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) -import Chainweb.Utils (sshow) +import Chainweb.Utils (sshow, toText) import Chainweb.Version (ChainwebVersion(..), HasVersion, ChainwebVersionName(..), withVersion) import Chainweb.Version.Development (devnet) import Chainweb.Version.Mainnet (mainnet) @@ -101,7 +101,7 @@ pactCalc logger pactConns rocksDb targets = do resolveCutHeadersAtHeights logger pactConns rocksDb ts pooledForConcurrently chainTargets $ \(b, cutHeader) -> do - fmap (b,) $ computeGrandHashesAt pactConns cutHeader + (b,) <$> computeGrandHashesAt pactConns cutHeader data PactCalcConfig = PactCalcConfig { pactDir :: FilePath @@ -150,7 +150,7 @@ pactCalcMain = do let msg = Text.concat [ "Hash mismatch when attempting to regenerate snapshot: " , "blockheight = ", sshow height, "; " - , "chainId = ", chainIdToText cid, "; " + , "chainId = ", toText cid, "; " ] logFunctionText logger Error msg _ -> do @@ -215,7 +215,7 @@ grandsToJson chainHashes = [ "hash" J..= hex (getChainGrandHash hash) , "header" J..= J.encodeWithAeson header ] - in (chainIdToText cid, o) + in (toText cid, o) in (key J..= val) -- | Output a Haskell module with the embedded hashes. This module is produced @@ -287,7 +287,7 @@ chainHashesToModule v input = prefix let jsonDecode j = "unsafeDecodeBlockHeader " ++ j fromHex b = "unsafeBase16Decode " ++ b - sCid = Text.unpack (chainIdToText cid) + sCid = Text.unpack (toText cid) sHash = inQuotes $ Text.unpack (hex (getChainGrandHash hash)) sHeader = embedQuotes $ Text.unpack (J.encodeText (J.encodeWithAeson header)) in diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs index 67c4bd58bf..74ba98c209 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Import.hs @@ -53,6 +53,21 @@ module Chainweb.Pact.Backend.PactState.GrandHash.Import where import Control.Applicative (optional) +import Chainweb.BlockHeader (blockHash, rankedBlockHash) +import Chainweb.BlockHeight (BlockHeight(..)) +import Chainweb.ChainId (ChainId) +import Chainweb.Logger (Logger, logFunctionText) +import Chainweb.Pact.Backend.Compaction qualified as C +import Chainweb.Pact.Backend.PactState (addChainIdLabel, allChains) +import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..)) +import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot.Mainnet qualified as MainnetSnapshots +import Chainweb.Pact.Backend.PactState.GrandHash.Utils (resolveLatestCutHeaders, resolveCutHeadersAtHeight, computeGrandHashesAt, exitLog, withConnections, chainwebDbFilePath, rocksParser, cwvParser) +import Chainweb.Pact.Backend.Types +import Chainweb.Pact.Backend.Utils qualified as PactDb +import Chainweb.Parent +import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) +import Chainweb.Utils (sshow, HasTextRepresentation (toText)) +import Chainweb.Version (ChainwebVersion(..), HasVersion, withVersion) import Control.Lens ((^?!), ix, view) import Control.Monad (forM_, when) import Data.HashMap.Strict (HashMap) @@ -69,22 +84,6 @@ import Patience.Map qualified as P import System.Directory (copyFile, createDirectoryIfMissing) import System.Environment (setEnv) import System.LogLevel (LogLevel(..)) -import qualified Chainweb.Pact.Backend.Utils as PactDb - -import Chainweb.BlockHeader (blockHash, rankedBlockHash) -import Chainweb.BlockHeight (BlockHeight(..)) -import Chainweb.ChainId (ChainId, chainIdToText) -import Chainweb.Logger (Logger, logFunctionText) -import Chainweb.Pact.Backend.Compaction qualified as C -import Chainweb.Pact.Backend.PactState (addChainIdLabel, allChains) -import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot (Snapshot(..)) -import Chainweb.Pact.Backend.PactState.EmbeddedSnapshot.Mainnet qualified as MainnetSnapshots -import Chainweb.Pact.Backend.PactState.GrandHash.Utils (resolveLatestCutHeaders, resolveCutHeadersAtHeight, computeGrandHashesAt, exitLog, withConnections, chainwebDbFilePath, rocksParser, cwvParser) -import Chainweb.Pact.Backend.Types -import Chainweb.Storage.Table.RocksDB (RocksDb, withReadOnlyRocksDb, modernDefaultOptions) -import Chainweb.Utils (sshow) -import Chainweb.Version (ChainwebVersion(..), HasVersion, withVersion) -import Chainweb.Parent -- | Verifies that the hashes and headers match @grands@. -- @@ -133,7 +132,7 @@ pactVerify logger pactConns rocksDb grands = do P.Delta (Snapshot eHash eHeader) (Snapshot hash header) -> do when (header /= eHeader) $ do logFunctionText logger' Error $ Text.unlines - [ "Chain " <> chainIdToText cid + [ "Chain " <> toText cid , "Block Header mismatch" , " Expected: " <> sshow (view blockHash eHeader) , " Actual: " <> sshow (view blockHash header) @@ -141,7 +140,7 @@ pactVerify logger pactConns rocksDb grands = do when (hash /= eHash) $ do logFunctionText logger' Error $ Text.unlines - [ "Chain " <> chainIdToText cid + [ "Chain " <> toText cid , "Grand Hash mismatch" , " Expected: " <> sshow eHash , " Actual: " <> sshow hash diff --git a/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs b/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs index db27f71711..3d459082aa 100644 --- a/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs +++ b/src/Chainweb/Pact/Backend/PactState/GrandHash/Utils.hs @@ -27,7 +27,7 @@ module Chainweb.Pact.Backend.PactState.GrandHash.Utils import Chainweb.BlockHeader (BlockHeader, blockHeight) import Chainweb.BlockHeight (BlockHeight(..)) -import Chainweb.ChainId (ChainId, chainIdToText) +import Chainweb.ChainId (ChainId) import Chainweb.CutDB (cutHashesTable, readHighestCutHeaders) import Chainweb.Logger (Logger, logFunctionText) import Chainweb.Pact.Backend.PactState (getLatestPactStateAt, getLatestBlockHeight, addChainIdLabel) @@ -37,7 +37,7 @@ import Chainweb.Pact.Backend.Types import Chainweb.Pact.Backend.Utils (startSqliteDb, stopSqliteDb) import Chainweb.Storage.Table.RocksDB (RocksDb) import Chainweb.TreeDB (seekAncestor) -import Chainweb.Utils (fromText, toText, sshow) +import Chainweb.Utils (fromTextM, toText, sshow) import Chainweb.Version (ChainwebVersion(..), HasVersion(..)) import Chainweb.Version.Mainnet (mainnet) import Chainweb.Version.Registry @@ -176,13 +176,13 @@ checkPactDbsExist :: FilePath -> [ChainId] -> IO () checkPactDbsExist dbDir cids = pooledForConcurrently_ cids $ \cid -> do e <- doesFileExist (chainwebDbFilePath cid dbDir) when (not e) $ do - error $ "Pact database doesn't exist for expected chain id " <> Text.unpack (chainIdToText cid) + error $ "Pact database doesn't exist for expected chain id " <> Text.unpack (toText cid) chainwebDbFilePath :: ChainId -> FilePath -> FilePath chainwebDbFilePath cid dbDir = let fileName = mconcat [ "pact-v1-chain-" - , Text.unpack (chainIdToText cid) + , Text.unpack (toText cid) , ".sqlite" ] in dbDir fileName @@ -220,8 +220,8 @@ hex :: ByteString -> Text hex = Text.decodeUtf8 . Base16.encode cwvParser :: O.Parser ChainwebVersion -cwvParser = fmap (fromMaybe (error "ChainwebVersion parse failed") . (>>= findKnownVersion) . fromText) - $ O.strOption +cwvParser = fromMaybe (error "ChainwebVersion parse failed") . (>>= findKnownVersion) . fromTextM + <$> O.strOption (O.long "graph-version" <> O.short 'v' <> O.metavar "CHAINWEB_VERSION" diff --git a/src/Chainweb/Pact/Backend/Utils.hs b/src/Chainweb/Pact/Backend/Utils.hs index b4782565f3..14220dcf87 100644 --- a/src/Chainweb/Pact/Backend/Utils.hs +++ b/src/Chainweb/Pact/Backend/Utils.hs @@ -227,7 +227,7 @@ startSqliteDb cid logger dbDir doResetDb = do chainDbFileName :: ChainId -> FilePath chainDbFileName cid = fold [ "pact-v1-chain-" - , T.unpack (chainIdToText cid) + , T.unpack (toText cid) , ".sqlite" ] diff --git a/src/Chainweb/Pact/PactService/ExecBlock.hs b/src/Chainweb/Pact/PactService/ExecBlock.hs index 9b51d8f0d0..14568aba0e 100644 --- a/src/Chainweb/Pact/PactService/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/ExecBlock.hs @@ -440,7 +440,7 @@ validateParsedChainwebTx _logger blockEnv tx checkChain :: ExceptT InsertError IO () checkChain = unless (Pact.assertChainId cid txCid) $ - throwError $ InsertErrorWrongChain (chainIdToText cid) (Pact._chainId txCid) + throwError $ InsertErrorWrongChain (toText cid) (Pact._chainId txCid) where txCid = view (Pact.cmdPayload . Pact.payloadObj . Pact.pMeta . Pact.pmChainId) tx diff --git a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs index bb40644700..63bca1349f 100644 --- a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs @@ -1,20 +1,20 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE PartialTypeSignatures #-} -{-# LANGUAGE ImportQualifiedPost #-} -- | -- Module: Chainweb.Pact.PactService.Pact4.ExecBlock @@ -254,7 +254,7 @@ checkChain :: ChainId -> Pact4.Transaction -> ExceptT InsertError IO () checkChain cid tx = unless (Pact4.assertChainId cid txCid) $ - throwError $ InsertErrorWrongChain (chainIdToText cid) (Pact4._chainId txCid) + throwError $ InsertErrorWrongChain (toText cid) (Pact4._chainId txCid) where txCid = view (Pact4.cmdPayload . to Pact4.payloadObj . Pact4.pMeta . Pact4.pmChainId) tx diff --git a/src/Chainweb/Pact/Payload.hs b/src/Chainweb/Pact/Payload.hs index 09d568468d..a0911dac5c 100644 --- a/src/Chainweb/Pact/Payload.hs +++ b/src/Chainweb/Pact/Payload.hs @@ -186,7 +186,7 @@ type BlockTransactionsHash = BlockTransactionsHash_ ChainwebMerkleHashAlgorithm newtype BlockTransactionsHash_ a = BlockTransactionsHash (MerkleLogHash a) deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) - deriving newtype (Hashable, ToJSON, FromJSON) + deriving newtype (Hashable, ToJSON, FromJSON, HasTextRepresentation) deriving (IsMerkleLogEntry a ChainwebHashTag) via MerkleRootLogEntry a 'BlockTransactionsHashTag encodeBlockTransactionsHash :: MerkleHashAlgorithm a => BlockTransactionsHash_ a -> Put @@ -197,12 +197,6 @@ decodeBlockTransactionsHash => Get (BlockTransactionsHash_ a) decodeBlockTransactionsHash = BlockTransactionsHash <$!> decodeMerkleLogHash -instance HasTextRepresentation BlockTransactionsHash where - toText (BlockTransactionsHash h) = toText h - fromText = fmap BlockTransactionsHash . fromText - {-# INLINE toText #-} - {-# INLINE fromText #-} - -- -------------------------------------------------------------------------- -- -- Block Outputs Hash @@ -211,7 +205,7 @@ type BlockOutputsHash = BlockOutputsHash_ ChainwebMerkleHashAlgorithm newtype BlockOutputsHash_ a = BlockOutputsHash (MerkleLogHash a) deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) - deriving newtype (Hashable, ToJSON, FromJSON) + deriving newtype (Hashable, ToJSON, FromJSON, HasTextRepresentation) encodeBlockOutputsHash :: MerkleHashAlgorithm a => BlockOutputsHash_ a -> Put encodeBlockOutputsHash (BlockOutputsHash w) = encodeMerkleLogHash w @@ -228,12 +222,6 @@ instance MerkleHashAlgorithm a => IsMerkleLogEntry a ChainwebHashTag (BlockOutpu {-# INLINE toMerkleNode #-} {-# INLINE fromMerkleNode #-} -instance HasTextRepresentation BlockOutputsHash where - toText (BlockOutputsHash h) = toText h - fromText = fmap BlockOutputsHash . fromText - {-# INLINE toText #-} - {-# INLINE fromText #-} - -- -------------------------------------------------------------------------- -- -- Transaction diff --git a/src/Chainweb/Pact/Utils.hs b/src/Chainweb/Pact/Utils.hs index 6551c842bf..5a7fc1a4ac 100644 --- a/src/Chainweb/Pact/Utils.hs +++ b/src/Chainweb/Pact/Utils.hs @@ -1,4 +1,5 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} -- | @@ -29,31 +30,21 @@ module Chainweb.Pact.Utils ) where import Data.Aeson -import qualified Data.Text as T - +import Data.Text qualified as T import Control.Monad.Catch - --- import Pact.Parse --- import qualified Pact.Types.ChainId as P --- import qualified Pact.Types.Term as P --- import Pact.Types.ChainMeta --- import Pact.Types.KeySet (ed25519HexFormat) - -import qualified Pact.JSON.Encode as J - --- Internal modules - +import Pact.JSON.Encode qualified as J import Chainweb.ChainId import Chainweb.Miner.Pact import Chainweb.Pact.Payload import Chainweb.Time -import qualified Pact.Core.ChainData as P -import qualified Pact.Core.Guards as P +import Pact.Core.ChainData qualified as P +import Pact.Core.Guards qualified as P import Pact.Core.Guards (ed25519HexFormat) -import qualified Data.Set as Set +import Data.Set qualified as Set +import Chainweb.Utils (fromTextM) fromPactChainId :: MonadThrow m => P.ChainId -> m ChainId -fromPactChainId (P.ChainId t) = chainIdFromText t +fromPactChainId (P.ChainId t) = fromTextM t -- | This is the recursion principle of an 'Aeson' 'Result' of type 'a'. -- Similar to 'either', 'maybe', or 'bool' combinators diff --git a/src/Chainweb/Pact/Validations.hs b/src/Chainweb/Pact/Validations.hs index 4aca2370ba..21164ad151 100644 --- a/src/Chainweb/Pact/Validations.hs +++ b/src/Chainweb/Pact/Validations.hs @@ -1,6 +1,8 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} + -- | -- Module: Chainweb.Pact.Validations -- Copyright: Copyright © 2018,2019,2020,2021,2022 Kadena LLC. @@ -34,32 +36,27 @@ module Chainweb.Pact.Validations ) where import Control.Lens - -import Data.Decimal (decimalPlaces) -import Data.Maybe -import Data.Either (isRight) -import Data.List.NonEmpty (NonEmpty, nonEmpty) -import Data.Text (Text) -import qualified Data.Text as Text -import qualified Data.ByteString.Short as SBS -import Data.Word (Word8) - --- internal modules - import Chainweb.BlockCreationTime (BlockCreationTime(..)) +import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Pact.Types import Chainweb.Parent import Chainweb.Time (Seconds(..), Time(..), secondsToTimeSpan, scaleTimeSpan, second, add) +import Chainweb.Utils (ebool_, int, HasTextRepresentation (toText)) import Chainweb.Version - -import qualified Pact.Core.Command.Types as Pact -import qualified Pact.Core.ChainData as Pact -import qualified Pact.Core.Gas.Types as Pact -import qualified Pact.Core.Hash as Pact -import qualified Chainweb.Pact.Transaction as Pact -import Chainweb.Utils (ebool_, int) import Chainweb.Version.Guards (maxBlockGasLimit) +import Data.ByteString.Short qualified as SBS +import Data.Decimal (decimalPlaces) +import Data.Either (isRight) +import Data.List.NonEmpty (NonEmpty, nonEmpty) +import Data.Maybe +import Data.Text (Text) +import Data.Text qualified as Text +import Data.Word (Word8) import Numeric.Natural +import Pact.Core.ChainData qualified as Pact +import Pact.Core.Command.Types qualified as Pact +import Pact.Core.Gas.Types qualified as Pact +import Pact.Core.Hash qualified as Pact -- | Check whether a local Api request has valid metadata @@ -115,7 +112,7 @@ assertPreflightMetadata env cmd@(Pact.Command pay sigs hsh) blockCtx sigVerify = -- chainweb node structure -- assertChainId :: ChainId -> Pact.ChainId -> Bool -assertChainId cid0 cid1 = chainIdToText cid0 == Pact._chainId cid1 +assertChainId cid0 cid1 = toText cid0 == Pact._chainId cid1 -- | Check and assert that 'GasPrice' is rounded to at most 12 decimal -- places. diff --git a/src/Chainweb/Pact4/Validations.hs b/src/Chainweb/Pact4/Validations.hs index e651f6d792..c5c5c0d878 100644 --- a/src/Chainweb/Pact4/Validations.hs +++ b/src/Chainweb/Pact4/Validations.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -42,37 +43,32 @@ module Chainweb.Pact4.Validations , defaultLenientTimeSlop ) where +import Chainweb.BlockCreationTime (BlockCreationTime(..)) +import Chainweb.Pact.Types +import Chainweb.Pact4.Transaction +import Chainweb.Parent +import Chainweb.Time (Seconds(..), Time(..), secondsToTimeSpan, scaleTimeSpan, second, add) +import Chainweb.Utils (ebool_, int, fromTextM, HasTextRepresentation (toText)) +import Chainweb.Version +import Chainweb.Version.Guards (isWebAuthnPrefixLegal, validPPKSchemes) import Control.Lens - -import Data.Decimal (decimalPlaces) import Data.Bifunctor (first) -import Data.Maybe (isJust, catMaybes, fromMaybe) +import Data.ByteString.Short qualified as SBS +import Data.Decimal (decimalPlaces) import Data.Either (isRight) import Data.List.NonEmpty (NonEmpty, nonEmpty) +import Data.Maybe (isJust, catMaybes, fromMaybe) import Data.Text (Text) -import qualified Data.Text as Text -import qualified Data.ByteString.Short as SBS +import Data.Text qualified as Text import Data.Word (Word8) - --- internal modules - -import Chainweb.BlockCreationTime (BlockCreationTime(..)) -import Chainweb.Pact.Types -import Chainweb.Time (Seconds(..), Time(..), secondsToTimeSpan, scaleTimeSpan, second, add) -import Chainweb.Pact4.Transaction -import Chainweb.Version -import Chainweb.Version.Guards (isWebAuthnPrefixLegal, validPPKSchemes) - -import qualified Pact.Types.Gas as P -import qualified Pact.Types.Hash as P -import qualified Pact.Types.ChainId as P -import qualified Pact.Types.Command as P -import qualified Pact.Types.ChainMeta as P -import qualified Pact.Types.KeySet as P -import qualified Pact.Parse as P -import Chainweb.Utils (ebool_, int) -import Chainweb.Parent -import qualified Pact.Core.Gas.Types as Pact5 +import Pact.Core.Gas.Types qualified as Pact5 +import Pact.Parse qualified as P +import Pact.Types.ChainId qualified as P +import Pact.Types.ChainMeta qualified as P +import Pact.Types.Command qualified as P +import Pact.Types.Gas qualified as P +import Pact.Types.Hash qualified as P +import Pact.Types.KeySet qualified as P -- | Check whether a local Api request has valid metadata @@ -125,7 +121,7 @@ assertPreflightMetadata cmd@(P.Command pay sigs hsh) bctx sigVerify serviceEnv = -- | Check whether a particular Pact chain id is parseable -- assertParseChainId :: P.ChainId -> Bool -assertParseChainId (P.ChainId cid) = isJust $ chainIdFromText cid +assertParseChainId (P.ChainId cid) = isJust $ fromTextM @_ @ChainId cid -- | Check whether the chain id defined in the metadata of a Pact/Chainweb -- command payload matches a given chain id. @@ -134,7 +130,7 @@ assertParseChainId (P.ChainId cid) = isJust $ chainIdFromText cid -- chainweb node structure -- assertChainId :: ChainId -> P.ChainId -> Bool -assertChainId cid0 cid1 = chainIdToText cid0 == P._chainId cid1 +assertChainId cid0 cid1 = toText cid0 == P._chainId cid1 -- | Check and assert that 'GasPrice' is rounded to at most 12 decimal -- places. diff --git a/src/Chainweb/PayloadProvider.hs b/src/Chainweb/PayloadProvider.hs index e56bd73112..74cd90d146 100644 --- a/src/Chainweb/PayloadProvider.hs +++ b/src/Chainweb/PayloadProvider.hs @@ -938,19 +938,11 @@ renderPayloadSpvException (ProofPending e curHeight) = newtype TransactionIndex = TransactionIndex Natural deriving (Show, Eq, Ord, Generic) - deriving newtype (FromJSON, ToJSON, Num, Enum, Real, Integral) - -instance HasTextRepresentation TransactionIndex where - toText (TransactionIndex n) = toText n - fromText = fmap TransactionIndex <$> fromText + deriving newtype (FromJSON, ToJSON, Num, Enum, Real, Integral, HasTextRepresentation) newtype EventIndex = EventIndex Natural deriving (Show, Eq, Ord, Generic) - deriving newtype (FromJSON, ToJSON, Num, Enum, Real, Integral) - -instance HasTextRepresentation EventIndex where - toText (EventIndex n) = toText n - fromText = fmap EventIndex <$> fromText + deriving newtype (FromJSON, ToJSON, Num, Enum, Real, Integral, HasTextRepresentation) -- | A way to identify cross chain events. -- diff --git a/src/Chainweb/PayloadProvider/EVM.hs b/src/Chainweb/PayloadProvider/EVM.hs index 846c462fc1..708af053d6 100644 --- a/src/Chainweb/PayloadProvider/EVM.hs +++ b/src/Chainweb/PayloadProvider/EVM.hs @@ -25,6 +25,7 @@ {-# OPTIONS_GHC -Wprepositive-qualified-module #-} {-# LANGUAGE AllowAmbiguousTypes #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- | -- Module: Chainweb.PayloadProvider.EVM @@ -148,10 +149,7 @@ pMinerAddress cid = textOption newtype EngineUri = EngineUri { _engineUri :: URI } deriving (Show, Eq, Generic) deriving (ToJSON, FromJSON) via (JsonTextRepresentation "EngineUri" EngineUri) - -instance HasTextRepresentation EngineUri where - toText (EngineUri u) = toText u - fromText = fmap EngineUri . fromText + deriving newtype (HasTextRepresentation) pEngineUri :: ChainId -> OptionParser EngineUri pEngineUri cid = textOption diff --git a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs index c73c809aa3..61baa3fcc1 100644 --- a/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs +++ b/src/Chainweb/PayloadProvider/EVM/EngineAPI.hs @@ -877,10 +877,7 @@ instance FromJSON PayloadAttributesV3 where newtype PayloadId = PayloadId { _payloadId :: E.BytesN 8 } deriving (Show, Eq, Ord, Generic) deriving (ToJSON, FromJSON) via JsonTextRepresentation "PayloadId" PayloadId - -instance HasTextRepresentation PayloadId where - toText = toText . HexBytes . _payloadId - fromText = fmap (PayloadId . fromHexBytes) . fromText + deriving (HasTextRepresentation) via HexBytes (E.BytesN 8) -- -------------------------------------------------------------------------- -- -- Errors diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs index f8eba6cedb..dff0442727 100644 --- a/src/Chainweb/PayloadProvider/EVM/Utils.hs +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -99,8 +99,7 @@ deriving instance Functor HexBytes -- The implementation should be moved to the Ethereum.Utils instance HasTextRepresentation (HexQuantity Natural) where toText (HexQuantity a) = T.pack $ printf @(Natural -> String) "0x%x" a - fromText t = do - T.hexadecimal <$> strip0x t >>= \case + fromText t = T.hexadecimal <$> strip0x t >>= \case Right (x, "") -> return $ HexQuantity x Right (x, _) -> throwM $ TextFormatException $ "pending characters after parsing " <> sshow x @@ -218,12 +217,7 @@ newtype Address32 = Address32 (E.BytesN 32) deriving (Show, Eq, Ord) deriving newtype (RLP, E.Bytes, Storable) deriving (FromJSON, ToJSON) via (HexBytes (E.BytesN 32)) - -instance HasTextRepresentation Address32 where - toText = toText . HexBytes . E.bytes - fromText = fmap (Address32 . fromHexBytes) . fromText - {-# INLINE toText #-} - {-# INLINE fromText #-} + deriving HasTextRepresentation via (HexBytes (E.BytesN 32)) toAddress32 :: E.Address @@ -271,8 +265,8 @@ _blockValueStu (BlockValue (Wei v)) = Stu (int v) newtype ExecutionRequest = ExecutionRequest BS.ShortByteString deriving (Show, Eq, Ord) deriving newtype (RLP, E.Bytes, Hashable) - deriving ToJSON via (HexBytes (SB.ShortByteString)) - deriving FromJSON via (HexBytes (SB.ShortByteString)) + deriving ToJSON via (HexBytes SB.ShortByteString) + deriving FromJSON via (HexBytes SB.ShortByteString) -- -------------------------------------------------------------------------- -- -- Default Block Parameter diff --git a/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs b/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs index 97366beff3..0ebb7dd127 100644 --- a/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs +++ b/src/Chainweb/PayloadProvider/Pact/BlockHistoryMigration.hs @@ -25,7 +25,6 @@ import Chainweb.Pact.Backend.PactState (qryStream) import Streaming qualified as S import Streaming.Prelude qualified as S import Chainweb.Utils (sshow, whenM) -import Control.Exception.Safe import Chainweb.Utils.Serialization (runPutS, runGetS) import Data.IORef (newIORef, readIORef, modifyIORef') import Control.Monad diff --git a/src/Chainweb/RestAPI/Backup.hs b/src/Chainweb/RestAPI/Backup.hs index efccced472..c20508c5b4 100644 --- a/src/Chainweb/RestAPI/Backup.hs +++ b/src/Chainweb/RestAPI/Backup.hs @@ -1,6 +1,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} @@ -17,25 +18,23 @@ module Chainweb.RestAPI.Backup ) where import Control.Concurrent.Async +import Chainweb.Backup qualified as Backup +import Chainweb.Logger +import Chainweb.RestAPI.Utils +import Chainweb.Time +import Chainweb.Utils +import Chainweb.Version import Control.Concurrent.STM import Control.Monad import Control.Monad.Catch import Control.Monad.IO.Class import Data.Proxy import Data.Text (Text) -import qualified Data.Text as T +import Data.Text qualified as T +import Servant import System.IO.Unsafe import System.LogLevel -import Servant - -import qualified Chainweb.Backup as Backup -import Chainweb.Logger -import Chainweb.Time - -import Chainweb.RestAPI.Utils -import Chainweb.Version - type BackupApi_ = "make-backup" :> QueryFlag "backupPact" :> PostAccepted '[PlainText] Text :<|> "check-backup" :> Capture "backup-name" FilePath :> Get '[PlainText] Backup.BackupStatus @@ -87,4 +86,4 @@ someBackupServer backupEnv = case implicitVersion of getNextBackupIdentifier :: IO Text getNextBackupIdentifier = do Time (epochToNow :: TimeSpan Integer) <- getCurrentTimeIntegral - return $ microsToText (timeSpanToMicros epochToNow) + return $ toText (timeSpanToMicros epochToNow) diff --git a/src/Chainweb/RestAPI/NetworkID.hs b/src/Chainweb/RestAPI/NetworkID.hs index 9d8c46710e..641e4a02df 100644 --- a/src/Chainweb/RestAPI/NetworkID.hs +++ b/src/Chainweb/RestAPI/NetworkID.hs @@ -4,6 +4,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -42,24 +43,17 @@ module Chainweb.RestAPI.NetworkID ) where import Configuration.Utils - +import Chainweb.ChainId +import Chainweb.Utils hiding (check) import Control.DeepSeq import Control.Monad.Catch - import Data.Hashable import Data.Proxy -import qualified Data.Text as T - +import Data.Singletons +import Data.Text qualified as T import GHC.Generics (Generic) import GHC.Stack (HasCallStack) --- Internal imports - -import Chainweb.ChainId -import Chainweb.Utils hiding (check) - -import Data.Singletons - -- -------------------------------------------------------------------------- -- -- Network ID @@ -77,15 +71,15 @@ data NetworkId -- networkIdToText :: NetworkId -> T.Text networkIdToText CutNetwork = "cut" -networkIdToText (ChainNetwork cid) = "chain/" <> chainIdToText cid -networkIdToText (MempoolNetwork cid) = "chain/" <> chainIdToText cid <> "/mempool" +networkIdToText (ChainNetwork cid) = "chain/" <> toText cid +networkIdToText (MempoolNetwork cid) = "chain/" <> toText cid <> "/mempool" {-# INLINE networkIdToText #-} networkIdFromText :: MonadThrow m => T.Text -> m NetworkId networkIdFromText t = case T.split (== '/') t of ["cut"] -> return CutNetwork - ["chain", a, "mempool"] -> MempoolNetwork <$> chainIdFromText a - ["chain", a] -> ChainNetwork <$> chainIdFromText a + ["chain", a, "mempool"] -> MempoolNetwork <$> fromTextM a + ["chain", a] -> ChainNetwork <$> fromTextM a _ -> throwM $ TextFormatException $ "unrecognized network id: \"" <> t <> "\"." unsafeNetworkIdFromText :: HasCallStack => T.Text -> NetworkId diff --git a/src/Chainweb/RestAPI/NodeInfo.hs b/src/Chainweb/RestAPI/NodeInfo.hs index 37009b641d..3a0e4765ec 100644 --- a/src/Chainweb/RestAPI/NodeInfo.hs +++ b/src/Chainweb/RestAPI/NodeInfo.hs @@ -98,7 +98,7 @@ nodeInfoHandler (SomeCutDb (CutDbT db :: CutDbT l v)) = do , nodeNumberOfChains = length curChains , nodeGraphHistory = graphs , nodeLatestBehaviorHeight = latestBehaviorAt - , nodeGenesisHeights = map (\c -> (chainIdToText c, genesisHeight c)) $ HS.toList chainIds + , nodeGenesisHeights = map (\c -> (toText c, genesisHeight c)) $ HS.toList chainIds , nodeHistoricalChains = ruleElems $ HM.toList . HM.map HS.toList . toAdjacencySets <$> _versionGraphs implicitVersion , nodeServiceDate = T.pack <$> _versionServiceDate implicitVersion diff --git a/src/Chainweb/RestAPI/Orphans.hs b/src/Chainweb/RestAPI/Orphans.hs index c081fc5b6d..fa8e69defe 100644 --- a/src/Chainweb/RestAPI/Orphans.hs +++ b/src/Chainweb/RestAPI/Orphans.hs @@ -59,6 +59,7 @@ import P2P.Peer import Pact.Parse (ParsedInteger(..)) import Pact.Server.API () import Pact.Types.Gas (GasLimit(..)) +import Control.Monad.Catch -- -------------------------------------------------------------------------- -- -- HttpApiData @@ -73,7 +74,7 @@ instance ToHttpApiData PeerId where toUrlPiece = toText instance FromHttpApiData PeerId where - parseUrlPiece = first T.pack . eitherFromText + parseUrlPiece = first (T.pack . displayException) . fromText instance FromHttpApiData HostAddress where parseUrlPiece = first sshow . readHostAddressBytes . T.encodeUtf8 @@ -93,16 +94,16 @@ instance ToHttpApiData BlockPayloadHash where toUrlPiece = encodeB64UrlNoPaddingText . runPutS . encodeBlockPayloadHash instance FromHttpApiData ChainwebVersionName where - parseUrlPiece = first T.pack . eitherFromText + parseUrlPiece = first (T.pack . displayException) . fromText instance ToHttpApiData ChainwebVersionName where toUrlPiece = toText instance FromHttpApiData ChainId where - parseUrlPiece = first sshow . chainIdFromText + parseUrlPiece = first sshow . fromText instance ToHttpApiData ChainId where - toUrlPiece = chainIdToText + toUrlPiece = toText deriving newtype instance FromHttpApiData BlockHeight diff --git a/src/Chainweb/SPV/EventProof.hs b/src/Chainweb/SPV/EventProof.hs index 82bb5d1824..1f1c6b0cb1 100644 --- a/src/Chainweb/SPV/EventProof.hs +++ b/src/Chainweb/SPV/EventProof.hs @@ -411,13 +411,9 @@ type BlockEventsHash = BlockEventsHash_ ChainwebMerkleHashAlgorithm newtype BlockEventsHash_ a = BlockEventsHash (MerkleLogHash a) deriving (Show, Eq, Ord, Generic) deriving anyclass (NFData) - deriving newtype (Hashable, ToJSON, FromJSON) + deriving newtype (Hashable, ToJSON, FromJSON, HasTextRepresentation) deriving (IsMerkleLogEntry a ChainwebHashTag) via MerkleRootLogEntry a 'BlockEventsHashTag -instance MerkleHashAlgorithm a => HasTextRepresentation (BlockEventsHash_ a) where - toText (BlockEventsHash h) = toText h - fromText t = BlockEventsHash <$> fromText t - encodeBlockEventsHash :: MerkleHashAlgorithm a => BlockEventsHash_ a -> Put encodeBlockEventsHash (BlockEventsHash w) = encodeMerkleLogHash w diff --git a/src/Chainweb/Time.hs b/src/Chainweb/Time.hs index 50f12a21e8..5a1098d6a1 100644 --- a/src/Chainweb/Time.hs +++ b/src/Chainweb/Time.hs @@ -73,15 +73,11 @@ module Chainweb.Time , Seconds(..) , secondsToTimeSpan , timeSpanToSeconds -, secondsToText -, secondsFromText -- * Micros , Micros(..) , microsToTimeSpan , timeSpanToMicros -, microsToText -, microsFromText -- * Math, constants , add @@ -97,7 +93,6 @@ module Chainweb.Time import Control.DeepSeq import Control.Monad ((<$!>)) -import Control.Monad.Catch import Data.Aeson (FromJSON, ToJSON) import Data.Data @@ -105,7 +100,6 @@ import Data.Hashable (Hashable) import Data.Int import qualified Data.Memory.Endian as BA import Data.Ratio -import qualified Data.Text as T import Data.Time import Data.Time.Clock.POSIX import Data.Time.Clock.System @@ -179,7 +173,7 @@ addTimeSpan (TimeSpan a) (TimeSpan b) = TimeSpan (a + b) {-# INLINE addTimeSpan #-} divTimeSpan :: Integral a => Integral b => TimeSpan b -> a -> TimeSpan b -divTimeSpan (TimeSpan a) s = TimeSpan $ a `div` (int s) +divTimeSpan (TimeSpan a) s = TimeSpan $ a `div` int s {-# INLINE divTimeSpan #-} -- -------------------------------------------------------------------------- -- @@ -323,6 +317,7 @@ newtype Seconds = Seconds Int64 deriving anyclass (Hashable, NFData) deriving newtype (FromJSON, ToJSON) deriving newtype (Num, Enum, Real, Integral) + deriving newtype (HasTextRepresentation) deriving J.Encode via (J.Aeson Int64) secondsToTimeSpan :: Num a => Seconds -> TimeSpan a @@ -333,20 +328,6 @@ timeSpanToSeconds :: Integral a => TimeSpan a -> Seconds timeSpanToSeconds (TimeSpan us) = Seconds $! int $ us `div` 1000000 {-# INLINE timeSpanToSeconds #-} -secondsToText :: Seconds -> T.Text -secondsToText (Seconds s) = sshow s -{-# INLINE secondsToText #-} - -secondsFromText :: MonadThrow m => T.Text -> m Seconds -secondsFromText = fmap Seconds . treadM -{-# INLINABLE secondsFromText #-} - -instance HasTextRepresentation Seconds where - toText = secondsToText - {-# INLINE toText #-} - fromText = secondsFromText - {-# INLINE fromText #-} - -- -------------------------------------------------------------------------- -- -- Microseconds @@ -357,6 +338,7 @@ newtype Micros = Micros Int64 deriving anyclass (Hashable, NFData) deriving newtype (Num, Integral, Real, AdditiveGroup, AdditiveMonoid, AdditiveSemigroup) deriving newtype (ToJSON, FromJSON) + deriving newtype (HasTextRepresentation) deriving J.Encode via (J.Aeson Int64) microsToTimeSpan :: Num a => Micros -> TimeSpan a @@ -364,19 +346,6 @@ microsToTimeSpan (Micros us) = scaleTimeSpan us microsecond {-# INLINE microsToTimeSpan #-} timeSpanToMicros :: Integral a => TimeSpan a -> Micros -timeSpanToMicros (TimeSpan us) = Micros $! int $ us +timeSpanToMicros (TimeSpan us) = Micros $! int us {-# INLINE timeSpanToMicros #-} -microsToText :: Micros -> T.Text -microsToText (Micros us) = sshow us -{-# INLINE microsToText #-} - -microsFromText :: MonadThrow m => T.Text -> m Micros -microsFromText = fmap Micros . treadM -{-# INLINABLE microsFromText #-} - -instance HasTextRepresentation Micros where - toText = microsToText - {-# INLINE toText #-} - fromText = microsFromText - {-# INLINABLE fromText #-} diff --git a/src/Chainweb/Utils.hs b/src/Chainweb/Utils.hs index a019c68516..6fbe131ee8 100644 --- a/src/Chainweb/Utils.hs +++ b/src/Chainweb/Utils.hs @@ -101,7 +101,7 @@ module Chainweb.Utils , tread , treadM , HasTextRepresentation(..) -, eitherFromText +, fromTextM , unsafeFromText , parseM , parseText @@ -345,6 +345,7 @@ import System.Timeout qualified as Timeout import Text.Printf (printf) import Text.Read (readEither) +import Data.Int -- -------------------------------------------------------------------------- -- -- SI unit prefixes @@ -516,7 +517,7 @@ class IxedGet a where ixg :: Index a -> Fold a (IxValue a) default ixg :: Ixed a => Index a -> Fold a (IxValue a) - ixg i = ix i + ixg = ix {-# INLINE ixg #-} -- | A strict version of 'ix'. It requires a 'Monad' constraint on the context. @@ -566,21 +567,25 @@ sshow = fromString . show -- | Read a value from a textual encoding using its 'Read' instance. Returns and -- textual error message if the operation fails. -- -tread :: Read a => T.Text -> Either T.Text a -tread = first T.pack . readEither . T.unpack +tread :: Read a => T.Text -> Either SomeException a +tread = first (toException . TextFormatException . T.pack) . readEither . T.unpack {-# INLINE tread #-} -- | Throws 'TextFormatException' on failure. -- treadM :: MonadThrow m => Read a => T.Text -> m a -treadM = fromEitherM . first TextFormatException . tread +treadM = fromEitherM . tread {-# INLINE treadM #-} -- | Class of types that have an textual representation. -- class HasTextRepresentation a where toText :: a -> T.Text - fromText :: MonadThrow m => T.Text -> m a + fromText :: T.Text -> Either SomeException a + +fromTextM :: MonadThrow m => HasTextRepresentation a => T.Text -> m a +fromTextM = fromEitherM . fromText +{-# INLINE fromTextM #-} instance HasTextRepresentation T.Text where toText = id @@ -597,31 +602,73 @@ instance HasTextRepresentation [Char] where instance HasTextRepresentation Int where toText = sshow {-# INLINE toText #-} - fromText = treadM + fromText = tread {-# INLINE fromText #-} instance HasTextRepresentation Integer where toText = sshow {-# INLINE toText #-} - fromText = treadM + fromText = tread {-# INLINE fromText #-} instance HasTextRepresentation Natural where toText = sshow {-# INLINE toText #-} - fromText = treadM + fromText = tread {-# INLINE fromText #-} instance HasTextRepresentation Word where toText = sshow {-# INLINE toText #-} - fromText = treadM + fromText = tread + {-# INLINE fromText #-} + +instance HasTextRepresentation Word8 where + toText = sshow + {-# INLINE toText #-} + fromText = tread + {-# INLINE fromText #-} + +instance HasTextRepresentation Word16 where + toText = sshow + {-# INLINE toText #-} + fromText = tread + {-# INLINE fromText #-} + +instance HasTextRepresentation Word32 where + toText = sshow + {-# INLINE toText #-} + fromText = tread {-# INLINE fromText #-} instance HasTextRepresentation Word64 where toText = sshow {-# INLINE toText #-} - fromText = treadM + fromText = tread + {-# INLINE fromText #-} + +instance HasTextRepresentation Int8 where + toText = sshow + {-# INLINE toText #-} + fromText = tread + {-# INLINE fromText #-} + +instance HasTextRepresentation Int16 where + toText = sshow + {-# INLINE toText #-} + fromText = tread + {-# INLINE fromText #-} + +instance HasTextRepresentation Int32 where + toText = sshow + {-# INLINE toText #-} + fromText = tread + {-# INLINE fromText #-} + +instance HasTextRepresentation Int64 where + toText = sshow + {-# INLINE toText #-} + fromText = tread {-# INLINE fromText #-} instance HasTextRepresentation UTCTime where @@ -629,7 +676,7 @@ instance HasTextRepresentation UTCTime where {-# INLINE toText #-} fromText d = case parseTimeM False defaultTimeLocale fmt (T.unpack d) of - Nothing -> throwM $ TextFormatException $ "failed to parse utc date " <> sshow d + Nothing -> throwM $ TextFormatException ("failed to parse utc date " <> d) Just x -> return x where fmt = iso8601DateTimeFormat @@ -640,24 +687,11 @@ instance HasTextRepresentation UTCTime where instance HasTextRepresentation URI where toText uri = T.pack $ uriToString id uri "" fromText t = case parseAbsoluteURI (T.unpack t) of - Nothing -> throwM $ TextFormatException $ "failed to parse URI " <> t + Nothing -> throwM $ TextFormatException ("failed to parse URI " <> t) Just u -> return u {-# INLINE toText #-} {-# INLINE fromText #-} --- | Decode a value from its textual representation. --- -eitherFromText - :: HasTextRepresentation a - => T.Text - -> Either String a -eitherFromText = either f return . fromText - where - f e = Left $ case fromException e of - Just (TextFormatException err) -> T.unpack err - _ -> displayException e -{-# INLINE eitherFromText #-} - -- | Unsafely decode a value rom its textual representation. It is an program -- error if decoding fails. -- Marked NOINLINE heuristically, because usually the runtime performance is not @@ -665,7 +699,11 @@ eitherFromText = either f return . fromText -- *Payload files) it can result in severe code blowup, slowing compilation. -- unsafeFromText :: HasCallStack => HasTextRepresentation a => T.Text -> a -unsafeFromText = fromJuste . fromText +unsafeFromText t = case fromText t of + Right a -> a + Left e -> error $ "Chainweb.Utils.unsafeFromText" + <> ": failed to parse" <> T.unpack t + <> "; " <> displayException e {-# NOINLINE unsafeFromText #-} -- | Run a 'A.Parser' on a text input. All input must be consume by the parser. @@ -854,7 +892,7 @@ parseJsonFromText => String -> Value -> Aeson.Parser a -parseJsonFromText l = withText l $! either fail return . eitherFromText +parseJsonFromText l = withText l $! either (fail . displayException) return . fromText -- | A newtype wrapper for derving ToJSON and FromJSON instances via -- a 'HasTextRepresentation' instance @@ -872,7 +910,7 @@ instance ( KnownSymbol s , HasTextRepresentation a ) - => FromJSON (JsonTextRepresentation s a) + => FromJSON (JsonTextRepresentation s a) where parseJSON = fmap JsonTextRepresentation . parseJsonFromText (symbolVal_ @s) {-# INLINE parseJSON #-} @@ -885,7 +923,7 @@ newtype CsvDecimal = CsvDecimal { _csvDecimal :: Decimal } instance CSV.FromField CsvDecimal where parseField s = do - cs <- either (fail . show) pure $ T.unpack <$> T.decodeUtf8' s + cs <- either (fail . show) (pure . T.unpack) (T.decodeUtf8' s) either fail pure $ readEither cs {-# INLINE parseField #-} @@ -1405,7 +1443,7 @@ resourceToBracket res = do liftIO $ writeIORef s' releaseMap return (r, s') - (runResourceT prime, (\(_, s) -> closeInternalState s)) + (runResourceT prime, \(_, s) -> closeInternalState s) -- | Like `sequence` for IO but concurrent concurrentlies :: forall a. [IO a] -> IO [a] @@ -1518,8 +1556,7 @@ approximateThreadDelay d = withMVar threadDelayRng (approximately d) manager :: Int -> IO HTTP.Manager manager micros = HTTP.newManager - $ setManagerRequestTimeout micros - $ HTTP.tlsManagerSettings + $ setManagerRequestTimeout micros HTTP.tlsManagerSettings unsafeManager :: Int -> IO HTTP.Manager unsafeManager micros = HTTP.newTlsManagerWith diff --git a/src/Chainweb/Utils/Paging.hs b/src/Chainweb/Utils/Paging.hs index 0d371edb5a..0d923468f8 100644 --- a/src/Chainweb/Utils/Paging.hs +++ b/src/Chainweb/Utils/Paging.hs @@ -156,7 +156,7 @@ nextItemToText :: HasTextRepresentation k => NextItem k -> T.Text nextItemToText (Inclusive k) = "inclusive:" <> toText k nextItemToText (Exclusive k) = "exclusive:" <> toText k -nextItemFromText :: MonadThrow m => HasTextRepresentation k => T.Text -> m (NextItem k) +nextItemFromText :: HasTextRepresentation k => T.Text -> Either SomeException (NextItem k) nextItemFromText t = case T.break (== ':') t of (a, b) | a == "inclusive" -> Inclusive <$> fromText (T.drop 1 b) diff --git a/src/Chainweb/Version.hs b/src/Chainweb/Version.hs index a7b5856537..c2e2c08244 100644 --- a/src/Chainweb/Version.hs +++ b/src/Chainweb/Version.hs @@ -3,25 +3,25 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DerivingStrategies #-} -{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE FunctionalDependencies #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE QuantifiedConstraints #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeAbstractions #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE ViewPatterns #-} -{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE QuantifiedConstraints #-} -{-# LANGUAGE LambdaCase #-} -{-# LANGUAGE TypeAbstractions #-} -{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE ViewPatterns #-} -- | -- Module: Chainweb.Version @@ -152,29 +152,23 @@ module Chainweb.Version import Control.DeepSeq import Control.Lens hiding ((.=), (<.>), index) import Control.Monad.Catch - import Data.Aeson hiding (pairs) import Data.Aeson.Types import Data.Foldable import Data.Hashable import Data.HashMap.Strict (HashMap) -import qualified Data.HashMap.Strict as HM -import qualified Data.HashSet as HS +import Data.HashMap.Strict qualified as HM +import Data.HashSet qualified as HS import Data.Set(Set) import Data.Proxy -import qualified Data.Text as T +import Data.Text qualified as T import Data.Word - import GHC.Generics(Generic) import GHC.TypeLits hiding (Nat, fromSNat, withSomeSNat) import GHC.TypeNats import GHC.Stack - --- internal modules - import Pact.Core.Gas (Gas) import Pact.Core.Names (VerifierName) - import Chainweb.BlockCreationTime import Chainweb.BlockHeight import Chainweb.ChainId @@ -188,12 +182,11 @@ import Chainweb.Pact.Transaction qualified as Pact import Chainweb.Utils import Chainweb.Utils.Rule import Chainweb.Utils.Serialization - import Data.Singletons - import P2P.Peer import GHC.Exts (WithDict(..)) -import qualified Chainweb.Pact4.Transaction as Pact4 +import Chainweb.Pact4.Transaction qualified as Pact4 +import Data.Bifunctor -- -------------------------------------------------------------------------- -- -- Forks @@ -330,7 +323,7 @@ instance ToJSONKey Fork where instance FromJSON Fork where parseJSON = parseJsonFromText "Fork" instance FromJSONKey Fork where - fromJSONKey = FromJSONKeyTextParser $ either fail return . eitherFromText + fromJSONKey = FromJSONKeyTextParser $ either fail return . first displayException . fromText data ForkHeight = ForkAtBlockHeight BlockHeight | ForkAtGenesis | ForkNever deriving stock (Generic, Eq, Ord, Show) @@ -344,15 +337,11 @@ makePrisms ''ForkHeight newtype ChainwebVersionName = ChainwebVersionName { getChainwebVersionName :: T.Text } deriving stock (Generic, Eq, Ord) - deriving newtype (ToJSON, FromJSON) + deriving newtype (ToJSON, FromJSON, HasTextRepresentation) deriving anyclass (Hashable, NFData) instance Show ChainwebVersionName where show = T.unpack . getChainwebVersionName -instance HasTextRepresentation ChainwebVersionName where - toText = getChainwebVersionName - fromText = pure . ChainwebVersionName - -- -------------------------------------------------------------------------- -- -- Chainweb Version Code diff --git a/test/lib/Chainweb/Test/Orphans/Internal.hs b/test/lib/Chainweb/Test/Orphans/Internal.hs index 4fc272a827..ddd9a52228 100644 --- a/test/lib/Chainweb/Test/Orphans/Internal.hs +++ b/test/lib/Chainweb/Test/Orphans/Internal.hs @@ -267,7 +267,7 @@ instance Arbitrary NodeInfo where , nodeNumberOfChains = length curChains , nodeGraphHistory = graphs , nodeLatestBehaviorHeight = latestBehaviorAt - , nodeGenesisHeights = map (\c -> (chainIdToText c, genesisHeight c)) $ HS.toList chainIds + , nodeGenesisHeights = map (\c -> (toText c, genesisHeight c)) $ HS.toList chainIds , nodeHistoricalChains = ruleElems $ fmap (HM.toList . HM.map HS.toList . toAdjacencySets) $ _versionGraphs v , nodeServiceDate = T.pack <$> _versionServiceDate v , nodeBlockDelay = _versionBlockDelay v diff --git a/test/lib/Chainweb/Test/Pact/CmdBuilder.hs b/test/lib/Chainweb/Test/Pact/CmdBuilder.hs index f3b3c85f63..c1fd73c400 100644 --- a/test/lib/Chainweb/Test/Pact/CmdBuilder.hs +++ b/test/lib/Chainweb/Test/Pact/CmdBuilder.hs @@ -177,7 +177,7 @@ defaultCmd cid = CmdBuilder , _cbVerifiers = [] , _cbRPC = mkExec' "1" , _cbNonce = Nothing - , _cbChainId = chainIdToText cid + , _cbChainId = toText cid , _cbSender = "sender00" , _cbGasLimit = GasLimit (Gas 10_000) , _cbGasPrice = GasPrice 0.000_1 diff --git a/test/lib/Chainweb/Test/Utils/APIValidation.hs b/test/lib/Chainweb/Test/Utils/APIValidation.hs index e68aa39e69..d9d7a96b84 100644 --- a/test/lib/Chainweb/Test/Utils/APIValidation.hs +++ b/test/lib/Chainweb/Test/Utils/APIValidation.hs @@ -92,7 +92,7 @@ mkApiValidationMiddleware = do ("" : "chainweb" : "0.0" : rawVersion : "chain" : rawChainId : "pact" : "api" : "v1" : rest) -> do let reqVersion = T.decodeUtf8 rawVersion guard (reqVersion == getChainwebVersionName (_versionName implicitVersion)) - reqChainId <- chainIdFromText (T.decodeUtf8 rawChainId) + reqChainId <- fromTextM (T.decodeUtf8 rawChainId) guard (HashSet.member reqChainId chainIds) return (B8.intercalate "/" ("":rest), pactOpenApiSpec) _ -> Nothing diff --git a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs index 29f16deae2..42d912b586 100644 --- a/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs +++ b/test/unit/Chainweb/Test/BlockHeaderDB/PruneForks.hs @@ -170,7 +170,11 @@ progressArbitraryFork cutTable wbhdb candCutCount = do put (succ nextNonce) return () -pruneTestRandom :: (HasVersion, _) => _ +pruneTestRandom + :: HasVersion + => RocksDb + -> (Casify RocksDbTable CutHashes -> WebBlockHeaderDb -> StateT Nonce IO a) + -> IO Int pruneTestRandom baseRdb f = runResourceT $ do rdb <- withTestRocksDb "" baseRdb liftIO $ do diff --git a/test/unit/Chainweb/Test/Misc.hs b/test/unit/Chainweb/Test/Misc.hs index 6370f64dcd..2bc729f600 100644 --- a/test/unit/Chainweb/Test/Misc.hs +++ b/test/unit/Chainweb/Test/Misc.hs @@ -22,10 +22,9 @@ import Chainweb.ChainId import Chainweb.MerkleUniverse import Chainweb.Parent import Chainweb.Test.Orphans.Internal () -import Chainweb.Utils (HasTextRepresentation(..)) +import Chainweb.Utils import Chainweb.Utils.Serialization import Control.Concurrent (threadDelay) -import Control.Lens import Control.Scheduler (Comp(..), scheduleWork, terminateWith, withScheduler) import Data.HashMap.Strict qualified as HM import PropertyMatchers qualified as P @@ -93,7 +92,7 @@ propPayloadWithOutputsEncoding pwo hashedAdjacentBlockHashRecordSmokeTest :: IO () hashedAdjacentBlockHashRecordSmokeTest = do -- smoke test - blockHashHash <- fromText "rxPASJkSJKXkxmREa2iKr0j7VFbbNilgGwDsFgx05VQ" + blockHashHash <- fromTextM "rxPASJkSJKXkxmREa2iKr0j7VFbbNilgGwDsFgx05VQ" let hashRecord = BlockHashRecord (HM.fromList [(unsafeChainId 0, Parent nullBlockHash)]) encodeAdjacentsHash (adjacentsHash hashRecord) & runPutS diff --git a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs index d205c2263e..2e4f2bb428 100644 --- a/test/unit/Chainweb/Test/Pact/PactServiceTest.hs +++ b/test/unit/Chainweb/Test/Pact/PactServiceTest.hs @@ -327,7 +327,7 @@ testNewBlockExcludesInvalid baseRdb = withVersion v $ runResourceT $ do ] } - badChain <- buildCwCmd $ transferCmd 1.0 & set cbChainId (chainIdToText $ unsafeChainId 1) + badChain <- buildCwCmd $ transferCmd 1.0 & set cbChainId (toText $ unsafeChainId 1) _ <- advanceAllChains fixture $ onChain chain0 $ \ph -> do mempoolInsert fixture chain0 Mempool.CheckedInsert [regularTx1] diff --git a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs index a58afcc9ac..8223d4d0e3 100644 --- a/test/unit/Chainweb/Test/Pact/RemotePactTest.hs +++ b/test/unit/Chainweb/Test/Pact/RemotePactTest.hs @@ -246,11 +246,11 @@ crosschainTest baseRdb step = withVersion v $ runResourceT $ do [ PString "sender00" , PString "sender01" , PDecimal 1.0 - , PString (chainIdToText targetChain) + , PString (toText targetChain) ] ] ] - $ set cbRPC (mkExec ("(coin.transfer-crosschain \"sender00\" \"sender01\" (read-keyset 'k) \"" <> chainIdToText targetChain <> "\" 1.0)") (mkKeySetData "k" [sender01])) + $ set cbRPC (mkExec ("(coin.transfer-crosschain \"sender00\" \"sender01\" (read-keyset 'k) \"" <> toText targetChain <> "\" 1.0)") (mkKeySetData "k" [sender01])) $ defaultCmd srcChain send fx srcChain [initiator] @@ -310,7 +310,7 @@ crosschainTest baseRdb step = withVersion v $ runResourceT $ do , P.checkAll [ P.fun _peName ? P.equals "TRANSFER_XCHAIN_RECD" , P.fun _peArgs ? P.equals - [PString "", PString "sender01", PDecimal 1.0, PString (chainIdToText srcChain)] + [PString "", PString "sender01", PDecimal 1.0, PString (toText srcChain)] ] , P.fun _peName ? P.equals "X_RESUME" , P.succeed diff --git a/test/unit/Chainweb/Test/Roundtrips.hs b/test/unit/Chainweb/Test/Roundtrips.hs index 9a95ccd69b..b5da0628a7 100644 --- a/test/unit/Chainweb/Test/Roundtrips.hs +++ b/test/unit/Chainweb/Test/Roundtrips.hs @@ -463,7 +463,7 @@ base64RoundtripTests = testGroup "Base64 encoding roundtrips" hasTextRepresentationTests :: TestTree hasTextRepresentationTests = testGroup "HasTextRepresentation roundtrips" - [ testProperty "ChainwebVersionName" $ prop_iso' @_ @ChainwebVersionName eitherFromText toText + [ testProperty "ChainwebVersionName" $ prop_iso' @_ @ChainwebVersionName fromText toText , testProperty "ChainId" $ prop_iso' @_ @ChainId fromText toText , testProperty "BlockHash" $ prop_iso' @_ @BlockHash fromText toText , testProperty "Seconds" $ prop_iso' @_ @Seconds fromText toText From 5593f48e7b64f59f36baa283e88251aa3d00db72 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 8 Oct 2025 10:19:03 -0700 Subject: [PATCH 374/378] improve some failure message as recommended in review --- src/Chainweb/PayloadProvider/EVM/Utils.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Chainweb/PayloadProvider/EVM/Utils.hs b/src/Chainweb/PayloadProvider/EVM/Utils.hs index dff0442727..fe8a7dd85f 100644 --- a/src/Chainweb/PayloadProvider/EVM/Utils.hs +++ b/src/Chainweb/PayloadProvider/EVM/Utils.hs @@ -101,8 +101,8 @@ instance HasTextRepresentation (HexQuantity Natural) where toText (HexQuantity a) = T.pack $ printf @(Natural -> String) "0x%x" a fromText t = T.hexadecimal <$> strip0x t >>= \case Right (x, "") -> return $ HexQuantity x - Right (x, _) -> throwM $ TextFormatException - $ "pending characters after parsing " <> sshow x + Right (x, p) -> throwM $ TextFormatException + $ sshow (T.length p) <> " pending characters after parsing " <> sshow x Left e -> throwM $ TextFormatException (T.pack e) {-# INLINE toText #-} {-# INLINE fromText #-} From fbd049a0fb632e924452dd6814f71661ff8f7101 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 8 Oct 2025 10:19:49 -0700 Subject: [PATCH 375/378] remove redundant import --- test/lib/Chainweb/Test/Utils/APIValidation.hs | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/test/lib/Chainweb/Test/Utils/APIValidation.hs b/test/lib/Chainweb/Test/Utils/APIValidation.hs index d9d7a96b84..efb43d023a 100644 --- a/test/lib/Chainweb/Test/Utils/APIValidation.hs +++ b/test/lib/Chainweb/Test/Utils/APIValidation.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE NumericUnderscores #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} @@ -15,36 +15,26 @@ module Chainweb.Test.Utils.APIValidation ) where import Control.Exception (Exception, evaluate) +import Chainweb.Utils +import Chainweb.Version import Control.Monad - -import qualified Data.ByteString.Char8 as B8 -import qualified Data.ByteString.Lazy as BL +import Data.ByteString.Char8 qualified as B8 +import Data.ByteString.Lazy qualified as BL import Data.Foldable -import qualified Data.HashSet as HashSet +import Data.HashSet qualified as HashSet import Data.IORef -import qualified Data.Map as Map -import qualified Data.Text.Encoding as T -import Data.Typeable -import qualified Data.Yaml as Yaml - +import Data.Map qualified as Map +import Data.Text.Encoding qualified as T +import Data.Yaml qualified as Yaml import GHC.Stack - -import qualified Network.HTTP.Client as HTTP +import Network.HTTP.Client qualified as HTTP import Network.HTTP.Types -import qualified Network.Wai as W +import Network.Wai qualified as W import Network.Wai.Middleware.OpenApi(OpenApi) -import qualified Network.Wai.Middleware.Validation as WV - +import Network.Wai.Middleware.Validation qualified as WV import System.IO.Unsafe(unsafePerformIO) - import Text.Show.Pretty --- internal modules - -import Chainweb.ChainId -import Chainweb.Utils -import Chainweb.Version - -- -------------------------------------------------------------------------- -- -- Validation Exception @@ -53,7 +43,7 @@ data ValidationException = ValidationException , vResp :: (ResponseHeaders, Status, BL.ByteString) , vErr :: WV.TopLevelError } - deriving (Show, Typeable) + deriving (Show) instance Exception ValidationException From 40ab7ab510248a120e760e47ce06c508c635b928 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 8 Oct 2025 10:20:01 -0700 Subject: [PATCH 376/378] fix/disable builds for cwtools --- cwtools/cwtools.cabal | 2 ++ cwtools/evm-genesis/Main.hs | 5 ++- cwtools/header-dump/Main.hs | 4 +-- cwtools/known-graphs/Main.hs | 2 +- cwtools/run-nodes/Main.hs | 2 +- cwtools/txstream/Main.hs | 64 ++++++++++++++++++++---------------- 6 files changed, 44 insertions(+), 35 deletions(-) diff --git a/cwtools/cwtools.cabal b/cwtools/cwtools.cabal index 3bb9fe5f2d..6a719d14b1 100644 --- a/cwtools/cwtools.cabal +++ b/cwtools/cwtools.cabal @@ -147,6 +147,7 @@ executable compact executable db-checksum import: warning-flags, debugging-flags default-language: Haskell2010 + buildable: False ghc-options: -threaded -rtsopts @@ -232,6 +233,7 @@ executable genconf executable header-dump import: warning-flags, debugging-flags default-language: Haskell2010 + buildable: False ghc-options: -threaded -rtsopts diff --git a/cwtools/evm-genesis/Main.hs b/cwtools/evm-genesis/Main.hs index 6699c3f3b4..966cc58e0c 100644 --- a/cwtools/evm-genesis/Main.hs +++ b/cwtools/evm-genesis/Main.hs @@ -96,8 +96,7 @@ main = do queryNode :: Natural -> FilePath -> IO E.Header queryNode cid spec = withRethNode cid spec $ \uri -> do ctx <- mkRpcCtx uri - hdr <- getBlockAtNumber ctx 0 - return hdr + getBlockAtNumber ctx 0 -- | Run reth node with chain-spec file at default port 8545 and execute an -- action with the node URI. @@ -107,7 +106,7 @@ withRethNode cid specfile act = withCreateProcess runProc $ \_stdin _stdout _stderr _ph -> do recoverAll policy $ \_ -> do hPutStrLn stderr $ "Waiting for reth node for chain " <> show cid <> " to start..." - uri <- fromText (fromString ("http://localhost:" <> rethPort)) + uri <- fromTextM (fromString ("http://localhost:" <> rethPort)) r <- act uri hFlush stdout return r diff --git a/cwtools/header-dump/Main.hs b/cwtools/header-dump/Main.hs index d880c5c228..b80a704a49 100644 --- a/cwtools/header-dump/Main.hs +++ b/cwtools/header-dump/Main.hs @@ -246,9 +246,9 @@ validateConfig o = do checkIfValidChain (_configChainId o) mapM_ (validateDirectory "database") (_configDatabasePath o) when (_configValidate o && isJust (_configChainId o)) - $ throwError $ "validation (--validate) can only be used if no particular chain is selected" + $ throwError "validation (--validate) can only be used if no particular chain is selected" where - chains = chainIds $ _configChainwebVersion o + chains = withVersion (_configChainwebVersion o) chainIds checkIfValidChain Nothing = return () checkIfValidChain (Just cid) = unless (HS.member cid chains) diff --git a/cwtools/known-graphs/Main.hs b/cwtools/known-graphs/Main.hs index df8b8559fb..40c0f4f87e 100644 --- a/cwtools/known-graphs/Main.hs +++ b/cwtools/known-graphs/Main.hs @@ -24,7 +24,7 @@ import System.Environment main :: IO () main = do args <- getArgs - graphs <- traverse (fromText . T.pack) args + graphs <- traverse (fromTextM . T.pack) args BL8.putStrLn $ encode $ knownChainGraph <$> graphs instance ToJSON KnownGraph where diff --git a/cwtools/run-nodes/Main.hs b/cwtools/run-nodes/Main.hs index 7d5c652d9c..148a905bcb 100644 --- a/cwtools/run-nodes/Main.hs +++ b/cwtools/run-nodes/Main.hs @@ -43,7 +43,7 @@ pNodes = option auto pVersion :: Parser ChainwebVersion pVersion = option (findKnownVersion =<< textReader) (long "chainweb-version" <> metavar "CHAINWEB_VERSION" - <> value (fastForkingCpmTestVersion petersenChainGraph) + <> value (barebonesTestVersion petersenChainGraph) <> help "Chainweb Version to run the Nodes with (default: timedCPM-petersen)") pConfig :: Parser FilePath diff --git a/cwtools/txstream/Main.hs b/cwtools/txstream/Main.hs index 5957ae393f..93ad112d78 100644 --- a/cwtools/txstream/Main.hs +++ b/cwtools/txstream/Main.hs @@ -26,6 +26,7 @@ -- module Main (main) where +import Chainweb.BlockHash import Chainweb.BlockHeader import Chainweb.BlockHeaderDB.RemoteDB import Chainweb.BlockHeight @@ -85,7 +86,7 @@ defaultConfig = Config { _configLogHandle = Y.StdOut , _configLogLevel = Y.Info , _configChainwebVersion = RecapDevelopment - , _configChainId = someChainId RecapDevelopment + , _configChainId = withVersion RecapDevelopment someChainId , _configNode = HostAddress (unsafeHostnameFromText "us1.tn1.chainweb.com") 443 , _configPretty = True , _configOutputs = True @@ -151,7 +152,6 @@ hostAddressBaseUrl h = BaseUrl devNetDb :: Config -> Manager -> LogFunction -> IO RemoteDb devNetDb c mgr l = mkDb - (_configChainwebVersion c) (_configChainId c) mgr l @@ -160,25 +160,22 @@ devNetDb c mgr l = mkDb -- TreeDB mkDb - :: HasChainwebVersion v - => HasChainId cid - => v - -> cid + :: HasChainId cid + => cid -> Manager -> LogFunction -> HostAddress -> IO RemoteDb -mkDb v c mgr logg h = do +mkDb c mgr logg h = do return $ RemoteDb (env mgr h) (ALogFunction logg) - (_chainwebVersion v) (_chainId c) -- -------------------------------------------------------------------------- -- -- Payload Data -run :: Config -> LogFunction -> IO () +run :: HasVersion => Config -> LogFunction -> IO () run config logg = do mgr <- newTlsManager txStream config mgr logg @@ -215,11 +212,12 @@ prettyCommand p (bh, c) = T.decodeUtf8 , "payload" .= either (const $ String $ _cmdPayload c) (id @Value) - (eitherDecodeStrict' $ T.encodeUtf8 $ _cmdPayload $ c) + (eitherDecodeStrict' $ T.encodeUtf8 $ _cmdPayload c) ] txStream - :: Config + :: HasVersion + => Config -> Manager -> LogFunction -> S.Stream (Of (BlockHeight, Command T.Text)) IO () @@ -227,7 +225,7 @@ txStream config mgr logg = do hdb <- liftIO $ devNetDb config mgr logg c <- liftIO $ devNetCut config mgr - let h = _bhwhHash $ _cutHashes c ^?! ix (_configChainId config) + let h = _rankedBlockHashHash $ _cutHashes c ^?! ix (_configChainId config) getBranch hdb mempty (HS.singleton (UpperBound h)) & S.chain (logg @T.Text Debug . sshow) @@ -243,7 +241,7 @@ txStream config mgr logg = do -- -------------------------------------------------------------------------- -- -- PayloadWithOutputs -runOutputs :: Config -> LogFunction -> IO () +runOutputs :: HasVersion => Config -> LogFunction -> IO () runOutputs config logg = do mgr <- newTlsManager txOutputsStream config mgr logg @@ -269,12 +267,13 @@ prettyCommandWithOutputs p (bh, c, o) = T.decodeUtf8 , "payload" .= either (const $ String $ _cmdPayload c) (id @Value) - (eitherDecodeStrict' $ T.encodeUtf8 $ _cmdPayload $ c) + (eitherDecodeStrict' $ T.encodeUtf8 $ _cmdPayload c) , "output" .= J.toJsonViaEncode o ] txOutputsStream - :: Config + :: HasVersion + => Config -> Manager -> LogFunction -> S.Stream (Of (BlockHeight, Command T.Text, CommandResult T.Text)) IO () @@ -282,7 +281,7 @@ txOutputsStream config mgr logg = do hdb <- liftIO $ devNetDb config mgr logg cut <- liftIO $ devNetCut config mgr - let h = _bhwhHash $ _cutHashes cut ^?! ix (_configChainId config) + let h = _rankedBlockHashHash $ _cutHashes cut ^?! ix (_configChainId config) getBranch hdb mempty (HS.singleton (UpperBound h)) & S.chain (logg @T.Text Debug . sshow) @@ -303,55 +302,64 @@ txOutputsStream config mgr logg = do -- -------------------------------------------------------------------------- -- -- Cut -devNetCut :: Config -> Manager -> IO CutHashes -devNetCut config mgr = runClientM (cutGetClient ver) (env mgr node) >>= \case +devNetCut :: HasVersion => Config -> Manager -> IO CutHashes +devNetCut config mgr = runClientM cutGetClient (env mgr node) >>= \case Left e -> error (show e) Right x -> return x where - ver = _configChainwebVersion config node = _configNode config -- -------------------------------------------------------------------------- -- -- Payloads -devNetPayload :: Config -> Manager -> BlockHeight -> BlockPayloadHash -> IO PayloadData -devNetPayload config mgr h x = runClientM (payloadClient ver cid x (Just h)) (env mgr node) >>= \case +devNetPayload + :: HasVersion + => Config + -> Manager + -> BlockHeight + -> BlockPayloadHash + -> IO PayloadData +devNetPayload config mgr h x = runClientM (payloadClient cid x (Just h)) (env mgr node) >>= \case Left e -> error (show e) Right a -> return a where cid = _configChainId config - ver = _configChainwebVersion config node = _configNode config -devNetPayloadWithOutput :: Config -> Manager -> BlockHeight -> BlockPayloadHash -> IO PayloadWithOutputs +devNetPayloadWithOutput + :: HasVersion + => Config + -> Manager + -> BlockHeight + -> BlockPayloadHash + -> IO PayloadWithOutputs devNetPayloadWithOutput config mgr h x - = runClientM (outputsClient ver cid x (Just h)) (env mgr node) >>= \case + = runClientM (outputsClient cid x (Just h)) (env mgr node) >>= \case Left e -> error (show e) Right a -> return a where cid = _configChainId config - ver = _configChainwebVersion config node = _configNode config -- -------------------------------------------------------------------------- -- -- Main mainWithConfig :: Config -> IO () -mainWithConfig config = withLog $ \logger -> do +mainWithConfig config = withLog $ \logger -> withVersion ver $ do let logg :: LogFunction logg = logFunction $ logger & addLabel ("host", toText $ _configNode config) & addLabel ("version", toText $ _versionName $ _configChainwebVersion config) & addLabel ("chain", toText $ _configChainId config) liftIO $ do - registerVersion (_configChainwebVersion config) if _configOutputs config then runOutputs config logg else run config logg where + ver = _configChainwebVersion config logconfig = Y.defaultLogConfig - & Y.logConfigLogger . Y.loggerConfigThreshold .~ (_configLogLevel config) + & Y.logConfigLogger . Y.loggerConfigThreshold .~ _configLogLevel config & Y.logConfigBackend . Y.handleBackendConfigHandle .~ _configLogHandle config withLog inner = Y.withHandleBackend_ logText (logconfig ^. Y.logConfigBackend) $ \backend -> Y.withLogger (logconfig ^. Y.logConfigLogger) backend inner From d8098d4d93c7ae33e6326586f8cf22085f37bf63 Mon Sep 17 00:00:00 2001 From: Lars Kuhtz Date: Wed, 8 Oct 2025 11:09:24 -0700 Subject: [PATCH 377/378] fix formatting of pragmas --- src/Chainweb/Pact/Backend/Compaction.hs | 32 ++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Chainweb/Pact/Backend/Compaction.hs b/src/Chainweb/Pact/Backend/Compaction.hs index eea05b8429..25f6243828 100644 --- a/src/Chainweb/Pact/Backend/Compaction.hs +++ b/src/Chainweb/Pact/Backend/Compaction.hs @@ -1,19 +1,19 @@ -{-# LANGUAGE BangPatterns#-} -{-# LANGUAGE CPP#-} -{-# LANGUAGE DeriveAnyClass#-} -{-# LANGUAGE DeriveGeneric#-} -{-# LANGUAGE DerivingStrategies#-} -{-# LANGUAGE DuplicateRecordFields#-} -{-# LANGUAGE FlexibleContexts#-} -{-# LANGUAGE GeneralizedNewtypeDeriving#-} -{-# LANGUAGE ImportQualifiedPost#-} -{-# LANGUAGE LambdaCase#-} -{-# LANGUAGE NumericUnderscores#-} -{-# LANGUAGE OverloadedRecordDot#-} -{-# LANGUAGE OverloadedStrings#-} -{-# LANGUAGE PackageImports#-} -{-# LANGUAGE ScopedTypeVariables#-} -{-# LANGUAGE TypeApplications#-} +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedRecordDot #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PackageImports #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} module Chainweb.Pact.Backend.Compaction From bbf23a87e6d5850bef3e71b0470153f47c7a54a7 Mon Sep 17 00:00:00 2001 From: Edmund Noble Date: Thu, 25 Sep 2025 15:18:44 -0400 Subject: [PATCH 378/378] Fix broken SPV oracles Change-Id: Id0000000fbd4c58b18ba9e4063c224da92b97b43 --- src/Chainweb/Pact/Backend/ChainwebPactDb.hs | 2 +- src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs | 7 +++---- src/Chainweb/Pact4/Backend/ChainwebPactDb.hs | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs index 1d389d458c..6a410d67ba 100644 --- a/src/Chainweb/Pact/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact/Backend/ChainwebPactDb.hs @@ -261,7 +261,7 @@ chainwebPactBlockDb env = ChainwebPactDb , consult = \(Parent hsh) -> do throwOnDbError (lookupBlockHash (_blockHandlerDb env) hsh) <&> \case Nothing -> False - Just rootHeight -> rootHeight > currentHeight + Just rootHeight -> rootHeight < currentHeight } let spv = pactSPV headerOracle r <- liftIO $ kont pactDb spv diff --git a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs index 63bca1349f..f5fd5bd338 100644 --- a/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs +++ b/src/Chainweb/Pact/PactService/Pact4/ExecBlock.hs @@ -50,6 +50,7 @@ import Chainweb.Pact.Types (ServiceEnv(..), Transactions (..), bctxParentCreatio import Chainweb.Pact4.Backend.ChainwebPactDb import Chainweb.Pact4.ModuleCache import Chainweb.Pact4.NoCoinbase +import Chainweb.Pact4.SPV qualified as Pact4 import Chainweb.Pact4.Transaction qualified as Pact4 import Chainweb.Pact4.TransactionExec qualified as Pact4 import Chainweb.Pact4.Types @@ -505,9 +506,7 @@ applyPactCmd logger serviceEnv blockEnv txIdxInBlock miner cmd = StateT $ \(T2 m if _bctxIsGenesis bCtx then liftIO $! Pact4.applyGenesisCmd logger pactDb Pact4.noSPVSupport bCtx gasLimitedCmd else do - -- FIXME - -- let bhdb = view psBlockHeaderDb serviceEnv - -- let spv = Pact4.pactSPV bhdb (_parentHeader parent) + let spv = Pact4.pactSPV (_cpHeaderOracle $ _benvDbEnv blockEnv) bCtx let txTimeout io = do logFunctionText logger Debug $ "txTimeLimit was not set - defaulting to a function of the block gas limit" @@ -516,7 +515,7 @@ applyPactCmd logger serviceEnv blockEnv txIdxInBlock miner cmd = StateT $ \(T2 m liftIO $ txTimeout $ Pact4.applyCmd logger -- FIXME spv - blockEnv miner gasModel txIdxInBlock undefined gasLimitedCmd initialGas mcache + blockEnv miner gasModel txIdxInBlock spv gasLimitedCmd initialGas mcache pure $ T2 r c if _bctxIsGenesis bCtx diff --git a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs index 94b26f7c62..77e7c7a535 100644 --- a/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs +++ b/src/Chainweb/Pact4/Backend/ChainwebPactDb.hs @@ -10,7 +10,6 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ImportQualifiedPost #-} --- TODO pact5: fix the orphan PactDbFor instance {-# OPTIONS_GHC -Wno-orphans #-} {-# LANGUAGE ViewPatterns #-} @@ -918,6 +917,6 @@ headerOracleForBlock env = HeaderOracle { consult = \(Parent blkHash) -> do lookupBlockHash (_blockHandlerDb env) blkHash <&> \case Nothing -> False - Just rootHeight -> rootHeight > _blockHandlerBlockHeight env + Just rootHeight -> rootHeight < _blockHandlerBlockHeight env , chain = _blockHandlerChainId env }